Add Deck dashboard with Athena telemetry history

This commit is contained in:
Mikei386
2026-09-29 22:31:03 +02:00
parent be40869712
commit aaf9f570e9
19 changed files with 1073 additions and 25 deletions
+25 -4
View File
@@ -24,7 +24,7 @@ from inference import LlamaWorker,Scheduler
from endpoint import Endpoint
from chat_test import ChatTests
from auto_test import AutoTests
from urllib.parse import urlsplit, parse_qs
from urllib.parse import urlsplit, parse_qs, unquote
from network.client import NetworkClient
from network.config import parse_config, ConfigError
import json
@@ -50,12 +50,12 @@ class HardwareProvider:
if os.environ.get('DECK_LOCAL_HARDWARE', '1') == '1':
from collect_hardware import collect
with self.lock:
if self.cached is None or time.monotonic()-self.checked >= 5:
if self.cached is None or time.monotonic()-self.checked >= 1:
self.cached = dict(collect(), available=True)
self.checked = time.monotonic()
return self.cached
with self.lock:
if time.monotonic() - self.checked < 5 and self.cached is not None:
if time.monotonic() - self.checked < 1 and self.cached is not None:
return self.cached
try:
result = subprocess.run(['ssh', '-i', str(Path.home()/'.ssh/athena_key'), '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=4', '-o', 'StrictHostKeyChecking=yes', 'root@192.168.1.212', 'python3 -'], input=(ROOT/'collect_hardware.py').read_text(), capture_output=True, text=True, timeout=10, check=True)
@@ -113,6 +113,8 @@ class Server(ThreadingHTTPServer):
self.auto_tests.stop();self.chat_tests.stop();self.image_tests.stop();self.prompt_enhancer.stop_preview();self.tts_tests.stop();self.worker.stop()
self.video=Video(self.scheduler,stop_gpu_work,self.docker,self.catalog.root.parent/'video')
self.endpoint.video=self.video
from dashboard_data import Dashboard
self.dashboard=Dashboard(self,self.catalog.root.parent)
if self.endpoint.config['autostart']:
try:self.endpoint.start()
except ValueError as exc:self.endpoint.error=str(exc)
@@ -272,7 +274,7 @@ class Handler(BaseHTTPRequestHandler):
return self.respond({'error':'Anmeldung erforderlich.'},401)
if not self.authenticated() and self.path == '/':
return self.respond((ROOT/'login.html').read_bytes(), mime='text/html; charset=utf-8')
routes = {'/video-ui.js':('video-ui.js','text/javascript'),'/stt-ui.js':('stt-ui.js','text/javascript'),'/tts-ui.js':('tts-ui.js','text/javascript'),'/auto-test-ui.js':('auto-test-ui.js','text/javascript'),'/chat-test-ui.js':('chat-test-ui.js','text/javascript'),'/endpoint-ui.js':('endpoint-ui.js','text/javascript'),'/docker-ui.js': ('docker-ui.js','text/javascript'), '/': ('index.html', 'text/html; charset=utf-8'), '/app.js': ('app.js', 'text/javascript'), '/style.css': ('style.css', 'text/css'), '/network-ui.js': ('network-ui.js', 'text/javascript'), '/access-ui.js': ('access-ui.js', 'text/javascript'), '/studio.js': ('studio.js', 'text/javascript'), '/catalog-ui.js': ('catalog-ui.js','text/javascript'), '/runtime-ui.js': ('runtime-ui.js','text/javascript'), '/profiles-ui.js': ('profiles-ui.js','text/javascript'), '/image-test-ui.js': ('image-test-ui.js','text/javascript')}
routes = {'/dashboard-ui.js':('dashboard-ui.js','text/javascript'),'/dashboard.css':('dashboard.css','text/css'),'/video-ui.js':('video-ui.js','text/javascript'),'/stt-ui.js':('stt-ui.js','text/javascript'),'/tts-ui.js':('tts-ui.js','text/javascript'),'/auto-test-ui.js':('auto-test-ui.js','text/javascript'),'/chat-test-ui.js':('chat-test-ui.js','text/javascript'),'/endpoint-ui.js':('endpoint-ui.js','text/javascript'),'/docker-ui.js': ('docker-ui.js','text/javascript'), '/': ('index.html', 'text/html; charset=utf-8'), '/app.js': ('app.js', 'text/javascript'), '/style.css': ('style.css', 'text/css'), '/network-ui.js': ('network-ui.js', 'text/javascript'), '/access-ui.js': ('access-ui.js', 'text/javascript'), '/studio.js': ('studio.js', 'text/javascript'), '/catalog-ui.js': ('catalog-ui.js','text/javascript'), '/runtime-ui.js': ('runtime-ui.js','text/javascript'), '/profiles-ui.js': ('profiles-ui.js','text/javascript'), '/image-test-ui.js': ('image-test-ui.js','text/javascript')}
if self.path in routes:
name, mime = routes[self.path]
return self.respond((ROOT/name).read_bytes(), mime=mime)
@@ -308,6 +310,24 @@ class Handler(BaseHTTPRequestHandler):
if self.path == '/api/v1/huggingface':return self.respond(self.server.catalog.hub_auth.status())
if self.path == '/api/v1/hardware':
return self.respond(self.server.hardware.snapshot())
if self.path == '/api/v1/dashboard':return self.respond(self.server.dashboard.snapshot())
if urlsplit(self.path).path == '/api/v1/dashboard/history':
period=parse_qs(urlsplit(self.path).query).get('range',['24h'])[0]
return self.respond(self.server.dashboard.history.query(period))
if self.path == '/api/v1/dashboard/backups':return self.respond(self.server.dashboard.backups())
if self.path.startswith('/api/v1/dashboard/backups/download/'):
try:path=self.server.dashboard.backup_file(unquote(self.path.rsplit('/',1)[-1]))
except ValueError:return self.respond({'error':'Backup nicht verfügbar.'},404)
self.send_response(200)
self.send_header('Content-Type','application/octet-stream')
self.send_header('Content-Length',str(path.stat().st_size))
self.send_header('Content-Disposition','attachment; filename="'+path.name+'"')
self.send_header('Cache-Control','no-store')
self.send_header('X-Content-Type-Options','nosniff')
self.end_headers()
with path.open('rb') as stream:
while chunk:=stream.read(1024*1024):self.wfile.write(chunk)
return
if self.path.startswith('/api/v1/runtime'):
actions={'/api/v1/runtime':self.server.runtime.status,'/api/v1/runtime/prerequisites':self.server.runtime.prerequisites,'/api/v1/runtime/releases':self.server.runtime.releases,'/api/v1/runtime/log':self.server.runtime.log,'/api/v1/runtime/references':self.server.runtime.references}
if self.path in actions:
@@ -559,6 +579,7 @@ def main():
try:
server.serve_forever()
finally:
server.dashboard.close()
server.auto_tests.stop()
server.chat_tests.stop()
server.video.close()