Coverage for src/lexigram/admin/dashboard/route_integrator.py: 80%

337 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-21 14:56 +0800

1from __future__ import annotations 

2 

3from collections.abc import Sequence 

4import inspect 

5import re 

6import types 

7from typing import TYPE_CHECKING, Any, cast, get_args, get_origin, get_type_hints 

8 

9from markupsafe import Markup 

10from starlette.requests import Request as StarletteRequest 

11from starlette.responses import HTMLResponse 

12 

13from lexigram.admin.navigation.clusters import ( 

14 CLUSTER_GROUP, 

15 CLUSTER_LABEL, 

16 CLUSTER_URL, 

17 cluster_child_href, 

18 cluster_items, 

19 is_cluster_path, 

20) 

21from lexigram.admin.state.context import wants_fragment 

22from lexigram.contracts.admin.page_content import PageContent 

23from lexigram.contracts.admin.types import ( 

24 ManagementPageDefinition, 

25 SettingsPanelDefinition, 

26) 

27from lexigram.contracts.admin.widget_content import EmptyContent 

28from lexigram.contracts.exceptions import UnresolvableDependencyError 

29from lexigram.logging import get_logger 

30 

31if TYPE_CHECKING: 

32 from lexigram.admin.core.routing import AdminRouter 

33 from lexigram.admin.dashboard.naming_policy import NamingPolicy 

34 from lexigram.contracts.admin.contributor import BaseAdminContributor 

35 from lexigram.contracts.core.di import ContainerResolverProtocol 

36 

37logger = get_logger(__name__) 

38 

39 

40def _strip_optional(tp: Any) -> Any: 

41 """If *tp* is ``Optional[X]`` (``Union[X, None]`` or ``X | None``), 

42 return ``X``. Otherwise return *tp* unchanged.""" 

43 origin = get_origin(tp) 

44 if origin is types.UnionType: 

45 args = get_args(tp) 

46 non_none = [a for a in args if a is not type(None)] 

47 if len(non_none) == 1: 

48 return non_none[0] 

49 return tp 

50 

51 

52_DEFAULT_PRIMARY_COLOR = "#6b7280" 

53 

54_CLUSTER_HEADER_DESCRIPTION = ( 

55 "Monitor and manage the services powering your application: web, data, " 

56 "and runtime areas." 

57) 

58 

59 

60def _cluster_header_html() -> str: 

61 """Render the cluster center top-level title + description block. 

62 

63 Mirrors the settings center header: a ``mb-2`` wrapper with the 

64 section title and a muted one-line description, rendered above the 

65 whole center layout (secondary sidebar and page content). 

66 """ 

67 from lexigram.ui import el, render_to_string 

68 

69 return render_to_string( 

70 el( 

71 "div", 

72 el("h1", CLUSTER_LABEL, class_="text-2xl font-bold text-foreground"), 

73 el( 

74 "p", 

75 _CLUSTER_HEADER_DESCRIPTION, 

76 class_="text-muted-foreground mt-1", 

77 ), 

78 class_="mb-2", 

79 ) 

80 ) 

81 

82 

83#: First header block rendered by management pages (h1 + description + 

84#: divider). Cluster pages have their own top-level header injected by the 

85#: shell wrapper, so this inline header is dropped. 

86_PAGE_HEADER_RE = re.compile( 

87 r"<h1[^>]*>.*?</h1>\s*<p[^>]*>.*?</p>\s*(?:<hr[^>]*/?>)?", 

88 re.S, 

89) 

90 

91 

92async def _resolve_primary_color(container: Any) -> str: 

93 """Resolve the saved branding primary color, best-effort. 

94 

95 Falls back to the framework default when no registry/db store is 

96 available. 

97 """ 

98 try: 

99 from lexigram.admin.settings.panel.registry import ConfigRegistry 

100 

101 registry = await container.resolve( 

102 ConfigRegistry, 

103 bypass_visibility=True, 

104 ) 

105 values = await registry.get_values("admin.branding", "db") 

106 color = values.get("primary_color") 

107 if color: 

