Complete dashboard UI for controlling and monitoring optimization runs. Backend API (Flask): - RESTful endpoints for study management - Start/stop/resume optimization runs - Real-time status monitoring - Configuration management - Visualization data endpoints Frontend (HTML/CSS/JS + Chart.js): - Modern gradient design with cards and charts - Study list sidebar with metadata - Active optimizations monitoring (5s polling) - Interactive charts (progress, design vars, constraints) - Trial history table - New optimization modal - Resume/delete study actions Features: - List all studies with trial counts - View detailed study results - Start new optimizations from UI - Resume existing studies with additional trials - Real-time progress monitoring - Delete unwanted studies - Chart.js visualizations (progress, DVs, constraints) - Configuration file selection - Study metadata tracking Usage: python dashboard/start_dashboard.py # Opens browser to http://localhost:5000 Dependencies: flask, flask-cors (auto-installed) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
508 lines
15 KiB
JavaScript
508 lines
15 KiB
JavaScript
// Atomizer Dashboard - Frontend JavaScript
|
|
|
|
const API_BASE = 'http://localhost:5000/api';
|
|
let currentStudy = null;
|
|
let charts = {
|
|
progress: null,
|
|
designVars: null,
|
|
constraints: null
|
|
};
|
|
|
|
// Initialize dashboard
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('Atomizer Dashboard loaded');
|
|
refreshStudies();
|
|
startActiveOptimizationsPolling();
|
|
});
|
|
|
|
// ====================
|
|
// Studies Management
|
|
// ====================
|
|
|
|
async function refreshStudies() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/studies`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
renderStudiesList(data.studies);
|
|
} else {
|
|
showError('Failed to load studies: ' + data.error);
|
|
}
|
|
} catch (error) {
|
|
showError('Connection error: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function renderStudiesList(studies) {
|
|
const container = document.getElementById('studiesList');
|
|
|
|
if (!studies || studies.length === 0) {
|
|
container.innerHTML = '<p class="empty">No studies found</p>';
|
|
return;
|
|
}
|
|
|
|
const html = studies.map(study => `
|
|
<div class="study-item" onclick="loadStudy('${study.study_name}')">
|
|
<div class="study-name">${study.study_name}</div>
|
|
<div class="study-info">
|
|
<span class="badge">${study.total_trials || 0} trials</span>
|
|
${study.resume_count > 0 ? `<span class="badge-secondary">Resumed ${study.resume_count}x</span>` : ''}
|
|
</div>
|
|
<div class="study-date">${formatDate(study.created_at)}</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
async function loadStudy(studyName) {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/studies/${studyName}`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
currentStudy = studyName;
|
|
displayStudyDetails(data);
|
|
} else {
|
|
showError('Failed to load study: ' + data.error);
|
|
}
|
|
} catch (error) {
|
|
showError('Connection error: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function displayStudyDetails(data) {
|
|
// Hide welcome, show details
|
|
document.getElementById('welcomeScreen').style.display = 'none';
|
|
document.getElementById('studyDetails').style.display = 'block';
|
|
|
|
// Update header
|
|
document.getElementById('studyTitle').textContent = data.study_name;
|
|
document.getElementById('studyMeta').textContent =
|
|
`Created: ${formatDate(data.metadata.created_at)} | Config Hash: ${data.metadata.config_hash.substring(0, 8)}`;
|
|
|
|
// Update summary cards
|
|
if (data.summary && data.summary.best_value !== undefined) {
|
|
document.getElementById('bestObjective').textContent = data.summary.best_value.toFixed(4);
|
|
document.getElementById('totalTrials').textContent = data.summary.n_trials || data.history.length;
|
|
|
|
// Best parameters
|
|
const paramsHtml = Object.entries(data.summary.best_params || {})
|
|
.map(([name, value]) => `<div><strong>${name}:</strong> ${value.toFixed(4)}</div>`)
|
|
.join('');
|
|
document.getElementById('bestParams').innerHTML = paramsHtml || '<p>No data</p>';
|
|
}
|
|
|
|
// Render charts
|
|
renderCharts(data.history);
|
|
|
|
// Render history table
|
|
renderHistoryTable(data.history);
|
|
}
|
|
|
|
// ====================
|
|
// Charts
|
|
// ====================
|
|
|
|
async function renderCharts(history) {
|
|
if (!history || history.length === 0) return;
|
|
|
|
// Get visualization data
|
|
try {
|
|
const response = await fetch(`${API_BASE}/results/visualization/${currentStudy}`);
|
|
const data = await response.json();
|
|
|
|
if (!data.success) return;
|
|
|
|
// Progress Chart
|
|
renderProgressChart(data.trials, data.total_objectives, data.running_best);
|
|
|
|
// Design Variables Chart
|
|
renderDesignVarsChart(data.trials, data.design_variables);
|
|
|
|
// Constraints Chart
|
|
renderConstraintsChart(data.trials, data.constraints);
|
|
} catch (error) {
|
|
console.error('Error rendering charts:', error);
|
|
}
|
|
}
|
|
|
|
function renderProgressChart(trials, objectives, runningBest) {
|
|
const ctx = document.getElementById('progressChart').getContext('2d');
|
|
|
|
if (charts.progress) {
|
|
charts.progress.destroy();
|
|
}
|
|
|
|
charts.progress = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: trials,
|
|
datasets: [
|
|
{
|
|
label: 'Total Objective',
|
|
data: objectives,
|
|
borderColor: '#3b82f6',
|
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
tension: 0.1
|
|
},
|
|
{
|
|
label: 'Running Best',
|
|
data: runningBest,
|
|
borderColor: '#10b981',
|
|
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
|
borderWidth: 2,
|
|
tension: 0.1
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'top'
|
|
},
|
|
tooltip: {
|
|
mode: 'index',
|
|
intersect: false
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: 'Trial Number'
|
|
}
|
|
},
|
|
y: {
|
|
title: {
|
|
display: true,
|
|
text: 'Objective Value'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderDesignVarsChart(trials, designVars) {
|
|
const ctx = document.getElementById('designVarsChart').getContext('2d');
|
|
|
|
if (charts.designVars) {
|
|
charts.designVars.destroy();
|
|
}
|
|
|
|
const colors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'];
|
|
const datasets = Object.entries(designVars).map(([name, values], index) => ({
|
|
label: name,
|
|
data: values,
|
|
borderColor: colors[index % colors.length],
|
|
backgroundColor: colors[index % colors.length] + '20',
|
|
tension: 0.1
|
|
}));
|
|
|
|
charts.designVars = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: trials,
|
|
datasets: datasets
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'top'
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: 'Trial Number'
|
|
}
|
|
},
|
|
y: {
|
|
title: {
|
|
display: true,
|
|
text: 'Value'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderConstraintsChart(trials, constraints) {
|
|
const ctx = document.getElementById('constraintsChart').getContext('2d');
|
|
|
|
if (charts.constraints) {
|
|
charts.constraints.destroy();
|
|
}
|
|
|
|
const colors = ['#3b82f6', '#10b981', '#f59e0b'];
|
|
const datasets = Object.entries(constraints).map(([name, values], index) => ({
|
|
label: name,
|
|
data: values,
|
|
borderColor: colors[index % colors.length],
|
|
backgroundColor: colors[index % colors.length] + '20',
|
|
tension: 0.1
|
|
}));
|
|
|
|
charts.constraints = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: trials,
|
|
datasets: datasets
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'top'
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: 'Trial Number'
|
|
}
|
|
},
|
|
y: {
|
|
title: {
|
|
display: true,
|
|
text: 'Value'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// ====================
|
|
// History Table
|
|
// ====================
|
|
|
|
function renderHistoryTable(history) {
|
|
const tbody = document.querySelector('#historyTable tbody');
|
|
|
|
if (!history || history.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="5">No trials yet</td></tr>';
|
|
return;
|
|
}
|
|
|
|
const html = history.map(trial => `
|
|
<tr>
|
|
<td>${trial.trial_number}</td>
|
|
<td>${trial.total_objective.toFixed(4)}</td>
|
|
<td>${formatDesignVars(trial.design_variables)}</td>
|
|
<td>${formatConstraints(trial.constraints)}</td>
|
|
<td>${formatDateTime(trial.timestamp)}</td>
|
|
</tr>
|
|
`).join('');
|
|
|
|
tbody.innerHTML = html;
|
|
}
|
|
|
|
function formatDesignVars(vars) {
|
|
return Object.entries(vars)
|
|
.map(([name, value]) => `${name}=${value.toFixed(4)}`)
|
|
.join(', ');
|
|
}
|
|
|
|
function formatConstraints(constraints) {
|
|
return Object.entries(constraints)
|
|
.map(([name, value]) => `${name}=${value.toFixed(4)}`)
|
|
.join(', ');
|
|
}
|
|
|
|
// ====================
|
|
// New Optimization
|
|
// ====================
|
|
|
|
function showNewOptimizationModal() {
|
|
document.getElementById('newOptimizationModal').style.display = 'flex';
|
|
}
|
|
|
|
function closeNewOptimizationModal() {
|
|
document.getElementById('newOptimizationModal').style.display = 'none';
|
|
}
|
|
|
|
async function startOptimization() {
|
|
const studyName = document.getElementById('newStudyName').value || `study_${Date.now()}`;
|
|
const nTrials = parseInt(document.getElementById('newTrials').value) || 50;
|
|
const configPath = document.getElementById('newConfigPath').value;
|
|
const resume = document.getElementById('resumeExisting').checked;
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/optimization/start`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
study_name: studyName,
|
|
n_trials: nTrials,
|
|
config_path: configPath,
|
|
resume: resume
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showSuccess(`Optimization "${studyName}" started successfully!`);
|
|
closeNewOptimizationModal();
|
|
setTimeout(refreshStudies, 1000);
|
|
} else {
|
|
showError('Failed to start optimization: ' + data.error);
|
|
}
|
|
} catch (error) {
|
|
showError('Connection error: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// ====================
|
|
// Active Optimizations Polling
|
|
// ====================
|
|
|
|
function startActiveOptimizationsPolling() {
|
|
setInterval(updateActiveOptimizations, 5000); // Poll every 5 seconds
|
|
updateActiveOptimizations();
|
|
}
|
|
|
|
async function updateActiveOptimizations() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/optimization/status`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
renderActiveOptimizations(data.active_optimizations);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to update active optimizations:', error);
|
|
}
|
|
}
|
|
|
|
function renderActiveOptimizations(optimizations) {
|
|
const container = document.getElementById('activeOptimizations');
|
|
|
|
const entries = Object.entries(optimizations);
|
|
if (entries.length === 0) {
|
|
container.innerHTML = '<p class="empty">No active optimizations</p>';
|
|
return;
|
|
}
|
|
|
|
const html = entries.map(([name, opt]) => `
|
|
<div class="active-item">
|
|
<div class="active-name">${name}</div>
|
|
<div class="active-status status-${opt.status}">${opt.status}</div>
|
|
${opt.status === 'running' ? `
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" style="width: ${(opt.current_trial / opt.n_trials * 100).toFixed(0)}%"></div>
|
|
</div>
|
|
<div class="active-progress">${opt.current_trial || 0} / ${opt.n_trials} trials</div>
|
|
` : ''}
|
|
</div>
|
|
`).join('');
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
// ====================
|
|
// Study Actions
|
|
// ====================
|
|
|
|
async function resumeCurrentStudy() {
|
|
if (!currentStudy) return;
|
|
|
|
const nTrials = prompt('Number of additional trials:', '25');
|
|
if (!nTrials) return;
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/optimization/start`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
study_name: currentStudy,
|
|
n_trials: parseInt(nTrials),
|
|
resume: true
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showSuccess(`Study "${currentStudy}" resumed with ${nTrials} additional trials`);
|
|
} else {
|
|
showError('Failed to resume study: ' + data.error);
|
|
}
|
|
} catch (error) {
|
|
showError('Connection error: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function deleteCurrentStudy() {
|
|
if (!currentStudy) return;
|
|
|
|
if (!confirm(`Are you sure you want to delete study "${currentStudy}"? This cannot be undone.`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/studies/${currentStudy}/delete`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showSuccess(`Study "${currentStudy}" deleted successfully`);
|
|
currentStudy = null;
|
|
document.getElementById('studyDetails').style.display = 'none';
|
|
document.getElementById('welcomeScreen').style.display = 'block';
|
|
refreshStudies();
|
|
} else {
|
|
showError('Failed to delete study: ' + data.error);
|
|
}
|
|
} catch (error) {
|
|
showError('Connection error: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// ====================
|
|
// Utility Functions
|
|
// ====================
|
|
|
|
function formatDate(dateString) {
|
|
if (!dateString) return 'N/A';
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString();
|
|
}
|
|
|
|
function formatDateTime(dateString) {
|
|
if (!dateString) return 'N/A';
|
|
const date = new Date(dateString);
|
|
return date.toLocaleString();
|
|
}
|
|
|
|
function showSuccess(message) {
|
|
// Simple success notification
|
|
alert('✓ ' + message);
|
|
}
|
|
|
|
function showError(message) {
|
|
// Simple error notification
|
|
alert('✗ Error: ' + message);
|
|
console.error(message);
|
|
}
|