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