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

1"""Infrastructure center controller — cluster landing page. 

2 

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

8 

9from __future__ import annotations 

10 

11from typing import TYPE_CHECKING, Any 

12 

13from starlette.requests import Request 

14from starlette.responses import Response 

15 

16from lexigram.admin.controllers.base import AdminController 

17from lexigram.contracts.web import get 

18from lexigram.logging import get_logger 

19from lexigram.ui import el 

20 

21if TYPE_CHECKING: 

22 from lexigram.admin.engine.renderer import AdminRenderer 

23 from lexigram.admin.services.settings_service import AdminSettingsService 

24 

25logger = get_logger(__name__) 

26 

27__all__ = ["InfrastructureController"] 

28 

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} 

37 

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

39 

40 

41class InfrastructureController(AdminController): 

42 """Landing page for the Infrastructure center. 

43 

44 Routes: 

45 GET /admin/infrastructure - Overview of cluster areas 

46 """ 

47 

48 prefix = "/infrastructure" 

49 

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) 

57 

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 

63 

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 ) 

83 

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 ) 

99 

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 ) 

121 

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 ) 

133 

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

135 from lexigram.admin.navigation.clusters import cluster_child_href 

136 

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 ) 

185 

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

187 try: 

188 from lexigram.ui import get_icon 

189 

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