Coverage for src / lexigram / admin / controllers / infrastructure.py: 48%
44 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-13 22:14 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-13 22:14 +0800
1"""Infrastructure center controller — cluster landing page.
3Shows an overview of the areas contributed to the ``infrastructure``
4navigation group (web, sql, cache, events, queue, tasks) as linked cards.
5The secondary nav column renders inside the content section next to the
6overview (settings ConfigLayout pattern).
7"""
9from __future__ import annotations
11from typing import TYPE_CHECKING, Any
13from starlette.requests import Request
14from starlette.responses import Response
16from lexigram.admin.controllers.base import AdminController
17from lexigram.contracts.web import get
18from lexigram.logging import get_logger
19from lexigram.ui import el
21if TYPE_CHECKING:
22 from lexigram.admin.engine.renderer import AdminRenderer
23 from lexigram.admin.services.settings_service import AdminSettingsService
25logger = get_logger(__name__)
27__all__ = ["InfrastructureController"]
29_AREA_DESCRIPTIONS: dict[str, str] = {
30 "web": "HTTP routing, middleware, and web API endpoints.",
31 "sql": "Database connections, queries, and schema management.",
32 "cache": "Cache backends, keys, and TTL policies.",
33 "events": "Event bus, subscriptions, and message delivery.",
34 "queue": "Background job queue and worker management.",
35 "tasks": "Scheduled tasks, cron jobs, and automation.",
36}
38_DEFAULT_AREA_DESCRIPTION = "Manage and monitor this infrastructure area."
41class InfrastructureController(AdminController):
42 """Landing page for the Infrastructure center.
44 Routes:
45 GET /admin/infrastructure - Overview of cluster areas
46 """
48 prefix = "/infrastructure"
50 def __init__(
51 self,
52 renderer: AdminRenderer,
53 settings_service: AdminSettingsService | None = None,
54 **kwargs: Any,
55 ) -> None:
56 super().__init__(renderer=renderer, settings_service=settings_service, **kwargs)
58 @get("/")
59 async def index(self, request: Request) -> Response:
60 from lexigram.admin.engine.renderer import resolve_admin_nav
61 from lexigram.admin.navigation.clusters import cluster_items
62 from lexigram.admin.ui.organisms.secondary_nav import ClusterLayout
64 state = getattr(request, "app", None)
65 groups = (
66 getattr(state.state, "assembler_groups", None)
67 if state and hasattr(state, "state")
68 else None
69 )
70 content = self._render_overview(cluster_items(groups))
71 _, _, secondary_nav = resolve_admin_nav(request)
72 if secondary_nav:
73 content = ClusterLayout(items=secondary_nav, content=content).render()
74 return await self.render_admin(
75 request,
76 content,
77 title="Infrastructure",
78 breadcrumbs=self.generate_breadcrumbs(
79 ("Home", "/admin/"),
80 current="Infrastructure",
81 ),
82 )
84 def _render_overview(self, items: list[Any]) -> Any:
85 header = el(
86 "div",
87 el(
88 "h1",
89 "Infrastructure",
90 class_="text-2xl font-bold text-foreground",
91 ),
92 el(
93 "p",
94 "Monitor and manage the services powering your application: web, data, and runtime areas.",
95 class_="text-muted-foreground mt-1",
96 ),
97 class_="mb-2",
98 )
100 if not items:
101 return el(
102 "div",
103 header,
104 el(
105 "div",
106 el("div", "⚙️", class_="text-5xl mb-4"),
107 el(
108 "h3",
109 "No Infrastructure Areas",
110 class_="text-lg font-semibold text-foreground",
111 ),
112 el(
113 "p",
114 "Install framework packages (web, sql, cache, events, queue, tasks) to populate this center.",
115 class_="text-muted-foreground mt-2 max-w-sm",
116 ),
117 class_="text-center py-16",
118 ),
119 class_="space-y-6",
120 )
122 cards = [self._render_card(item) for item in items]
123 return el(
124 "div",
125 header,
126 el(
127 "div",
128 *cards,
129 class_="grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-3 gap-6",
130 ),
131 class_="space-y-6",
132 )
134 def _render_card(self, item: Any) -> Any:
135 from lexigram.admin.navigation.clusters import cluster_child_href
137 child_links = [
138 el(
139 "a",
140 el(
141 "span",
142 child.label,
143 class_="truncate",
144 ),
145 href=cluster_child_href(child.url),
146 class_="block px-3 py-1.5 text-sm text-muted-foreground hover:text-primary-600 dark:hover:text-primary-400 transition-colors",
147 )
148 for child in item.children
149 ]
150 return el(
151 "div",
152 el(
153 "a",
154 el(
155 "div",
156 self._render_icon(item.icon),
157 class_="w-10 h-10 rounded-lg bg-primary-50 dark:bg-primary-900/30 flex items-center justify-center mb-3",
158 ),
159 el(
160 "h3",
161 item.label,
162 class_="text-base font-semibold text-foreground",
163 ),
164 el(
165 "p",
166 _AREA_DESCRIPTIONS.get(
167 item.label.lower(), _DEFAULT_AREA_DESCRIPTION
168 ),
169 class_="text-sm text-muted-foreground mt-1",
170 ),
171 href=cluster_child_href(item.url),
172 class_="block",
173 ),
174 (
175 el("div", *child_links, class_="mt-3 space-y-1")
176 if child_links
177 else el(
178 "p",
179 "Open area",
180 class_="mt-2 text-sm text-muted-foreground",
181 )
182 ),
183 class_="block bg-card rounded-xl border border-border p-5 hover:border-primary/50 transition-colors",
184 )
186 def _render_icon(self, icon_name: str) -> Any:
187 try:
188 from lexigram.ui import get_icon
190 return get_icon(
191 icon_name, class_name="w-5 h-5 text-primary-600 dark:text-primary-400"
192 )
193 except (ImportError, ModuleNotFoundError, AttributeError):
194 return el("span", "●", class_="text-muted-foreground")