portal-mojo 0.2.19 → 0.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/docs/README.md +7 -1
  2. package/docs/admin-credentials.md +27 -0
  3. package/docs/admin-deployments.md +201 -0
  4. package/docs/admin-dns-certificates.md +160 -0
  5. package/docs/admin-dns-domains.md +92 -0
  6. package/docs/admin-dns.md +18 -0
  7. package/docs/admin-edge.md +200 -196
  8. package/docs/admin-fleet-configuration.md +98 -2
  9. package/docs/admin-geolocation.md +100 -0
  10. package/docs/admin-identity-directories.md +12 -0
  11. package/docs/admin-maintenance.md +133 -0
  12. package/docs/admin-modals.md +45 -2
  13. package/docs/admin-operational-fleet.md +206 -0
  14. package/docs/admin-presentation.md +51 -0
  15. package/docs/admin-signin.md +12 -2
  16. package/docs/auth-pages.md +18 -6
  17. package/docs/auth.md +16 -2
  18. package/docs/client.md +7 -0
  19. package/docs/collection-select.md +14 -0
  20. package/docs/combobox.md +18 -0
  21. package/docs/popover.md +5 -4
  22. package/package.json +1 -1
  23. package/src/admin/core/OperationsHelp.tsx +10 -0
  24. package/src/admin/core/index.ts +6 -4
  25. package/src/admin/core/route-context.ts +6 -0
  26. package/src/admin/credentials/AcmeConsumerKeyDialog.tsx +88 -0
  27. package/src/admin/credentials/acme-consumer-key.ts +34 -0
  28. package/src/admin/credentials/group-api-keys.tsx +5 -3
  29. package/src/admin/deployments/ApiDeploymentDetail.tsx +58 -0
  30. package/src/admin/deployments/DeploymentsPage.tsx +73 -0
  31. package/src/admin/deployments/WebAppDeploySetup.tsx +49 -0
  32. package/src/admin/deployments/WebAppDeploymentDetail.tsx +59 -0
  33. package/src/admin/deployments/WebAppDetail.tsx +104 -0
  34. package/src/admin/deployments/WebAppEditDialog.tsx +70 -0
  35. package/src/admin/deployments/WebAppKeyDialog.tsx +25 -0
  36. package/src/admin/deployments/WebAppOnboardingDialog.tsx +217 -0
  37. package/src/admin/deployments/WebAppRollbackDialog.tsx +82 -0
  38. package/src/admin/deployments/WebAppUploadPanel.tsx +132 -0
  39. package/src/admin/deployments/api.ts +158 -0
  40. package/src/admin/deployments/authority-state.ts +9 -0
  41. package/src/admin/deployments/deployment-state.ts +13 -0
  42. package/src/admin/deployments/index.ts +4 -0
  43. package/src/admin/deployments/onboarding-state.ts +11 -0
  44. package/src/admin/deployments/permissions.ts +37 -0
  45. package/src/admin/deployments/receipts.ts +78 -0
  46. package/src/admin/deployments/sanitize.ts +73 -0
  47. package/src/admin/deployments/shared.tsx +39 -0
  48. package/src/admin/deployments/types.ts +238 -0
  49. package/src/admin/deployments/upload.ts +53 -0
  50. package/src/admin/deployments/useDeploymentObservation.ts +104 -0
  51. package/src/admin/dns/CertificateDetail.tsx +8 -3
  52. package/src/admin/dns/CertificateRequestDialog.tsx +45 -7
  53. package/src/admin/dns/DomainDetail.tsx +14 -4
  54. package/src/admin/dns/DomainOnboardingContent.tsx +44 -0
  55. package/src/admin/dns/DomainOnboardingDialog.tsx +5 -24
  56. package/src/admin/dns/DomainPurchaseWizard.tsx +1 -1
  57. package/src/admin/dns/DomainRegisterExisting.tsx +63 -0
  58. package/src/admin/dns/DomainRegistrarSettings.tsx +52 -0
  59. package/src/admin/dns/DomainsPage.tsx +26 -13
  60. package/src/admin/dns/SharedValidationDialog.tsx +298 -0
  61. package/src/admin/dns/SharedValidationPanel.tsx +76 -0
  62. package/src/admin/dns/api.ts +4 -2
  63. package/src/admin/dns/domain-navigation.ts +16 -0
  64. package/src/admin/dns/registrar-api.ts +13 -0
  65. package/src/admin/dns/shared-validation-api.ts +90 -0
  66. package/src/admin/dns/shared-validation-data.ts +106 -0
  67. package/src/admin/domains/assistant.ts +5 -4
  68. package/src/admin/domains/communications.ts +10 -9
  69. package/src/admin/domains/identity.ts +8 -7
  70. package/src/admin/domains/infrastructure.ts +19 -13
  71. package/src/admin/domains/lazy-page.ts +6 -0
  72. package/src/admin/domains/observability.ts +6 -5
  73. package/src/admin/domains/operations.ts +27 -10
  74. package/src/admin/domains/security.ts +16 -15
  75. package/src/admin/edge/EdgeHelp.tsx +10 -0
  76. package/src/admin/edge/ServingReadiness.tsx +54 -0
  77. package/src/admin/edge/VhostDetail.tsx +145 -139
  78. package/src/admin/edge/VhostWizard.tsx +204 -115
  79. package/src/admin/edge/VhostsPage.tsx +27 -28
  80. package/src/admin/edge/api.ts +24 -10
  81. package/src/admin/edge/models.ts +4 -4
  82. package/src/admin/edge/serving-data.ts +78 -0
  83. package/src/admin/fleet/CapacityEditor.tsx +81 -0
  84. package/src/admin/fleet/CapacityInspector.tsx +19 -0
  85. package/src/admin/fleet/CapacityProgress.tsx +9 -0
  86. package/src/admin/fleet/FleetHelp.tsx +7 -0
  87. package/src/admin/fleet/FleetOverview.tsx +150 -0
  88. package/src/admin/fleet/FleetPage.tsx +11 -0
  89. package/src/admin/fleet/FleetWorkspace.tsx +82 -0
  90. package/src/admin/fleet/ScaleFleetDialog.tsx +169 -0
  91. package/src/admin/fleet/access.ts +22 -0
  92. package/src/admin/fleet/api.ts +77 -0
  93. package/src/admin/fleet/data.ts +148 -0
  94. package/src/admin/fleet/index.ts +8 -0
  95. package/src/admin/fleet/operations.ts +179 -0
  96. package/src/admin/fleet/standalone-data.ts +87 -0
  97. package/src/admin/fleet/types.ts +208 -0
  98. package/src/admin/fleet-configuration/FleetConfigurationPage.tsx +14 -8
  99. package/src/admin/fleet-configuration/SharedValidationConnectionPanel.tsx +166 -0
  100. package/src/admin/fleet-configuration/data.ts +4 -0
  101. package/src/admin/fleet-configuration/operator-destinations.ts +20 -0
  102. package/src/admin/fleet-configuration/shared-validation-data.ts +143 -0
  103. package/src/admin/geolocation/GeoLocationPage.tsx +171 -0
  104. package/src/admin/geolocation/data.ts +138 -0
  105. package/src/admin/geolocation/index.ts +3 -0
  106. package/src/admin/identity/groups/group-sections/SubGroupsSection.tsx +73 -2
  107. package/src/admin/identity/groups/models.ts +2 -0
  108. package/src/admin/identity/members/GroupMembersPanel.tsx +2 -8
  109. package/src/admin/index.ts +8 -0
  110. package/src/admin/maintenance/MaintenanceDialogs.tsx +161 -0
  111. package/src/admin/maintenance/MaintenancePage.tsx +442 -0
  112. package/src/admin/maintenance/data.ts +486 -0
  113. package/src/admin/maintenance/index.ts +3 -0
  114. package/src/admin/maintenance/operations.ts +208 -0
  115. package/src/admin/public/infrastructure.ts +1 -0
  116. package/src/admin/public/operations.ts +3 -0
  117. package/src/admin/registry.ts +6 -2
  118. package/src/admin/settings/SettingDetail.tsx +7 -2
  119. package/src/admin/settings/SettingEditor.tsx +2 -1
  120. package/src/admin/settings/SettingsPage.tsx +3 -1
  121. package/src/admin/settings/model.ts +1 -0
  122. package/src/admin/signin/SigninPage.tsx +17 -3
  123. package/src/admin/signin/api.ts +2 -0
  124. package/src/charts/MetricsChartView.tsx +4 -11
  125. package/src/charts/MetricsMiniWidget.tsx +3 -15
  126. package/src/client/auth.ts +77 -25
  127. package/src/client/client.ts +6 -0
  128. package/src/client/deployments-mock.ts +375 -0
  129. package/src/client/domain-operations-mock.ts +37 -0
  130. package/src/client/fleet-mock.ts +73 -1
  131. package/src/client/geo-provider-mock.ts +82 -0
  132. package/src/client/index.ts +2 -2
  133. package/src/client/maintenance-mock.ts +285 -0
  134. package/src/client/mock.ts +280 -11
  135. package/src/client/operational-fleet-mock.ts +279 -0
  136. package/src/client/operations-mock.ts +85 -0
  137. package/src/client/serving-mock.ts +22 -0
  138. package/src/styles/components/admin-deployments.css +35 -0
  139. package/src/styles/components/admin-dns-certificates.css +33 -0
  140. package/src/styles/components/admin-domains.css +12 -0
  141. package/src/styles/components/admin-edge.css +125 -1
  142. package/src/styles/components/admin-geolocation.css +40 -0
  143. package/src/styles/components/admin-maintenance.css +127 -0
  144. package/src/styles/components/admin-operational-fleet.css +38 -0
  145. package/src/styles/components/admin-operations.css +9 -0
  146. package/src/styles/components/admin-signin.css +2 -0
  147. package/src/styles/components/combobox.css +12 -2
  148. package/src/styles/components/fleet-page.css +9 -0
  149. package/src/styles/index.css +7 -0
  150. package/src/ui/CollectionSelect.tsx +30 -8
  151. package/src/ui/ComboBox.tsx +39 -8
  152. package/src/ui/FilterBar.tsx +3 -5
  153. package/src/ui/ModelTable.tsx +2 -8
  154. package/src/ui/Popover.tsx +15 -7
  155. package/src/ui/date/TimePicker.tsx +4 -14
  156. package/src/ui/image-editor/ImageEditor.tsx +4 -5
  157. package/src/ui/schema-form-core.tsx +3 -5
