feat: add Mine Map v1
This commit is contained in:
1 parent
548051899e
commit
60a831950c
16 files changed
+2027
-163
No files matched your search
@@ -79,48 +79,93 @@ def _dashboard_content() -> str:
|
||||
"""
|
||||
|
||||
|
||||
def _map_content() -> str:
|
||||
return """
|
||||
<div class="notice" role="status">Live layout and telemetry data are not
|
||||
connected to this Internal Alpha canvas. No production state is represented
|
||||
below.</div>
|
||||
<section class="map-layout" aria-label="Mine Map placeholder">
|
||||
<aside class="map-controls" aria-labelledby="mapHierarchyTitle">
|
||||
<h2 id="mapHierarchyTitle">Location hierarchy</h2>
|
||||
<ul class="hierarchy">
|
||||
<li>Site</li>
|
||||
<li>Room</li>
|
||||
<li>Rack</li>
|
||||
<li>Shelf / position</li>
|
||||
</ul>
|
||||
<div class="control-stack">
|
||||
<div class="field">
|
||||
<label for="statusLayer">Status layer</label>
|
||||
<select id="statusLayer" disabled>
|
||||
<option>Live status unavailable</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="mapFilter">Filters</label>
|
||||
<input id="mapFilter" type="search"
|
||||
placeholder="Filters not connected" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="map-viewport" aria-label="Operational map viewport">
|
||||
<div class="map-empty">
|
||||
<strong>Operational canvas</strong>
|
||||
<span>Site, room, rack, miner, and status overlays will render here
|
||||
when authoritative layout and telemetry sources are connected.</span>
|
||||
def _map_content(can_manage_locations: bool = False) -> str:
|
||||
manage_link = (
|
||||
'<a class="object-link" href="/locations">Manage locations</a>'
|
||||
if can_manage_locations
|
||||
else ""
|
||||
)
|
||||
return f"""
|
||||
<section class="map-context" aria-labelledby="mapControlsTitle">
|
||||
<div class="map-context-head">
|
||||
<div>
|
||||
<h2 id="mapControlsTitle">Map context</h2>
|
||||
<p id="mapScope" class="panel-copy">Select a site and room.</p>
|
||||
</div>
|
||||
{manage_link}
|
||||
</div>
|
||||
<aside class="map-inspector" aria-labelledby="selectionTitle">
|
||||
<h2 id="selectionTitle">Selection details</h2>
|
||||
<dl class="definition-list">
|
||||
<dt>Selection</dt><dd>None</dd>
|
||||
<dt>Status</dt><dd>Not connected</dd>
|
||||
<dt>Freshness</dt><dd>Unavailable</dd>
|
||||
</dl>
|
||||
<div class="map-control-grid">
|
||||
<div class="field"><label for="mapSite">Site</label>
|
||||
<select id="mapSite" aria-describedby="mapScope"><option>Loading sites...</option></select></div>
|
||||
<div class="field"><label for="mapRoom">Room</label>
|
||||
<select id="mapRoom"><option>Loading rooms...</option></select></div>
|
||||
<div class="field"><label for="mapLayer">Layer</label>
|
||||
<select id="mapLayer">
|
||||
<option value="status">Operational Status</option>
|
||||
<option value="temperature">Temperature</option>
|
||||
<option value="hashrate">Hashrate</option>
|
||||
<option value="power">Power</option>
|
||||
<option value="repair">Repair State</option>
|
||||
</select></div>
|
||||
<div class="field map-search"><label for="mapSearch">Find miner</label>
|
||||
<input id="mapSearch" type="search" autocomplete="off"
|
||||
placeholder="Asset ID, serial, MAC, or IP"></div>
|
||||
</div>
|
||||
<details class="map-filters" id="mapFilters">
|
||||
<summary>Filters <span id="mapFilterCount" aria-live="polite"></span></summary>
|
||||
<div class="map-filter-grid">
|
||||
<div class="field"><label for="mapLifecycle">Lifecycle</label>
|
||||
<select id="mapLifecycle"><option value="">All lifecycle states</option></select></div>
|
||||
<div class="field"><label for="mapModel">Model</label>
|
||||
<select id="mapModel"><option value="">All models</option></select></div>
|
||||
<div class="field"><label for="mapRepair">Repair</label>
|
||||
<select id="mapRepair"><option value="">All repair states</option><option value="active">Active repair</option><option value="none">No active repair</option><option value="legacy">Legacy repair location</option></select></div>
|
||||
<div class="field"><label for="mapStatus">Operational status</label>
|
||||
<select id="mapStatus"><option value="">All operational states</option></select></div>
|
||||
<button id="mapClearFilters" class="quiet-button" type="button">Clear filters</button>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
<section class="map-status-band" aria-label="Map summary">
|
||||
<div id="mapSummary" class="map-summary" aria-live="polite"></div>
|
||||
<div id="mapFreshness" class="freshness">Waiting for map data</div>
|
||||
</section>
|
||||
<div id="mapCriticalState" class="state-message" data-kind="error" role="alert" hidden>
|
||||
<strong id="mapCriticalTitle">Map unavailable</strong>
|
||||
<span id="mapCriticalDetail">The map could not be loaded.</span>
|
||||
<button id="mapRetry" class="command-button" type="button">Retry</button>
|
||||
</div>
|
||||
<div id="mapLoading" class="state-message" role="status">
|
||||
<strong>Loading Mine Map</strong>
|
||||
<span>Requesting the current physical layout and operational observations.</span>
|
||||
</div>
|
||||
<section class="mine-map-workspace" id="mapWorkspace" hidden>
|
||||
<div class="map-main-column">
|
||||
<div id="mapLegend" class="map-legend" aria-label="Map legend"></div>
|
||||
<div id="mapNotice" class="map-notice" role="status" hidden></div>
|
||||
<div id="mapViewport" class="map-viewport-v1"
|
||||
aria-label="Room racks, shelves, and positions"></div>
|
||||
<section id="mapLegacySection" class="unplaced-section" hidden
|
||||
aria-labelledby="mapLegacyTitle">
|
||||
<div class="panel-head"><div><h2 id="mapLegacyTitle">Unscoped legacy positions</h2>
|
||||
<p class="panel-copy">Stored positions without a durable room relationship. They are not assigned to the selected site or room.</p></div></div>
|
||||
<div id="mapLegacyPositions" class="legacy-position-list"></div>
|
||||
</section>
|
||||
<section id="mapUnplacedSection" class="unplaced-section" hidden
|
||||
aria-labelledby="unplacedTitle">
|
||||
<div class="panel-head"><div><h2 id="unplacedTitle">Unplaced and operational areas</h2>
|
||||
<p class="panel-copy">Miner records without a configured rack position.</p></div></div>
|
||||
<div id="mapUnplaced"></div>
|
||||
</section>
|
||||
</div>
|
||||
<aside id="mapInspector" class="map-inspector-v1" aria-labelledby="mapInspectorTitle" hidden>
|
||||
<div class="inspector-head">
|
||||
<div><p class="eyebrow">Selected position</p><h2 id="mapInspectorTitle">Miner details</h2></div>
|
||||
<button id="mapInspectorClose" class="icon-button" type="button" aria-label="Close miner details" title="Close miner details">X</button>
|
||||
</div>
|
||||
<p id="mapInspectorStatus" class="status-callout"></p>
|
||||
<dl id="mapInspectorDetails" class="definition-list inspector-details"></dl>
|
||||
<a id="mapOpenMiner" class="command-link" href="/assets/miners">Open Miner</a>
|
||||
</aside>
|
||||
</section>
|
||||
"""
|
||||
@@ -242,7 +287,16 @@ def render_app_page(
|
||||
if can_view_miners
|
||||
else '<div class="global-search-unavailable" aria-hidden="true"></div>'
|
||||
)
|
||||
content = CONTENT[view]()
|
||||
content = (
|
||||
_map_content(has_permission("locations.manage"))
|
||||
if view == "map"
|
||||
else CONTENT[view]()
|
||||
)
|
||||
map_script = (
|
||||
'<script src="/assets/minerops-mine-map.js" defer></script>'
|
||||
if view == "map"
|
||||
else ""
|
||||
)
|
||||
return f"""<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -297,5 +351,6 @@ def render_app_page(
|
||||
</main>
|
||||
</div>
|
||||
<script src="/assets/minerops-app.js" defer></script>
|
||||
{map_script}
|
||||
</body>
|
||||
</html>"""
|
||||
Reference in new issue
Block a user