@package-broker/ui 2.0.0-rc.2 → 2.0.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/api-types.d.ts +8148 -2725
- 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 +10 -0
- package/dist/api.js.map +1 -1
- package/dist/app-path.d.ts +2 -0
- package/dist/app-path.d.ts.map +1 -0
- package/dist/app-path.js +2 -0
- package/dist/app-path.js.map +1 -0
- package/dist/app.d.ts +10 -14
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +37 -22
- package/dist/app.js.map +1 -1
- package/dist/assets/access.62e9484517a0.js +1 -0
- package/dist/assets/app.45b156168495.css +2 -0
- package/dist/assets/app.c064670cdc0d.js +9 -0
- package/dist/assets/audit.b7c40b9dc998.js +1 -0
- package/dist/assets/chunk.28a4d8aab337.js +1 -0
- package/dist/assets/chunk.3fcd6998795a.js +1 -0
- package/dist/assets/chunk.51497edc7697.js +4 -0
- package/dist/assets/chunk.56d473a1587d.js +1 -0
- package/dist/assets/chunk.675b635d9462.js +1 -0
- package/dist/assets/chunk.6ea6bdcaa465.js +1 -0
- package/dist/assets/chunk.741aec8d1ff9.js +1 -0
- package/dist/assets/chunk.815f9a91c741.js +1 -0
- package/dist/assets/chunk.8442124f6802.js +1 -0
- package/dist/assets/chunk.965b76812604.js +2 -0
- package/dist/assets/chunk.a6d755a9789f.js +1 -0
- package/dist/assets/chunk.d18497920e4d.js +1 -0
- package/dist/assets/chunk.d813fefb410d.js +1 -0
- package/dist/assets/chunk.e3b0c44298fc.js +0 -0
- package/dist/assets/chunk.f54ea1378c8c.js +1 -0
- package/dist/assets/chunk.fd375c54b526.js +1 -0
- package/dist/assets/import.e7a1e5834631.js +1 -0
- package/dist/assets/index.html +9 -2
- package/dist/assets/manifest.json +108 -8
- package/dist/assets/notifications.19225fe12044.js +1 -0
- package/dist/assets/npm-login.e059678af13d.js +1 -0
- package/dist/assets/packages.ab5ab25b1c75.js +2 -0
- package/dist/assets/projects.927fe18c785c.js +1 -0
- package/dist/assets/setup.8e977608d413.js +1 -0
- package/dist/assets/setup.html +3 -3
- package/dist/assets/sources.ae50f239f80f.js +1 -0
- package/dist/assets/tokens.6b86d8d97e07.js +4 -0
- package/dist/auth.d.ts +26 -3
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +133 -19
- 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/components/ui/status-badge.d.ts +5 -0
- package/dist/components/ui/status-badge.d.ts.map +1 -1
- package/dist/components/ui/status-badge.js +10 -0
- package/dist/components/ui/status-badge.js.map +1 -1
- 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/package-limits.d.ts +3 -0
- package/dist/package-limits.d.ts.map +1 -0
- package/dist/package-limits.js +3 -0
- package/dist/package-limits.js.map +1 -0
- package/dist/page-hash.d.ts +1 -1
- package/dist/page-hash.d.ts.map +1 -1
- package/dist/page-hash.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/audit.d.ts.map +1 -1
- package/dist/pages/notification-actions.d.ts +41 -0
- package/dist/pages/notification-actions.d.ts.map +1 -0
- package/dist/pages/notification-actions.js +187 -0
- package/dist/pages/notification-actions.js.map +1 -0
- package/dist/pages/notification-draft.d.ts +13 -0
- package/dist/pages/notification-draft.d.ts.map +1 -0
- package/dist/pages/notification-draft.js +75 -0
- package/dist/pages/notification-draft.js.map +1 -0
- package/dist/pages/notification-form.d.ts +35 -0
- package/dist/pages/notification-form.d.ts.map +1 -0
- package/dist/pages/notification-form.js +167 -0
- package/dist/pages/notification-form.js.map +1 -0
- package/dist/pages/notifications-controller.d.ts +41 -0
- package/dist/pages/notifications-controller.d.ts.map +1 -0
- package/dist/pages/notifications-controller.js +184 -0
- package/dist/pages/notifications-controller.js.map +1 -0
- package/dist/pages/notifications.d.ts +10 -0
- package/dist/pages/notifications.d.ts.map +1 -0
- package/dist/pages/notifications.js +301 -0
- package/dist/pages/notifications.js.map +1 -0
- package/dist/pages/npm-login.d.ts +2 -0
- package/dist/pages/npm-login.d.ts.map +1 -0
- package/dist/pages/npm-login.js +62 -0
- package/dist/pages/npm-login.js.map +1 -0
- package/dist/pages/packages/npm-workflow.d.ts +31 -0
- package/dist/pages/packages/npm-workflow.d.ts.map +1 -0
- package/dist/pages/packages/npm-workflow.js +381 -0
- package/dist/pages/packages/npm-workflow.js.map +1 -0
- package/dist/pages/packages/package-detail.d.ts +9 -0
- package/dist/pages/packages/package-detail.d.ts.map +1 -0
- package/dist/pages/packages/package-detail.js +242 -0
- package/dist/pages/packages/package-detail.js.map +1 -0
- package/dist/pages/packages/source-actions.d.ts +49 -0
- package/dist/pages/packages/source-actions.d.ts.map +1 -0
- package/dist/pages/packages/source-actions.js +319 -0
- package/dist/pages/packages/source-actions.js.map +1 -0
- package/dist/pages/packages.d.ts +15 -4
- package/dist/pages/packages.d.ts.map +1 -1
- package/dist/pages/packages.js +138 -20
- 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 +29 -38
- package/dist/pages/projects.js.map +1 -1
- package/dist/pages/sources/source-form.d.ts +5 -2
- package/dist/pages/sources/source-form.d.ts.map +1 -1
- package/dist/pages/sources/source-form.js +39 -27
- 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 +11 -3
- package/dist/pages/sources.d.ts.map +1 -1
- package/dist/pages/sources.js +103 -25
- 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 +155 -0
- package/dist/pages/tokens/principal-form.js.map +1 -0
- package/dist/pages/tokens/trusted-publishers-controller.d.ts +42 -0
- package/dist/pages/tokens/trusted-publishers-controller.d.ts.map +1 -0
- package/dist/pages/tokens/trusted-publishers-controller.js +202 -0
- package/dist/pages/tokens/trusted-publishers-controller.js.map +1 -0
- package/dist/pages/tokens/trusted-publishers.d.ts +6 -0
- package/dist/pages/tokens/trusted-publishers.d.ts.map +1 -0
- package/dist/pages/tokens/trusted-publishers.js +116 -0
- package/dist/pages/tokens/trusted-publishers.js.map +1 -0
- package/dist/pages/tokens.d.ts +100 -6
- package/dist/pages/tokens.d.ts.map +1 -1
- package/dist/pages/tokens.js +324 -86
- package/dist/pages/tokens.js.map +1 -1
- package/dist/setup.d.ts +3 -2
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +70 -10
- 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.b2abf3c46f6e.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,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { createTrustedPublishersController } from './tokens/trusted-publishers-controller.js';
|
|
3
|
+
import { TrustedPublishers } from './tokens/trusted-publishers.js';
|
|
4
|
+
import { pendingStorage } from '../token-pending.js';
|
|
2
5
|
import { useEffect, useMemo, useState, useSyncExternalStore } from 'react';
|
|
3
6
|
import { useServices } from '../auth.js';
|
|
4
7
|
import { loadPending, savePending } from '../pending.js';
|
|
@@ -15,7 +18,10 @@ import { Badge } from '../components/ui/badge.js';
|
|
|
15
18
|
import { LoadedListFilters } from '../components/ui/toolbar.js';
|
|
16
19
|
import { ListState } from '../components/ui/list-state.js';
|
|
17
20
|
import { ConfirmAction } from '../components/ui/confirm-action.js';
|
|
21
|
+
import { IssueTokenForm, accessLabels } from './tokens/issue-token-form.js';
|
|
22
|
+
import { PrincipalForm } from './tokens/principal-form.js';
|
|
18
23
|
import { tokenClientConfig } from './tokens/client-config.js';
|
|
24
|
+
export const dayLabel = (days) => `${days} ${days === 1 ? 'day' : 'days'}`;
|
|
19
25
|
export function lifetimeChoices(policy) {
|
|
20
26
|
return [...new Set([30, 90, 180, 365, policy.maxDays, policy.defaultDays])]
|
|
21
27
|
.filter((days) => days > 0 && days <= policy.maxDays)
|
|
@@ -27,6 +33,7 @@ export function tokensReducer(state, action) {
|
|
|
27
33
|
...state,
|
|
28
34
|
secret: null,
|
|
29
35
|
secretProject: null,
|
|
36
|
+
result: null,
|
|
30
37
|
issuedId: null,
|
|
31
38
|
oldPersonalId: null,
|
|
32
39
|
pending: null,
|
|
@@ -36,16 +43,6 @@ export function tokensReducer(state, action) {
|
|
|
36
43
|
};
|
|
37
44
|
return state;
|
|
38
45
|
}
|
|
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
46
|
function record(value) {
|
|
50
47
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
51
48
|
}
|
|
@@ -72,6 +69,13 @@ function restoredIssue(value) {
|
|
|
72
69
|
target: value.target,
|
|
73
70
|
project: value.project,
|
|
74
71
|
key: value.key,
|
|
72
|
+
...(value.refresh === true ? { refresh: true } : {}),
|
|
73
|
+
...(['personal', 'project', 'organization'].includes(String(value.listTarget))
|
|
74
|
+
? {
|
|
75
|
+
listTarget: value.listTarget,
|
|
76
|
+
listProject: typeof value.listProject === 'string' ? value.listProject : null,
|
|
77
|
+
}
|
|
78
|
+
: {}),
|
|
75
79
|
...(typeof value.rotate === 'string' ? { rotate: value.rotate } : {}),
|
|
76
80
|
...(typeof value.replace === 'string' ? { replace: value.replace } : {}),
|
|
77
81
|
draft: {
|
|
@@ -105,17 +109,48 @@ function publicToken(t) {
|
|
|
105
109
|
revokedAt: t.revokedAt,
|
|
106
110
|
};
|
|
107
111
|
}
|
|
112
|
+
export function inResultScope(result, project) {
|
|
113
|
+
if (!result)
|
|
114
|
+
return true;
|
|
115
|
+
if (result.target === 'project')
|
|
116
|
+
return project.slug === result.project;
|
|
117
|
+
if (result.target === 'personal')
|
|
118
|
+
return result.draft.projectMode !== 'selected' || result.draft.projectIds.includes(project.id);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
export function hasSetupAccess(state, project) {
|
|
122
|
+
const result = state.result;
|
|
123
|
+
if (!result)
|
|
124
|
+
return false;
|
|
125
|
+
const permissions = result.target === 'personal'
|
|
126
|
+
? project.permissions
|
|
127
|
+
: state.principals.find((p) => p.id === result.draft.servicePrincipalId)?.permissions;
|
|
128
|
+
return (permissions?.includes('project:packages:read') === true ||
|
|
129
|
+
(result.draft.scope !== 'read' &&
|
|
130
|
+
project.cap !== 'read' &&
|
|
131
|
+
permissions?.includes('project:packages:write') === true));
|
|
132
|
+
}
|
|
108
133
|
export function createTokensController(services, deps) {
|
|
109
134
|
let state = {
|
|
110
135
|
target: 'personal',
|
|
111
136
|
project: null,
|
|
137
|
+
issuanceProject: null,
|
|
112
138
|
items: [],
|
|
113
139
|
next: null,
|
|
114
140
|
principals: [],
|
|
115
141
|
principalsNext: null,
|
|
116
142
|
principalsRestricted: false,
|
|
143
|
+
principalsLoading: true,
|
|
144
|
+
principalsError: false,
|
|
145
|
+
organizationPermissions: [],
|
|
146
|
+
issuanceDraft: null,
|
|
147
|
+
issuancePrincipal: null,
|
|
148
|
+
issuanceNonce: 0,
|
|
117
149
|
names: {},
|
|
118
150
|
projectSlugs: {},
|
|
151
|
+
projects: [],
|
|
152
|
+
projectsError: false,
|
|
153
|
+
result: null,
|
|
119
154
|
lifetime: null,
|
|
120
155
|
userId: null,
|
|
121
156
|
secret: null,
|
|
@@ -132,6 +167,8 @@ export function createTokensController(services, deps) {
|
|
|
132
167
|
let epoch = 0;
|
|
133
168
|
let listing = 0;
|
|
134
169
|
let navigation = 0;
|
|
170
|
+
const policyQueue = [];
|
|
171
|
+
let policyWorkers = 0;
|
|
135
172
|
const listeners = new Set();
|
|
136
173
|
const set = (next) => {
|
|
137
174
|
state = { ...state, ...next };
|
|
@@ -157,6 +194,7 @@ export function createTokensController(services, deps) {
|
|
|
157
194
|
busy: true,
|
|
158
195
|
secret: null,
|
|
159
196
|
secretProject: null,
|
|
197
|
+
result: null,
|
|
160
198
|
issuedId: null,
|
|
161
199
|
oldPersonalId: null,
|
|
162
200
|
pending: null,
|
|
@@ -203,10 +241,15 @@ export function createTokensController(services, deps) {
|
|
|
203
241
|
if (result.ok) {
|
|
204
242
|
set({
|
|
205
243
|
secret: result.data.token,
|
|
206
|
-
secretProject: target === 'project'
|
|
244
|
+
secretProject: target === 'project'
|
|
245
|
+
? project
|
|
246
|
+
: d.projectMode === 'selected' && d.projectIds.length === 1
|
|
247
|
+
? (state.projectSlugs[d.projectIds[0]] ?? null)
|
|
248
|
+
: null,
|
|
249
|
+
result: action,
|
|
207
250
|
oldPersonalId: action.replace ?? null,
|
|
208
251
|
issuedId: result.data.id,
|
|
209
|
-
notice: '
|
|
252
|
+
notice: 'Token created.',
|
|
210
253
|
});
|
|
211
254
|
}
|
|
212
255
|
else if (result.code === 'reauth_required') {
|
|
@@ -233,7 +276,11 @@ export function createTokensController(services, deps) {
|
|
|
233
276
|
failure(result);
|
|
234
277
|
}
|
|
235
278
|
const controller = {
|
|
279
|
+
trustedPublishers: createTrustedPublishersController(services),
|
|
236
280
|
snapshot: () => state,
|
|
281
|
+
announce(notice) {
|
|
282
|
+
set({ notice });
|
|
283
|
+
},
|
|
237
284
|
subscribe(listener) {
|
|
238
285
|
listeners.add(listener);
|
|
239
286
|
return () => {
|
|
@@ -241,10 +288,16 @@ export function createTokensController(services, deps) {
|
|
|
241
288
|
};
|
|
242
289
|
},
|
|
243
290
|
navigate() {
|
|
291
|
+
controller.trustedPublishers.clear();
|
|
244
292
|
epoch++;
|
|
245
293
|
navigation++;
|
|
246
294
|
listing++;
|
|
247
|
-
|
|
295
|
+
policyQueue.length = 0;
|
|
296
|
+
set({
|
|
297
|
+
...tokensReducer(state, { type: 'navigate' }),
|
|
298
|
+
principalsLoading: false,
|
|
299
|
+
projects: state.projects.map((p) => p.policyState === 'loading' ? { ...p, policyState: 'idle' } : p),
|
|
300
|
+
});
|
|
248
301
|
},
|
|
249
302
|
dispose() {
|
|
250
303
|
controller.navigate();
|
|
@@ -254,24 +307,33 @@ export function createTokensController(services, deps) {
|
|
|
254
307
|
if (state.busy)
|
|
255
308
|
return;
|
|
256
309
|
epoch++;
|
|
257
|
-
set({
|
|
310
|
+
set({
|
|
311
|
+
secret: null,
|
|
312
|
+
secretProject: null,
|
|
313
|
+
result: null,
|
|
314
|
+
issuedId: null,
|
|
315
|
+
oldPersonalId: null,
|
|
316
|
+
notice: null,
|
|
317
|
+
renewal: null,
|
|
318
|
+
issuanceDraft: state.pending === null ? null : state.issuanceDraft,
|
|
319
|
+
});
|
|
258
320
|
},
|
|
259
321
|
restore() {
|
|
260
322
|
const loaded = loadPending(storage, deps.now());
|
|
261
323
|
const action = loaded.ok ? restoredIssue(loaded.action?.body) : null;
|
|
262
324
|
if (action !== null)
|
|
263
|
-
set({
|
|
325
|
+
set({ pending: action, issuanceDraft: action.refresh ? action : null, secret: null });
|
|
264
326
|
},
|
|
265
327
|
async start(target, project) {
|
|
328
|
+
set({ issuanceProject: project });
|
|
266
329
|
controller.restore();
|
|
267
330
|
const action = state.pending;
|
|
268
|
-
await controller.load(action?.target ?? target, action
|
|
331
|
+
await controller.load(action?.listTarget ?? action?.target ?? target, action ? (action.listTarget ? (action.listProject ?? null) : action.project) : project);
|
|
269
332
|
},
|
|
270
333
|
async selectTarget(target, project) {
|
|
271
334
|
if (state.busy)
|
|
272
335
|
return;
|
|
273
|
-
|
|
274
|
-
set(tokensReducer(state, { type: 'navigate' }));
|
|
336
|
+
set({ pending: null, renewal: null });
|
|
275
337
|
await controller.load(target, project);
|
|
276
338
|
},
|
|
277
339
|
decline() {
|
|
@@ -282,11 +344,40 @@ export function createTokensController(services, deps) {
|
|
|
282
344
|
if (action === null || state.busy || state.loading)
|
|
283
345
|
return;
|
|
284
346
|
if (state.lifetime === null) {
|
|
285
|
-
await controller.load(
|
|
347
|
+
await controller.load();
|
|
286
348
|
if (state.pending !== action || state.lifetime === null || state.loading)
|
|
287
349
|
return;
|
|
288
350
|
}
|
|
289
351
|
await send(action);
|
|
352
|
+
if (action.refresh && state.secret !== null)
|
|
353
|
+
await controller.load();
|
|
354
|
+
},
|
|
355
|
+
async issueFor(target, project, draft) {
|
|
356
|
+
const action = {
|
|
357
|
+
target,
|
|
358
|
+
project,
|
|
359
|
+
draft,
|
|
360
|
+
key: deps.newKey(),
|
|
361
|
+
refresh: true,
|
|
362
|
+
listTarget: state.target,
|
|
363
|
+
listProject: state.project,
|
|
364
|
+
};
|
|
365
|
+
set({ issuanceDraft: action });
|
|
366
|
+
await send(action);
|
|
367
|
+
if (state.secret !== null)
|
|
368
|
+
await controller.load();
|
|
369
|
+
},
|
|
370
|
+
configureProject(slug) {
|
|
371
|
+
const result = state.result;
|
|
372
|
+
const project = state.projects.find((p) => p.slug === slug);
|
|
373
|
+
if (!project ||
|
|
374
|
+
!result ||
|
|
375
|
+
(result.target === 'project' && result.project !== slug) ||
|
|
376
|
+
(result.target === 'personal' &&
|
|
377
|
+
result.draft.projectMode === 'selected' &&
|
|
378
|
+
!result.draft.projectIds.includes(project.id)))
|
|
379
|
+
return;
|
|
380
|
+
set({ secretProject: slug });
|
|
290
381
|
},
|
|
291
382
|
async issue(draft) {
|
|
292
383
|
await send({
|
|
@@ -316,6 +407,9 @@ export function createTokensController(services, deps) {
|
|
|
316
407
|
}
|
|
317
408
|
set({ renewal: token, secret: null, issuedId: null, oldPersonalId: null, notice: null });
|
|
318
409
|
},
|
|
410
|
+
invalidProjects() {
|
|
411
|
+
set({ notice: 'Choose between 1 and 200 projects.' });
|
|
412
|
+
},
|
|
319
413
|
registryOrigin: () => new URL(deps.origin).origin,
|
|
320
414
|
clientConfig() {
|
|
321
415
|
return state.secret === null || state.secretProject === null
|
|
@@ -338,7 +432,9 @@ export function createTokensController(services, deps) {
|
|
|
338
432
|
catch {
|
|
339
433
|
if (version === epoch)
|
|
340
434
|
set({
|
|
341
|
-
notice: kind === undefined
|
|
435
|
+
notice: kind === undefined
|
|
436
|
+
? 'Could not copy. Select and copy the token manually.'
|
|
437
|
+
: 'Could not copy. Select and copy the configuration manually.',
|
|
342
438
|
});
|
|
343
439
|
}
|
|
344
440
|
},
|
|
@@ -349,7 +445,7 @@ export function createTokensController(services, deps) {
|
|
|
349
445
|
project,
|
|
350
446
|
loading: true,
|
|
351
447
|
listError: false,
|
|
352
|
-
notice: null,
|
|
448
|
+
notice: state.secret === null ? null : state.notice,
|
|
353
449
|
items: [],
|
|
354
450
|
next: null,
|
|
355
451
|
lifetime: null,
|
|
@@ -375,36 +471,106 @@ export function createTokensController(services, deps) {
|
|
|
375
471
|
},
|
|
376
472
|
async loadPrincipals(after) {
|
|
377
473
|
const version = navigation;
|
|
378
|
-
|
|
379
|
-
|
|
474
|
+
set({ principalsLoading: true, principalsError: false });
|
|
475
|
+
const principals = [];
|
|
476
|
+
let cursor = after;
|
|
477
|
+
do {
|
|
478
|
+
const result = await services.client.request('get', '/_pb/service-principals', {
|
|
479
|
+
query: { after: cursor },
|
|
480
|
+
});
|
|
481
|
+
if (version !== navigation)
|
|
482
|
+
return;
|
|
483
|
+
if (!result.ok) {
|
|
484
|
+
set({
|
|
485
|
+
principalsLoading: false,
|
|
486
|
+
principalsError: result.status !== 403,
|
|
487
|
+
principalsRestricted: result.status === 403,
|
|
488
|
+
principals: [],
|
|
489
|
+
principalsNext: null,
|
|
490
|
+
});
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
principals.push(...result.data.items.map((p) => ({
|
|
494
|
+
id: p.id,
|
|
495
|
+
name: p.name,
|
|
496
|
+
projectId: p.projectId,
|
|
497
|
+
status: p.status,
|
|
498
|
+
revision: p.revision,
|
|
499
|
+
createdByUserId: p.createdByUserId,
|
|
500
|
+
permissions: p.permissions,
|
|
501
|
+
})));
|
|
502
|
+
cursor = result.data.next ?? undefined;
|
|
503
|
+
} while (cursor !== undefined);
|
|
504
|
+
set({
|
|
505
|
+
principals,
|
|
506
|
+
principalsNext: null,
|
|
507
|
+
principalsRestricted: false,
|
|
508
|
+
principalsLoading: false,
|
|
380
509
|
});
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
510
|
+
},
|
|
511
|
+
loadProjectPolicies(ids, retry = false) {
|
|
512
|
+
const marked = new Set();
|
|
513
|
+
for (const id of ids) {
|
|
514
|
+
const project = state.projects.find((p) => p.id === id);
|
|
515
|
+
if (!project ||
|
|
516
|
+
project.policyState === 'loading' ||
|
|
517
|
+
project.policyState === 'loaded' ||
|
|
518
|
+
(!retry && project.policyState === 'error'))
|
|
519
|
+
continue;
|
|
520
|
+
marked.add(id);
|
|
521
|
+
policyQueue.push(id);
|
|
522
|
+
}
|
|
523
|
+
if (marked.size)
|
|
384
524
|
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,
|
|
525
|
+
projects: state.projects.map((p) => marked.has(p.id) ? { ...p, policyState: 'loading' } : p),
|
|
396
526
|
});
|
|
397
|
-
|
|
398
|
-
|
|
527
|
+
async function work() {
|
|
528
|
+
policyWorkers++;
|
|
529
|
+
try {
|
|
530
|
+
while (policyQueue.length) {
|
|
531
|
+
const id = policyQueue.shift();
|
|
532
|
+
const project = state.projects.find((p) => p.id === id);
|
|
533
|
+
if (!project)
|
|
534
|
+
continue;
|
|
535
|
+
const version = navigation;
|
|
536
|
+
const detail = await services.client.request('get', '/_pb/projects/{project}', {
|
|
537
|
+
params: { project: project.slug },
|
|
538
|
+
});
|
|
539
|
+
const authority = await services.client.request('get', '/_pb/projects/{project}/permissions', { params: { project: project.slug } });
|
|
540
|
+
if (version !== navigation)
|
|
541
|
+
continue;
|
|
542
|
+
set({
|
|
543
|
+
projects: state.projects.map((p) => p.id === id
|
|
544
|
+
? {
|
|
545
|
+
...p,
|
|
546
|
+
cap: detail.ok ? detail.data.tokenMaxScope : null,
|
|
547
|
+
permissions: authority.ok ? authority.data.project : null,
|
|
548
|
+
policyState: detail.ok && authority.ok ? 'loaded' : 'error',
|
|
549
|
+
}
|
|
550
|
+
: p),
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
finally {
|
|
555
|
+
policyWorkers--;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
while (policyQueue.length && policyWorkers < 4)
|
|
559
|
+
void work();
|
|
560
|
+
},
|
|
561
|
+
choosePrincipal(id, project, organization = project === null) {
|
|
562
|
+
set({
|
|
563
|
+
issuancePrincipal: id,
|
|
564
|
+
issuanceProject: organization ? '__organization' : (project ?? ''),
|
|
565
|
+
issuanceNonce: state.issuanceNonce + 1,
|
|
566
|
+
});
|
|
399
567
|
},
|
|
400
568
|
async identity() {
|
|
401
569
|
const version = navigation;
|
|
402
|
-
const
|
|
403
|
-
if (version !== navigation)
|
|
404
|
-
return;
|
|
405
|
-
if (result.ok)
|
|
406
|
-
set({ userId: result.data.user.id });
|
|
570
|
+
const session = services.client.request('get', '/_pb/session');
|
|
407
571
|
const projectSlugs = {};
|
|
572
|
+
const catalogue = [];
|
|
573
|
+
let projectsError = false;
|
|
408
574
|
let projectAfter;
|
|
409
575
|
do {
|
|
410
576
|
const projects = await services.client.request('get', '/_pb/projects', {
|
|
@@ -412,16 +578,36 @@ export function createTokensController(services, deps) {
|
|
|
412
578
|
});
|
|
413
579
|
if (version !== navigation)
|
|
414
580
|
return;
|
|
415
|
-
if (!projects.ok)
|
|
581
|
+
if (!projects.ok) {
|
|
582
|
+
projectsError = true;
|
|
416
583
|
break;
|
|
417
|
-
|
|
584
|
+
}
|
|
585
|
+
for (const project of projects.data.items) {
|
|
418
586
|
projectSlugs[project.id] = project.slug;
|
|
587
|
+
if (project.status === 'archived')
|
|
588
|
+
continue;
|
|
589
|
+
catalogue.push({
|
|
590
|
+
id: project.id,
|
|
591
|
+
slug: project.slug,
|
|
592
|
+
name: project.name ?? project.slug,
|
|
593
|
+
cap: null,
|
|
594
|
+
permissions: null,
|
|
595
|
+
policyState: 'idle',
|
|
596
|
+
});
|
|
597
|
+
}
|
|
419
598
|
projectAfter = projects.data.next ?? undefined;
|
|
599
|
+
set({ projectSlugs: { ...projectSlugs }, projects: [...catalogue], projectsError });
|
|
420
600
|
} while (projectAfter !== undefined);
|
|
421
|
-
set({ projectSlugs });
|
|
601
|
+
set({ projectSlugs, projects: catalogue, projectsError });
|
|
602
|
+
const result = await session;
|
|
603
|
+
if (version !== navigation)
|
|
604
|
+
return;
|
|
605
|
+
if (result.ok)
|
|
606
|
+
set({ userId: result.data.user.id });
|
|
422
607
|
const permissions = await services.client.request('get', '/_pb/me/permissions');
|
|
423
608
|
if (version !== navigation)
|
|
424
609
|
return;
|
|
610
|
+
set({ organizationPermissions: permissions.ok ? permissions.data.organization : [] });
|
|
425
611
|
if (!permissions.ok || !permissions.data.organization.includes('org:users:read')) {
|
|
426
612
|
set({ names: {} });
|
|
427
613
|
return;
|
|
@@ -489,7 +675,8 @@ export function createTokensController(services, deps) {
|
|
|
489
675
|
failure(result);
|
|
490
676
|
else {
|
|
491
677
|
await controller.loadPrincipals();
|
|
492
|
-
set({ notice: 'Service principal created.' });
|
|
678
|
+
set({ notice: 'Service principal created. Create a token to use it.' });
|
|
679
|
+
return result.data;
|
|
493
680
|
}
|
|
494
681
|
},
|
|
495
682
|
async suspendPrincipal(principal) {
|
|
@@ -518,7 +705,9 @@ export function createTokensController(services, deps) {
|
|
|
518
705
|
}
|
|
519
706
|
export function filterTokens(items, filters) {
|
|
520
707
|
return items.filter((item) => item.name.toLowerCase().includes(filters.q.toLowerCase()) &&
|
|
521
|
-
(!filters.scope ||
|
|
708
|
+
(!filters.scope ||
|
|
709
|
+
item.scope === filters.scope ||
|
|
710
|
+
accessLabels[item.scope] === filters.scope) &&
|
|
522
711
|
(!filters.status || item.status === filters.status));
|
|
523
712
|
}
|
|
524
713
|
export function TokenList({ state: s, controller: c }) {
|
|
@@ -532,7 +721,7 @@ export function TokenList({ state: s, controller: c }) {
|
|
|
532
721
|
: 'Organization';
|
|
533
722
|
return (_jsxs(_Fragment, { children: [_jsx(LoadedListFilters, { values: filters, fields: [
|
|
534
723
|
{ key: 'q', label: 'Name (this page)' },
|
|
535
|
-
{ key: 'scope', label: 'Scope', options:
|
|
724
|
+
{ key: 'scope', label: 'Scope', options: Object.values(accessLabels) },
|
|
536
725
|
{ key: 'status', label: 'Status', options: ['active', 'expired', 'revoked'] },
|
|
537
726
|
], 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
727
|
{
|
|
@@ -551,7 +740,7 @@ export function TokenList({ state: s, controller: c }) {
|
|
|
551
740
|
key: 'scope',
|
|
552
741
|
label: 'Scope',
|
|
553
742
|
value: (token) => token.scope,
|
|
554
|
-
render: (token) => _jsx(Badge, { variant: "outline", children: token.scope }),
|
|
743
|
+
render: (token) => _jsx(Badge, { variant: "outline", children: accessLabels[token.scope] }),
|
|
555
744
|
},
|
|
556
745
|
{
|
|
557
746
|
key: 'project',
|
|
@@ -599,8 +788,25 @@ export function TokenList({ state: s, controller: c }) {
|
|
|
599
788
|
},
|
|
600
789
|
] })), 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
790
|
}
|
|
791
|
+
function focusResult(node) {
|
|
792
|
+
node?.focus();
|
|
793
|
+
}
|
|
794
|
+
function SetupProjectAccess({ state: s, controller: c }) {
|
|
795
|
+
const [setupCandidate, setSetupCandidate] = useState('');
|
|
796
|
+
const candidate = s.projects.find((p) => p.id === setupCandidate);
|
|
797
|
+
useEffect(() => {
|
|
798
|
+
if (s.secret && candidate?.policyState === 'loaded' && hasSetupAccess(s, candidate))
|
|
799
|
+
c.configureProject(candidate.slug);
|
|
800
|
+
}, [c, s.secret, candidate]);
|
|
801
|
+
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) => {
|
|
802
|
+
setSetupCandidate(event.target.value);
|
|
803
|
+
c.loadProjectPolicies([event.target.value]);
|
|
804
|
+
}, children: [_jsx("option", { value: "", children: "Choose a project" }), s.projects
|
|
805
|
+
.filter((p) => inResultScope(s.result, p))
|
|
806
|
+
.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." }))] }))] }));
|
|
807
|
+
}
|
|
602
808
|
export function TokensView({ state: s, controller: c }) {
|
|
603
|
-
const personal = s.renewal
|
|
809
|
+
const personal = s.renewal !== null ? s.renewal.kind === 'pat' : s.target === 'personal';
|
|
604
810
|
const policy = s.lifetime?.[personal ? 'personal' : 'service'];
|
|
605
811
|
const renewal = s.renewal;
|
|
606
812
|
const clientConfig = c.clientConfig();
|
|
@@ -609,26 +815,20 @@ export function TokensView({ state: s, controller: c }) {
|
|
|
609
815
|
: s.target === 'project'
|
|
610
816
|
? Object.keys(s.projectSlugs).find((id) => s.projectSlugs[id] === s.project)
|
|
611
817
|
: 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');
|
|
818
|
+
const principalChoices = s.principals.filter((p) => p.status === 'active');
|
|
626
819
|
function issue(event) {
|
|
627
820
|
event.preventDefault();
|
|
628
821
|
if (s.busy)
|
|
629
822
|
return;
|
|
630
823
|
const form = event.currentTarget;
|
|
631
824
|
const data = new FormData(form);
|
|
825
|
+
const selectedIds = data.getAll('projectIds').map(String);
|
|
826
|
+
if (personal &&
|
|
827
|
+
data.get('projectMode') === 'selected' &&
|
|
828
|
+
(selectedIds.length < 1 || selectedIds.length > 200)) {
|
|
829
|
+
c.invalidProjects();
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
632
832
|
const scope = renewal?.kind === 'svc' ? renewal.scope : String(data.get('scope'));
|
|
633
833
|
void c.issue({
|
|
634
834
|
name: String(data.get('name')),
|
|
@@ -636,28 +836,49 @@ export function TokensView({ state: s, controller: c }) {
|
|
|
636
836
|
lifetimeDays: Number(data.get('lifetimeDays')),
|
|
637
837
|
servicePrincipalId: String(data.get('principal') ?? ''),
|
|
638
838
|
projectMode: data.get('projectMode') === 'selected' ? 'selected' : 'all',
|
|
639
|
-
projectIds:
|
|
640
|
-
.split(/\s+/)
|
|
641
|
-
.filter(Boolean),
|
|
839
|
+
projectIds: data.getAll('projectIds').map(String),
|
|
642
840
|
overlapDays: Number(data.get('overlap') ?? 0),
|
|
643
841
|
});
|
|
644
|
-
form.reset();
|
|
645
842
|
}
|
|
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
|
-
|
|
843
|
+
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'
|
|
844
|
+
? (s.projects.find((p) => p.slug === s.result?.project)?.name ??
|
|
845
|
+
'Project name unavailable')
|
|
846
|
+
: s.result.target === 'organization'
|
|
847
|
+
? 'Organization-wide'
|
|
848
|
+
: s.result.draft.projectMode === 'all'
|
|
849
|
+
? 'All accessible projects'
|
|
850
|
+
: s.result.draft.projectIds.length === 1
|
|
851
|
+
? (s.projects.find((p) => p.id === s.result?.draft.projectIds[0])?.name ??
|
|
852
|
+
'1 selected project')
|
|
853
|
+
: `${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: () => {
|
|
854
|
+
c.closeSecret();
|
|
855
|
+
requestAnimationFrame(() => document.getElementById('new-token')?.focus());
|
|
856
|
+
}, 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
|
|
857
|
+
.filter((p) => hasSetupAccess(s, p))
|
|
858
|
+
.filter((p) => inResultScope(s.result, p))
|
|
859
|
+
.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." }), [
|
|
860
|
+
['Composer', ['composer', 'composerAuth']],
|
|
861
|
+
['npm', ['npm']],
|
|
862
|
+
['CI environment', ['ciComposer', 'ciNpm']],
|
|
863
|
+
].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: {
|
|
864
|
+
composer: 'Composer repository command',
|
|
865
|
+
composerAuth: 'Merge this into your local auth.json. Do not commit credentials.',
|
|
866
|
+
npm: 'Use this on your computer. For CI, use the environment-variable version below.',
|
|
867
|
+
ciComposer: 'Save PACKAGE_BROKER_TOKEN as a secret in your CI settings. These examples install packages; they do not publish them.',
|
|
868
|
+
ciNpm: 'Save this as .npmrc, then run npm ci.',
|
|
869
|
+
}[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: {
|
|
870
|
+
composer: 'Copy Composer configuration',
|
|
871
|
+
composerAuth: 'Copy auth.json',
|
|
872
|
+
npm: 'Copy npm configuration',
|
|
873
|
+
ciComposer: 'Copy CI Composer',
|
|
874
|
+
ciNpm: 'Copy CI npm',
|
|
875
|
+
}[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
876
|
? null
|
|
650
|
-
: lifetimeChoices(policy).map((days) => (
|
|
877
|
+
: 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
878
|
.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: [
|
|
879
|
+
.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) => {
|
|
880
|
+
void c.selectTarget(e.target.value, s.project);
|
|
881
|
+
}, 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}`), s.project === null && s.organizationPermissions.includes('org:service-principals:write') ? (_jsx(TrustedPublishers, { controller: c.trustedPublishers, permissions: s.organizationPermissions })) : null, _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
882
|
{
|
|
662
883
|
key: 'name',
|
|
663
884
|
label: 'Name',
|
|
@@ -687,13 +908,21 @@ export function bindTokensLifecycle(controller, events, onNavigate) {
|
|
|
687
908
|
onNavigate();
|
|
688
909
|
};
|
|
689
910
|
const clear = () => controller.navigate();
|
|
911
|
+
const show = (event) => {
|
|
912
|
+
clear();
|
|
913
|
+
if (event.persisted === true) {
|
|
914
|
+
onNavigate();
|
|
915
|
+
if (controller.snapshot().organizationPermissions.includes('org:service-principals:write'))
|
|
916
|
+
void controller.trustedPublishers.load();
|
|
917
|
+
}
|
|
918
|
+
};
|
|
690
919
|
events.addEventListener('hashchange', change);
|
|
691
920
|
events.addEventListener('pagehide', clear);
|
|
692
|
-
events.addEventListener('pageshow',
|
|
921
|
+
events.addEventListener('pageshow', show);
|
|
693
922
|
return () => {
|
|
694
923
|
events.removeEventListener('hashchange', change);
|
|
695
924
|
events.removeEventListener('pagehide', clear);
|
|
696
|
-
events.removeEventListener('pageshow',
|
|
925
|
+
events.removeEventListener('pageshow', show);
|
|
697
926
|
controller.dispose();
|
|
698
927
|
};
|
|
699
928
|
}
|
|
@@ -733,10 +962,19 @@ export function TokensPage() {
|
|
|
733
962
|
if (window.location.hash.startsWith('#/tokens'))
|
|
734
963
|
void controller.start(project === null ? 'personal' : 'project', project);
|
|
735
964
|
}
|
|
965
|
+
function reload() {
|
|
966
|
+
if (!window.location.hash.startsWith('#/tokens'))
|
|
967
|
+
return;
|
|
968
|
+
void controller.identity();
|
|
969
|
+
void controller.loadPrincipals();
|
|
970
|
+
}
|
|
736
971
|
change();
|
|
737
972
|
void controller.identity();
|
|
738
973
|
void controller.loadPrincipals();
|
|
739
|
-
return bindTokensLifecycle(controller, window,
|
|
974
|
+
return bindTokensLifecycle(controller, window, () => {
|
|
975
|
+
change();
|
|
976
|
+
reload();
|
|
977
|
+
});
|
|
740
978
|
}, [controller]);
|
|
741
979
|
useEffect(() => bindTokensReplay(controller, services), [controller, services]);
|
|
742
980
|
return _jsx(TokensView, { state: state, controller: controller });
|