""" Admin GPU Infrastructure Component. Provides UI controls for vast.ai GPU management: - Start/Stop buttons - Status display with GPU info - Cost tracking - Auto-shutdown timer """ def get_admin_gpu_css() -> str: """CSS fuer GPU Control Panel.""" return """ /* ========================================== GPU INFRASTRUCTURE STYLES ========================================== */ .gpu-control-panel { background: var(--bp-surface-elevated); border-radius: 12px; padding: 20px; margin-bottom: 20px; border: 1px solid var(--bp-border); } .gpu-status-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; } .gpu-status-badge { display: inline-flex; align-items: center; gap: 8px; padding: 6px 12px; border-radius: 20px; font-size: 13px; font-weight: 600; } .gpu-status-badge.running { background: rgba(34, 197, 94, 0.15); color: #22c55e; } .gpu-status-badge.stopped { background: rgba(156, 163, 175, 0.15); color: #9ca3af; } .gpu-status-badge.loading { background: rgba(59, 130, 246, 0.15); color: #3b82f6; } .gpu-status-badge.error { background: rgba(239, 68, 68, 0.15); color: #ef4444; } .gpu-status-dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; } .gpu-status-dot.running { animation: pulse 2s infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } .gpu-info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 20px; } .gpu-info-card { background: var(--bp-surface); border-radius: 8px; padding: 12px; border: 1px solid var(--bp-border-subtle); } .gpu-info-label { font-size: 11px; color: var(--bp-text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; } .gpu-info-value { font-size: 16px; font-weight: 600; color: var(--bp-text); } .gpu-info-value.cost { color: #f59e0b; } .gpu-info-value.time { color: #3b82f6; } .gpu-controls { display: flex; gap: 12px; margin-top: 20px; } .gpu-btn { flex: 1; padding: 12px 20px; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; transition: all 0.2s ease; } .gpu-btn:disabled { opacity: 0.5; cursor: not-allowed; } .gpu-btn-start { background: #22c55e; color: white; } .gpu-btn-start:hover:not(:disabled) { background: #16a34a; } .gpu-btn-stop { background: #ef4444; color: white; } .gpu-btn-stop:hover:not(:disabled) { background: #dc2626; } .gpu-btn-refresh { background: var(--bp-surface); color: var(--bp-text); border: 1px solid var(--bp-border); flex: 0 0 auto; padding: 12px; } .gpu-btn-refresh:hover:not(:disabled) { background: var(--bp-surface-elevated); } .gpu-shutdown-warning { background: rgba(251, 191, 36, 0.1); border: 1px solid rgba(251, 191, 36, 0.3); border-radius: 8px; padding: 12px; margin-top: 16px; display: flex; align-items: center; gap: 10px; color: #fbbf24; font-size: 13px; } .gpu-shutdown-warning svg { flex-shrink: 0; } .gpu-cost-summary { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--bp-border); } .gpu-cost-summary h4 { margin: 0 0 12px 0; font-size: 14px; color: var(--bp-text-muted); } .gpu-audit-log { margin-top: 20px; max-height: 200px; overflow-y: auto; background: var(--bp-surface); border-radius: 8px; border: 1px solid var(--bp-border-subtle); } .gpu-audit-entry { padding: 8px 12px; border-bottom: 1px solid var(--bp-border-subtle); font-size: 12px; font-family: monospace; } .gpu-audit-entry:last-child { border-bottom: none; } .gpu-audit-time { color: var(--bp-text-muted); margin-right: 8px; } .gpu-audit-event { color: var(--bp-text); } .gpu-endpoint-url { margin-top: 12px; padding: 10px 12px; background: var(--bp-surface); border-radius: 8px; font-family: monospace; font-size: 12px; color: var(--bp-text-muted); word-break: break-all; } .gpu-endpoint-url.active { color: #22c55e; } .gpu-spinner { width: 16px; height: 16px; border: 2px solid transparent; border-top-color: currentColor; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } [data-theme="light"] .gpu-control-panel { background: #fafafa; } [data-theme="light"] .gpu-info-card { background: #ffffff; } """ def get_admin_gpu_html() -> str: """HTML fuer GPU Control Tab.""" return """

vast.ai GPU Instance

Unbekannt
GPU
-
Kosten/Stunde
-
Session
-
Gesamt
-

Kosten-Zusammenfassung

Laufzeit Gesamt
0h 0m
Kosten Gesamt
$0.00
Audit Log (letzte Aktionen)
Keine Eintraege
Hinweis: Die GPU-Instanz wird automatisch nach 30 Minuten Inaktivitaet gestoppt. Bei jedem LLM-Request wird die Aktivitaet aufgezeichnet und der Timer zurueckgesetzt.
""" def get_admin_gpu_js() -> str: """JavaScript fuer GPU Control.""" return """ // ========================================== // GPU INFRASTRUCTURE CONTROLS // ========================================== let gpuStatusInterval = null; const GPU_CONTROL_KEY = window.CONTROL_API_KEY || ''; async function gpuFetch(endpoint, options = {}) { const headers = { 'Content-Type': 'application/json', 'X-API-Key': GPU_CONTROL_KEY, ...options.headers, }; try { const response = await fetch(`/infra/vast${endpoint}`, { ...options, headers, }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: response.statusText })); throw new Error(error.detail || 'Request failed'); } return await response.json(); } catch (error) { console.error('GPU API Error:', error); throw error; } } async function gpuRefreshStatus() { const btnRefresh = document.querySelector('.gpu-btn-refresh'); const btnStart = document.getElementById('gpu-btn-start'); const btnStop = document.getElementById('gpu-btn-stop'); try { if (btnRefresh) btnRefresh.disabled = true; const status = await gpuFetch('/status'); // Update status badge const badge = document.getElementById('gpu-status-badge'); const statusText = document.getElementById('gpu-status-text'); const statusDot = badge.querySelector('.gpu-status-dot'); badge.className = 'gpu-status-badge ' + status.status; statusText.textContent = formatGpuStatus(status.status); statusDot.className = 'gpu-status-dot ' + status.status; // Update info cards document.getElementById('gpu-name').textContent = status.gpu_name || '-'; document.getElementById('gpu-dph').textContent = status.dph_total ? `$${status.dph_total.toFixed(2)}/h` : '-'; // Update endpoint const endpointDiv = document.getElementById('gpu-endpoint'); const endpointUrl = document.getElementById('gpu-endpoint-url'); if (status.endpoint_base_url && status.status === 'running') { endpointDiv.style.display = 'block'; endpointDiv.className = 'gpu-endpoint-url active'; endpointUrl.textContent = status.endpoint_base_url; } else { endpointDiv.style.display = 'none'; } // Update shutdown warning const warningDiv = document.getElementById('gpu-shutdown-warning'); const shutdownMinutes = document.getElementById('gpu-shutdown-minutes'); if (status.auto_shutdown_in_minutes !== null && status.status === 'running') { warningDiv.style.display = 'flex'; shutdownMinutes.textContent = status.auto_shutdown_in_minutes; } else { warningDiv.style.display = 'none'; } // Update totals document.getElementById('gpu-total-runtime').textContent = formatRuntime(status.total_runtime_hours || 0); document.getElementById('gpu-total-cost-all').textContent = `$${(status.total_cost_usd || 0).toFixed(2)}`; // Update buttons const isRunning = status.status === 'running'; const isLoading = ['loading', 'scheduling', 'creating'].includes(status.status); btnStart.disabled = isRunning || isLoading; btnStop.disabled = !isRunning; if (isLoading) { btnStart.innerHTML = ' Startet...'; } else { btnStart.innerHTML = ` Starten `; } // Load audit log loadGpuAuditLog(); } catch (error) { console.error('Failed to refresh GPU status:', error); document.getElementById('gpu-status-text').textContent = 'Fehler'; document.getElementById('gpu-status-badge').className = 'gpu-status-badge error'; } finally { if (btnRefresh) btnRefresh.disabled = false; } } async function gpuPowerOn() { const btnStart = document.getElementById('gpu-btn-start'); const btnStop = document.getElementById('gpu-btn-stop'); if (!confirm('GPU-Instanz starten? Es fallen Kosten an solange sie laeuft.')) { return; } try { btnStart.disabled = true; btnStop.disabled = true; btnStart.innerHTML = ' Startet...'; const result = await gpuFetch('/power/on', { method: 'POST', body: JSON.stringify({ wait_for_health: true, }), }); showNotification('GPU gestartet: ' + (result.message || result.status), 'success'); await gpuRefreshStatus(); // Start polling for status updates startGpuStatusPolling(); } catch (error) { showNotification('GPU Start fehlgeschlagen: ' + error.message, 'error'); await gpuRefreshStatus(); } } async function gpuPowerOff() { const btnStart = document.getElementById('gpu-btn-start'); const btnStop = document.getElementById('gpu-btn-stop'); if (!confirm('GPU-Instanz stoppen?')) { return; } try { btnStart.disabled = true; btnStop.disabled = true; btnStop.innerHTML = ' Stoppt...'; const result = await gpuFetch('/power/off', { method: 'POST', body: JSON.stringify({}), }); const msg = result.session_runtime_minutes ? `GPU gestoppt. Session: ${result.session_runtime_minutes.toFixed(1)} min, $${result.session_cost_usd.toFixed(3)}` : 'GPU gestoppt'; showNotification(msg, 'success'); await gpuRefreshStatus(); // Stop polling stopGpuStatusPolling(); } catch (error) { showNotification('GPU Stop fehlgeschlagen: ' + error.message, 'error'); await gpuRefreshStatus(); } finally { btnStop.innerHTML = ` Stoppen `; } } async function loadGpuAuditLog() { try { const entries = await gpuFetch('/audit?limit=20'); const container = document.getElementById('gpu-audit-log'); if (!entries || entries.length === 0) { container.innerHTML = '
Keine Eintraege
'; return; } container.innerHTML = entries.map(entry => { const time = new Date(entry.ts).toLocaleString('de-DE', { hour: '2-digit', minute: '2-digit', day: '2-digit', month: '2-digit', }); return `
${time} ${entry.event}
`; }).join(''); } catch (error) { console.error('Failed to load audit log:', error); } } function formatGpuStatus(status) { const statusMap = { 'running': 'Laeuft', 'stopped': 'Gestoppt', 'exited': 'Beendet', 'loading': 'Laedt...', 'scheduling': 'Plane...', 'creating': 'Erstelle...', 'unconfigured': 'Nicht konfiguriert', 'not_found': 'Nicht gefunden', 'unknown': 'Unbekannt', 'error': 'Fehler', }; return statusMap[status] || status; } function formatRuntime(hours) { const h = Math.floor(hours); const m = Math.round((hours - h) * 60); return `${h}h ${m}m`; } function startGpuStatusPolling() { stopGpuStatusPolling(); gpuStatusInterval = setInterval(gpuRefreshStatus, 30000); // Every 30s } function stopGpuStatusPolling() { if (gpuStatusInterval) { clearInterval(gpuStatusInterval); gpuStatusInterval = null; } } function showNotification(message, type = 'info') { // Use existing notification system if available if (window.showToast) { window.showToast(message, type); return; } // Fallback alert(message); } // Initialize when GPU tab is activated document.addEventListener('DOMContentLoaded', () => { const gpuTab = document.querySelector('.admin-tab[data-tab="gpu"]'); if (gpuTab) { gpuTab.addEventListener('click', () => { gpuRefreshStatus(); startGpuStatusPolling(); }); } }); """