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

1"""Generic cluster center controller — landing page for any cluster. 

2 

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""" 

10 

11from __future__ import annotations 

12 

13from typing import TYPE_CHECKING, Any 

14 

15from starlette.requests import Request 

16from starlette.responses import Response 

17 

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 

24 

25if TYPE_CHECKING: 

26 from lexigram.admin.engine.renderer import AdminRenderer 

27 from lexigram.admin.services.settings_service import AdminSettingsService 

28 

29logger = get_logger(__name__) 

30 

31__all__ = ["ClusterCenterController"] 

32 

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} 

41 

42_DEFAULT_AREA_DESCRIPTION = "Manage and monitor this infrastructure area." 

43 

44 

45class ClusterCenterController(AdminController): 

46 """Landing page for a cluster center. 

47 

48 Routes: 

49 GET /admin/{slug} - Overview of cluster areas 

50 """ 

51 

52 prefix = "/cluster" 

53 

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}" 

64 

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 

71 

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 ) 

97 

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 ) 

113 

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 ) 

131 

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 ) 

142 

143 def _render_card(self, item: Any) -> Any: 

144 from lexigram.admin.navigation.clusters import cluster_child_href 

145 

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 ) 

195 

196 def _render_icon(self, icon_name: str) -> Any: 

197 try: 

198 from lexigram.ui import get_icon 

199 

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")