89 lines
4.7 KiB
Python
89 lines
4.7 KiB
Python
"""Keyed system-health view using only the supplied snapshot."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any, Callable, Dict, Optional
|
||
|
||
import customtkinter as ctk
|
||
|
||
from antigravity_provider.router.state_store import HubSnapshot
|
||
from antigravity_provider.router.ui.components import HubCard, SectionHeader, StatusBadge
|
||
from antigravity_provider.router.ui.theme import Theme
|
||
|
||
|
||
class HealthProfileRow(HubCard):
|
||
def __init__(self, master: Any):
|
||
super().__init__(master, corner_radius=Theme.RADIUS_SM, border_color=Theme.BORDER_SUBTLE)
|
||
self.identity = ctk.CTkLabel(self, text="", font=Theme.font_body_bold(), text_color=Theme.TEXT_PRIMARY)
|
||
self.identity.grid(row=0, column=0, padx=Theme.CARD_PAD_X, pady=Theme.SPACE_SM, sticky="w")
|
||
self.models = ctk.CTkLabel(self, text="", font=Theme.font_caption(), text_color=Theme.TEXT_MUTED)
|
||
self.models.grid(row=0, column=1, padx=Theme.CARD_PAD_X, pady=Theme.SPACE_SM, sticky="w")
|
||
self.status = StatusBadge(self, "not_tested")
|
||
self.status.grid(row=0, column=2, padx=Theme.CARD_PAD_X, pady=Theme.SPACE_SM, sticky="e")
|
||
self.grid_columnconfigure(0, weight=2)
|
||
self.grid_columnconfigure(1, weight=3)
|
||
self.grid_columnconfigure(2, weight=1)
|
||
|
||
def update_profile(self, profile: Any) -> None:
|
||
identity = profile.email or profile.account_identity or profile.profile_id
|
||
self.identity.configure(text=f"{identity}\n{profile.profile_id}")
|
||
families = (
|
||
", ".join(f"{family}: {state.status_label_ru}" for family, state in profile.model_states.items())
|
||
or "Семейства моделей: Н/Д"
|
||
)
|
||
self.models.configure(text=families)
|
||
self.status.set_status(profile.health_state, profile.health_label_ru)
|
||
|
||
|
||
class HealthView(ctk.CTkFrame):
|
||
def __init__(
|
||
self, master: Any, app_state: Optional[Dict[str, Any]] = None, on_refresh: Optional[Callable] = None, **kwargs
|
||
):
|
||
super().__init__(master=master, fg_color="transparent", **kwargs)
|
||
self._rows: Dict[str, HealthProfileRow] = {}
|
||
SectionHeader(
|
||
self,
|
||
title="Состояние системы",
|
||
subtitle="Авторизация, локальное здоровье и наблюдаемые состояния моделей",
|
||
action_text="Обновить аудит",
|
||
action_cmd=on_refresh,
|
||
).pack(fill="x", padx=Theme.PAGE_PAD_X, pady=(Theme.PAGE_PAD_Y, Theme.SPACE_SM))
|
||
self.scroll = ctk.CTkScrollableFrame(self, fg_color="transparent")
|
||
self.scroll.pack(fill="both", expand=True, padx=Theme.PAGE_PAD_X, pady=(0, Theme.PAGE_PAD_Y))
|
||
self.banner = HubCard(self.scroll, border_color=Theme.BORDER_ACCENT, fg_color=Theme.DARK)
|
||
self.banner.pack(fill="x", pady=(0, Theme.SECTION_GAP))
|
||
self.title = ctk.CTkLabel(
|
||
self.banner, text="Ожидание snapshot", font=Theme.font_heading(), text_color=Theme.TEXT_ACCENT
|
||
)
|
||
self.title.pack(anchor="w", padx=Theme.CARD_PAD_X, pady=(Theme.CARD_PAD_Y, Theme.SPACE_XS))
|
||
self.summary = ctk.CTkLabel(self.banner, text="", font=Theme.font_body(), text_color=Theme.TEXT_SECONDARY)
|
||
self.summary.pack(anchor="w", padx=Theme.CARD_PAD_X, pady=(0, Theme.CARD_PAD_Y))
|
||
self.rows = ctk.CTkFrame(self.scroll, fg_color="transparent")
|
||
self.rows.pack(fill="x")
|
||
|
||
def update_data(self, snapshot: Optional[HubSnapshot] = None) -> None:
|
||
if not isinstance(snapshot, HubSnapshot):
|
||
return
|
||
readiness = snapshot.readiness
|
||
self.title.configure(text=readiness.title_ru)
|
||
warnings = " • ".join(readiness.warnings) if readiness.warnings else "Предупреждений нет"
|
||
self.summary.configure(
|
||
text=(
|
||
f"{readiness.summary_ru}\n"
|
||
f"Аккаунты {readiness.accounts_connected_count}/{readiness.total_accounts} • "
|
||
f"Роли {readiness.roles_ready_count}/{readiness.total_roles} • {warnings}"
|
||
)
|
||
)
|
||
live = set(snapshot.all_profiles)
|
||
for profile_id in list(self._rows):
|
||
if profile_id not in live:
|
||
self._rows.pop(profile_id).destroy()
|
||
ordered = sorted(snapshot.all_profiles.values(), key=lambda profile: (profile.provider, profile.display_name))
|
||
for index, profile in enumerate(ordered):
|
||
row = self._rows.get(profile.profile_id)
|
||
if row is None:
|
||
row = HealthProfileRow(self.rows)
|
||
self._rows[profile.profile_id] = row
|
||
row.update_profile(profile)
|
||
row.grid(row=index, column=0, sticky="ew", pady=Theme.SPACE_XS)
|
||
self.rows.grid_columnconfigure(0, weight=1)
|