Initial commit: MLflow dashboard project
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
118
static/app.js
Normal file
118
static/app.js
Normal file
@@ -0,0 +1,118 @@
|
||||
const API_BASE = window.location.origin;
|
||||
|
||||
function getTrackingUri() {
|
||||
return document.getElementById('trackingUri').value.trim() || null;
|
||||
}
|
||||
|
||||
function formatTime(ts) {
|
||||
if (!ts) return '';
|
||||
const d = new Date(ts);
|
||||
return d.toLocaleDateString('ko-KR') + ' ' + d.toLocaleTimeString('ko-KR', {hour: '2-digit', minute: '2-digit'});
|
||||
}
|
||||
|
||||
async function loadExperiments() {
|
||||
const container = document.getElementById('content');
|
||||
container.innerHTML = '<div class="loading">Loading...</div>';
|
||||
|
||||
const uri = getTrackingUri();
|
||||
const params = uri ? '?tracking_uri=' + encodeURIComponent(uri) : '';
|
||||
|
||||
try {
|
||||
const res = await fetch(API_BASE + '/api/experiments' + params);
|
||||
if (!res.ok) throw new Error('Failed: ' + res.status);
|
||||
const experiments = await res.json();
|
||||
|
||||
if (experiments.length === 0) {
|
||||
container.innerHTML = '<div class="loading">No experiments found.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
experiments.forEach(function(exp) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'exp-card';
|
||||
card.innerHTML =
|
||||
'<div class="exp-header" onclick="toggleExp(this, \'' + exp.experiment_id + '\')">' +
|
||||
'<span class="exp-arrow">▶</span>' +
|
||||
'<span class="exp-name">' + exp.name + '</span>' +
|
||||
'<span class="exp-badge">' + exp.run_count + ' runs</span>' +
|
||||
'</div>' +
|
||||
'<div class="run-list" id="runs-' + exp.experiment_id + '">' +
|
||||
'<div class="loading">Loading runs...</div>' +
|
||||
'</div>';
|
||||
container.appendChild(card);
|
||||
});
|
||||
} catch (e) {
|
||||
container.innerHTML = '<div class="error">Connection failed: ' + e.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleExp(header, expId) {
|
||||
const arrow = header.querySelector('.exp-arrow');
|
||||
const runList = document.getElementById('runs-' + expId);
|
||||
|
||||
if (runList.classList.contains('open')) {
|
||||
runList.classList.remove('open');
|
||||
arrow.classList.remove('open');
|
||||
return;
|
||||
}
|
||||
|
||||
arrow.classList.add('open');
|
||||
runList.classList.add('open');
|
||||
runList.innerHTML = '<div class="loading">Loading runs...</div>';
|
||||
|
||||
const uri = getTrackingUri();
|
||||
const params = uri ? '?tracking_uri=' + encodeURIComponent(uri) : '';
|
||||
|
||||
try {
|
||||
const res = await fetch(API_BASE + '/api/experiments/' + expId + '/runs' + params);
|
||||
const runs = await res.json();
|
||||
|
||||
if (runs.length === 0) {
|
||||
runList.innerHTML = '<div class="run-row" style="color:#888;">No runs</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
runList.innerHTML = '';
|
||||
runs.forEach(function(run) {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'run-row';
|
||||
row.innerHTML =
|
||||
'<span class="run-name">' + (run.run_name || run.run_id.substring(0, 8)) + '</span>' +
|
||||
'<span class="status ' + run.status + '">' + run.status + '</span>' +
|
||||
'<span class="run-time">' + formatTime(run.start_time) + '</span>' +
|
||||
'<div class="btn-group">' +
|
||||
'<button class="btn btn-view" onclick="viewRun(\'' + run.run_id + '\')">View</button>' +
|
||||
'<button class="btn btn-train" onclick="trainRun(\'' + run.run_id + '\')">Train</button>' +
|
||||
'<button class="btn btn-serve" onclick="serveRun(\'' + run.run_id + '\')">Serve</button>' +
|
||||
'</div>';
|
||||
runList.appendChild(row);
|
||||
});
|
||||
} catch (e) {
|
||||
runList.innerHTML = '<div class="error">Failed to load runs</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function viewRun(runId) {
|
||||
const uri = getTrackingUri();
|
||||
const params = uri ? '?tracking_uri=' + encodeURIComponent(uri) : '';
|
||||
try {
|
||||
const res = await fetch(API_BASE + '/api/runs/' + runId + '/mlflow-link' + params);
|
||||
const data = await res.json();
|
||||
window.open(data.url, '_blank');
|
||||
} catch (e) {
|
||||
alert('Failed to get MLflow link');
|
||||
}
|
||||
}
|
||||
|
||||
function trainRun(runId) {
|
||||
alert('Train is not implemented yet.');
|
||||
}
|
||||
|
||||
function serveRun(runId) {
|
||||
alert('Serve: model_uri required. Use Swagger UI (/docs) for now.');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadExperiments();
|
||||
});
|
||||
Reference in New Issue
Block a user