feat: add operational Dashboard

This commit is contained in:
lussierj committed 2026-08-17 17:11:40 +00:00
1 parent 4a1959ecd6
commit b8b741ccee
23 files changed
+1278 -59

No files matched your search

+104 -48
View File
@@ -13,7 +13,7 @@ NAVIGATION = (
PAGE_DETAILS = {
"dashboard": (
"Fleet overview",
"Operational overview",
"Dashboard",
"A concise starting point for operational state and recent activity.",
),
@@ -35,46 +35,104 @@ PAGE_DETAILS = {
}
def _dashboard_content() -> str:
metrics = (
("Site health", "--", "Live health summary not yet connected"),
("Total miners", "--", "Inventory summary not yet connected"),
("Miners in repair", "--", "Repair summary not yet connected"),
("Current hashrate", "-- TH/s", "Telemetry not yet connected"),
("Current power", "-- kW", "Telemetry not yet connected"),
)
metric_html = "".join(
"<article class='metric'>"
f"<span class='metric-label'>{escape(label)}</span>"
f"<strong class='metric-value'>{escape(value)}</strong>"
f"<span class='metric-state'>{escape(state)}</span>"
"</article>"
for label, value, state in metrics
def _dashboard_content(
can_view_miners: bool,
can_view_repairs: bool,
can_view_locations: bool,
) -> str:
fleet = """
<section class="dashboard-section dashboard-fleet" aria-labelledby="fleetOverviewTitle">
<div class="dashboard-section-head"><div><p class="eyebrow">Assets</p>
<h2 id="fleetOverviewTitle">Fleet overview</h2></div>
<a class="object-link" href="/assets/miners">Open Fleet</a></div>
<div class="dashboard-total"><span>Total miners</span>
<strong id="dashboardFleetTotal">-</strong></div>
<div class="dashboard-dimension-grid">
<section aria-labelledby="runtimeTitle"><h3 id="runtimeTitle">Runtime</h3>
<dl class="dashboard-count-list">
<div><dt>Online</dt><dd id="dashboardRuntimeOnline">-</dd></div>
<div><dt>Offline</dt><dd id="dashboardRuntimeOffline">-</dd></div>
<div><dt>Unknown</dt><dd id="dashboardRuntimeUnknown">-</dd></div>
</dl></section>
<section aria-labelledby="lifecycleTitle"><h3 id="lifecycleTitle">Lifecycle</h3>
<dl class="dashboard-count-list">
<div><dt>In repair</dt><dd id="dashboardLifecycleRepair">-</dd></div>
<div><dt>Spare</dt><dd id="dashboardLifecycleSpare">-</dd></div>
<div><dt>Retired</dt><dd id="dashboardLifecycleRetired">-</dd></div>
</dl></section>
</div>
<p class="dashboard-boundary">Runtime and lifecycle are separate dimensions and may overlap.</p>
</section>
<section class="dashboard-section dashboard-attention" aria-labelledby="attentionTitle">
<div class="dashboard-section-head"><div><p class="eyebrow">Current state</p>
<h2 id="attentionTitle">Needs attention</h2></div></div>
<nav class="attention-list" aria-label="Fleet conditions requiring attention">
<a href="/assets/miners?operational_state=offline" data-state="offline">
<span>Offline</span><strong id="dashboardAttentionOffline">-</strong></a>
<a href="/assets/miners?operational_state=unknown" data-state="unknown">
<span>Unknown</span><strong id="dashboardAttentionUnknown">-</strong></a>
<a href="/assets/miners?operational_state=unstable" data-state="unstable">
<span>Hash unstable</span><strong id="dashboardAttentionUnstable">-</strong></a>
<a href="/assets/miners?operational_state=repair" data-state="repair">
<span>In repair</span><strong id="dashboardAttentionRepair">-</strong></a>
</nav>
</section>
""" if can_view_miners else ""
repairs = """
<section class="dashboard-section" aria-labelledby="dashboardRepairsTitle">
<div class="dashboard-section-head"><div><p class="eyebrow">Operations</p>
<h2 id="dashboardRepairsTitle">Repairs</h2></div>
<a class="object-link" href="/operations/repairs?scope=active">Open Repair Center</a></div>
<div class="dashboard-total"><span>Active repair records</span>
<strong id="dashboardActiveRepairs">-</strong></div>
<dl class="dashboard-status-list" id="dashboardRepairStatuses"></dl>
</section>
""" if can_view_repairs else ""
mapped_metrics = """
<div><dt>Mapped miners</dt><dd id="dashboardMappedMiners">-</dd></div>
<div><dt>Unmapped miners</dt><dd id="dashboardUnmappedMiners">-</dd></div>
""" if can_view_miners else ""
locations = f"""
<section class="dashboard-section" aria-labelledby="dashboardLocationsTitle">
<div class="dashboard-section-head"><div><p class="eyebrow">Spatial operations</p>
<h2 id="dashboardLocationsTitle">Locations</h2></div>
<a class="object-link" href="/monitoring/map">Open Mine Map</a></div>
<dl class="dashboard-count-list dashboard-location-counts">
<div><dt>Sites</dt><dd id="dashboardSites">-</dd></div>
<div><dt>Rooms</dt><dd id="dashboardRooms">-</dd></div>
<div><dt>Positions</dt><dd id="dashboardPositions">-</dd></div>
<div><dt>Occupied</dt><dd id="dashboardOccupied">-</dd></div>
<div><dt>Empty</dt><dd id="dashboardEmptyPositions">-</dd></div>
{mapped_metrics}
</dl>
</section>
""" if can_view_locations else ""
available = sum((can_view_miners, can_view_repairs, can_view_locations))
partial = (
'<p class="dashboard-permission-note">This overview includes only the '
'operational areas available to your account.</p>'
if 0 < available < 3 else ""
)
return f"""
<section class="metrics" aria-label="Fleet summary placeholders">
{metric_html}
</section>
<div class="content-grid">
<section class="panel" aria-labelledby="activityTitle">
<div class="panel-head">
<div>
<h2 id="activityTitle">Recent activity</h2>
<p class="panel-copy">Operational events will appear here when
the dashboard activity feed is connected.</p>
</div>
</div>
<div class="state-message">
<strong>No activity feed connected</strong>
<span>No events are fabricated for this placeholder.</span>
</div>
</section>
<aside class="panel" aria-labelledby="attentionTitle">
<h2 id="attentionTitle">Data boundary</h2>
<p class="panel-copy">Summary cards intentionally show unavailable
values until live APIs are connected and freshness can be represented
accurately.</p>
</aside>
<div class="state-message" id="dashboardLoading" role="status">
<strong>Loading operational overview</strong>
<span>Requesting current fleet, repair, and location summaries.</span>
</div>
<div class="state-message" id="dashboardError" data-kind="error" role="alert" hidden>
<strong>Dashboard unavailable</strong>
<span id="dashboardErrorDetail">The operational overview could not be loaded.</span>
<button class="command-button" id="dashboardRetry" type="button">Retry</button>
</div>
<div class="state-message" id="dashboardEmpty" hidden>
<strong>No operational sections available</strong>
<span>Your account can open Dashboard but has no additional overview permissions.</span>
</div>
<div id="dashboardContent" hidden>
<div class="dashboard-status-band">
<p id="dashboardLoadStatus" class="freshness" aria-live="polite">Waiting for current summaries</p>
{partial}
</div>
<div class="dashboard-grid">{fleet}{repairs}{locations}</div>
</div>
"""
@@ -345,14 +403,6 @@ def _miners_content(can_view_locations: bool) -> str:
"""
CONTENT = {
"dashboard": _dashboard_content,
"map": _map_content,
"repairs": _repairs_content,
"miners": _miners_content,
}
def render_app_page(
view: str,
current_user: dict,
@@ -395,7 +445,11 @@ def render_app_page(
has_permission("locations.view"), has_permission("miners.view")
)
if view == "repairs"
else CONTENT[view]()
else _dashboard_content(
has_permission("miners.view"),
has_permission("repairs.view"),
has_permission("locations.view"),
)
)
page_script = (
'<script src="/assets/minerops-mine-map.js" defer></script>'
@@ -404,6 +458,8 @@ def render_app_page(
if view == "miners"
else '<script src="/assets/minerops-repair-center.js" defer></script>'
if view == "repairs"
else '<script src="/assets/minerops-dashboard.js" defer></script>'
if view == "dashboard"
else ""
)
return f"""<!doctype html>