@fuzdev/fuz_app 0.29.0 → 0.31.0
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/dist/actions/CLAUDE.md +630 -0
- package/dist/actions/action_rpc.d.ts +29 -0
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +42 -6
- package/dist/actions/action_types.d.ts +2 -2
- package/dist/actions/cancel.d.ts +12 -13
- package/dist/actions/cancel.d.ts.map +1 -1
- package/dist/actions/cancel.js +10 -13
- package/dist/actions/heartbeat.d.ts +8 -13
- package/dist/actions/heartbeat.d.ts.map +1 -1
- package/dist/actions/heartbeat.js +5 -8
- package/dist/actions/register_action_ws.d.ts +3 -3
- package/dist/actions/register_action_ws.js +2 -2
- package/dist/actions/register_ws_endpoint.d.ts +4 -4
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +3 -3
- package/dist/actions/socket.svelte.d.ts +16 -16
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +15 -15
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.d.ts +15 -0
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +17 -0
- package/dist/auth/CLAUDE.md +923 -0
- package/dist/auth/account_action_specs.d.ts +216 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -0
- package/dist/auth/account_action_specs.js +159 -0
- package/dist/auth/account_actions.d.ts +51 -0
- package/dist/auth/account_actions.d.ts.map +1 -0
- package/dist/auth/account_actions.js +119 -0
- package/dist/auth/account_queries.d.ts +6 -2
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +40 -4
- package/dist/auth/account_routes.d.ts +94 -16
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +108 -180
- package/dist/auth/account_schema.d.ts +85 -30
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +40 -8
- package/dist/auth/admin_action_specs.d.ts +674 -0
- package/dist/auth/admin_action_specs.d.ts.map +1 -0
- package/dist/auth/admin_action_specs.js +287 -0
- package/dist/auth/admin_actions.d.ts +69 -0
- package/dist/auth/admin_actions.d.ts.map +1 -0
- package/dist/auth/admin_actions.js +256 -0
- package/dist/auth/api_token.d.ts +10 -0
- package/dist/auth/api_token.d.ts.map +1 -1
- package/dist/auth/api_token.js +9 -0
- package/dist/auth/api_token_queries.d.ts +3 -3
- package/dist/auth/api_token_queries.js +3 -3
- package/dist/auth/app_settings_schema.d.ts +4 -3
- package/dist/auth/app_settings_schema.d.ts.map +1 -1
- package/dist/auth/app_settings_schema.js +2 -1
- package/dist/auth/audit_log_routes.d.ts +14 -6
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +22 -79
- package/dist/auth/audit_log_schema.d.ts +100 -29
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +83 -11
- package/dist/auth/bootstrap_routes.d.ts +14 -0
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +10 -3
- package/dist/auth/cleanup.d.ts +63 -0
- package/dist/auth/cleanup.d.ts.map +1 -0
- package/dist/auth/cleanup.js +80 -0
- package/dist/auth/invite_schema.d.ts +11 -10
- package/dist/auth/invite_schema.d.ts.map +1 -1
- package/dist/auth/invite_schema.js +4 -3
- package/dist/auth/migrations.d.ts +6 -0
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +28 -0
- package/dist/auth/permit_offer_action_specs.d.ts +364 -0
- package/dist/auth/permit_offer_action_specs.d.ts.map +1 -0
- package/dist/auth/permit_offer_action_specs.js +216 -0
- package/dist/auth/permit_offer_actions.d.ts +96 -0
- package/dist/auth/permit_offer_actions.d.ts.map +1 -0
- package/dist/auth/permit_offer_actions.js +428 -0
- package/dist/auth/permit_offer_notifications.d.ts +361 -0
- package/dist/auth/permit_offer_notifications.d.ts.map +1 -0
- package/dist/auth/permit_offer_notifications.js +179 -0
- package/dist/auth/permit_offer_queries.d.ts +165 -0
- package/dist/auth/permit_offer_queries.d.ts.map +1 -0
- package/dist/auth/permit_offer_queries.js +390 -0
- package/dist/auth/permit_offer_schema.d.ts +103 -0
- package/dist/auth/permit_offer_schema.d.ts.map +1 -0
- package/dist/auth/permit_offer_schema.js +142 -0
- package/dist/auth/permit_queries.d.ts +77 -14
- package/dist/auth/permit_queries.d.ts.map +1 -1
- package/dist/auth/permit_queries.js +119 -24
- package/dist/auth/session_queries.d.ts +4 -2
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +4 -2
- package/dist/auth/signup_routes.d.ts +13 -0
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +14 -7
- package/dist/http/CLAUDE.md +584 -0
- package/dist/http/pending_effects.d.ts +29 -0
- package/dist/http/pending_effects.d.ts.map +1 -0
- package/dist/http/pending_effects.js +31 -0
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +4 -3
- package/dist/rate_limiter.d.ts +30 -0
- package/dist/rate_limiter.d.ts.map +1 -1
- package/dist/rate_limiter.js +25 -2
- package/dist/realtime/sse_auth_guard.d.ts +2 -0
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +5 -3
- package/dist/testing/CLAUDE.md +668 -1
- package/dist/testing/admin_integration.d.ts +10 -7
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +382 -482
- package/dist/testing/app_server.d.ts +7 -6
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/attack_surface.d.ts +9 -3
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +4 -4
- package/dist/testing/audit_completeness.d.ts +6 -0
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +158 -134
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +4 -33
- package/dist/testing/db.d.ts +1 -1
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +2 -0
- package/dist/testing/entities.d.ts +35 -13
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +17 -0
- package/dist/testing/integration.d.ts +10 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +352 -340
- package/dist/testing/integration_helpers.d.ts +16 -5
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +24 -4
- package/dist/testing/rate_limiting.d.ts +7 -0
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +41 -10
- package/dist/testing/rpc_helpers.d.ts +153 -1
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +184 -8
- package/dist/testing/sse_round_trip.d.ts +8 -0
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +10 -3
- package/dist/testing/standard.d.ts +9 -1
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +6 -2
- package/dist/testing/surface_invariants.d.ts +7 -3
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +5 -4
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +9 -38
- package/dist/ui/AccountSessions.svelte +8 -4
- package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAccounts.svelte +61 -33
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAuditLog.svelte +3 -2
- package/dist/ui/AdminAuditLog.svelte.d.ts.map +1 -1
- package/dist/ui/AdminInvites.svelte +3 -2
- package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +14 -9
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminPermitHistory.svelte +3 -2
- package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSessions.svelte +29 -25
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +351 -0
- package/dist/ui/OpenSignupToggle.svelte +6 -3
- package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
- package/dist/ui/PermitOfferForm.svelte +141 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts +14 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts.map +1 -0
- package/dist/ui/PermitOfferHistory.svelte +109 -0
- package/dist/ui/PermitOfferHistory.svelte.d.ts +11 -0
- package/dist/ui/PermitOfferHistory.svelte.d.ts.map +1 -0
- package/dist/ui/PermitOfferInbox.svelte +121 -0
- package/dist/ui/PermitOfferInbox.svelte.d.ts +12 -0
- package/dist/ui/PermitOfferInbox.svelte.d.ts.map +1 -0
- package/dist/ui/account_sessions_state.svelte.d.ts +53 -3
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +39 -16
- package/dist/ui/admin_accounts_state.svelte.d.ts +118 -2
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +99 -23
- package/dist/ui/admin_invites_state.svelte.d.ts +47 -1
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_invites_state.svelte.js +38 -26
- package/dist/ui/admin_sessions_state.svelte.d.ts +26 -0
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_sessions_state.svelte.js +35 -21
- package/dist/ui/app_settings_state.svelte.d.ts +39 -0
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
- package/dist/ui/app_settings_state.svelte.js +34 -18
- package/dist/ui/audit_log_state.svelte.d.ts +40 -3
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +36 -42
- package/dist/ui/auth_state.svelte.d.ts +4 -3
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +4 -1
- package/dist/ui/permit_offers_state.svelte.d.ts +125 -0
- package/dist/ui/permit_offers_state.svelte.d.ts.map +1 -0
- package/dist/ui/permit_offers_state.svelte.js +197 -0
- package/package.json +3 -3
- package/dist/auth/admin_routes.d.ts +0 -29
- package/dist/auth/admin_routes.d.ts.map +0 -1
- package/dist/auth/admin_routes.js +0 -226
- package/dist/auth/app_settings_routes.d.ts +0 -27
- package/dist/auth/app_settings_routes.d.ts.map +0 -1
- package/dist/auth/app_settings_routes.js +0 -66
- package/dist/auth/invite_routes.d.ts +0 -18
- package/dist/auth/invite_routes.d.ts.map +0 -1
- package/dist/auth/invite_routes.js +0 -129
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminSessions.svelte"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminSessions.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminSessions.svelte"],"names":[],"mappings":"AA6FA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,aAAa;;kBAA+E,CAAC;AACjF,KAAK,aAAa,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC;AAC1D,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# ui/
|
|
2
|
+
|
|
3
|
+
Frontend subsystem — Svelte 5 components, reactive state classes, and DOM
|
|
4
|
+
utilities. Cookie-based SPA auth; prerendered static HTML served by Hono
|
|
5
|
+
(no SvelteKit SSR for sessions). State classes extend `Loadable` and
|
|
6
|
+
hold `$state` fields exclusively via runes. Shared dependencies flow
|
|
7
|
+
through Svelte context, never through props — RPC adapters in particular
|
|
8
|
+
are provisioned once at the admin shell and read by every `Admin*.svelte`.
|
|
9
|
+
|
|
10
|
+
See ../../docs/usage.md for end-to-end wiring examples (sections "Permit
|
|
11
|
+
offer UI" and "Admin UI"). This file is a reference, not a tutorial.
|
|
12
|
+
|
|
13
|
+
## Key patterns
|
|
14
|
+
|
|
15
|
+
### RPC adapter contexts with `() => null` fallback
|
|
16
|
+
|
|
17
|
+
Five narrow RPC adapter contexts — `admin_accounts_rpc_context`,
|
|
18
|
+
`admin_invites_rpc_context`, `audit_log_rpc_context`,
|
|
19
|
+
`app_settings_rpc_context`, `account_sessions_rpc_context` — carry a
|
|
20
|
+
reactive `() => Rpc | null` accessor. All five declare a `() => () => null`
|
|
21
|
+
default so components mounted without a provisioner render the "rpc adapter
|
|
22
|
+
not wired" state instead of crashing. (`permit_offers_state_context` carries
|
|
23
|
+
a `PermitOffersState` directly, not an RPC accessor, and isn't counted
|
|
24
|
+
here.) The standard consumer shape:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const get_rpc = admin_accounts_rpc_context.get();
|
|
28
|
+
const admin_accounts = new AdminAccountsState({get_rpc});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
or for direct calls:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const get_rpc = admin_accounts_rpc_context.get();
|
|
35
|
+
const rpc = $derived(get_rpc());
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The provisioner calls `context.set(() => rpc)` once at the admin route
|
|
39
|
+
shell. Every admin component plus `OpenSignupToggle.svelte` consumes the
|
|
40
|
+
context — RPC adapters are never threaded through props.
|
|
41
|
+
|
|
42
|
+
### `has_rpc` gates fetch and mutations
|
|
43
|
+
|
|
44
|
+
Every state class backed by a narrow RPC interface exposes a `has_rpc`
|
|
45
|
+
getter. When `false`, `fetch()`, mutations, and `subscribe` no-op and
|
|
46
|
+
set `error` to `'rpc adapter not wired'`. Post-2026-04-23 RPC migration
|
|
47
|
+
this applies uniformly — `AdminSessionsState`'s listing + mutations all
|
|
48
|
+
run through the shared `AdminAccountsRpc`, so `has_rpc` gates the whole
|
|
49
|
+
surface.
|
|
50
|
+
|
|
51
|
+
### `$state.raw` Map keyed by id + `$derived` views
|
|
52
|
+
|
|
53
|
+
`PermitOffersState` maintains a single `Map<string, PermitOfferJson>` in
|
|
54
|
+
`$state.raw`, keyed by offer id, and exposes `incoming` / `outgoing` /
|
|
55
|
+
`history` as `$derived.by` arrays. Writes go through `#merge_offers`
|
|
56
|
+
(clone-and-replace) / `#remove_offer` — never mutate the Map in place
|
|
57
|
+
because `$state.raw` expects reference swaps.
|
|
58
|
+
|
|
59
|
+
### Reducer pattern for WS notifications
|
|
60
|
+
|
|
61
|
+
`PermitOffersState.apply_notification(notification)` is the single
|
|
62
|
+
reducer — `subscribe(subscribe_fn)` is a thin subscription adapter over
|
|
63
|
+
it. Six methods land on the reducer: `permit_offer_received` /
|
|
64
|
+
`_retracted` / `_accepted` / `_declined` / `_supersede` all merge a
|
|
65
|
+
`{offer}` payload; `permit_revoke` is ignored at this layer (permit
|
|
66
|
+
lifecycle lives in auth/permits state). The six notification specs and
|
|
67
|
+
their payload shapes are defined in `../auth/permit_offer_notifications.ts`
|
|
68
|
+
(see `../auth/CLAUDE.md` §WS notifications).
|
|
69
|
+
|
|
70
|
+
### Svelte 5 inline `$props` shape
|
|
71
|
+
|
|
72
|
+
Always `const {...}: {...} = $props()` — never `interface Props`.
|
|
73
|
+
Destructure defaults in the binding list; put the type literal inline.
|
|
74
|
+
This matches the user-memory Svelte props rule and the existing file
|
|
75
|
+
conventions.
|
|
76
|
+
|
|
77
|
+
### Context over props for shared deps
|
|
78
|
+
|
|
79
|
+
Auth, RPC adapters, sidebar, and permit offers all flow through
|
|
80
|
+
`create_context` from `@fuzdev/fuz_ui/context_helpers.js`. Components
|
|
81
|
+
consume with `const x = x_context.get()` (or a `get_rpc`/`$derived`
|
|
82
|
+
pair when the value may change reactively). New shared state joins the
|
|
83
|
+
pattern rather than reintroducing prop-drilling.
|
|
84
|
+
|
|
85
|
+
## Shell + layout
|
|
86
|
+
|
|
87
|
+
- `AppShell.svelte` — sidebar-and-main shell. Props: `children`,
|
|
88
|
+
`sidebar` (Snippet), `sidebar_width = 180`, `sidebar_state?`,
|
|
89
|
+
`keyboard_shortcut?`, `show_toggle?`, `toggle_button?`.
|
|
90
|
+
Provisions `sidebar_state_context` internally (creates a fresh
|
|
91
|
+
`SidebarState` if `sidebar_state` prop is not supplied).
|
|
92
|
+
- `ColumnLayout.svelte` — fixed `aside` column + fluid `children`
|
|
93
|
+
column; `column_width = '280px'`.
|
|
94
|
+
- `MenuLink.svelte` — SvelteKit `<a>` with `selected`/`highlighted`
|
|
95
|
+
derived from `page.url.pathname`. Takes `path` (resolved via
|
|
96
|
+
`resolve` from `$app/paths`).
|
|
97
|
+
- `sidebar_state.svelte.ts` — `SidebarState` (with `activate()` cleanup
|
|
98
|
+
pattern, optional reactive `enabled` getter), `sidebar_state_context`.
|
|
99
|
+
|
|
100
|
+
## Auth forms
|
|
101
|
+
|
|
102
|
+
All four consume `auth_state_context.get()`; all three form-driven ones
|
|
103
|
+
attach a `FormState` for Enter-advance + blur-touched validation.
|
|
104
|
+
|
|
105
|
+
- `LoginForm.svelte` — props `username_label = 'username or email'`,
|
|
106
|
+
`redirect_on_login`. Clears `auth_state.verify_error` on input.
|
|
107
|
+
- `BootstrapForm.svelte` — token + username + password + confirm;
|
|
108
|
+
validates `Username` schema and `PASSWORD_LENGTH_MIN`; focuses the
|
|
109
|
+
first invalid field on submit.
|
|
110
|
+
- `SignupForm.svelte` — username + optional email + password + confirm;
|
|
111
|
+
calls `auth_state.signup(username, password, email?)`.
|
|
112
|
+
- `LogoutButton.svelte` — wraps `PendingButton`; calls
|
|
113
|
+
`auth_state.logout()` when `onclick` doesn't `preventDefault()`.
|
|
114
|
+
|
|
115
|
+
## Account
|
|
116
|
+
|
|
117
|
+
- `AccountSessions.svelte` — self-serve session list for the logged-in
|
|
118
|
+
account. Instantiates `AccountSessionsState`, renders a `Datatable`
|
|
119
|
+
with per-row `revoke` and an optional `revoke all`. Calling
|
|
120
|
+
`revoke_all` clears `auth_state.verified` so the UI falls back to
|
|
121
|
+
the login page.
|
|
122
|
+
|
|
123
|
+
## Admin
|
|
124
|
+
|
|
125
|
+
Every admin component below consumes its RPC adapter via the matching
|
|
126
|
+
context and delegates rendering to `Datatable` + `ConfirmButton` for
|
|
127
|
+
destructive actions.
|
|
128
|
+
|
|
129
|
+
- `AdminAccounts.svelte` — accounts + permits + pending offers.
|
|
130
|
+
Consumes `admin_accounts_rpc_context`. Per-row actions: grant (+role
|
|
131
|
+
chip with `ConfirmButton`), revoke (`actor_id` + `permit_id`),
|
|
132
|
+
retract pending offer. Tracks `granting_keys` / `revoking_ids` /
|
|
133
|
+
`retracting_ids` for per-action spinners.
|
|
134
|
+
- `AdminAuditLog.svelte` — audit event stream. Consumes
|
|
135
|
+
`audit_log_rpc_context`. Filter by `event_type`, manual refresh,
|
|
136
|
+
toggle SSE streaming (via `EventSource` — not RPC).
|
|
137
|
+
- `AdminInvites.svelte` — invite CRUD + embeds `OpenSignupToggle`.
|
|
138
|
+
Consumes `admin_invites_rpc_context`. Tracks `creating` +
|
|
139
|
+
`deleting_ids`.
|
|
140
|
+
- `AdminOverview.svelte` — dashboard panels (accounts / sessions /
|
|
141
|
+
invites / recent activity / security / system). Consumes all four
|
|
142
|
+
RPC contexts plus `auth_state_context`; fetches in parallel on mount.
|
|
143
|
+
Derives `role_counts`, `failed_logins`, `permit_changes` from
|
|
144
|
+
the audit log.
|
|
145
|
+
- `AdminPermitHistory.svelte` — permit-grant/revoke history table.
|
|
146
|
+
Consumes `audit_log_rpc_context`, calls
|
|
147
|
+
`audit_log.fetch_permit_history()` once on mount.
|
|
148
|
+
- `AdminSessions.svelte` — cross-account active sessions.
|
|
149
|
+
Both listing (`admin_session_list` RPC) and the two revoke-all
|
|
150
|
+
mutations go through `admin_accounts_rpc_context` (reused).
|
|
151
|
+
Per-row: revoke sessions, revoke tokens — both `ConfirmButton`.
|
|
152
|
+
- `AdminSettings.svelte` — shell for `OpenSignupToggle` + the logged-in
|
|
153
|
+
account line + logout `ConfirmButton`. No direct RPC calls.
|
|
154
|
+
- `AdminSurface.svelte` — attack-surface viewer. Fetches
|
|
155
|
+
`/api/surface` (REST) and delegates to `SurfaceExplorer`.
|
|
156
|
+
- `OpenSignupToggle.svelte` — single checkbox bound to
|
|
157
|
+
`AppSettingsState.settings.open_signup`. Consumes
|
|
158
|
+
`app_settings_rpc_context`; hides gracefully when `has_rpc` is `false`.
|
|
159
|
+
- `SurfaceExplorer.svelte` — reads-only `AppSurface` renderer. Props:
|
|
160
|
+
`surface: AppSurface`. Filter routes by auth type; expand a row to
|
|
161
|
+
dump `params`/`query`/`input`/`output`/`errors` schemas as JSON.
|
|
162
|
+
Also tables middleware, env, events, and diagnostics.
|
|
163
|
+
|
|
164
|
+
## Permit offers
|
|
165
|
+
|
|
166
|
+
- `PermitOfferInbox.svelte` — recipient-side pending inbox; renders
|
|
167
|
+
`PermitOffersState.incoming`. Props: `format_actor?`, `format_scope?`,
|
|
168
|
+
`format_role?` — consumers plug in display names for actor/scope ids.
|
|
169
|
+
Accept is a `PendingButton`; decline is a `ConfirmButton` whose
|
|
170
|
+
popover contains a textarea (max `PERMIT_OFFER_MESSAGE_LENGTH_MAX`).
|
|
171
|
+
- `PermitOfferForm.svelte` — grantor-side create form. Props:
|
|
172
|
+
`to_account_id`, `roles: Array<string>` (pre-filtered upstream by
|
|
173
|
+
`web_grantable`), `scope_id = null`, `on_created?`, `format_role?`.
|
|
174
|
+
Surfaces three reason codes with friendly copy:
|
|
175
|
+
`ERROR_OFFER_SELF_TARGET`, `ERROR_OFFER_ROLE_NOT_GRANTABLE`,
|
|
176
|
+
`ERROR_OFFER_NOT_AUTHORIZED` — imported from `../auth/permit_offer_action_specs.js`
|
|
177
|
+
(see `../auth/CLAUDE.md` for `permit_offer_action_specs.ts` + `permit_offer_actions.ts`).
|
|
178
|
+
- `PermitOfferHistory.svelte` — both-directions history (recipient +
|
|
179
|
+
grantor, including terminal). Props: `current_actor_id: string | null`
|
|
180
|
+
(classifies row as "sent" vs "received"), `format_actor?`,
|
|
181
|
+
`format_scope?`, `format_role?`. Consumes
|
|
182
|
+
`permit_offers_state_context`; caller seeds via
|
|
183
|
+
`PermitOffersState.fetch_history()`.
|
|
184
|
+
- `permit_offers_state.svelte.ts` — `PermitOffersState` (extends
|
|
185
|
+
`Loadable`) + `permit_offers_state_context`. Options:
|
|
186
|
+
`rpc: PermitOffersRpc`, `account_id: () => string | null`,
|
|
187
|
+
`actor_id: () => string | null`. The narrow `PermitOffersRpc`
|
|
188
|
+
interface has six methods: `list`, `history`, `create`, `accept`,
|
|
189
|
+
`decline`, `retract`. `$state.raw` Map keyed by offer id;
|
|
190
|
+
`$derived.by` views: `incoming` (recipient-side pending, soonest-
|
|
191
|
+
expiry first), `outgoing` (grantor-side pending, newest-created
|
|
192
|
+
first), `history` (all known, newest-created first). Reducer
|
|
193
|
+
`apply_notification` handles the six permit-offer notification
|
|
194
|
+
methods; `permit_revoke` is deliberately ignored here (auth/permits
|
|
195
|
+
concern). `reset()` clears the Map.
|
|
196
|
+
|
|
197
|
+
## State primitives
|
|
198
|
+
|
|
199
|
+
- `loadable.svelte.ts` — `Loadable<TError = string>` base class.
|
|
200
|
+
`loading`, `error`, `error_data` (raw caught value for programmatic
|
|
201
|
+
inspection). Protected `run(fn, map_error?)` wraps async operations
|
|
202
|
+
with loading + error handling; subclasses add `$state` fields and
|
|
203
|
+
call `run`. `reset()` clears state; subclasses override to clear
|
|
204
|
+
domain data.
|
|
205
|
+
- `auth_state.svelte.ts` — `AuthState`, `auth_state_context`.
|
|
206
|
+
Fields: `verifying`, `verified`, `verify_error`, `account`, `actor`
|
|
207
|
+
(the caller's own `ActorSummaryJson` — surfaced directly so consumers
|
|
208
|
+
don't derive `actor_id` from the permit list), `permits`,
|
|
209
|
+
`active_permits` (derived via `is_permit_active`), `roles` (derived),
|
|
210
|
+
`needs_bootstrap`. Methods: `check_session()`
|
|
211
|
+
(GET `/api/account/status`), `login`, `bootstrap`, `signup`,
|
|
212
|
+
`logout`. Handles 401/403/409/429 translations inline.
|
|
213
|
+
- `table_state.svelte.ts` — `TableState` extends `Loadable`.
|
|
214
|
+
Paginated DB browser state: `table_name`, `columns`, `rows`,
|
|
215
|
+
`total`, `offset`, `limit` (capped by `TABLE_LIMIT_MAX = 1000`),
|
|
216
|
+
`primary_key`. Derived `showing_start`/`showing_end`/`has_prev`/
|
|
217
|
+
`has_next`. Methods: `fetch`, `go_prev`/`go_next`, `delete_row`.
|
|
218
|
+
- `form_state.svelte.ts` — `FormState`. Enter-advance between
|
|
219
|
+
focusable elements via `keydown`; per-field `touched` set via
|
|
220
|
+
delegated `focusout`; form-level `attempted` set on submit attempt.
|
|
221
|
+
Methods: `form()` (returns a Svelte `Attachment` for the form
|
|
222
|
+
element), `show(field)` (touched OR attempted), `is_touched(field)`,
|
|
223
|
+
`touch(field)` (programmatic), `focus(field)` (queries by `name`),
|
|
224
|
+
`attempt()`, `reset()`. In DEV throws if an input loses focus
|
|
225
|
+
without a `name` attribute — all tracked inputs must be named.
|
|
226
|
+
- `sidebar_state.svelte.ts` — see Shell + layout above.
|
|
227
|
+
|
|
228
|
+
## Per-domain state modules
|
|
229
|
+
|
|
230
|
+
- `account_sessions_state.svelte.ts` — `AccountSessionsState` extends
|
|
231
|
+
`Loadable` + `account_sessions_rpc_context` + narrow
|
|
232
|
+
`AccountSessionsRpc` (`list`, `revoke`, `revoke_all`). Wraps the
|
|
233
|
+
`account_session_list` / `account_session_revoke` /
|
|
234
|
+
`account_session_revoke_all` RPC actions. Derived `active_count`.
|
|
235
|
+
- `audit_log_state.svelte.ts` — `AuditLogState` extends `Loadable`
|
|
236
|
+
- `audit_log_rpc_context` + narrow `AuditLogRpc` (`list` +
|
|
237
|
+
`permit_history`). Fields: `events`, `permit_history_events`,
|
|
238
|
+
`connected`. Internal `#last_seq` for SSE gap fill on reconnect.
|
|
239
|
+
Methods: `fetch(options?)` (RPC), `fetch_permit_history`,
|
|
240
|
+
`subscribe()` (opens `EventSource` at `#stream_url`, default
|
|
241
|
+
`/api/admin/audit-log/stream`; prepends new events; refills gap
|
|
242
|
+
via `since_seq`), `disconnect()`. SSE stays on `EventSource` —
|
|
243
|
+
streaming is not an RPC concern.
|
|
244
|
+
- `admin_accounts_state.svelte.ts` — `AdminAccountsState` extends
|
|
245
|
+
`Loadable` + `admin_accounts_rpc_context` + narrow
|
|
246
|
+
`AdminAccountsRpc` (six methods: `list_accounts`, `grant_permit`,
|
|
247
|
+
`revoke_permit`, `retract_offer`, `session_revoke_all`,
|
|
248
|
+
`token_revoke_all` — the last two are also reused by
|
|
249
|
+
`AdminSessionsState`). `SvelteSet`s for in-flight tracking:
|
|
250
|
+
`granting_keys` (`${account_id}:${role}`), `revoking_ids`
|
|
251
|
+
(permit id), `retracting_ids` (offer id). `revoke_permit` keys on
|
|
252
|
+
`actor_id` (permits are actor-scoped — matches `row.actor.id`
|
|
253
|
+
straight from the listing) with optional `reason`.
|
|
254
|
+
- `admin_invites_state.svelte.ts` — `AdminInvitesState` extends
|
|
255
|
+
`Loadable` + `admin_invites_rpc_context` + narrow
|
|
256
|
+
`AdminInvitesRpc` (`list`, `create`, `delete`). Fields:
|
|
257
|
+
`invites`, `creating`, `deleting_ids`; derived `invite_count`,
|
|
258
|
+
`unclaimed_count`.
|
|
259
|
+
- `admin_sessions_state.svelte.ts` — `AdminSessionsState` extends
|
|
260
|
+
`Loadable`. **Reuses** `admin_accounts_rpc_context` /
|
|
261
|
+
`AdminAccountsRpc` for the listing (`list_sessions` wraps
|
|
262
|
+
`admin_session_list`) and the two revoke-all mutations. `SvelteSet`s:
|
|
263
|
+
`revoking_account_ids`, `revoking_token_account_ids`. `has_rpc`
|
|
264
|
+
gates the listing + both revoke controls.
|
|
265
|
+
- `app_settings_state.svelte.ts` — `AppSettingsState` extends
|
|
266
|
+
`Loadable` + `app_settings_rpc_context` + narrow `AppSettingsRpc`
|
|
267
|
+
(`get`, `update`). Fields: `settings`, `updating`. Single mutation
|
|
268
|
+
`update_open_signup(boolean)`.
|
|
269
|
+
|
|
270
|
+
## RPC adapter contexts
|
|
271
|
+
|
|
272
|
+
All five RPC-carrying contexts have a `() => () => null` default and
|
|
273
|
+
share the same `has_rpc`-gated state-class shape; consumers wire a typed
|
|
274
|
+
RPC client to each narrow interface. See "Key patterns" above for the
|
|
275
|
+
provisioner pattern.
|
|
276
|
+
|
|
277
|
+
- `auth_state_context` — carries `AuthState` directly (not an RPC
|
|
278
|
+
accessor). Used by every auth form, `AdminOverview`,
|
|
279
|
+
`AdminSettings`, `AccountSessions`, `LogoutButton`.
|
|
280
|
+
- `admin_accounts_rpc_context` — `() => AdminAccountsRpc | null`.
|
|
281
|
+
Consumed by `AdminAccounts`, `AdminSessions`, `AdminOverview`.
|
|
282
|
+
- `admin_invites_rpc_context` — `() => AdminInvitesRpc | null`.
|
|
283
|
+
Consumed by `AdminInvites`, `AdminOverview`.
|
|
284
|
+
- `audit_log_rpc_context` — `() => AuditLogRpc | null`. Consumed by
|
|
285
|
+
`AdminAuditLog`, `AdminPermitHistory`, `AdminOverview`.
|
|
286
|
+
- `app_settings_rpc_context` — `() => AppSettingsRpc | null`.
|
|
287
|
+
Consumed by `OpenSignupToggle`, `AdminOverview`.
|
|
288
|
+
- `account_sessions_rpc_context` — `() => AccountSessionsRpc | null`.
|
|
289
|
+
Consumed by `AccountSessions`.
|
|
290
|
+
- `permit_offers_state_context` — carries `PermitOffersState`
|
|
291
|
+
directly. Consumed by `PermitOfferInbox`, `PermitOfferForm`,
|
|
292
|
+
`PermitOfferHistory`. Wiring is ctor-bound (RPC + account/actor
|
|
293
|
+
getters), so there's no separate `permit_offers_rpc_context`.
|
|
294
|
+
- `sidebar_state_context` — `() => SidebarState`. Provisioned by
|
|
295
|
+
`AppShell`.
|
|
296
|
+
|
|
297
|
+
## Popovers
|
|
298
|
+
|
|
299
|
+
- `popover.svelte.ts` — `Popover` class. Owns `visible`, `position`,
|
|
300
|
+
`align`, `offset`, `popover_class`, `disable_outside_click` as
|
|
301
|
+
`$state.raw`. Three `Attachment` factories: `container`,
|
|
302
|
+
`trigger(params?)`, `content(params?)`. `show()` / `hide()` /
|
|
303
|
+
`toggle()`, plus `update(params)` to swap config. ARIA roles +
|
|
304
|
+
`aria-expanded` / `aria-controls` wired automatically.
|
|
305
|
+
- `position_helpers.ts` — `Position` / `Alignment` / `CardinalPosition`
|
|
306
|
+
types; `generate_position_styles(position, align, offset)` returns
|
|
307
|
+
CSS styles record for absolute positioning (left/right/top/bottom/
|
|
308
|
+
center/overlay).
|
|
309
|
+
- `PopoverButton.svelte` — button + popover composition. Required
|
|
310
|
+
`popover_content: Snippet<[Popover]>`. Either `children` (simple
|
|
311
|
+
content inside the default `<button>`) or `button: Snippet<[Popover]>`
|
|
312
|
+
(custom trigger) — logs in DEV if both or neither are supplied.
|
|
313
|
+
Auto-hides when `disabled`.
|
|
314
|
+
- `ConfirmButton.svelte` — wraps `PopoverButton` for destructive
|
|
315
|
+
actions. Required `onconfirm: (Popover) => void`. `hide_on_confirm`
|
|
316
|
+
default `true`. `position` default `'left'`. Three optional
|
|
317
|
+
snippets — `children`, `popover_content`, `popover_button_content` —
|
|
318
|
+
each receiving `(Popover, confirm)`. Falls back to a remove-glyph
|
|
319
|
+
button when no snippets are supplied.
|
|
320
|
+
|
|
321
|
+
## Data
|
|
322
|
+
|
|
323
|
+
- `Datatable.svelte` — generic grid (`<script generics="T">`).
|
|
324
|
+
Props: `columns`, `rows`, `row_key = 'id'`, `height?`, optional
|
|
325
|
+
`header` / `cell` / `empty` snippets. Sticky header, CSS-subgrid
|
|
326
|
+
layout, pointer-based column resize (writes deltas to a keyed
|
|
327
|
+
record). Default cell renders `column.format(value, row)` or
|
|
328
|
+
`format_value(value)`.
|
|
329
|
+
- `datatable.ts` — `DatatableColumn<T>` interface (`key`, `label`,
|
|
330
|
+
`width?`, `min_width?`, `format?`), `DATATABLE_COLUMN_WIDTH_DEFAULT`
|
|
331
|
+
(120), `DATATABLE_MIN_COLUMN_WIDTH` (50).
|
|
332
|
+
|
|
333
|
+
## Fetch + format
|
|
334
|
+
|
|
335
|
+
- `ui_fetch.ts` — `ui_fetch(input, init?)` wraps `fetch` with
|
|
336
|
+
`credentials: 'include'` for cookie-based session auth;
|
|
337
|
+
`parse_response_error(response, fallback?)` safely extracts
|
|
338
|
+
`body.error` even from non-JSON responses (HTML 404 pages, etc.).
|
|
339
|
+
- `ui_format.ts` — display helpers:
|
|
340
|
+
- `format_relative_time(timestamp, now?)` — "2m ago", "3h ago",
|
|
341
|
+
"5d ago", "2mo ago", "1y ago"; "just now" when under a minute;
|
|
342
|
+
bidirectional (future timestamps render as "in 5m" etc.).
|
|
343
|
+
- `format_uptime(ms)` — "45s", "12m", "3h 15m", "2d 5h".
|
|
344
|
+
- `truncate_middle(str, max_length, separator = '…')`.
|
|
345
|
+
- `truncate_uuid(uuid)` — 12-char middle-truncation.
|
|
346
|
+
- `format_datetime_local(timestamp)` — absolute UTC string for
|
|
347
|
+
`title` attributes.
|
|
348
|
+
- `format_value(value)` — table-cell stringifier (NULL / undefined /
|
|
349
|
+
JSON / primitive).
|
|
350
|
+
- `format_audit_metadata(event_type, metadata)` — event-type-
|
|
351
|
+
specific metadata summary (switch across every `AuditEventType`).
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {AppSettingsState} from './app_settings_state.svelte.js';
|
|
2
|
+
import {AppSettingsState, app_settings_rpc_context} from './app_settings_state.svelte.js';
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const get_rpc = app_settings_rpc_context.get();
|
|
5
|
+
const app_settings = new AppSettingsState({get_rpc});
|
|
5
6
|
|
|
6
7
|
void app_settings.fetch();
|
|
7
8
|
</script>
|
|
8
9
|
|
|
9
10
|
<div class="open-signup-toggle">
|
|
10
|
-
{#if app_settings.
|
|
11
|
+
{#if !app_settings.has_rpc}
|
|
12
|
+
<p class="text_50">rpc adapter not wired</p>
|
|
13
|
+
{:else if app_settings.loading}
|
|
11
14
|
<p class="text_50">loading settings...</p>
|
|
12
15
|
{:else if app_settings.settings}
|
|
13
16
|
<label class="row">
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OpenSignupToggle.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/OpenSignupToggle.svelte"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OpenSignupToggle.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/OpenSignupToggle.svelte"],"names":[],"mappings":"AAqCA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,gBAAgB;;kBAA+E,CAAC;AACpF,KAAK,gBAAgB,GAAG,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAChE,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Grantor-side permit offer form.
|
|
4
|
+
*
|
|
5
|
+
* Caller supplies `to_account_id`, the subset of roles the grantor may
|
|
6
|
+
* offer (typically filtered by `web_grantable`), an optional `scope_id`,
|
|
7
|
+
* and an optional `on_created` callback for post-submit UX. Errors from
|
|
8
|
+
* the RPC surface the three distinct reason codes — self-target,
|
|
9
|
+
* role-not-grantable, not-authorized — so consumers can render them
|
|
10
|
+
* appropriately.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import PendingButton from '@fuzdev/fuz_ui/PendingButton.svelte';
|
|
14
|
+
|
|
15
|
+
import {permit_offers_state_context} from './permit_offers_state.svelte.js';
|
|
16
|
+
import {FormState} from './form_state.svelte.js';
|
|
17
|
+
import {
|
|
18
|
+
PERMIT_OFFER_MESSAGE_LENGTH_MAX,
|
|
19
|
+
type PermitOfferJson,
|
|
20
|
+
} from '../auth/permit_offer_schema.js';
|
|
21
|
+
import {
|
|
22
|
+
ERROR_OFFER_NOT_AUTHORIZED,
|
|
23
|
+
ERROR_OFFER_ROLE_NOT_GRANTABLE,
|
|
24
|
+
ERROR_OFFER_SELF_TARGET,
|
|
25
|
+
} from '../auth/permit_offer_action_specs.js';
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
to_account_id,
|
|
29
|
+
roles,
|
|
30
|
+
scope_id = null,
|
|
31
|
+
on_created,
|
|
32
|
+
format_role = (role: string) => role,
|
|
33
|
+
}: {
|
|
34
|
+
to_account_id: string;
|
|
35
|
+
/** Roles the caller may offer — caller filters by `web_grantable` upstream. */
|
|
36
|
+
roles: Array<string>;
|
|
37
|
+
/** Resource scope for the offer; `null` (default) yields a global offer. */
|
|
38
|
+
scope_id?: string | null;
|
|
39
|
+
on_created?: (offer: PermitOfferJson) => void;
|
|
40
|
+
format_role?: (role: string) => string;
|
|
41
|
+
} = $props();
|
|
42
|
+
|
|
43
|
+
const permit_offers = permit_offers_state_context.get();
|
|
44
|
+
const form_state = new FormState();
|
|
45
|
+
|
|
46
|
+
let role: string | undefined = $state.raw();
|
|
47
|
+
const selected_role = $derived(role ?? roles[0] ?? '');
|
|
48
|
+
let message = $state.raw('');
|
|
49
|
+
let local_error: string | null = $state.raw(null);
|
|
50
|
+
|
|
51
|
+
const submitting = $derived(permit_offers.loading);
|
|
52
|
+
|
|
53
|
+
const surface_error = (reason: string | null): string | null => {
|
|
54
|
+
switch (reason) {
|
|
55
|
+
case ERROR_OFFER_SELF_TARGET:
|
|
56
|
+
return 'You cannot offer a permit to yourself.';
|
|
57
|
+
case ERROR_OFFER_ROLE_NOT_GRANTABLE:
|
|
58
|
+
return 'That role cannot be offered through this form.';
|
|
59
|
+
case ERROR_OFFER_NOT_AUTHORIZED:
|
|
60
|
+
return 'You are not authorized to offer that role.';
|
|
61
|
+
default:
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handle_submit = async (): Promise<void> => {
|
|
67
|
+
form_state.attempt();
|
|
68
|
+
local_error = null;
|
|
69
|
+
if (!selected_role) {
|
|
70
|
+
form_state.focus('role');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const offer = await permit_offers.create({
|
|
74
|
+
to_account_id,
|
|
75
|
+
role: selected_role,
|
|
76
|
+
scope_id,
|
|
77
|
+
message: message.trim() || null,
|
|
78
|
+
});
|
|
79
|
+
if (offer) {
|
|
80
|
+
message = '';
|
|
81
|
+
form_state.reset();
|
|
82
|
+
on_created?.(offer);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Structured error data carries the reason; fall back to raw error string.
|
|
86
|
+
const data = permit_offers.error_data as
|
|
87
|
+
| {data?: {reason?: string}; reason?: string}
|
|
88
|
+
| null
|
|
89
|
+
| undefined;
|
|
90
|
+
const reason = data?.data?.reason ?? data?.reason ?? null;
|
|
91
|
+
local_error = surface_error(reason) ?? permit_offers.error;
|
|
92
|
+
};
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<form
|
|
96
|
+
class="width_atmost_md column gap_sm"
|
|
97
|
+
onsubmit={(e) => {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
void handle_submit();
|
|
100
|
+
}}
|
|
101
|
+
{@attach form_state.form()}
|
|
102
|
+
>
|
|
103
|
+
<label>
|
|
104
|
+
<div class="title">role</div>
|
|
105
|
+
<select
|
|
106
|
+
name="role"
|
|
107
|
+
value={selected_role}
|
|
108
|
+
onchange={(e) => (role = e.currentTarget.value)}
|
|
109
|
+
disabled={submitting}
|
|
110
|
+
>
|
|
111
|
+
{#each roles as role_option (role_option)}
|
|
112
|
+
<option value={role_option}>{format_role(role_option)}</option>
|
|
113
|
+
{/each}
|
|
114
|
+
</select>
|
|
115
|
+
</label>
|
|
116
|
+
|
|
117
|
+
<label>
|
|
118
|
+
<div class="title">message (optional)</div>
|
|
119
|
+
<textarea
|
|
120
|
+
name="message"
|
|
121
|
+
bind:value={message}
|
|
122
|
+
maxlength={PERMIT_OFFER_MESSAGE_LENGTH_MAX}
|
|
123
|
+
placeholder="optional note for the recipient"
|
|
124
|
+
disabled={submitting}
|
|
125
|
+
></textarea>
|
|
126
|
+
</label>
|
|
127
|
+
|
|
128
|
+
<div class="row gap_sm">
|
|
129
|
+
<PendingButton
|
|
130
|
+
pending={submitting}
|
|
131
|
+
disabled={submitting || !selected_role}
|
|
132
|
+
onclick={handle_submit}
|
|
133
|
+
>
|
|
134
|
+
send offer
|
|
135
|
+
</PendingButton>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
{#if local_error}
|
|
139
|
+
<p class="color_c_50 font_size_sm mt_xs mb_0">{local_error}</p>
|
|
140
|
+
{/if}
|
|
141
|
+
</form>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type PermitOfferJson } from '../auth/permit_offer_schema.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
to_account_id: string;
|
|
4
|
+
/** Roles the caller may offer — caller filters by `web_grantable` upstream. */
|
|
5
|
+
roles: Array<string>;
|
|
6
|
+
/** Resource scope for the offer; `null` (default) yields a global offer. */
|
|
7
|
+
scope_id?: string | null;
|
|
8
|
+
on_created?: (offer: PermitOfferJson) => void;
|
|
9
|
+
format_role?: (role: string) => string;
|
|
10
|
+
};
|
|
11
|
+
declare const PermitOfferForm: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type PermitOfferForm = ReturnType<typeof PermitOfferForm>;
|
|
13
|
+
export default PermitOfferForm;
|
|
14
|
+
//# sourceMappingURL=PermitOfferForm.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PermitOfferForm.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/PermitOfferForm.svelte"],"names":[],"mappings":"AAiBA,OAAO,EAEL,KAAK,eAAe,EACpB,MAAM,gCAAgC,CAAC;AAOxC,KAAK,gBAAgB,GAAI;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAC9C,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC,CAAC;AAsGH,QAAA,MAAM,eAAe,sDAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Both-directions permit offer history table.
|
|
4
|
+
*
|
|
5
|
+
* Shows every offer involving the current account — recipient or grantor
|
|
6
|
+
* — including terminal rows (accepted, declined, retracted, superseded,
|
|
7
|
+
* expired). Backed by `permit_offer_history` (new RPC action); seeded
|
|
8
|
+
* via `PermitOffersState.fetch_history()`.
|
|
9
|
+
*
|
|
10
|
+
* Consumers plug in optional `format_actor` / `format_scope` callbacks
|
|
11
|
+
* for display names.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {permit_offers_state_context} from './permit_offers_state.svelte.js';
|
|
15
|
+
import Datatable from './Datatable.svelte';
|
|
16
|
+
import type {DatatableColumn} from './datatable.js';
|
|
17
|
+
import {format_relative_time, format_datetime_local, truncate_uuid} from './ui_format.js';
|
|
18
|
+
import type {PermitOfferJson} from '../auth/permit_offer_schema.js';
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
current_actor_id,
|
|
22
|
+
format_actor = truncate_uuid,
|
|
23
|
+
format_scope,
|
|
24
|
+
format_role = (role: string) => role,
|
|
25
|
+
}: {
|
|
26
|
+
/** Used to label a row as sent vs received. When `null`, direction shows as `-`. */
|
|
27
|
+
current_actor_id: string | null;
|
|
28
|
+
format_actor?: (from_actor_id: string) => string;
|
|
29
|
+
format_scope?: (scope_id: string | null, role: string) => string;
|
|
30
|
+
format_role?: (role: string) => string;
|
|
31
|
+
} = $props();
|
|
32
|
+
|
|
33
|
+
const permit_offers = permit_offers_state_context.get();
|
|
34
|
+
|
|
35
|
+
const now = $state.raw(Date.now());
|
|
36
|
+
|
|
37
|
+
const status_of = (offer: PermitOfferJson): string => {
|
|
38
|
+
if (offer.accepted_at) return 'accepted';
|
|
39
|
+
if (offer.declined_at) return 'declined';
|
|
40
|
+
if (offer.retracted_at) return 'retracted';
|
|
41
|
+
if (offer.superseded_at) return 'superseded';
|
|
42
|
+
if (Date.parse(offer.expires_at) <= now) return 'expired';
|
|
43
|
+
return 'pending';
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const status_chip_class = (status: string): string => {
|
|
47
|
+
switch (status) {
|
|
48
|
+
case 'accepted':
|
|
49
|
+
return 'chip color_b';
|
|
50
|
+
case 'pending':
|
|
51
|
+
return 'chip color_a';
|
|
52
|
+
case 'declined':
|
|
53
|
+
case 'retracted':
|
|
54
|
+
case 'superseded':
|
|
55
|
+
case 'expired':
|
|
56
|
+
return 'chip color_c';
|
|
57
|
+
default:
|
|
58
|
+
return 'chip';
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const scope_label = (scope_id: string | null, role: string): string => {
|
|
63
|
+
if (format_scope) return format_scope(scope_id, role);
|
|
64
|
+
return scope_id === null ? 'global' : truncate_uuid(scope_id);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const columns: Array<DatatableColumn<PermitOfferJson>> = [
|
|
68
|
+
{key: 'from_actor_id', label: 'direction', width: 110},
|
|
69
|
+
{key: 'role', label: 'role', width: 140},
|
|
70
|
+
{key: 'scope_id', label: 'scope', width: 160},
|
|
71
|
+
{key: 'created_at', label: 'status', width: 120},
|
|
72
|
+
{key: 'expires_at', label: 'time', width: 110},
|
|
73
|
+
];
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<section>
|
|
77
|
+
<h2>offer history</h2>
|
|
78
|
+
|
|
79
|
+
{#if permit_offers.loading}
|
|
80
|
+
<p class="text_50">loading history...</p>
|
|
81
|
+
{:else if permit_offers.error}
|
|
82
|
+
<p class="color_c_50">{permit_offers.error}</p>
|
|
83
|
+
{:else}
|
|
84
|
+
<Datatable {columns} rows={permit_offers.history} height="400px" row_key="id">
|
|
85
|
+
{#snippet cell(column, row)}
|
|
86
|
+
{#if column.key === 'from_actor_id'}
|
|
87
|
+
{#if current_actor_id && row.from_actor_id === current_actor_id}
|
|
88
|
+
<span class="chip">sent</span>
|
|
89
|
+
<span class="text_50 font_size_sm">to {truncate_uuid(row.to_account_id)}</span>
|
|
90
|
+
{:else}
|
|
91
|
+
<span class="chip">received</span>
|
|
92
|
+
<span class="text_50 font_size_sm">from {format_actor(row.from_actor_id)}</span>
|
|
93
|
+
{/if}
|
|
94
|
+
{:else if column.key === 'role'}
|
|
95
|
+
{format_role(row.role)}
|
|
96
|
+
{:else if column.key === 'scope_id'}
|
|
97
|
+
<span class="text_50">{scope_label(row.scope_id, row.role)}</span>
|
|
98
|
+
{:else if column.key === 'created_at'}
|
|
99
|
+
{@const status = status_of(row)}
|
|
100
|
+
<span class={status_chip_class(status)}>{status}</span>
|
|
101
|
+
{:else if column.key === 'expires_at'}
|
|
102
|
+
<span title={format_datetime_local(row.created_at)}>
|
|
103
|
+
{format_relative_time(row.created_at)}
|
|
104
|
+
</span>
|
|
105
|
+
{/if}
|
|
106
|
+
{/snippet}
|
|
107
|
+
</Datatable>
|
|
108
|
+
{/if}
|
|
109
|
+
</section>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
/** Used to label a row as sent vs received. When `null`, direction shows as `-`. */
|
|
3
|
+
current_actor_id: string | null;
|
|
4
|
+
format_actor?: (from_actor_id: string) => string;
|
|
5
|
+
format_scope?: (scope_id: string | null, role: string) => string;
|
|
6
|
+
format_role?: (role: string) => string;
|
|
7
|
+
};
|
|
8
|
+
declare const PermitOfferHistory: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type PermitOfferHistory = ReturnType<typeof PermitOfferHistory>;
|
|
10
|
+
export default PermitOfferHistory;
|
|
11
|
+
//# sourceMappingURL=PermitOfferHistory.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PermitOfferHistory.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/PermitOfferHistory.svelte"],"names":[],"mappings":"AAoBC,KAAK,gBAAgB,GAAI;IACxB,oFAAoF;IACpF,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,YAAY,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,CAAC;IACjD,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACjE,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC,CAAC;AAiGH,QAAA,MAAM,kBAAkB,sDAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
|