portal-mojo 0.2.2 → 0.2.4
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.
- package/README.md +3 -0
- package/docs/DetailView.md +19 -15
- package/docs/ModelTable.md +26 -0
- package/docs/README.md +2 -1
- package/docs/admin-assistant.md +3 -1
- package/docs/admin-credentials.md +7 -0
- package/docs/admin-dashboard.md +1 -1
- package/docs/admin-dns.md +8 -2
- package/docs/admin-incidents.md +26 -3
- package/docs/admin-jobs.md +7 -4
- package/docs/admin-messaging.md +6 -5
- package/docs/admin-modals.md +122 -0
- package/docs/admin-network-security.md +4 -4
- package/docs/admin-phonehub.md +24 -6
- package/docs/admin-push.md +7 -0
- package/docs/admin-rules.md +8 -0
- package/docs/admin-shortlinks.md +2 -1
- package/docs/admin-storage.md +2 -1
- package/docs/auth.md +28 -0
- package/docs/client.md +12 -0
- package/docs/detail-primitives.md +12 -1
- package/docs/feedback.md +70 -1
- package/docs/forms.md +51 -0
- package/docs/loading.md +3 -2
- package/docs/popover.md +18 -3
- package/package.json +1 -1
- package/src/admin/assistant/api.ts +1 -1
- package/src/admin/assistant/pages.tsx +71 -17
- package/src/admin/assistant/skill-lifecycle.ts +9 -0
- package/src/admin/core/index.ts +7 -2
- package/src/admin/credentials/group-api-keys.tsx +67 -43
- package/src/admin/credentials/webhook-subscriptions.tsx +49 -40
- package/src/admin/dashboard/AdminDashboardPage.tsx +1 -1
- package/src/admin/dns/CertificateLifecyclePoller.tsx +15 -3
- package/src/admin/dns/ProviderCredentialsPage.tsx +33 -49
- package/src/admin/dns/setup.ts +1 -1
- package/src/admin/domains/observability.ts +1 -1
- package/src/admin/identity/members/MemberDetail.tsx +48 -3
- package/src/admin/identity/users/UserDetail.tsx +36 -20
- package/src/admin/identity/users/sections/OAuthSection.tsx +13 -6
- package/src/admin/identity/users/sections/actions.tsx +20 -9
- package/src/admin/incidents/EventDetail.tsx +147 -5
- package/src/admin/incidents/IncidentDetail.tsx +181 -8
- package/src/admin/incidents/models.ts +1 -1
- package/src/admin/jobs/RunnerDetail.tsx +5 -8
- package/src/admin/jobs/ScheduledTaskDetail.tsx +5 -71
- package/src/admin/jobs/index.ts +1 -1
- package/src/admin/jobs/purge-preview.ts +25 -0
- package/src/admin/jobs/sections/JobOperationsSection.tsx +46 -35
- package/src/admin/messaging/EmailDomainsPage.tsx +3 -3
- package/src/admin/messaging/EmailTemplatesPage.tsx +2 -4
- package/src/admin/messaging/MailboxesPage.tsx +3 -3
- package/src/admin/messaging/PublicMessagesPage.tsx +3 -5
- package/src/admin/messaging/push/PushPage.tsx +20 -8
- package/src/admin/monitoring/LogInspector.tsx +3 -19
- package/src/admin/network/IPSetDetail.tsx +32 -63
- package/src/admin/phonehub/PhoneHubPage.tsx +285 -13
- package/src/admin/phonehub/SmsComposer.tsx +97 -0
- package/src/admin/phonehub/api.ts +11 -5
- package/src/admin/rules/RuleSetDetailPage.tsx +31 -7
- package/src/admin/rules/condition-removal.ts +7 -0
- package/src/admin/security/geoip/models.ts +5 -0
- package/src/admin/security/tickets.tsx +40 -17
- package/src/admin/shortlinks/ShortlinkDetail.tsx +18 -12
- package/src/admin/storage/BucketDetail.tsx +28 -9
- package/src/admin/storage/FileManagerDetail.tsx +23 -5
- package/src/admin/storage/FileView.tsx +28 -16
- package/src/admin/storage/FilesPage.tsx +4 -5
- package/src/client/auth.ts +10 -1
- package/src/client/client.ts +14 -6
- package/src/client/mock.ts +90 -6
- package/src/client/upload.ts +1 -0
- package/src/ui/DetailView.tsx +22 -5
- package/src/ui/FormView.tsx +24 -1
- package/src/ui/ModelTable.tsx +12 -4
- package/src/ui/Popover.tsx +13 -12
- package/src/ui/detail/MetadataSection.tsx +46 -10
- package/src/ui/form-autosave.ts +134 -23
- package/src/ui/guardrail.tsx +125 -0
- package/src/ui/index.ts +1 -0
- package/src/ui/modal.tsx +8 -4
- package/src/ui/toast.tsx +48 -5
package/README.md
CHANGED
|
@@ -67,6 +67,9 @@ are identical in both forms.
|
|
|
67
67
|
(`panel`, `chip`, `tbl`, …) styled by the app's `theme.css` under
|
|
68
68
|
`data-theme="light"` **and** `"dark"` (reference set:
|
|
69
69
|
`apps/portal/src/theme.css`).
|
|
70
|
+
This includes the `.guardrail*` rules behind `confirmGuardrail`
|
|
71
|
+
(reference: `apps/portal/src/theme/guardrail.css`) — the package ships no
|
|
72
|
+
CSS, so an app that opens a guardrail must carry that block.
|
|
70
73
|
- **Tailwind scan of this package.** In an installed app's CSS:
|
|
71
74
|
`@source "../node_modules/portal-mojo/src";` (adjust the relative path from
|
|
72
75
|
that CSS file). Utility classes used in package components are generated by
|
package/docs/DetailView.md
CHANGED
|
@@ -37,7 +37,7 @@ of the toolkit it must render inside the app's `QueryClientProvider`
|
|
|
37
37
|
contextMenu={[
|
|
38
38
|
{ label: 'Resend invite', icon: 'bi-envelope-paper', when: (u) => u?.last_login == null, onSelect: ... },
|
|
39
39
|
{ divider: true },
|
|
40
|
-
{ label: '
|
|
40
|
+
{ label: 'Deactivate', icon: 'bi-pause-circle', permissions: ['manage_users', 'users'], disabled: pending, onSelect: ... },
|
|
41
41
|
]}
|
|
42
42
|
sections={[
|
|
43
43
|
{ key: 'profile', label: 'Profile', icon: 'bi-person', render: () => <>…</> },
|
|
@@ -64,16 +64,16 @@ trap — those belong to the host dialog). The affordance follows the callback:
|
|
|
64
64
|
- **Pass a history-back handler** when a routed record page should keep the
|
|
65
65
|
✕ as "back" (`onClose={() => navigate(-1)}` — linkable record routes read
|
|
66
66
|
well this way).
|
|
67
|
-
- **Modal hosts
|
|
68
|
-
|
|
69
|
-
Escape/backdrop
|
|
70
|
-
in a modal yields an Escape-only dialog.
|
|
67
|
+
- **Modal hosts pass it** for the normal identity-header close. The flush
|
|
68
|
+
envelope supplies a fallback close during loading/error states without a
|
|
69
|
+
DetailView header; Escape/backdrop follow the live `canDismiss` gate.
|
|
71
70
|
|
|
72
|
-
Sizing is the consuming app's concern
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
Sizing is the consuming app's concern. Only `.mojo-modal-flush > .detail`
|
|
72
|
+
receives a viewport-height limit; page/embedded bodies have no modal minimum
|
|
73
|
+
or maximum. At narrow container widths an in-flow section button expands the
|
|
74
|
+
same filtered rail entries, including rich, zero and dot badges. No native
|
|
75
|
+
select stringification or second top-layer overlay is involved. The chrome
|
|
76
|
+
lives in `theme/detailview.css` in both consuming apps.
|
|
77
77
|
|
|
78
78
|
## Permission-gated sections (fail-closed)
|
|
79
79
|
|
|
@@ -110,7 +110,7 @@ follows. There is no imperative handle.
|
|
|
110
110
|
|
|
111
111
|
## Header kebab menu
|
|
112
112
|
|
|
113
|
-
`contextMenu?: Array<{ label, icon?, permissions?, when?, onSelect, danger? } | { divider: true }>`
|
|
113
|
+
`contextMenu?: Array<{ label, icon?, permissions?, when?, onSelect, danger?, disabled? } | { divider: true }>`
|
|
114
114
|
renders a three-dot button in the header gutter (before the close X, when one renders) that
|
|
115
115
|
opens a `Popover` menu (`placement: bottom-end`, top-layer — works inside
|
|
116
116
|
`modal.detail`). Filtering is `ContextMenu.visibleItems` parity:
|
|
@@ -172,10 +172,12 @@ permission behavior, not misconfig.
|
|
|
172
172
|
| `FlatRow` | label + value row; `action` adds the edit pencil (`actionIcon` to override, e.g. `bi-plus-lg` for empty values) |
|
|
173
173
|
| `SecurityItem` | icon + title + description row with a trailing slot (badge/button) |
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
users (
|
|
175
|
+
Pencil actions open `formModal` with current values as `initial`. Current-record
|
|
176
|
+
operations live in the header menu or focused dialogs; content rails never
|
|
177
|
+
contain danger/action panes. Child-specific actions remain contextual. Fields
|
|
178
|
+
the wire does not carry for other users (`has_passkey`, `requires_mfa`) do not
|
|
179
|
+
get rows. See [Admin modal policy](admin-modals.md) for lifecycle exceptions.
|
|
180
|
+
`active.disabled` and menu-entry `disabled` block repeat submission while pending.
|
|
179
181
|
|
|
180
182
|
## Pitfalls
|
|
181
183
|
|
|
@@ -192,3 +194,5 @@ users (me-graph only: `has_passkey`, `requires_mfa`) don't get rows.
|
|
|
192
194
|
- The styles for the C1 chrome live in `apps/portal/src/theme/detailview.css`
|
|
193
195
|
(`.rail-badge`, `.rail-dot`, `.dv-menu*`, `.dv-keep`) — tokens only,
|
|
194
196
|
both themes.
|
|
197
|
+
|
|
198
|
+
Keyboard selection in the compact section rail returns focus to the visible section toggle before the rail collapses. The desktop rail retains ordinary button focus.
|
package/docs/ModelTable.md
CHANGED
|
@@ -110,6 +110,32 @@ passed to every run — e.g. collect ONE disable reason) →
|
|
|
110
110
|
/ `"N succeeded, M failed"` (warning) / `"failed for all N"` → always
|
|
111
111
|
clear selection + invalidate.
|
|
112
112
|
|
|
113
|
+
`confirm` is the permission step and takes three forms:
|
|
114
|
+
|
|
115
|
+
| `confirm` | Behaviour |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `string` (default `"<label> N item(s)?"`) | `modal.confirm` with that copy; `danger` styles the button. |
|
|
118
|
+
| `false` | No prompt. |
|
|
119
|
+
| `(rows) => Promise<boolean>` | **Replaces** the built-in confirm — resolve `false` to cancel. `prepare` still runs after it. Open a `confirmGuardrail` here for a batch that takes a tenant dark or can't be undone. |
|
|
120
|
+
|
|
121
|
+
Ask permission in `confirm`, collect input in `prepare` — don't fold a
|
|
122
|
+
guardrail into `prepare` and return `null` to cancel; that conflates
|
|
123
|
+
"nothing collected" with "declined".
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
{
|
|
127
|
+
key: 'revoke', label: 'Remove access', danger: true,
|
|
128
|
+
confirm: (rows) => confirmGuardrail({
|
|
129
|
+
title: `Remove access for ${rows.length} members?`,
|
|
130
|
+
effect: <>They lose all access the moment each call lands.</>,
|
|
131
|
+
why: [<>Accounts stay; grants go — re-inviting restores a fresh preset, not the old unlocks.</>],
|
|
132
|
+
confirmText: `Remove ${rows.length}`,
|
|
133
|
+
typeToConfirm: rows.length >= 5 ? String(rows.length) : undefined,
|
|
134
|
+
}),
|
|
135
|
+
run: (row) => revoke(row),
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
113
139
|
`runBatch` executes exactly once for selection-wide operations such as incident
|
|
114
140
|
merge. A rejection produces one actionable error; cancellation mutates
|
|
115
141
|
nothing. Selection clears and the endpoint invalidates after a non-cancelled
|
package/docs/README.md
CHANGED
|
@@ -41,8 +41,9 @@ everything: run the showcase (`npm run dev:showcase`) → **Develop → Componen
|
|
|
41
41
|
| [ModelTable.md](ModelTable.md) | The server-driven table: columns, filters, selection/batch, chooser, persist, autoRefresh, expand, groupBy, export, skeleton |
|
|
42
42
|
| [forms.md](forms.md) | The `Field` language, `SchemaForm`, `formModal`, `FormView` inline autosave + showWhen + tabsets |
|
|
43
43
|
| [tabs-and-form-wizard.md](tabs-and-form-wizard.md) | Accessible Tabs variants and shared-state FormWizard, async finish, reset/roster semantics, modal helper |
|
|
44
|
+
| [admin-modals.md](admin-modals.md) | Admin modal inventory, presentation, resource lifecycle and retained exception policy |
|
|
44
45
|
| [DetailView.md](DetailView.md) | The UserView-style detail surface + row/section primitives |
|
|
45
|
-
| [feedback.md](feedback.md) | Awaitable `modal.*`, `toast
|
|
46
|
+
| [feedback.md](feedback.md) | Awaitable `modal.*`, `toast.*`, `confirmGuardrail` (the are-you-sure-and-here-is-why stop) |
|
|
46
47
|
| [idioms.md](idioms.md) | ArmedButton (two-step confirm), `undoToast` (grace-period undo), `progressToast` — the dangerous-action trio |
|
|
47
48
|
| [file-upload-ux.md](file-upload-ux.md) | Pure file validation, accessible picker/drop controls, bounded component-local upload queues, recovery, and completion callbacks |
|
|
48
49
|
| [image-editor.md](image-editor.md) | Full-resolution crop/transform/filter composition, bounded history, PNG Blob results, modal helper, and ImageField pre-upload editing |
|
package/docs/admin-assistant.md
CHANGED
|
@@ -45,7 +45,9 @@ Import from `portal-mojo/admin`.
|
|
|
45
45
|
the outcome is marked unknown, the list is refreshed, and the send is never
|
|
46
46
|
retried automatically.
|
|
47
47
|
- `POST /api/assistant/context` sends only `{model, pk}`. The returned conversation id is immediately fetched from `/api/assistant/conversation/<id>?graph=detail`; the client never synthesizes or reposts context text.
|
|
48
|
-
- Conversation and Skill lists/details
|
|
48
|
+
- Conversation and Skill lists/details are imperative and component-local.
|
|
49
|
+
Built-in Delete controls are absent. Skill deactivation/reactivation requires
|
|
50
|
+
`sys.view_admin` and reloads authoritative state; conversations retain history. They do not use `defineModel`, Query cache, `ModelTable`, a `RecordFeed` adapter, persistence, or exports.
|
|
49
51
|
- A foreign conversation visible to an administrator is inspect-only. Only `conversation.user.id === me.id` enables continuation.
|
|
50
52
|
|
|
51
53
|
`AssistantFeed` uses controlled `RecordFeed` plus the shared attachment queue at
|
|
@@ -187,3 +187,10 @@ the `group-admin.css` semantic rules in its theme entry, as the repository's
|
|
|
187
187
|
The personal `/api/account/api_keys` page and `/api/auth/generate_api_key` flow
|
|
188
188
|
are intentionally unchanged. This package domain owns only group-scoped
|
|
189
189
|
`/api/group/apikey` credentials and `/api/group/webhook_subscriptions`.
|
|
190
|
+
|
|
191
|
+
## Modal lifecycle convention
|
|
192
|
+
|
|
193
|
+
See [Admin modal policy](admin-modals.md) for header actions, content-only
|
|
194
|
+
sections, supported lifecycle fields and retained irreversible exceptions.
|
|
195
|
+
Group API keys and webhook subscriptions deactivate/reactivate; their built-in
|
|
196
|
+
Delete controls are removed. Reveal, secret rotation and cache boundaries remain.
|
package/docs/admin-dashboard.md
CHANGED
|
@@ -6,7 +6,7 @@ The page uses only authoritative django-mojo signals: time series `user_activity
|
|
|
6
6
|
|
|
7
7
|
Each panel owns an independent permission gate. A caller without metrics access can still see authorized incident, job, email, or login-geography panels, and no denied panel issues a request. Every dashboard query cache key includes the authenticated caller UID. Login geography starts at the previous 30-day date and deliberately omits `dr_end`, matching the backend's instant-bound semantics.
|
|
8
8
|
|
|
9
|
-
The dashboard section contributes
|
|
9
|
+
The dashboard section contributes a direct, top-level Dashboard menu entry instead of wrapping it in an Overview category. If the caller has none of the aggregate dashboard grants, the root route redirects to their first visible Admin route; if no Admin route is visible it renders Access denied.
|
|
10
10
|
|
|
11
11
|
Attention links preserve the exact server filter in the URL:
|
|
12
12
|
|
package/docs/admin-dns.md
CHANGED
|
@@ -69,8 +69,7 @@ Verification is transactional at the UI boundary: a failed first link adds
|
|
|
69
69
|
nothing; a failed rotation rejects, preserves the previous masks/encrypted
|
|
70
70
|
pair, and refetches the now-unverified row with its provider error. Successful
|
|
71
71
|
verification replaces the masks and marks the row verified. Viewer-only users
|
|
72
|
-
can inspect safe rows; managers can link, rotate
|
|
73
|
-
deletion.
|
|
72
|
+
can inspect safe rows; managers can link, rotate and activate/retire; built-in deletion is absent.
|
|
74
73
|
|
|
75
74
|
## Themes and showcase
|
|
76
75
|
|
|
@@ -78,3 +77,10 @@ The page composes existing portal primitives and semantic tokens; it adds no
|
|
|
78
77
|
fixed colors or theme-specific stylesheet. The executable three-leg showcase
|
|
79
78
|
is **Develop → Components → Admin → DNS**: live manager lifecycle,
|
|
80
79
|
viewer-only contract, and malformed-capability unavailable state.
|
|
80
|
+
|
|
81
|
+
## Modal lifecycle convention
|
|
82
|
+
|
|
83
|
+
See [Admin modal policy](admin-modals.md) for header actions, content-only
|
|
84
|
+
sections, supported lifecycle fields and retained irreversible exceptions.
|
|
85
|
+
Provider credentials retire/reactivate through `is_active`, keeping history;
|
|
86
|
+
rotation remains write-only and imperative. Built-in credential Delete is absent.
|
package/docs/admin-incidents.md
CHANGED
|
@@ -37,9 +37,16 @@ Rows, history, optimistic notes, mutation variables, curated copy surfaces, and
|
|
|
37
37
|
exports pass through a recursive non-mutating sanitizer. It redacts normalized
|
|
38
38
|
secret keys, auth/header blocks, JWT/Bearer values, sensitive URL/form params,
|
|
39
39
|
and parsed bodies; evidence/traces are explicitly bounded. Heuristic redaction
|
|
40
|
-
cannot guarantee arbitrary opaque prose, so
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
cannot guarantee arbitrary opaque prose, so exports remain sanitized and
|
|
41
|
+
project only declared fields through `createSafeExporter`.
|
|
42
|
+
|
|
43
|
+
Incident and Event detail have one intentional operator-facing exception:
|
|
44
|
+
**Raw data** shows the exact record returned by Django's authenticated detailed
|
|
45
|
+
endpoint. That includes every top-level field, complete metadata, and expanded
|
|
46
|
+
relations such as an Incident's `ip_info`. Each uses a separate TanStack Query
|
|
47
|
+
cache key so the raw record cannot replace sanitized rows used by tables,
|
|
48
|
+
drill-ins, exports, activity, or formatted sections. Django remains the
|
|
49
|
+
authorization boundary for what those endpoints return.
|
|
43
50
|
|
|
44
51
|
Before that recursive sanitizer runs, every history `media` value is positively
|
|
45
52
|
rebuilt to exactly `id`, `filename`, `content_type`, and `category`; URLs,
|
|
@@ -53,6 +60,22 @@ the child reveals the parent. Ticket/rule/AI/network-response/dashboard controls
|
|
|
53
60
|
remain excluded. Bouncer discovery stays category-prefix plus MUID search
|
|
54
61
|
because its reporter writes MUID into incident details.
|
|
55
62
|
|
|
63
|
+
Incidents with a source address add a formatted **Source** section. It promotes
|
|
64
|
+
the detailed graph's `ip_info` into location/network, risk and reputation,
|
|
65
|
+
effective block/whitelist state, checked-firewall reconciliation, observation
|
|
66
|
+
times, and incident connection context. The address and action open the shared
|
|
67
|
+
GeoIP dossier from the existing cache table (never a billed provider lookup).
|
|
68
|
+
Missing enrichment stays visibly unknown; the exact `ip_info` object remains in
|
|
69
|
+
Raw data.
|
|
70
|
+
|
|
71
|
+
MojoSec events add a dedicated formatted section over the backend's fixed,
|
|
72
|
+
server-derived `metadata.mojosec` projection. It promotes the detection and
|
|
73
|
+
sensor identity, timing, policy revision, recommendation, and every currently
|
|
74
|
+
validated evidence field—including sudo command provenance and receipt
|
|
75
|
+
semantics. The separate Raw data section preserves the complete response for
|
|
76
|
+
operator investigation. The top-level `geo_ip` display string is shown in
|
|
77
|
+
Source when Django supplies it.
|
|
78
|
+
|
|
56
79
|
## Backend evidence
|
|
57
80
|
|
|
58
81
|
The contract was checked against django-mojo source rather than inferred from
|
package/docs/admin-jobs.md
CHANGED
|
@@ -309,11 +309,14 @@ Two more corrections found while building, refining 6 and the gate table:
|
|
|
309
309
|
blanket success.
|
|
310
310
|
- **Purge is dry-run first.** The dialog runs `dry_run:true` and reports
|
|
311
311
|
"N jobs would be deleted before `<cutoff>`"; only the armed confirm performs
|
|
312
|
-
the real run, which is read from `deleted`.
|
|
313
|
-
|
|
312
|
+
the real run, which is read from `deleted`. Preview parameters and generation
|
|
313
|
+
are captured; changing filters or replacing a preview resets arming. The
|
|
314
|
+
preview is an estimate: execution recalculates cutoff and includes cascaded
|
|
315
|
+
event/log rows. Inputs/dismissal lock during execution; failure requires a new
|
|
316
|
+
preview; actual cutoff and counts remain visible.
|
|
314
317
|
- **Armed confirmations** for purge, clear-queue, clear-stuck,
|
|
315
|
-
cleanup-consumers, shutdown, broadcast `shutdown`, force-scheduler-lead
|
|
316
|
-
task
|
|
318
|
+
cleanup-consumers, shutdown, broadcast `shutdown`, force-scheduler-lead. Scheduled-task Delete is absent; enabled state
|
|
319
|
+
preserves task-result history. `clear-queue` sends `confirm:"yes"` **only after** arming: the
|
|
317
320
|
token is a server-side safety gate, and pre-satisfying it (as web-mojo did on
|
|
318
321
|
every call) removes the safety rather than honouring it.
|
|
319
322
|
- **KISS modals.** Job, runner, scheduled-task and task-result inspection are
|
package/docs/admin-messaging.md
CHANGED
|
@@ -25,9 +25,10 @@ Domain credentials are write-only. Secret-bearing saves, onboarding, audit,
|
|
|
25
25
|
reconcile, and send are imperative and never use MutationCache. Audit is POST,
|
|
26
26
|
has no automatic retry, and always refreshes because it persists readiness.
|
|
27
27
|
Create reports only that the row was created: django-mojo's best-effort
|
|
28
|
-
create-time audit/reconcile can fail after the row exists.
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
create-time audit/reconcile can fail after the row exists. Built-in domain,
|
|
29
|
+
mailbox, template and contact deletion is absent so local audit history stays
|
|
30
|
+
available. No archive field is invented. Mailbox inbound/outbound flags remain
|
|
31
|
+
independent; support status changes use their existing status permission.
|
|
31
32
|
|
|
32
33
|
Onboarding composes the shared `FormWizard`. Managed mode is available only
|
|
33
34
|
when the optional DNS Admin integration resolves the normalized domain and
|
|
@@ -46,8 +47,8 @@ Stable mock identities (password `mojo`):
|
|
|
46
47
|
|
|
47
48
|
- `email.operator@nativemojo.com` — email only (`comms`).
|
|
48
49
|
- `support.viewer@nativemojo.com` — contact read only.
|
|
49
|
-
- `support.manager@nativemojo.com` — contact read/update
|
|
50
|
-
- `support.manage-only@nativemojo.com` — delete authority but intentionally no page view.
|
|
50
|
+
- `support.manager@nativemojo.com` — contact read/status update; the low-level delete gate is not a UI action.
|
|
51
|
+
- `support.manage-only@nativemojo.com` — low-level delete authority but intentionally no page view or status-write grant.
|
|
51
52
|
- `showcase.operator@nativemojo.com` — executable showcase coverage.
|
|
52
53
|
|
|
53
54
|
Live verification is read-only by default. Never create/edit/delete a domain,
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Admin modal presentation and lifecycle
|
|
2
|
+
|
|
3
|
+
Admin inspection uses `modal.detail` and `DetailView`: identity, useful subtitle and
|
|
4
|
+
chips, supported lifecycle state, a contextual header menu, and close. Navigation
|
|
5
|
+
contains record content. Editors and operations use focused `modal.open` or
|
|
6
|
+
`formModal` dialogs. Tables remain pages and retain their URL/query state.
|
|
7
|
+
|
|
8
|
+
A current-record action belongs in the header menu or a focused dialog. Do not
|
|
9
|
+
create Delete, Danger, Manage, Control, Operations or Actions navigation sections,
|
|
10
|
+
or rename an action pane to disguise it. Genuine autosave fields remain content.
|
|
11
|
+
A nested child may have its own contextual actions. Evidence such as a firewall
|
|
12
|
+
log's Action **column** describes the event and is not an operation section.
|
|
13
|
+
|
|
14
|
+
Use `modal-pad`, `modal-title`, `modal-message`, grouped fields and `modal-actions`.
|
|
15
|
+
Cancel precedes the primary button. Pending operations disable repeat submission;
|
|
16
|
+
`canDismiss` also protects native Escape/backdrop dismissal during a write.
|
|
17
|
+
Cancellation preserves data. Errors retain authoritative state and surface a
|
|
18
|
+
refusal. Fresh authentication, write-only secrets and resource-specific imperative
|
|
19
|
+
save/reconciliation helpers stay in force. Closing a view never cancels a server
|
|
20
|
+
operation that has already started.
|
|
21
|
+
|
|
22
|
+
The content rail becomes a collapsible in-flow button list at narrow **container**
|
|
23
|
+
widths. Both presentations use the same permission-filtered sections, active key,
|
|
24
|
+
rich ReactNode badges (including zero and dots), dividers and keep-alive state.
|
|
25
|
+
Revoked sections unmount. Modal height limits use available dynamic viewport
|
|
26
|
+
height; embedded/page hosts have no modal minimum or maximum and may omit
|
|
27
|
+
`onClose`. A flush modal has a fallback close control while its body loads or
|
|
28
|
+
fails; successful DetailView bodies supply the normal header close.
|
|
29
|
+
|
|
30
|
+
## Executable inventory
|
|
31
|
+
|
|
32
|
+
`scripts/admin-modal-inventory.json` records file, owner, launcher/helper, body,
|
|
33
|
+
category, helper target, parent flow and expression fingerprint. The finite AST
|
|
34
|
+
scanner follows imported aliases, namespace/destructured/generic modal calls,
|
|
35
|
+
named forwarding helpers, literal dynamic imports and resolved section literals,
|
|
36
|
+
arrays, conditionals, spreads and explicit pushes. Unrelated table columns are
|
|
37
|
+
not section configurations. Assignment, unsupported mutation/property forwarding,
|
|
38
|
+
unknown computed members and unclassified modal references fail review.
|
|
39
|
+
|
|
40
|
+
`scripts/admin-modal-review.json` contains the reviewed semantics of generic
|
|
41
|
+
modal bodies and destructive exceptions. Editors are classified by their actual
|
|
42
|
+
body, not by whether the launcher happens to be `formModal`. Forwarders inherit
|
|
43
|
+
categories from their target; a flow with multiple categories is `nested-flow`.
|
|
44
|
+
Unknown syntax requires an exact expression/fingerprint mapping with a reason,
|
|
45
|
+
classification and runtime case; stale mappings fail. This is a finite syntax
|
|
46
|
+
contract, not a general JavaScript evaluator. When changing a surface, review the
|
|
47
|
+
body, permission/lifecycle behavior and matching runtime evidence before updating
|
|
48
|
+
the inventory using `node scripts/verify-admin-modals.mjs --write-inventory`.
|
|
49
|
+
|
|
50
|
+
## Family map
|
|
51
|
+
|
|
52
|
+
| Family | Record bodies and focused flows | Lifecycle policy |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| Groups and members | Group, Member, nested parent/subgroup/user; admission/invite, disable reason, auth configuration/reset | Existing disable/reactivate and inherited configuration semantics |
|
|
55
|
+
| Users and credentials | User, group API key, webhook; profile/password/MFA/avatar/passkey/OAuth/personal keys, reveal/rate limits/rotation | Header lifecycle; deactivate group keys/webhooks; child credentials stay focused |
|
|
56
|
+
| Bouncer and device intelligence | Signal, device, signature, login/location/GeoIP dossiers; signature editor, lookup/threat/enforcement | Evidence retained; signature lifecycle and explicit enforcement remain |
|
|
57
|
+
| Security operations | Ticket, Incident, Event, RuleSet; feed/attachments, status/LLM/Assistant, conditions/handler editor | Header operations; properties remain content; RuleSet deactivation |
|
|
58
|
+
| Monitoring and CloudWatch | Log, resource/dashboard/metrics drills, chart/data/range/permission dialogs | Read-only evidence; explicit permission reset |
|
|
59
|
+
| Jobs | Job, runner, schedule, result; retry/cancel/payload/broadcast, schedule editor, queue maintenance | Schedule enabled state; runner controls focused; bounded purge only |
|
|
60
|
+
| Network/geofence | IPSet, blocked IP, GeoIP; expiry/editor/sync/source refresh, policy/reset/allowlist | IPSet disable/enable; no unsupported IPSet DELETE |
|
|
61
|
+
| DNS | Domain, certificate, credential, purchase; record set, credential rotation/link, revoke/onboard/contact | Retire credentials; certificate revoke; provider record-set removal only |
|
|
62
|
+
| 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 |
|
|
63
|
+
| Email/support | Domain, mailbox, sent message, template, contact; credentials/onboarding/audit/reconcile/test/template/status | Preserve evidence; separate mailbox directions and support status |
|
|
64
|
+
| Phone/Push | Number, SMS, config, device, delivery, template; lookup/test/config editors | Config/template lifecycle; number/SMS/device/delivery evidence retained |
|
|
65
|
+
| Assistant/settings | Conversation, Skill, Setting; feed continuation, scoped memory, typed settings editor | Owner-only continuation; Skill active selection; memory is scoped editing |
|
|
66
|
+
|
|
67
|
+
## Resource lifecycle matrix
|
|
68
|
+
|
|
69
|
+
| Resource | Built-in Admin behavior |
|
|
70
|
+
|---|---|
|
|
71
|
+
| User / Group / Member | Existing deactivate/reactivate actions; preserve reason, fresh-auth and membership rules |
|
|
72
|
+
| Group API key / webhook | Toggle `is_active`; retain history; reveal and rotate remain credential-safe |
|
|
73
|
+
| RuleSet / schedule | Disable/reactivate; no parent/result cascade deletion. Disabling a schedule does not cancel already published jobs |
|
|
74
|
+
| IPSet | Existing enable/disable/sync/source refresh; server `CAN_DELETE=False` |
|
|
75
|
+
| DNS credential | Retire/reactivate and rotate with `sys.manage_dns OR sys.security` |
|
|
76
|
+
| FileManager | Existing active state and provider/owner operations via atomic imperative save |
|
|
77
|
+
| File | No File, filename-confirmation or batch Delete; no active toggle. Delivery URLs and file-backed shortlinks do not consistently enforce File active state |
|
|
78
|
+
| Shortlink / File share | Deactivate/reactivate using existing reconciler; preserve click history. Previously issued destinations cannot be recalled |
|
|
79
|
+
| Email domain / template | Retain rows; no delete or invented archive flag |
|
|
80
|
+
| Mailbox | Independent inbound/outbound flags; no delete or synthetic combined active flag |
|
|
81
|
+
| Support contact | Existing status transition using the status gate; delete permission does not grant status-write authority |
|
|
82
|
+
| Phone cache / SMS audit | Retain evidence; deleting a local row cannot recall a provider delivery |
|
|
83
|
+
| Phone config | Existing `is_active` through write-only-secret-safe save; disabling restores backend fallback. Test mode is not a disable switch |
|
|
84
|
+
| Push config / template | Existing active selection with separate manage/test grants |
|
|
85
|
+
| Assistant conversation | Retain history; no arbitrary REST save or invented archive |
|
|
86
|
+
| Assistant Skill | Existing `is_active` save requires `sys.view_admin`; authoritative reload after mutation |
|
|
87
|
+
| Other logs/devices/history | Read-only by default, even where low-level REST supports DELETE |
|
|
88
|
+
|
|
89
|
+
The public low-level exported delete APIs remain compatible. Built-in controls
|
|
90
|
+
are a narrower product contract. File delivery enforcement, email archival and
|
|
91
|
+
conversation archival require backend work before new lifecycle UI is added.
|
|
92
|
+
|
|
93
|
+
## Retained irreversible operations
|
|
94
|
+
|
|
95
|
+
| Operation | Authority and confirmation contract |
|
|
96
|
+
|---|---|
|
|
97
|
+
| Rule condition removal | `sys.manage_security OR sys.security`; selected children of the exact parent, freshly read before confirmation. ALL removal broadens matching, ANY removal narrows it while conditions remain; last removal makes either mode catch-all. Warn when active. After partial refusal, refresh parent and children and report counts |
|
|
98
|
+
| OAuth unlink | Live owner or `sys.manage_users OR sys.users`, plus caller restriction; confirm provider and user. Removes association only; reconnect requires provider authorization. Preserve server lockout refusal |
|
|
99
|
+
| Passkey removal | Live owner or user-management grant; confirm friendly name, relying party and user. Disable remains reversible; removal requires reenrollment and never erases a physical device |
|
|
100
|
+
| Memory key removal | `sys.assistant`; selected group additionally requires inherited member `assistant` authority or superuser. Confirm tier/group/key, freeze scope, recheck authority, no optimistic deletion |
|
|
101
|
+
| DNS record-set removal | DNS manage gate, provider capability and record coordinator. Preview all values, apex/service impact and exact name/type; stale-state preflight and reconcile failures; no domain deletion |
|
|
102
|
+
| Empty bucket | `sys.manage_aws OR sys.files`, then fresh auth; header to focused armed confirmation and exact case-sensitive bucket name. Removes objects, versions, markers and multipart uploads; retains bucket. Display partial/unknown outcomes and always refresh |
|
|
103
|
+
| Jobs retention purge | `sys.manage_jobs OR sys.jobs`; dry-run estimate bound to normalized days/status plus generation. Editing/replacing preview resets arming. Ignore stale/unmounted responses; lock inputs/dismissal during execution. Report actual cutoff and cascade-inclusive counts; rejection requires another preview |
|
|
104
|
+
| Queue/consumer cleanup | Jobs manage grant, exact channel/scope and armed confirmation; queue clear sends `confirm:yes` only after consent and may cancel pending database jobs. Report partial outcomes |
|
|
105
|
+
|
|
106
|
+
Other non-row removals retain their scoped editor semantics: rate-limit override
|
|
107
|
+
clear, secret clear, inherited auth/geofence reset, metrics permission clear,
|
|
108
|
+
registrant clear, geofence exemption removal, unsaved handler entries, local
|
|
109
|
+
upload candidates and supported avatar/attachment detachment. Explain fallback
|
|
110
|
+
and inheritance rather than promising restoration of discarded values.
|
|
111
|
+
|
|
112
|
+
## Verification
|
|
113
|
+
|
|
114
|
+
Run `verify:admin-modals`, `verify:admin-modal-scanner`,
|
|
115
|
+
`verify:admin-modal-lifecycle` and `verify:admin-modal-mounted`. The mounted Node
|
|
116
|
+
assertions use JSDOM and React StrictMode for asynchronous scope/effect behavior;
|
|
117
|
+
they do not establish native dialog stacking, focus or layout. Use actual mock
|
|
118
|
+
browser interactions for all family bodies, both themes, desktop/narrow,
|
|
119
|
+
390px embedded hosts and short viewports, including nested dialogs, rich badges,
|
|
120
|
+
keyboard navigation and console checks. The lazy Showcase **Admin modal audit**
|
|
121
|
+
demo supplies long-title, rich/zero/dot badges, nested editor, lifecycle-cancel and
|
|
122
|
+
embedded-host cases. Existing ToastHost top-layer visibility work remains separate.
|
|
@@ -337,7 +337,7 @@ shows the server's own message.
|
|
|
337
337
|
|
|
338
338
|
Anything that reaches the fleet is an `ArmedButton` whose armed label names the
|
|
339
339
|
blast radius: the rules replace (with a plain-language clause diff), override
|
|
340
|
-
removal, ipset enable / disable / sync / refresh
|
|
340
|
+
removal, ipset enable / disable / sync / refresh, allowlist removal and
|
|
341
341
|
whitelist removal. Operations that need input (a reason, an expiry) use
|
|
342
342
|
`formModal` instead — an armed button cannot collect input.
|
|
343
343
|
|
|
@@ -345,9 +345,9 @@ whitelist removal. Operations that need input (a reason, an expiry) use
|
|
|
345
345
|
|
|
346
346
|
Do not add these back.
|
|
347
347
|
|
|
348
|
-
1. **
|
|
349
|
-
|
|
350
|
-
|
|
348
|
+
1. **IPSet deletion.** The backend has `CAN_DELETE=False`; both single-record
|
|
349
|
+
and batch Delete are absent. Header actions use the supported enabled state,
|
|
350
|
+
sync and source-refresh operations with focused confirmation.
|
|
351
351
|
2. **Creating a block from the Blocked IPs table.** The backend supports it and
|
|
352
352
|
web-mojo did not expose it either. Unblock and whitelist — both *relaxing* —
|
|
353
353
|
are exposed.
|
package/docs/admin-phonehub.md
CHANGED
|
@@ -7,21 +7,39 @@ Import `PhoneHubPage`, the three individual pages, models, permission clauses, a
|
|
|
7
7
|
- `POST /api/phonehub/number/normalize` is public. `normalizePhoneNumber()` accepts the backend result only when it is strict E.164.
|
|
8
8
|
- `POST /api/phonehub/number/lookup` requires authentication and is offered only under `PHONE_LOOKUP_UI_PERMISSIONS`. The helper always normalizes first. For a new or expired record it omits `force_refresh` so the backend's normal lookup path refreshes once. For a known-fresh record it requires confirmation, then sends `force_refresh: true`. A successful response must provide a first lookup timestamp/count or advance one of them relative to the prior row; a 2xx stale row is rejected as provider failure evidence.
|
|
9
9
|
- `PhoneNumberModel` requests the default graph but positively sanitizes every row before Query. Raw `lookup_data` is never retained or exported.
|
|
10
|
-
- `SmsModel`
|
|
11
|
-
-
|
|
10
|
+
- `SmsModel` reads SMS audit records. It requests the default graph and positively projects known display fields before Query; raw metadata, provider payloads, and provider message ids are discarded. Its client exporter is bounded and omits message bodies, errors, user/group details, and raw fields.
|
|
11
|
+
- The SMS table's **Send SMS** action opens a focused composer for one recipient and a custom message. It normalizes the recipient through the existing public endpoint, then calls `POST /api/phonehub/sms/send` with exactly `{to_number, body}`. The message body is preserved verbatim. This is global sending through the effective system configuration/default sender: no ambient group, provider override, metadata, or audit-creation call. Drafts and allowlisted receipts stay local to the dialog, outside MutationCache. Sending rechecks the original actor and current permissions after normalization and immediately before transport. A synchronous guard blocks repeat clicks; native Escape/backdrop and Cancel are locked while pending.
|
|
12
|
+
- A successful HTTP envelope can contain a `failed` or `undelivered` SMS record: render that status as failure. Other returned statuses remain literal; only `delivered` confirms delivery. The default graph does not expose `is_test`, so a `sent` receipt does not claim a real delivery. Transport/malformed-receipt failures retain the draft and ask the operator to check SMS Audit before retrying. Every attempted send invalidates the audit list; refresh failure cannot turn a completed send into a retry. Provider refusal is demonstrated in the mock with a recipient ending in `0000`; no mock request contacts a provider.
|
|
13
|
+
- `PhoneConfigModel` reads sanitized scalar/default-graph rows. Saves and `test_connection` use imperative, cache-free calls. The editor follows web-mojo's provider-conditional layout: selecting Twilio, AWS SNS, or Mojo Remote shows and submits only that provider's connection fields. Mojo Remote uses `https://api.mojoverify.com` when the saved URL is unset or the editor is switched to Mojo; an explicitly configured custom URL is preserved. Secret inputs are DOM refs, not React or Query state: blank means untouched and is omitted; replacement must be non-empty; clear sends `null` only after a separate confirmation. Switching providers resets pending clears, and hidden provider credentials remain untouched. Responses are allowlisted before cache reconciliation. This surface edits existing provider credentials; it does not provision Django user/group API keys.
|
|
14
|
+
|
|
15
|
+
Connection tests read the provider's `success` verdict, not the outer REST
|
|
16
|
+
`status`. A failed or missing verdict is an error; `test_mode` is a warning
|
|
17
|
+
that the provider was not contacted. Results persist inside the detail modal
|
|
18
|
+
on both sections, alongside a toast. Scope is labeled in the header and
|
|
19
|
+
Connection section. Editing is disabled while a connection test is pending, so an old response
|
|
20
|
+
cannot certify replacement credentials. Saving clears the previous test result.
|
|
21
|
+
The stored URL is shown literally; a saved URL plus `missing_credentials` for
|
|
22
|
+
Mojo means the API key is absent. Re-enter it in Edit configuration and save;
|
|
23
|
+
blank password inputs preserve existing values and do not prove a key exists.
|
|
12
24
|
|
|
13
25
|
## Exact permissions
|
|
14
26
|
|
|
15
|
-
Every exported array is an ANY-of clause and retains the `sys.` UI namespace. Number view is `view_phone_numbers | manage_phone_numbers | comms | manage_users`; save is `manage_phone_numbers | comms | manage_users`; delete is only `manage_phone_numbers`. SMS view is `view_sms | manage_sms | comms | owner | manage_notifications`; save is `manage_sms | comms | manage_notifications`; delete is `manage_sms | manage_notifications`. Config view/save is `manage_phone_config | manage_groups | comms`; delete is `manage_phone_config | manage_groups`.
|
|
27
|
+
Every exported array is an ANY-of clause and retains the `sys.` UI namespace. Number view is `view_phone_numbers | manage_phone_numbers | comms | manage_users`; save is `manage_phone_numbers | comms | manage_users`; delete is only `manage_phone_numbers`. SMS view is `view_sms | manage_sms | comms | owner | manage_notifications`; save is `manage_sms | comms | manage_notifications`; delete is `manage_sms | manage_notifications`. Config view/save is `manage_phone_config | manage_groups | comms`; delete is `manage_phone_config | manage_groups`. These delete clauses describe low-level API compatibility; built-in number,
|
|
28
|
+
SMS and config Delete controls are absent. The composer additionally requires
|
|
29
|
+
`sys.send_sms | sys.comms`; audit read/manage grants alone never allow sending.
|
|
30
|
+
It remains inside the existing SMS-view audience. Config deactivation uses the
|
|
31
|
+
credential-safe imperative save path. The server remains authoritative.
|
|
16
32
|
|
|
17
33
|
Group choices are fetched only with a separate global group-directory clause and are capped at 100 basic rows. Without that grant the editor does not issue a group-directory request and preserves the existing scope (or creates a system default).
|
|
18
34
|
|
|
19
35
|
## Provider and deletion reality
|
|
20
36
|
|
|
21
|
-
Phone lookup
|
|
37
|
+
Phone lookup uses the global Twilio Lookup integration. Mojo SMS delivery uses the selected PhoneConfig’s saved remote URL and encrypted API key. Twilio SMS delivery uses a complete saved credential pair from the selected config, rejects a partial pair, and falls back to global settings only when neither credential is stored. Its sender uses the config’s from number when set, otherwise the global default. AWS credentials can be stored and tested, but AWS delivery is not wired. `test_mode` only short-circuits connection testing; it does not block sending.
|
|
22
38
|
|
|
23
|
-
An active group config wins; otherwise `PhoneConfig.get_for_group` falls back to the first active system default.
|
|
39
|
+
An active group config wins; otherwise `PhoneConfig.get_for_group` falls back to the first active system default. Deactivating a group config restores that fallback; Admin exposes this reversible lifecycle. Deleting a group cascades its config and SMS rows. Deleting a user cascades their SMS rows. Deleting a config does not delete SMS audit rows. Deleting an SMS row affects only local audit storage and cannot recall provider delivery.
|
|
24
40
|
|
|
25
41
|
## Reuse rules
|
|
26
42
|
|
|
27
|
-
Use KISS detail modals, not `:id` routes. Never add provider payloads to a model graph, cache, error, export, or request history. Never reuse `PhoneConfigModel.useSave()` for credentials.
|
|
43
|
+
Use KISS detail modals, not `:id` routes. Never add provider payloads to a model graph, cache, error, export, or request history. Never reuse `PhoneConfigModel.useSave()` for credentials or `SmsModel.useSave()` to deliver messages. Sending follows the contract above; API-key provisioning remains separate.
|
|
44
|
+
|
|
45
|
+
Verify with `npm run verify:admin-sms-compose`, `npm run verify:admin-phonehub`, and the Admin modal inventory check. The mounted composer regression covers permissions, concurrent clicks, permission revocation, exact global payloads, uncertain results, failed receipts, cache isolation and audit refresh. Use the Phone Hub showcase to click the composer in both themes and at narrow widths.
|
package/docs/admin-push.md
CHANGED
|
@@ -34,3 +34,10 @@ FCM writes are imperative and cache-free. A service account textarea is an uncon
|
|
|
34
34
|
Connection testing calls only `POST /api/account/devices/push/config/<id>/test` with an empty body. The backend uses its dummy token to validate credentials. The portal never accepts a device token. It also does not expose `/send`, caller `/test`, delivery retry, device registration/unregistration, or any delete action.
|
|
35
35
|
|
|
36
36
|
All four REST models have live `CAN_DELETE = false`; the mock returns 403 without removing or cascading anything. Templates/configs can be created and edited under their save clauses. Devices and deliveries remain investigative here.
|
|
37
|
+
|
|
38
|
+
## Modal lifecycle convention
|
|
39
|
+
|
|
40
|
+
See [Admin modal policy](admin-modals.md) for header actions, content-only
|
|
41
|
+
sections, supported lifecycle fields and retained irreversible exceptions.
|
|
42
|
+
Config/template active-state controls live in the detail header. Config saves
|
|
43
|
+
retain write-only credential handling, and test sends keep their separate gate.
|
package/docs/admin-rules.md
CHANGED
|
@@ -85,3 +85,11 @@ The showcase demo is under **Admin → Rule Engine** and includes supported
|
|
|
85
85
|
steps, notification target commas, duplicate/extra parameters, unsupported
|
|
86
86
|
content, structural errors, move/remove actions, exact raw serialization, and
|
|
87
87
|
the empty-chain case.
|
|
88
|
+
|
|
89
|
+
## Modal lifecycle convention
|
|
90
|
+
|
|
91
|
+
See [Admin modal policy](admin-modals.md) for header actions, content-only
|
|
92
|
+
sections, supported lifecycle fields and retained irreversible exceptions.
|
|
93
|
+
RuleSet parents deactivate. Child removal confirms fresh parent/mode/selection;
|
|
94
|
+
ALL broadens, ANY narrows while nonempty, and the last removal is catch-all.
|
|
95
|
+
Partial deletion refreshes authoritative parent and condition state.
|
package/docs/admin-shortlinks.md
CHANGED
|
@@ -18,7 +18,8 @@ Neither table supports search or export, and list parameters are allowlisted.
|
|
|
18
18
|
|
|
19
19
|
## Mutations and short URLs
|
|
20
20
|
|
|
21
|
-
Create
|
|
21
|
+
Create and active-state changes are imperative one-shot requests. Built-in
|
|
22
|
+
Admin deletion is absent; exported low-level delete helpers remain compatible. They
|
|
22
23
|
do not claim fresh-auth protection the backend does not provide and do not use
|
|
23
24
|
retrying mutation hooks. Every outcome runs an authoritative reconciliation in
|
|
24
25
|
`finally` semantics. If reconciliation itself fails, the UI warns that the
|
package/docs/admin-storage.md
CHANGED
|
@@ -65,7 +65,8 @@ FileManager DELETE and `check_public_access` are absent.
|
|
|
65
65
|
`FileModel` forces the list graph and normal saves to `filename`, `is_public`,
|
|
66
66
|
and explicit `group`. Group moves refetch and compare the authoritative FK.
|
|
67
67
|
Selection supports public/private, explicit prepared group move, safe
|
|
68
|
-
same-gesture download
|
|
68
|
+
same-gesture download. File deletion is absent from detail and batch controls;
|
|
69
|
+
`is_active` is not presented as a safe revocation mechanism. Authorized operators
|
|
69
70
|
also get **Add File** and a drag-only whole-page drop overlay. Both open one
|
|
70
71
|
configuration modal; the bounded queue lives above it, so closing the modal
|
|
71
72
|
does not cancel work or discard outcomes.
|
package/docs/auth.md
CHANGED
|
@@ -30,6 +30,34 @@ onAuth('logout', () => queryClient.invalidateQueries()); // every cached answe
|
|
|
30
30
|
- Events for `onAuth`: `'login' | 'logout' | 'refreshed' | 'refresh-failed'
|
|
31
31
|
| 'unauthorized'`.
|
|
32
32
|
|
|
33
|
+
Both localStorage and sessionStorage keep the existing token keys. Refresh
|
|
34
|
+
preserves the chosen storage, including sessions with only a valid refresh token
|
|
35
|
+
at boot. Password login can replace malformed/expired credentials without the
|
|
36
|
+
old dead session blocking its own recovery request.
|
|
37
|
+
|
|
38
|
+
## Packaged Django Admin
|
|
39
|
+
|
|
40
|
+
The generic auth client has no Admin side effects. The packaged app installs
|
|
41
|
+
its app-local source coordinator before `initAuth`, awaits hosted exchange and
|
|
42
|
+
source readiness before creating its router, and wraps all lazy loaders.
|
|
43
|
+
It uses the page's own API origin and hosted auth. Source grants renew at 70%
|
|
44
|
+
of their effective lifetime, independently of access-token refresh; focus and
|
|
45
|
+
suspended-tab recovery recheck the grant. Eager recovery preserves the route.
|
|
46
|
+
|
|
47
|
+
The app's `revokeAdminSourceSession()` first persists a nonsecret logout
|
|
48
|
+
tombstone, clears credentials, then waits for the shared exclusive Web Lock and
|
|
49
|
+
the completed `DELETE <admin>/_session` response. It must be awaited by app
|
|
50
|
+
sign-out callers; generic `logout()` retains its existing synchronous semantics
|
|
51
|
+
for ordinary toolkit consumers. All legacy/gate/Portal participants share
|
|
52
|
+
`mojo:admin-source-session:v1` (lock and channel) and
|
|
53
|
+
`mojo:admin-source-generation:v1` (authoritative localStorage record and per-tab
|
|
54
|
+
sessionStorage binding). No credentials appear in coordination messages/storage.
|
|
55
|
+
|
|
56
|
+
The complete app contract is in the producer repository's
|
|
57
|
+
`docs/admin-artifact.md`; the frozen source fixture is
|
|
58
|
+
`scripts/fixtures/admin-source-session-v1.json`. Django owns real cookie race
|
|
59
|
+
and protected-browser acceptance against the exact artifact manifest digest.
|
|
60
|
+
|
|
33
61
|
## Hosted auth pages (the django-mojo "bouncer" pages)
|
|
34
62
|
|
|
35
63
|
The backend serves themed login/register pages at `<origin>/auth` (behind
|
package/docs/client.md
CHANGED
|
@@ -10,6 +10,13 @@ import {
|
|
|
10
10
|
|
|
11
11
|
## Transport
|
|
12
12
|
|
|
13
|
+
- The controlled Django Admin artifact explicitly selects packaged mode:
|
|
14
|
+
`isPackagedAdmin()` is true, `apiOrigin()` is `window.location.origin`, and
|
|
15
|
+
`usingMockTransport()` is false. Every API path stays `/api/...`, independently
|
|
16
|
+
of the Admin asset mount. Upload/provider credentials retain their existing
|
|
17
|
+
isolation; realtime, hosted auth, exports and Shortlinks reuse `apiOrigin()`.
|
|
18
|
+
This mode is set by the Admin producer, not by ordinary deployment dotenv.
|
|
19
|
+
|
|
13
20
|
- `VITE_MOJO_API` unset → the in-memory mock answers every call
|
|
14
21
|
(`npm run dev`). Set to a django-mojo origin → real backend
|
|
15
22
|
(`npm run dev:live`, configured in `apps/portal/.env.live`).
|
|
@@ -27,6 +34,11 @@ status, semantic `error_code`, and structured safe failure evidence. Nothing out
|
|
|
27
34
|
parses envelopes; `mojoCall(path, {method, params, body})` is the typed
|
|
28
35
|
escape hatch for protocol modules and returns the unwrapped `Envelope`.
|
|
29
36
|
|
|
37
|
+
`FetchOpts.beforeSend` is an optional synchronous final validity check, invoked
|
|
38
|
+
after the auth gate refreshes credentials and before headers/network activity.
|
|
39
|
+
The packaged app uses it to recheck its source-generation binding at the cookie
|
|
40
|
+
boundary. Throwing rejects the request. Ordinary consumers need no callback.
|
|
41
|
+
|
|
30
42
|
Live non-2xx responses and mock `status:false` envelopes preserve the same
|
|
31
43
|
shape. Modern envelopes use numeric top-level `code` plus a string semantic
|
|
32
44
|
`error_code`; numeric legacy `error_code` remains a status fallback. Failed
|