@package-broker/ui 2.0.0-rc.1 → 2.0.0-rc.3
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/.tsbuildinfo +1 -1
- package/dist/api-types.d.ts +50 -5
- package/dist/api-types.d.ts.map +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -0
- package/dist/api.js.map +1 -1
- package/dist/app.d.ts +8 -14
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +22 -20
- package/dist/app.js.map +1 -1
- package/dist/assets/access.6859f43998ca.js +1 -0
- package/dist/assets/app.e8434f3343de.js +9 -0
- package/dist/assets/app.f15f8f21f88b.css +2 -0
- package/dist/assets/audit.576009342714.js +1 -0
- package/dist/assets/chunk.297a2d571f9c.js +4 -0
- package/dist/assets/chunk.3704953b4a66.js +1 -0
- package/dist/assets/chunk.6e23a75a1324.js +1 -0
- package/dist/assets/chunk.7cb6f6b4daaa.js +1 -0
- package/dist/assets/chunk.855a50f45830.js +1 -0
- package/dist/assets/chunk.862544cf5835.js +2 -0
- package/dist/assets/chunk.976a2ff4338e.js +1 -0
- package/dist/assets/chunk.bb25fc845269.js +1 -0
- package/dist/assets/chunk.bf48b04e6a00.js +1 -0
- package/dist/assets/chunk.ce96495b8b55.js +1 -0
- package/dist/assets/chunk.f922af39a279.js +1 -0
- package/dist/assets/import.e2a1e35fcb8d.js +1 -0
- package/dist/assets/index.html +6 -2
- package/dist/assets/manifest.json +80 -8
- package/dist/assets/packages.a8726e5144de.js +1 -0
- package/dist/assets/projects.711deb86b7c9.js +1 -0
- package/dist/assets/setup.dfed30ecfbad.js +1 -0
- package/dist/assets/setup.html +3 -3
- package/dist/assets/sources.d0ba0048bd96.js +1 -0
- package/dist/assets/tokens.f48d235e8d25.js +4 -0
- package/dist/auth.d.ts +4 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +4 -0
- package/dist/auth.js.map +1 -1
- package/dist/components/product-footer-content.d.ts +21 -0
- package/dist/components/product-footer-content.d.ts.map +1 -0
- package/dist/components/product-footer-content.js +30 -0
- package/dist/components/product-footer-content.js.map +1 -0
- package/dist/components/product-footer.d.ts +3 -0
- package/dist/components/product-footer.d.ts.map +1 -0
- package/dist/components/product-footer.js +6 -0
- package/dist/components/product-footer.js.map +1 -0
- package/dist/manifest.d.ts +32 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +41 -0
- package/dist/manifest.js.map +1 -1
- package/dist/page-loader.d.ts +5 -0
- package/dist/page-loader.d.ts.map +1 -0
- package/dist/page-loader.js +39 -0
- package/dist/page-loader.js.map +1 -0
- package/dist/pages/access/grant-form.d.ts +48 -0
- package/dist/pages/access/grant-form.d.ts.map +1 -0
- package/dist/pages/access/grant-form.js +153 -0
- package/dist/pages/access/grant-form.js.map +1 -0
- package/dist/pages/access/permission-descriptions.d.ts +2 -0
- package/dist/pages/access/permission-descriptions.d.ts.map +1 -0
- package/dist/pages/access/permission-descriptions.js +36 -0
- package/dist/pages/access/permission-descriptions.js.map +1 -0
- package/dist/pages/access/role-catalogue.d.ts +22 -0
- package/dist/pages/access/role-catalogue.d.ts.map +1 -0
- package/dist/pages/access/role-catalogue.js +40 -0
- package/dist/pages/access/role-catalogue.js.map +1 -0
- package/dist/pages/access.d.ts +6 -0
- package/dist/pages/access.d.ts.map +1 -1
- package/dist/pages/access.js +66 -31
- package/dist/pages/access.js.map +1 -1
- package/dist/pages/packages/source-actions.d.ts +48 -0
- package/dist/pages/packages/source-actions.d.ts.map +1 -0
- package/dist/pages/packages/source-actions.js +312 -0
- package/dist/pages/packages/source-actions.js.map +1 -0
- package/dist/pages/packages.d.ts.map +1 -1
- package/dist/pages/packages.js +3 -2
- package/dist/pages/packages.js.map +1 -1
- package/dist/pages/projects/project-form-model.d.ts +25 -0
- package/dist/pages/projects/project-form-model.d.ts.map +1 -0
- package/dist/pages/projects/project-form-model.js +69 -0
- package/dist/pages/projects/project-form-model.js.map +1 -0
- package/dist/pages/projects/project-form.d.ts +14 -0
- package/dist/pages/projects/project-form.d.ts.map +1 -0
- package/dist/pages/projects/project-form.js +91 -0
- package/dist/pages/projects/project-form.js.map +1 -0
- package/dist/pages/projects.d.ts +3 -2
- package/dist/pages/projects.d.ts.map +1 -1
- package/dist/pages/projects.js +28 -37
- package/dist/pages/projects.js.map +1 -1
- package/dist/pages/sources/source-form.d.ts +3 -1
- package/dist/pages/sources/source-form.d.ts.map +1 -1
- package/dist/pages/sources/source-form.js +18 -16
- package/dist/pages/sources/source-form.js.map +1 -1
- package/dist/pages/sources/sync-copy.d.ts +5 -0
- package/dist/pages/sources/sync-copy.d.ts.map +1 -0
- package/dist/pages/sources/sync-copy.js +11 -0
- package/dist/pages/sources/sync-copy.js.map +1 -0
- package/dist/pages/sources.d.ts +2 -0
- package/dist/pages/sources.d.ts.map +1 -1
- package/dist/pages/sources.js +54 -15
- package/dist/pages/sources.js.map +1 -1
- package/dist/pages/tokens/client-config.d.ts +3 -0
- package/dist/pages/tokens/client-config.d.ts.map +1 -1
- package/dist/pages/tokens/client-config.js +12 -2
- package/dist/pages/tokens/client-config.js.map +1 -1
- package/dist/pages/tokens/issue-token-form.d.ts +27 -0
- package/dist/pages/tokens/issue-token-form.d.ts.map +1 -0
- package/dist/pages/tokens/issue-token-form.js +270 -0
- package/dist/pages/tokens/issue-token-form.js.map +1 -0
- package/dist/pages/tokens/principal-form.d.ts +8 -0
- package/dist/pages/tokens/principal-form.d.ts.map +1 -0
- package/dist/pages/tokens/principal-form.js +151 -0
- package/dist/pages/tokens/principal-form.js.map +1 -0
- package/dist/pages/tokens.d.ts +47 -6
- package/dist/pages/tokens.d.ts.map +1 -1
- package/dist/pages/tokens.js +317 -86
- package/dist/pages/tokens.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +4 -1
- package/dist/setup.js.map +1 -1
- package/dist/token-pending.d.ts +4 -0
- package/dist/token-pending.d.ts.map +1 -0
- package/dist/token-pending.js +12 -0
- package/dist/token-pending.js.map +1 -0
- package/package.json +3 -3
- package/dist/assets/app.0348e5888f99.js +0 -15
- package/dist/assets/app.e8cea8b32d3a.css +0 -2
- package/dist/assets/setup.98343039898f.js +0 -1
package/dist/pages/tokens.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { pendingStorage } from '../token-pending.js';
|
|
2
3
|
import { useEffect, useMemo, useState, useSyncExternalStore } from 'react';
|
|
3
4
|
import { useServices } from '../auth.js';
|
|
4
5
|
import { loadPending, savePending } from '../pending.js';
|
|
@@ -15,7 +16,10 @@ import { Badge } from '../components/ui/badge.js';
|
|
|
15
16
|
import { LoadedListFilters } from '../components/ui/toolbar.js';
|
|
16
17
|
import { ListState } from '../components/ui/list-state.js';
|
|
17
18
|
import { ConfirmAction } from '../components/ui/confirm-action.js';
|
|
19
|
+
import { IssueTokenForm, accessLabels } from './tokens/issue-token-form.js';
|
|
20
|
+
import { PrincipalForm } from './tokens/principal-form.js';
|
|
18
21
|
import { tokenClientConfig } from './tokens/client-config.js';
|
|
22
|
+
export const dayLabel = (days) => `${days} ${days === 1 ? 'day' : 'days'}`;
|
|
19
23
|
export function lifetimeChoices(policy) {
|
|
20
24
|
return [...new Set([30, 90, 180, 365, policy.maxDays, policy.defaultDays])]
|
|
21
25
|
.filter((days) => days > 0 && days <= policy.maxDays)
|
|
@@ -27,6 +31,7 @@ export function tokensReducer(state, action) {
|
|
|
27
31
|
...state,
|
|
28
32
|
secret: null,
|
|
29
33
|
secretProject: null,
|
|
34
|
+
result: null,
|
|
30
35
|
issuedId: null,
|
|
31
36
|
oldPersonalId: null,
|
|
32
37
|
pending: null,
|
|
@@ -36,16 +41,6 @@ export function tokensReducer(state, action) {
|
|
|
36
41
|
};
|
|
37
42
|
return state;
|
|
38
43
|
}
|
|
39
|
-
function pendingStorage(storage) {
|
|
40
|
-
return {
|
|
41
|
-
getItem: (key) => storage.getItem(`tokens:${key}`),
|
|
42
|
-
setItem: (key, value) => storage.setItem(`tokens:${key}`, value),
|
|
43
|
-
removeItem: (key) => storage.removeItem(`tokens:${key}`),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
export function clearPendingTokenIssue(storage) {
|
|
47
|
-
loadPending(pendingStorage(storage), Date.now());
|
|
48
|
-
}
|
|
49
44
|
function record(value) {
|
|
50
45
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
51
46
|
}
|
|
@@ -72,6 +67,13 @@ function restoredIssue(value) {
|
|
|
72
67
|
target: value.target,
|
|
73
68
|
project: value.project,
|
|
74
69
|
key: value.key,
|
|
70
|
+
...(value.refresh === true ? { refresh: true } : {}),
|
|
71
|
+
...(['personal', 'project', 'organization'].includes(String(value.listTarget))
|
|
72
|
+
? {
|
|
73
|
+
listTarget: value.listTarget,
|
|
74
|
+
listProject: typeof value.listProject === 'string' ? value.listProject : null,
|
|
75
|
+
}
|
|
76
|
+
: {}),
|
|
75
77
|
...(typeof value.rotate === 'string' ? { rotate: value.rotate } : {}),
|
|
76
78
|
...(typeof value.replace === 'string' ? { replace: value.replace } : {}),
|
|
77
79
|
draft: {
|
|
@@ -105,17 +107,48 @@ function publicToken(t) {
|
|
|
105
107
|
revokedAt: t.revokedAt,
|
|
106
108
|
};
|
|
107
109
|
}
|
|
110
|
+
export function inResultScope(result, project) {
|
|
111
|
+
if (!result)
|
|
112
|
+
return true;
|
|
113
|
+
if (result.target === 'project')
|
|
114
|
+
return project.slug === result.project;
|
|
115
|
+
if (result.target === 'personal')
|
|
116
|
+
return result.draft.projectMode !== 'selected' || result.draft.projectIds.includes(project.id);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
export function hasSetupAccess(state, project) {
|
|
120
|
+
const result = state.result;
|
|
121
|
+
if (!result)
|
|
122
|
+
return false;
|
|
123
|
+
const permissions = result.target === 'personal'
|
|
124
|
+
? project.permissions
|
|
125
|
+
: state.principals.find((p) => p.id === result.draft.servicePrincipalId)?.permissions;
|
|
126
|
+
return (permissions?.includes('project:packages:read') === true ||
|
|
127
|
+
(result.draft.scope !== 'read' &&
|
|
128
|
+
project.cap !== 'read' &&
|
|
129
|
+
permissions?.includes('project:packages:write') === true));
|
|
130
|
+
}
|
|
108
131
|
export function createTokensController(services, deps) {
|
|
109
132
|
let state = {
|
|
110
133
|
target: 'personal',
|
|
111
134
|
project: null,
|
|
135
|
+
issuanceProject: null,
|
|
112
136
|
items: [],
|
|
113
137
|
next: null,
|
|
114
138
|
principals: [],
|
|
115
139
|
principalsNext: null,
|
|
116
140
|
principalsRestricted: false,
|
|
141
|
+
principalsLoading: true,
|
|
142
|
+
principalsError: false,
|
|
143
|
+
organizationPermissions: [],
|
|
144
|
+
issuanceDraft: null,
|
|
145
|
+
issuancePrincipal: null,
|
|
146
|
+
issuanceNonce: 0,
|
|
117
147
|
names: {},
|
|
118
148
|
projectSlugs: {},
|
|
149
|
+
projects: [],
|
|
150
|
+
projectsError: false,
|
|
151
|
+
result: null,
|
|
119
152
|
lifetime: null,
|
|
120
153
|
userId: null,
|
|
121
154
|
secret: null,
|
|
@@ -132,6 +165,8 @@ export function createTokensController(services, deps) {
|
|
|
132
165
|
let epoch = 0;
|
|
133
166
|
let listing = 0;
|
|
134
167
|
let navigation = 0;
|
|
168
|
+
const policyQueue = [];
|
|
169
|
+
let policyWorkers = 0;
|
|
135
170
|
const listeners = new Set();
|
|
136
171
|
const set = (next) => {
|
|
137
172
|
state = { ...state, ...next };
|
|
@@ -157,6 +192,7 @@ export function createTokensController(services, deps) {
|
|
|
157
192
|
busy: true,
|
|
158
193
|
secret: null,
|
|
159
194
|
secretProject: null,
|
|
195
|
+
result: null,
|
|
160
196
|
issuedId: null,
|
|
161
197
|
oldPersonalId: null,
|
|
162
198
|
pending: null,
|
|
@@ -203,10 +239,15 @@ export function createTokensController(services, deps) {
|
|
|
203
239
|
if (result.ok) {
|
|
204
240
|
set({
|
|
205
241
|
secret: result.data.token,
|
|
206
|
-
secretProject: target === 'project'
|
|
242
|
+
secretProject: target === 'project'
|
|
243
|
+
? project
|
|
244
|
+
: d.projectMode === 'selected' && d.projectIds.length === 1
|
|
245
|
+
? (state.projectSlugs[d.projectIds[0]] ?? null)
|
|
246
|
+
: null,
|
|
247
|
+
result: action,
|
|
207
248
|
oldPersonalId: action.replace ?? null,
|
|
208
249
|
issuedId: result.data.id,
|
|
209
|
-
notice: '
|
|
250
|
+
notice: 'Token created.',
|
|
210
251
|
});
|
|
211
252
|
}
|
|
212
253
|
else if (result.code === 'reauth_required') {
|
|
@@ -234,6 +275,9 @@ export function createTokensController(services, deps) {
|
|
|
234
275
|
}
|
|
235
276
|
const controller = {
|
|
236
277
|
snapshot: () => state,
|
|
278
|
+
announce(notice) {
|
|
279
|
+
set({ notice });
|
|
280
|
+
},
|
|
237
281
|
subscribe(listener) {
|
|
238
282
|
listeners.add(listener);
|
|
239
283
|
return () => {
|
|
@@ -244,7 +288,12 @@ export function createTokensController(services, deps) {
|
|
|
244
288
|
epoch++;
|
|
245
289
|
navigation++;
|
|
246
290
|
listing++;
|
|
247
|
-
|
|
291
|
+
policyQueue.length = 0;
|
|
292
|
+
set({
|
|
293
|
+
...tokensReducer(state, { type: 'navigate' }),
|
|
294
|
+
principalsLoading: false,
|
|
295
|
+
projects: state.projects.map((p) => p.policyState === 'loading' ? { ...p, policyState: 'idle' } : p),
|
|
296
|
+
});
|
|
248
297
|
},
|
|
249
298
|
dispose() {
|
|
250
299
|
controller.navigate();
|
|
@@ -254,24 +303,33 @@ export function createTokensController(services, deps) {
|
|
|
254
303
|
if (state.busy)
|
|
255
304
|
return;
|
|
256
305
|
epoch++;
|
|
257
|
-
set({
|
|
306
|
+
set({
|
|
307
|
+
secret: null,
|
|
308
|
+
secretProject: null,
|
|
309
|
+
result: null,
|
|
310
|
+
issuedId: null,
|
|
311
|
+
oldPersonalId: null,
|
|
312
|
+
notice: null,
|
|
313
|
+
renewal: null,
|
|
314
|
+
issuanceDraft: state.pending === null ? null : state.issuanceDraft,
|
|
315
|
+
});
|
|
258
316
|
},
|
|
259
317
|
restore() {
|
|
260
318
|
const loaded = loadPending(storage, deps.now());
|
|
261
319
|
const action = loaded.ok ? restoredIssue(loaded.action?.body) : null;
|
|
262
320
|
if (action !== null)
|
|
263
|
-
set({
|
|
321
|
+
set({ pending: action, issuanceDraft: action.refresh ? action : null, secret: null });
|
|
264
322
|
},
|
|
265
323
|
async start(target, project) {
|
|
324
|
+
set({ issuanceProject: project });
|
|
266
325
|
controller.restore();
|
|
267
326
|
const action = state.pending;
|
|
268
|
-
await controller.load(action?.target ?? target, action
|
|
327
|
+
await controller.load(action?.listTarget ?? action?.target ?? target, action ? (action.listTarget ? (action.listProject ?? null) : action.project) : project);
|
|
269
328
|
},
|
|
270
329
|
async selectTarget(target, project) {
|
|
271
330
|
if (state.busy)
|
|
272
331
|
return;
|
|
273
|
-
|
|
274
|
-
set(tokensReducer(state, { type: 'navigate' }));
|
|
332
|
+
set({ pending: null, renewal: null });
|
|
275
333
|
await controller.load(target, project);
|
|
276
334
|
},
|
|
277
335
|
decline() {
|
|
@@ -282,11 +340,40 @@ export function createTokensController(services, deps) {
|
|
|
282
340
|
if (action === null || state.busy || state.loading)
|
|
283
341
|
return;
|
|
284
342
|
if (state.lifetime === null) {
|
|
285
|
-
await controller.load(
|
|
343
|
+
await controller.load();
|
|
286
344
|
if (state.pending !== action || state.lifetime === null || state.loading)
|
|
287
345
|
return;
|
|
288
346
|
}
|
|
289
347
|
await send(action);
|
|
348
|
+
if (action.refresh && state.secret !== null)
|
|
349
|
+
await controller.load();
|
|
350
|
+
},
|
|
351
|
+
async issueFor(target, project, draft) {
|
|
352
|
+
const action = {
|
|
353
|
+
target,
|
|
354
|
+
project,
|
|
355
|
+
draft,
|
|
356
|
+
key: deps.newKey(),
|
|
357
|
+
refresh: true,
|
|
358
|
+
listTarget: state.target,
|
|
359
|
+
listProject: state.project,
|
|
360
|
+
};
|
|
361
|
+
set({ issuanceDraft: action });
|
|
362
|
+
await send(action);
|
|
363
|
+
if (state.secret !== null)
|
|
364
|
+
await controller.load();
|
|
365
|
+
},
|
|
366
|
+
configureProject(slug) {
|
|
367
|
+
const result = state.result;
|
|
368
|
+
const project = state.projects.find((p) => p.slug === slug);
|
|
369
|
+
if (!project ||
|
|
370
|
+
!result ||
|
|
371
|
+
(result.target === 'project' && result.project !== slug) ||
|
|
372
|
+
(result.target === 'personal' &&
|
|
373
|
+
result.draft.projectMode === 'selected' &&
|
|
374
|
+
!result.draft.projectIds.includes(project.id)))
|
|
375
|
+
return;
|
|
376
|
+
set({ secretProject: slug });
|
|
290
377
|
},
|
|
291
378
|
async issue(draft) {
|
|
292
379
|
await send({
|
|
@@ -316,6 +403,9 @@ export function createTokensController(services, deps) {
|
|
|
316
403
|
}
|
|
317
404
|
set({ renewal: token, secret: null, issuedId: null, oldPersonalId: null, notice: null });
|
|
318
405
|
},
|
|
406
|
+
invalidProjects() {
|
|
407
|
+
set({ notice: 'Choose between 1 and 200 projects.' });
|
|
408
|
+
},
|
|
319
409
|
registryOrigin: () => new URL(deps.origin).origin,
|
|
320
410
|
clientConfig() {
|
|
321
411
|
return state.secret === null || state.secretProject === null
|
|
@@ -338,7 +428,9 @@ export function createTokensController(services, deps) {
|
|
|
338
428
|
catch {
|
|
339
429
|
if (version === epoch)
|
|
340
430
|
set({
|
|
341
|
-
notice: kind === undefined
|
|
431
|
+
notice: kind === undefined
|
|
432
|
+
? 'Could not copy. Select and copy the token manually.'
|
|
433
|
+
: 'Could not copy. Select and copy the configuration manually.',
|
|
342
434
|
});
|
|
343
435
|
}
|
|
344
436
|
},
|
|
@@ -349,7 +441,7 @@ export function createTokensController(services, deps) {
|
|
|
349
441
|
project,
|
|
350
442
|
loading: true,
|
|
351
443
|
listError: false,
|
|
352
|
-
notice: null,
|
|
444
|
+
notice: state.secret === null ? null : state.notice,
|
|
353
445
|
items: [],
|
|
354
446
|
next: null,
|
|
355
447
|
lifetime: null,
|
|
@@ -375,36 +467,106 @@ export function createTokensController(services, deps) {
|
|
|
375
467
|
},
|
|
376
468
|
async loadPrincipals(after) {
|
|
377
469
|
const version = navigation;
|
|
378
|
-
|
|
379
|
-
|
|
470
|
+
set({ principalsLoading: true, principalsError: false });
|
|
471
|
+
const principals = [];
|
|
472
|
+
let cursor = after;
|
|
473
|
+
do {
|
|
474
|
+
const result = await services.client.request('get', '/_pb/service-principals', {
|
|
475
|
+
query: { after: cursor },
|
|
476
|
+
});
|
|
477
|
+
if (version !== navigation)
|
|
478
|
+
return;
|
|
479
|
+
if (!result.ok) {
|
|
480
|
+
set({
|
|
481
|
+
principalsLoading: false,
|
|
482
|
+
principalsError: result.status !== 403,
|
|
483
|
+
principalsRestricted: result.status === 403,
|
|
484
|
+
principals: [],
|
|
485
|
+
principalsNext: null,
|
|
486
|
+
});
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
principals.push(...result.data.items.map((p) => ({
|
|
490
|
+
id: p.id,
|
|
491
|
+
name: p.name,
|
|
492
|
+
projectId: p.projectId,
|
|
493
|
+
status: p.status,
|
|
494
|
+
revision: p.revision,
|
|
495
|
+
createdByUserId: p.createdByUserId,
|
|
496
|
+
permissions: p.permissions,
|
|
497
|
+
})));
|
|
498
|
+
cursor = result.data.next ?? undefined;
|
|
499
|
+
} while (cursor !== undefined);
|
|
500
|
+
set({
|
|
501
|
+
principals,
|
|
502
|
+
principalsNext: null,
|
|
503
|
+
principalsRestricted: false,
|
|
504
|
+
principalsLoading: false,
|
|
380
505
|
});
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
506
|
+
},
|
|
507
|
+
loadProjectPolicies(ids, retry = false) {
|
|
508
|
+
const marked = new Set();
|
|
509
|
+
for (const id of ids) {
|
|
510
|
+
const project = state.projects.find((p) => p.id === id);
|
|
511
|
+
if (!project ||
|
|
512
|
+
project.policyState === 'loading' ||
|
|
513
|
+
project.policyState === 'loaded' ||
|
|
514
|
+
(!retry && project.policyState === 'error'))
|
|
515
|
+
continue;
|
|
516
|
+
marked.add(id);
|
|
517
|
+
policyQueue.push(id);
|
|
518
|
+
}
|
|
519
|
+
if (marked.size)
|
|
384
520
|
set({
|
|
385
|
-
|
|
386
|
-
id: p.id,
|
|
387
|
-
name: p.name,
|
|
388
|
-
projectId: p.projectId,
|
|
389
|
-
status: p.status,
|
|
390
|
-
revision: p.revision,
|
|
391
|
-
createdByUserId: p.createdByUserId,
|
|
392
|
-
permissions: p.permissions,
|
|
393
|
-
})),
|
|
394
|
-
principalsNext: result.data.next,
|
|
395
|
-
principalsRestricted: false,
|
|
521
|
+
projects: state.projects.map((p) => marked.has(p.id) ? { ...p, policyState: 'loading' } : p),
|
|
396
522
|
});
|
|
397
|
-
|
|
398
|
-
|
|
523
|
+
async function work() {
|
|
524
|
+
policyWorkers++;
|
|
525
|
+
try {
|
|
526
|
+
while (policyQueue.length) {
|
|
527
|
+
const id = policyQueue.shift();
|
|
528
|
+
const project = state.projects.find((p) => p.id === id);
|
|
529
|
+
if (!project)
|
|
530
|
+
continue;
|
|
531
|
+
const version = navigation;
|
|
532
|
+
const detail = await services.client.request('get', '/_pb/projects/{project}', {
|
|
533
|
+
params: { project: project.slug },
|
|
534
|
+
});
|
|
535
|
+
const authority = await services.client.request('get', '/_pb/projects/{project}/permissions', { params: { project: project.slug } });
|
|
536
|
+
if (version !== navigation)
|
|
537
|
+
continue;
|
|
538
|
+
set({
|
|
539
|
+
projects: state.projects.map((p) => p.id === id
|
|
540
|
+
? {
|
|
541
|
+
...p,
|
|
542
|
+
cap: detail.ok ? detail.data.tokenMaxScope : null,
|
|
543
|
+
permissions: authority.ok ? authority.data.project : null,
|
|
544
|
+
policyState: detail.ok && authority.ok ? 'loaded' : 'error',
|
|
545
|
+
}
|
|
546
|
+
: p),
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
finally {
|
|
551
|
+
policyWorkers--;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
while (policyQueue.length && policyWorkers < 4)
|
|
555
|
+
void work();
|
|
556
|
+
},
|
|
557
|
+
choosePrincipal(id, project, organization = project === null) {
|
|
558
|
+
set({
|
|
559
|
+
issuancePrincipal: id,
|
|
560
|
+
issuanceProject: organization ? '__organization' : (project ?? ''),
|
|
561
|
+
issuanceNonce: state.issuanceNonce + 1,
|
|
562
|
+
});
|
|
399
563
|
},
|
|
400
564
|
async identity() {
|
|
401
565
|
const version = navigation;
|
|
402
|
-
const
|
|
403
|
-
if (version !== navigation)
|
|
404
|
-
return;
|
|
405
|
-
if (result.ok)
|
|
406
|
-
set({ userId: result.data.user.id });
|
|
566
|
+
const session = services.client.request('get', '/_pb/session');
|
|
407
567
|
const projectSlugs = {};
|
|
568
|
+
const catalogue = [];
|
|
569
|
+
let projectsError = false;
|
|
408
570
|
let projectAfter;
|
|
409
571
|
do {
|
|
410
572
|
const projects = await services.client.request('get', '/_pb/projects', {
|
|
@@ -412,16 +574,36 @@ export function createTokensController(services, deps) {
|
|
|
412
574
|
});
|
|
413
575
|
if (version !== navigation)
|
|
414
576
|
return;
|
|
415
|
-
if (!projects.ok)
|
|
577
|
+
if (!projects.ok) {
|
|
578
|
+
projectsError = true;
|
|
416
579
|
break;
|
|
417
|
-
|
|
580
|
+
}
|
|
581
|
+
for (const project of projects.data.items) {
|
|
418
582
|
projectSlugs[project.id] = project.slug;
|
|
583
|
+
if (project.status === 'archived')
|
|
584
|
+
continue;
|
|
585
|
+
catalogue.push({
|
|
586
|
+
id: project.id,
|
|
587
|
+
slug: project.slug,
|
|
588
|
+
name: project.name ?? project.slug,
|
|
589
|
+
cap: null,
|
|
590
|
+
permissions: null,
|
|
591
|
+
policyState: 'idle',
|
|
592
|
+
});
|
|
593
|
+
}
|
|
419
594
|
projectAfter = projects.data.next ?? undefined;
|
|
595
|
+
set({ projectSlugs: { ...projectSlugs }, projects: [...catalogue], projectsError });
|
|
420
596
|
} while (projectAfter !== undefined);
|
|
421
|
-
set({ projectSlugs });
|
|
597
|
+
set({ projectSlugs, projects: catalogue, projectsError });
|
|
598
|
+
const result = await session;
|
|
599
|
+
if (version !== navigation)
|
|
600
|
+
return;
|
|
601
|
+
if (result.ok)
|
|
602
|
+
set({ userId: result.data.user.id });
|
|
422
603
|
const permissions = await services.client.request('get', '/_pb/me/permissions');
|
|
423
604
|
if (version !== navigation)
|
|
424
605
|
return;
|
|
606
|
+
set({ organizationPermissions: permissions.ok ? permissions.data.organization : [] });
|
|
425
607
|
if (!permissions.ok || !permissions.data.organization.includes('org:users:read')) {
|
|
426
608
|
set({ names: {} });
|
|
427
609
|
return;
|
|
@@ -489,7 +671,8 @@ export function createTokensController(services, deps) {
|
|
|
489
671
|
failure(result);
|
|
490
672
|
else {
|
|
491
673
|
await controller.loadPrincipals();
|
|
492
|
-
set({ notice: 'Service principal created.' });
|
|
674
|
+
set({ notice: 'Service principal created. Create a token to use it.' });
|
|
675
|
+
return result.data;
|
|
493
676
|
}
|
|
494
677
|
},
|
|
495
678
|
async suspendPrincipal(principal) {
|
|
@@ -518,7 +701,9 @@ export function createTokensController(services, deps) {
|
|
|
518
701
|
}
|
|
519
702
|
export function filterTokens(items, filters) {
|
|
520
703
|
return items.filter((item) => item.name.toLowerCase().includes(filters.q.toLowerCase()) &&
|
|
521
|
-
(!filters.scope ||
|
|
704
|
+
(!filters.scope ||
|
|
705
|
+
item.scope === filters.scope ||
|
|
706
|
+
accessLabels[item.scope] === filters.scope) &&
|
|
522
707
|
(!filters.status || item.status === filters.status));
|
|
523
708
|
}
|
|
524
709
|
export function TokenList({ state: s, controller: c }) {
|
|
@@ -532,7 +717,7 @@ export function TokenList({ state: s, controller: c }) {
|
|
|
532
717
|
: 'Organization';
|
|
533
718
|
return (_jsxs(_Fragment, { children: [_jsx(LoadedListFilters, { values: filters, fields: [
|
|
534
719
|
{ key: 'q', label: 'Name (this page)' },
|
|
535
|
-
{ key: 'scope', label: 'Scope', options:
|
|
720
|
+
{ key: 'scope', label: 'Scope', options: Object.values(accessLabels) },
|
|
536
721
|
{ key: 'status', label: 'Status', options: ['active', 'expired', 'revoked'] },
|
|
537
722
|
], onChange: setFilters }), _jsxs("div", { className: "my-3 flex flex-wrap items-center justify-between gap-3", children: [_jsxs("p", { className: "text-sm text-neutral-600", children: [items.length, " tokens on this page \u00B7", ' ', items.filter((token) => token.status === 'active').length, " active"] }), _jsx(Button, { type: "button", variant: "outline", disabled: s.loading || s.busy, onClick: () => void c.load(), children: "Reload tokens" })] }), s.loading ? (_jsx(ListState, { kind: "loading", title: "Loading tokens\u2026" })) : s.listError ? (_jsx(ListState, { kind: "error", title: "Unable to load tokens." })) : items.length === 0 ? (_jsx(ListState, { kind: filtered ? 'filtered-empty' : 'empty', title: filtered ? 'No tokens match these filters on this page.' : 'No tokens on this page.' })) : (_jsx(DataTable, { label: "Tokens", items: items, columns: [
|
|
538
723
|
{
|
|
@@ -551,7 +736,7 @@ export function TokenList({ state: s, controller: c }) {
|
|
|
551
736
|
key: 'scope',
|
|
552
737
|
label: 'Scope',
|
|
553
738
|
value: (token) => token.scope,
|
|
554
|
-
render: (token) => _jsx(Badge, { variant: "outline", children: token.scope }),
|
|
739
|
+
render: (token) => _jsx(Badge, { variant: "outline", children: accessLabels[token.scope] }),
|
|
555
740
|
},
|
|
556
741
|
{
|
|
557
742
|
key: 'project',
|
|
@@ -599,8 +784,25 @@ export function TokenList({ state: s, controller: c }) {
|
|
|
599
784
|
},
|
|
600
785
|
] })), s.next === null ? null : (_jsx(Button, { type: "button", disabled: s.busy || s.loading, onClick: () => void c.load(s.target, s.project, s.next), children: "Next tokens" })), _jsx("p", { className: "mt-3 text-xs text-neutral-600", children: "Secrets are never listed. Only tokens in your accessible scope appear here." })] }));
|
|
601
786
|
}
|
|
787
|
+
function focusResult(node) {
|
|
788
|
+
node?.focus();
|
|
789
|
+
}
|
|
790
|
+
function SetupProjectAccess({ state: s, controller: c }) {
|
|
791
|
+
const [setupCandidate, setSetupCandidate] = useState('');
|
|
792
|
+
const candidate = s.projects.find((p) => p.id === setupCandidate);
|
|
793
|
+
useEffect(() => {
|
|
794
|
+
if (s.secret && candidate?.policyState === 'loaded' && hasSetupAccess(s, candidate))
|
|
795
|
+
c.configureProject(candidate.slug);
|
|
796
|
+
}, [c, s.secret, candidate]);
|
|
797
|
+
return (_jsxs(_Fragment, { children: [' ', s.projects.some((p) => p.permissions === null) && (_jsxs("details", { children: [_jsx("summary", { className: "min-h-11 cursor-pointer", children: "Check another project's package access" }), _jsx(Label, { htmlFor: "setup-candidate", children: "Project to configure" }), _jsxs(Select, { id: "setup-candidate", className: "min-h-11", value: setupCandidate, onChange: (event) => {
|
|
798
|
+
setSetupCandidate(event.target.value);
|
|
799
|
+
c.loadProjectPolicies([event.target.value]);
|
|
800
|
+
}, children: [_jsx("option", { value: "", children: "Choose a project" }), s.projects
|
|
801
|
+
.filter((p) => inResultScope(s.result, p))
|
|
802
|
+
.map((p) => (_jsx("option", { value: p.id, children: p.name }, p.id)))] }), candidate?.policyState === 'loading' && _jsx("p", { children: "Loading project access\u2026" }), candidate?.policyState === 'error' && (_jsx(Button, { type: "button", onClick: () => c.loadProjectPolicies([candidate.id], true), children: "Try again loading project access" })), candidate?.policyState === 'loaded' && !hasSetupAccess(s, candidate) && (_jsx("p", { children: "This token does not have package access to this project." }))] }))] }));
|
|
803
|
+
}
|
|
602
804
|
export function TokensView({ state: s, controller: c }) {
|
|
603
|
-
const personal = s.renewal
|
|
805
|
+
const personal = s.renewal !== null ? s.renewal.kind === 'pat' : s.target === 'personal';
|
|
604
806
|
const policy = s.lifetime?.[personal ? 'personal' : 'service'];
|
|
605
807
|
const renewal = s.renewal;
|
|
606
808
|
const clientConfig = c.clientConfig();
|
|
@@ -609,26 +811,20 @@ export function TokensView({ state: s, controller: c }) {
|
|
|
609
811
|
: s.target === 'project'
|
|
610
812
|
? Object.keys(s.projectSlugs).find((id) => s.projectSlugs[id] === s.project)
|
|
611
813
|
: null;
|
|
612
|
-
const principalChoices = s.
|
|
613
|
-
? [
|
|
614
|
-
...new Map(s.items
|
|
615
|
-
.filter((t) => t.kind === 'svc' && t.status === 'active' && t.servicePrincipalId !== null)
|
|
616
|
-
.map((t) => [
|
|
617
|
-
t.servicePrincipalId,
|
|
618
|
-
{
|
|
619
|
-
id: t.servicePrincipalId,
|
|
620
|
-
name: t.servicePrincipalId,
|
|
621
|
-
projectId: t.projectId,
|
|
622
|
-
},
|
|
623
|
-
])).values(),
|
|
624
|
-
]
|
|
625
|
-
: s.principals.filter((p) => p.status === 'active');
|
|
814
|
+
const principalChoices = s.principals.filter((p) => p.status === 'active');
|
|
626
815
|
function issue(event) {
|
|
627
816
|
event.preventDefault();
|
|
628
817
|
if (s.busy)
|
|
629
818
|
return;
|
|
630
819
|
const form = event.currentTarget;
|
|
631
820
|
const data = new FormData(form);
|
|
821
|
+
const selectedIds = data.getAll('projectIds').map(String);
|
|
822
|
+
if (personal &&
|
|
823
|
+
data.get('projectMode') === 'selected' &&
|
|
824
|
+
(selectedIds.length < 1 || selectedIds.length > 200)) {
|
|
825
|
+
c.invalidProjects();
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
632
828
|
const scope = renewal?.kind === 'svc' ? renewal.scope : String(data.get('scope'));
|
|
633
829
|
void c.issue({
|
|
634
830
|
name: String(data.get('name')),
|
|
@@ -636,28 +832,49 @@ export function TokensView({ state: s, controller: c }) {
|
|
|
636
832
|
lifetimeDays: Number(data.get('lifetimeDays')),
|
|
637
833
|
servicePrincipalId: String(data.get('principal') ?? ''),
|
|
638
834
|
projectMode: data.get('projectMode') === 'selected' ? 'selected' : 'all',
|
|
639
|
-
projectIds:
|
|
640
|
-
.split(/\s+/)
|
|
641
|
-
.filter(Boolean),
|
|
835
|
+
projectIds: data.getAll('projectIds').map(String),
|
|
642
836
|
overlapDays: Number(data.get('overlap') ?? 0),
|
|
643
837
|
});
|
|
644
|
-
form.reset();
|
|
645
838
|
}
|
|
646
|
-
return (_jsxs("section", { "aria-labelledby": "page-heading", className: "space-y-4", children: [_jsx("h1", { tabIndex: -1, id: "page-heading", className: "text-xl font-semibold", children: "Tokens" }), _jsx("p", { className: "text-neutral-600", children: "
|
|
647
|
-
|
|
648
|
-
|
|
839
|
+
return (_jsxs("section", { "aria-labelledby": "page-heading", className: "space-y-4", children: [_jsx("h1", { tabIndex: -1, id: "page-heading", className: "text-xl font-semibold", children: "Tokens" }), _jsx("p", { className: "text-neutral-600", children: "Connect your tools and CI pipelines to your packages." }), s.notice === null || s.listError ? null : (_jsx(Alert, { role: "status", "aria-live": "polite", children: _jsx(AlertDescription, { children: s.notice }) })), s.pending === null ? null : (_jsxs("section", { "aria-label": "Confirm token issue", className: "space-y-2", children: [_jsx("h2", { children: "Confirm token issue" }), _jsx("p", { children: "Your identity was checked. Issue this token now?" }), _jsx(Button, { type: "button", disabled: s.busy || s.loading || s.lifetime === null, onClick: () => void c.confirm(), children: "Confirm token issue" }), _jsx(Button, { type: "button", variant: "outline", onClick: () => c.decline(), children: "Cancel" })] })), s.secret === null ? null : (_jsxs("section", { "aria-label": "Token secret", className: "detail-group space-y-3 border-emerald-200", children: [_jsx("h2", { tabIndex: -1, ref: focusResult, children: "Token created" }), s.result && (_jsxs("p", { className: "break-words", children: [s.result.draft.name, " \u00B7", ' ', s.result.target === 'project'
|
|
840
|
+
? (s.projects.find((p) => p.slug === s.result?.project)?.name ??
|
|
841
|
+
'Project name unavailable')
|
|
842
|
+
: s.result.target === 'organization'
|
|
843
|
+
? 'Organization-wide'
|
|
844
|
+
: s.result.draft.projectMode === 'all'
|
|
845
|
+
? 'All accessible projects'
|
|
846
|
+
: s.result.draft.projectIds.length === 1
|
|
847
|
+
? (s.projects.find((p) => p.id === s.result?.draft.projectIds[0])?.name ??
|
|
848
|
+
'1 selected project')
|
|
849
|
+
: `${s.result.draft.projectIds.length} selected projects`, ' ', "\u00B7 ", accessLabels[s.result.draft.scope], " \u00B7 Expires after", ' ', dayLabel(s.result.draft.lifetimeDays)] })), _jsx("p", { children: "Copy this token now. You cannot view it again. Closing this panel keeps it active." }), _jsx("pre", { className: "rounded-md border bg-neutral-50 p-3 overflow-x-auto whitespace-pre-wrap break-all", children: _jsx("code", { role: "region", "aria-label": "Created token", children: s.secret }) }), _jsx(Button, { className: "min-h-11", type: "button", onClick: () => void c.copy(), children: "Copy token" }), _jsx(Button, { type: "button", variant: "outline", className: "min-h-11", onClick: () => {
|
|
850
|
+
c.closeSecret();
|
|
851
|
+
requestAnimationFrame(() => document.getElementById('new-token')?.focus());
|
|
852
|
+
}, children: "Done" }), _jsx(SetupProjectAccess, { state: s, controller: c }), _jsx(Label, { htmlFor: "configure-project", children: "Configure for project" }), _jsxs(Select, { className: "min-h-11", id: "configure-project", value: s.secretProject ?? '', onChange: (event) => c.configureProject(event.target.value), children: [_jsx("option", { value: "", children: "Choose a project" }), s.projects
|
|
853
|
+
.filter((p) => hasSetupAccess(s, p))
|
|
854
|
+
.filter((p) => inResultScope(s.result, p))
|
|
855
|
+
.map((p) => (_jsx("option", { value: p.slug, children: p.name }, p.id)))] }), clientConfig === null ? (_jsx("p", { children: "Choose a project this token can access to configure Composer or npm." })) : (_jsxs(_Fragment, { children: [_jsx("p", { children: "Composer sends this credential to this registry host. The token still limits which projects it can access." }), [
|
|
856
|
+
['Composer', ['composer', 'composerAuth']],
|
|
857
|
+
['npm', ['npm']],
|
|
858
|
+
['CI environment', ['ciComposer', 'ciNpm']],
|
|
859
|
+
].map(([title, kinds], index) => (_jsxs("details", { open: index === 0, className: "min-w-0", children: [_jsx("summary", { className: "min-h-11 cursor-pointer", children: title }), kinds.map((kind) => (_jsxs("div", { children: [_jsx("p", { children: {
|
|
860
|
+
composer: 'Composer repository command',
|
|
861
|
+
composerAuth: 'Merge this into your local auth.json. Do not commit credentials.',
|
|
862
|
+
npm: 'Use this on your computer. For CI, use the environment-variable version below.',
|
|
863
|
+
ciComposer: 'Save PACKAGE_BROKER_TOKEN as a secret in your CI settings. These examples install packages; they do not publish them.',
|
|
864
|
+
ciNpm: 'Save this as .npmrc, then run npm ci.',
|
|
865
|
+
}[kind] }), _jsx("pre", { className: "whitespace-pre-wrap break-all rounded-md border bg-neutral-50 p-3", children: _jsx("code", { children: clientConfig[kind] }) }), _jsx(Button, { type: "button", variant: "outline", className: "min-h-11", onClick: () => void c.copy(kind), children: {
|
|
866
|
+
composer: 'Copy Composer configuration',
|
|
867
|
+
composerAuth: 'Copy auth.json',
|
|
868
|
+
npm: 'Copy npm configuration',
|
|
869
|
+
ciComposer: 'Copy CI Composer',
|
|
870
|
+
ciNpm: 'Copy CI npm',
|
|
871
|
+
}[kind] })] }, kind)))] }, title)))] })), s.oldPersonalId !== null ? (_jsx(ConfirmAction, { action: "Revoke old personal token", target: s.oldPersonalId, scope: "My personal tokens", description: "Clients using the old token will lose access immediately. This cannot be undone. Update clients to the new token before revoking.", disabled: s.busy, onConfirm: () => void c.revoke(s.oldPersonalId, true) })) : null] })), _jsx("div", { hidden: s.pending !== null || s.secret !== null || renewal !== null, children: _jsx(IssueTokenForm, { state: s, controller: c }) }), s.pending !== null || s.secret !== null || renewal === null ? null : (_jsxs("details", { open: renewal !== null, className: "detail-group", children: [_jsx("summary", { className: "min-h-11 cursor-pointer font-semibold", children: renewal === null ? 'Issue token' : 'Renew token' }), _jsxs("form", { onSubmit: issue, className: "grid gap-2", children: [_jsx(Label, { htmlFor: "token-name", children: "Name" }), _jsx(Input, { id: "token-name", name: "name", defaultValue: renewal?.name ?? '', required: true }), _jsx(Label, { htmlFor: "token-scope", children: "Scope" }), _jsxs(Select, { id: "token-scope", name: "scope", defaultValue: renewal?.scope ?? 'read', disabled: renewal?.kind === 'svc', children: [_jsx("option", { value: "read", children: "Install and download" }), _jsx("option", { value: "write", children: "Include publish permission" }), _jsx("option", { value: "full", children: "Include grants, sources and audit access" })] }), _jsx(Label, { htmlFor: "token-lifetime", children: "Lifetime" }), _jsx(Select, { id: "token-lifetime", name: "lifetimeDays", defaultValue: policy?.defaultDays, required: true, children: policy === undefined
|
|
649
872
|
? null
|
|
650
|
-
: lifetimeChoices(policy).map((days) => (
|
|
873
|
+
: lifetimeChoices(policy).map((days) => (_jsx("option", { value: days, children: dayLabel(days) }, days))) }), personal ? (_jsxs(_Fragment, { children: [_jsx(Label, { htmlFor: "token-project-mode", children: "Projects" }), _jsxs(Select, { id: "token-project-mode", name: "projectMode", defaultValue: renewal?.projectMode ?? 'all', children: [_jsx("option", { value: "all", children: "All accessible projects" }), _jsx("option", { value: "selected", children: "Selected projects" })] }), _jsxs("fieldset", { children: [_jsx("legend", { children: "Choose projects again when renewing a selected token" }), s.projects.map((p) => (_jsxs("label", { className: "flex min-h-11 items-center gap-2", children: [_jsx("input", { type: "checkbox", name: "projectIds", value: p.id }), p.name] }, p.id)))] })] })) : (_jsxs(_Fragment, { children: [_jsx(Label, { htmlFor: "token-principal", children: "Service principal" }), _jsxs(Select, { id: "token-principal", name: "principal", defaultValue: renewal?.servicePrincipalId ?? '', disabled: renewal?.kind === 'svc', required: renewal?.kind !== 'svc', children: [_jsx("option", { value: "", disabled: true, children: "Select a principal" }), principalChoices
|
|
651
874
|
.filter((p) => p.projectId === projectId)
|
|
652
|
-
.map((p) => (_jsx("option", { value: p.id, children: p.name }, p.id)))] }), s.principalsRestricted ? (_jsx("p", { children: "
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
return;
|
|
656
|
-
const form = event.currentTarget;
|
|
657
|
-
const data = new FormData(form);
|
|
658
|
-
void c.createPrincipal(String(data.get('name')), String(data.get('project')), String(data.get('permissions')).split(/\s+/).filter(Boolean));
|
|
659
|
-
form.reset();
|
|
660
|
-
}, children: [_jsx(Label, { htmlFor: "principal-name", children: "Principal name" }), _jsx(Input, { id: "principal-name", name: "name", required: true }), _jsx(Label, { htmlFor: "principal-project", children: "Project ID (empty for organization)" }), _jsx(Input, { id: "principal-project", name: "project" }), _jsx(Label, { htmlFor: "principal-permissions", children: "Permissions (space separated)" }), _jsx(Input, { id: "principal-permissions", name: "permissions" }), _jsx(Button, { type: "submit", disabled: s.busy, children: "Create service principal" })] }), _jsx(Button, { type: "button", variant: "outline", disabled: s.busy, onClick: () => void c.loadPrincipals(), children: "Reload service principals" }), s.principalsRestricted ? (_jsx(ListState, { kind: "placeholder", title: "Service principal discovery is restricted." })) : s.principals.length === 0 ? (_jsx(ListState, { kind: "empty", title: "No service principals loaded." })) : (_jsx(DataTable, { label: "Service principals", items: s.principals, columns: [
|
|
875
|
+
.map((p) => (_jsx("option", { value: p.id, children: p.name }, p.id)))] }), s.principalsRestricted ? (_jsx("p", { children: "Ask an organization administrator to create a service identity for this project." })) : null] })), renewal?.kind !== 'svc' ? null : (_jsxs(_Fragment, { children: [_jsx(Label, { htmlFor: "token-overlap", children: "Overlap" }), _jsxs(Select, { id: "token-overlap", name: "overlap", defaultValue: "0", children: [_jsx("option", { value: "0", children: "None" }), _jsx("option", { value: "1", children: "1 day" }), _jsx("option", { value: "7", children: "7 days" })] }), _jsx("p", { children: "The old token stops at the end of the overlap or when the new token expires, whichever is first." })] })), _jsx(Button, { type: "submit", disabled: s.busy || s.loading || policy === undefined, children: renewal === null ? 'Issue token' : 'Renew token' }), renewal === null ? null : (_jsx(Button, { type: "button", variant: "outline", disabled: s.busy, onClick: () => c.closeSecret(), children: "Cancel renewal" }))] }, `${s.target}:${renewal?.id ?? ''}:${policy?.defaultDays}`)] }, `${s.target}:${renewal?.id ?? ''}`)), _jsx(Label, { htmlFor: "token-list", children: "Token list" }), _jsxs(Select, { id: "token-list", value: s.target, disabled: s.busy, onChange: (e) => {
|
|
876
|
+
void c.selectTarget(e.target.value, s.project);
|
|
877
|
+
}, children: [_jsx("option", { value: "personal", children: "My personal tokens" }), s.project === null ? null : _jsx("option", { value: "project", children: "Project tokens" }), _jsx("option", { value: "organization", children: "Organization tokens" })] }), _jsxs("details", { className: "detail-group", children: [_jsx("summary", { children: "How token access works" }), _jsx("p", { children: "Personal tokens use your access and stop working if you leave or your account is suspended. Service tokens use a service identity and keep working when their creator leaves. Each project's access policy can limit a token further." })] }), _jsx(TokenList, { state: s, controller: c }, `${s.target}:${s.project}`), _jsxs("details", { className: "detail-group", children: [_jsx("summary", { className: "min-h-11 cursor-pointer font-semibold", children: "Service principals" }), _jsx(PrincipalForm, { state: s, controller: c }), _jsx(Button, { type: "button", variant: "outline", disabled: s.busy, onClick: () => void c.loadPrincipals(), children: "Reload service principals" }), s.principalsRestricted ? (_jsx(ListState, { kind: "placeholder", title: "You cannot browse service principals here." })) : s.principalsLoading ? (_jsx(ListState, { kind: "loading", title: "Loading service principals\u2026" })) : s.principals.length === 0 ? (_jsx(ListState, { kind: "empty", title: "No service principals loaded." })) : (_jsx(DataTable, { label: "Service principals", items: s.principals, columns: [
|
|
661
878
|
{
|
|
662
879
|
key: 'name',
|
|
663
880
|
label: 'Name',
|
|
@@ -687,13 +904,18 @@ export function bindTokensLifecycle(controller, events, onNavigate) {
|
|
|
687
904
|
onNavigate();
|
|
688
905
|
};
|
|
689
906
|
const clear = () => controller.navigate();
|
|
907
|
+
const show = (event) => {
|
|
908
|
+
clear();
|
|
909
|
+
if (event.persisted === true)
|
|
910
|
+
onNavigate();
|
|
911
|
+
};
|
|
690
912
|
events.addEventListener('hashchange', change);
|
|
691
913
|
events.addEventListener('pagehide', clear);
|
|
692
|
-
events.addEventListener('pageshow',
|
|
914
|
+
events.addEventListener('pageshow', show);
|
|
693
915
|
return () => {
|
|
694
916
|
events.removeEventListener('hashchange', change);
|
|
695
917
|
events.removeEventListener('pagehide', clear);
|
|
696
|
-
events.removeEventListener('pageshow',
|
|
918
|
+
events.removeEventListener('pageshow', show);
|
|
697
919
|
controller.dispose();
|
|
698
920
|
};
|
|
699
921
|
}
|
|
@@ -733,10 +955,19 @@ export function TokensPage() {
|
|
|
733
955
|
if (window.location.hash.startsWith('#/tokens'))
|
|
734
956
|
void controller.start(project === null ? 'personal' : 'project', project);
|
|
735
957
|
}
|
|
958
|
+
function reload() {
|
|
959
|
+
if (!window.location.hash.startsWith('#/tokens'))
|
|
960
|
+
return;
|
|
961
|
+
void controller.identity();
|
|
962
|
+
void controller.loadPrincipals();
|
|
963
|
+
}
|
|
736
964
|
change();
|
|
737
965
|
void controller.identity();
|
|
738
966
|
void controller.loadPrincipals();
|
|
739
|
-
return bindTokensLifecycle(controller, window,
|
|
967
|
+
return bindTokensLifecycle(controller, window, () => {
|
|
968
|
+
change();
|
|
969
|
+
reload();
|
|
970
|
+
});
|
|
740
971
|
}, [controller]);
|
|
741
972
|
useEffect(() => bindTokensReplay(controller, services), [controller, services]);
|
|
742
973
|
return _jsx(TokensView, { state: state, controller: controller });
|