108 return str(color) 

109 except Exception: # noqa: BLE001 — non-fatal 

110 logger.exception("admin.theme_overrides_failed") 

111 return _DEFAULT_PRIMARY_COLOR 

112 

113 

114class AdminPageHandler: 

115 """ASGI adapter that resolves a management page handler from the DI 

116 container at request time and delegates to its ``handle()`` method. 

117 

118 Starlette treats class endpoints as ASGI apps — it calls 

119 ``cls(scope, receive, send)`` which becomes ``__init__(scope, receive, 

120 send)``. Management page handlers use keyword-only constructor DI 

121 (``def __init__(self, *, repo: ..., ...)``), so direct registration 

122 always raises TypeError. This wrapper sidesteps that by storing the 

123 page **class** at route-build time and resolving an instance from the 

124 container at request time. 

125 """ 

126 

127 def __init__( 

128 self, 

129 page_cls: type, 

130 container: ContainerResolverProtocol, 

131 ) -> None: 

132 self._page_cls = page_cls 

133 self._container = container 

134 

135 async def __call__(self, scope: Any, receive: Any, send: Any) -> None: 

136 request = StarletteRequest(scope, receive, send) 

137 try: 

138 instance = await self._resolve_page() 

139 response = await instance.handle(request) 

140 if not isinstance(response, HTMLResponse): 

141 from lexigram.admin.dashboard.page_renderer import ( 

142 render_page_content, 

143 ) 

144 from lexigram.contracts.admin.page_content import PageContent 

145 

146 if isinstance(response, PageContent): 

147 response = render_page_content(response) 

148 else: 

149 logger.error( 

150 "admin_page_contract_violation", 

151 page=self._page_cls.__name__, 

152 result_type=type(response).__name__, 

153 ) 

154 response = await _placeholder_page(request, self._container) 

155 except Exception: 

156 logger.exception( 

157 "admin_page_handler_error", 

158 page=self._page_cls.__name__, 

159 ) 

160 response = await _placeholder_page(request, self._container) 

161 

162 try: 

163 is_htmx = wants_fragment(request) 

164 except KeyError: 

165 is_htmx = False 

166 if isinstance(response, HTMLResponse): 

167 response = await self._apply_cluster_header(request, response) 

168 if not is_htmx and isinstance(response, HTMLResponse): 

169 response = await self._wrap_in_shell(request, response) 

170 

171 await response(scope, receive, send) 

172 

173 async def _apply_cluster_header( 

174 self, 

175 request: StarletteRequest, 

176 response: HTMLResponse, 

177 ) -> HTMLResponse: 

178 """Drop the page's own inline title/description block. 

179 

180 Cluster pages receive a single top-level header rendered above 

181 the whole center layout (see ``_cluster_header_html``), so the 

182 page's inline header is removed. Only applies to pages living 

183 inside a cluster center (e.g. ``/admin/infrastructure/...``). 

184 """ 

185 state = getattr(request, "app", None) 

186 groups = ( 

187 getattr(state.state, "assembler_groups", None) 

188 if state and hasattr(state, "state") 

189 else None 

190 ) 

191 if not is_cluster_path(request.url.path, cluster_items(groups)): 

192 return response 

193 

194 content = ( 

195 response.body.decode() 

196 if isinstance(response.body, bytes) 

197 else str(response.body) 

198 ) 

199 return HTMLResponse(_PAGE_HEADER_RE.sub("", content, count=1)) 

200 

201 async def _wrap_in_shell( 

202 self, 

203 request: StarletteRequest, 

204 response: HTMLResponse, 

205 ) -> HTMLResponse: 

206 from pathlib import Path 

207 

208 from starlette.templating import Jinja2Templates 

209 

210 from lexigram.admin.engine.renderer import resolve_admin_nav 

211 from lexigram.admin.ui.templates.shell import AdminShell 

212 from lexigram.ui import raw, render_to_string 

213 

214 content = ( 

215 response.body.decode() 

216 if isinstance(response.body, bytes) 

217 else str(response.body) 

218 ) 

219 

