'use client' /** * GPU Infrastructure Admin Page * * vast.ai GPU Management for LLM Processing * Part of KI-Werkzeuge */ import { useEffect, useState, useCallback } from 'react' import { PagePurpose } from '@/components/common/PagePurpose' import { AIToolsSidebarResponsive } from '@/components/ai/AIToolsSidebar' interface VastStatus { instance_id: number | null status: string gpu_name: string | null dph_total: number | null endpoint_base_url: string | null last_activity: string | null auto_shutdown_in_minutes: number | null total_runtime_hours: number | null total_cost_usd: number | null account_credit: number | null account_total_spend: number | null session_runtime_minutes: number | null session_cost_usd: number | null message: string | null error?: string } export default function GPUInfrastructurePage() { const [status, setStatus] = useState(null) const [loading, setLoading] = useState(true) const [actionLoading, setActionLoading] = useState(null) const [error, setError] = useState(null) const [message, setMessage] = useState(null) const API_PROXY = '/api/admin/gpu' const fetchStatus = useCallback(async () => { setLoading(true) setError(null) try { const response = await fetch(API_PROXY) const data = await response.json() if (!response.ok) { throw new Error(data.error || `HTTP ${response.status}`) } setStatus(data) } catch (err) { setError(err instanceof Error ? err.message : 'Verbindungsfehler') setStatus({ instance_id: null, status: 'error', gpu_name: null, dph_total: null, endpoint_base_url: null, last_activity: null, auto_shutdown_in_minutes: null, total_runtime_hours: null, total_cost_usd: null, account_credit: null, account_total_spend: null, session_runtime_minutes: null, session_cost_usd: null, message: 'Verbindung fehlgeschlagen' }) } finally { setLoading(false) } }, []) useEffect(() => { fetchStatus() }, [fetchStatus]) useEffect(() => { const interval = setInterval(fetchStatus, 30000) return () => clearInterval(interval) }, [fetchStatus]) const powerOn = async () => { setActionLoading('on') setError(null) setMessage(null) try { const response = await fetch(API_PROXY, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'on' }), }) const data = await response.json() if (!response.ok) { throw new Error(data.error || data.detail || 'Aktion fehlgeschlagen') } setMessage('Start angefordert') setTimeout(fetchStatus, 3000) setTimeout(fetchStatus, 10000) } catch (err) { setError(err instanceof Error ? err.message : 'Fehler beim Starten') fetchStatus() } finally { setActionLoading(null) } } const powerOff = async () => { setActionLoading('off') setError(null) setMessage(null) try { const response = await fetch(API_PROXY, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'off' }), }) const data = await response.json() if (!response.ok) { throw new Error(data.error || data.detail || 'Aktion fehlgeschlagen') } setMessage('Stop angefordert') setTimeout(fetchStatus, 3000) setTimeout(fetchStatus, 10000) } catch (err) { setError(err instanceof Error ? err.message : 'Fehler beim Stoppen') fetchStatus() } finally { setActionLoading(null) } } const getStatusBadge = (s: string) => { const baseClasses = 'px-3 py-1 rounded-full text-sm font-semibold uppercase' switch (s) { case 'running': return `${baseClasses} bg-green-100 text-green-800` case 'stopped': case 'exited': return `${baseClasses} bg-red-100 text-red-800` case 'loading': case 'scheduling': case 'creating': case 'starting...': case 'stopping...': return `${baseClasses} bg-yellow-100 text-yellow-800` default: return `${baseClasses} bg-slate-100 text-slate-600` } } const getCreditColor = (credit: number | null) => { if (credit === null) return 'text-slate-500' if (credit < 5) return 'text-red-600' if (credit < 15) return 'text-yellow-600' return 'text-green-600' } return (
{/* Page Purpose */} {/* KI-Werkzeuge Sidebar */} {/* Status Cards */}
Status
{loading ? ( Laden... ) : ( {actionLoading === 'on' ? 'starting...' : actionLoading === 'off' ? 'stopping...' : status?.status || 'unbekannt'} )}
GPU
{status?.gpu_name || '-'}
Kosten/h
{status?.dph_total ? `$${status.dph_total.toFixed(3)}` : '-'}
Auto-Stop
{status && status.auto_shutdown_in_minutes !== null ? `${status.auto_shutdown_in_minutes} min` : '-'}
Budget
{status && status.account_credit !== null ? `$${status.account_credit.toFixed(2)}` : '-'}
Session
{status && status.session_runtime_minutes !== null && status.session_cost_usd !== null ? `${Math.round(status.session_runtime_minutes)} min / $${status.session_cost_usd.toFixed(3)}` : '-'}
{/* Buttons */}
{message && ( {message} )} {error && ( {error} )}
{/* Extended Stats */}

Kosten-Uebersicht

Session Laufzeit {status && status.session_runtime_minutes !== null ? `${Math.round(status.session_runtime_minutes)} Minuten` : '-'}
Session Kosten {status && status.session_cost_usd !== null ? `$${status.session_cost_usd.toFixed(4)}` : '-'}
Gesamtlaufzeit {status && status.total_runtime_hours !== null ? `${status.total_runtime_hours.toFixed(1)} Stunden` : '-'}
Gesamtkosten {status && status.total_cost_usd !== null ? `$${status.total_cost_usd.toFixed(2)}` : '-'}
vast.ai Ausgaben {status && status.account_total_spend !== null ? `$${status.account_total_spend.toFixed(2)}` : '-'}

Instanz-Details

Instanz ID {status?.instance_id || '-'}
GPU {status?.gpu_name || '-'}
Stundensatz {status?.dph_total ? `$${status.dph_total.toFixed(4)}/h` : '-'}
Letzte Aktivitaet {status?.last_activity ? new Date(status.last_activity).toLocaleString('de-DE') : '-'}
{status?.endpoint_base_url && status.status === 'running' && (
Endpoint
{status.endpoint_base_url}
)}
{/* Info */}

Auto-Shutdown

Die GPU-Instanz wird automatisch gestoppt, wenn sie laengere Zeit inaktiv ist. Der Status wird alle 30 Sekunden automatisch aktualisiert.

) }