@fuzdev/fuz_app 0.60.0 → 0.62.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 +28 -22
- package/dist/auth/CLAUDE.md +4 -4
- package/dist/server/app_server.d.ts +54 -6
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +32 -4
- package/dist/testing/CLAUDE.md +8 -8
- package/dist/ui/AccountSessions.svelte +21 -6
- package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAccounts.svelte +32 -25
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAuditLog.svelte +3 -3
- package/dist/ui/AdminInvites.svelte +20 -15
- package/dist/ui/AdminOverview.svelte +19 -21
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminRoleGrantHistory.svelte +3 -3
- package/dist/ui/AdminSessions.svelte +19 -21
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSettings.svelte +1 -3
- package/dist/ui/AdminSettings.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +123 -69
- package/dist/ui/ConfirmButton.svelte +82 -24
- package/dist/ui/ConfirmButton.svelte.d.ts +8 -34
- package/dist/ui/ConfirmButton.svelte.d.ts.map +1 -1
- package/dist/ui/OpenSignupToggle.svelte +6 -4
- package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
- package/dist/ui/RoleGrantOfferForm.svelte +4 -4
- package/dist/ui/RoleGrantOfferHistory.svelte +3 -3
- package/dist/ui/RoleGrantOfferInbox.svelte +10 -6
- package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.d.ts +17 -7
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +32 -33
- package/dist/ui/admin_accounts_state.svelte.d.ts +48 -17
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +58 -76
- package/dist/ui/admin_invites_state.svelte.d.ts +14 -7
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_invites_state.svelte.js +32 -48
- package/dist/ui/admin_sessions_state.svelte.d.ts +15 -8
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_sessions_state.svelte.js +30 -47
- package/dist/ui/app_settings_state.svelte.d.ts +8 -3
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
- package/dist/ui/app_settings_state.svelte.js +19 -27
- package/dist/ui/async_slot.svelte.d.ts +173 -0
- package/dist/ui/async_slot.svelte.d.ts.map +1 -0
- package/dist/ui/async_slot.svelte.js +241 -0
- package/dist/ui/audit_log_state.svelte.d.ts +8 -2
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +19 -18
- package/dist/ui/keyed_async_slot.svelte.d.ts +139 -0
- package/dist/ui/keyed_async_slot.svelte.d.ts.map +1 -0
- package/dist/ui/keyed_async_slot.svelte.js +177 -0
- package/dist/ui/role_grant_offers_state.svelte.d.ts +39 -7
- package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
- package/dist/ui/role_grant_offers_state.svelte.js +34 -15
- package/dist/ui/table_state.svelte.d.ts +10 -7
- package/dist/ui/table_state.svelte.d.ts.map +1 -1
- package/dist/ui/table_state.svelte.js +11 -8
- package/package.json +1 -1
- package/dist/ui/loadable.svelte.d.ts +0 -60
- package/dist/ui/loadable.svelte.d.ts.map +0 -1
- package/dist/ui/loadable.svelte.js +0 -80
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reactive state for admin account management.
|
|
3
3
|
*
|
|
4
|
+
* Holds one fetch `AsyncSlot` (`list`) plus three `KeyedAsyncSlot`s —
|
|
5
|
+
* `grant` (offer creation, keyed by `account_id:role` or
|
|
6
|
+
* `account_id:role:to_actor_id`), `revoke` (role_grant revoke, keyed
|
|
7
|
+
* by `role_grant_id`), `retract` (offer retraction, keyed by
|
|
8
|
+
* `offer_id`). Per-row supersession is correct (clicking row B no
|
|
9
|
+
* longer aborts row A) and `error(key)` surfaces failure per-row.
|
|
10
|
+
* Method names use the `submit_*` prefix to avoid slot-name
|
|
11
|
+
* collisions.
|
|
12
|
+
*
|
|
4
13
|
* @module
|
|
5
14
|
*/
|
|
6
|
-
import { SvelteSet } from 'svelte/reactivity';
|
|
7
15
|
import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
|
|
8
|
-
import {
|
|
16
|
+
import { AsyncSlot } from './async_slot.svelte.js';
|
|
17
|
+
import { KeyedAsyncSlot } from './keyed_async_slot.svelte.js';
|
|
9
18
|
/**
|
|
10
19
|
* Svelte context carrying the reactive `AdminAccountsRpc` accessor. The
|
|
11
20
|
* provisioner (typically the admin route shell) calls `set(() => rpc)`;
|
|
@@ -17,16 +26,23 @@ import { Loadable } from './loadable.svelte.js';
|
|
|
17
26
|
* not wired" path.
|
|
18
27
|
*/
|
|
19
28
|
export const admin_accounts_rpc_context = create_context(() => () => null);
|
|
20
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Compose the `grant` keyed-slot key for an offer. Account-grain offers
|
|
31
|
+
* key on `${account_id}:${role}`; actor-targeted offers add the actor
|
|
32
|
+
* suffix so the two variants can be in flight simultaneously without
|
|
33
|
+
* colliding on per-row spinners.
|
|
34
|
+
*/
|
|
35
|
+
export const grant_key = (account_id, role, to_actor_id) => to_actor_id ? `${account_id}:${role}:${to_actor_id}` : `${account_id}:${role}`;
|
|
36
|
+
export class AdminAccountsState {
|
|
21
37
|
#get_rpc;
|
|
38
|
+
list = new AsyncSlot();
|
|
39
|
+
grant = new KeyedAsyncSlot();
|
|
40
|
+
revoke = new KeyedAsyncSlot();
|
|
41
|
+
retract = new KeyedAsyncSlot();
|
|
22
42
|
accounts = $state.raw([]);
|
|
23
43
|
grantable_roles = $state.raw([]);
|
|
24
|
-
granting_keys = new SvelteSet();
|
|
25
|
-
revoking_ids = new SvelteSet();
|
|
26
|
-
retracting_ids = new SvelteSet();
|
|
27
44
|
account_count = $derived(this.accounts.length);
|
|
28
45
|
constructor(options) {
|
|
29
|
-
super();
|
|
30
46
|
this.#get_rpc = options?.get_rpc ?? (() => null);
|
|
31
47
|
}
|
|
32
48
|
/**
|
|
@@ -36,14 +52,15 @@ export class AdminAccountsState extends Loadable {
|
|
|
36
52
|
get has_rpc() {
|
|
37
53
|
return this.#get_rpc() !== null;
|
|
38
54
|
}
|
|
39
|
-
|
|
55
|
+
#require_rpc() {
|
|
40
56
|
const rpc = this.#get_rpc();
|
|
41
|
-
if (!rpc)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
if (!rpc)
|
|
58
|
+
throw new Error('rpc adapter not wired');
|
|
59
|
+
return rpc;
|
|
60
|
+
}
|
|
61
|
+
async fetch() {
|
|
62
|
+
await this.list.run(async () => {
|
|
63
|
+
const { accounts, grantable_roles } = await this.#require_rpc().list_accounts();
|
|
47
64
|
this.accounts = accounts;
|
|
48
65
|
this.grantable_roles = grantable_roles;
|
|
49
66
|
});
|
|
@@ -59,40 +76,25 @@ export class AdminAccountsState extends Loadable {
|
|
|
59
76
|
* across those calls.
|
|
60
77
|
*
|
|
61
78
|
* `to_actor_id` (optional) narrows the offer to a specific actor on
|
|
62
|
-
* `account_id`; the
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* No-op when the rpc adapter is absent; `error` is set to a descriptive
|
|
69
|
-
* message so the UI surfaces the misconfiguration.
|
|
79
|
+
* `account_id`; the keyed-slot key stays at `account_id:role` for the
|
|
80
|
+
* account-grain default (so existing consumers keep working) and
|
|
81
|
+
* becomes `account_id:role:to_actor_id` when actor-targeted, so the
|
|
82
|
+
* two variants can be in flight without colliding on the per-row
|
|
83
|
+
* spinner.
|
|
70
84
|
*/
|
|
71
|
-
async
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return undefined;
|
|
76
|
-
}
|
|
77
|
-
const key = to_actor_id ? `${account_id}:${role}:${to_actor_id}` : `${account_id}:${role}`;
|
|
78
|
-
this.granting_keys.add(key);
|
|
79
|
-
try {
|
|
80
|
-
const { offer } = await rpc.create_role_grant({
|
|
85
|
+
async submit_grant(account_id, role, to_actor_id) {
|
|
86
|
+
const key = grant_key(account_id, role, to_actor_id);
|
|
87
|
+
const offer = await this.grant.run(key, async () => {
|
|
88
|
+
const result = await this.#require_rpc().create_role_grant({
|
|
81
89
|
to_account_id: account_id,
|
|
82
90
|
role,
|
|
83
91
|
...(to_actor_id ? { to_actor_id } : {}),
|
|
84
92
|
});
|
|
85
|
-
|
|
93
|
+
return result.offer;
|
|
94
|
+
});
|
|
95
|
+
if (offer)
|
|
86
96
|
await this.fetch();
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
catch (e) {
|
|
90
|
-
this.error = e instanceof Error ? e.message : 'Failed to grant role_grant';
|
|
91
|
-
return undefined;
|
|
92
|
-
}
|
|
93
|
-
finally {
|
|
94
|
-
this.granting_keys.delete(key);
|
|
95
|
-
}
|
|
97
|
+
return offer;
|
|
96
98
|
}
|
|
97
99
|
/**
|
|
98
100
|
* Revoke an active role_grant via the `role_grant_revoke` RPC.
|
|
@@ -103,24 +105,16 @@ export class AdminAccountsState extends Loadable {
|
|
|
103
105
|
* The optional `reason` is stamped on `role_grant.revoked_reason` and
|
|
104
106
|
* surfaced on the revokee's WS notification.
|
|
105
107
|
*/
|
|
106
|
-
async
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.error = null;
|
|
108
|
+
async submit_revoke(actor_id, role_grant_id, reason) {
|
|
109
|
+
await this.revoke.run(role_grant_id, async () => {
|
|
110
|
+
await this.#require_rpc().revoke_role_grant({
|
|
111
|
+
actor_id,
|
|
112
|
+
role_grant_id,
|
|
113
|
+
reason: reason ?? null,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
if (this.revoke.succeeded(role_grant_id))
|
|
116
117
|
await this.fetch();
|
|
117
|
-
}
|
|
118
|
-
catch (e) {
|
|
119
|
-
this.error = e instanceof Error ? e.message : 'Failed to revoke role_grant';
|
|
120
|
-
}
|
|
121
|
-
finally {
|
|
122
|
-
this.revoking_ids.delete(role_grant_id);
|
|
123
|
-
}
|
|
124
118
|
}
|
|
125
119
|
/**
|
|
126
120
|
* Retract a pending offer the admin issued via the `role_grant_offer_retract`
|
|
@@ -130,23 +124,11 @@ export class AdminAccountsState extends Loadable {
|
|
|
130
124
|
* After success, refetches the listing so `pending_offers` drops the
|
|
131
125
|
* row and the "+ {role}" button un-hides.
|
|
132
126
|
*/
|
|
133
|
-
async
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
this.retracting_ids.add(offer_id);
|
|
140
|
-
try {
|
|
141
|
-
await rpc.retract_offer(offer_id);
|
|
142
|
-
this.error = null;
|
|
127
|
+
async submit_retract(offer_id) {
|
|
128
|
+
await this.retract.run(offer_id, async () => {
|
|
129
|
+
await this.#require_rpc().retract_offer(offer_id);
|
|
130
|
+
});
|
|
131
|
+
if (this.retract.succeeded(offer_id))
|
|
143
132
|
await this.fetch();
|
|
144
|
-
}
|
|
145
|
-
catch (e) {
|
|
146
|
-
this.error = e instanceof Error ? e.message : 'Failed to retract offer';
|
|
147
|
-
}
|
|
148
|
-
finally {
|
|
149
|
-
this.retracting_ids.delete(offer_id);
|
|
150
|
-
}
|
|
151
133
|
}
|
|
152
134
|
}
|
|
@@ -5,11 +5,17 @@
|
|
|
5
5
|
* class stays decoupled from the concrete RPC client so tests can inject
|
|
6
6
|
* plain-function stubs. Mirrors `AdminAccountsRpc` / `AuditLogRpc`.
|
|
7
7
|
*
|
|
8
|
+
* Holds two `AsyncSlot`s — `list` (fetch) and `create` (singular write) —
|
|
9
|
+
* plus one `KeyedAsyncSlot<Uuid>` (`remove`) for the per-row delete with
|
|
10
|
+
* correct per-row supersession and per-row error surfacing. Method names
|
|
11
|
+
* use the `submit_*` prefix to avoid slot-name collisions (`delete` is
|
|
12
|
+
* reserved at top-level positions; renamed for symmetry).
|
|
13
|
+
*
|
|
8
14
|
* @module
|
|
9
15
|
*/
|
|
10
|
-
import { SvelteSet } from 'svelte/reactivity';
|
|
11
16
|
import type { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
12
|
-
import {
|
|
17
|
+
import { AsyncSlot } from './async_slot.svelte.js';
|
|
18
|
+
import { KeyedAsyncSlot } from './keyed_async_slot.svelte.js';
|
|
13
19
|
import type { InviteWithUsernamesJson } from '../auth/invite_schema.js';
|
|
14
20
|
import type { InviteCreateInput, InviteCreateOutput, InviteDeleteInput, InviteDeleteOutput, InviteListOutput } from '../auth/admin_action_specs.js';
|
|
15
21
|
/**
|
|
@@ -39,18 +45,19 @@ export interface AdminInvitesStateOptions {
|
|
|
39
45
|
*/
|
|
40
46
|
get_rpc?: () => AdminInvitesRpc | null;
|
|
41
47
|
}
|
|
42
|
-
export declare class AdminInvitesState
|
|
48
|
+
export declare class AdminInvitesState {
|
|
43
49
|
#private;
|
|
50
|
+
readonly list: AsyncSlot<void, string>;
|
|
51
|
+
readonly create: AsyncSlot<void, string>;
|
|
52
|
+
readonly remove: KeyedAsyncSlot<string & import("zod").$brand<"Uuid">, void, string>;
|
|
44
53
|
invites: Array<InviteWithUsernamesJson>;
|
|
45
|
-
creating: boolean;
|
|
46
|
-
readonly deleting_ids: SvelteSet<string>;
|
|
47
54
|
readonly invite_count: number;
|
|
48
55
|
readonly unclaimed_count: number;
|
|
49
56
|
constructor(options?: AdminInvitesStateOptions);
|
|
50
57
|
/** True when an RPC adapter is wired. All ops require it. */
|
|
51
58
|
get has_rpc(): boolean;
|
|
52
59
|
fetch(): Promise<void>;
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
submit_create(email?: string, username?: string): Promise<boolean>;
|
|
61
|
+
submit_delete(id: Uuid): Promise<void>;
|
|
55
62
|
}
|
|
56
63
|
//# sourceMappingURL=admin_invites_state.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_invites_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_invites_state.svelte.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"admin_invites_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_invites_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAC,SAAS,EAAC,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAC5D,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,0BAA0B,CAAC;AACtE,OAAO,KAAK,EACX,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,MAAM,+BAA+B,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACtC,MAAM,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACnE,MAAM,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACnE;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB;qBAAwB,eAAe,GAAG,IAAI;yBAAtB,eAAe,GAAG,IAAI,wBAAtB,eAAe,GAAG,IAAI;CAEnF,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACxC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;CACvC;AAED,qBAAa,iBAAiB;;IAG7B,QAAQ,CAAC,IAAI,0BAAyB;IACtC,QAAQ,CAAC,MAAM,0BAAyB;IACxC,QAAQ,CAAC,MAAM,sEAAoC;IAEnD,OAAO,EAAE,KAAK,CAAC,uBAAuB,CAAC,CAAkB;IAEzD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAiC;IAC9D,QAAQ,CAAC,eAAe,EAAE,MAAM,CAA8D;gBAElF,OAAO,CAAC,EAAE,wBAAwB;IAI9C,6DAA6D;IAC7D,IAAI,OAAO,IAAI,OAAO,CAErB;IAQK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASlE,aAAa,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAM5C"}
|
|
@@ -5,79 +5,63 @@
|
|
|
5
5
|
* class stays decoupled from the concrete RPC client so tests can inject
|
|
6
6
|
* plain-function stubs. Mirrors `AdminAccountsRpc` / `AuditLogRpc`.
|
|
7
7
|
*
|
|
8
|
+
* Holds two `AsyncSlot`s — `list` (fetch) and `create` (singular write) —
|
|
9
|
+
* plus one `KeyedAsyncSlot<Uuid>` (`remove`) for the per-row delete with
|
|
10
|
+
* correct per-row supersession and per-row error surfacing. Method names
|
|
11
|
+
* use the `submit_*` prefix to avoid slot-name collisions (`delete` is
|
|
12
|
+
* reserved at top-level positions; renamed for symmetry).
|
|
13
|
+
*
|
|
8
14
|
* @module
|
|
9
15
|
*/
|
|
10
|
-
import { SvelteSet } from 'svelte/reactivity';
|
|
11
16
|
import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
|
|
12
|
-
import {
|
|
17
|
+
import { AsyncSlot } from './async_slot.svelte.js';
|
|
18
|
+
import { KeyedAsyncSlot } from './keyed_async_slot.svelte.js';
|
|
13
19
|
/**
|
|
14
20
|
* Svelte context carrying the reactive `AdminInvitesRpc` accessor. Mirrors
|
|
15
21
|
* `admin_accounts_rpc_context`. Unset context falls back to `() => null`.
|
|
16
22
|
*/
|
|
17
23
|
export const admin_invites_rpc_context = create_context(() => () => null);
|
|
18
|
-
export class AdminInvitesState
|
|
24
|
+
export class AdminInvitesState {
|
|
19
25
|
#get_rpc;
|
|
26
|
+
list = new AsyncSlot();
|
|
27
|
+
create = new AsyncSlot();
|
|
28
|
+
remove = new KeyedAsyncSlot();
|
|
20
29
|
invites = $state.raw([]);
|
|
21
|
-
creating = $state.raw(false);
|
|
22
|
-
deleting_ids = new SvelteSet();
|
|
23
30
|
invite_count = $derived(this.invites.length);
|
|
24
31
|
unclaimed_count = $derived(this.invites.filter((i) => !i.claimed_at).length);
|
|
25
32
|
constructor(options) {
|
|
26
|
-
super();
|
|
27
33
|
this.#get_rpc = options?.get_rpc ?? (() => null);
|
|
28
34
|
}
|
|
29
35
|
/** True when an RPC adapter is wired. All ops require it. */
|
|
30
36
|
get has_rpc() {
|
|
31
37
|
return this.#get_rpc() !== null;
|
|
32
38
|
}
|
|
33
|
-
|
|
39
|
+
#require_rpc() {
|
|
34
40
|
const rpc = this.#get_rpc();
|
|
35
|
-
if (!rpc)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
if (!rpc)
|
|
42
|
+
throw new Error('rpc adapter not wired');
|
|
43
|
+
return rpc;
|
|
44
|
+
}
|
|
45
|
+
async fetch() {
|
|
46
|
+
await this.list.run(async () => {
|
|
47
|
+
const { invites } = await this.#require_rpc().list();
|
|
41
48
|
this.invites = invites;
|
|
42
49
|
});
|
|
43
50
|
}
|
|
44
|
-
async
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
this.creating = true;
|
|
51
|
-
this.error = null;
|
|
52
|
-
try {
|
|
53
|
-
await rpc.create({ email: email ?? null, username: username ?? null });
|
|
54
|
-
await this.fetch();
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
catch (e) {
|
|
58
|
-
this.error = e instanceof Error ? e.message : 'Failed to create invite';
|
|
51
|
+
async submit_create(email, username) {
|
|
52
|
+
await this.create.run(async () => {
|
|
53
|
+
await this.#require_rpc().create({ email: email ?? null, username: username ?? null });
|
|
54
|
+
});
|
|
55
|
+
if (!this.create.succeeded)
|
|
59
56
|
return false;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
this.creating = false;
|
|
63
|
-
}
|
|
57
|
+
await this.fetch();
|
|
58
|
+
return true;
|
|
64
59
|
}
|
|
65
|
-
async
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
this.deleting_ids.add(id);
|
|
72
|
-
try {
|
|
73
|
-
await rpc.delete({ invite_id: id });
|
|
60
|
+
async submit_delete(id) {
|
|
61
|
+
await this.remove.run(id, async () => {
|
|
62
|
+
await this.#require_rpc().delete({ invite_id: id });
|
|
63
|
+
});
|
|
64
|
+
if (this.remove.succeeded(id))
|
|
74
65
|
await this.fetch();
|
|
75
|
-
}
|
|
76
|
-
catch (e) {
|
|
77
|
-
this.error = e instanceof Error ? e.message : 'Failed to delete invite';
|
|
78
|
-
}
|
|
79
|
-
finally {
|
|
80
|
-
this.deleting_ids.delete(id);
|
|
81
|
-
}
|
|
82
66
|
}
|
|
83
67
|
}
|
|
@@ -6,18 +6,24 @@
|
|
|
6
6
|
* `token_revoke_all`); the listing wraps the `admin_session_list` RPC
|
|
7
7
|
* method.
|
|
8
8
|
*
|
|
9
|
+
* Holds one fetch `AsyncSlot` (`list`) plus two `KeyedAsyncSlot`s keyed by
|
|
10
|
+
* `account_id` — `revoke_sessions` and `revoke_tokens`. Per-account
|
|
11
|
+
* concurrent revokes are independent (clicking row B does not abort row A)
|
|
12
|
+
* and per-row errors surface via `revoke_sessions.error(account_id)` /
|
|
13
|
+
* `revoke_tokens.error(account_id)`.
|
|
14
|
+
*
|
|
9
15
|
* @module
|
|
10
16
|
*/
|
|
11
|
-
import { SvelteSet } from 'svelte/reactivity';
|
|
12
17
|
import type { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
13
|
-
import {
|
|
18
|
+
import { AsyncSlot } from './async_slot.svelte.js';
|
|
19
|
+
import { KeyedAsyncSlot } from './keyed_async_slot.svelte.js';
|
|
14
20
|
import type { AdminAccountsRpc } from './admin_accounts_state.svelte.js';
|
|
15
21
|
import type { AdminSessionJson } from '../auth/audit_log_schema.js';
|
|
16
22
|
/**
|
|
17
23
|
* Options for `AdminSessionsState`.
|
|
18
24
|
*
|
|
19
25
|
* The RPC adapter drives every operation (listing + the two revoke-all
|
|
20
|
-
* mutations). Without it, `
|
|
26
|
+
* mutations). Without it, the slots' `run()` calls fail with
|
|
21
27
|
* `'rpc adapter not wired'` on `error`.
|
|
22
28
|
*/
|
|
23
29
|
export interface AdminSessionsStateOptions {
|
|
@@ -29,17 +35,18 @@ export interface AdminSessionsStateOptions {
|
|
|
29
35
|
*/
|
|
30
36
|
get_rpc?: () => AdminAccountsRpc | null;
|
|
31
37
|
}
|
|
32
|
-
export declare class AdminSessionsState
|
|
38
|
+
export declare class AdminSessionsState {
|
|
33
39
|
#private;
|
|
40
|
+
readonly list: AsyncSlot<void, string>;
|
|
41
|
+
readonly revoke_sessions: KeyedAsyncSlot<string & import("zod").$brand<"Uuid">, void, string>;
|
|
42
|
+
readonly revoke_tokens: KeyedAsyncSlot<string & import("zod").$brand<"Uuid">, void, string>;
|
|
34
43
|
sessions: Array<AdminSessionJson>;
|
|
35
|
-
readonly revoking_account_ids: SvelteSet<string>;
|
|
36
|
-
readonly revoking_token_account_ids: SvelteSet<string>;
|
|
37
44
|
readonly active_count: number;
|
|
38
45
|
constructor(options?: AdminSessionsStateOptions);
|
|
39
46
|
/** True when an RPC adapter is wired. `fetch` and the revoke controls no-op without it. */
|
|
40
47
|
get has_rpc(): boolean;
|
|
41
48
|
fetch(): Promise<void>;
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
submit_revoke_sessions(account_id: Uuid): Promise<void>;
|
|
50
|
+
submit_revoke_tokens(account_id: Uuid): Promise<void>;
|
|
44
51
|
}
|
|
45
52
|
//# sourceMappingURL=admin_sessions_state.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_sessions_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_sessions_state.svelte.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"admin_sessions_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_sessions_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAC,SAAS,EAAC,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAC5D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,6BAA6B,CAAC;AAElE;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACzC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,gBAAgB,GAAG,IAAI,CAAC;CACxC;AAED,qBAAa,kBAAkB;;IAG9B,QAAQ,CAAC,IAAI,0BAAyB;IACtC,QAAQ,CAAC,eAAe,sEAAoC;IAC5D,QAAQ,CAAC,aAAa,sEAAoC;IAE1D,QAAQ,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAkB;IAEnD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAkC;gBAEnD,OAAO,CAAC,EAAE,yBAAyB;IAI/C,2FAA2F;IAC3F,IAAI,OAAO,IAAI,OAAO,CAErB;IAQK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,sBAAsB,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvD,oBAAoB,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAM3D"}
|
|
@@ -6,71 +6,54 @@
|
|
|
6
6
|
* `token_revoke_all`); the listing wraps the `admin_session_list` RPC
|
|
7
7
|
* method.
|
|
8
8
|
*
|
|
9
|
+
* Holds one fetch `AsyncSlot` (`list`) plus two `KeyedAsyncSlot`s keyed by
|
|
10
|
+
* `account_id` — `revoke_sessions` and `revoke_tokens`. Per-account
|
|
11
|
+
* concurrent revokes are independent (clicking row B does not abort row A)
|
|
12
|
+
* and per-row errors surface via `revoke_sessions.error(account_id)` /
|
|
13
|
+
* `revoke_tokens.error(account_id)`.
|
|
14
|
+
*
|
|
9
15
|
* @module
|
|
10
16
|
*/
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
export class AdminSessionsState
|
|
17
|
+
import { AsyncSlot } from './async_slot.svelte.js';
|
|
18
|
+
import { KeyedAsyncSlot } from './keyed_async_slot.svelte.js';
|
|
19
|
+
export class AdminSessionsState {
|
|
14
20
|
#get_rpc;
|
|
21
|
+
list = new AsyncSlot();
|
|
22
|
+
revoke_sessions = new KeyedAsyncSlot();
|
|
23
|
+
revoke_tokens = new KeyedAsyncSlot();
|
|
15
24
|
sessions = $state.raw([]);
|
|
16
|
-
revoking_account_ids = new SvelteSet();
|
|
17
|
-
revoking_token_account_ids = new SvelteSet();
|
|
18
25
|
active_count = $derived(this.sessions.length);
|
|
19
26
|
constructor(options) {
|
|
20
|
-
super();
|
|
21
27
|
this.#get_rpc = options?.get_rpc ?? (() => null);
|
|
22
28
|
}
|
|
23
29
|
/** True when an RPC adapter is wired. `fetch` and the revoke controls no-op without it. */
|
|
24
30
|
get has_rpc() {
|
|
25
31
|
return this.#get_rpc() !== null;
|
|
26
32
|
}
|
|
27
|
-
|
|
33
|
+
#require_rpc() {
|
|
28
34
|
const rpc = this.#get_rpc();
|
|
29
|
-
if (!rpc)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
if (!rpc)
|
|
36
|
+
throw new Error('rpc adapter not wired');
|
|
37
|
+
return rpc;
|
|
38
|
+
}
|
|
39
|
+
async fetch() {
|
|
40
|
+
await this.list.run(async () => {
|
|
41
|
+
const { sessions } = await this.#require_rpc().list_sessions();
|
|
35
42
|
this.sessions = sessions;
|
|
36
43
|
});
|
|
37
44
|
}
|
|
38
|
-
async
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
this.revoking_account_ids.add(account_id);
|
|
45
|
-
try {
|
|
46
|
-
await rpc.session_revoke_all({ account_id });
|
|
47
|
-
this.error = null;
|
|
45
|
+
async submit_revoke_sessions(account_id) {
|
|
46
|
+
await this.revoke_sessions.run(account_id, async () => {
|
|
47
|
+
await this.#require_rpc().session_revoke_all({ account_id });
|
|
48
|
+
});
|
|
49
|
+
if (this.revoke_sessions.succeeded(account_id))
|
|
48
50
|
await this.fetch();
|
|
49
|
-
}
|
|
50
|
-
catch (e) {
|
|
51
|
-
this.error = e instanceof Error ? e.message : 'Failed to revoke sessions';
|
|
52
|
-
}
|
|
53
|
-
finally {
|
|
54
|
-
this.revoking_account_ids.delete(account_id);
|
|
55
|
-
}
|
|
56
51
|
}
|
|
57
|
-
async
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
this.revoking_token_account_ids.add(account_id);
|
|
64
|
-
try {
|
|
65
|
-
await rpc.token_revoke_all({ account_id });
|
|
66
|
-
this.error = null;
|
|
52
|
+
async submit_revoke_tokens(account_id) {
|
|
53
|
+
await this.revoke_tokens.run(account_id, async () => {
|
|
54
|
+
await this.#require_rpc().token_revoke_all({ account_id });
|
|
55
|
+
});
|
|
56
|
+
if (this.revoke_tokens.succeeded(account_id))
|
|
67
57
|
await this.fetch();
|
|
68
|
-
}
|
|
69
|
-
catch (e) {
|
|
70
|
-
this.error = e instanceof Error ? e.message : 'Failed to revoke tokens';
|
|
71
|
-
}
|
|
72
|
-
finally {
|
|
73
|
-
this.revoking_token_account_ids.delete(account_id);
|
|
74
|
-
}
|
|
75
58
|
}
|
|
76
59
|
}
|
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
* `AdminInvitesRpc` / `AuditLogRpc`. Tests can inject plain-function stubs
|
|
6
6
|
* and consumers adapt their typed RPC client to the same shape.
|
|
7
7
|
*
|
|
8
|
+
* Holds two `AsyncSlot`s — `list` (the initial fetch) and `update` (the
|
|
9
|
+
* `app_settings_update` write). Slots track status/error; the canonical
|
|
10
|
+
* `settings` lives on the class so consumers don't unwrap `slot.data`.
|
|
11
|
+
*
|
|
8
12
|
* @module
|
|
9
13
|
*/
|
|
10
|
-
import {
|
|
14
|
+
import { AsyncSlot } from './async_slot.svelte.js';
|
|
11
15
|
import type { AppSettingsWithUsernameJson } from '../auth/app_settings_schema.js';
|
|
12
16
|
import type { AppSettingsGetOutput, AppSettingsUpdateInput, AppSettingsUpdateOutput } from '../auth/admin_action_specs.js';
|
|
13
17
|
/**
|
|
@@ -35,10 +39,11 @@ export interface AppSettingsStateOptions {
|
|
|
35
39
|
*/
|
|
36
40
|
get_rpc?: () => AppSettingsRpc | null;
|
|
37
41
|
}
|
|
38
|
-
export declare class AppSettingsState
|
|
42
|
+
export declare class AppSettingsState {
|
|
39
43
|
#private;
|
|
44
|
+
readonly list: AsyncSlot<void, string>;
|
|
45
|
+
readonly update: AsyncSlot<void, string>;
|
|
40
46
|
settings: AppSettingsWithUsernameJson | null;
|
|
41
|
-
updating: boolean;
|
|
42
47
|
constructor(options?: AppSettingsStateOptions);
|
|
43
48
|
/** True when an RPC adapter is wired. All ops require it. */
|
|
44
49
|
get has_rpc(): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_settings_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/app_settings_state.svelte.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"app_settings_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/app_settings_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,EAAC,SAAS,EAAC,MAAM,wBAAwB,CAAC;AACjD,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,gCAAgC,CAAC;AAChF,OAAO,KAAK,EACX,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,MAAM,+BAA+B,CAAC;AAEvC;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,GAAG,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACzC,MAAM,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC7E;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;qBAAwB,cAAc,GAAG,IAAI;yBAArB,cAAc,GAAG,IAAI,wBAArB,cAAc,GAAG,IAAI;CAEjF,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACvC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,cAAc,GAAG,IAAI,CAAC;CACtC;AAED,qBAAa,gBAAgB;;IAG5B,QAAQ,CAAC,IAAI,0BAAyB;IACtC,QAAQ,CAAC,MAAM,0BAAyB;IAExC,QAAQ,EAAE,2BAA2B,GAAG,IAAI,CAAoB;gBAEpD,OAAO,CAAC,EAAE,uBAAuB;IAI7C,6DAA6D;IAC7D,IAAI,OAAO,IAAI,OAAO,CAErB;IAQK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAMvD"}
|
|
@@ -5,56 +5,48 @@
|
|
|
5
5
|
* `AdminInvitesRpc` / `AuditLogRpc`. Tests can inject plain-function stubs
|
|
6
6
|
* and consumers adapt their typed RPC client to the same shape.
|
|
7
7
|
*
|
|
8
|
+
* Holds two `AsyncSlot`s — `list` (the initial fetch) and `update` (the
|
|
9
|
+
* `app_settings_update` write). Slots track status/error; the canonical
|
|
10
|
+
* `settings` lives on the class so consumers don't unwrap `slot.data`.
|
|
11
|
+
*
|
|
8
12
|
* @module
|
|
9
13
|
*/
|
|
10
14
|
import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
|
|
11
|
-
import {
|
|
15
|
+
import { AsyncSlot } from './async_slot.svelte.js';
|
|
12
16
|
/**
|
|
13
17
|
* Svelte context carrying the reactive `AppSettingsRpc` accessor. Mirrors
|
|
14
18
|
* `admin_accounts_rpc_context`. Unset context falls back to `() => null` so
|
|
15
19
|
* `OpenSignupToggle` mounted outside a provisioner hides gracefully.
|
|
16
20
|
*/
|
|
17
21
|
export const app_settings_rpc_context = create_context(() => () => null);
|
|
18
|
-
export class AppSettingsState
|
|
22
|
+
export class AppSettingsState {
|
|
19
23
|
#get_rpc;
|
|
24
|
+
list = new AsyncSlot();
|
|
25
|
+
update = new AsyncSlot();
|
|
20
26
|
settings = $state.raw(null);
|
|
21
|
-
updating = $state.raw(false);
|
|
22
27
|
constructor(options) {
|
|
23
|
-
super();
|
|
24
28
|
this.#get_rpc = options?.get_rpc ?? (() => null);
|
|
25
29
|
}
|
|
26
30
|
/** True when an RPC adapter is wired. All ops require it. */
|
|
27
31
|
get has_rpc() {
|
|
28
32
|
return this.#get_rpc() !== null;
|
|
29
33
|
}
|
|
30
|
-
|
|
34
|
+
#require_rpc() {
|
|
31
35
|
const rpc = this.#get_rpc();
|
|
32
|
-
if (!rpc)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
if (!rpc)
|
|
37
|
+
throw new Error('rpc adapter not wired');
|
|
38
|
+
return rpc;
|
|
39
|
+
}
|
|
40
|
+
async fetch() {
|
|
41
|
+
await this.list.run(async () => {
|
|
42
|
+
const { settings } = await this.#require_rpc().get();
|
|
38
43
|
this.settings = settings;
|
|
39
44
|
});
|
|
40
45
|
}
|
|
41
46
|
async update_open_signup(value) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.error = 'rpc adapter not wired';
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
this.updating = true;
|
|
48
|
-
this.error = null;
|
|
49
|
-
try {
|
|
50
|
-
const { settings } = await rpc.update({ open_signup: value });
|
|
47
|
+
await this.update.run(async () => {
|
|
48
|
+
const { settings } = await this.#require_rpc().update({ open_signup: value });
|
|
51
49
|
this.settings = settings;
|
|
52
|
-
}
|
|
53
|
-
catch (e) {
|
|
54
|
-
this.error = e instanceof Error ? e.message : 'Failed to update settings';
|
|
55
|
-
}
|
|
56
|
-
finally {
|
|
57
|
-
this.updating = false;
|
|
58
|
-
}
|
|
50
|
+
});
|
|
59
51
|
}
|
|
60
52
|
}
|