@@ -0,0 +1,100 @@
1
+ # GeoLocation provider setup
2
+
3
+ `GeoLocationPage` (`portal-mojo/admin/operations`) is the global Operations →
4
+ GeoLocation surface. GeoIP Cache inspects IPs; Fleet Configuration applies
5
+ published settings. Dashboard is unchanged. Hosts may override the mount-derived Fleet link with
6
+ `fleetConfigurationPath`; Showcase opens the actual Fleet Configuration page with a return link.
7
+
8
+ Admission requires literal `is_superuser === true` and a current interactive
9
+ session. The backend also rejects key-backed sessions, requires same-origin
10
+ POSTs and authentication within 600 seconds. Global/group grants do not replace
11
+ these checks. `usePrivateOperator` remounts on session change, aborts requests and
12
+ owned dialogs, and rechecks authority before sending and accepting responses.
13
+
14
+ ## Tasks
15
+
16
+ Primary, fallback and ordered additional providers form one visible configuration task,
17
+ with add, remove and move controls. MojoVerify credentials and threat sharing sit
18
+ below the chain. One retained draft and Save/Discard footer cover the form; there
19
+ is no tab navigation for parts of the same task. The uncontrolled password element
20
+ stays mounted while editing, and shared Tabs is unchanged.
21
+
22
+ MojoVerify always uses `https://api.mojoverify.com`; no URL control is shown.
23
+ Older origins remain readable, but this page stages the hosted URL and warns
24
+ that switching requires a replacement key. Provider names are human-readable;
25
+ unknown configured identifiers are retained. New additional rows exclude primary,
26
+ fallback and existing rows. Runtime deduplicates the chain; existing overlaps are
27
+ not silently rewritten. Non-Mojo credentials are configured outside this endpoint.
28
+ The server's values are authoritative: setup defaults differ from runtime defaults.
29
+
30
+ No key shows **Add key**; a configured key shows **Replace key** and **Remove
31
+ key**. Test key sits with the credential. Blank replacement preserves the stored
32
+ key; confirmed removal is staged until Save changes and does not revoke remotely.
33
+ Save/Discard reflect actual draft changes. Refresh and the page-owned Open Fleet Configuration link confirm before discarding an
34
+ unsaved draft. Keep editing preserves both provider selections and the entered key;
35
+ explicit departure wipes the key before navigation. An in-flight operation cannot
36
+ be interrupted through that link. Info popovers hold secondary explanations; Configuration information
37
+ opens a focused native dialog for revision and publication facts. No disclosure
38
+ sections or sidebar modal are used.
39
+
40
+ Last stored-key check shows its historical outcome and timestamp separately from
41
+ the draft result. Pending activation and Open Fleet Configuration remain visible.
42
+ A false pending flag concerns only this request node, never proves fleet health.
43
+
44
+ ## Private protocol
45
+
46
+ `readGeoLocation()` positively projects only `provider_setup` from schema-v1
47
+ `/api/account/admin/settings`. Catalog, SMS, unknown fields and raw credentials
48
+ are discarded. Form state is imperative, outside Query/Mutation caches.
49
+
50
+ `buildGeoPayload(action, configuration_revision, draft, keyEdit)` sends exactly
51
+ `{action,topic:'geoip',providers:{expected_revision,geoip}}`. The revision is sent
52
+ even when null. The five static keys are primary, fallback, additional, Mojo URL
53
+ and sync. Untouched/blank replacement omits credentials; nonempty replacement
54
+ adds `GEOIP_API_KEY_MOJO`; confirmed removal adds `clear_api_key:true`.
55
+ Additional providers are unique and ordered, at most 16. Identifiers match
56
+ `^[a-z][a-z0-9_-]{0,31}$`. HTTPS origins permit no credentials, path, query,
57
+ fragment or alternate port (443 allowed); keys are at most 4096 characters.
58
+
59
+ Secrets stay only in the password DOM and short-lived request objects, whose
60
+ raw key is deleted in `finally`. They never enter React/Query/Mutation state,
61
+ URLs, storage, logs or mock observations. Editing retains the element and candidate;
62
+ Save attempts, explicit discard, fresh-auth refusal, session change and unmount
63
+ clear it. A stable ref callback wipes the password element before detachment;
64
+ removing the DOM node alone is insufficient. Writes are single-flight. A 440 invokes fresh authentication without
65
+ replaying; Refresh and a deliberate retry are required. Stale revisions
66
+ (400 “changed; reload before publishing”, or 409), unreadable state and uncertain
67
+ saves require authoritative Refresh before another write.
68
+
69
+ Test verifies **only MojoVerify credentials**, regardless of primary. It requires
70
+ the five delegated descriptor keys but not Fleet bucket/object/KMS/restart
71
+ availability; Save requires the publishing plane. Sharing requires the remote
72
+ key's `geoip_sync` permission. The current backend also verifies Mojo on every
73
+ Save except explicit removal: selecting another primary does not enable an
74
+ independent no-Mojo setup. This page does not invent provider-chain testing,
75
+ other-provider key editing, provisioning, revocation or an Apply endpoint.
76
+
77
+ A draft-key test does not rewrite stored verification. The backend's stored-test
78
+ predicate is no replacement plus the node-loaded origin and sync value; chain
79
+ changes do not affect it. Tests reread stored evidence without rebasing the
80
+ draft's revision. Revision changes or lost normalization authority block retry;
81
+ publishing-only unavailability does not block diagnostics. Loaded/desired origin
82
+ drift can prevent stored-key reuse until Fleet apply or explicit replacement.
83
+
84
+ Save rereads authoritative configuration. `published:true` means static
85
+ publication with restart pending; `unchanged:true` can still mean the database
86
+ credential changed. Neither result proves activation. Failed, stale and uncertain
87
+ requests keep errors actionable and never become successful saves.
88
+
89
+ ## Verification
90
+
91
+ `geo-provider-mock.ts` supplies isolated safe observations (action, key action,
92
+ stored-test predicate, expected revision), with no submitted key retained.
93
+ Scenarios include no-key setup, real registry names, unknown/empty registry,
94
+ publishing unavailable, stale/fresh-auth/uncertain outcomes and loaded-origin drift.
95
+ Showcase's GeoLocation demo mounts the actual page. Run
96
+ `npm run verify:admin-geolocation` and `npm run verify:admin-geolocation-mounted`;
97
+ JSDOM behavior does not establish native focus or layout. Inspect both apps,
98
+ light/dark, desktop/390px, opened dialogs and error recovery per
99
+ [Admin presentation](admin-presentation.md). The scoped stylesheet is included
100
+ through `portal-mojo/styles.css`.
@@ -35,6 +35,18 @@ existing system permission gate and conditional queries. Parent, child, member
35
35
  and user navigation opens native detail modals while keeping the directory