220 title = self._page_cls.__name__.removesuffix("Page") 

221 

222 user = ( 

223 getattr(request.state, "user", None) if hasattr(request, "state") else None 

224 ) 

225 nav_items, system_menu_items, secondary_nav = resolve_admin_nav(request) 

226 state = getattr(request, "app", None) 

227 groups = ( 

228 getattr(state.state, "assembler_groups", None) 

229 if state and hasattr(state, "state") 

230 else None 

231 ) 

232 is_cluster = is_cluster_path(request.url.path, cluster_items(groups)) 

233 if secondary_nav: 

234 from lexigram.admin.ui.organisms.secondary_nav import ClusterLayout 

235 

236 content = render_to_string( 

237 ClusterLayout(items=secondary_nav, content=raw(content)) 

238 ) 

239 if is_cluster: 

240 content = _cluster_header_html() + content 

241 

242 breadcrumbs: list[dict[str, str]] | None = None 

243 if secondary_nav and is_cluster: 

244 breadcrumbs = [ 

245 {"label": "Home", "url": "/admin/"}, 

246 {"label": CLUSTER_LABEL, "url": CLUSTER_URL}, 

247 ] 

248 path = request.url.path 

249 for item in secondary_nav: 

250 item_href = item.get("href", "") 

251 if path == item_href: 

252 breadcrumbs.append({"label": item.get("label", ""), "url": ""}) 

253 title = item.get("label", title) 

254 break 

255 child = next( 

256 (c for c in item.get("children", []) if path == c.get("href", "")), 

257 None, 

258 ) 

259 if child is not None: 

260 breadcrumbs.append( 

261 {"label": item.get("label", ""), "url": item_href} 

262 ) 

263 breadcrumbs.append({"label": child.get("label", ""), "url": ""}) 

264 title = child.get("label", title) 

265 break 

266 

267 theme_css = "" 

268 try: 

269 from lexigram.admin.theme.service import AdminThemeService 

270 

271 service = AdminThemeService( 

272 primary_color=await _resolve_primary_color(self._container) 

273 ) 

274 theme_css = service.generate_theme_css() 

275 except Exception: # noqa: BLE001, S110 — non-fatal 

276 pass 

277 

278 from lexigram.admin.navigation.manager import NavigationManager 

279 

280 user_menu_items: list[dict[str, str | None]] = ( 

281 NavigationManager(request).user_menu_items() if request is not None else [] 

282 ) 

283 

284 branding: dict[str, str] = {} 

285 try: 

286 from lexigram.admin.multitenancy.adapter import resolve_tenant_id 

287 from lexigram.admin.services.settings_service import ( 

288 resolve_admin_settings_service, 

289 ) 

290 

291 container = ( 

292 getattr(request.state, "root_container", None) 

293 or getattr(request.state, "container", None) 

294 or getattr(request.app.state, "container", None) 

295 or self._container 

296 ) 

297 settings_service = await resolve_admin_settings_service(container) 

298 if settings_service is not None: 

299 tenant = await resolve_tenant_id(request, default="default") 

300 overrides = await settings_service.get_all(tenant) 

301 for field in ("primary_color", "site_name", "logo_url", "dark_mode"): 

302 value = overrides.get(field) or overrides.get( 

303 f"admin.branding.{field}" 

304 ) 

305 if value: 

306 branding[field] = value 

307 if branding.get("primary_color"): 

308 from lexigram.admin.theme.service import AdminThemeService 

309 

310 theme_css = AdminThemeService( 

311 primary_color=branding["primary_color"] 

312 ).generate_theme_css() 

313 except Exception: # noqa: BLE001, S110 — non-fatal 

314 pass 

315 

316 shell = AdminShell( 

317 content=content, 

318 title=title, 

319 user=user, 

320 nav_items=nav_items, 

321 system_menu_items=system_menu_items, 

322 user_menu_items=user_menu_items, 

323 breadcrumbs=breadcrumbs, 

324 theme_css=theme_css, 

325 **cast( 

326 "Any", 

327 { 

328 k: v 

329 for k, v in branding.items() 

330 if k in ("dark_mode", "site_name", "logo_url") 

331 }, 

332 ), 

333 ) 

