Files
SluijsensandClaude Sonnet 5 0447993181 Completes local-dev-master-slave-setup: dual-instance frontend tooling, module-capability gating, and master/slave protocol self-healing fixes
Frontend (Unit 2 completion): dual dev-server tooling (pnpm dev:slave,
pnpm dev:all), per-instance browser tab titles, and a backend
capability check (SystemController + useSystemCapabilities +
ModuleGuard) so a Master-only page is hidden on a slave instance
instead of assuming every backend has every module.

Master/slave protocol fixes surfaced by actually running master and
slave side by side locally:
- Deactivating a CMS instance (Inactive) now releases the slave's
  master gate instead of leaving it stuck on its last pushed status.
- The periodic integrity check now also re-pushes status to every
  reachable slave (previously URL-verification only) and runs once
  immediately on startup.
- Added the originally-specified (but never implemented) slave-pull
  path: a slave now periodically polls its own status from the master
  (GET /api/v1/SlaveStatus) and fails open to Available if the master
  is unreachable for too long, complementing the existing push.
- The slave's own Settings page can no longer "successfully" change
  local availability while the master controls it; it's now locked
  with an explanatory banner and the backend rejects the write with
  409 instead of silently no-op'ing it.
- CMS instance status badges now match the dashboard's color/icon
  styling instead of a plain grey badge.

Also corrected the master-cms-module design docs to match this
as-built behavior, and flagged (without a full rewrite) a larger,
pre-existing divergence between its inception-stage application
design and what construction actually built.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 19:53:52 +02:00

3.7 KiB

Fix — CMS Page Was Visible on Slave (No Backend Capability Check)

Problem

The /cms page (managing registered CMS instances — an Owner-only feature backed by SlpModularCms.Modules.Master) was gated purely by role (Owner), with no awareness of whether the connected backend actually has the Master module loaded. Before this feature, this was never an issue — every deployed instance always had Modules.Master loaded. Now that a Master-less slave instance exists, an Owner using the frontend against the slave could still see the nav link and open /cms, where its data calls (GET /api/v1/CmsInstances) would 404 against a backend that has no such controller.

Fix

Backend

  • SlpModularCms.Core.Hosting.ModuleOrchestrator — added ModuleNames (public IReadOnlyList<string>), listing the names of modules actually discovered on this instance.
  • New SlpModularCms.Core.Hosting.SystemControllerGET /api/v1/System/capabilities returns { "modules": [...] } for whichever instance is asked.
  • SlpModularCms.Api/Program.cs and SlpModularCms.Api.Slave/Program.cs — registered the ModuleOrchestrator instance itself as a DI singleton (builder.Services.AddSingleton(orchestrator)) so the new controller can inject it.

Frontend

  • src/api/types.ts — added SystemCapabilities { modules: string[] }.
  • src/api/useSystemCapabilities.ts — new React Query hook (staleTime: Infinity — a backend's module set never changes mid-session), calling /api/v1/System/capabilities.
  • src/components/auth/ModuleGuard.tsx — new guard component (mirrors RoleGuard), hides its children and shows a "not available on this instance" message when the required module isn't in the backend's capability list.
  • src/router.tsx/cms route now wraps its page in <ModuleGuard requiredModule="Master"> (inside the existing <RoleGuard allowedRoles={['Owner']}>).
  • src/components/layout/Sidebar.tsx — the CMS nav item now also requires capabilities.modules.includes('Master') before rendering.
  • src/i18n/locales/{nl,en}/translation.json — added errors.featureUnavailableTitle / errors.featureUnavailable.
  • src/mocks/system/handlers.ts — new MSW handler for */System/capabilities, defaulting to ['Availability', 'Identity', 'Master'] so existing CMS-related tests keep passing unchanged; registered in src/mocks/index.ts.
  • src/components/layout/Sidebar.test.tsx — updated the "Owner sees ... CMS" assertion to findByTestId (now async, since nav-cms visibility depends on the capabilities fetch), and added a new regression test: "Owner does not see CMS when the backend has no Master module (slave instance)".

Verification Performed

  • dotnet build / dotnet test — succeed; all 193 backend tests still pass (module relocation from the earlier fix is untouched; this only adds new code).
  • pnpm build — succeeds, no type errors.
  • pnpm test — 210/210 pass in isolation (209/210 in the full parallel run; the 1 failure is the same pre-existing flaky AddCmsInstanceDialog.test.tsx timeout seen earlier in this feature's Build and Test, unrelated to this change — confirmed passing 5/5 when re-run alone).
  • Live verification against real running instances: started both SlpModularCms.Api (master) and SlpModularCms.Api.Slave against the local SQL Server and curled the new endpoint directly:
    • Master: curl https://localhost:7221/api/v1/System/capabilities{"modules":["Availability","Identity","Master"]}
    • Slave: curl https://localhost:7222/api/v1/System/capabilities{"modules":["Availability","Identity"]}
    • Confirms the capability check reflects each instance's actual loaded modules, not just a hardcoded assumption.