diff --git a/dashboard/dashboard.py b/dashboard/dashboard.py index 9b4e064..b8f218f 100644 --- a/dashboard/dashboard.py +++ b/dashboard/dashboard.py @@ -1,4 +1,4 @@ -"""Harness Dashboard.""" +"""SyslogAI Harness Dashboard.""" import os, json, time, queue, threading import requests from flask import Flask, request, render_template_string, Response, stream_with_context @@ -6,8 +6,7 @@ from flask import Flask, request, render_template_string, Response, stream_with_ ROUTER_METRICS = os.environ.get("ROUTER_METRICS_URL", "http://router:9000/metrics") app = Flask(__name__) -sse_subscribers = [] -sse_lock = threading.Lock() +sse_subscribers = []; sse_lock = threading.Lock() def fetch_state(): try: @@ -19,8 +18,7 @@ def fetch_state(): def broadcast_loop(): while True: time.sleep(3) - data = fetch_state() - payload = json.dumps(data) + data = fetch_state(); payload = json.dumps(data) with sse_lock: dead = [] for q in sse_subscribers: @@ -48,10 +46,7 @@ body { background: var(--bg); color: var(--text); min-height: 100vh; padding: clamp(12px, 3vw, 32px); } -.header { - display: flex; align-items: center; justify-content: space-between; - flex-wrap: wrap; gap: 12px; margin-bottom: 24px; -} +.header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-bottom: 24px; } .header h1 { font-size: clamp(18px, 4vw, 26px); font-weight: 700; color: #fff; } .header h1 span { color: var(--accent); } .status-bar { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; font-size: 13px; color: var(--dim); } @@ -59,23 +54,12 @@ body { .status-dot.live { background: var(--green); animation: pulse 2s infinite; } @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.3} } .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 340px), 1fr)); gap: 16px; } -.card { - background: var(--card); border: 1px solid var(--border); - border-radius: 12px; padding: clamp(12px, 3vw, 20px); -} -.card-title { - font-size: 13px; font-weight: 600; text-transform: uppercase; - letter-spacing: 0.5px; color: var(--dim); margin-bottom: 14px; -} -.gpu-row { - display: flex; align-items: center; gap: 14px; padding: 10px 0; - border-bottom: 1px solid rgba(255,255,255,0.04); -} +.card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: clamp(12px, 3vw, 20px); } +.card-title { font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: var(--dim); margin-bottom: 14px; } +.full { grid-column: 1 / -1; } +.gpu-row { display: flex; align-items: center; gap: 14px; padding: 10px 0; border-bottom: 1px solid rgba(255,255,255,0.04); } .gpu-row:last-child { border-bottom: none; } -.gpu-icon { - width: 40px; height: 40px; border-radius: 10px; display: flex; - align-items: center; justify-content: center; font-size: 18px; flex-shrink: 0; -} +.gpu-icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 18px; flex-shrink: 0; } .gpu-icon.green { background: rgba(127,217,98,0.12); color: var(--green); } .gpu-icon.yellow { background: rgba(255,180,84,0.12); color: var(--yellow); } .gpu-icon.red { background: rgba(242,109,120,0.12); color: var(--red); } @@ -90,6 +74,10 @@ body { .vram-fill.green { background: var(--green); } .vram-fill.yellow { background: var(--yellow); } .vram-fill.red { background: var(--red); } +.slot-row { display: flex; gap: 3px; margin-top: 6px; } +.slot-dot { flex: 1; height: 6px; border-radius: 2px; background: rgba(255,255,255,0.06); } +.slot-dot.active { background: var(--accent); } +.slot-label { font-size: 10px; margin-top: 2px; } .bar-row { margin-bottom: 10px; } .bar-label { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 4px; } .bar-label .name { color: #e6edf3; } @@ -106,16 +94,13 @@ body { .agent-tanko { background: rgba(255,180,84,0.15); color: var(--yellow); } .agent-koby { background: rgba(89,194,255,0.15); color: var(--blue); } .agent-kagenz0 { background: rgba(127,217,98,0.15); color: var(--green); } +.agent-koonimo { background: rgba(255,180,84,0.15); color: #ff8c42; } .agent-unknown { background: rgba(255,255,255,0.06); color: var(--dim); } -.agent-admin { background: rgba(255,255,255,0.08); color: #e6edf3; } -.full { grid-column: 1 / -1; } -.period-btn { - background: var(--card); border: 1px solid var(--border); color: var(--dim); - padding: 4px 12px; border-radius: 6px; font-size: 12px; cursor: pointer; - font-family: inherit; transition: all 0.2s; -} +.period-btn { background: var(--card); border: 1px solid var(--border); color: var(--dim); padding: 4px 12px; border-radius: 6px; font-size: 12px; cursor: pointer; font-family: inherit; transition: all 0.2s; } .period-btn.active { background: var(--accent); color: #000; border-color: var(--accent); } .period-btn:hover { border-color: var(--accent); color: #e6edf3; } +.queue-ring { position: relative; display: inline-block; margin-bottom: 8px; } +.queue-center { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); text-align: center; } @media (max-width: 600px) { .gpu-metrics { gap: 10px; } .route-table { font-size: 11px; } @@ -134,15 +119,28 @@ body {