334 shell_html = render_to_string(shell) 

335 

336 templates_dir = Path(__file__).resolve().parent.parent / "views" / "templates" 

337 templates = Jinja2Templates(directory=str(templates_dir)) 

338 return templates.TemplateResponse( 

339 request, 

340 "admin_shell.html", 

341 context={ 

342 "content": Markup(shell_html), # noqa: S704 — framework-composed trusted HTML 

343 "title": title, 

344 "dark_mode": branding.get("dark_mode", ""), 

345 }, 

346 ) 

347 

348 async def _resolve_page(self) -> Any: 

349 """Resolve page instance from container. 

350 

351 Uses ``container.call(cls.__init__)`` to resolve each constructor 

352 parameter from the DI container, then constructs the instance 

353 manually. This is necessary because ``get_type_hints(cls)`` 

354 returns an empty dict for classes with ``from __future__ import 

355 annotations`` (PEP 563), so ``container.call(cls)`` cannot 

356 discover parameter types. 

357 """ 

358 init_method = self._page_cls.__init__ # type: ignore[misc] 

359 sig = inspect.signature(init_method) 

360 hints = get_type_hints(init_method) 

361 kwargs: dict[str, Any] = {} 

362 for name, param in sig.parameters.items(): 

363 if name == "self": 

364 continue 

365 if param.kind in ( 

366 inspect.Parameter.VAR_POSITIONAL, 

367 inspect.Parameter.VAR_KEYWORD, 

368 ): 

369 continue 

370 param_type = hints.get(name) 

371 if param_type is not None: 

372 try: 

373 resolution_target = _strip_optional(param_type) 

374 kwargs[name] = await self._container.resolve(resolution_target) 

375 continue 

376 except UnresolvableDependencyError: 

377 pass 

378 if param.default is not inspect.Parameter.empty: 

379 kwargs[name] = param.default 

380 elif param_type is not None: 

381 raise UnresolvableDependencyError( 

382 f"Cannot resolve parameter {name!r} for " 

383 f"{self._page_cls.__name__}: type {param_type} not registered.", 

384 ) 

385 else: 

386 raise UnresolvableDependencyError( 

387 f"Cannot resolve parameter {name!r} for " 

388 f"{self._page_cls.__name__}: no type hint and no default.", 

389 ) 

390 return self._page_cls(**kwargs) 

391 

392 

393async def _placeholder_page( 

394 request: Any, 

395 container: Any | None = None, 

396) -> HTMLResponse: 

397 """Placeholder for admin pages without an implemented handler. 

398 

399 For HTMX requests returns only the content fragment (no shell) so 

400 the sidebar/topbar from the existing page stays intact. For direct 

401 navigation returns the full admin layout. 

402 

403 Args: 

404 request: Starlette request. 

405 container: Optional resolver for theme settings. 

406 """ 

407 content = ( 

408 '<div class="flex items-center justify-center h-64">' 

409 '<div class="text-center">' 

410 '<h2 class="text-xl font-semibold text-muted-foreground">Under Construction</h2>' 

411 '<p class="text-muted-foreground mt-2">This page has not been implemented yet.</p>' 

412 "</div></div>" 

413 ) 

414 

415 try: 

416 from lexigram.admin.engine.renderer import resolve_admin_nav 

417 

418 nav_items, system_menu_items, secondary_nav = resolve_admin_nav(request) 

419 except Exception: # noqa: BLE001 — non-fatal 

420 nav_items, system_menu_items, secondary_nav = [], [], None 

421 

422 if secondary_nav: 

423 from lexigram.admin.ui.organisms.secondary_nav import ClusterLayout 

424 from lexigram.ui import raw, render_to_string 

425 

426 content = render_to_string( 

427 ClusterLayout(items=secondary_nav, content=raw(content)) 

428 ) 

429 

430 is_htmx = wants_fragment(request) 

431 

432 if is_htmx: 