36
36
  underneath.
37
37
 
38
+ The Sub-Groups section carries the "Administrators can create sub-groups"
39
+ switch for `sys.groups | sys.manage_groups` holders only; nobody else sees it.
40
+ It reads `metadata.protected.allow_member_subgroups === true` (anything else
41
+ is off) and changes it only through the django-mojo Group action
42
+ `POST /api/group/<id> {"allow_member_subgroups": true|false}`, which answers
43
+ `{"status": true, "allow_member_subgroups": <bool>}`. Never write
44
+ `metadata.protected` directly: that path refuses `manage_groups` admins. The
45
+ action needs django-mojo with #9126; the server refuses a caller without the
46
+ global grant with 403 and a non-boolean with 400. While on, people who manage
47
+ the group as members can create groups directly under it; the setting is not
48
+ inherited by the new groups, and turning it off leaves existing groups alone.
49
+
38
50
  The group model and section code are adapted from the reference portal at
39
51
  `v0.2.5` commit `93c7fdfeb3096d33010e72bd4483cf9ca6e3d6f5`. The source hashes
40
52
  and corresponding package hashes are recorded in
@@ -0,0 +1,133 @@
1
+ # Maintenance
2
+
3
+ ```tsx
4
+ import { MaintenancePage } from 'portal-mojo/admin/operations';
5
+ ```
6
+
7
+ `MaintenancePage` supplies one global Operations destination at `maintenance`.
8
+ It renders Database, Cache and Framework as independent evidence sections.
9
+ Page admission is AWS read (`manage_aws`) **or** framework read
10
+ (`view_platform`, `manage_platform`, `admin`), with literal superuser bypass.
11
+ Each read uses the Fleet exact backend permission helper; group membership,
12
+ the frontend admin wildcard and `comms` do not grant AWS authority.
13
+
14
+ A scan reports offered major upgrades, region, scan time, scheduled-scanning
15
+ state and each warning's exact IAM action. It supplies no complete inventory
16
+ counts. Empty findings mean **No offered major upgrades found in this scan**.
17
+ Partial or unavailable scans never establish support coverage. Standard and
18
+ extended deadlines are separate; an unpublished deadline remains unknown.
19
+ Targets and resource identifiers retain their server spelling. Release notes
20
+ accept credential-free HTTPS links only.
21
+
22
+ Writes additionally require platform management authority and current managed
23
+ infrastructure proof. Bootstrap has its own independent dashboard permission:
24
+ without that grant the page retains authorized inventory but cannot establish
25
+ mode or offer writes. External infrastructure remains readable and explains
26
+ that IaC owns changes. Missing, denied or malformed mode proof blocks writes.
27
+ The backend remains authoritative for interactive, non-key-backed access and
28
+ 600-second fresh authentication.
29
+
30
+ The native confirmation names current and target versions, outage/restart
31
+ impact, and snapshot-based recovery. Type the exact case-sensitive resource
32
+ identifier and explicitly choose Immediately or Next maintenance window.
33
+ `apply_immediately` is a real boolean, including `false`. Changing evidence or
34
+ session closes the old confirmation. Confirmation is single-flight; transport
35
+ uses `mojoCall`, AbortSignals and a final session/authority fence without retry,
36
+ `withFreshAuth`, Query mutations or raw fetch.
37
+
38
+ After acceptance the exact resource and target are observed every ten seconds,
39
+ at most 180 times. Completion requires both `settled === true` and
40
+ `upgraded === true`. A target version before settlement, old available version,
41
+ missing resource, denied/malformed read, or mixed cache member versions remains
42
+ unconfirmed. Timeout retains the receipt and offers explicit status refresh.
43
+
44
+ Attempts are scoped by installation and actor in a module registry, surviving
45
+ dialog closure, navigation/remount, timeout, ordinary token renewal and a new
46
+ session for the same actor. Unmount/session changes abort local observations;
47
+ provider work can continue. An attempted/unknown provider outcome never unlocks
48
+ another apply merely because a refreshed report still offers the old version.
49
+ A completed receipt permits a new, different offered target with new consent;
50
+ switching only the timing never bypasses the same-target guard.
51
+ Reconcile the provider operation and maintenance window. A definitive refusal
52
+ before provider work can be cleared only by a deliberate successful Refresh
53
+ Maintenance and a new confirmation. A 440 requests authentication once and
54
+ never replays the write.
55
+
56
+ Framework shows installed/latest versions, check time/source, pin policy,
57
+ update availability and the backend blocker. Held/pinned versions use
58
+ “Resume and update”. The operation clears the pin and
59
+ redeploys the last converged application SHA. Policy clearing and deployment
60
+ acceptance are separate facts: a 503 or lost response can leave automatic policy
61
+ active with unknown deployment acceptance. That attempt remains guarded across
62
+ remount and reauthentication. Refresh Framework and inspect Deployments even
63
+ when no deployment ID was returned. Acceptance points to Deployments and never
64
+ claims fleet completion. The separate Update policy dialog saves Automatic (`""`), Hold (`"hold"`) or an
65
+ exact version through `POST /api/account/admin/advanced/settings` with only
66
+ `framework_pin`. It requires an active literal superuser and fresh authentication;
67
+ this page also keeps external infrastructure read-only. Syntax validation does
68
+ not verify that an exact version is published. Hold resolves the last converged
69
+ framework version. Saving policy never submits a deployment. The framework
70
+ Update action explicitly clears that policy and requests latest; it cannot
71
+ deploy an exact selection while retaining the pin.
72
+
73
+ A successful policy response followed by matching live policy readback completes
74
+ that policy attempt. A lost/uncertain response remains guarded even if the
75
+ desired value is later observed: current value does not prove request ownership
76
+ or that a delayed writer finished. There is no policy revision or durable receipt
77
+ lookup. Reconcile server audit before further policy writes; there is no reset
78
+ button. Generic Settings directs `EDGE_FRAMEWORK_VERSION` to the mounted
79
+ Maintenance owner, rather than submitting through the rejected generic writer.
80
+
81
+ Framework submissions with an acknowledged UUID observe that exact deployment
82
+ and SHA through the bounded platform deployments collector. Only `converged`,
83
+ `failed`, and `superseded` release its submission guard for a different target;
84
+ `verified`, `partial`, `unknown`, absence, and errors do not. Deployment terminal
85
+ status is distinct from requested framework-version proof. That requires a
86
+ nonempty unique frozen runner roster, unique matching proof for each runner,
87
+ exact UUID/full SHA/version, and fresh valid observation timestamps. Missing
88
+ version proof remains explicit even when deployment converges. Lost acceptance
89
+ without a UUID cannot be reconciled from the latest same-SHA row.
90
+
91
+ Submitted operations remain compact rows across categories; View progress opens
92
+ a focused dialog with target, timing, last successful observation, current/pending
93
+ and member versions, or correlated deployment evidence. Manual refresh only reads.
94
+ Aurora settlement proves cluster-level state, not individual database members.
95
+ Module-memory receipts survive navigation and same-actor renewal; a full browser
96
+ reload loses them. Durable reload-safe reconciliation needs backend support.
97
+
98
+ `maintenanceDeploymentsHref(pathname)` preserves an embedded Admin prefix.
99
+ `MaintenancePage` also accepts an explicit `deploymentsHref`, `pollInterval`
100
+ and `pollLimit`; production defaults are ten seconds and 180 observations.
101
+
102
+ Mock integration: call `maintenanceMock(path, method, body, params, context)`
103
+ only for its owned paths. It returns `undefined` for every sibling path.
104
+ `context` contains authenticated user, claim-derived `keyBacked` and `fresh`.
105
+ Use `readMaintenanceBootstrapFacts()` in the **single central bootstrap
106
+ composer**; Maintenance does not claim that endpoint. Preserve its explicit
107
+ `bootstrap` denied/malformed scenario and derive mode and managed capability
108
+ from the same facts. `MAINTENANCE_SCENARIOS` and `setMockMaintenanceScenario`
109
+ exercise partial scans, waiting/settlement, provider uncertainty and Framework
110
+ policy/deployment uncertainty through the actual page.
111
+
112
+ Integration manifest: lazy-load `admin/maintenance/MaintenancePage.tsx`; global
113
+ section id/path `maintenance`, label Maintenance, navigationGroup operations,
114
+ nav union `sys.manage_aws | sys.view_platform | sys.manage_platform | sys.admin`.
115
+ Import `styles/components/admin-maintenance.css`; register `AdminMaintenanceDemo`
116
+ from `apps/showcase/src/pages/components/demos-admin-maintenance.tsx` lazily.
117
+ Register `verify-admin-maintenance.mjs` and `verify-admin-maintenance-mounted.mjs`
118
+ once each; the harness is an imported test adapter, not a command. Public index
119
+ exports are MaintenancePage, maintenanceDeploymentsHref, maintenanceAccess,
120
+ frameworkActionLabel, MAINTENANCE_ENDPOINTS and maintenanceCompleted.
121
+
122
+ Resource rows keep the offered version, provider state and upgrade action visible. Support deadlines, member evidence and recovery notes remain under each row’s disclosure. Dates use compact local presentation and preserve the original wire value in their title. Framework policy uses readable labels; missing or refused evidence remains explicit.
123
+
124
+ ### KISS presentation
125
+
126
+ Database, Cache and Django-MOJO use tabs when multiple categories are authorized.
127
+ A reader with only one authorized category sees its panel directly without a tab strip. Scan metadata is collapsed; scan errors, incomplete evidence and scanning-off warnings stay above the tabs. Submitted operations remain visible independently of the selected category. See [visual QA](admin-presentation.md).
128
+
129
+ Standard support deadlines within 30 days or overdue remain visible on resource rows;
130
+ standard and extended support stay separate. Missing support dates are not
131
+ interpreted as supported. Framework status distinguishes current, policy-paused
132
+ published versions, and unavailable/latest-unknown evidence, with source details
133
+ in accessible info popovers.
@@ -11,6 +11,20 @@ or rename an action pane to disguise it. Genuine autosave fields remain content.
11
11
  A nested child may have its own contextual actions. Evidence such as a firewall
