"""Debian Manager — управление Debian-серверами.""" import asyncio import flet as ft from app.db.database import init_db from app.core import servers as servers_core from app.core import scheduler from app.db.models import Setting from app.db.database import get_session from app.ui.pages.dashboard import DashboardPage from app.ui.pages.servers import ServersPage from app.ui.pages.server_detail import ServerDetailPage from app.ui.pages.group_ops import GroupOpsPage from app.ui.pages.playbooks import PlaybooksPage from app.ui.pages.diff import DiffPage from app.ui.pages.gitea import GiteaPage from app.ui.pages.scheduler import SchedulerPage from app.ui.pages.backups import BackupsPage from app.ui.pages.reports import ReportsPage from app.ui.pages.portainer import PortainerPage from app.ui.pages.agent import AgentPage from app.ui.pages.proxmox_page import ProxmoxPage from app.ui.pages.wizard import WizardPage from app.ui.pages.settings import SettingsPage from app.ui.pages.tasks import TasksPage def _get_setting(key: str) -> str: with get_session() as s: row = s.get(Setting, key) return row.value if row else "" def _set_setting(key: str, value: str) -> None: with get_session() as s: row = s.get(Setting, key) or Setting(key=key) row.value = value s.add(row) s.commit() def main(page: ft.Page): page.title = "Debian Manager" page.window.width = 1400 page.window.height = 900 page.padding = 0 init_db() # тема из настроек theme = _get_setting("theme") page.theme_mode = (ft.ThemeMode.DARK if theme != "light" else ft.ThemeMode.LIGHT) # seed тестового сервера при первом запуске if not servers_core.list_servers(): servers_core.add_server( name="Test VPS", host="132.243.17.164", port=2292, username="dimon", password="123456789987654321", tags="удалённые", ) # запуск планировщика и фоновых сервисов async def _start_bg(): scheduler.start() try: from app.core import webhook, agent await webhook.start() await agent.start() except Exception: pass page.run_task(_start_bg) content = ft.Container(expand=True, padding=20) PAGES = { "dashboard": ("Дашборд", ft.Icons.DASHBOARD, DashboardPage), "servers": ("Серверы", ft.Icons.DNS, ServersPage), "wizard": ("Мастер настройки", ft.Icons.AUTO_FIX_HIGH, WizardPage), "group": ("Групповые операции", ft.Icons.GROUPS, GroupOpsPage), "scheduler": ("Планировщик", ft.Icons.SCHEDULE, SchedulerPage), "playbooks": ("Плейбуки", ft.Icons.PLAYLIST_PLAY, PlaybooksPage), "backups": ("Бэкапы", ft.Icons.BACKUP, BackupsPage), "reports": ("Отчёты", ft.Icons.SUMMARIZE, ReportsPage), "portainer": ("Portainer", ft.Icons.WIDGETS, PortainerPage), "agent": ("Агент", ft.Icons.SENSORS, AgentPage), "proxmox": ("Proxmox", ft.Icons.CLOUD_QUEUE, ProxmoxPage), "diff": ("Сравнение", ft.Icons.COMPARE_ARROWS, DiffPage), "gitea": ("Gitea Deploy", ft.Icons.ROCKET_LAUNCH, GiteaPage), "tasks": ("Журнал задач", ft.Icons.TASK_ALT, TasksPage), "settings": ("Настройки", ft.Icons.SETTINGS, SettingsPage), } state = {"route": "dashboard", "detail_server_id": None, "group_filter": "Все"} # top_bar объявлен до render, чтобы render мог его обновлять top_bar = ft.Container(padding=ft.Padding.only(left=20, top=10)) def render(): top_bar.content = group_chips() route = state["route"] if route == "server_detail": content.content = ServerDetailPage(page, state["detail_server_id"], go) elif route == "dashboard": content.content = DashboardPage(page, go, group_filter=state["group_filter"]) else: cls = PAGES[route][2] content.content = cls(page, go) nav.selected_index = list(PAGES.keys()).index(route) if route in PAGES else 0 page.update() def go(route: str, server_id=None, group_filter=None): state["route"] = route state["detail_server_id"] = server_id if group_filter is not None: state["group_filter"] = group_filter render() def toggle_theme(e): new = "light" if page.theme_mode == ft.ThemeMode.DARK else "dark" page.theme_mode = ft.ThemeMode.LIGHT if new == "light" else ft.ThemeMode.DARK _set_setting("theme", new) theme_btn.icon = ft.Icons.LIGHT_MODE if new == "dark" else ft.Icons.DARK_MODE page.update() # группы серверов для дашборда def group_chips(): servers = servers_core.list_servers() tags = sorted({t.strip() for s in servers for t in (s.tags or "").split(",") if t.strip()}) groups = ["Все"] + tags return ft.Row([ ft.Chip( label=ft.Text(g), selected=state["group_filter"] == g, on_select=lambda e, g=g: go("dashboard", group_filter=g), ) for g in groups ], spacing=6) theme_btn = ft.IconButton( ft.Icons.LIGHT_MODE if page.theme_mode == ft.ThemeMode.DARK else ft.Icons.DARK_MODE, tooltip="Сменить тему", on_click=toggle_theme, ) # NavigationRail — компактный режим (только иконки), tooltip через label nav = ft.NavigationRail( selected_index=0, label_type=ft.NavigationRailLabelType.NONE, min_width=60, destinations=[ ft.NavigationRailDestination( icon=ft.Icon(v[1], tooltip=v[0]), # tooltip на самой иконке label=v[0], ) for v in PAGES.values() ], on_change=lambda e: go(list(PAGES.keys())[e.control.selected_index]), trailing=ft.Column([theme_btn], alignment=ft.MainAxisAlignment.END), ) # верхняя панель с группами (только на дашборде) top_bar.content = group_chips() page.add( ft.Column([ top_bar, ft.Row( [nav, ft.VerticalDivider(width=1), content], expand=True, spacing=0, ), ], expand=True, spacing=0), ) render() if __name__ == "__main__": ft.app(target=main)