apps/dashboard/backend/app/services/aggregator.py aktualisiert

This commit is contained in:
2026-04-06 12:12:50 +00:00
parent 98e0a55e36
commit 08f7025a4c
@@ -120,7 +120,7 @@ class AggregatorService:
async def get_immich(self) -> ImmichSnapshot: async def get_immich(self) -> ImmichSnapshot:
return await self.cache.get_or_load( return await self.cache.get_or_load(
"immich", "immich",
self.settings.cache_ttl_storage_seconds, self.settings.cache_ttl_services_seconds,
self.immich_client.fetch_stats, self.immich_client.fetch_stats,
) )
@@ -191,19 +191,42 @@ class AggregatorService:
async def _build_storage(self) -> StorageResponse: async def _build_storage(self) -> StorageResponse:
snapshot = await self.beszel_client.fetch_system_snapshot() snapshot = await self.beszel_client.fetch_system_snapshot()
now = datetime.now(timezone.utc) now = datetime.now(timezone.utc)
disks = [self._map_disk(d, snapshot.source_status) for d in snapshot.disks] disks = [self._map_disk(d, snapshot.source_status) for d in snapshot.disks]
total_used = sum(d.used_gb for d in snapshot.disks) storage_source_status = snapshot.source_status
total_size = sum(d.total_gb for d in snapshot.disks) if snapshot.source_status == "online" and not disks:
total_free = sum(d.free_gb for d in snapshot.disks) storage_source_status = "unsupported"
overall_pct = round(total_used / total_size * 100, 1) if total_size > 0 else 0.0
if disks:
root = next((d for d in disks if d.mount == "/"), disks[0])
else:
root = StorageDisk(
name="rootfs",
mount="/",
used_gb=0.0,
total_gb=0.0,
free_gb=0.0,
usage_percent=0.0,
status="offline" if snapshot.source_status == "offline" else "online",
)
critical_count = sum(1 for d in disks if d.status == "critical")
warning_count = sum(1 for d in disks if d.status == "warning")
overall_status: OverallStatus = (
"offline" if snapshot.source_status == "offline"
else ("degraded" if critical_count or warning_count else "online")
)
return StorageResponse( return StorageResponse(
generated_at=now, generated_at=now,
summary=StorageSummary( summary=StorageSummary(
total_used_gb=round(total_used, 2), overall_status=overall_status,
total_size_gb=round(total_size, 2), source_status=storage_source_status,
total_free_gb=round(total_free, 2), critical_disks=critical_count,
overall_usage_percent=overall_pct, warning_disks=warning_count,
total_disks=len(disks),
), ),
root=root,
disks=disks, disks=disks,
) )
@@ -214,33 +237,41 @@ class AggregatorService:
) )
now = datetime.now(timezone.utc) now = datetime.now(timezone.utc)
monitor_by_name = {m.name.lower(): m for m in uk_snap.monitors} monitor_by_name = {
self._normalize_identifier(m.name): m for m in uk_snap.monitors
}
docker_by_name = {
self._normalize_identifier(c.name): c for c in docker_snap.containers
}
items: list[ServiceItem] = [] items: list[ServiceItem] = []
for container in docker_snap.containers: merged_names = sorted(set(docker_by_name) | set(monitor_by_name))
name_lower = container.name.lower() for norm in merged_names:
monitor = monitor_by_name.get(name_lower) container = docker_by_name.get(norm)
overall = self._resolve_overall_status(container.state, monitor) monitor = monitor_by_name.get(norm)
status = self._resolve_overall_status(
container.state if container else "unknown", monitor
)
items.append(ServiceItem( items.append(ServiceItem(
name=container.name, id=norm,
docker_state=container.state, name=monitor.name if monitor else container.name,
uptime_kuma_status=monitor.status if monitor else None, kind="service",
overall_status=overall, status=status,
health=self._status_to_health(overall), health=self._status_to_health(status),
latency_ms=monitor.latency_ms if monitor else None,
docker_state=container.state if container else "unknown",
url=None,
source="uptime_kuma" if monitor else "docker",
last_checked=now.isoformat(),
)) ))
statuses: list[OverallStatus] = [i.overall_status for i in items] statuses = [i.status for i in items]
summary_status = self._aggregate_statuses(statuses) overall = self._aggregate_statuses(statuses)
return ServicesResponse( return ServicesResponse(
generated_at=now, generated_at=now,
summary=ServicesSummary( summary=ServicesSummary(
overall_status=summary_status, overall_status=overall,
total=len(items),
online=sum(1 for s in statuses if s == "online"),
degraded=sum(1 for s in statuses if s == "degraded"),
offline=sum(1 for s in statuses if s == "offline"),
),
docker=ServicesDockerSummary( docker=ServicesDockerSummary(
running=docker_snap.running, running=docker_snap.running,
stopped=docker_snap.stopped, stopped=docker_snap.stopped,
@@ -251,9 +282,12 @@ class AggregatorService:
uptime_kuma=ServicesUptimeKumaSummary( uptime_kuma=ServicesUptimeKumaSummary(
monitors_up=uk_snap.monitors_up, monitors_up=uk_snap.monitors_up,
monitors_down=uk_snap.monitors_down, monitors_down=uk_snap.monitors_down,
monitors_paused=uk_snap.monitors_paused,
total=uk_snap.total,
source_status=uk_snap.source_status, source_status=uk_snap.source_status,
), ),
items=items, ),
services=items,
) )
async def _build_overview(self) -> OverviewResponse: async def _build_overview(self) -> OverviewResponse:
@@ -267,8 +301,11 @@ class AggregatorService:
statuses: list[OverallStatus] = [] statuses: list[OverallStatus] = []
for container in docker_snap.containers: for container in docker_snap.containers:
name_lower = container.name.lower() name_lower = self._normalize_identifier(container.name)
monitor = next((m for m in uk_snap.monitors if m.name.lower() == name_lower), None) monitor = next(
(m for m in uk_snap.monitors if self._normalize_identifier(m.name) == name_lower),
None,
)
statuses.append(self._resolve_overall_status(container.state, monitor)) statuses.append(self._resolve_overall_status(container.state, monitor))
overall = self._aggregate_statuses(statuses) overall = self._aggregate_statuses(statuses)
@@ -293,7 +330,7 @@ class AggregatorService:
system=OverviewSystemSummary( system=OverviewSystemSummary(
cpu_percent=system_snap.cpu_usage_percent, cpu_percent=system_snap.cpu_usage_percent,
ram_percent=system_snap.memory_usage_percent, ram_percent=system_snap.memory_usage_percent,
root_storage_percent=system_snap.disks[0].usage_percent if system_snap.disks else 0, root_storage_percent=system_snap.disks[0].usage_percent if system_snap.disks else 0.0,
network_rx_mbps=system_snap.network_rx_mbps, network_rx_mbps=system_snap.network_rx_mbps,
network_tx_mbps=system_snap.network_tx_mbps, network_tx_mbps=system_snap.network_tx_mbps,
uptime_seconds=system_snap.uptime_seconds, uptime_seconds=system_snap.uptime_seconds,
@@ -303,10 +340,14 @@ class AggregatorService:
label=ha_snap.label, label=ha_snap.label,
version=ha_snap.version, version=ha_snap.version,
response_time_ms=ha_snap.response_time_ms, response_time_ms=ha_snap.response_time_ms,
last_checked=ha_snap.last_checked, last_checked=ha_snap.last_checked.isoformat() if ha_snap.last_checked else None,
), ),
) )
@staticmethod
def _normalize_identifier(value: str) -> str:
return "".join(ch.lower() for ch in value if ch.isalnum())
@staticmethod @staticmethod
def _resolve_overall_status( def _resolve_overall_status(
docker_state: str, docker_state: str,
@@ -317,7 +358,6 @@ class AggregatorService:
return "offline" return "offline"
if monitor.status == "degraded": if monitor.status == "degraded":
return "degraded" return "degraded"
if docker_state == "unhealthy": if docker_state == "unhealthy":
return "degraded" return "degraded"
if docker_state == "stopped": if docker_state == "stopped":
@@ -342,7 +382,8 @@ class AggregatorService:
elif disk.usage_percent >= 75: elif disk.usage_percent >= 75:
status = "warning" status = "warning"
else: else:
status = "healthy" status = "online"
return StorageDisk( return StorageDisk(
name=disk.name, name=disk.name,
mount=disk.mount, mount=disk.mount,
@@ -358,9 +399,9 @@ class AggregatorService:
normalized = list(statuses) normalized = list(statuses)
if not normalized: if not normalized:
return "offline" return "offline"
if any(status == "offline" for status in normalized): if any(s == "offline" for s in normalized):
return "degraded" if any(status == "online" for status in normalized) else "offline" return "degraded" if any(s == "online" for s in normalized) else "offline"
if any(status in {"degraded", "warning", "critical"} for status in normalized): if any(s in {"degraded", "warning", "critical"} for s in normalized):
return "degraded" return "degraded"
return "online" return "online"