12
12
  log's Action **column** describes the event and is not an operation section.
13
13
 
14
+ Serving hostname inspection uses a compact `modal.open` with horizontal task
15
+ tabs instead of a side rail: Overview, conditional Routes and Settings.
16
+ Certificate details and a direct certificate editor are available in Overview.
17
+ Its Hostname/Certificate/Settings editor and Hostname → Certificate → Review
18
+ create flow keep one draft. The launcher guards native dismissal while any write or
19
+ route preflight is pending; focused child editors retain the same owner lifetime.
20
+ This is scoped to Serving and does not change the shared DetailView contract.
21
+
22
+ Deployments likewise uses compact `modal.open` task dialogs: API Overview/Nodes/
23
+ Activity; WebApp Overview/Deployments/Releases/Deploy/Settings. Read-only actors
24
+ omit deployment controls, and attempt viewers get a direct overview when there
25
+ is no admitted node evidence. Workflow, integration, setup identity and profile
26
+ editing use focused child dialogs. No shared DetailView default is changed.
27
+
14
28
  Use `modal-pad`, `modal-title`, `modal-message`, grouped fields and `modal-actions`.
15
29
  Cancel precedes the primary button. Pending operations disable repeat submission;
16
30
  `canDismiss` also protects native Escape/backdrop dismissal during a write.
