tahamajs/RR / Codes /django_api /static /dashboard.html
tahamajs's picture
download
raw
22.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Efficient Transformer Toolkit – Live Dashboard</title>
<script src="https://unpkg.com/plotly.js@2.35.2/dist/plotly.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/xterm@4.18.0/css/xterm.css" />
<script src="https://unpkg.com/xterm@4.18.0/lib/xterm.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Segoe UI', sans-serif; height: 100vh; background: #1e1e2f; color: #ccc; }
.login-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: #1e1e2f; display: flex; align-items: center; justify-content: center; z-index: 1000;
}
.login-box {
background: #2d2d44; padding: 2rem; border-radius: 8px; width: 360px;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
.login-box h2 { color: #fff; margin-bottom: 1.5rem; text-align: center; }
.login-box input { width: 100%; padding: 0.7rem; margin-bottom: 1rem; border-radius: 4px; border: 1px solid #555; background: #3b3b55; color: #ddd; }
.login-box button { width: 100%; padding: 0.7rem; background: #7baeff; border: none; border-radius: 4px; color: #fff; font-weight: bold; cursor: pointer; }
.login-box .error { color: #ff6b6b; font-size: 0.85rem; margin-bottom: 0.5rem; }
.app { display: flex; height: 100vh; }
.sidebar { width: 340px; background: #2d2d44; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.8rem; }
.sidebar h2 { color: #fff; margin-bottom: 0.3rem; font-size: 1.3rem; display: flex; justify-content: space-between; align-items: center; }
.sidebar label { font-size: 0.85rem; color: #aaa; margin-bottom: 2px; display: block; }
select, input { width: 100%; padding: 0.5rem; border-radius: 4px; border: 1px solid #555; background: #3b3b55; color: #ddd; font-size: 0.9rem; }
select:focus, input:focus { border-color: #7baeff; outline: none; }
.btn { padding: 0.6rem 1rem; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; color: #fff; }
.btn-start { background: #28a745; }
.btn-stop { background: #dc3545; }
.btn-update { background: #ffc107; color: #000; }
.btn-logout { background: #6c757d; font-size: 0.8rem; padding: 0.3rem 0.6rem; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.main { flex: 1; display: flex; flex-direction: column; }
.top-bar { background: #2d2d44; padding: 0.8rem 1.5rem; display: flex; align-items: center; gap: 1rem; }
.progress-container { flex: 1; background: #444; border-radius: 10px; height: 20px; margin: 0 1rem; position: relative; }
.progress-bar { height: 100%; background: #28a745; border-radius: 10px; width: 0%; transition: width 0.3s; }
.progress-text { position: absolute; left: 50%; transform: translateX(-50%); color: #fff; font-size: 0.75rem; line-height: 20px; white-space: nowrap; }
.gpu-info { display: flex; gap: 1rem; align-items: center; font-size: 0.85rem; }
.panels { flex: 1; display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; gap: 0.8rem; padding: 0.8rem; overflow-y: auto; }
.panel { background: #2d2d44; border-radius: 6px; padding: 1rem; display: flex; flex-direction: column; }
.panel h3 { margin-bottom: 0.6rem; font-size: 1rem; color: #fff; }
.log-viewer { grid-column: span 2; overflow-y: auto; background: #1a1a2e; color: #0f0; font-family: monospace; font-size: 0.8rem; padding: 0.5rem; height: 200px; white-space: pre-wrap; }
.job-list { font-size: 0.8rem; max-height: 200px; overflow-y: auto; background: #1a1a2e; padding: 0.3rem; border-radius: 4px; margin-top: 0.5rem; }
.job-item { padding: 0.3rem 0.4rem; cursor: pointer; border-bottom: 1px solid #444; display: flex; justify-content: space-between; }
.job-item:hover { background: #3b3b60; }
.status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; }
.queued { background: yellow; }
.running { background: cyan; }
.completed { background: lime; }
.failed { background: red; }
.stopped { background: orange; }
.toast { position: fixed; top: 1rem; right: 1rem; padding: 0.8rem 1.2rem; border-radius: 6px; color: #fff; z-index: 2000; animation: fadeIn 0.3s; }
.toast-success { background: #28a745; }
.toast-error { background: #dc3545; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
</style>
</head>
<body>
<!-- Login overlay -->
<div class="login-overlay" id="loginOverlay">
<div class="login-box">
<h2 id="authTitle">🔐 Login</h2>
<div class="error" id="authError"></div>
<div id="loginFields">
<input type="text" id="loginUsername" placeholder="Username" autocomplete="username">
<input type="password" id="loginPassword" placeholder="Password" autocomplete="current-password">
<button onclick="doLogin()">Sign In</button>
</div>
<div id="registerFields" style="display:none;">
<input type="text" id="regUsername" placeholder="Choose a username" autocomplete="username">
<input type="password" id="regPassword" placeholder="Create password">
<input type="password" id="regPassword2" placeholder="Confirm password">
<button onclick="doRegister()">Create Account</button>
</div>
<div style="margin-top:1rem; text-align:center; font-size:0.85rem; color:#aaa;">
<span id="toggleText">Don't have an account?</span>
<a href="#" id="toggleLink" onclick="toggleAuth()">Create one</a>
</div>
</div>
</div>
<!-- Main app -->
<div class="app" id="app" style="display:none;">
<div class="sidebar">
<h2>🚀 Experiment Config
<button class="btn btn-logout" onclick="doLogout()">Logout</button>
</h2>
<label>Model</label>
<select id="modelSelect"></select>
<label>Task</label>
<select id="taskSelect"></select>
<label>Attention Type</label>
<select id="attentionSelect"></select>
<label>Training Method</label>
<select id="methodSelect">
<option value="baseline">Baseline</option>
<option value="lora">LoRA</option>
<option value="rdrop">RDrop</option>
<option value="r3f">R3F</option>
</select>
<label>Epochs</label>
<input type="number" id="epochs" value="3" min="1" max="100">
<label>Batch Size</label>
<input type="number" id="batchSize" value="16" min="1" max="256">
<label>Learning Rate</label>
<input type="number" id="learningRate" value="2e-5" step="1e-6" min="1e-8" max="1">
<label>Compression Rank</label>
<input type="number" id="rank" value="64" min="1" max="512">
<label>Max Seq Length</label>
<input type="number" id="maxLen" value="128" min="8" max="1024">
<div style="display:flex; gap:0.5rem;">
<button class="btn btn-start" id="startBtn">▶ Start Training</button>
<button class="btn btn-stop" id="stopBtn" disabled>⏹ Stop</button>
</div>
<div style="display:flex; gap:0.5rem;">
<button class="btn btn-update" id="updateBtn" disabled>✎ Update Config</button>
</div>
<div class="job-list" id="jobList"><div style="color:#aaa">Loading jobs...</div></div>
</div>
<div class="main">
<div class="top-bar">
<span id="jobStatus" style="font-weight:bold;">⚪ Idle</span>
<div class="progress-container">
<div class="progress-bar" id="progressBar"></div>
<div class="progress-text" id="progressText">0%</div>
</div>
<div class="gpu-info" id="gpuInfo">GPU: N/A</div>
</div>
<div class="panels">
<div class="panel"><h3>Training Loss</h3><div id="lossPlot" style="flex:1;"></div></div>
<div class="panel"><h3>Evaluation Metrics</h3><div id="evalPlot" style="flex:1;"></div></div>
<div class="panel log-viewer"><h3>Logs</h3><div id="logContent" style="flex:1;"></div></div>
<div class="panel"><h3>System</h3><div id="systemInfo">CPU: -- | Mem: --</div></div>
<div class="panel" style="grid-column: span 2;">
<h3>Live Terminal (stdout/stderr)</h3>
<pre id="terminalOutput" style="background:#000; color:#0f0; height:200px; overflow-y:auto; padding:5px; font-family:monospace; font-size:12px;"></pre>
</div>
</div>
</div>
</div>
<script>
// ======================== AUTH & STATE ========================
const API_BASE = 'http://127.0.0.1:8000/api';
const WS_BASE = 'ws://127.0.0.1:8000/ws/jobs';
let authToken = localStorage.getItem('authToken');
let currentJobId = null;
let ws = null;
let plotData = { lossSteps: [], losses: [], evalSteps: [], evalLosses: [] };
let reconnectTimeout = null;
let jobListInterval = null;
let systemInterval = null;
// Toast notifications
function showToast(msg, type = 'success') {
const t = document.createElement('div');
t.className = `toast toast-${type}`;
t.textContent = msg;
document.body.appendChild(t);
setTimeout(() => t.remove(), 3000);
}
// ======================== AUTH FUNCTIONS ========================
async function doLogin() {
const username = document.getElementById('loginUsername').value.trim();
const password = document.getElementById('loginPassword').value;
if (!username || !password) {
document.getElementById('authError').textContent = 'Username and password required';
return;
}
try {
const resp = await fetch(`${API_BASE}/auth/login/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
if (!resp.ok) throw new Error('Invalid credentials');
const data = await resp.json();
authToken = data.token;
localStorage.setItem('authToken', authToken);
document.getElementById('loginOverlay').style.display = 'none';
document.getElementById('app').style.display = 'flex';
initApp();
showToast('Logged in successfully');
} catch (e) {
document.getElementById('authError').textContent = e.message;
}
}
let authMode = 'login';
function toggleAuth() {
if (authMode === 'login') {
authMode = 'register';
document.getElementById('authTitle').textContent = '📝 Create Account';
document.getElementById('loginFields').style.display = 'none';
document.getElementById('registerFields').style.display = 'block';
document.getElementById('toggleText').textContent = 'Already have an account?';
document.getElementById('toggleLink').textContent = 'Sign In';
} else {
authMode = 'login';
document.getElementById('authTitle').textContent = '🔐 Login';
document.getElementById('loginFields').style.display = 'block';
document.getElementById('registerFields').style.display = 'none';
document.getElementById('toggleText').textContent = "Don't have an account?";
document.getElementById('toggleLink').textContent = 'Create one';
}
}
async function doRegister() {
const username = document.getElementById('regUsername').value.trim();
const password = document.getElementById('regPassword').value;
const password2 = document.getElementById('regPassword2').value;
if (!username || !password) {
document.getElementById('authError').textContent = 'All fields are required';
return;
}
if (password !== password2) {
document.getElementById('authError').textContent = 'Passwords do not match';
return;
}
try {
const resp = await fetch(`${API_BASE}/auth/register/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
if (!resp.ok) {
const data = await resp.json();
throw new Error(data.error || 'Registration failed');
}
const data = await resp.json();
authToken = data.token;
localStorage.setItem('authToken', authToken);
document.getElementById('loginOverlay').style.display = 'none';
document.getElementById('app').style.display = 'flex';
initApp();
showToast('Account created successfully');
} catch (e) {
document.getElementById('authError').textContent = e.message;
}
}
function doLogout() {
localStorage.removeItem('authToken');
authToken = null;
if (ws) { ws.close(); ws = null; }
if (reconnectTimeout) clearTimeout(reconnectTimeout);
if (jobListInterval) clearInterval(jobListInterval);
if (systemInterval) clearInterval(systemInterval);
location.reload();
}
function authHeaders() {
return authToken ? { 'Authorization': `Token ${authToken}` } : {};
}
// ======================== API HELPERS ========================
async function apiGet(url) {
const res = await fetch(`${API_BASE}${url}`, { headers: authHeaders() });
if (res.status === 401) { doLogout(); return null; }
if (!res.ok) throw new Error(`API error: ${res.status}`);
return res.json();
}
async function apiPost(url, body = {}) {
const res = await fetch(`${API_BASE}${url}`, {
method: 'POST',
headers: { ...authHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (res.status === 401) { doLogout(); return null; }
if (!res.ok) throw new Error(`API error: ${res.status}`);
return res.json();
}
// ======================== CONFIG LOADING ========================
async function loadConfigOptions() {
try {
const [models, tasks, attentions] = await Promise.all([
apiGet('/config/models/'),
apiGet('/config/tasks/'),
apiGet('/config/attentions/')
]);
if (models) populateSelect('modelSelect', models.models, 'distilbert-base-uncased');
if (tasks) populateSelect('taskSelect', tasks.tasks, 'mrpc');
if (attentions) populateSelect('attentionSelect', attentions.attentions, 'full');
} catch (e) { console.error('Config load error:', e); }
}
function populateSelect(id, options, defaultVal) {
const sel = document.getElementById(id);
sel.innerHTML = '';
options.forEach(o => {
const opt = document.createElement('option');
opt.value = o;
opt.text = o;
sel.appendChild(opt);
});
if (defaultVal && options.includes(defaultVal)) sel.value = defaultVal;
}
// ======================== JOB LIST ========================
async function loadJobList() {
try {
const data = await apiGet('/experiments/');
if (!data) return;
const container = document.getElementById('jobList');
if (!data.length) {
container.innerHTML = '<div style="color:#aaa">No jobs yet</div>';
return;
}
container.innerHTML = data.slice(0, 20).map(j => `
<div class="job-item" onclick="selectJob('${j.id}')">
<span><span class="status-dot ${j.status}"></span>${j.name || j.id.slice(0, 8)}</span>
<span style="color:#888;font-size:0.75rem;">${j.status}</span>
</div>
`).join('');
} catch (e) { console.error('Job list error', e); }
}
// ======================== JOB MANAGEMENT ========================
function getConfig() {
return {
model_name: document.getElementById('modelSelect').value,
task_name: document.getElementById('taskSelect').value,
attention_type: document.getElementById('attentionSelect').value,
method: document.getElementById('methodSelect').value,
epochs: parseInt(document.getElementById('epochs').value),
batch_size: parseInt(document.getElementById('batchSize').value),
learning_rate: parseFloat(document.getElementById('learningRate').value),
compression_rank: parseInt(document.getElementById('rank').value),
max_seq_length: parseInt(document.getElementById('maxLen').value),
};
}
async function startTraining() {
try {
const config = getConfig();
const data = await apiPost('/train/', config);
if (data) {
selectJob(data.id);
showToast('Training started!');
}
} catch (e) {
showToast('Failed to start training', 'error');
}
}
async function stopTraining() {
if (!currentJobId) return;
try {
await apiPost(`/jobs/${currentJobId}/stop/`);
showToast('Stop signal sent');
} catch (e) {
showToast('Failed to stop job', 'error');
}
}
async function updateConfig() {
if (!currentJobId) return;
try {
const config = getConfig();
await fetch(`${API_BASE}/jobs/${currentJobId}/config/`, {
method: 'PATCH',
headers: { ...authHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
showToast('Config updated');
} catch (e) {
showToast('Failed to update config', 'error');
}
}
// ======================== WEBSOCKET & JOB SELECTION ========================
function connectWebSocket(jobId) {
if (ws) {
ws.close();
ws = null;
}
ws = new WebSocket(`${WS_BASE}/${jobId}/`);
ws.onmessage = handleWSMessage;
ws.onerror = (err) => console.error('WebSocket error', err);
ws.onclose = () => {
console.log('WebSocket closed, will reconnect in 2s');
if (reconnectTimeout) clearTimeout(reconnectTimeout);
reconnectTimeout = setTimeout(() => {
if (currentJobId) connectWebSocket(currentJobId);
}, 2000);
};
}
function resetUI() {
document.getElementById('progressBar').style.width = '0%';
document.getElementById('progressText').innerText = '0%';
plotData = { lossSteps: [], losses: [], evalSteps: [], evalLosses: [] };
Plotly.newPlot('lossPlot', [{ x: [], y: [], type: 'scatter', mode: 'lines+markers', name: 'Loss' }],
{ margin: { t: 10 }, paper_bgcolor: '#2d2d44', plot_bgcolor: '#2d2d44', font: { color: '#ccc' } });
Plotly.newPlot('evalPlot', [{ x: [], y: [], type: 'scatter', mode: 'lines+markers', name: 'Eval Loss' }],
{ margin: { t: 10 }, paper_bgcolor: '#2d2d44', plot_bgcolor: '#2d2d44', font: { color: '#ccc' } });
document.getElementById('logContent').innerText = '';
}
async function selectJob(jobId) {
if (currentJobId === jobId) return;
currentJobId = jobId;
document.getElementById('startBtn').disabled = true;
document.getElementById('stopBtn').disabled = false;
document.getElementById('updateBtn').disabled = false;
resetUI();
connectWebSocket(jobId);
try {
const job = await apiGet(`/jobs/${jobId}/`);
if (job) {
document.getElementById('jobStatus').innerHTML = `<span class="status-dot ${job.status}"></span>${job.status}`;
if (job.logs) document.getElementById('logContent').innerText = job.logs;
if (job.current_step && job.total_steps) {
const pct = (job.current_step / job.total_steps) * 100;
document.getElementById('progressBar').style.width = pct + '%';
document.getElementById('progressText').innerText = Math.round(pct) + '%';
}
}
} catch (e) { console.error('Failed to load job details', e); }
}
// ======================== WEBSOCKET MESSAGE HANDLER ========================
function handleWSMessage(event) {
try {
const msg = JSON.parse(event.data);
switch (msg.type) {
case 'status':
document.getElementById('jobStatus').innerHTML = `<span class="status-dot ${msg.status}"></span>${msg.status}`;
if (['completed', 'stopped', 'failed'].includes(msg.status)) {
document.getElementById('startBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
document.getElementById('updateBtn').disabled = true;
if (ws) { ws.close(); ws = null; }
if (reconnectTimeout) clearTimeout(reconnectTimeout);
loadJobList();
}
break;
case 'terminal':
const termEl = document.getElementById('terminalOutput');
termEl.innerText += msg.data + '\n';
termEl.scrollTop = termEl.scrollHeight;
break;
case 'log':
const logEl = document.getElementById('logContent');
logEl.innerText += msg.data + '\n';
logEl.scrollTop = logEl.scrollHeight;
break;
case 'metrics':
if (msg.data.loss !== undefined) {
plotData.lossSteps.push(plotData.lossSteps.length + 1);
plotData.losses.push(msg.data.loss);
Plotly.update('lossPlot', { x: [plotData.lossSteps], y: [plotData.losses] }, {}, [0]);
}
if (msg.data.eval_loss !== undefined) {
plotData.evalSteps.push(plotData.evalSteps.length + 1);
plotData.evalLosses.push(msg.data.eval_loss);
Plotly.update('evalPlot', { x: [plotData.evalSteps], y: [plotData.evalLosses] }, {}, [0]);
}
break;
case 'progress':
const pct = Math.min(100, Math.round(msg.progress || 0));
document.getElementById('progressBar').style.width = pct + '%';
document.getElementById('progressText').innerText = pct + '% (Remaining: ' + formatTime(msg.eta_seconds) + ')';
break;
default:
// ignore
}
} catch (e) {
console.error('Failed to parse WebSocket message', e);
}
}
function formatTime(seconds) {
if (!seconds || seconds < 0) return '--';
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = Math.round(seconds % 60);
return `${h}h ${m}m ${s}s`;
}
// ======================== SYSTEM POLLING ========================
async function pollSystem() {
try {
const sys = await apiGet('/system/status/');
if (sys) document.getElementById('systemInfo').innerHTML = `CPU: ${sys.cpu}% | Mem: ${sys.memory}%`;
const gpu = await apiGet('/system/gpu/');
if (gpu && gpu.cuda_available) {
const used = gpu.memory_allocated || 0;
const total = gpu.memory_total || 0;
document.getElementById('gpuInfo').innerHTML = `GPU: ${used.toFixed(2)} / ${total.toFixed(2)} GB`;
} else {
document.getElementById('gpuInfo').innerHTML = 'GPU: N/A';
}
} catch (e) { console.warn('System poll error', e); }
}
// ======================== INITIALIZATION ========================
async function initApp() {
await loadConfigOptions();
await loadJobList();
jobListInterval = setInterval(loadJobList, 10000);
systemInterval = setInterval(pollSystem, 3000);
pollSystem();
}
// Check login state on load
if (authToken) {
document.getElementById('loginOverlay').style.display = 'none';
document.getElementById('app').style.display = 'flex';
initApp();
} else {
document.getElementById('loginOverlay').style.display = 'flex';
document.getElementById('app').style.display = 'none';
}
// Attach event listeners
document.getElementById('startBtn').addEventListener('click', startTraining);
document.getElementById('stopBtn').addEventListener('click', stopTraining);
document.getElementById('updateBtn').addEventListener('click', updateConfig);
document.getElementById('loginPassword')?.addEventListener('keydown', (e) => {
if (e.key === 'Enter') doLogin();
});
</script>
</body>
</html>

Xet Storage Details

Size:
22.7 kB
·
Xet hash:
0e2cacfbd50f86351a217bc1c5dd70e76d2b89f0c48fb74ba6f4c9687bf1f259

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.