@shipfox/client-runners 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/main.ts +1 -0
- package/.storybook/preview.css +4 -0
- package/.storybook/preview.tsx +64 -0
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +112 -0
- package/LICENSE +21 -0
- package/dist/components/create-manual-registration-token-form.d.ts +10 -0
- package/dist/components/create-manual-registration-token-form.d.ts.map +1 -0
- package/dist/components/create-manual-registration-token-form.js +206 -0
- package/dist/components/create-manual-registration-token-form.js.map +1 -0
- package/dist/components/create-provisioner-token-form.d.ts +10 -0
- package/dist/components/create-provisioner-token-form.d.ts.map +1 -0
- package/dist/components/create-provisioner-token-form.js +206 -0
- package/dist/components/create-provisioner-token-form.js.map +1 -0
- package/dist/components/form-errors.d.ts +6 -0
- package/dist/components/form-errors.d.ts.map +1 -0
- package/dist/components/form-errors.js +9 -0
- package/dist/components/form-errors.js.map +1 -0
- package/dist/components/manual-registration-token-errors.d.ts +2 -0
- package/dist/components/manual-registration-token-errors.d.ts.map +1 -0
- package/dist/components/manual-registration-token-errors.js +14 -0
- package/dist/components/manual-registration-token-errors.js.map +1 -0
- package/dist/components/manual-registration-token-format.d.ts +5 -0
- package/dist/components/manual-registration-token-format.d.ts.map +1 -0
- package/dist/components/manual-registration-token-format.js +14 -0
- package/dist/components/manual-registration-token-format.js.map +1 -0
- package/dist/components/manual-registration-token-list.d.ts +8 -0
- package/dist/components/manual-registration-token-list.d.ts.map +1 -0
- package/dist/components/manual-registration-token-list.js +293 -0
- package/dist/components/manual-registration-token-list.js.map +1 -0
- package/dist/components/manual-registration-tokens-settings-section.d.ts +4 -0
- package/dist/components/manual-registration-tokens-settings-section.d.ts.map +1 -0
- package/dist/components/manual-registration-tokens-settings-section.js +109 -0
- package/dist/components/manual-registration-tokens-settings-section.js.map +1 -0
- package/dist/components/provisioner-token-errors.d.ts +2 -0
- package/dist/components/provisioner-token-errors.d.ts.map +1 -0
- package/dist/components/provisioner-token-errors.js +13 -0
- package/dist/components/provisioner-token-errors.js.map +1 -0
- package/dist/components/provisioner-token-form-errors.d.ts +6 -0
- package/dist/components/provisioner-token-form-errors.d.ts.map +1 -0
- package/dist/components/provisioner-token-form-errors.js +9 -0
- package/dist/components/provisioner-token-form-errors.js.map +1 -0
- package/dist/components/provisioner-token-format.d.ts +21 -0
- package/dist/components/provisioner-token-format.d.ts.map +1 -0
- package/dist/components/provisioner-token-format.js +36 -0
- package/dist/components/provisioner-token-format.js.map +1 -0
- package/dist/components/provisioner-token-list.d.ts +9 -0
- package/dist/components/provisioner-token-list.d.ts.map +1 -0
- package/dist/components/provisioner-token-list.js +344 -0
- package/dist/components/provisioner-token-list.js.map +1 -0
- package/dist/components/provisioner-tokens-settings-section.d.ts +4 -0
- package/dist/components/provisioner-tokens-settings-section.d.ts.map +1 -0
- package/dist/components/provisioner-tokens-settings-section.js +117 -0
- package/dist/components/provisioner-tokens-settings-section.js.map +1 -0
- package/dist/components/token-date.d.ts +6 -0
- package/dist/components/token-date.d.ts.map +1 -0
- package/dist/components/token-date.js +27 -0
- package/dist/components/token-date.js.map +1 -0
- package/dist/components/token-expiration-select.d.ts +11 -0
- package/dist/components/token-expiration-select.d.ts.map +1 -0
- package/dist/components/token-expiration-select.js +68 -0
- package/dist/components/token-expiration-select.js.map +1 -0
- package/dist/components/token-name.d.ts +4 -0
- package/dist/components/token-name.d.ts.map +1 -0
- package/dist/components/token-name.js +24 -0
- package/dist/components/token-name.js.map +1 -0
- package/dist/components/token-settings-sections.stories.d.ts +26 -0
- package/dist/components/token-settings-sections.stories.d.ts.map +1 -0
- package/dist/components/token-settings-sections.stories.js +370 -0
- package/dist/components/token-settings-sections.stories.js.map +1 -0
- package/dist/hooks/api/manual-registration-tokens.d.ts +83 -0
- package/dist/hooks/api/manual-registration-tokens.d.ts.map +1 -0
- package/dist/hooks/api/manual-registration-tokens.js +53 -0
- package/dist/hooks/api/manual-registration-tokens.js.map +1 -0
- package/dist/hooks/api/provisioner-tokens.d.ts +125 -0
- package/dist/hooks/api/provisioner-tokens.d.ts.map +1 -0
- package/dist/hooks/api/provisioner-tokens.js +81 -0
- package/dist/hooks/api/provisioner-tokens.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/package.json +88 -0
- package/src/components/create-manual-registration-token-form.tsx +197 -0
- package/src/components/create-provisioner-token-form.tsx +193 -0
- package/src/components/form-errors.test.ts +24 -0
- package/src/components/form-errors.ts +9 -0
- package/src/components/manual-registration-token-errors.ts +13 -0
- package/src/components/manual-registration-token-format.test.ts +48 -0
- package/src/components/manual-registration-token-format.ts +18 -0
- package/src/components/manual-registration-token-list.tsx +246 -0
- package/src/components/manual-registration-tokens-settings-section.test.tsx +251 -0
- package/src/components/manual-registration-tokens-settings-section.tsx +110 -0
- package/src/components/provisioner-token-errors.ts +12 -0
- package/src/components/provisioner-token-form-errors.test.ts +35 -0
- package/src/components/provisioner-token-form-errors.ts +9 -0
- package/src/components/provisioner-token-format.test.ts +96 -0
- package/src/components/provisioner-token-format.ts +40 -0
- package/src/components/provisioner-token-list.tsx +280 -0
- package/src/components/provisioner-tokens-settings-section.test.tsx +362 -0
- package/src/components/provisioner-tokens-settings-section.tsx +114 -0
- package/src/components/token-date.tsx +27 -0
- package/src/components/token-expiration-select.tsx +64 -0
- package/src/components/token-name.tsx +19 -0
- package/src/components/token-settings-sections.stories.tsx +360 -0
- package/src/hooks/api/manual-registration-tokens.test.ts +87 -0
- package/src/hooks/api/manual-registration-tokens.ts +71 -0
- package/src/hooks/api/provisioner-tokens.test.ts +127 -0
- package/src/hooks/api/provisioner-tokens.ts +102 -0
- package/src/index.ts +4 -0
- package/test/setup.ts +3 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +8 -0
- package/vercel.json +8 -0
- package/vitest.config.ts +74 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type {ProvisionerTokenDto} from '@shipfox/api-runners-dto';
|
|
2
|
+
import {
|
|
3
|
+
formatProvisionerTokenDate,
|
|
4
|
+
formatProvisionerTokenTimestamp,
|
|
5
|
+
provisionerConnectionStatus,
|
|
6
|
+
provisionerTokenDisplayName,
|
|
7
|
+
} from './provisioner-token-format.js';
|
|
8
|
+
|
|
9
|
+
const token: ProvisionerTokenDto = {
|
|
10
|
+
id: '33333333-3333-4333-8333-333333333333',
|
|
11
|
+
workspace_id: '11111111-1111-4111-8111-111111111111',
|
|
12
|
+
prefix: 'sf_pt_abcde',
|
|
13
|
+
name: 'Docker provisioner',
|
|
14
|
+
created_by_user_id: '22222222-2222-4222-8222-222222222222',
|
|
15
|
+
revoked_by_user_id: null,
|
|
16
|
+
expires_at: '2026-05-09T00:00:00.000Z',
|
|
17
|
+
revoked_at: null,
|
|
18
|
+
last_seen_at: null,
|
|
19
|
+
created_at: '2026-05-08T00:00:00.000Z',
|
|
20
|
+
updated_at: '2026-05-08T00:00:00.000Z',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('provisionerTokenDisplayName', () => {
|
|
24
|
+
test('uses the token name when present', () => {
|
|
25
|
+
const result = provisionerTokenDisplayName(token);
|
|
26
|
+
|
|
27
|
+
expect(result).toBe('Docker provisioner');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('falls back for unnamed tokens', () => {
|
|
31
|
+
const result = provisionerTokenDisplayName({...token, name: null});
|
|
32
|
+
|
|
33
|
+
expect(result).toBe('Unnamed provisioner');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('formatProvisionerTokenDate', () => {
|
|
38
|
+
test('formats timestamps as dates', () => {
|
|
39
|
+
const result = formatProvisionerTokenDate('2026-05-08T00:00:00.000Z');
|
|
40
|
+
|
|
41
|
+
expect(result).not.toBe('Never');
|
|
42
|
+
expect(result).not.toContain(':');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('formats null as never', () => {
|
|
46
|
+
const result = formatProvisionerTokenDate(null);
|
|
47
|
+
|
|
48
|
+
expect(result).toBe('Never');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('formatProvisionerTokenTimestamp', () => {
|
|
53
|
+
test('formats timestamps with time', () => {
|
|
54
|
+
const result = formatProvisionerTokenTimestamp('2026-05-08T00:00:00.000Z');
|
|
55
|
+
|
|
56
|
+
expect(result).toContain(':');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('formats null as undefined', () => {
|
|
60
|
+
const result = formatProvisionerTokenTimestamp(null);
|
|
61
|
+
|
|
62
|
+
expect(result).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('provisionerConnectionStatus', () => {
|
|
67
|
+
test('returns connected when the token id is active', () => {
|
|
68
|
+
const result = provisionerConnectionStatus(token, new Set([token.id]));
|
|
69
|
+
|
|
70
|
+
expect(result).toEqual({kind: 'connected', dotVariant: 'success', label: 'Connected'});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('returns last seen when inactive with a last seen timestamp', () => {
|
|
74
|
+
const result = provisionerConnectionStatus(
|
|
75
|
+
{...token, last_seen_at: '2026-05-08T01:00:00.000Z'},
|
|
76
|
+
new Set(),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(result).toEqual({
|
|
80
|
+
kind: 'last-seen',
|
|
81
|
+
dotVariant: 'neutral',
|
|
82
|
+
label: 'Last seen',
|
|
83
|
+
lastSeenAt: '2026-05-08T01:00:00.000Z',
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('returns never connected when inactive with no last seen timestamp', () => {
|
|
88
|
+
const result = provisionerConnectionStatus(token, new Set());
|
|
89
|
+
|
|
90
|
+
expect(result).toEqual({
|
|
91
|
+
kind: 'never-connected',
|
|
92
|
+
dotVariant: 'neutral',
|
|
93
|
+
label: 'Never connected',
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type {ProvisionerTokenDto} from '@shipfox/api-runners-dto';
|
|
2
|
+
import type {DotVariant} from '@shipfox/react-ui/dot';
|
|
3
|
+
import {formatDate, formatTimestamp} from '@shipfox/react-ui/utils';
|
|
4
|
+
|
|
5
|
+
export type ProvisionerConnectionStatus =
|
|
6
|
+
| {kind: 'connected'; dotVariant: DotVariant; label: 'Connected'}
|
|
7
|
+
| {kind: 'last-seen'; dotVariant: DotVariant; label: 'Last seen'; lastSeenAt: string}
|
|
8
|
+
| {kind: 'never-connected'; dotVariant: DotVariant; label: 'Never connected'};
|
|
9
|
+
|
|
10
|
+
export function formatProvisionerTokenDate(value: string | null): string {
|
|
11
|
+
if (!value) return 'Never';
|
|
12
|
+
return formatDate(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function formatProvisionerTokenTimestamp(value: string | null): string | undefined {
|
|
16
|
+
if (!value) return undefined;
|
|
17
|
+
return formatTimestamp(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function provisionerTokenDisplayName(token: Pick<ProvisionerTokenDto, 'name'>): string {
|
|
21
|
+
return token.name || 'Unnamed provisioner';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function provisionerConnectionStatus(
|
|
25
|
+
token: Pick<ProvisionerTokenDto, 'id' | 'last_seen_at'>,
|
|
26
|
+
activeIds: ReadonlySet<string>,
|
|
27
|
+
): ProvisionerConnectionStatus {
|
|
28
|
+
if (activeIds.has(token.id)) {
|
|
29
|
+
return {kind: 'connected', dotVariant: 'success', label: 'Connected'};
|
|
30
|
+
}
|
|
31
|
+
if (token.last_seen_at) {
|
|
32
|
+
return {
|
|
33
|
+
kind: 'last-seen',
|
|
34
|
+
dotVariant: 'neutral',
|
|
35
|
+
label: 'Last seen',
|
|
36
|
+
lastSeenAt: token.last_seen_at,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {kind: 'never-connected', dotVariant: 'neutral', label: 'Never connected'};
|
|
40
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import type {ProvisionerTokenDto} from '@shipfox/api-runners-dto';
|
|
2
|
+
import {Button, IconButton} from '@shipfox/react-ui/button';
|
|
3
|
+
import {Callout} from '@shipfox/react-ui/callout';
|
|
4
|
+
import {Dot} from '@shipfox/react-ui/dot';
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuItem,
|
|
9
|
+
DropdownMenuTrigger,
|
|
10
|
+
} from '@shipfox/react-ui/dropdown-menu';
|
|
11
|
+
import {EmptyState} from '@shipfox/react-ui/empty-state';
|
|
12
|
+
import {
|
|
13
|
+
Modal,
|
|
14
|
+
ModalBody,
|
|
15
|
+
ModalContent,
|
|
16
|
+
ModalFooter,
|
|
17
|
+
ModalHeader,
|
|
18
|
+
ModalTitle,
|
|
19
|
+
} from '@shipfox/react-ui/modal';
|
|
20
|
+
import {RelativeTime} from '@shipfox/react-ui/relative-time';
|
|
21
|
+
import {Skeleton} from '@shipfox/react-ui/skeleton';
|
|
22
|
+
import {
|
|
23
|
+
Table,
|
|
24
|
+
TableBody,
|
|
25
|
+
TableCell,
|
|
26
|
+
TableHead,
|
|
27
|
+
TableHeader,
|
|
28
|
+
TableRow,
|
|
29
|
+
} from '@shipfox/react-ui/table';
|
|
30
|
+
import {Code, Text} from '@shipfox/react-ui/typography';
|
|
31
|
+
import {useQueryClient} from '@tanstack/react-query';
|
|
32
|
+
import {useState} from 'react';
|
|
33
|
+
import {
|
|
34
|
+
provisionerTokenQueryKeys,
|
|
35
|
+
useRevokeProvisionerTokenMutation,
|
|
36
|
+
} from '#hooks/api/provisioner-tokens.js';
|
|
37
|
+
import {provisionerTokenErrorMessage} from './provisioner-token-errors.js';
|
|
38
|
+
import {
|
|
39
|
+
formatProvisionerTokenDate,
|
|
40
|
+
formatProvisionerTokenTimestamp,
|
|
41
|
+
provisionerConnectionStatus,
|
|
42
|
+
provisionerTokenDisplayName,
|
|
43
|
+
} from './provisioner-token-format.js';
|
|
44
|
+
import {TokenDate} from './token-date.js';
|
|
45
|
+
import {TokenName} from './token-name.js';
|
|
46
|
+
|
|
47
|
+
export function ProvisionerTokenList({
|
|
48
|
+
workspaceId,
|
|
49
|
+
tokens,
|
|
50
|
+
activeIds,
|
|
51
|
+
}: {
|
|
52
|
+
workspaceId: string;
|
|
53
|
+
tokens: ProvisionerTokenDto[];
|
|
54
|
+
activeIds: ReadonlySet<string>;
|
|
55
|
+
}) {
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
<div className="max-[760px]:hidden">
|
|
59
|
+
<Table className="table-fixed">
|
|
60
|
+
<TableHeader>
|
|
61
|
+
<TableRow>
|
|
62
|
+
<TableHead className="w-[28%]">Name</TableHead>
|
|
63
|
+
<TableHead>Prefix</TableHead>
|
|
64
|
+
<TableHead className="w-[18%]">Status</TableHead>
|
|
65
|
+
<TableHead className="w-112">Expires</TableHead>
|
|
66
|
+
<TableHead className="w-112">Created</TableHead>
|
|
67
|
+
<TableHead className="w-80 text-right">Actions</TableHead>
|
|
68
|
+
</TableRow>
|
|
69
|
+
</TableHeader>
|
|
70
|
+
<TableBody>
|
|
71
|
+
{tokens.map((token) => (
|
|
72
|
+
<TableRow key={token.id}>
|
|
73
|
+
<TableCell>
|
|
74
|
+
<TokenName name={provisionerTokenDisplayName(token)} />
|
|
75
|
+
</TableCell>
|
|
76
|
+
<TableCell>
|
|
77
|
+
<Code variant="paragraph" className="block truncate">
|
|
78
|
+
{token.prefix}
|
|
79
|
+
</Code>
|
|
80
|
+
</TableCell>
|
|
81
|
+
<TableCell>
|
|
82
|
+
<ProvisionerStatusCell token={token} activeIds={activeIds} />
|
|
83
|
+
</TableCell>
|
|
84
|
+
<TableCell>
|
|
85
|
+
<ProvisionerTokenDate value={token.expires_at} />
|
|
86
|
+
</TableCell>
|
|
87
|
+
<TableCell>
|
|
88
|
+
<ProvisionerTokenDate value={token.created_at} />
|
|
89
|
+
</TableCell>
|
|
90
|
+
<TableCell className="text-right">
|
|
91
|
+
<RevokeProvisionerTokenButton workspaceId={workspaceId} token={token} />
|
|
92
|
+
</TableCell>
|
|
93
|
+
</TableRow>
|
|
94
|
+
))}
|
|
95
|
+
</TableBody>
|
|
96
|
+
</Table>
|
|
97
|
+
</div>
|
|
98
|
+
<ul className="hidden flex-col gap-10 max-[760px]:flex" aria-label="Provisioner tokens">
|
|
99
|
+
{tokens.map((token) => (
|
|
100
|
+
<li
|
|
101
|
+
key={token.id}
|
|
102
|
+
className="rounded-8 border border-border-neutral-base bg-background-neutral-base p-14"
|
|
103
|
+
>
|
|
104
|
+
<div className="flex items-start justify-between gap-12">
|
|
105
|
+
<div className="min-w-0 flex-1">
|
|
106
|
+
<TokenName name={provisionerTokenDisplayName(token)} />
|
|
107
|
+
<Code variant="paragraph" className="block truncate text-foreground-neutral-muted">
|
|
108
|
+
{token.prefix}
|
|
109
|
+
</Code>
|
|
110
|
+
</div>
|
|
111
|
+
<RevokeProvisionerTokenButton workspaceId={workspaceId} token={token} />
|
|
112
|
+
</div>
|
|
113
|
+
<dl className="mt-12 grid grid-cols-2 gap-10 text-sm">
|
|
114
|
+
<div>
|
|
115
|
+
<dt className="text-foreground-neutral-muted">Status</dt>
|
|
116
|
+
<dd>
|
|
117
|
+
<ProvisionerStatusCell token={token} activeIds={activeIds} />
|
|
118
|
+
</dd>
|
|
119
|
+
</div>
|
|
120
|
+
<div>
|
|
121
|
+
<dt className="text-foreground-neutral-muted">Expires</dt>
|
|
122
|
+
<dd>
|
|
123
|
+
<ProvisionerTokenDate value={token.expires_at} />
|
|
124
|
+
</dd>
|
|
125
|
+
</div>
|
|
126
|
+
<div>
|
|
127
|
+
<dt className="text-foreground-neutral-muted">Created</dt>
|
|
128
|
+
<dd>
|
|
129
|
+
<ProvisionerTokenDate value={token.created_at} />
|
|
130
|
+
</dd>
|
|
131
|
+
</div>
|
|
132
|
+
</dl>
|
|
133
|
+
</li>
|
|
134
|
+
))}
|
|
135
|
+
</ul>
|
|
136
|
+
</>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function ProvisionerTokenDate({value}: {value: string | null}) {
|
|
141
|
+
return (
|
|
142
|
+
<TokenDate
|
|
143
|
+
value={value}
|
|
144
|
+
date={formatProvisionerTokenDate(value)}
|
|
145
|
+
timestamp={formatProvisionerTokenTimestamp(value)}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function ProvisionerStatusCell({
|
|
151
|
+
token,
|
|
152
|
+
activeIds,
|
|
153
|
+
}: {
|
|
154
|
+
token: ProvisionerTokenDto;
|
|
155
|
+
activeIds: ReadonlySet<string>;
|
|
156
|
+
}) {
|
|
157
|
+
const status = provisionerConnectionStatus(token, activeIds);
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<span className="inline-flex items-center gap-6">
|
|
161
|
+
<Dot variant={status.dotVariant} />
|
|
162
|
+
{status.kind === 'last-seen' ? (
|
|
163
|
+
<span>
|
|
164
|
+
{status.label} <RelativeTime value={status.lastSeenAt} />
|
|
165
|
+
</span>
|
|
166
|
+
) : (
|
|
167
|
+
<span>{status.label}</span>
|
|
168
|
+
)}
|
|
169
|
+
</span>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function RevokeProvisionerTokenButton({
|
|
174
|
+
workspaceId,
|
|
175
|
+
token,
|
|
176
|
+
}: {
|
|
177
|
+
workspaceId: string;
|
|
178
|
+
token: ProvisionerTokenDto;
|
|
179
|
+
}) {
|
|
180
|
+
const queryClient = useQueryClient();
|
|
181
|
+
const revokeToken = useRevokeProvisionerTokenMutation();
|
|
182
|
+
const [open, setOpen] = useState(false);
|
|
183
|
+
const tokenName = provisionerTokenDisplayName(token);
|
|
184
|
+
|
|
185
|
+
async function handleRevoke() {
|
|
186
|
+
try {
|
|
187
|
+
await revokeToken.mutateAsync({workspaceId, tokenId: token.id});
|
|
188
|
+
await queryClient.invalidateQueries({
|
|
189
|
+
queryKey: provisionerTokenQueryKeys.list(workspaceId),
|
|
190
|
+
});
|
|
191
|
+
await queryClient.invalidateQueries({
|
|
192
|
+
queryKey: provisionerTokenQueryKeys.active(workspaceId),
|
|
193
|
+
});
|
|
194
|
+
setOpen(false);
|
|
195
|
+
} catch {
|
|
196
|
+
// React Query stores the error for the inline modal alert.
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function handleOpenChange(nextOpen: boolean) {
|
|
201
|
+
setOpen(nextOpen);
|
|
202
|
+
if (nextOpen) {
|
|
203
|
+
revokeToken.reset();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function openRevokeConfirmation() {
|
|
208
|
+
revokeToken.reset();
|
|
209
|
+
setOpen(true);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<>
|
|
214
|
+
<DropdownMenu>
|
|
215
|
+
<DropdownMenuTrigger asChild>
|
|
216
|
+
<IconButton
|
|
217
|
+
type="button"
|
|
218
|
+
size="sm"
|
|
219
|
+
variant="transparent"
|
|
220
|
+
icon="more2Line"
|
|
221
|
+
aria-label={`Open ${tokenName} token actions`}
|
|
222
|
+
/>
|
|
223
|
+
</DropdownMenuTrigger>
|
|
224
|
+
<DropdownMenuContent align="end" size="sm">
|
|
225
|
+
<DropdownMenuItem onSelect={openRevokeConfirmation}>Revoke token</DropdownMenuItem>
|
|
226
|
+
</DropdownMenuContent>
|
|
227
|
+
</DropdownMenu>
|
|
228
|
+
<Modal open={open} onOpenChange={handleOpenChange}>
|
|
229
|
+
<ModalContent aria-describedby={undefined} className="max-w-[420px]">
|
|
230
|
+
<ModalTitle className="sr-only">Revoke token</ModalTitle>
|
|
231
|
+
<ModalHeader title="Revoke token?" />
|
|
232
|
+
<ModalBody className="gap-16">
|
|
233
|
+
<Text size="sm" className="text-foreground-neutral-muted">
|
|
234
|
+
{tokenName} will stop authenticating this provisioner. Runners it already provisioned
|
|
235
|
+
keep running until their leases expire.
|
|
236
|
+
</Text>
|
|
237
|
+
{revokeToken.isError ? (
|
|
238
|
+
<Callout role="alert" type="error">
|
|
239
|
+
<Text size="sm">{provisionerTokenErrorMessage(revokeToken.error)}</Text>
|
|
240
|
+
</Callout>
|
|
241
|
+
) : null}
|
|
242
|
+
</ModalBody>
|
|
243
|
+
<ModalFooter>
|
|
244
|
+
<Button size="sm" variant="secondary" onClick={() => setOpen(false)}>
|
|
245
|
+
Cancel
|
|
246
|
+
</Button>
|
|
247
|
+
<Button
|
|
248
|
+
size="sm"
|
|
249
|
+
variant="danger"
|
|
250
|
+
isLoading={revokeToken.isPending}
|
|
251
|
+
onClick={handleRevoke}
|
|
252
|
+
>
|
|
253
|
+
Revoke
|
|
254
|
+
</Button>
|
|
255
|
+
</ModalFooter>
|
|
256
|
+
</ModalContent>
|
|
257
|
+
</Modal>
|
|
258
|
+
</>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function EmptyProvisionerTokens() {
|
|
263
|
+
return (
|
|
264
|
+
<EmptyState
|
|
265
|
+
icon="key2Line"
|
|
266
|
+
title="No usable provisioner registration tokens"
|
|
267
|
+
description="Create a token to connect a provisioner that provisions runners on demand."
|
|
268
|
+
/>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function ProvisionerTokenTableSkeleton() {
|
|
273
|
+
return (
|
|
274
|
+
<div role="status" aria-label="Loading provisioner tokens" className="flex flex-col gap-8">
|
|
275
|
+
{[0, 1, 2].map((row) => (
|
|
276
|
+
<Skeleton key={row} className="h-44 w-full" />
|
|
277
|
+
))}
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
}
|