433 return HTMLResponse(content) 

434 

435 try: 

436 from pathlib import Path 

437 

438 from starlette.templating import Jinja2Templates 

439 

440 from lexigram.admin.ui.templates.shell import AdminShell 

441 from lexigram.ui import render_to_string 

442 

443 user = ( 

444 getattr(request.state, "user", None) if hasattr(request, "state") else None 

445 ) 

446 

447 from lexigram.admin.navigation.manager import NavigationManager 

448 

449 user_menu_items = ( 

450 NavigationManager(request).user_menu_items(include_plugins=False) 

451 if request is not None 

452 else [] 

453 ) 

454 

455 theme_css = "" 

456 try: 

457 from lexigram.admin.theme.service import AdminThemeService 

458 

459 service = AdminThemeService( 

460 primary_color=( 

461 await _resolve_primary_color(container) 

462 if container is not None 

463 else _DEFAULT_PRIMARY_COLOR 

464 ) 

465 ) 

466 theme_css = service.generate_theme_css() 

467 except Exception: # noqa: BLE001, S110 — non-fatal 

468 pass 

469 

470 shell = AdminShell( 

471 content=content, 

472 title="Under Construction", 

473 user=user, 

474 nav_items=nav_items, 

475 system_menu_items=system_menu_items, 

476 user_menu_items=user_menu_items, 

477 theme_css=theme_css, 

478 ) 

479 shell_html = render_to_string(shell) 

480 

481 templates_dir = Path(__file__).resolve().parent.parent / "views" / "templates" 

482 templates = Jinja2Templates(directory=str(templates_dir)) 

483 return templates.TemplateResponse( 

484 request, 

485 "admin_shell.html", 

486 context={ 

487 "content": shell_html, 

488 "title": "Under Construction", 

489 "dark_mode": "", 

490 }, 

491 ) 

492 except Exception: 

493 return HTMLResponse(content) 

494 

495 

496def _resolve_handler(handler: Any) -> Any: 

497 """Resolve a string dotted-path handler to the actual callable.""" 

498 if not isinstance(handler, str): 

499 return handler 

500 try: 

501 module_path, _, func_name = handler.partition(":") 

502 mod = __import__(module_path, fromlist=[func_name]) 

503 resolved = getattr(mod, func_name, None) 

504 if resolved is None: 

505 logger.warning("handler_import_failed", handler=handler) 

506 return resolved 

507 except Exception: # noqa: BLE001 

508 logger.warning("handler_import_failed", handler=handler, exc_info=True) 

509 return None 

510 

511 

512class StructuredPageHandler: 

513 """Wrap management page handlers so only ``PageContent`` reaches the browser. 

514 

515 Starlette treats class endpoints as ASGI apps (``__call__(scope, receive, 

516 send)``), so this wrapper builds a ``StarletteRequest`` from the ASGI scope 

517 before delegating to the page handler. 

518 

519 Any other return (str, HTMLResponse, template, ...) is a contract 

520 violation: it is logged and replaced with an error page. 

521 """ 

522 

523 def __init__(self, handler: Any) -> None: 

524 self._handler = handler 

525 

526 async def __call__(self, scope: Any, receive: Any, send: Any) -> None: 

527 from lexigram.admin.dashboard.page_renderer import render_page_content 

528 

529 request = StarletteRequest(scope, receive, send) 

530 handler = self._handler 

531 callable_handler = handler.handle if hasattr(handler, "handle") else handler 

532 result = await callable_handler(request) 

533 if isinstance(result, PageContent): 

534 response = render_page_content(result) 

535 else: 

536 logger.error( 

537 "page_contract_violation", 

538 handler=type(self._handler).__name__, 

539 result_type=type(result).__name__, 

540 ) 

541 response = render_page_content( 

542 PageContent( 

543 title="Page Contract Violation", 

544 body=EmptyContent( 

545 title="Invalid Page Content", 

546 message=( 

547 "The page handler returned raw HTML. " 

548 "Convert it to PageContent." 

549 ), 

550 icon="alert-triangle", 

551 ), 

552 ) 

553 ) 

