feat: add Fleet operations view

This commit is contained in:
lussierj committed 2026-08-11 17:37:36 +00:00
1 parent c6d01df31c
commit 786a1ab910
12 files changed
+1062 -148

No files matched your search

+67 -23
View File
@@ -222,33 +222,72 @@ def _repairs_content() -> str:
"""
def _miners_content() -> str:
def _miners_content(can_view_locations: bool) -> str:
location_filters = """
<div class="field"><label for="fleetSite">Site</label>
<select id="fleetSite"><option value="">All sites</option></select></div>
<div class="field"><label for="fleetRoom">Room</label>
<select id="fleetRoom"><option value="">All rooms</option></select></div>
""" if can_view_locations else ""
return f"""
<section class="panel" aria-labelledby="minerListTitle">
<section class="panel fleet-panel" aria-labelledby="minerListTitle">
<div class="panel-head">
<div>
<h2 id="minerListTitle">Miner inventory</h2>
<p class="panel-copy">Real inventory records. Search matches the
fields supported by the Inventory API.</p>
</div>
<span class="freshness">Up to 100 results</span>
<div><h2 id="minerListTitle">Fleet</h2>
<p class="panel-copy">Current miner identity, operating state, telemetry, and repair context.</p></div>
<span class="freshness" id="fleetFreshness">Loading current fleet</span>
</div>
<form class="filter-bar" id="minerSearchForm" role="search">
<div class="field">
<label for="minerSearchInput">Search miners</label>
<input id="minerSearchInput" type="search" autocomplete="off"
placeholder="Asset ID, MAC, serial, model, or IP">
<form class="fleet-filters" id="fleetFilters" role="search">
<div class="field fleet-search"><label for="fleetQuery">Search miners</label>
<input id="fleetQuery" type="search" autocomplete="off"
placeholder="Asset ID, MAC, IP, model, or serial"></div>
<div class="field"><label for="fleetLifecycle">Lifecycle</label>
<select id="fleetLifecycle"><option value="">All lifecycle states</option>
<option value="active">Active</option><option value="spare">Spare</option>
<option value="in_repair">In repair</option><option value="retired">Retired</option></select></div>
<div class="field"><label for="fleetModel">Model</label>
<input id="fleetModel" type="text" autocomplete="off" placeholder="Any model"></div>
<div class="field"><label for="fleetRuntime">Runtime</label>
<select id="fleetRuntime"><option value="">All runtime states</option>
<option value="online">Online</option><option value="offline">Offline</option>
<option value="unknown">Unknown</option></select></div>
<div class="field"><label for="fleetRepair">Repair</label>
<select id="fleetRepair"><option value="">Any repair state</option>
<option value="active">Active repair</option><option value="none">No active repair</option></select></div>
<div class="field"><label for="fleetOperational">Operational state</label>
<select id="fleetOperational"><option value="">All operational states</option>
<option value="online">Online</option><option value="offline">Offline</option>
<option value="unstable">Hash unstable</option><option value="repair">In repair</option>
<option value="spare">Spare</option><option value="retired">Retired</option>
<option value="unknown">Unknown</option></select></div>
{location_filters}
<div class="field"><label for="fleetTags">Tags</label>
<input id="fleetTags" type="text" autocomplete="off" placeholder="Comma separated"></div>
<label class="check-control" for="fleetProblems">
<input id="fleetProblems" type="checkbox"> Problems only</label>
<div class="fleet-filter-actions">
<button class="command-button" type="submit">Apply</button>
<button class="quiet-button" id="fleetReset" type="button">Reset</button>
</div>
<button class="command-button" type="submit">Search</button>
</form>
{_state_messages("miners")}
<div class="table-wrap" data-view-state="ready" hidden>
<table>
<thead><tr><th>Asset ID</th><th>Current MAC</th><th>Model</th>
<th>Lifecycle</th><th>Runtime</th><th>Location</th></tr></thead>
<tbody id="minerRows"></tbody>
</table>
<div data-view-state="ready" hidden>
<p class="fleet-result-summary" id="fleetResultSummary" aria-live="polite"></p>
<div class="table-wrap fleet-table-wrap">
<table class="fleet-table">
<thead><tr><th>Miner</th><th>Operational</th><th>Model / Serial</th>
<th>Network</th><th>Location</th><th>Hashrate</th><th>Power</th>
<th>Max chip temp</th><th>Lifecycle / Repair</th><th>Actions</th></tr></thead>
<tbody id="fleetRows"></tbody>
</table>
</div>
</div>
<nav class="fleet-pagination" aria-label="Fleet pages">
<button class="quiet-button" id="fleetPrevious" type="button" disabled>Previous</button>
<label for="fleetLimit">Rows per page</label>
<select id="fleetLimit"><option value="25">25</option><option value="50">50</option>
<option value="100">100</option><option value="250">250</option></select>
<button class="quiet-button" id="fleetNext" type="button" disabled>Next</button>
</nav>
</section>
"""
@@ -297,11 +336,15 @@ def render_app_page(
content = (
_map_content(has_permission("locations.manage"))
if view == "map"
else _miners_content(has_permission("locations.view"))
if view == "miners"
else CONTENT[view]()
)
map_script = (
page_script = (
'<script src="/assets/minerops-mine-map.js" defer></script>'
if view == "map"
else '<script src="/assets/minerops-fleet.js" defer></script>'
if view == "miners"
else ""
)
return f"""<!doctype html>
@@ -313,7 +356,8 @@ def render_app_page(
<link rel="stylesheet" href="/assets/minerops-app.css">
</head>
<body data-view="{escape(view)}"
data-can-view-miners="{str(can_view_miners).lower()}">
data-can-view-miners="{str(can_view_miners).lower()}"
data-can-view-locations="{str(has_permission("locations.view")).lower()}">
<a class="skip-link" href="#mainContent">Skip to main content</a>
<div class="app-shell">
<header class="topbar">
@@ -358,6 +402,6 @@ def render_app_page(
</main>
</div>
<script src="/assets/minerops-app.js" defer></script>
{map_script}
{page_script}
</body>
</html>"""