@@ -70,7 +84,8 @@ the inventory using `node scripts/verify-admin-modals.mjs --write-inventory`.
70
84
  | Storage/shortlinks | Bucket, FileManager, File, shortlink; owner/backend/test/CORS/upload/rendition/share | FileManager/shortlink/share active state; no File disable promise; bucket empty only |
71
85
  | Email/support | Domain, mailbox, sent message, template, contact; credentials/onboarding/audit/reconcile/test/template/status | Preserve evidence; separate mailbox directions and support status |
72
86
  | Phone/Push | Number, SMS, config, device, delivery, template; lookup/test/config editors | Config/template lifecycle; number/SMS/device/delivery evidence retained |
73
- | Edge | Vhost with nested routes; shape-first wizard, route editor, upstream declare/retire, blocklist entry editor | Vhost and upstream enabled state; upstreams retire and are never deleted; vhost, route and blocklist deletion are retained operations |
87
+ | Operations | Fleet capacity planning, Maintenance exact resource/window confirmation, GeoLocation local key-removal confirmation, API/WebApp detail and focused onboarding/recovery/upload/credential flows | No generic deployment/release edits or deletion; staged intent, observed receipts and transient secrets remain distinct |
88
+ | Edge | Vhost with nested routes; Hostname → Certificate → Review creation, route editor, upstream declare/retire, blocklist entry editor | Vhost and upstream enabled state; upstreams retire and are never deleted; vhost, route and blocklist deletion are retained operations |
74
89
  | Assistant/settings | Conversation, Skill, Setting; feed continuation, scoped memory, typed settings editor | Owner-only continuation; Skill active selection; memory is scoped editing |
75
90
 
76
91
  ## Resource lifecycle matrix
@@ -93,7 +108,7 @@ the inventory using `node scripts/verify-admin-modals.mjs --write-inventory`.
93
108
  | Push config / template | Existing active selection with separate manage/test grants |
94
109
  | Assistant conversation | Retain history; no arbitrary REST save or invented archive |
95
110
  | Assistant Skill | Existing `is_active` save requires `sys.view_admin`; authoritative reload after mutation |
96
- | Edge vhost | Enable/disable from the header; Edit and Delete in the header menu. Disabling keeps the row and is how a replacement is staged |
111
+ | Edge vhost | Enable/disable and Edit in the header; Delete in the header menu. Disabling keeps the row and is how a replacement is staged |
97
112
  | Edge upstream | `is_enabled` only. Retire (platform administrators) disables and keeps the row; server `CAN_DELETE=False` |
98
113
  | Edge blocklist entry | Mode is the lifecycle: `off` parks an entry without losing it. Deletion is a retained operation |
99
114
  | Other logs/devices/history | Read-only by default, even where low-level REST supports DELETE |
@@ -135,3 +150,31 @@ browser interactions for all family bodies, both themes, desktop/narrow,
135
150
  keyboard navigation and console checks. The lazy Showcase **Admin modal audit**
136
151
  demo supplies long-title, rich/zero/dot badges, nested editor, lifecycle-cancel and
137
152
  embedded-host cases. Existing ToastHost top-layer visibility work remains separate.
