aegis-stack 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aegis-stack might be problematic. Click here for more details.
- aegis/__init__.py +5 -0
- aegis/__main__.py +374 -0
- aegis/core/CLAUDE.md +365 -0
- aegis/core/__init__.py +6 -0
- aegis/core/components.py +115 -0
- aegis/core/dependency_resolver.py +119 -0
- aegis/core/template_generator.py +163 -0
- aegis/templates/CLAUDE.md +306 -0
- aegis/templates/cookiecutter-aegis-project/cookiecutter.json +27 -0
- aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +172 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +70 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +127 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +211 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py +321 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py +638 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py +41 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +134 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +247 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +14 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/tasks.py.j2 +596 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +133 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +16 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +190 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/theme.py +46 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py +687 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py +138 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md +213 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +48 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +41 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +36 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +526 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +32 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +279 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +60 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py +67 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +85 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +61 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +661 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +269 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1105 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +169 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +195 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +414 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +156 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +104 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +239 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +76 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +81 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +376 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +633 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +665 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +602 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +96 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +224 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +50 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
- aegis_stack-0.1.0.dist-info/METADATA +114 -0
- aegis_stack-0.1.0.dist-info/RECORD +103 -0
- aegis_stack-0.1.0.dist-info/WHEEL +4 -0
- aegis_stack-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections.abc import Awaitable, Callable
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import flet as ft
|
|
7
|
+
|
|
8
|
+
from app.services.system import get_system_status
|
|
9
|
+
from app.services.system.ui import get_status_icon, get_component_label
|
|
10
|
+
|
|
11
|
+
from .core.theme import ThemeManager
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_frontend_app() -> Callable[[ft.Page], Awaitable[None]]:
|
|
15
|
+
"""Returns the Flet target function - system health dashboard"""
|
|
16
|
+
|
|
17
|
+
async def flet_main(page: ft.Page) -> None:
|
|
18
|
+
page.title = "Aegis Stack - System Dashboard"
|
|
19
|
+
page.padding = 20
|
|
20
|
+
page.scroll = ft.ScrollMode.AUTO
|
|
21
|
+
|
|
22
|
+
# Initialize theme system
|
|
23
|
+
theme_manager = ThemeManager(page)
|
|
24
|
+
await theme_manager.initialize_themes()
|
|
25
|
+
|
|
26
|
+
# Theme toggle button
|
|
27
|
+
theme_button = ft.IconButton(
|
|
28
|
+
icon=ft.Icons.DARK_MODE,
|
|
29
|
+
tooltip="Switch to Dark Mode",
|
|
30
|
+
icon_size=24,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
async def toggle_theme(_: Any) -> None:
|
|
34
|
+
"""Toggle theme and update button icon"""
|
|
35
|
+
await theme_manager.toggle_theme()
|
|
36
|
+
# Update button icon based on new theme
|
|
37
|
+
if theme_manager.is_dark_mode:
|
|
38
|
+
theme_button.icon = ft.Icons.LIGHT_MODE
|
|
39
|
+
theme_button.tooltip = "Switch to Light Mode"
|
|
40
|
+
else:
|
|
41
|
+
theme_button.icon = ft.Icons.DARK_MODE
|
|
42
|
+
theme_button.tooltip = "Switch to Dark Mode"
|
|
43
|
+
page.update()
|
|
44
|
+
|
|
45
|
+
theme_button.on_click = toggle_theme
|
|
46
|
+
|
|
47
|
+
# Dashboard header with theme switch
|
|
48
|
+
header = ft.Container(
|
|
49
|
+
content=ft.Row(
|
|
50
|
+
[
|
|
51
|
+
# Left side - title and subtitle
|
|
52
|
+
ft.Column(
|
|
53
|
+
[
|
|
54
|
+
ft.Text(
|
|
55
|
+
"🏛️ Aegis Stack",
|
|
56
|
+
size=36,
|
|
57
|
+
weight=ft.FontWeight.BOLD,
|
|
58
|
+
color=ft.Colors.PRIMARY,
|
|
59
|
+
),
|
|
60
|
+
ft.Text(
|
|
61
|
+
"System Health Dashboard",
|
|
62
|
+
size=18,
|
|
63
|
+
color=ft.Colors.GREY_700,
|
|
64
|
+
),
|
|
65
|
+
],
|
|
66
|
+
spacing=5,
|
|
67
|
+
),
|
|
68
|
+
# Center - status summary (will be updated)
|
|
69
|
+
ft.Container(
|
|
70
|
+
content=ft.Text(
|
|
71
|
+
"Loading...", size=16, color=ft.Colors.ON_SURFACE
|
|
72
|
+
),
|
|
73
|
+
padding=15,
|
|
74
|
+
bgcolor=ft.Colors.SURFACE,
|
|
75
|
+
border_radius=8,
|
|
76
|
+
),
|
|
77
|
+
# Right side - theme toggle
|
|
78
|
+
ft.Container(
|
|
79
|
+
content=theme_button,
|
|
80
|
+
padding=10,
|
|
81
|
+
),
|
|
82
|
+
],
|
|
83
|
+
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
|
84
|
+
),
|
|
85
|
+
margin=ft.margin.only(bottom=30),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Create responsive grid layout containers
|
|
89
|
+
# Top row - System metrics
|
|
90
|
+
metrics_row = ft.Container(
|
|
91
|
+
content=ft.Row([], spacing=15),
|
|
92
|
+
margin=ft.margin.only(bottom=20),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Middle row - Main components
|
|
96
|
+
components_row = ft.Container(
|
|
97
|
+
content=ft.Column(
|
|
98
|
+
[
|
|
99
|
+
ft.Text(
|
|
100
|
+
"🏗️ Infrastructure Components",
|
|
101
|
+
size=22,
|
|
102
|
+
weight=ft.FontWeight.BOLD,
|
|
103
|
+
color=ft.Colors.PRIMARY,
|
|
104
|
+
),
|
|
105
|
+
ft.Row([], spacing=15, wrap=True),
|
|
106
|
+
]
|
|
107
|
+
),
|
|
108
|
+
margin=ft.margin.only(bottom=20),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Bottom row - System info and queues
|
|
112
|
+
details_row = ft.Container(
|
|
113
|
+
content=ft.Row([], spacing=15),
|
|
114
|
+
margin=ft.margin.only(bottom=20),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Add components to page with new horizontal layout
|
|
118
|
+
page.add(
|
|
119
|
+
header,
|
|
120
|
+
metrics_row,
|
|
121
|
+
components_row,
|
|
122
|
+
details_row,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
async def refresh_status() -> None:
|
|
126
|
+
"""Refresh system status data using Material Design color tokens"""
|
|
127
|
+
try:
|
|
128
|
+
status = await get_system_status()
|
|
129
|
+
|
|
130
|
+
# Get current theme state directly from page (most reliable)
|
|
131
|
+
is_light_mode = page.theme_mode == ft.ThemeMode.LIGHT
|
|
132
|
+
|
|
133
|
+
# Extract aegis component and its sub-components
|
|
134
|
+
aegis_component = None
|
|
135
|
+
if "aegis" in status.components:
|
|
136
|
+
aegis_component = status.components["aegis"]
|
|
137
|
+
|
|
138
|
+
if not aegis_component or not aegis_component.sub_components:
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
components = aegis_component.sub_components
|
|
142
|
+
|
|
143
|
+
# Update header status summary
|
|
144
|
+
status_color = (
|
|
145
|
+
ft.Colors.GREEN if status.overall_healthy else ft.Colors.ERROR
|
|
146
|
+
)
|
|
147
|
+
status_icon = "✅" if status.overall_healthy else "❌"
|
|
148
|
+
status_text = (
|
|
149
|
+
"System Healthy" if status.overall_healthy else "Issues Detected"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
header.content.controls[1].content = ft.Column(
|
|
153
|
+
[
|
|
154
|
+
ft.Row(
|
|
155
|
+
[
|
|
156
|
+
ft.Text(status_icon, size=18),
|
|
157
|
+
ft.Text(
|
|
158
|
+
status_text,
|
|
159
|
+
size=16,
|
|
160
|
+
weight=ft.FontWeight.BOLD,
|
|
161
|
+
color=status_color,
|
|
162
|
+
),
|
|
163
|
+
],
|
|
164
|
+
spacing=8,
|
|
165
|
+
),
|
|
166
|
+
ft.Text(
|
|
167
|
+
f"{status.health_percentage:.1f}% • "
|
|
168
|
+
f"{len(status.healthy_components)} healthy",
|
|
169
|
+
size=12,
|
|
170
|
+
color=ft.Colors.GREY_700,
|
|
171
|
+
),
|
|
172
|
+
ft.Text(
|
|
173
|
+
f"Updated: {status.timestamp.strftime('%H:%M:%S')}",
|
|
174
|
+
size=10,
|
|
175
|
+
color=ft.Colors.GREY_700,
|
|
176
|
+
),
|
|
177
|
+
],
|
|
178
|
+
spacing=2,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Update header status container background
|
|
182
|
+
header.content.controls[1].bgcolor = ft.Colors.SURFACE
|
|
183
|
+
|
|
184
|
+
# Create system metrics cards (horizontal row)
|
|
185
|
+
metrics_cards = []
|
|
186
|
+
backend_component = components.get("backend")
|
|
187
|
+
if backend_component and backend_component.sub_components:
|
|
188
|
+
for metric_name, metric in backend_component.sub_components.items():
|
|
189
|
+
if metric.healthy:
|
|
190
|
+
bg_color = (
|
|
191
|
+
ft.Colors.GREEN_100
|
|
192
|
+
if is_light_mode
|
|
193
|
+
else ft.Colors.GREEN_900
|
|
194
|
+
)
|
|
195
|
+
text_color = (
|
|
196
|
+
ft.Colors.GREEN_800
|
|
197
|
+
if is_light_mode
|
|
198
|
+
else ft.Colors.GREEN_100
|
|
199
|
+
)
|
|
200
|
+
border_color = ft.Colors.GREEN
|
|
201
|
+
else:
|
|
202
|
+
bg_color = (
|
|
203
|
+
ft.Colors.RED_100
|
|
204
|
+
if is_light_mode
|
|
205
|
+
else ft.Colors.RED_900
|
|
206
|
+
)
|
|
207
|
+
text_color = (
|
|
208
|
+
ft.Colors.RED_800
|
|
209
|
+
if is_light_mode
|
|
210
|
+
else ft.Colors.RED_100
|
|
211
|
+
)
|
|
212
|
+
border_color = ft.Colors.ERROR
|
|
213
|
+
icon = get_status_icon(metric.status)
|
|
214
|
+
|
|
215
|
+
# Extract percentage from message
|
|
216
|
+
percentage = "0%"
|
|
217
|
+
if "%" in metric.message:
|
|
218
|
+
percentage = metric.message.split(":")[1].strip()
|
|
219
|
+
|
|
220
|
+
metrics_cards.append(
|
|
221
|
+
ft.Container(
|
|
222
|
+
content=ft.Column(
|
|
223
|
+
[
|
|
224
|
+
ft.Row(
|
|
225
|
+
[
|
|
226
|
+
ft.Text(icon, size=14),
|
|
227
|
+
ft.Text(
|
|
228
|
+
metric_name.upper(),
|
|
229
|
+
size=12,
|
|
230
|
+
weight=ft.FontWeight.BOLD,
|
|
231
|
+
color=text_color,
|
|
232
|
+
),
|
|
233
|
+
],
|
|
234
|
+
alignment=ft.MainAxisAlignment.CENTER,
|
|
235
|
+
),
|
|
236
|
+
ft.Text(
|
|
237
|
+
percentage,
|
|
238
|
+
size=20,
|
|
239
|
+
weight=ft.FontWeight.BOLD,
|
|
240
|
+
color=text_color,
|
|
241
|
+
),
|
|
242
|
+
ft.Text(
|
|
243
|
+
metric.message.split(":")[0],
|
|
244
|
+
size=10,
|
|
245
|
+
color=text_color,
|
|
246
|
+
),
|
|
247
|
+
],
|
|
248
|
+
alignment=ft.MainAxisAlignment.CENTER,
|
|
249
|
+
spacing=4,
|
|
250
|
+
),
|
|
251
|
+
padding=15,
|
|
252
|
+
bgcolor=bg_color,
|
|
253
|
+
border=ft.border.all(1, border_color),
|
|
254
|
+
border_radius=8,
|
|
255
|
+
width=120,
|
|
256
|
+
height=100,
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
metrics_row.content.controls = metrics_cards
|
|
261
|
+
|
|
262
|
+
# Create main component cards (horizontal grid)
|
|
263
|
+
component_cards = []
|
|
264
|
+
|
|
265
|
+
for comp_name, component in components.items():
|
|
266
|
+
# Show backend as a component card too, for consistency
|
|
267
|
+
|
|
268
|
+
if component.healthy:
|
|
269
|
+
bg_color = (
|
|
270
|
+
ft.Colors.GREEN_100
|
|
271
|
+
if is_light_mode
|
|
272
|
+
else ft.Colors.GREEN_900
|
|
273
|
+
)
|
|
274
|
+
text_color = (
|
|
275
|
+
ft.Colors.GREEN_800
|
|
276
|
+
if is_light_mode
|
|
277
|
+
else ft.Colors.GREEN_100
|
|
278
|
+
)
|
|
279
|
+
border_color = ft.Colors.GREEN
|
|
280
|
+
else:
|
|
281
|
+
bg_color = (
|
|
282
|
+
ft.Colors.RED_100 if is_light_mode else ft.Colors.RED_900
|
|
283
|
+
)
|
|
284
|
+
text_color = (
|
|
285
|
+
ft.Colors.RED_800 if is_light_mode else ft.Colors.RED_100
|
|
286
|
+
)
|
|
287
|
+
border_color = ft.Colors.ERROR
|
|
288
|
+
icon = get_status_icon(component.status)
|
|
289
|
+
tech_name = get_component_label(comp_name)
|
|
290
|
+
|
|
291
|
+
# Build card content
|
|
292
|
+
card_content = [
|
|
293
|
+
ft.Row(
|
|
294
|
+
[
|
|
295
|
+
ft.Text(icon, size=16),
|
|
296
|
+
ft.Column(
|
|
297
|
+
[
|
|
298
|
+
ft.Text(
|
|
299
|
+
comp_name.title(),
|
|
300
|
+
size=16,
|
|
301
|
+
weight=ft.FontWeight.BOLD,
|
|
302
|
+
color=text_color,
|
|
303
|
+
),
|
|
304
|
+
ft.Text(
|
|
305
|
+
tech_name,
|
|
306
|
+
size=12,
|
|
307
|
+
color=text_color,
|
|
308
|
+
weight=ft.FontWeight.BOLD,
|
|
309
|
+
),
|
|
310
|
+
],
|
|
311
|
+
spacing=0,
|
|
312
|
+
),
|
|
313
|
+
],
|
|
314
|
+
spacing=10,
|
|
315
|
+
),
|
|
316
|
+
ft.Text(
|
|
317
|
+
component.message[:50] + "..."
|
|
318
|
+
if len(component.message) > 50
|
|
319
|
+
else component.message,
|
|
320
|
+
size=11,
|
|
321
|
+
color=text_color,
|
|
322
|
+
),
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
# Add database-specific metadata display
|
|
326
|
+
if comp_name == "database" and component.metadata:
|
|
327
|
+
db_info = []
|
|
328
|
+
|
|
329
|
+
# Show SQLite version if available
|
|
330
|
+
if "version" in component.metadata:
|
|
331
|
+
db_info.append(f"SQLite v{component.metadata['version']}")
|
|
332
|
+
|
|
333
|
+
# Show file size if available
|
|
334
|
+
if "file_size_human" in component.metadata:
|
|
335
|
+
size = component.metadata['file_size_human']
|
|
336
|
+
db_info.append(f"Size: {size}")
|
|
337
|
+
|
|
338
|
+
# Show WAL status if available
|
|
339
|
+
if "wal_enabled" in component.metadata:
|
|
340
|
+
wal_enabled = component.metadata["wal_enabled"]
|
|
341
|
+
wal_status = "WAL" if wal_enabled else "DELETE"
|
|
342
|
+
db_info.append(f"Mode: {wal_status}")
|
|
343
|
+
|
|
344
|
+
# Show connection pool size if available
|
|
345
|
+
if "connection_pool_size" in component.metadata:
|
|
346
|
+
pool_size = component.metadata['connection_pool_size']
|
|
347
|
+
db_info.append(f"Pool: {pool_size}")
|
|
348
|
+
|
|
349
|
+
# Add database info to card if we have any
|
|
350
|
+
if db_info:
|
|
351
|
+
card_content.append(
|
|
352
|
+
ft.Container(
|
|
353
|
+
content=ft.Column(
|
|
354
|
+
[
|
|
355
|
+
ft.Text(
|
|
356
|
+
"Database Info:",
|
|
357
|
+
size=10,
|
|
358
|
+
weight=ft.FontWeight.BOLD,
|
|
359
|
+
color=text_color,
|
|
360
|
+
),
|
|
361
|
+
ft.Text(
|
|
362
|
+
" • ".join(db_info),
|
|
363
|
+
size=10,
|
|
364
|
+
color=text_color,
|
|
365
|
+
),
|
|
366
|
+
],
|
|
367
|
+
spacing=2,
|
|
368
|
+
),
|
|
369
|
+
margin=ft.margin.only(top=8),
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
# Add sub-component indicators
|
|
374
|
+
if component.sub_components:
|
|
375
|
+
sub_status = []
|
|
376
|
+
for sub_name, sub_comp in component.sub_components.items():
|
|
377
|
+
sub_icon = get_status_icon(sub_comp.status)
|
|
378
|
+
sub_status.append(f"{sub_icon} {sub_name}")
|
|
379
|
+
|
|
380
|
+
if (
|
|
381
|
+
comp_name == "worker"
|
|
382
|
+
and "queues" in component.sub_components
|
|
383
|
+
):
|
|
384
|
+
# Special handling for worker queues
|
|
385
|
+
queues_comp = component.sub_components["queues"]
|
|
386
|
+
if queues_comp.sub_components:
|
|
387
|
+
queue_icons = [
|
|
388
|
+
get_status_icon(q.status)
|
|
389
|
+
for q in queues_comp.sub_components.values()
|
|
390
|
+
]
|
|
391
|
+
card_content.append(
|
|
392
|
+
ft.Container(
|
|
393
|
+
content=ft.Row(
|
|
394
|
+
[
|
|
395
|
+
ft.Text(
|
|
396
|
+
"Queues:",
|
|
397
|
+
size=10,
|
|
398
|
+
weight=ft.FontWeight.BOLD,
|
|
399
|
+
color=text_color,
|
|
400
|
+
),
|
|
401
|
+
ft.Text(" ".join(queue_icons), size=12),
|
|
402
|
+
],
|
|
403
|
+
spacing=5,
|
|
404
|
+
),
|
|
405
|
+
margin=ft.margin.only(top=8),
|
|
406
|
+
)
|
|
407
|
+
)
|
|
408
|
+
else:
|
|
409
|
+
# Show sub-components as compact indicators
|
|
410
|
+
if len(sub_status) <= 3:
|
|
411
|
+
card_content.append(
|
|
412
|
+
ft.Container(
|
|
413
|
+
content=ft.Text(
|
|
414
|
+
" | ".join(sub_status),
|
|
415
|
+
size=10,
|
|
416
|
+
color=text_color,
|
|
417
|
+
),
|
|
418
|
+
margin=ft.margin.only(top=8),
|
|
419
|
+
)
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
component_cards.append(
|
|
423
|
+
ft.Container(
|
|
424
|
+
content=ft.Column(card_content, spacing=8),
|
|
425
|
+
padding=15,
|
|
426
|
+
bgcolor=bg_color,
|
|
427
|
+
border=ft.border.all(1, border_color),
|
|
428
|
+
border_radius=8,
|
|
429
|
+
width=240,
|
|
430
|
+
height=140,
|
|
431
|
+
)
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
components_row.content.controls[1].controls = component_cards
|
|
435
|
+
|
|
436
|
+
# Create bottom row with system info and detailed worker queues
|
|
437
|
+
bottom_cards = []
|
|
438
|
+
|
|
439
|
+
# System info card
|
|
440
|
+
info_bg_color = (
|
|
441
|
+
ft.Colors.BLUE_100 if is_light_mode else ft.Colors.BLUE_900
|
|
442
|
+
)
|
|
443
|
+
info_text_color = (
|
|
444
|
+
ft.Colors.BLUE_800 if is_light_mode else ft.Colors.BLUE_100
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
sys_info_content = [
|
|
448
|
+
ft.Text(
|
|
449
|
+
"System Info",
|
|
450
|
+
size=14,
|
|
451
|
+
weight=ft.FontWeight.BOLD,
|
|
452
|
+
color=info_text_color,
|
|
453
|
+
)
|
|
454
|
+
]
|
|
455
|
+
if status.system_info:
|
|
456
|
+
for key, value in status.system_info.items():
|
|
457
|
+
sys_info_content.append(
|
|
458
|
+
ft.Text(
|
|
459
|
+
f"{key.replace('_', ' ').title()}: {value}",
|
|
460
|
+
size=11,
|
|
461
|
+
color=info_text_color,
|
|
462
|
+
)
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
bottom_cards.append(
|
|
466
|
+
ft.Container(
|
|
467
|
+
content=ft.Column(sys_info_content, spacing=4),
|
|
468
|
+
padding=15,
|
|
469
|
+
bgcolor=info_bg_color,
|
|
470
|
+
border=ft.border.all(1, ft.Colors.PRIMARY),
|
|
471
|
+
border_radius=8,
|
|
472
|
+
width=300,
|
|
473
|
+
)
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
# Worker queues detailed card
|
|
477
|
+
worker_comp = components.get("worker")
|
|
478
|
+
if (
|
|
479
|
+
worker_comp
|
|
480
|
+
and worker_comp.sub_components
|
|
481
|
+
and "queues" in worker_comp.sub_components
|
|
482
|
+
):
|
|
483
|
+
queues_comp = worker_comp.sub_components["queues"]
|
|
484
|
+
queue_bg_color = (
|
|
485
|
+
ft.Colors.PURPLE_100 if is_light_mode else ft.Colors.PURPLE_900
|
|
486
|
+
)
|
|
487
|
+
queue_text_color = (
|
|
488
|
+
ft.Colors.PURPLE_800 if is_light_mode else ft.Colors.PURPLE_100
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
queue_content = [
|
|
492
|
+
ft.Text(
|
|
493
|
+
"Worker Queues",
|
|
494
|
+
size=14,
|
|
495
|
+
weight=ft.FontWeight.BOLD,
|
|
496
|
+
color=queue_text_color,
|
|
497
|
+
)
|
|
498
|
+
]
|
|
499
|
+
|
|
500
|
+
if queues_comp.sub_components:
|
|
501
|
+
for queue_name, queue in queues_comp.sub_components.items():
|
|
502
|
+
icon = get_status_icon(queue.status)
|
|
503
|
+
# Extract job count from message
|
|
504
|
+
job_info = ""
|
|
505
|
+
if "completed" in queue.message:
|
|
506
|
+
job_info = queue.message.split(":")[-1].strip()
|
|
507
|
+
|
|
508
|
+
queue_content.append(
|
|
509
|
+
ft.Row(
|
|
510
|
+
[
|
|
511
|
+
ft.Text(icon, size=12),
|
|
512
|
+
ft.Text(
|
|
513
|
+
queue_name,
|
|
514
|
+
size=12,
|
|
515
|
+
weight=ft.FontWeight.BOLD,
|
|
516
|
+
color=queue_text_color,
|
|
517
|
+
),
|
|
518
|
+
ft.Text(
|
|
519
|
+
job_info, size=10, color=queue_text_color
|
|
520
|
+
),
|
|
521
|
+
],
|
|
522
|
+
spacing=8,
|
|
523
|
+
)
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
bottom_cards.append(
|
|
527
|
+
ft.Container(
|
|
528
|
+
content=ft.Column(queue_content, spacing=6),
|
|
529
|
+
padding=15,
|
|
530
|
+
bgcolor=queue_bg_color,
|
|
531
|
+
border=ft.border.all(1, ft.Colors.PURPLE),
|
|
532
|
+
border_radius=8,
|
|
533
|
+
width=300,
|
|
534
|
+
)
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
# Database details card (if database component exists)
|
|
538
|
+
database_comp = components.get("database")
|
|
539
|
+
if database_comp and database_comp.metadata:
|
|
540
|
+
db_bg_color = (
|
|
541
|
+
ft.Colors.CYAN_100 if is_light_mode else ft.Colors.CYAN_900
|
|
542
|
+
)
|
|
543
|
+
db_text_color = (
|
|
544
|
+
ft.Colors.CYAN_800 if is_light_mode else ft.Colors.CYAN_100
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
db_content = [
|
|
548
|
+
ft.Text(
|
|
549
|
+
"Database Details",
|
|
550
|
+
size=14,
|
|
551
|
+
weight=ft.FontWeight.BOLD,
|
|
552
|
+
color=db_text_color,
|
|
553
|
+
)
|
|
554
|
+
]
|
|
555
|
+
|
|
556
|
+
# Show detailed database metadata
|
|
557
|
+
metadata = database_comp.metadata
|
|
558
|
+
|
|
559
|
+
# Version and implementation
|
|
560
|
+
if "version" in metadata and "implementation" in metadata:
|
|
561
|
+
db_content.append(
|
|
562
|
+
ft.Text(
|
|
563
|
+
f"{metadata['implementation'].upper()} "
|
|
564
|
+
f"v{metadata['version']}",
|
|
565
|
+
size=12,
|
|
566
|
+
weight=ft.FontWeight.BOLD,
|
|
567
|
+
color=db_text_color,
|
|
568
|
+
)
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# File info
|
|
572
|
+
if "file_size_human" in metadata and "file_size_bytes" in metadata:
|
|
573
|
+
db_content.append(
|
|
574
|
+
ft.Text(
|
|
575
|
+
f"File Size: {metadata['file_size_human']} "
|
|
576
|
+
f"({metadata['file_size_bytes']:,} bytes)",
|
|
577
|
+
size=11,
|
|
578
|
+
color=db_text_color,
|
|
579
|
+
)
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
# Connection info
|
|
583
|
+
if "connection_pool_size" in metadata:
|
|
584
|
+
db_content.append(
|
|
585
|
+
ft.Text(
|
|
586
|
+
f"Connection Pool: "
|
|
587
|
+
f"{metadata['connection_pool_size']} connections",
|
|
588
|
+
size=11,
|
|
589
|
+
color=db_text_color,
|
|
590
|
+
)
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
# SQLite PRAGMA settings
|
|
594
|
+
if "pragma_settings" in metadata:
|
|
595
|
+
pragma = metadata["pragma_settings"]
|
|
596
|
+
pragma_info = []
|
|
597
|
+
|
|
598
|
+
if "foreign_keys" in pragma:
|
|
599
|
+
fk_status = "ON" if pragma["foreign_keys"] else "OFF"
|
|
600
|
+
pragma_info.append(f"Foreign Keys: {fk_status}")
|
|
601
|
+
|
|
602
|
+
if "journal_mode" in pragma:
|
|
603
|
+
journal_mode = pragma["journal_mode"].upper()
|
|
604
|
+
pragma_info.append(f"Journal: {journal_mode}")
|
|
605
|
+
|
|
606
|
+
if "cache_size" in pragma:
|
|
607
|
+
# Remove negative sign
|
|
608
|
+
cache_size = abs(pragma["cache_size"])
|
|
609
|
+
if cache_size > 1000:
|
|
610
|
+
cache_display = f"{cache_size // 1000}K pages"
|
|
611
|
+
else:
|
|
612
|
+
cache_display = f"{cache_size} pages"
|
|
613
|
+
pragma_info.append(f"Cache: {cache_display}")
|
|
614
|
+
|
|
615
|
+
if pragma_info:
|
|
616
|
+
db_content.append(
|
|
617
|
+
ft.Text(
|
|
618
|
+
"Configuration:",
|
|
619
|
+
size=11,
|
|
620
|
+
weight=ft.FontWeight.BOLD,
|
|
621
|
+
color=db_text_color,
|
|
622
|
+
)
|
|
623
|
+
)
|
|
624
|
+
for info in pragma_info:
|
|
625
|
+
db_content.append(
|
|
626
|
+
ft.Text(
|
|
627
|
+
f" • {info}",
|
|
628
|
+
size=10,
|
|
629
|
+
color=db_text_color,
|
|
630
|
+
)
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
bottom_cards.append(
|
|
634
|
+
ft.Container(
|
|
635
|
+
content=ft.Column(db_content, spacing=4),
|
|
636
|
+
padding=15,
|
|
637
|
+
bgcolor=db_bg_color,
|
|
638
|
+
border=ft.border.all(1, ft.Colors.CYAN),
|
|
639
|
+
border_radius=8,
|
|
640
|
+
width=300,
|
|
641
|
+
)
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
details_row.content.controls = bottom_cards
|
|
645
|
+
|
|
646
|
+
page.update()
|
|
647
|
+
|
|
648
|
+
except Exception as e:
|
|
649
|
+
# Show error in header status
|
|
650
|
+
header.content.controls[1].content = ft.Column(
|
|
651
|
+
[
|
|
652
|
+
ft.Row(
|
|
653
|
+
[
|
|
654
|
+
ft.Text("❌", size=18),
|
|
655
|
+
ft.Text(
|
|
656
|
+
"Error",
|
|
657
|
+
size=16,
|
|
658
|
+
weight=ft.FontWeight.BOLD,
|
|
659
|
+
color=ft.Colors.ERROR,
|
|
660
|
+
),
|
|
661
|
+
],
|
|
662
|
+
spacing=8,
|
|
663
|
+
),
|
|
664
|
+
ft.Text(
|
|
665
|
+
str(e)[:40] + "..." if len(str(e)) > 40 else str(e),
|
|
666
|
+
size=10,
|
|
667
|
+
color=ft.Colors.GREY_700,
|
|
668
|
+
),
|
|
669
|
+
],
|
|
670
|
+
spacing=2,
|
|
671
|
+
)
|
|
672
|
+
header.content.controls[1].bgcolor = ft.Colors.SURFACE
|
|
673
|
+
page.update()
|
|
674
|
+
|
|
675
|
+
async def auto_refresh() -> None:
|
|
676
|
+
# Wait initial delay before starting auto-refresh cycle
|
|
677
|
+
await asyncio.sleep(30)
|
|
678
|
+
while True:
|
|
679
|
+
await refresh_status()
|
|
680
|
+
await asyncio.sleep(30)
|
|
681
|
+
|
|
682
|
+
# Initial load
|
|
683
|
+
await refresh_status()
|
|
684
|
+
# Start auto-refresh task (will wait 30s before first auto-refresh)
|
|
685
|
+
asyncio.create_task(auto_refresh())
|
|
686
|
+
|
|
687
|
+
return flet_main
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Scheduler component
|