feat: add Fleet operations view
This commit is contained in:
1 parent
c6d01df31c
commit
786a1ab910
12 files changed
+1062
-148
No files matched your search
@@ -59,6 +59,101 @@ MINER_SELECT = f"""
|
||||
) tag_data ON true
|
||||
"""
|
||||
|
||||
ACTIVE_REPAIR_STATUSES = (
|
||||
"OPEN",
|
||||
"IN_PROGRESS",
|
||||
"WAITING_FOR_PARTS",
|
||||
"READY_FOR_TEST",
|
||||
)
|
||||
|
||||
OPERATIONAL_STATE_SQL = """
|
||||
CASE
|
||||
WHEN active_repair.id IS NOT NULL OR miners.lifecycle_status = 'in_repair'
|
||||
THEN 'repair'
|
||||
WHEN miners.lifecycle_status = 'retired' THEN 'retired'
|
||||
WHEN miners.lifecycle_status = 'spare' THEN 'spare'
|
||||
WHEN lower(trim(miners.status)) = 'offline' THEN 'offline'
|
||||
WHEN latest.hash_stable = false THEN 'unstable'
|
||||
WHEN lower(trim(miners.status)) = 'online' THEN 'online'
|
||||
ELSE 'unknown'
|
||||
END
|
||||
"""
|
||||
|
||||
FLEET_SELECT = f"""
|
||||
SELECT
|
||||
miners.id, miners.asset_tag, miners.current_mac,
|
||||
host(miners.current_ip) AS current_ip, miners.model, miners.miner_sn,
|
||||
miners.lifecycle_status, miners.status, miners.created_at,
|
||||
miners.updated_at, miners.last_seen_at, tag_data.tags,
|
||||
latest.observed_at, latest.hashrate_ths, latest.power_w,
|
||||
latest.chip_temp_max_c, latest.hash_stable,
|
||||
active_repair.id AS active_repair_id,
|
||||
active_repair.status AS active_repair_status,
|
||||
active_repair.reason AS active_repair_reason,
|
||||
{{location_fields}}
|
||||
FROM miners
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COALESCE(
|
||||
jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', tags.id, 'name', tags.name,
|
||||
'created_at', tags.created_at, 'updated_at', tags.updated_at
|
||||
) ORDER BY lower({TAG_TRIMMED_NAME_SQL}), tags.id
|
||||
), '[]'::jsonb
|
||||
) AS tags
|
||||
FROM miner_tags JOIN tags ON tags.id = miner_tags.tag_id
|
||||
WHERE miner_tags.miner_id = miners.id
|
||||
) tag_data ON true
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT snapshots.observed_at,
|
||||
ROUND((snapshots.mhs_av / 1000000.0)::numeric, 2)::float AS hashrate_ths,
|
||||
ROUND(snapshots.power_w::numeric, 2)::float AS power_w,
|
||||
ROUND(snapshots.chip_temp_max_c::numeric, 2)::float AS chip_temp_max_c,
|
||||
snapshots.hash_stable
|
||||
FROM miner_status_snapshots snapshots
|
||||
WHERE snapshots.miner_id = miners.id
|
||||
ORDER BY snapshots.observed_at DESC, snapshots.id DESC
|
||||
LIMIT 1
|
||||
) latest ON true
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT repairs.id, repairs.status, repairs.reason
|
||||
FROM repairs
|
||||
WHERE repairs.miner_id = miners.id AND repairs.status = ANY(%s)
|
||||
ORDER BY repairs.opened_at, repairs.id
|
||||
LIMIT 1
|
||||
) active_repair ON true
|
||||
{{location_join}}
|
||||
"""
|
||||
|
||||
LOCATION_FIELDS = """
|
||||
mapped.site_id, mapped.site_name, mapped.room_id, mapped.room_name,
|
||||
mapped.location_position_id, mapped.rack_label, mapped.shelf_label,
|
||||
mapped.position_label, mapped.position_index
|
||||
"""
|
||||
|
||||
REDACTED_LOCATION_FIELDS = """
|
||||
NULL::uuid AS site_id, NULL::text AS site_name,
|
||||
NULL::uuid AS room_id, NULL::text AS room_name,
|
||||
NULL::uuid AS location_position_id, NULL::text AS rack_label,
|
||||
NULL::text AS shelf_label, NULL::text AS position_label,
|
||||
NULL::integer AS position_index
|
||||
"""
|
||||
|
||||
LOCATION_JOIN = """
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT sites.id AS site_id, sites.name AS site_name, rooms.id AS room_id,
|
||||
COALESCE(rooms.name, lp.room_name) AS room_name,
|
||||
lp.id AS location_position_id, lp.rack_name AS rack_label,
|
||||
lp.shelf_name AS shelf_label, lp.position_label, lp.position_index
|
||||
FROM location_positions lp
|
||||
LEFT JOIN rooms ON rooms.id = lp.room_id
|
||||
LEFT JOIN sites ON sites.id = rooms.site_id
|
||||
WHERE lp.miner_id = miners.id
|
||||
ORDER BY lp.id
|
||||
LIMIT 1
|
||||
) mapped ON true
|
||||
"""
|
||||
|
||||
MINER_WRITE_COLUMNS = {
|
||||
"site_id",
|
||||
"room_id",
|
||||
@@ -184,8 +279,18 @@ class InventoryRepository:
|
||||
(asset_id,),
|
||||
)
|
||||
|
||||
def list_miners(self, *, limit: int = 100, offset: int = 0) -> list[dict]:
|
||||
return self.search_miners(limit=limit, offset=offset)
|
||||
def list_miners(
|
||||
self,
|
||||
*,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
include_location_details: bool = False,
|
||||
) -> list[dict]:
|
||||
return self.search_miners(
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
include_location_details=include_location_details,
|
||||
)
|
||||
|
||||
def search_miners(
|
||||
self,
|
||||
@@ -193,12 +298,20 @@ class InventoryRepository:
|
||||
query: str | None = None,
|
||||
asset_id: str | None = None,
|
||||
lifecycle_status: str | None = None,
|
||||
model: str | None = None,
|
||||
runtime_status: str | None = None,
|
||||
repair: str | None = None,
|
||||
operational_state: str | None = None,
|
||||
problems: bool = False,
|
||||
site_id: UUID | None = None,
|
||||
room_id: UUID | None = None,
|
||||
tag_names: Sequence[str] = (),
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
include_location_details: bool = False,
|
||||
) -> list[dict]:
|
||||
conditions: list[str] = []
|
||||
params: list[Any] = []
|
||||
params: list[Any] = [list(ACTIVE_REPAIR_STATUSES)]
|
||||
|
||||
if query:
|
||||
pattern = f"%{query}%"
|
||||
@@ -217,15 +330,36 @@ class InventoryRepository:
|
||||
"""
|
||||
)
|
||||
params.extend([pattern] * 8)
|
||||
|
||||
if asset_id:
|
||||
conditions.append("lower(trim(miners.asset_tag)) = lower(trim(%s))")
|
||||
params.append(asset_id)
|
||||
|
||||
if lifecycle_status:
|
||||
conditions.append("miners.lifecycle_status = %s")
|
||||
params.append(lifecycle_status)
|
||||
|
||||
if model:
|
||||
conditions.append("lower(trim(miners.model)) = lower(trim(%s))")
|
||||
params.append(model)
|
||||
if runtime_status:
|
||||
conditions.append("lower(trim(miners.status)) = lower(trim(%s))")
|
||||
params.append(runtime_status)
|
||||
if repair == "active":
|
||||
conditions.append("active_repair.id IS NOT NULL")
|
||||
elif repair == "none":
|
||||
conditions.append("active_repair.id IS NULL")
|
||||
if operational_state:
|
||||
conditions.append(f"({OPERATIONAL_STATE_SQL}) = %s")
|
||||
params.append(operational_state)
|
||||
if problems:
|
||||
conditions.append(
|
||||
f"({OPERATIONAL_STATE_SQL}) = ANY(%s)"
|
||||
)
|
||||
params.append(["offline", "unknown", "unstable", "repair"])
|
||||
if site_id:
|
||||
conditions.append("mapped.site_id = %s")
|
||||
params.append(site_id)
|
||||
if room_id:
|
||||
conditions.append("mapped.room_id = %s")
|
||||
params.append(room_id)
|
||||
for tag_name in tag_names:
|
||||
conditions.append(
|
||||
f"""
|
||||
@@ -241,6 +375,14 @@ class InventoryRepository:
|
||||
)
|
||||
params.append(tag_name)
|
||||
|
||||
location_fields = (
|
||||
LOCATION_FIELDS if include_location_details else REDACTED_LOCATION_FIELDS
|
||||
)
|
||||
location_join = LOCATION_JOIN if include_location_details else ""
|
||||
select_sql = FLEET_SELECT.format(
|
||||
location_fields=location_fields,
|
||||
location_join=location_join,
|
||||
)
|
||||
where_sql = " AND ".join(conditions) if conditions else "true"
|
||||
params.extend([limit, offset])
|
||||
|
||||
@@ -248,18 +390,44 @@ class InventoryRepository:
|
||||
with connection.cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(
|
||||
f"""
|
||||
{MINER_SELECT}
|
||||
{select_sql}
|
||||
WHERE {where_sql}
|
||||
ORDER BY
|
||||
lower(trim(miners.asset_tag)) NULLS LAST,
|
||||
miners.created_at,
|
||||
miners.id
|
||||
ORDER BY lower(trim(miners.asset_tag)) NULLS LAST,
|
||||
miners.created_at, miners.id
|
||||
LIMIT %s OFFSET %s
|
||||
""",
|
||||
params,
|
||||
)
|
||||
return [dict(row) for row in cur.fetchall()]
|
||||
|
||||
def list_location_filter_options(self) -> dict[str, list[dict]]:
|
||||
with self._connection_scope() as connection:
|
||||
with connection.cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT sites.id, sites.name
|
||||
FROM location_positions lp
|
||||
JOIN miners ON miners.id = lp.miner_id
|
||||
JOIN rooms ON rooms.id = lp.room_id
|
||||
JOIN sites ON sites.id = rooms.site_id
|
||||
GROUP BY sites.id, sites.name
|
||||
ORDER BY lower(sites.name), sites.id
|
||||
"""
|
||||
)
|
||||
sites = [dict(row) for row in cur.fetchall()]
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT rooms.id, rooms.site_id, rooms.name
|
||||
FROM location_positions lp
|
||||
JOIN miners ON miners.id = lp.miner_id
|
||||
JOIN rooms ON rooms.id = lp.room_id
|
||||
GROUP BY rooms.id, rooms.site_id, rooms.name
|
||||
ORDER BY lower(rooms.name), rooms.id
|
||||
"""
|
||||
)
|
||||
rooms = [dict(row) for row in cur.fetchall()]
|
||||
return {"sites": sites, "rooms": rooms}
|
||||
|
||||
def create_miner(self, values: Mapping[str, Any]) -> dict:
|
||||
clean_values = {
|
||||
key: value for key, value in values.items() if key in MINER_WRITE_COLUMNS
|
||||
|
||||
Reference in new issue
Block a user