153
+
154
+ ## Shared validation confirmations
155
+
156
+ The DNS shared-validation editor freezes the target group/domain, existing
157
+ allocation and session while an operation is pending. Grant additionally freezes
158
+ the imported request, active exact recipient UUID and client reference. Participant
159
+ revoke/cleanup freezes the selected current-page participant independently of any
160
+ request or recipient lookup; cleanup retry leaves the reference revoked.
161
+ Its owner AbortSignal closes only its confirmation on unmount, scope loss or
162
+ auth/session replacement; submitted writes can still persist. Escape/backdrop
163
+ and Close are unavailable while the outer operation is pending.
164
+
165
+ Prepare explains the pause of direct validation/renewal while valid material
166
+ remains available. Grant names the exact project and apex/wildcard issuance
167
+ authority and independent private-key custody. Revoke explains that future
168
+ validation stops, already-issued certificates remain valid and other consumers
169
+ keep working. Revoked references require a fresh, explicitly prepared request.
170
+ No confirmation is inferred from a copied descriptor. Fresh authentication
171
+ prompts without replay; every submitted result reconciles before another write.
172
+
173
+
174
+ The protected connection topic separately confirms Publish and Apply, naming
175
+ profile/project and exact revision consequences. The ACME consumer key form
176
+ confirms the current original Group name/UUID and fixed protected purpose before
177
+ issuance; its token display is transient and the existing audited reveal is
178
+ available for recovery. Every owned nested dialog receives the auth-owner abort
179
+ signal. Session replacement, scope loss and unmount close those dialogs; submitted
180
+ writes may still persist and are never replayed after fresh authentication.
@@ -0,0 +1,206 @@
1
+ # Operational Fleet
2
+
3
+ `FleetOverview` is the reusable health matrix at the top of the global Dashboard.
4
+ `FleetPage` presents a separate standalone workspace at the mount-relative `/fleet` destination. Its task dialogs and styles are isolated from `FleetOverview`; the Admin Dashboard keeps its existing overview, disclosure, editor and behavior.
5
+ Import both, `backendGlobalCan`, contracts and pure staging helpers from
6
+ `portal-mojo/admin` (or `portal-mojo/admin/infrastructure`). The scale editor loads
7
+ only when opened. Existing Dashboard directory, activity and login panels remain
8
+ below this component.
9
+
10
+ ```tsx
11
+ <FleetOverview mount="/product/control" />
12
+ <FleetPage mount="/product/control" />
13
+ ```
14
+
15
+ Both accept optional `mount`, `links` and `showConfigurationLink` props. The mount
16
+ is inferred from the current dashboard pathname, removing a final `/fleet` on
17
+ that page; pass it explicitly in a custom host. `links` maps source names to
18
+ `{ to: 'relative/registered/route', grants: ['exact_backend_key'], superuser?: true }`
19
+ or `null` to suppress a destination. Hosts with a partial registry must suppress
20
+ or replace unavailable destinations. Built-in links target existing Admin pages
21
+ only; Fleet Configuration remains its separate advanced superuser destination.
22
+ Use `showConfigurationLink={false}` when that destination is not mounted.
23
+ `FleetOverview` also accepts `headingLevel` (`1 | 2`, default `2`); use `1` when
24
+ rendering it as a standalone page. `FleetPage` supplies its own level-one Fleet heading.
25
+ `capacityDetails="modal"` replaces the capacity disclosure with a button and a
26
+ read-only modal. This option applies to `FleetOverview`. The reusable overview defaults to `"inline"` to preserve the current Admin Dashboard. `FleetPage` always uses the standalone tabbed capacity inspector.
27
+
28
+ ## Authority and evidence
29
+
30
+ `backendGlobalCan(user, grant | grants)` checks global permissions only. It mirrors
31
+ Python truthiness of JSON values and the finite `helpers/perms.py` categories:
32
+ `users`, `groups`, `security`, `comms`, `jobs`, `metrics`, `files` imply only their
33
+ own `view_`/`manage_` checks. It has no group membership or implicit `admin`
34
+ wildcard. In particular `comms` and `admin` do not imply `manage_aws`.
35
+
36
+ The dashboard request needs `view_admin | manage_users | manage_settings | admin`
37
+ or literal superuser. Capacity reads separately require `manage_aws` or literal
38
+ superuser. Every request and query key is bound to actor plus session revision;
39
+ ordinary token renewal preserves that revision. Each denied read is suppressed.
40
+ Source statuses and server timestamps survive independently. The availability
41
+ headline is the server verdict; attention counts never recompute it. Missing
42
+ jobs/check observations stay unknown. Wholly restricted sections collapse to
43
+ one Restricted row. Unconfigured SMS/email and zero-ticket rows are absent.
44
+ Dashboard data is positively projected before Query storage; arbitrary nested
45
+ provider output and deployment stderr never enter this cache.
46
+
47
+ Load-balancer target health, fixed ingress addresses, and stable node outbound
48
+ addresses are distinct observations. Unknown egress reads say unavailable, never
49
+ an empty allowlist. Serving-process database/Redis reader routing is explicitly
50
+ per process. Database and cache members retain their own lifecycle/status. An Aurora reader absent from filtered instance facts may carry null class/status with unknown lifecycle. That database remains transition-blocked while node/cache evidence and independently supported changes remain available.
51
+
52
+ ## Standalone Fleet workspace
53
+
54
+ `FleetPage` lazy-loads `FleetWorkspace`, and each inspection, editing and progress
55
+ body loads only when opened. Its scoped `.fleet-page` and
56
+ `.fleet-standalone-dialog` styles do not change the Dashboard. Infrastructure and Operations task tabs keep
57
+ the backend availability verdict and each source status. Overall observation age
58
+ is visible; each source timestamp stays in its info popover, while stale sources
59
+ also show their age inline. Stale thresholds remain in info popovers; refreshing never relabels
60
+ old evidence as current. Missing sources stay unknown, denied sections stay
61
+ restricted. Authorized, mounted related destinations are direct links on source names;
62
+ unauthorized destinations are omitted. Info popovers contain evidence, not navigation.
63
+
64
+ The primary action is **Change capacity**. **Inspect capacity** opens App nodes,
65
+ Database, Redis and Addresses tabs. Fleet outbound allowlist, fallback attachments,
66
+ reserved addresses and fixed load-balancer ingress appear separately. **Copy
67
+ allowlist** requires every availability proof, an enabled observed policy and no
68
+ pending nodes. Missing evidence never becomes an empty allowlist. Database and
69
+ Redis reader routing explicitly describe the serving process.
70
+
71
+ The standalone editor uses one `CapacityDraft` from `standalone-data.ts` across
72
+ App nodes, Database and Redis tabs. App nodes has Add nodes and Remove nodes tasks.
73
+ Additions require an explicit healthy, registered, running, settled source; its
74
+ size and target groups are shown, and Change subnet opens a focused override dialog.
75
+ Removal retains the existing self-node and healthy-floor checks; the server also
76
+ re-proves each target group's floor and ownership.
77
+
78
+ Every database member shows its exact ID, role and **current** class. Its size
79
+ select and remove checkbox target that ID; there is no automatic reader selection
80
+ or shared existing-reader sizing fan-out. Newly added readers inherit the current
81
+ source class, even when the writer also resizes. Observe their concrete IDs after
82
+ creation and resize separately. For a Redis group, stage size **or** replica
83
+ count in one plan. The other control is locked until the first returns to current
84
+ values; apply, refresh and separately review the second change. The pure compiler
85
+ rejects mixed-dimension intents independently of those controls because the
86
+ current backend cannot quote them cumulatively.
87
+
88
+ The explicit **Review changes (N)** button is the only plan request. It replaces
89
+ the editor with the authoritative server plan, including step order, warnings,
90
+ unknown prices, expiry and exact-plan consent. Review shows only allowlisted
91
+ server parameters for source node, subnet, target size, count and immediate-apply
92
+ mode when the server reports supported scalar values. Draft intent never fills
93
+ in missing plan facts. Source choices include the exact node ID. **Back to changes**
94
+ preserves the draft and invalidates consent. No background/debounced quote is
95
+ requested by the standalone editor. **Refresh and clear changes** explicitly discards the draft
96
+ before reading fresh inventory; there is no silent reset. Initial open also
97
+ requires a fresh observation before staging. Apply uses only `fleetOperations`.
98
+ Both standalone and Dashboard changes require a literal superuser, an interactive
99
+ user credential and backend fresh authentication. A 440 opens fresh authentication,
100
+ spends consent and requires an explicit refreshed review; planning and applying
101
+ are never automatically replayed.
102
+
103
+ **View capacity progress** is independently available when an in-session operation
104
+ record is retained with a non-idle status and the account has capacity-read
105
+ authority, including external infrastructure, unavailable inventories and non-superuser
106
+ readers. It uses the same in-session operation record as Dashboard. It never
107
+ hydrates an operation from storage or creates another coordinator. When an apply
108
+ reply is lost, the exact batch ID can be entered for read-only reconciliation of
109
+ the retained plan. Matching terminal evidence is required before another change.
110
+ Keep the current document open while reconciling: a full document reload loses
111
+ the in-memory operation record. Batch-ID entry reconciles a retained plan and
112
+ does not import an arbitrary operation after reload. Actor/session changes and
113
+ permission loss fence requests and close owned dialogs;
114
+ Escape/backdrop and explicit close are locked while requests are pending.
115
+
116
+ The Showcase demo uses `FleetPage` and a focused Demo controls dialog. Scenario
117
+ switching is disabled during unresolved operations. Read-only mock batch IDs are
118
+ available through Refresh mock evidence so lost replies can be reconciled without
119
+ clearing the browser's guard.
120
+
121
+ ## Dashboard reviewed scaling
122
+
123
+ Scaling requires a literal superuser with capacity authority and a browser user
124
+ session. External infrastructure remains read-only. The backend enforces its
125
+ credential provenance and 600-second freshness requirement. A 440 opens the
126
+ existing fresh-auth handler, spends consent, and requires explicit refreshed
127
+ review. Neither planning nor applying is automatically replayed.
128
+
129
+ The existing Dashboard focused native dialog refreshes inventory before staging. Local intent is
130
+ compiled into at most 20 steps, then debounced about 400ms into
131
+ `POST /api/aws/capacity/plan {steps}`. Displayed order, descriptions, warnings,
132
+ prices and expiry come from that stored plan. Null prices are unknown. Editing,
133
+ refreshing, expiry, permission loss or session replacement invalidates consent.
134
+ A synchronous guard protects submission, including duplicate activation.
135
+ `POST /api/aws/capacity/plan/apply` sends only `{plan_id}`.
136
+
137
+ Node removal stages drain then terminate, excludes this serving node, requires
138
+ identified self and complete inventory, and retains one currently healthy target.
139
+ Unproven additions never satisfy the healthy floor. A freshly observed owned,
140
+ unregistered, settled node gets a separate Terminate drained node recovery path;
141
+ AWS ownership and drain completion are re-proved by the server. App size remains
142
+ informational. Add-node placement omits empty fields and locally validates subnet
143
+ shape; AWS proves the source, VPC, availability zone and address suitability.
144
+
145
+ Database writer and existing-reader sizes are independent curated keys. Reader
146
+ resizing fans out over surviving concrete reader IDs with different current
147
+ classes; staged removals are excluded. New readers inherit the **current** source
148
+ class, even when the writer also resizes in the plan. Refresh their concrete IDs
149
+ after creation and separately review resizing them. Writer resizing causes
150
+ minutes offline. Redis sizing applies to the whole group; replica counts retain
151
+ the server minimum and the editor explains rolling or downtime impact before
152
+ review. All count/cache/database resize steps explicitly send
153
+ `apply_immediately: true`.
154
+
155
+ ## Observation and uncertain outcomes
156
+
157
+ `fleetOperations` lives outside component/dialog state. Its records contain safe
158
+ IDs and observed batch facts, keyed by installation and actor. Unresolved state
159
+ survives dialog closure, Dashboard/Fleet navigation, and same-actor reauthentication;
160
+ it does not survive a full document reload. Keep the current document open when
161
+ reconciling an uncertain submission. New sends and read replies are still fenced
162
+ to the exact session revision. A
163
+ different actor sees no previous actor record. No credentials or mutation drafts
164
+ are stored locally. The coordinator never replays a mutation. Expiry or session refusal at the final transport boundary is provably unsent: refresh inventory and review again. A transport failure after that boundary remains unknown and retains the reconciliation guard.
165
+
166
+ Running batches are observed about every ten seconds, with a backstop covering
167
+ 90 minutes per step. Completed steps remain visible on failure; nothing rolls
168
+ back. `stalled` directs the operator to the jobs runner/cleanup channel. Poll
169
+ failures and exhausted budgets say progress unknown and offer explicit observation
170
+ refresh. A duplicate apply receipt follows its existing batch. Stale/expired plans
171
+ require another review.
172
+
173
+ A lost apply reply retains the submitted plan ID and blocks new mutations, even
174
+ when no batch ID was acknowledged. Closing, refreshing inventory, signing in
175
+ again as the same actor, and selecting a mock scenario do not clear this block.
176
+ Inspect capacity server logs and AWS state, obtain the exact plan's batch ID, and
177
+ enter it for read-only observation. Only matching terminal evidence resolves the
178
+ block; an unrelated batch is refused. Ambiguous provider outcomes remain unknown.
179
+
180
+ ## Mock and executable verification
181
+
182
+ `client/operational-fleet-mock.ts` is mock-only and exports:
183
+
184
+ - `handleOperationalFleetMock(path, method, body, params, actor, authTime)` → an
185
+ envelope, `undefined` for unowned routes, or an intentional lost-response error.
186
+ The central mock supplies its validated actor/global grants and bearer auth time;
187
+ optional `actor.credential_kind` identifies a confirmed confined credential.
188
+ - `mockOperationalFleetScenarios`, `setMockOperationalFleetScenario`,
189
+ `getMockOperationalFleetScenario`, and read-only `getMockOperationalFleetBatchIds`.
190
+
191
+ The default fixture includes asymmetric reader classes. Scenarios cover external,
192
+ restricted, partial, transitioning/drained nodes, missing evidence, unknown prices,
193
+ stale/expired/duplicate plans, partial failures, stalled progress and lost replies.
194
+ Changing scenario resets the simulated server, not the browser operation record.
195
+
196
+ Run `npm run verify:admin-fleet-workspace` for standalone compiler and mounted coverage, plus `node scripts/verify-admin-operational-fleet.mjs` and
197
+ `node scripts/verify-admin-operational-fleet-mounted.mjs`. The mounted verifier uses
198
+ an isolated injected transport and rejects every real host. It exercises actual
199
+ React components in StrictMode, custom embedded mounts, denied request suppression,
200
+ cancellation, stale responses, single-flight, fresh-auth without replay, cross-page
201
+ progress and unknown-outcome reconciliation. Browser light/dark, narrow layout,
202
+ native focus and console proof belong to the integrated closing run.
203
+
204
+ ### KISS presentation
205
+
206
+ The Dashboard overview uses its existing compact service/status rows and modal Details. The separate Fleet workspace uses compact evidence cards and info popovers. Both editors retain a single draft across App nodes, Database and Redis tabs; the standalone editor enters review only through Review changes and discards consent on Back. All staged changes appear in one exact server-plan review. See [visual QA](admin-presentation.md).
@@ -0,0 +1,51 @@
1
+ # Admin presentation and visual QA
2
+
3
+ Use intuitive, simple screens with clean spacing. Keep only important data and
4
+ short action-oriented copy on the primary surface. Use tabs, modals and help
5
+ popovers only when they improve the task. Tabs separate
6
+ distinct jobs; do not split a few related fields across tiny panels or render
7
+ a one-item tab strip. Use focused modals for substantial inspection/confirmation
8
+ and keyboard-accessible info popovers for secondary explanations. Keep navigation
9
+ directly discoverable rather than hiding destination links inside help. On pages
10
+ that already scroll, use modals/popovers rather
11
+ than collapsible detail sections. Compact disclosures such as Maintenance are
12
+ fine; this is contextual. Preserve Admin Dashboard as-is unless Ian explicitly
13
+ requests a change; separate Fleet work must not alter it. Keep action-specific errors, unknown outcomes and concrete
14
+ interruptions visible where the operator needs them. Permission/consent/session
15
+ and no-replay checks are behavioral requirements, not prose to remove.
16
+
17
+ Both applications import `portal-mojo/styles.css`. Never add app-local copies of
18
+ package component CSS: unlayered stale copies override the canonical layer.
19
+ Showcase owns only tokens, chrome and demo-only styling. Test fixture controls
20
+ are demo-only, separate from the product's UI.
21
+
22
+ ## Required acceptance
23
+
24
+ 1. Run `npm run verify:admin-presentation`. It compiles each app's real theme
25
+ through Vite and checks the six operations layouts, plus the shared style
26
+ entrypoint. A CSS file existing on disk does not prove it is loaded.
27
+ 2. Wait for actual loaded data and controls, not just a heading, spinner or
28
+ skeleton. Inspect initial-viewport screenshots of **all six** screens
29
+ (GeoLocation, Fleet, Maintenance, Deployments, Domains, Serving) in light and
30
+ dark at desktop and 390px. Revisit each image; producing one is not review.
31
+ 3. Check computed page/row display, padding/gap and heading size. Check field
32
+ separation, alignment, readable contrast, wrapping and no page overflow.
33
+ Tables may scroll inside their own container. Inspect internal scroll areas
34
+ and sticky actions, not just document width.
35
+ 4. Open help, tabs, details and dialogs. Verify popovers inside a native dialog,
36
+ Escape/outside dismissal, readable fields and reachable actions at narrow
37
+ width. Inspect every changed surface, including errors and unknown outcomes.
38
+ 5. Exercise mock workflows: provider test/save, a combined scaling draft retained
39
+ across tabs and exact plan review, maintenance outage/resource confirmation,
40
+ API/WebApp tabs and recovery/setup, Domain actions/details, Serving wizard.
41
+ Cancel billable/destructive actions unless a test explicitly owns the mock.
42
+ 6. Repeat a smoke check in the actual Admin shell and built Showcase output.
43
+ Check the browser console and run focused behavior contracts plus typecheck.
44
+ DOM-only tests verify behavior; they cannot certify layout or visual quality.
45
+ At mobile width, verify navigation starts collapsed, expands over the page,
46
+ closes with Escape/outside activation or navigation, and preserves the saved
47
+ desktop preference when returning to a wider viewport.
48
+
49
+ Keep screenshot evidence and the screen/theme/viewport/interaction results on
50
+ the Maestro item. State whether evidence is mock or live. A preview link must
51
+ work when handed over; the development server's lifetime is part of that check.
@@ -12,9 +12,11 @@ The page has two parts:
12
12
  layout/appearance/accent color, hero panel, sign-in heading/copy/methods,
