@shipfox/client-runners 4.0.0 → 6.0.1
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +46 -0
- package/dist/components/create-manual-registration-token-form.d.ts +3 -3
- package/dist/components/create-manual-registration-token-form.d.ts.map +1 -1
- package/dist/components/create-manual-registration-token-form.js +8 -23
- package/dist/components/create-manual-registration-token-form.js.map +1 -1
- package/dist/components/create-provisioner-token-form.d.ts +3 -3
- package/dist/components/create-provisioner-token-form.d.ts.map +1 -1
- package/dist/components/create-provisioner-token-form.js +8 -23
- package/dist/components/create-provisioner-token-form.js.map +1 -1
- package/dist/components/manual-registration-token-errors.d.ts.map +1 -1
- package/dist/components/manual-registration-token-errors.js +0 -1
- package/dist/components/manual-registration-token-errors.js.map +1 -1
- package/dist/components/manual-registration-token-format.d.ts +0 -2
- package/dist/components/manual-registration-token-format.d.ts.map +1 -1
- package/dist/components/manual-registration-token-format.js +0 -3
- package/dist/components/manual-registration-token-format.js.map +1 -1
- package/dist/components/manual-registration-token-list.d.ts +2 -2
- package/dist/components/manual-registration-token-list.d.ts.map +1 -1
- package/dist/components/manual-registration-token-list.js +12 -19
- package/dist/components/manual-registration-token-list.js.map +1 -1
- package/dist/components/manual-registration-tokens-settings-section.d.ts.map +1 -1
- package/dist/components/manual-registration-tokens-settings-section.js +1 -1
- package/dist/components/manual-registration-tokens-settings-section.js.map +1 -1
- package/dist/components/provisioner-token-errors.d.ts.map +1 -1
- package/dist/components/provisioner-token-errors.js +0 -1
- package/dist/components/provisioner-token-errors.js.map +1 -1
- package/dist/components/provisioner-token-format.d.ts +0 -18
- package/dist/components/provisioner-token-format.d.ts.map +1 -1
- package/dist/components/provisioner-token-format.js +0 -25
- package/dist/components/provisioner-token-format.js.map +1 -1
- package/dist/components/provisioner-token-list.d.ts +2 -2
- package/dist/components/provisioner-token-list.d.ts.map +1 -1
- package/dist/components/provisioner-token-list.js +9 -19
- package/dist/components/provisioner-token-list.js.map +1 -1
- package/dist/components/provisioner-tokens-settings-section.js +2 -2
- package/dist/components/provisioner-tokens-settings-section.js.map +1 -1
- package/dist/components/token-expiration-select.d.ts +7 -0
- package/dist/components/token-expiration-select.d.ts.map +1 -1
- package/dist/components/token-expiration-select.js +8 -0
- package/dist/components/token-expiration-select.js.map +1 -1
- package/dist/components/token-settings-sections.stories.d.ts.map +1 -1
- package/dist/components/token-settings-sections.stories.js +66 -23
- package/dist/components/token-settings-sections.stories.js.map +1 -1
- package/dist/core/token.d.ts +66 -0
- package/dist/core/token.d.ts.map +1 -0
- package/dist/core/token.js +37 -0
- package/dist/core/token.js.map +1 -0
- package/dist/feature.d.ts +39 -0
- package/dist/feature.d.ts.map +1 -0
- package/dist/feature.js +35 -0
- package/dist/feature.js.map +1 -0
- package/dist/hooks/api/manual-registration-tokens.d.ts +13 -71
- package/dist/hooks/api/manual-registration-tokens.d.ts.map +1 -1
- package/dist/hooks/api/manual-registration-tokens.js +40 -19
- package/dist/hooks/api/manual-registration-tokens.js.map +1 -1
- package/dist/hooks/api/provisioner-tokens.d.ts +17 -115
- package/dist/hooks/api/provisioner-tokens.d.ts.map +1 -1
- package/dist/hooks/api/provisioner-tokens.js +59 -32
- package/dist/hooks/api/provisioner-tokens.js.map +1 -1
- package/dist/hooks/api/token-mapper.d.ts +12 -0
- package/dist/hooks/api/token-mapper.d.ts.map +1 -0
- package/dist/hooks/api/token-mapper.js +61 -0
- package/dist/hooks/api/token-mapper.js.map +1 -0
- package/dist/routes/provisioners-settings.d.ts +5 -0
- package/dist/routes/provisioners-settings.d.ts.map +1 -0
- package/dist/routes/provisioners-settings.js +10 -0
- package/dist/routes/provisioners-settings.js.map +1 -0
- package/dist/routes/runners-settings.d.ts +5 -0
- package/dist/routes/runners-settings.d.ts.map +1 -0
- package/dist/routes/runners-settings.js +10 -0
- package/dist/routes/runners-settings.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +17 -8
- package/src/components/create-manual-registration-token-form.tsx +11 -21
- package/src/components/create-provisioner-token-form.tsx +11 -21
- package/src/components/form-errors.test.ts +2 -2
- package/src/components/manual-registration-token-errors.ts +0 -1
- package/src/components/manual-registration-token-format.test.ts +4 -4
- package/src/components/manual-registration-token-format.ts +0 -7
- package/src/components/manual-registration-token-list.tsx +13 -22
- package/src/components/manual-registration-tokens-settings-section.tsx +3 -5
- package/src/components/provisioner-token-errors.ts +0 -1
- package/src/components/provisioner-token-form-errors.test.ts +2 -2
- package/src/components/provisioner-token-format.test.ts +15 -14
- package/src/components/provisioner-token-format.ts +0 -29
- package/src/components/provisioner-token-list.tsx +14 -23
- package/src/components/provisioner-tokens-settings-section.tsx +4 -4
- package/src/components/token-expiration-select.test.ts +15 -0
- package/src/components/token-expiration-select.tsx +6 -0
- package/src/components/token-settings-sections.stories.tsx +72 -23
- package/src/core/token.test.ts +30 -0
- package/src/core/token.ts +83 -0
- package/src/feature.ts +35 -0
- package/src/hooks/api/manual-registration-tokens.test.ts +8 -5
- package/src/hooks/api/manual-registration-tokens.ts +71 -24
- package/src/hooks/api/provisioner-tokens.test.ts +10 -7
- package/src/hooks/api/provisioner-tokens.ts +100 -36
- package/src/hooks/api/token-mapper.test.ts +161 -0
- package/src/hooks/api/token-mapper.ts +79 -0
- package/src/routes/provisioners-settings.tsx +8 -0
- package/src/routes/runners-settings.tsx +8 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/vercel.json +5 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/client-runners",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.0.1",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -10,28 +10,37 @@
|
|
|
10
10
|
"type": "module",
|
|
11
11
|
"main": "dist/index.js",
|
|
12
12
|
"types": "dist/index.d.ts",
|
|
13
|
-
"imports": {
|
|
14
|
-
"#*": "./dist/*"
|
|
15
|
-
},
|
|
16
13
|
"exports": {
|
|
17
14
|
".": {
|
|
18
15
|
"types": "./dist/index.d.ts",
|
|
19
16
|
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./feature": {
|
|
19
|
+
"types": "./dist/feature.d.ts",
|
|
20
|
+
"default": "./dist/feature.js"
|
|
21
|
+
},
|
|
22
|
+
"./routes/*": {
|
|
23
|
+
"types": "./dist/routes/*.d.ts",
|
|
24
|
+
"default": "./dist/routes/*.js"
|
|
20
25
|
}
|
|
21
26
|
},
|
|
22
27
|
"dependencies": {
|
|
23
28
|
"@swc/helpers": "^0.5.17",
|
|
24
29
|
"@tanstack/react-form": "^1.32.0",
|
|
25
|
-
"@shipfox/api-runners-dto": "
|
|
26
|
-
"@shipfox/client-api": "
|
|
27
|
-
"@shipfox/client-
|
|
28
|
-
"@shipfox/
|
|
30
|
+
"@shipfox/api-runners-dto": "9.0.1",
|
|
31
|
+
"@shipfox/client-api": "6.0.1",
|
|
32
|
+
"@shipfox/client-shell": "6.0.1",
|
|
33
|
+
"@shipfox/client-ui": "6.0.1",
|
|
34
|
+
"@shipfox/react-ui": "0.3.6"
|
|
29
35
|
},
|
|
30
36
|
"peerDependencies": {
|
|
31
37
|
"@tanstack/react-query": "^5.101.0",
|
|
32
38
|
"react": "^19.0.0",
|
|
33
39
|
"react-dom": "^19.0.0"
|
|
34
40
|
},
|
|
41
|
+
"imports": {
|
|
42
|
+
"#*": "./dist/*"
|
|
43
|
+
},
|
|
35
44
|
"scripts": {
|
|
36
45
|
"build": "shipfox-swc",
|
|
37
46
|
"check": "shipfox-biome-check",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {CreateManualRegistrationTokenResponseDto} from '@shipfox/api-runners-dto';
|
|
2
1
|
import {Button} from '@shipfox/react-ui/button';
|
|
3
2
|
import {Callout, CalloutContent, CalloutDescription, CalloutTitle} from '@shipfox/react-ui/callout';
|
|
4
3
|
import {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';
|
|
@@ -6,15 +5,14 @@ import {useCopyToClipboard} from '@shipfox/react-ui/hooks';
|
|
|
6
5
|
import {ModalBody, ModalFooter} from '@shipfox/react-ui/modal';
|
|
7
6
|
import {Code, Text} from '@shipfox/react-ui/typography';
|
|
8
7
|
import {useForm} from '@tanstack/react-form';
|
|
9
|
-
import {useQueryClient} from '@tanstack/react-query';
|
|
10
8
|
import {useState} from 'react';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from '#hooks/api/manual-registration-tokens.js';
|
|
9
|
+
import type {CreatedManualRegistrationToken} from '#core/token.js';
|
|
10
|
+
import {createTokenCommand} from '#core/token.js';
|
|
11
|
+
import {useCreateManualRegistrationTokenMutation} from '#hooks/api/manual-registration-tokens.js';
|
|
15
12
|
import {manualRegistrationTokenCreateErrorToFormError} from './form-errors.js';
|
|
16
13
|
import {
|
|
17
14
|
ExpirationSelect,
|
|
15
|
+
expirationCommand,
|
|
18
16
|
expirationHint,
|
|
19
17
|
type TokenExpirationOption,
|
|
20
18
|
} from './token-expiration-select.js';
|
|
@@ -26,27 +24,19 @@ export function CreateManualRegistrationTokenForm({
|
|
|
26
24
|
onCreated,
|
|
27
25
|
}: {
|
|
28
26
|
workspaceId: string;
|
|
29
|
-
onCreated: (token:
|
|
27
|
+
onCreated: (token: CreatedManualRegistrationToken) => void;
|
|
30
28
|
}) {
|
|
31
|
-
const
|
|
32
|
-
const createToken = useCreateManualRegistrationTokenMutation();
|
|
29
|
+
const createToken = useCreateManualRegistrationTokenMutation(workspaceId);
|
|
33
30
|
const [formError, setFormError] = useState<string | undefined>();
|
|
34
31
|
|
|
35
32
|
const form = useForm({
|
|
36
33
|
defaultValues: {name: '', expiration: '86400' as TokenExpirationOption},
|
|
37
34
|
onSubmit: async ({value}) => {
|
|
38
35
|
setFormError(undefined);
|
|
39
|
-
const
|
|
40
|
-
const body = {
|
|
41
|
-
...(trimmedName ? {name: trimmedName} : {}),
|
|
42
|
-
...(value.expiration === 'never' ? {} : {ttl_seconds: Number(value.expiration)}),
|
|
43
|
-
};
|
|
36
|
+
const command = createTokenCommand(value.name, expirationCommand(value.expiration));
|
|
44
37
|
|
|
45
38
|
try {
|
|
46
|
-
const token = await createToken.mutateAsync(
|
|
47
|
-
await queryClient.invalidateQueries({
|
|
48
|
-
queryKey: manualRegistrationTokenQueryKeys.list(workspaceId),
|
|
49
|
-
});
|
|
39
|
+
const token = await createToken.mutateAsync(command);
|
|
50
40
|
onCreated(token);
|
|
51
41
|
} catch (error) {
|
|
52
42
|
const mapped = manualRegistrationTokenCreateErrorToFormError(error);
|
|
@@ -144,11 +134,11 @@ export function CreateManualRegistrationTokenForm({
|
|
|
144
134
|
export function CreatedManualRegistrationTokenPanel({
|
|
145
135
|
token,
|
|
146
136
|
}: {
|
|
147
|
-
token:
|
|
137
|
+
token: CreatedManualRegistrationToken;
|
|
148
138
|
}) {
|
|
149
139
|
const [copyState, setCopyState] = useState<'idle' | 'copied' | 'failed'>('idle');
|
|
150
140
|
const {copy} = useCopyToClipboard({
|
|
151
|
-
text: token.
|
|
141
|
+
text: token.token,
|
|
152
142
|
onCopy: () => {
|
|
153
143
|
setCopyState('copied');
|
|
154
144
|
window.setTimeout(() => setCopyState('idle'), 1500);
|
|
@@ -175,7 +165,7 @@ export function CreatedManualRegistrationTokenPanel({
|
|
|
175
165
|
</div>
|
|
176
166
|
<div className="flex items-center gap-8 max-[640px]:flex-col max-[640px]:items-stretch">
|
|
177
167
|
<Code variant="paragraph" className="min-w-0 flex-1 break-all">
|
|
178
|
-
{token.
|
|
168
|
+
{token.token}
|
|
179
169
|
</Code>
|
|
180
170
|
<Button
|
|
181
171
|
size="sm"
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {CreateProvisionerTokenResponseDto} from '@shipfox/api-runners-dto';
|
|
2
1
|
import {Button} from '@shipfox/react-ui/button';
|
|
3
2
|
import {Callout, CalloutContent, CalloutDescription, CalloutTitle} from '@shipfox/react-ui/callout';
|
|
4
3
|
import {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';
|
|
@@ -6,15 +5,14 @@ import {useCopyToClipboard} from '@shipfox/react-ui/hooks';
|
|
|
6
5
|
import {ModalBody, ModalFooter} from '@shipfox/react-ui/modal';
|
|
7
6
|
import {Code, Text} from '@shipfox/react-ui/typography';
|
|
8
7
|
import {useForm} from '@tanstack/react-form';
|
|
9
|
-
import {useQueryClient} from '@tanstack/react-query';
|
|
10
8
|
import {useState} from 'react';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from '#hooks/api/provisioner-tokens.js';
|
|
9
|
+
import type {CreatedProvisionerToken} from '#core/token.js';
|
|
10
|
+
import {createTokenCommand} from '#core/token.js';
|
|
11
|
+
import {useCreateProvisionerTokenMutation} from '#hooks/api/provisioner-tokens.js';
|
|
15
12
|
import {provisionerTokenCreateErrorToFormError} from './provisioner-token-form-errors.js';
|
|
16
13
|
import {
|
|
17
14
|
ExpirationSelect,
|
|
15
|
+
expirationCommand,
|
|
18
16
|
expirationHint,
|
|
19
17
|
type TokenExpirationOption,
|
|
20
18
|
} from './token-expiration-select.js';
|
|
@@ -26,27 +24,19 @@ export function CreateProvisionerTokenForm({
|
|
|
26
24
|
onCreated,
|
|
27
25
|
}: {
|
|
28
26
|
workspaceId: string;
|
|
29
|
-
onCreated: (token:
|
|
27
|
+
onCreated: (token: CreatedProvisionerToken) => void;
|
|
30
28
|
}) {
|
|
31
|
-
const
|
|
32
|
-
const createToken = useCreateProvisionerTokenMutation();
|
|
29
|
+
const createToken = useCreateProvisionerTokenMutation(workspaceId);
|
|
33
30
|
const [formError, setFormError] = useState<string | undefined>();
|
|
34
31
|
|
|
35
32
|
const form = useForm({
|
|
36
33
|
defaultValues: {name: '', expiration: '86400' as TokenExpirationOption},
|
|
37
34
|
onSubmit: async ({value}) => {
|
|
38
35
|
setFormError(undefined);
|
|
39
|
-
const
|
|
40
|
-
const body = {
|
|
41
|
-
...(trimmedName ? {name: trimmedName} : {}),
|
|
42
|
-
...(value.expiration === 'never' ? {} : {ttl_seconds: Number(value.expiration)}),
|
|
43
|
-
};
|
|
36
|
+
const command = createTokenCommand(value.name, expirationCommand(value.expiration));
|
|
44
37
|
|
|
45
38
|
try {
|
|
46
|
-
const token = await createToken.mutateAsync(
|
|
47
|
-
await queryClient.invalidateQueries({
|
|
48
|
-
queryKey: provisionerTokenQueryKeys.list(workspaceId),
|
|
49
|
-
});
|
|
39
|
+
const token = await createToken.mutateAsync(command);
|
|
50
40
|
onCreated(token);
|
|
51
41
|
} catch (error) {
|
|
52
42
|
const mapped = provisionerTokenCreateErrorToFormError(error);
|
|
@@ -141,10 +131,10 @@ export function CreateProvisionerTokenForm({
|
|
|
141
131
|
);
|
|
142
132
|
}
|
|
143
133
|
|
|
144
|
-
export function CreatedProvisionerTokenPanel({token}: {token:
|
|
134
|
+
export function CreatedProvisionerTokenPanel({token}: {token: CreatedProvisionerToken}) {
|
|
145
135
|
const [copyState, setCopyState] = useState<'idle' | 'copied' | 'failed'>('idle');
|
|
146
136
|
const {copy} = useCopyToClipboard({
|
|
147
|
-
text: token.
|
|
137
|
+
text: token.token,
|
|
148
138
|
onCopy: () => {
|
|
149
139
|
setCopyState('copied');
|
|
150
140
|
window.setTimeout(() => setCopyState('idle'), 1500);
|
|
@@ -171,7 +161,7 @@ export function CreatedProvisionerTokenPanel({token}: {token: CreateProvisionerT
|
|
|
171
161
|
</div>
|
|
172
162
|
<div className="flex items-center gap-8 max-[640px]:flex-col max-[640px]:items-stretch">
|
|
173
163
|
<Code variant="paragraph" className="min-w-0 flex-1 break-all">
|
|
174
|
-
{token.
|
|
164
|
+
{token.token}
|
|
175
165
|
</Code>
|
|
176
166
|
<Button
|
|
177
167
|
size="sm"
|
|
@@ -10,10 +10,10 @@ describe('manualRegistrationTokenCreateErrorToFormError', () => {
|
|
|
10
10
|
expect(result).toEqual({kind: 'form', message: 'Try later'});
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
test('
|
|
13
|
+
test('does not expose an unknown Error message', () => {
|
|
14
14
|
const result = manualRegistrationTokenCreateErrorToFormError(new Error('boom'));
|
|
15
15
|
|
|
16
|
-
expect(result).toEqual({kind: 'form', message: '
|
|
16
|
+
expect(result).toEqual({kind: 'form', message: 'Something went wrong. Try again.'});
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
test('falls back to generic copy for non-Error throwables', () => {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
+
import {tokenDisplayName} from '#core/token.js';
|
|
1
2
|
import {
|
|
2
3
|
formatManualRegistrationTokenDate,
|
|
3
4
|
formatManualRegistrationTokenTimestamp,
|
|
4
|
-
manualRegistrationTokenDisplayName,
|
|
5
5
|
} from './manual-registration-token-format.js';
|
|
6
6
|
|
|
7
|
-
describe('
|
|
7
|
+
describe('tokenDisplayName', () => {
|
|
8
8
|
test('uses the token name when present', () => {
|
|
9
|
-
const result =
|
|
9
|
+
const result = tokenDisplayName({name: 'Deploy runner'});
|
|
10
10
|
|
|
11
11
|
expect(result).toBe('Deploy runner');
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
test('falls back for unnamed tokens', () => {
|
|
15
|
-
const result =
|
|
15
|
+
const result = tokenDisplayName({name: null});
|
|
16
16
|
|
|
17
17
|
expect(result).toBe('Unnamed token');
|
|
18
18
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {ManualRegistrationTokenDto} from '@shipfox/api-runners-dto';
|
|
2
1
|
import {formatDate, formatTimestamp} from '@shipfox/react-ui/utils';
|
|
3
2
|
|
|
4
3
|
export function formatManualRegistrationTokenDate(value: string | null): string {
|
|
@@ -10,9 +9,3 @@ export function formatManualRegistrationTokenTimestamp(value: string | null): st
|
|
|
10
9
|
if (!value) return undefined;
|
|
11
10
|
return formatTimestamp(value);
|
|
12
11
|
}
|
|
13
|
-
|
|
14
|
-
export function manualRegistrationTokenDisplayName(
|
|
15
|
-
token: Pick<ManualRegistrationTokenDto, 'name'>,
|
|
16
|
-
): string {
|
|
17
|
-
return token.name || 'Unnamed token';
|
|
18
|
-
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {ManualRegistrationTokenDto} from '@shipfox/api-runners-dto';
|
|
2
1
|
import {Button, IconButton} from '@shipfox/react-ui/button';
|
|
3
2
|
import {Callout} from '@shipfox/react-ui/callout';
|
|
4
3
|
import {
|
|
@@ -26,17 +25,13 @@ import {
|
|
|
26
25
|
TableRow,
|
|
27
26
|
} from '@shipfox/react-ui/table';
|
|
28
27
|
import {Code, Text} from '@shipfox/react-ui/typography';
|
|
29
|
-
import {useQueryClient} from '@tanstack/react-query';
|
|
30
28
|
import {useState} from 'react';
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
useRevokeManualRegistrationTokenMutation,
|
|
34
|
-
} from '#hooks/api/manual-registration-tokens.js';
|
|
29
|
+
import {type ManualRegistrationToken, tokenDisplayName} from '#core/token.js';
|
|
30
|
+
import {useRevokeManualRegistrationTokenMutation} from '#hooks/api/manual-registration-tokens.js';
|
|
35
31
|
import {manualRegistrationTokenErrorMessage} from './manual-registration-token-errors.js';
|
|
36
32
|
import {
|
|
37
33
|
formatManualRegistrationTokenDate,
|
|
38
34
|
formatManualRegistrationTokenTimestamp,
|
|
39
|
-
manualRegistrationTokenDisplayName,
|
|
40
35
|
} from './manual-registration-token-format.js';
|
|
41
36
|
import {TokenDate} from './token-date.js';
|
|
42
37
|
import {TokenName} from './token-name.js';
|
|
@@ -46,7 +41,7 @@ export function ManualRegistrationTokenList({
|
|
|
46
41
|
tokens,
|
|
47
42
|
}: {
|
|
48
43
|
workspaceId: string;
|
|
49
|
-
tokens:
|
|
44
|
+
tokens: ManualRegistrationToken[];
|
|
50
45
|
}) {
|
|
51
46
|
return (
|
|
52
47
|
<>
|
|
@@ -65,7 +60,7 @@ export function ManualRegistrationTokenList({
|
|
|
65
60
|
{tokens.map((token) => (
|
|
66
61
|
<TableRow key={token.id}>
|
|
67
62
|
<TableCell>
|
|
68
|
-
<TokenName name={
|
|
63
|
+
<TokenName name={tokenDisplayName(token)} />
|
|
69
64
|
</TableCell>
|
|
70
65
|
<TableCell>
|
|
71
66
|
<Code variant="paragraph" className="block truncate">
|
|
@@ -73,10 +68,10 @@ export function ManualRegistrationTokenList({
|
|
|
73
68
|
</Code>
|
|
74
69
|
</TableCell>
|
|
75
70
|
<TableCell>
|
|
76
|
-
<ManualRegistrationTokenDate value={token.
|
|
71
|
+
<ManualRegistrationTokenDate value={token.expiresAt} />
|
|
77
72
|
</TableCell>
|
|
78
73
|
<TableCell>
|
|
79
|
-
<ManualRegistrationTokenDate value={token.
|
|
74
|
+
<ManualRegistrationTokenDate value={token.createdAt} />
|
|
80
75
|
</TableCell>
|
|
81
76
|
<TableCell className="text-right">
|
|
82
77
|
<RevokeManualRegistrationTokenButton workspaceId={workspaceId} token={token} />
|
|
@@ -97,7 +92,7 @@ export function ManualRegistrationTokenList({
|
|
|
97
92
|
>
|
|
98
93
|
<div className="flex items-start justify-between gap-12">
|
|
99
94
|
<div className="min-w-0 flex-1">
|
|
100
|
-
<TokenName name={
|
|
95
|
+
<TokenName name={tokenDisplayName(token)} />
|
|
101
96
|
<Code variant="paragraph" className="block truncate text-foreground-neutral-muted">
|
|
102
97
|
{token.prefix}
|
|
103
98
|
</Code>
|
|
@@ -108,13 +103,13 @@ export function ManualRegistrationTokenList({
|
|
|
108
103
|
<div>
|
|
109
104
|
<dt className="text-foreground-neutral-muted">Expires</dt>
|
|
110
105
|
<dd>
|
|
111
|
-
<ManualRegistrationTokenDate value={token.
|
|
106
|
+
<ManualRegistrationTokenDate value={token.expiresAt} />
|
|
112
107
|
</dd>
|
|
113
108
|
</div>
|
|
114
109
|
<div>
|
|
115
110
|
<dt className="text-foreground-neutral-muted">Created</dt>
|
|
116
111
|
<dd>
|
|
117
|
-
<ManualRegistrationTokenDate value={token.
|
|
112
|
+
<ManualRegistrationTokenDate value={token.createdAt} />
|
|
118
113
|
</dd>
|
|
119
114
|
</div>
|
|
120
115
|
</dl>
|
|
@@ -140,19 +135,15 @@ function RevokeManualRegistrationTokenButton({
|
|
|
140
135
|
token,
|
|
141
136
|
}: {
|
|
142
137
|
workspaceId: string;
|
|
143
|
-
token:
|
|
138
|
+
token: ManualRegistrationToken;
|
|
144
139
|
}) {
|
|
145
|
-
const
|
|
146
|
-
const revokeToken = useRevokeManualRegistrationTokenMutation();
|
|
140
|
+
const revokeToken = useRevokeManualRegistrationTokenMutation(workspaceId);
|
|
147
141
|
const [open, setOpen] = useState(false);
|
|
148
|
-
const tokenName =
|
|
142
|
+
const tokenName = tokenDisplayName(token);
|
|
149
143
|
|
|
150
144
|
async function handleRevoke() {
|
|
151
145
|
try {
|
|
152
|
-
await revokeToken.mutateAsync(
|
|
153
|
-
await queryClient.invalidateQueries({
|
|
154
|
-
queryKey: manualRegistrationTokenQueryKeys.list(workspaceId),
|
|
155
|
-
});
|
|
146
|
+
await revokeToken.mutateAsync(token.id);
|
|
156
147
|
setOpen(false);
|
|
157
148
|
} catch {
|
|
158
149
|
// React Query stores the error for the inline modal alert.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {CreateManualRegistrationTokenResponseDto} from '@shipfox/api-runners-dto';
|
|
2
1
|
import {QueryLoadError} from '@shipfox/client-ui';
|
|
3
2
|
import {Button} from '@shipfox/react-ui/button';
|
|
4
3
|
import {Icon} from '@shipfox/react-ui/icon';
|
|
@@ -12,6 +11,7 @@ import {
|
|
|
12
11
|
} from '@shipfox/react-ui/modal';
|
|
13
12
|
import {Header, Text} from '@shipfox/react-ui/typography';
|
|
14
13
|
import {useState} from 'react';
|
|
14
|
+
import type {CreatedManualRegistrationToken} from '#core/token.js';
|
|
15
15
|
import {useManualRegistrationTokensQuery} from '#hooks/api/manual-registration-tokens.js';
|
|
16
16
|
import {
|
|
17
17
|
CreatedManualRegistrationTokenPanel,
|
|
@@ -30,10 +30,8 @@ export function WorkspaceManualRegistrationTokensSettingsSection({
|
|
|
30
30
|
}) {
|
|
31
31
|
const tokensQuery = useManualRegistrationTokensQuery(workspaceId);
|
|
32
32
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
33
|
-
const [createdToken, setCreatedToken] = useState<
|
|
34
|
-
|
|
35
|
-
);
|
|
36
|
-
const tokens = tokensQuery.data?.manual_registration_tokens ?? [];
|
|
33
|
+
const [createdToken, setCreatedToken] = useState<CreatedManualRegistrationToken | null>(null);
|
|
34
|
+
const tokens = tokensQuery.data ?? [];
|
|
37
35
|
|
|
38
36
|
function handleOpenChange(nextOpen: boolean) {
|
|
39
37
|
setIsModalOpen(nextOpen);
|
|
@@ -21,10 +21,10 @@ describe('provisionerTokenCreateErrorToFormError', () => {
|
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
test('
|
|
24
|
+
test('does not expose an unknown Error message', () => {
|
|
25
25
|
const result = provisionerTokenCreateErrorToFormError(new Error('boom'));
|
|
26
26
|
|
|
27
|
-
expect(result).toEqual({kind: 'form', message: '
|
|
27
|
+
expect(result).toEqual({kind: 'form', message: 'Something went wrong. Try again.'});
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
test('falls back to generic copy for non-Error throwables', () => {
|
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
import type {ProvisionerTokenDto} from '@shipfox/api-runners-dto';
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
formatProvisionerTokenTimestamp,
|
|
2
|
+
type ProvisionerToken,
|
|
5
3
|
provisionerConnectionStatus,
|
|
6
4
|
provisionerTokenDisplayName,
|
|
5
|
+
} from '#core/token.js';
|
|
6
|
+
import {
|
|
7
|
+
formatProvisionerTokenDate,
|
|
8
|
+
formatProvisionerTokenTimestamp,
|
|
7
9
|
} from './provisioner-token-format.js';
|
|
8
10
|
|
|
9
|
-
const token:
|
|
11
|
+
const token: ProvisionerToken = {
|
|
10
12
|
id: '33333333-3333-4333-8333-333333333333',
|
|
11
|
-
|
|
12
|
-
workspace_id: '11111111-1111-4111-8111-111111111111',
|
|
13
|
+
workspaceId: '11111111-1111-4111-8111-111111111111',
|
|
13
14
|
prefix: 'sf_pt_abcde',
|
|
14
15
|
name: 'Docker provisioner',
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
createdByUserId: '22222222-2222-4222-8222-222222222222',
|
|
17
|
+
revokedByUserId: null,
|
|
18
|
+
expiresAt: '2026-05-09T00:00:00.000Z',
|
|
19
|
+
revokedAt: null,
|
|
20
|
+
lastSeenAt: null,
|
|
21
|
+
createdAt: '2026-05-08T00:00:00.000Z',
|
|
22
|
+
updatedAt: '2026-05-08T00:00:00.000Z',
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
describe('provisionerTokenDisplayName', () => {
|
|
@@ -73,7 +74,7 @@ describe('provisionerConnectionStatus', () => {
|
|
|
73
74
|
|
|
74
75
|
test('returns last seen when inactive with a last seen timestamp', () => {
|
|
75
76
|
const result = provisionerConnectionStatus(
|
|
76
|
-
{...token,
|
|
77
|
+
{...token, lastSeenAt: '2026-05-08T01:00:00.000Z'},
|
|
77
78
|
new Set(),
|
|
78
79
|
);
|
|
79
80
|
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import type {ProvisionerTokenDto} from '@shipfox/api-runners-dto';
|
|
2
|
-
import type {DotVariant} from '@shipfox/react-ui/dot';
|
|
3
1
|
import {formatDate, formatTimestamp} from '@shipfox/react-ui/utils';
|
|
4
2
|
|
|
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
3
|
export function formatProvisionerTokenDate(value: string | null): string {
|
|
11
4
|
if (!value) return 'Never';
|
|
12
5
|
return formatDate(value);
|
|
@@ -16,25 +9,3 @@ export function formatProvisionerTokenTimestamp(value: string | null): string |
|
|
|
16
9
|
if (!value) return undefined;
|
|
17
10
|
return formatTimestamp(value);
|
|
18
11
|
}
|
|
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
|
-
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {ProvisionerTokenDto} from '@shipfox/api-runners-dto';
|
|
2
1
|
import {Button, IconButton} from '@shipfox/react-ui/button';
|
|
3
2
|
import {Callout} from '@shipfox/react-ui/callout';
|
|
4
3
|
import {Dot} from '@shipfox/react-ui/dot';
|
|
@@ -28,18 +27,17 @@ import {
|
|
|
28
27
|
TableRow,
|
|
29
28
|
} from '@shipfox/react-ui/table';
|
|
30
29
|
import {Code, Text} from '@shipfox/react-ui/typography';
|
|
31
|
-
import {useQueryClient} from '@tanstack/react-query';
|
|
32
30
|
import {useState} from 'react';
|
|
33
31
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
type ProvisionerToken,
|
|
33
|
+
provisionerConnectionStatus,
|
|
34
|
+
provisionerTokenDisplayName,
|
|
35
|
+
} from '#core/token.js';
|
|
36
|
+
import {useRevokeProvisionerTokenMutation} from '#hooks/api/provisioner-tokens.js';
|
|
37
37
|
import {provisionerTokenErrorMessage} from './provisioner-token-errors.js';
|
|
38
38
|
import {
|
|
39
39
|
formatProvisionerTokenDate,
|
|
40
40
|
formatProvisionerTokenTimestamp,
|
|
41
|
-
provisionerConnectionStatus,
|
|
42
|
-
provisionerTokenDisplayName,
|
|
43
41
|
} from './provisioner-token-format.js';
|
|
44
42
|
import {TokenDate} from './token-date.js';
|
|
45
43
|
import {TokenName} from './token-name.js';
|
|
@@ -50,7 +48,7 @@ export function ProvisionerTokenList({
|
|
|
50
48
|
activeIds,
|
|
51
49
|
}: {
|
|
52
50
|
workspaceId: string;
|
|
53
|
-
tokens:
|
|
51
|
+
tokens: ProvisionerToken[];
|
|
54
52
|
activeIds: ReadonlySet<string>;
|
|
55
53
|
}) {
|
|
56
54
|
return (
|
|
@@ -82,10 +80,10 @@ export function ProvisionerTokenList({
|
|
|
82
80
|
<ProvisionerStatusCell token={token} activeIds={activeIds} />
|
|
83
81
|
</TableCell>
|
|
84
82
|
<TableCell>
|
|
85
|
-
<ProvisionerTokenDate value={token.
|
|
83
|
+
<ProvisionerTokenDate value={token.expiresAt} />
|
|
86
84
|
</TableCell>
|
|
87
85
|
<TableCell>
|
|
88
|
-
<ProvisionerTokenDate value={token.
|
|
86
|
+
<ProvisionerTokenDate value={token.createdAt} />
|
|
89
87
|
</TableCell>
|
|
90
88
|
<TableCell className="text-right">
|
|
91
89
|
<RevokeProvisionerTokenButton workspaceId={workspaceId} token={token} />
|
|
@@ -120,13 +118,13 @@ export function ProvisionerTokenList({
|
|
|
120
118
|
<div>
|
|
121
119
|
<dt className="text-foreground-neutral-muted">Expires</dt>
|
|
122
120
|
<dd>
|
|
123
|
-
<ProvisionerTokenDate value={token.
|
|
121
|
+
<ProvisionerTokenDate value={token.expiresAt} />
|
|
124
122
|
</dd>
|
|
125
123
|
</div>
|
|
126
124
|
<div>
|
|
127
125
|
<dt className="text-foreground-neutral-muted">Created</dt>
|
|
128
126
|
<dd>
|
|
129
|
-
<ProvisionerTokenDate value={token.
|
|
127
|
+
<ProvisionerTokenDate value={token.createdAt} />
|
|
130
128
|
</dd>
|
|
131
129
|
</div>
|
|
132
130
|
</dl>
|
|
@@ -151,7 +149,7 @@ function ProvisionerStatusCell({
|
|
|
151
149
|
token,
|
|
152
150
|
activeIds,
|
|
153
151
|
}: {
|
|
154
|
-
token:
|
|
152
|
+
token: ProvisionerToken;
|
|
155
153
|
activeIds: ReadonlySet<string>;
|
|
156
154
|
}) {
|
|
157
155
|
const status = provisionerConnectionStatus(token, activeIds);
|
|
@@ -175,22 +173,15 @@ function RevokeProvisionerTokenButton({
|
|
|
175
173
|
token,
|
|
176
174
|
}: {
|
|
177
175
|
workspaceId: string;
|
|
178
|
-
token:
|
|
176
|
+
token: ProvisionerToken;
|
|
179
177
|
}) {
|
|
180
|
-
const
|
|
181
|
-
const revokeToken = useRevokeProvisionerTokenMutation();
|
|
178
|
+
const revokeToken = useRevokeProvisionerTokenMutation(workspaceId);
|
|
182
179
|
const [open, setOpen] = useState(false);
|
|
183
180
|
const tokenName = provisionerTokenDisplayName(token);
|
|
184
181
|
|
|
185
182
|
async function handleRevoke() {
|
|
186
183
|
try {
|
|
187
|
-
await revokeToken.mutateAsync(
|
|
188
|
-
await queryClient.invalidateQueries({
|
|
189
|
-
queryKey: provisionerTokenQueryKeys.list(workspaceId),
|
|
190
|
-
});
|
|
191
|
-
await queryClient.invalidateQueries({
|
|
192
|
-
queryKey: provisionerTokenQueryKeys.active(workspaceId),
|
|
193
|
-
});
|
|
184
|
+
await revokeToken.mutateAsync(token.id);
|
|
194
185
|
setOpen(false);
|
|
195
186
|
} catch {
|
|
196
187
|
// React Query stores the error for the inline modal alert.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {CreateProvisionerTokenResponseDto} from '@shipfox/api-runners-dto';
|
|
2
1
|
import {QueryLoadError} from '@shipfox/client-ui';
|
|
3
2
|
import {Button} from '@shipfox/react-ui/button';
|
|
4
3
|
import {Icon} from '@shipfox/react-ui/icon';
|
|
@@ -13,6 +12,7 @@ import {
|
|
|
13
12
|
import {RelativeTimeProvider} from '@shipfox/react-ui/relative-time';
|
|
14
13
|
import {Header, Text} from '@shipfox/react-ui/typography';
|
|
15
14
|
import {useMemo, useState} from 'react';
|
|
15
|
+
import type {CreatedProvisionerToken} from '#core/token.js';
|
|
16
16
|
import {
|
|
17
17
|
useActiveProvisionersQuery,
|
|
18
18
|
useProvisionerTokensQuery,
|
|
@@ -31,10 +31,10 @@ export function WorkspaceProvisionerTokensSettingsSection({workspaceId}: {worksp
|
|
|
31
31
|
const tokensQuery = useProvisionerTokensQuery(workspaceId);
|
|
32
32
|
const activeProvisionersQuery = useActiveProvisionersQuery(workspaceId);
|
|
33
33
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
34
|
-
const [createdToken, setCreatedToken] = useState<
|
|
35
|
-
const tokens = tokensQuery.data
|
|
34
|
+
const [createdToken, setCreatedToken] = useState<CreatedProvisionerToken | null>(null);
|
|
35
|
+
const tokens = tokensQuery.data ?? [];
|
|
36
36
|
const activeIds = useMemo(
|
|
37
|
-
() => new Set(activeProvisionersQuery.data?.
|
|
37
|
+
() => new Set(activeProvisionersQuery.data?.map((provisioner) => provisioner.id)),
|
|
38
38
|
[activeProvisionersQuery.data],
|
|
39
39
|
);
|
|
40
40
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {expirationCommand} from './token-expiration-select.js';
|
|
2
|
+
|
|
3
|
+
describe('expirationCommand', () => {
|
|
4
|
+
test('maps "never" to a non-expiring command', () => {
|
|
5
|
+
const result = expirationCommand('never');
|
|
6
|
+
|
|
7
|
+
expect(result).toEqual({kind: 'never'});
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('maps a TTL option to an expires-after command in seconds', () => {
|
|
11
|
+
const result = expirationCommand('86400');
|
|
12
|
+
|
|
13
|
+
expect(result).toEqual({kind: 'expires-after', seconds: 86_400});
|
|
14
|
+
});
|
|
15
|
+
});
|