tahamajs's picture
download
raw
16.4 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 – Dashboard + Terminal</title>
<script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm/css/xterm.css" />
<script src="https://cdn.jsdelivr.net/npm/xterm/lib/xterm.js"></script>
<style>
/* same as before – I'll keep it compact */
* { 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; }
.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: auto auto; 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); } }
#terminal-container { height: 300px; background: black; border-radius: 6px; overflow: hidden; }
</style>
</head>
<body>
<!-- Login overlay (same as before) -->
<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">
<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;"><span id="toggleText">Don't have an account?</span> <a href="#" id="toggleLink" onclick="toggleAuth()">Create one</a></div>
</div>
</div>
<div class="app" id="app" style="display:none;">
<!-- Sidebar (same as before) -->
<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></select>
<label>Epochs</label><input type="number" id="epochs" value="3">
<label>Batch Size</label><input type="number" id="batchSize" value="16">
<label>Learning Rate</label><input type="number" id="learningRate" value="2e-5" step="1e-6">
<label>Compression Rank</label><input type="number" id="rank" value="64">
<label>Max Seq Length</label><input type="number" id="maxLen" value="128">
<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><button class="btn btn-update" id="updateBtn" disabled>✎ Update Config</button></div>
<div class="job-list" id="jobList">Loading jobs...</div>
</div>
<div class="main">
<div class="top-bar">
<span id="jobStatus">⚪ 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; height:200px;"></div></div>
<div class="panel"><h3>Evaluation Metrics</h3><div id="evalPlot" style="flex:1; height:200px;"></div></div>
<div class="panel log-viewer"><h3>Logs</h3><div id="logContent" style="flex:1; overflow-y:auto;"></div></div>
<div class="panel"><h3>System</h3><div id="systemInfo">CPU: -- | Mem: --</div></div>
<!-- TERMINAL PANEL -->
<div class="panel" style="grid-column: span 2; display: flex; flex-direction: column;">
<h3>Live Terminal (run commands: ls, nvidia-smi, top, etc.)</h3>
<div id="terminal-container" style="background: black; border-radius: 6px; overflow: hidden;"></div>
</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 terminalWs = null;
let term = null;
let plotData = {lossSteps:[], losses:[], evalSteps:[], evalLosses:[]};
// Toast
function showToast(msg, type='success'){ /* keep as before */ }
// Auth functions (same as before, keep them)
async function doLogin() { /* unchanged */ }
function toggleAuth() { /* unchanged */ }
async function doRegister() { /* unchanged */ }
function doLogout() { localStorage.removeItem('authToken'); if(ws) ws.close(); if(terminalWs) terminalWs.close(); location.reload(); }
function authHeaders() { return authToken ? {'Authorization': `Token ${authToken}`} : {}; }
// API helpers
async function apiGet(url) { /* unchanged */ }
async function apiPost(url, body) { /* unchanged */ }
// Load config options
async function loadConfigOptions() { /* unchanged */ }
function populateSelect(id, options, defaultVal) { /* unchanged */ }
// Job list
async function loadJobList() { /* unchanged */ }
// Config and job control
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 data = await apiPost('/train/', getConfig()); if(data) selectJob(data.id); showToast('Training started'); } catch(e) { showToast('Start failed','error'); } }
async function stopTraining() { if(currentJobId) await apiPost(`/jobs/${currentJobId}/stop/`); showToast('Stop signal sent'); }
async function updateConfig() { if(currentJobId) { await fetch(`${API_BASE}/jobs/${currentJobId}/config/`, { method:'PATCH', headers:{...authHeaders(),'Content-Type':'application/json'}, body:JSON.stringify(getConfig()) }); showToast('Config updated'); } }
// WebSocket for job updates
function connectWebSocket(jobId) { if(ws) ws.close(); ws = new WebSocket(`${WS_BASE}/${jobId}/`); ws.onmessage = handleWSMessage; ws.onerror = (e) => console.log('WS error'); ws.onclose = () => setTimeout(() => { if(currentJobId) connectWebSocket(currentJobId); }, 3000); }
// Terminal WebSocket and xterm.js initialization
function initTerminal() {
if (terminalWs) terminalWs.close();
terminalWs = new WebSocket('ws://127.0.0.1:8000/ws/terminal/');
term = new Terminal({ cursorBlink: true, theme: { background: '#000000', foreground: '#00ff00' } });
term.open(document.getElementById('terminal-container'));
term.onData(data => { if (terminalWs.readyState === WebSocket.OPEN) terminalWs.send(data); });
terminalWs.onmessage = ev => term.write(ev.data);
terminalWs.onerror = err => console.error('Terminal WS error', err);
terminalWs.onclose = () => {
term.write('\r\n\x1b[31mTerminal disconnected – reconnecting...\x1b[0m\r\n');
setTimeout(initTerminal, 3000);
};
}
async function selectJob(jobId) {
currentJobId = jobId;
document.getElementById('startBtn').disabled = true;
document.getElementById('stopBtn').disabled = false;
document.getElementById('updateBtn').disabled = false;
connectWebSocket(jobId);
const job = await apiGet(`/jobs/${jobId}/`);
if(job) {
document.getElementById('jobStatus').innerHTML = `<span class="status-dot ${job.status}"></span>${job.status}`;
document.getElementById('logContent').innerText = job.logs || '';
if(job.current_step && job.total_steps) {
let pct = (job.current_step/job.total_steps)*100;
document.getElementById('progressBar').style.width = pct+'%';
document.getElementById('progressText').innerText = Math.round(pct)+'%';
}
}
}
function handleWSMessage(event) {
let 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();
loadJobList();
}
break;
case 'log':
let 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':
let 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;
}
}
function formatTime(sec) { if(!sec || sec<0) return '--'; let h=Math.floor(sec/3600), m=Math.floor((sec%3600)/60), s=Math.round(sec%60); return `${h}h ${m}m ${s}s`; }
function getAuthToken() {
return localStorage.getItem('authToken');
}
async function apiFetch(url, options = {}) {
const token = getAuthToken();
const headers = {
'Content-Type': 'application/json',
...(token ? {'Authorization': `Token ${token}`} : {}),
...options.headers
};
const response = await fetch(url, {...options, headers});
if (response.status === 401) {
window.location.href = '/login/'; // or redirect to login
}
return response;
}
async function pollSystem() {
const res = await apiFetch('/api/system/status/');
const data = await res.json();
// ...
}
// System polling
async function pollSystem() {
try {
let sys = await apiGet('/system/status/');
if(sys) document.getElementById('systemInfo').innerHTML = `CPU: ${sys.cpu}% | Mem: ${sys.memory}%`;
let gpu = await apiGet('/system/gpu/');
if(gpu?.cuda_available) document.getElementById('gpuInfo').innerHTML = `GPU: ${gpu.memory_allocated?.toFixed(2)} / ${gpu.memory_total?.toFixed(2)} GB`;
else document.getElementById('gpuInfo').innerHTML = 'GPU: N/A';
} catch(e) {}
}
// Initialize app
async function initApp() {
await loadConfigOptions();
await loadJobList();
setInterval(loadJobList, 10000);
setInterval(pollSystem, 3000);
pollSystem();
initTerminal(); // start terminal connection
}
// Event listeners
document.getElementById('startBtn').addEventListener('click', startTraining);
document.getElementById('stopBtn').addEventListener('click', stopTraining);
document.getElementById('updateBtn').addEventListener('click', updateConfig);
// Login check
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'; }
</script>
</body>
</html>

Xet Storage Details

Size:
16.4 kB
·
Xet hash:
2b7fd743b8fd4cafee5e4902ec927d180338f1338c461f0eb7f6990b4d575337

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