13
13
  registration, custom CSS. It edits the same keys a group admin edits in the
14
14
  group **Configure Auth** dialog; a group's own settings still override these.
15
- - **Providers** — one card each for Google, Apple and GitHub: an Enabled
15
+ - **Providers** — one card per provider the server lists (Google, Apple,
16
+ GitHub and, from django-mojo's Microsoft sign-in, Microsoft): an Enabled
16
17
  switch, a read-only callback URL with a Copy button, the credential fields,
17
- a Ready / Needs setup badge, and a link to the provider's console.
18
+ a Ready / Needs setup badge, any server notices, and a link to the
19
+ provider's console.
18
20
 
19
21
  KISS by design: trusted admins, no step-up auth, no confirmation beyond a
20
22
  browser `confirm()` before clearing a stored value.
@@ -38,6 +40,14 @@ One endpoint, both methods, django-mojo envelope.
38
40
  multiline, configured, source, value, hint}`. `source` is `admin`,
39
41
  `deployment` (shown as "Set in server config") or `none`. Secret fields
40
42
  never carry `value`; `hint` (e.g. `…a1b2`) is shown when present.
43
+ - Optional `notices[]` on a provider: `{tone, text}`, `tone` one of `info`,
44
+ `warning` or `danger` (for example a Microsoft client secret nearing or past
45
+ its recorded expiry). Each renders as its own `role="status"` note under
46
+ the Needs setup note, in order; any other tone renders as `info`. An absent
47
+ or empty list renders nothing, so older backends are unaffected.
48
+ - Provider `name` tokens with a built-in label and icon: `google`, `apple`,
49
+ `github`, `microsoft` (`bi-microsoft`). Any other name falls back to the
50
+ server's `label` and a key icon.
41
51
 
42
52
  `POST /api/account/admin/signin` returns the same full payload, which the page
43
53
  writes straight into its query cache (`SIGNIN_QUERY_KEY`).