554 await response(scope, receive, send) 

555 

556 

557def _register_pages( 

558 router: AdminRouter, 

559 naming_policy: NamingPolicy, 

560 prefix: str, 

561 pages: list[ManagementPageDefinition], 

562 container: Any = None, 

563) -> None: 

564 """Register management page routes on the admin router. 

565 

566 Routes are registered relative to the admin app mount point 

567 (the prefix is stripped before registration — the admin router 

568 already lives under the admin prefix due to ``AdminRouter.mount()``). 

569 ``registered_internal_paths`` is updated externally so that 

570 ``_ensure_nav_route`` does not create duplicate placeholders. 

571 """ 

572 # prefix is intentionally unused — routes live inside the mounted 

573 # admin app and must be relative to its mount point. 

574 for page in pages: 

575 handler = _resolve_handler(page.handler) 

576 if handler is None: 

577 continue 

578 if inspect.isclass(handler) and container is not None: 

579 handler = AdminPageHandler(handler, container) 

580 else: 

581 handler = StructuredPageHandler(handler) 

582 path = page.route_path 

583 if not path.startswith("/"): 

584 path = f"/{path}" 

585 ns_name = naming_policy.namespaced(page.contributor, page.name) 

586 naming_policy.register("page", ns_name) 

587 router.add_route(path=path, method="GET", handler=handler, name=ns_name) 

588 

589 

590def _register_settings( 

591 router: AdminRouter, 

592 naming_policy: NamingPolicy, 

593 prefix: str, 

594 panels: list[SettingsPanelDefinition], 

595 container: Any = None, 

596) -> None: 

597 """Register settings panel routes on the admin router. 

598 

599 Routes are registered relative to the admin app mount point 

600 (the prefix is stripped before registration — see ``_register_pages``). 

601 """ 

602 for panel in panels: 

603 handler = _resolve_handler(panel.handler) 

604 if handler is None: 

605 continue 

606 if inspect.isclass(handler) and container is not None: 

607 handler = AdminPageHandler(handler, container) 

608 else: 

609 handler = StructuredPageHandler(handler) 

610 path = panel.route_path 

611 if not path.startswith("/"): 

612 path = f"/{path}" 

613 ns_name = naming_policy.namespaced(panel.contributor, panel.name) 

614 naming_policy.register("panel", ns_name) 

615 router.add_route(path=path, method="GET", handler=handler, name=ns_name) 

616 

617 

618class RouteIntegrator: 

619 """Collects ``AdminRouteSpec``, ``ManagementPageDefinition``, and 

620 ``SettingsPanelDefinition`` from contributors and registers them 

621 on the router.""" 

622 

623 def __init__( 

624 self, 

625 *, 

626 router: AdminRouter, 

627 naming_policy: NamingPolicy, 

628 route_prefix: str = "", 

629 container: Any = None, 

630 ) -> None: 

631 self._router = router 

632 self._naming = naming_policy 

633 self._prefix = route_prefix 

634 self._container = container 

635 

636 def register(self, contributors: Sequence[BaseAdminContributor]) -> None: 

637 """Register each contributor's routes, management pages, and 

638 settings panels on the admin router. Nav-item URLs that don't 

639 have a corresponding handler automatically get a placeholder 

640 route so they never 404.""" 

641 registered_internal_paths: set[str] = set() 

642 

643 for c in contributors: 

644 # Routes 

645 for spec in c.get_routes(): 

646 ns_name = self._naming.namespaced(c.package_source, spec.name) 

647 self._naming.register("route", ns_name) 

648 path = spec.path 

649 # Contributor specs carry the full URL (e.g. "/admin/...") 

650 # but routes live inside the mounted admin app, so strip 

651 # the mount prefix like _ensure_nav_route does. 

652 if self._prefix and path.startswith(self._prefix): 

653 path = path[len(self._prefix) :] 

654 if not path: 

655 path = "/" 

656 registered_internal_paths.add(path) 

657 self._router.add_route( 

658 path=path, 

659 method=spec.method, 

660 handler=spec.handler, 

661 name=ns_name, 

662 ) 

