Integrate dashboard natively into Deck UI

This commit is contained in:
Mikei386
2026-09-29 22:38:06 +02:00
parent aaf9f570e9
commit c2fcd5c509
4 changed files with 78 additions and 26 deletions
+4 -4
View File
@@ -5,13 +5,13 @@ Hardware-Seite entfällt. Die Übersicht für Endpunkt und GPU-Modus bleibt als
separater Bereich erhalten; die alten Router-, Bild- und Modus-Umschalter separater Bereich erhalten; die alten Router-, Bild- und Modus-Umschalter
werden im Dashboard nicht eingeblendet. werden im Dashboard nicht eingeblendet.
Die Karten und Ansichten ab „Aktives Profil“ übernehmen die Struktur des alten Das Dashboard ist Teil des normalen Deck-DOM und nutzt dessen Navigation, Typografie,
Athena-Dashboards: CPU, RAM, beide GPUs mit VRAM/Temperatur/Takt/Leistung, Karten und responsive Layoutregeln. Es übernimmt die Messwerte und Funktionen
ab „Aktives Profil“ aus dem alten Athena-Dashboard: CPU, RAM, beide GPUs mit VRAM/Temperatur/Takt/Leistung,
Inferenzraten, Prompt-Cache, Anfragen, Slots, Kontextfenster, MTP, Inferenzraten, Prompt-Cache, Anfragen, Slots, Kontextfenster, MTP,
Tokenzähler, GPU- und Slot-Verlauf, Nutzung nach Profil, Modellwechsel, Tokenzähler, GPU- und Slot-Verlauf, Nutzung nach Profil, Modellwechsel,
Profile, Dienste, Host/Netzwerk, GPU-Prozesse, llama.cpp-Parameter, Profile, Dienste, Host/Netzwerk, GPU-Prozesse, llama.cpp-Parameter,
GGUF-Dateien und verschlüsselte portable Backups. Die Farbwerte sind an Deck GGUF-Dateien und verschlüsselte portable Backups. Die Anzeige ist im Deck-Design neu aufgebaut. Nicht verfügbare Laufzeitwerte erscheinen als „–“; sie werden nicht
angepasst. Nicht verfügbare Laufzeitwerte erscheinen als „–“; sie werden nicht
aus historischen Werten geschätzt. Profil und Modell beziehen sich auf Decks aus historischen Werten geschätzt. Profil und Modell beziehen sich auf Decks
eigenen Worker, nicht auf den alten Router. eigenen Worker, nicht auf den alten Router.
+10 -10
View File
@@ -4,14 +4,14 @@ const esc=v=>String(v??'–').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>'
const pct=n=>n==null?'–':`${Number(n).toFixed(1)}%`; const pct=n=>n==null?'–':`${Number(n).toFixed(1)}%`;
const gib=b=>b==null?'–':`${(Number(b)/1073741824).toFixed(1)} GiB`; const gib=b=>b==null?'–':`${(Number(b)/1073741824).toFixed(1)} GiB`;
const dur=s=>{if(s==null)return'–';let d=Math.floor(s/86400),h=Math.floor(s%86400/3600),m=Math.floor(s%3600/60);return d?`${d}d ${h}h`:`${h}h ${m}m`}; const dur=s=>{if(s==null)return'–';let d=Math.floor(s/86400),h=Math.floor(s%86400/3600),m=Math.floor(s%3600/60);return d?`${d}d ${h}h`:`${h}h ${m}m`};
const $=id=>root.getElementById(id); const $=id=>root.querySelector(`#${id}`);
const imagePhaseLabel=p=>({'running':'Bildauftrag läuft','loading':'Bildmodell lädt','complete':'bereit','failed':'Fehler'})[p]||p||'inaktiv'; const imagePhaseLabel=p=>({'running':'Bildauftrag läuft','loading':'Bildmodell lädt','complete':'bereit','failed':'Fehler'})[p]||p||'inaktiv';
function cleanup(){for(const timer of timers)clearInterval(timer);timers=[];controller?.abort();controller=null;host=null;root=null} function cleanup(){for(const timer of timers)clearInterval(timer);timers=[];controller?.abort();controller=null;host=null;root=null}
function gpuCard(g){let total=g.memory_total_mib||0,used=g.memory_used_mib||0,p=total&&g.memory_used_mib!=null?used/total*100:0,load=Math.max(0,Math.min(100,g.gpu_percent||0)),memoryText=total&&g.memory_used_mib!=null?`${(used/1024).toFixed(1)} / ${(total/1024).toFixed(1)} GiB`:'–';return `<article class="card span6"><div class="gpu-title"><div><div class="label">GPU ${esc(g.index)}</div><div class="value">${esc(g.name)}</div></div><span class="badge">${esc(g.pstate)}</span></div><div class="metrics"><div class="metric"><b>${pct(g.gpu_percent)}</b><span>GPU-Kern</span></div><div class="metric"><b>${memoryText}</b><span>VRAM</span></div><div class="metric"><b>${esc(g.temperature_c)} °C</b><span>Temperatur</span></div><div class="metric"><b>${esc(g.power_w)} / ${esc(g.power_limit_w)} W</b><span>Leistung</span></div><div class="metric"><b>${esc(g.memory_controller_percent)} %</b><span>Speichercontroller</span></div><div class="metric"><b>${esc(g.graphics_clock_mhz)} MHz</b><span>GPU-Takt</span></div><div class="metric"><b>${esc(g.memory_clock_mhz)} MHz</b><span>Speichertakt</span></div><div class="metric"><b>${esc(g.fan_percent)} %</b><span>Lüfter</span></div></div><div class="bar-label"><span>GPU-Auslastung</span><span>${pct(load)}</span></div><div class="bar"><div class="fill gpu-load" style="width:${load}%"></div></div><div class="bar-label"><span>VRAM</span><span>${pct(p)}</span></div><div class="bar"><div class="fill" style="width:${p}%"></div></div></article>`} function gpuCard(g){let total=g.memory_total_mib||0,used=g.memory_used_mib||0,p=total&&g.memory_used_mib!=null?used/total*100:0,load=Math.max(0,Math.min(100,g.gpu_percent||0)),memoryText=total&&g.memory_used_mib!=null?`${(used/1024).toFixed(1)} / ${(total/1024).toFixed(1)} GiB`:'–';return `<article class="card span6"><div class="gpu-title"><div><div class="label">GPU ${esc(g.index)}</div><div class="value">${esc(g.name)}</div></div><span class="badge">${esc(g.pstate)}</span></div><div class="metrics"><div class="metric"><b>${pct(g.gpu_percent)}</b><span>GPU-Kern</span></div><div class="metric"><b>${memoryText}</b><span>VRAM</span></div><div class="metric"><b>${esc(g.temperature_c)} °C</b><span>Temperatur</span></div><div class="metric"><b>${esc(g.power_w)} / ${esc(g.power_limit_w)} W</b><span>Leistung</span></div><div class="metric"><b>${esc(g.memory_controller_percent)} %</b><span>Speichercontroller</span></div><div class="metric"><b>${esc(g.graphics_clock_mhz)} MHz</b><span>GPU-Takt</span></div><div class="metric"><b>${esc(g.memory_clock_mhz)} MHz</b><span>Speichertakt</span></div><div class="metric"><b>${esc(g.fan_percent)} %</b><span>Lüfter</span></div></div><div class="bar-label"><span>GPU-Auslastung</span><span>${pct(load)}</span></div><div class="bar"><div class="fill gpu-load" style="width:${load}%"></div></div><div class="bar-label"><span>VRAM</span><span>${pct(p)}</span></div><div class="bar"><div class="fill" style="width:${p}%"></div></div></article>`}
function mount(){ function mount(){
controller=new AbortController(); controller=new AbortController();
root=host.attachShadow({mode:'open'}); root=host;
root.innerHTML=`<link rel="stylesheet" href="/dashboard.css"><div class="dashboard-root"><main><div class="top"><div><div class="eyebrow">Athena Deck · Live Telemetry</div><h1>Dashboard</h1></div><div class="live"><span class="dot" id="dot"></span><span id="updated">verbinde …</span></div></div><section class="grid"> <article class="card span3"><div class="label">Aktives Profil</div><div class="value" id="profile">–</div><div class="sub" id="profileSub">Deck wird abgefragt</div></article> root.innerHTML=`<div class="dashboard-root"><div class="top"><div><div class="kicker">ATHENA / TELEMETRIE</div><h1>Dashboard</h1><p>System, Inferenz und Verlauf auf einen Blick.</p></div><div class="live"><span class="dot" id="dot"></span><span id="updated">verbinde …</span></div></div><section class="grid"> <article class="card span3"><div class="label">Aktives Profil</div><div class="value" id="profile">–</div><div class="sub" id="profileSub">Deck wird abgefragt</div></article>
<article class="card span3"><div class="label">Modell</div><div class="value" id="model">–</div><div class="sub" id="modelSub">–</div></article> <article class="card span3"><div class="label">Modell</div><div class="value" id="model">–</div><div class="sub" id="modelSub">–</div></article>
<article class="card span3"><div class="label">CPU</div><div class="value" id="cpu">–</div><div class="bar"><div class="fill" id="cpuBar"></div></div><div class="sub" id="load">–</div></article> <article class="card span3"><div class="label">CPU</div><div class="value" id="cpu">–</div><div class="bar"><div class="fill" id="cpuBar"></div></div><div class="sub" id="load">–</div></article>
<article class="card span3"><div class="label">System-RAM</div><div class="value" id="ram">–</div><div class="bar"><div class="fill" id="ramBar"></div></div><div class="sub" id="ramSub">–</div></article> <article class="card span3"><div class="label">System-RAM</div><div class="value" id="ram">–</div><div class="bar"><div class="fill" id="ramBar"></div></div><div class="sub" id="ramSub">–</div></article>
@@ -45,8 +45,8 @@ root.innerHTML=`<link rel="stylesheet" href="/dashboard.css"><div class="dashboa
<div class="section-title">Notfall-Backups · herunterladen</div> <div class="section-title">Notfall-Backups · herunterladen</div>
<article class="card span12"><div class="row"><div><div class="label">Verschlüsselte portable Sicherungen</div><div class="sub">Unersetzliche Daten ohne erneut ladbare Modellgewichte · maximal fünf Generationen</div></div></div><div class="scroll"><table><thead><tr><th>Erstellt</th><th>Größe</th><th>SHA256</th><th></th></tr></thead><tbody id="backupFiles"><tr><td colspan="4">Backups werden geladen …</td></tr></tbody></table></div><div class="sub" id="backupNote">Zum Wiederherstellen wird der separat verwahrte Age-Schlüssel benötigt.</div></article> <article class="card span12"><div class="row"><div><div class="label">Verschlüsselte portable Sicherungen</div><div class="sub">Unersetzliche Daten ohne erneut ladbare Modellgewichte · maximal fünf Generationen</div></div></div><div class="scroll"><table><thead><tr><th>Erstellt</th><th>Größe</th><th>SHA256</th><th></th></tr></thead><tbody id="backupFiles"><tr><td colspan="4">Backups werden geladen …</td></tr></tbody></table></div><div class="sub" id="backupNote">Zum Wiederherstellen wird der separat verwahrte Age-Schlüssel benötigt.</div></article>
<article class="card span12 error" id="errors" hidden></article> <article class="card span12 error" id="errors" hidden></article>
</section><div class="footer">Aktualisierung jede Sekunde · Steuerung über die Deck-Übersicht</div></main></div>`; </section><div class="footer">Aktualisierung jede Sekunde · Steuerung über die Deck-Übersicht</div></div>`;
root.querySelector('link').addEventListener('load',()=>{if(host?.isConnected)refreshHistory()},{once:true});
const deNum=n=>n==null?'–':Number(n).toLocaleString('de-DE',{maximumFractionDigits:1}); const deNum=n=>n==null?'–':Number(n).toLocaleString('de-DE',{maximumFractionDigits:1});
@@ -130,7 +130,7 @@ function drawHistory(points){
const canvas=$('gpuHistoryChart'),rect=canvas.getBoundingClientRect(),ratio=window.devicePixelRatio||1; const canvas=$('gpuHistoryChart'),rect=canvas.getBoundingClientRect(),ratio=window.devicePixelRatio||1;
canvas.width=Math.max(1,Math.floor(rect.width*ratio));canvas.height=Math.max(1,Math.floor(rect.height*ratio)); canvas.width=Math.max(1,Math.floor(rect.width*ratio));canvas.height=Math.max(1,Math.floor(rect.height*ratio));
const x=canvas.getContext('2d');x.scale(ratio,ratio);const w=rect.width,h=rect.height,pad={l:42,r:44,t:16,b:28}; const x=canvas.getContext('2d');x.scale(ratio,ratio);const w=rect.width,h=rect.height,pad={l:42,r:44,t:16,b:28};
x.clearRect(0,0,w,h);x.strokeStyle='#213044';x.fillStyle='#8fa1b5';x.font='11px system-ui';x.lineWidth=1; x.clearRect(0,0,w,h);x.strokeStyle='#303a37';x.fillStyle='#97a79e';x.font='11px system-ui';x.lineWidth=1;
for(let i=0;i<=4;i++){let y=pad.t+(h-pad.t-pad.b)*i/4;x.beginPath();x.moveTo(pad.l,y);x.lineTo(w-pad.r,y);x.stroke();x.fillText(`${100-i*25}%`,4,y+4);x.fillText(`${100-i*25}°`,w-pad.r+7,y+4)} for(let i=0;i<=4;i++){let y=pad.t+(h-pad.t-pad.b)*i/4;x.beginPath();x.moveTo(pad.l,y);x.lineTo(w-pad.r,y);x.stroke();x.fillText(`${100-i*25}%`,4,y+4);x.fillText(`${100-i*25}°`,w-pad.r+7,y+4)}
if(!points.length){x.fillText('Noch keine historischen Messwerte',pad.l+10,h/2);return} if(!points.length){x.fillText('Noch keine historischen Messwerte',pad.l+10,h/2);return}
const min=Math.min(...points.map(p=>p.ts)),max=Math.max(...points.map(p=>p.ts)); const min=Math.min(...points.map(p=>p.ts)),max=Math.max(...points.map(p=>p.ts));
@@ -143,8 +143,8 @@ function drawHistory(points){
:span<=45*86400 :span<=45*86400
?date.toLocaleString('de-DE',{day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}) ?date.toLocaleString('de-DE',{day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'})
:date.toLocaleDateString('de-DE',{day:'2-digit',month:'2-digit',year:'2-digit'}); :date.toLocaleDateString('de-DE',{day:'2-digit',month:'2-digit',year:'2-digit'});
x.strokeStyle='#172538';x.beginPath();x.moveTo(xx,pad.t);x.lineTo(xx,h-pad.b);x.stroke(); x.strokeStyle='#2b3532';x.beginPath();x.moveTo(xx,pad.t);x.lineTo(xx,h-pad.b);x.stroke();
x.fillStyle='#8fa1b5';x.textAlign=i===0?'left':i===ticks-1?'right':'center';x.fillText(label,xx,h-7); x.fillStyle='#97a79e';x.textAlign=i===0?'left':i===ticks-1?'right':'center';x.fillText(label,xx,h-7);
} }
x.textAlign='start'; x.textAlign='start';
const ids=[...new Set(points.map(p=>p.gpu_index))].sort(); const ids=[...new Set(points.map(p=>p.gpu_index))].sort();
@@ -157,7 +157,7 @@ function drawSlotHistory(points){
const canvas=$('slotHistoryChart'),rect=canvas.getBoundingClientRect(),ratio=window.devicePixelRatio||1; const canvas=$('slotHistoryChart'),rect=canvas.getBoundingClientRect(),ratio=window.devicePixelRatio||1;
canvas.width=Math.max(1,Math.floor(rect.width*ratio));canvas.height=Math.max(1,Math.floor(rect.height*ratio)); canvas.width=Math.max(1,Math.floor(rect.width*ratio));canvas.height=Math.max(1,Math.floor(rect.height*ratio));
const x=canvas.getContext('2d');x.scale(ratio,ratio);const w=rect.width,h=rect.height,pad={l:42,r:18,t:16,b:28}; const x=canvas.getContext('2d');x.scale(ratio,ratio);const w=rect.width,h=rect.height,pad={l:42,r:18,t:16,b:28};
x.clearRect(0,0,w,h);x.strokeStyle='#213044';x.fillStyle='#8fa1b5';x.font='11px system-ui';x.lineWidth=1; x.clearRect(0,0,w,h);x.strokeStyle='#303a37';x.fillStyle='#97a79e';x.font='11px system-ui';x.lineWidth=1;
for(let i=0;i<=4;i++){let y=pad.t+(h-pad.t-pad.b)*i/4;x.beginPath();x.moveTo(pad.l,y);x.lineTo(w-pad.r,y);x.stroke();x.fillText(`${100-i*25}%`,4,y+4)} for(let i=0;i<=4;i++){let y=pad.t+(h-pad.t-pad.b)*i/4;x.beginPath();x.moveTo(pad.l,y);x.lineTo(w-pad.r,y);x.stroke();x.fillText(`${100-i*25}%`,4,y+4)}
if(!points.length){x.fillText('Noch keine historischen Slot-Messwerte',pad.l+10,h/2);$('slotLegend').innerHTML='';return} if(!points.length){x.fillText('Noch keine historischen Slot-Messwerte',pad.l+10,h/2);$('slotLegend').innerHTML='';return}
const min=Math.min(...points.map(p=>p.ts)),max=Math.max(...points.map(p=>p.ts)); const min=Math.min(...points.map(p=>p.ts)),max=Math.max(...points.map(p=>p.ts));
@@ -166,7 +166,7 @@ function drawSlotHistory(points){
for(let i=0;i<ticks;i++){ for(let i=0;i<ticks;i++){
const ts=min+span*i/(ticks-1),xx=px(ts),date=new Date(ts*1000); const ts=min+span*i/(ticks-1),xx=px(ts),date=new Date(ts*1000);
const label=span<=2*86400?date.toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'}):span<=45*86400?date.toLocaleString('de-DE',{day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}):date.toLocaleDateString('de-DE',{day:'2-digit',month:'2-digit',year:'2-digit'}); const label=span<=2*86400?date.toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'}):span<=45*86400?date.toLocaleString('de-DE',{day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}):date.toLocaleDateString('de-DE',{day:'2-digit',month:'2-digit',year:'2-digit'});
x.strokeStyle='#172538';x.beginPath();x.moveTo(xx,pad.t);x.lineTo(xx,h-pad.b);x.stroke();x.fillStyle='#8fa1b5';x.textAlign=i===0?'left':i===ticks-1?'right':'center';x.fillText(label,xx,h-7); x.strokeStyle='#2b3532';x.beginPath();x.moveTo(xx,pad.t);x.lineTo(xx,h-pad.b);x.stroke();x.fillStyle='#97a79e';x.textAlign=i===0?'left':i===ticks-1?'right':'center';x.fillText(label,xx,h-7);
} }
x.textAlign='start';const ids=[...new Set(points.map(p=>p.slot_id))].sort((a,b)=>a-b); x.textAlign='start';const ids=[...new Set(points.map(p=>p.slot_id))].sort((a,b)=>a-b);
$('slotLegend').innerHTML=ids.map((id,idx)=>{let color=historyColors[idx%historyColors.length],key=String(id);return `<button data-slot-series="${key}" class="${hiddenSlotSeries.has(key)?'off':''}" style="--series:${color}" aria-pressed="${hiddenSlotSeries.has(key)?'false':'true'}">Slot ${id} · Kontext</button>`}).join(''); $('slotLegend').innerHTML=ids.map((id,idx)=>{let color=historyColors[idx%historyColors.length],key=String(id);return `<button data-slot-series="${key}" class="${hiddenSlotSeries.has(key)?'off':''}" style="--series:${color}" aria-pressed="${hiddenSlotSeries.has(key)?'false':'true'}">Slot ${id} · Kontext</button>`}).join('');
+63 -11
View File
@@ -1,11 +1,63 @@
/* Native Deck dashboard: one DOM, one typography and surface system. */
:host{color-scheme:dark;--bg:#101416;--panel:#181f21;--panel2:#202a25;--line:#303a37;--text:#e9edea;--muted:#97a79e;--cyan:#c5edaf;--green:#bdeba4;--amber:#ffc65c;--red:#ff8c8c} #deck-dashboard-host{--db-line:#303a37;--db-muted:#97a79e;--db-accent:#c5edaf;--db-danger:#e9aaa0;min-width:0;color:#e9edea}
*{box-sizing:border-box}.dashboard-root{margin:0;background:radial-gradient(circle at 15% -10%,#183049 0,transparent 35%),var(--bg);font:14px/1.45 Inter,ui-sans-serif,system-ui;color:var(--text)}main{max-width:1450px;margin:auto;padding:0}.top{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;margin-bottom:20px}.eyebrow{color:var(--cyan);font-weight:700;letter-spacing:.14em;text-transform:uppercase;font-size:11px}h1{margin:3px 0 0;font-size:30px}.live{display:flex;align-items:center;gap:8px;color:var(--muted)}.dot{width:9px;height:9px;border-radius:50%;background:var(--green);box-shadow:0 0 14px var(--green)}.grid{display:grid;grid-template-columns:repeat(12,1fr);gap:14px}.card{background:linear-gradient(145deg,rgba(17,27,40,.96),rgba(10,16,24,.96));border:1px solid var(--line);border-radius:14px;padding:17px;min-width:0}.span3{grid-column:span 3}.span4{grid-column:span 4}.span6{grid-column:span 6}.span12{grid-column:span 12}.label{color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:.08em}.value{font-size:26px;font-weight:750;margin-top:5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sub{color:var(--muted);margin-top:4px}.bar-label{display:flex;justify-content:space-between;color:var(--muted);font-size:11px;margin-top:13px}.bar{height:9px;background:#070b11;border-radius:99px;overflow:hidden;margin-top:5px}.fill{height:100%;width:0;background:linear-gradient(90deg,var(--cyan),var(--green));transition:width .5s}.fill.gpu-load{background:linear-gradient(90deg,#8b7cff,var(--cyan))}.gpu-title{display:flex;justify-content:space-between;align-items:center;gap:12px}.badge{border:1px solid var(--line);background:#07101a;color:var(--cyan);padding:4px 8px;border-radius:99px;font-size:11px}.metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-top:16px}.metric b{display:block;font-size:18px}.metric span{color:var(--muted);font-size:11px}.status{color:var(--green)}.status.bad{color:var(--red)}table{border-collapse:collapse;width:100%;margin-top:10px}th,td{text-align:left;padding:8px;border-bottom:1px solid var(--line)}th{color:var(--muted);font-weight:500}.error{color:var(--red);white-space:pre-wrap}.footer{color:var(--muted);font-size:12px;text-align:right;margin-top:14px}@media(max-width:900px){.span3,.span4,.span6{grid-column:span 12}.metrics{grid-template-columns:repeat(2,1fr)}.top{align-items:flex-start;flex-direction:column}} #deck-dashboard-host *{box-sizing:border-box}
.amber{color:var(--amber)}.section-title{grid-column:span 12;margin:10px 2px -3px;color:var(--cyan);font-size:12px;font-weight:750;letter-spacing:.13em;text-transform:uppercase}.slot-list{display:grid;gap:12px;margin-top:13px}.slot{border:1px solid var(--line);border-radius:11px;padding:13px;background:#141a1c}.slot-head,.row{display:flex;justify-content:space-between;align-items:center;gap:12px}.slot-head b{font-size:16px}.profiles{display:grid;grid-template-columns:repeat(2,1fr);gap:9px;margin-top:13px}.profile-item{border:1px solid var(--line);border-radius:10px;padding:10px}.profile-item.active{border-color:var(--cyan);box-shadow:inset 0 0 0 1px #c5edaf33}.profile-item b{display:block}.profile-item span{color:var(--muted);font-size:11px}.scroll{max-height:330px;overflow:auto}.scroll th{position:sticky;top:0;background:var(--panel)}.event{padding:8px 0;border-bottom:1px solid var(--line)}.event:last-child{border:0}.event time{color:var(--muted);font-size:11px;margin-right:8px}@media(max-width:900px){.profiles{grid-template-columns:1fr}} #deck-dashboard-host .top{display:flex;align-items:end;justify-content:space-between;gap:22px;margin-bottom:28px}
.span4 .metrics{grid-template-columns:repeat(2,1fr)} #deck-dashboard-host .top h1{margin:12px 0 6px}
.history-controls{display:flex;flex-wrap:wrap;gap:7px;margin:10px 0 14px}.history-controls button{border:1px solid var(--line);background:#141a1c;color:var(--muted);padding:6px 10px;border-radius:8px;cursor:pointer}.history-controls button.active{color:var(--cyan);border-color:var(--cyan)}.chart-legend{display:flex;flex-wrap:wrap;gap:8px;margin:2px 0 10px}.chart-legend button{border:1px solid var(--series);background:#141a1c;color:var(--text);padding:6px 10px;border-radius:8px;cursor:pointer}.chart-legend button::before{content:'';display:inline-block;width:10px;height:3px;background:var(--series);margin:0 7px 3px 0}.chart-legend button.off{opacity:.4;text-decoration:line-through}.chart{width:100%;height:250px;display:block}.token-total{font-size:24px;font-weight:750;margin-top:5px}.history-note{color:var(--muted);font-size:11px;margin-top:8px} #deck-dashboard-host .top p{margin:0}
.usage-list{display:grid;gap:13px;margin-top:8px}.usage-head{display:flex;justify-content:space-between;gap:14px;align-items:baseline}.usage-head b{font-size:16px}.usage-head span{color:var(--muted)}.usage-meta{display:flex;justify-content:space-between;gap:12px;color:var(--muted);font-size:11px;margin-top:5px} #deck-dashboard-host .live{display:flex;align-items:center;gap:9px;color:var(--db-muted);font-size:12px;white-space:nowrap}
.mode-row{display:flex;align-items:center;justify-content:space-between;gap:18px;flex-wrap:wrap}.mode-buttons,.mode-buttons span{display:flex;gap:9px;flex-wrap:wrap}.mode-buttons button,.mode-buttons a{border:1px solid var(--line);background:#141a1c;color:var(--text);padding:9px 14px;border-radius:9px;cursor:pointer;text-decoration:none;font:inherit}.mode-buttons button.active{border-color:var(--cyan);color:var(--cyan);box-shadow:inset 0 0 0 1px #c5edaf33}.mode-buttons button:disabled{opacity:.45;cursor:wait}.mode-buttons span[hidden]{display:none}.mode-buttons a.stable{border-color:#66e3a466;color:var(--green)}.mode-buttons a.experimental{border-color:#ffc65c66;color:var(--amber)}.mode-error{color:var(--red)} #deck-dashboard-host .dot{width:8px;height:8px;border-radius:50%;background:#bdeba4;box-shadow:0 0 0 4px #bdeba41c}
.image-controls{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px;margin-top:12px}.image-controls .mode-buttons{margin-top:8px}@media(max-width:900px){.image-controls{grid-template-columns:1fr}} #deck-dashboard-host .grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:18px;margin:0}
.download{display:inline-block;border:1px solid #66e3a466;color:var(--green);padding:6px 10px;border-radius:8px;text-decoration:none}.hash{font:11px ui-monospace,SFMono-Regular,monospace;color:var(--muted);word-break:break-all} #deck-dashboard-host .card{background:#181f21;border:1px solid var(--db-line);border-radius:13px;padding:24px;min-width:0}
#deck-dashboard-host .span3{grid-column:span 3}#deck-dashboard-host .span4{grid-column:span 4}#deck-dashboard-host .span6{grid-column:span 6}#deck-dashboard-host .span12{grid-column:span 12}
#deck-dashboard-host .label{color:#95a69b;font-size:11px;letter-spacing:1px;text-transform:uppercase}
#deck-dashboard-host .value{font-size:27px;font-weight:550;letter-spacing:-.7px;margin:14px 0 4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#e4f1de}
#deck-dashboard-host .sub{font-size:12px;line-height:1.55;color:var(--db-muted);margin-top:5px}
#deck-dashboard-host .section-title{grid-column:1/-1;color:#c5edaf;font-size:11px;letter-spacing:2px;text-transform:uppercase;margin:20px 0 -4px;padding-bottom:12px;border-bottom:1px solid var(--db-line)}
#deck-dashboard-host .bar-label{display:flex;justify-content:space-between;gap:10px;color:var(--db-muted);font-size:11px;margin-top:15px}
#deck-dashboard-host .bar{height:7px;background:#101718;border-radius:20px;overflow:hidden;margin-top:7px}
#deck-dashboard-host .fill{height:100%;width:0;background:#bdeba4;transition:width .45s}
#deck-dashboard-host .fill.gpu-load{background:#7fc8b6}
#deck-dashboard-host .gpu-title,#deck-dashboard-host .row,#deck-dashboard-host .slot-head{display:flex;align-items:center;justify-content:space-between;gap:16px}
#deck-dashboard-host .gpu-title .value{font-size:19px;white-space:normal;line-height:1.35}
#deck-dashboard-host .badge{border:1px solid #3a493f;border-radius:20px;padding:5px 9px;color:#c5edaf;font-size:11px;white-space:nowrap}
#deck-dashboard-host .metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;margin-top:20px}
#deck-dashboard-host .metric{min-width:0}
#deck-dashboard-host .metric b{display:block;font-size:17px;font-weight:550;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#deck-dashboard-host .metric span{display:block;color:var(--db-muted);font-size:11px;line-height:1.45;margin-top:4px}
#deck-dashboard-host .span4 .metrics{grid-template-columns:repeat(2,minmax(0,1fr))}
#deck-dashboard-host .status{color:#bdeba4}#deck-dashboard-host .status.bad,#deck-dashboard-host .error{color:var(--db-danger)}
#deck-dashboard-host table{border-collapse:collapse;width:100%;margin-top:15px;font-size:12px}
#deck-dashboard-host th,#deck-dashboard-host td{text-align:left;padding:11px 8px;border-bottom:1px solid var(--db-line);vertical-align:top}
#deck-dashboard-host th{font-weight:500;color:var(--db-muted)}
#deck-dashboard-host .scroll{max-height:330px;overflow:auto}
#deck-dashboard-host .scroll th{position:sticky;top:0;background:#181f21}
#deck-dashboard-host .slot-list,#deck-dashboard-host .usage-list{display:grid;gap:12px;margin-top:17px}
#deck-dashboard-host .slot,#deck-dashboard-host .profile-item{background:#151b1c;border:1px solid var(--db-line);border-radius:10px;padding:16px}
#deck-dashboard-host .slot-head b,#deck-dashboard-host .usage-head b{font-size:14px;font-weight:550}
#deck-dashboard-host .profiles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:15px}
#deck-dashboard-host .profile-item.active{border-color:#9acb88}
#deck-dashboard-host .profile-item b{display:block;font-weight:550}
#deck-dashboard-host .profile-item span{color:var(--db-muted);font-size:11px}
#deck-dashboard-host .event{padding:10px 0;border-bottom:1px solid var(--db-line)}
#deck-dashboard-host .event:last-child{border:0}
#deck-dashboard-host .event time{color:var(--db-muted);font-size:11px;margin-right:8px}
#deck-dashboard-host .history-controls{display:flex;flex-wrap:wrap;gap:6px;margin:0}
#deck-dashboard-host .history-controls button,#deck-dashboard-host .chart-legend button{border:1px solid #3a493f;background:#151b1c;color:#c5d2c9;border-radius:7px;padding:7px 11px;font:inherit;font-size:11px;cursor:pointer}
#deck-dashboard-host .history-controls button:hover,#deck-dashboard-host .chart-legend button:hover{background:#253229}
#deck-dashboard-host .history-controls button.active{background:#28362b;color:#d3efc5;border-color:#668659}
#deck-dashboard-host .chart-legend{display:flex;flex-wrap:wrap;gap:7px;margin:17px 0 6px}
#deck-dashboard-host .chart-legend button::before{content:'';display:inline-block;width:10px;height:3px;background:var(--series);margin:0 7px 3px 0}
#deck-dashboard-host .chart-legend button.off{opacity:.45}
#deck-dashboard-host .chart{display:block;width:100%;height:250px}
#deck-dashboard-host .token-total{font-size:25px;font-weight:550;color:#e4f1de;margin-top:12px}
#deck-dashboard-host .history-note{font-size:11px;color:var(--db-muted);margin-top:9px}
#deck-dashboard-host .usage-head,#deck-dashboard-host .usage-meta{display:flex;align-items:baseline;justify-content:space-between;gap:14px}
#deck-dashboard-host .usage-head span,#deck-dashboard-host .usage-meta{color:var(--db-muted)}
#deck-dashboard-host .usage-meta{font-size:11px;margin-top:7px}
#deck-dashboard-host .download{display:inline-block;border:1px solid #668659;color:#c5edaf;border-radius:7px;padding:6px 10px;text-decoration:none}
#deck-dashboard-host .hash{font:11px ui-monospace,SFMono-Regular,monospace;color:var(--db-muted);word-break:break-all}
#deck-dashboard-host .footer{font-size:11px;color:var(--db-muted);margin-top:22px;text-align:right}
#deck-dashboard-host .error{white-space:pre-wrap}
@media(max-width:1180px){#deck-dashboard-host .span3{grid-column:span 6}#deck-dashboard-host .span4{grid-column:span 6}#deck-dashboard-host .row{align-items:flex-start;flex-direction:column}}
@media(max-width:800px){#deck-dashboard-host .span3,#deck-dashboard-host .span4,#deck-dashboard-host .span6{grid-column:span 12}#deck-dashboard-host .top{align-items:flex-start;flex-direction:column}#deck-dashboard-host .profiles{grid-template-columns:1fr}}
@media(max-width:500px){#deck-dashboard-host .card{padding:18px}#deck-dashboard-host .metrics{grid-template-columns:repeat(2,minmax(0,1fr))}#deck-dashboard-host .gpu-title{align-items:flex-start;flex-direction:column}#deck-dashboard-host .usage-meta{flex-direction:column;gap:3px}}
+1 -1
View File
@@ -1 +1 @@
<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Athena Deck</title><link rel="stylesheet" href="/style.css"><script src="/network-ui.js" defer></script><script src="/access-ui.js" defer></script><script src="/catalog-ui.js" defer></script><script src="/runtime-ui.js" defer></script><script src="/profiles-ui.js" defer></script><script src="/auto-test-ui.js" defer></script><script src="/chat-test-ui.js" defer></script><script src="/image-test-ui.js" defer></script><script src="/docker-ui.js" defer></script><script src="/stt-ui.js" defer></script><script src="/tts-ui.js" defer></script><script src="/studio.js" defer></script><script src="/video-ui.js" defer></script><script src="/endpoint-ui.js" defer></script><script src="/dashboard-ui.js" defer></script><script src="/app.js" defer></script></head><body><aside><a class="brand" href="#dashboard"><span>Α</span> ATHENA <b>DECK</b></a><p class="eyebrow">WORKSPACE / ATHENA</p><nav aria-label="Hauptnavigation"><a href="#dashboard">Dashboard <span>01</span></a><a href="#home">Übersicht <span>02</span></a><a href="#chat">Sprachmodelle / Chat <span>02</span></a><a href="#image">Bildgenerierung <span>03</span></a><p class="nav-heading">AUDIO</p><a class="nav-sub" href="#audio">Text-to-Speech (TTS)</a><a class="nav-sub" href="#stt">Spracherkennung (STT)</a><a class="nav-sub" href="#music">Musik</a><a class="nav-sub" href="#voice">Stimme / Voice</a><a href="#video">Video <span>05</span></a><a href="#services">Weitere Dienste <span>06</span></a><p class="nav-heading">EINSTELLUNGEN</p><a href="#runtimes">Laufzeiten <span>↗</span></a><a class="nav-sub" href="#runtime">llama.cpp</a><a class="nav-sub" href="#image-runtime">Bildgenerierung</a><a class="nav-sub" href="#stt-runtime">Spracherkennung</a><a class="nav-sub" href="#tts-runtime">Text-to-Speech</a><a class="nav-sub" href="#docker-runtime">Docker</a><a href="#endpoint">Endpunkt &amp; Profile <span>↗</span></a><a href="#network">Netzwerk <span>↗</span></a><a href="#access">Zugang &amp; API <span>↗</span></a></nav><footer><i></i> Isolierte Entwicklungsumgebung<p>Eigener Modell-Router<br>Hardware · nur lesend</p></footer></aside><main><header><span>ATHENA CONTROL SURFACE</span><span class="pill">v0.7 · Router</span></header><div id="view"></div><p id="error" role="alert"></p></main></body></html> <!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Athena Deck</title><link rel="stylesheet" href="/style.css"><link rel="stylesheet" href="/dashboard.css"><script src="/network-ui.js" defer></script><script src="/access-ui.js" defer></script><script src="/catalog-ui.js" defer></script><script src="/runtime-ui.js" defer></script><script src="/profiles-ui.js" defer></script><script src="/auto-test-ui.js" defer></script><script src="/chat-test-ui.js" defer></script><script src="/image-test-ui.js" defer></script><script src="/docker-ui.js" defer></script><script src="/stt-ui.js" defer></script><script src="/tts-ui.js" defer></script><script src="/studio.js" defer></script><script src="/video-ui.js" defer></script><script src="/endpoint-ui.js" defer></script><script src="/dashboard-ui.js" defer></script><script src="/app.js" defer></script></head><body><aside><a class="brand" href="#dashboard"><span>Α</span> ATHENA <b>DECK</b></a><p class="eyebrow">WORKSPACE / ATHENA</p><nav aria-label="Hauptnavigation"><a href="#dashboard">Dashboard <span>01</span></a><a href="#home">Übersicht <span>02</span></a><a href="#chat">Sprachmodelle / Chat <span>02</span></a><a href="#image">Bildgenerierung <span>03</span></a><p class="nav-heading">AUDIO</p><a class="nav-sub" href="#audio">Text-to-Speech (TTS)</a><a class="nav-sub" href="#stt">Spracherkennung (STT)</a><a class="nav-sub" href="#music">Musik</a><a class="nav-sub" href="#voice">Stimme / Voice</a><a href="#video">Video <span>05</span></a><a href="#services">Weitere Dienste <span>06</span></a><p class="nav-heading">EINSTELLUNGEN</p><a href="#runtimes">Laufzeiten <span>↗</span></a><a class="nav-sub" href="#runtime">llama.cpp</a><a class="nav-sub" href="#image-runtime">Bildgenerierung</a><a class="nav-sub" href="#stt-runtime">Spracherkennung</a><a class="nav-sub" href="#tts-runtime">Text-to-Speech</a><a class="nav-sub" href="#docker-runtime">Docker</a><a href="#endpoint">Endpunkt &amp; Profile <span>↗</span></a><a href="#network">Netzwerk <span>↗</span></a><a href="#access">Zugang &amp; API <span>↗</span></a></nav><footer><i></i> Isolierte Entwicklungsumgebung<p>Eigener Modell-Router<br>Hardware · nur lesend</p></footer></aside><main><header><span>ATHENA CONTROL SURFACE</span><span class="pill">v0.7 · Router</span></header><div id="view"></div><p id="error" role="alert"></p></main></body></html>