663 

664 # Management pages 

665 pages = c.get_management_pages() 

666 if pages: 

667 for page in pages: 

668 internal = page.route_path 

669 if not internal.startswith("/"): 

670 internal = f"/{internal}" 

671 registered_internal_paths.add(internal) 

672 _register_pages( 

673 self._router, 

674 self._naming, 

675 self._prefix, 

676 pages, # type: ignore[arg-type] 

677 container=self._container, 

678 ) 

679 

680 # Settings panels 

681 panels = c.get_settings_panels() 

682 if panels: 

683 for panel in panels: 

684 internal = panel.route_path 

685 if not internal.startswith("/"): 

686 internal = f"/{internal}" 

687 registered_internal_paths.add(internal) 

688 _register_settings( 

689 self._router, 

690 self._naming, 

691 self._prefix, 

692 panels, # type: ignore[arg-type] 

693 container=self._container, 

694 ) 

695 

696 # Auto-register placeholder routes for nav items without handlers. 

697 for c in contributors: 

698 for item in c.get_navigation_items(): 

699 self._ensure_nav_route(item, registered_internal_paths) 

700 for child in item.children or (): 

701 self._ensure_nav_route(child, registered_internal_paths) 

702 

703 # Cluster areas are also reachable under the center namespace 

704 # (e.g. /admin/infrastructure/web), mirroring how settings 

705 # sub-pages nest below /admin/settings. Aliases share the source 

706 # route's handler — real page or placeholder alike. 

707 for c in contributors: 

708 for item in c.get_navigation_items(): 

709 self._register_cluster_alias(item) 

710 for child in item.children or (): 

711 self._register_cluster_alias(child) 

712 

713 def _ensure_nav_route( 

714 self, 

715 item: Any, 

716 registered_paths: set[str], 

717 ) -> None: 

718 """Register a placeholder route for *item* if its URL isn't covered.""" 

719 url = item.url 

720 if not url or url.startswith("http"): 

721 return 

722 

723 internal = url 

724 if self._prefix and internal.startswith(self._prefix): 

725 internal = internal[len(self._prefix) :] 

726 if not internal: 

727 internal = "/" 

728 

729 if internal in registered_paths or url in registered_paths: 

730 return 

731 

732 safe_label = item.label.lower().replace(" ", "_").replace("/", "_") 

733 self._router.add_route( 

734 path=internal, 

735 method="GET", 

736 handler=_placeholder_page, 

737 name=f"placeholder_{safe_label}", 

738 ) 

739 registered_paths.add(internal) 

740 

741 def _register_cluster_alias(self, item: Any) -> None: 

742 """Register a namespaced alias for a cluster group nav item. 

743 

744 Cluster areas live under the center namespace (``/admin/ 

745 infrastructure/web``) in addition to their contributor URL. When 

746 the source URL has a real handler, the alias reuses it; otherwise 

747 the alias falls back to the placeholder page. 

748 """ 

749 if getattr(item, "group", None) != CLUSTER_GROUP: 

750 return 

751 url = item.url 

752 if not url or url.startswith("http"): 

753 return 

754 namespaced = cluster_child_href(url) 

755 if not namespaced or namespaced == url: 

756 return 

757 

758 internal = url 

759 internal_ns = namespaced 

760 if self._prefix and internal.startswith(self._prefix): 

761 internal = internal[len(self._prefix) :] 

762 if self._prefix and internal_ns.startswith(self._prefix): 

763 internal_ns = internal_ns[len(self._prefix) :] 

764 if internal_ns == internal: 

765 return 

766 

767 safe_label = item.label.lower().replace(" ", "_").replace("/", "_") 

768 if not self._router.alias_route( 

769 internal, 

770 internal_ns, 

771 name=f"cluster_alias_{safe_label}", 

772 ): 

773 self._router.add_route( 

774 path=internal_ns, 

775 method="GET", 

776 handler=_placeholder_page, 

777 name=f"cluster_alias_{safe_label}", 

778 )