@shipfox/client-workspace-settings 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/.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 +197 -0
- package/LICENSE +21 -0
- package/dist/components/members/form-errors.d.ts +11 -0
- package/dist/components/members/form-errors.d.ts.map +1 -0
- package/dist/components/members/form-errors.js +28 -0
- package/dist/components/members/form-errors.js.map +1 -0
- package/dist/components/members/workspace-members-section.d.ts +5 -0
- package/dist/components/members/workspace-members-section.d.ts.map +1 -0
- package/dist/components/members/workspace-members-section.js +541 -0
- package/dist/components/members/workspace-members-section.js.map +1 -0
- package/dist/components/workspace-settings-shell.d.ts +8 -0
- package/dist/components/workspace-settings-shell.d.ts.map +1 -0
- package/dist/components/workspace-settings-shell.js +7 -0
- package/dist/components/workspace-settings-shell.js.map +1 -0
- package/dist/feature.d.ts +98 -0
- package/dist/feature.d.ts.map +1 -0
- package/dist/feature.js +120 -0
- package/dist/feature.js.map +1 -0
- package/dist/hooks/api/create-invitation.d.ts +14 -0
- package/dist/hooks/api/create-invitation.d.ts.map +1 -0
- package/dist/hooks/api/create-invitation.js +25 -0
- package/dist/hooks/api/create-invitation.js.map +1 -0
- package/dist/hooks/api/list-invitations.d.ts +15 -0
- package/dist/hooks/api/list-invitations.d.ts.map +1 -0
- package/dist/hooks/api/list-invitations.js +18 -0
- package/dist/hooks/api/list-invitations.js.map +1 -0
- package/dist/hooks/api/list-members.d.ts +13 -0
- package/dist/hooks/api/list-members.d.ts.map +1 -0
- package/dist/hooks/api/list-members.js +18 -0
- package/dist/hooks/api/list-members.js.map +1 -0
- package/dist/hooks/api/remove-member.d.ts +2 -0
- package/dist/hooks/api/remove-member.d.ts.map +1 -0
- package/dist/hooks/api/remove-member.js +24 -0
- package/dist/hooks/api/remove-member.js.map +1 -0
- package/dist/hooks/api/revoke-invitation.d.ts +2 -0
- package/dist/hooks/api/revoke-invitation.d.ts.map +1 -0
- package/dist/hooks/api/revoke-invitation.js +24 -0
- package/dist/hooks/api/revoke-invitation.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/pages/events-settings-page.d.ts +2 -0
- package/dist/pages/events-settings-page.d.ts.map +1 -0
- package/dist/pages/events-settings-page.js +30 -0
- package/dist/pages/events-settings-page.js.map +1 -0
- package/dist/pages/integrations-settings-page.d.ts +2 -0
- package/dist/pages/integrations-settings-page.d.ts.map +1 -0
- package/dist/pages/integrations-settings-page.js +10 -0
- package/dist/pages/integrations-settings-page.js.map +1 -0
- package/dist/pages/members-settings-page.d.ts +2 -0
- package/dist/pages/members-settings-page.d.ts.map +1 -0
- package/dist/pages/members-settings-page.js +15 -0
- package/dist/pages/members-settings-page.js.map +1 -0
- package/dist/pages/model-providers-settings-page.d.ts +2 -0
- package/dist/pages/model-providers-settings-page.d.ts.map +1 -0
- package/dist/pages/model-providers-settings-page.js +20 -0
- package/dist/pages/model-providers-settings-page.js.map +1 -0
- package/dist/pages/provisioners-settings-page.d.ts +2 -0
- package/dist/pages/provisioners-settings-page.d.ts.map +1 -0
- package/dist/pages/provisioners-settings-page.js +14 -0
- package/dist/pages/provisioners-settings-page.js.map +1 -0
- package/dist/pages/runners-settings-page.d.ts +2 -0
- package/dist/pages/runners-settings-page.d.ts.map +1 -0
- package/dist/pages/runners-settings-page.js +14 -0
- package/dist/pages/runners-settings-page.js.map +1 -0
- package/dist/pages/secrets-settings-page.d.ts +2 -0
- package/dist/pages/secrets-settings-page.d.ts.map +1 -0
- package/dist/pages/secrets-settings-page.js +14 -0
- package/dist/pages/secrets-settings-page.js.map +1 -0
- package/dist/pages/variables-settings-page.d.ts +2 -0
- package/dist/pages/variables-settings-page.d.ts.map +1 -0
- package/dist/pages/variables-settings-page.js +14 -0
- package/dist/pages/variables-settings-page.js.map +1 -0
- package/dist/routes/agents.d.ts +6 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +7 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/events.d.ts +8 -0
- package/dist/routes/events.d.ts.map +1 -0
- package/dist/routes/events.js +9 -0
- package/dist/routes/events.js.map +1 -0
- package/dist/routes/index.d.ts +9 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +14 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/integrations.d.ts +6 -0
- package/dist/routes/integrations.d.ts.map +1 -0
- package/dist/routes/integrations.js +7 -0
- package/dist/routes/integrations.js.map +1 -0
- package/dist/routes/members.d.ts +6 -0
- package/dist/routes/members.d.ts.map +1 -0
- package/dist/routes/members.js +7 -0
- package/dist/routes/members.js.map +1 -0
- package/dist/routes/provisioners.d.ts +6 -0
- package/dist/routes/provisioners.d.ts.map +1 -0
- package/dist/routes/provisioners.js +7 -0
- package/dist/routes/provisioners.js.map +1 -0
- package/dist/routes/runners.d.ts +6 -0
- package/dist/routes/runners.d.ts.map +1 -0
- package/dist/routes/runners.js +7 -0
- package/dist/routes/runners.js.map +1 -0
- package/dist/routes/secrets.d.ts +6 -0
- package/dist/routes/secrets.d.ts.map +1 -0
- package/dist/routes/secrets.js +7 -0
- package/dist/routes/secrets.js.map +1 -0
- package/dist/routes/variables.d.ts +6 -0
- package/dist/routes/variables.d.ts.map +1 -0
- package/dist/routes/variables.js +7 -0
- package/dist/routes/variables.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/package.json +104 -0
- package/src/components/members/form-errors.test.ts +40 -0
- package/src/components/members/form-errors.ts +24 -0
- package/src/components/members/workspace-members-section.tsx +476 -0
- package/src/components/workspace-settings-shell.tsx +12 -0
- package/src/feature.ts +120 -0
- package/src/hooks/api/create-invitation.ts +21 -0
- package/src/hooks/api/list-invitations.ts +17 -0
- package/src/hooks/api/list-members.ts +17 -0
- package/src/hooks/api/remove-member.ts +19 -0
- package/src/hooks/api/revoke-invitation.ts +19 -0
- package/src/index.ts +8 -0
- package/src/pages/events-settings-page.tsx +25 -0
- package/src/pages/integrations-settings-page.test.tsx +72 -0
- package/src/pages/integrations-settings-page.tsx +6 -0
- package/src/pages/members-settings-page.tsx +17 -0
- package/src/pages/model-providers-settings-page.test.tsx +48 -0
- package/src/pages/model-providers-settings-page.tsx +15 -0
- package/src/pages/provisioners-settings-page.test.tsx +29 -0
- package/src/pages/provisioners-settings-page.tsx +14 -0
- package/src/pages/runners-settings-page.test.tsx +23 -0
- package/src/pages/runners-settings-page.tsx +14 -0
- package/src/pages/secrets-settings-page.tsx +14 -0
- package/src/pages/variables-settings-page.tsx +14 -0
- package/src/routes/agents.tsx +3 -0
- package/src/routes/events.tsx +7 -0
- package/src/routes/index.tsx +8 -0
- package/src/routes/integrations.tsx +3 -0
- package/src/routes/members.tsx +3 -0
- package/src/routes/provisioners.tsx +3 -0
- package/src/routes/runners.tsx +3 -0
- package/src/routes/secrets.tsx +3 -0
- package/src/routes/variables.tsx +3 -0
- package/test/pages.tsx +101 -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/vitest.config.ts +14 -0
package/src/feature.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {defineClientFeature} from '@shipfox/client-shell';
|
|
2
|
+
import type {IconName} from '@shipfox/react-ui/icon';
|
|
3
|
+
|
|
4
|
+
export const workspaceSettingsFeature = defineClientFeature({
|
|
5
|
+
id: 'shipfox.workspace-settings',
|
|
6
|
+
routes: [
|
|
7
|
+
{
|
|
8
|
+
path: '/workspaces/$wid/settings',
|
|
9
|
+
parent: 'workspaceSettings',
|
|
10
|
+
impl: '@shipfox/client-workspace-settings/routes/index',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
path: '/workspaces/$wid/settings/members',
|
|
14
|
+
parent: 'workspaceSettings',
|
|
15
|
+
impl: '@shipfox/client-workspace-settings/routes/members',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
path: '/workspaces/$wid/settings/runners',
|
|
19
|
+
parent: 'workspaceSettings',
|
|
20
|
+
impl: '@shipfox/client-workspace-settings/routes/runners',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
path: '/workspaces/$wid/settings/provisioners',
|
|
24
|
+
parent: 'workspaceSettings',
|
|
25
|
+
impl: '@shipfox/client-workspace-settings/routes/provisioners',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
path: '/workspaces/$wid/settings/agents',
|
|
29
|
+
parent: 'workspaceSettings',
|
|
30
|
+
impl: '@shipfox/client-workspace-settings/routes/agents',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
path: '/workspaces/$wid/settings/secrets',
|
|
34
|
+
parent: 'workspaceSettings',
|
|
35
|
+
impl: '@shipfox/client-workspace-settings/routes/secrets',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
path: '/workspaces/$wid/settings/variables',
|
|
39
|
+
parent: 'workspaceSettings',
|
|
40
|
+
impl: '@shipfox/client-workspace-settings/routes/variables',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
path: '/workspaces/$wid/settings/integrations',
|
|
44
|
+
parent: 'workspaceSettings',
|
|
45
|
+
impl: '@shipfox/client-workspace-settings/routes/integrations',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
path: '/workspaces/$wid/settings/events',
|
|
49
|
+
parent: 'workspaceSettings',
|
|
50
|
+
impl: '@shipfox/client-workspace-settings/routes/events',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
navigation: [
|
|
54
|
+
{
|
|
55
|
+
id: 'nav.settings',
|
|
56
|
+
scope: 'workspace',
|
|
57
|
+
label: 'Settings',
|
|
58
|
+
to: '/workspaces/$wid/settings',
|
|
59
|
+
order: 200,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
settingsSections: [
|
|
63
|
+
{
|
|
64
|
+
id: 'settings.members',
|
|
65
|
+
pathSegment: 'members',
|
|
66
|
+
label: 'Members',
|
|
67
|
+
icon: 'userLine' as IconName,
|
|
68
|
+
order: 100,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'settings.runners',
|
|
72
|
+
pathSegment: 'runners',
|
|
73
|
+
label: 'Runners',
|
|
74
|
+
icon: 'settings3Line' as IconName,
|
|
75
|
+
order: 200,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'settings.provisioners',
|
|
79
|
+
pathSegment: 'provisioners',
|
|
80
|
+
label: 'Runner provisioners',
|
|
81
|
+
icon: 'serverLine' as IconName,
|
|
82
|
+
order: 300,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'settings.agents',
|
|
86
|
+
pathSegment: 'agents',
|
|
87
|
+
label: 'Agents',
|
|
88
|
+
icon: 'robot2Line' as IconName,
|
|
89
|
+
order: 400,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'settings.secrets',
|
|
93
|
+
pathSegment: 'secrets',
|
|
94
|
+
label: 'Secrets',
|
|
95
|
+
icon: 'keyLine' as IconName,
|
|
96
|
+
order: 500,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'settings.variables',
|
|
100
|
+
pathSegment: 'variables',
|
|
101
|
+
label: 'Variables',
|
|
102
|
+
icon: 'bracesLine' as IconName,
|
|
103
|
+
order: 600,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'settings.integrations',
|
|
107
|
+
pathSegment: 'integrations',
|
|
108
|
+
label: 'Integrations',
|
|
109
|
+
icon: 'plugLine' as IconName,
|
|
110
|
+
order: 700,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'settings.events',
|
|
114
|
+
pathSegment: 'events',
|
|
115
|
+
label: 'Events',
|
|
116
|
+
icon: 'pulseLine' as IconName,
|
|
117
|
+
order: 800,
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type {CreateInvitationBodyDto, InvitationDto} from '@shipfox/api-workspaces-dto';
|
|
2
|
+
import {apiRequest} from '@shipfox/client-api';
|
|
3
|
+
import {useMutation, useQueryClient} from '@tanstack/react-query';
|
|
4
|
+
import {listInvitationsQueryKey} from './list-invitations.js';
|
|
5
|
+
|
|
6
|
+
async function createInvitation(params: {workspaceId: string; body: CreateInvitationBodyDto}) {
|
|
7
|
+
return await apiRequest<InvitationDto>(`/workspaces/${params.workspaceId}/invitations`, {
|
|
8
|
+
method: 'POST',
|
|
9
|
+
body: params.body,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useCreateInvitation(workspaceId: string) {
|
|
14
|
+
const queryClient = useQueryClient();
|
|
15
|
+
return useMutation({
|
|
16
|
+
mutationFn: (body: CreateInvitationBodyDto) => createInvitation({workspaceId, body}),
|
|
17
|
+
onSuccess: async () => {
|
|
18
|
+
await queryClient.invalidateQueries({queryKey: listInvitationsQueryKey(workspaceId)});
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type {ListInvitationsResponseDto} from '@shipfox/api-workspaces-dto';
|
|
2
|
+
import {apiRequest} from '@shipfox/client-api';
|
|
3
|
+
import {useQuery} from '@tanstack/react-query';
|
|
4
|
+
|
|
5
|
+
export const listInvitationsQueryKey = (workspaceId: string) =>
|
|
6
|
+
['workspaces', workspaceId, 'invitations'] as const;
|
|
7
|
+
|
|
8
|
+
async function listInvitations(workspaceId: string): Promise<ListInvitationsResponseDto> {
|
|
9
|
+
return await apiRequest<ListInvitationsResponseDto>(`/workspaces/${workspaceId}/invitations`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useListInvitations(workspaceId: string) {
|
|
13
|
+
return useQuery({
|
|
14
|
+
queryKey: listInvitationsQueryKey(workspaceId),
|
|
15
|
+
queryFn: () => listInvitations(workspaceId),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type {ListMembersResponseDto} from '@shipfox/api-workspaces-dto';
|
|
2
|
+
import {apiRequest} from '@shipfox/client-api';
|
|
3
|
+
import {useQuery} from '@tanstack/react-query';
|
|
4
|
+
|
|
5
|
+
export const listMembersQueryKey = (workspaceId: string) =>
|
|
6
|
+
['workspaces', workspaceId, 'members'] as const;
|
|
7
|
+
|
|
8
|
+
async function listMembers(workspaceId: string): Promise<ListMembersResponseDto> {
|
|
9
|
+
return await apiRequest<ListMembersResponseDto>(`/workspaces/${workspaceId}/members`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useListMembers(workspaceId: string) {
|
|
13
|
+
return useQuery({
|
|
14
|
+
queryKey: listMembersQueryKey(workspaceId),
|
|
15
|
+
queryFn: () => listMembers(workspaceId),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {apiRequest} from '@shipfox/client-api';
|
|
2
|
+
import {useMutation, useQueryClient} from '@tanstack/react-query';
|
|
3
|
+
import {listMembersQueryKey} from './list-members.js';
|
|
4
|
+
|
|
5
|
+
async function removeMember(params: {workspaceId: string; userId: string}) {
|
|
6
|
+
await apiRequest<void>(`/workspaces/${params.workspaceId}/members/${params.userId}`, {
|
|
7
|
+
method: 'DELETE',
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useRemoveMember(workspaceId: string) {
|
|
12
|
+
const queryClient = useQueryClient();
|
|
13
|
+
return useMutation({
|
|
14
|
+
mutationFn: (userId: string) => removeMember({workspaceId, userId}),
|
|
15
|
+
onSuccess: async () => {
|
|
16
|
+
await queryClient.invalidateQueries({queryKey: listMembersQueryKey(workspaceId)});
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {apiRequest} from '@shipfox/client-api';
|
|
2
|
+
import {useMutation, useQueryClient} from '@tanstack/react-query';
|
|
3
|
+
import {listInvitationsQueryKey} from './list-invitations.js';
|
|
4
|
+
|
|
5
|
+
async function revokeInvitation(params: {workspaceId: string; invitationId: string}) {
|
|
6
|
+
await apiRequest<void>(`/workspaces/${params.workspaceId}/invitations/${params.invitationId}`, {
|
|
7
|
+
method: 'DELETE',
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useRevokeInvitation(workspaceId: string) {
|
|
12
|
+
const queryClient = useQueryClient();
|
|
13
|
+
return useMutation({
|
|
14
|
+
mutationFn: (invitationId: string) => revokeInvitation({workspaceId, invitationId}),
|
|
15
|
+
onSuccess: async () => {
|
|
16
|
+
await queryClient.invalidateQueries({queryKey: listInvitationsQueryKey(workspaceId)});
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './pages/events-settings-page.js';
|
|
2
|
+
export * from './pages/integrations-settings-page.js';
|
|
3
|
+
export * from './pages/members-settings-page.js';
|
|
4
|
+
export * from './pages/model-providers-settings-page.js';
|
|
5
|
+
export * from './pages/provisioners-settings-page.js';
|
|
6
|
+
export * from './pages/runners-settings-page.js';
|
|
7
|
+
export * from './pages/secrets-settings-page.js';
|
|
8
|
+
export * from './pages/variables-settings-page.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {EventsPage, type TriggerEventFilters} from '@shipfox/client-triggers';
|
|
2
|
+
import {getRouteApi} from '@tanstack/react-router';
|
|
3
|
+
import {WorkspaceSettingsShell} from '#components/workspace-settings-shell.js';
|
|
4
|
+
|
|
5
|
+
const routeApi = getRouteApi('/workspaces/$wid/_layout/settings/events');
|
|
6
|
+
|
|
7
|
+
export function EventsSettingsPage() {
|
|
8
|
+
const search = routeApi.useSearch();
|
|
9
|
+
const navigate = routeApi.useNavigate();
|
|
10
|
+
|
|
11
|
+
// Merge the patch into the current URL search. Undefined values drop their key, so
|
|
12
|
+
// clearing a filter removes it from the URL. `replace` keeps filter tweaks off the
|
|
13
|
+
// back stack.
|
|
14
|
+
const onFiltersChange = (patch: Partial<TriggerEventFilters>) => {
|
|
15
|
+
void navigate({search: {...search, ...patch}, replace: true});
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<WorkspaceSettingsShell>
|
|
20
|
+
{(workspace) => (
|
|
21
|
+
<EventsPage workspaceId={workspace.id} filters={search} onFiltersChange={onFiltersChange} />
|
|
22
|
+
)}
|
|
23
|
+
</WorkspaceSettingsShell>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {configureApiClient} from '@shipfox/client-api';
|
|
2
|
+
import {screen} from '@testing-library/react';
|
|
3
|
+
import {
|
|
4
|
+
jsonResponse,
|
|
5
|
+
renderWorkspaceSettingsPage,
|
|
6
|
+
WORKSPACE_SETTINGS_TEST_WID,
|
|
7
|
+
} from '#test/pages.js';
|
|
8
|
+
import {IntegrationsSettingsPage} from './integrations-settings-page.js';
|
|
9
|
+
|
|
10
|
+
const providers = {
|
|
11
|
+
providers: [
|
|
12
|
+
{provider: 'sentry', display_name: 'Sentry', capabilities: []},
|
|
13
|
+
{provider: 'github', display_name: 'GitHub', capabilities: ['source_control']},
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const githubConnection = {
|
|
18
|
+
id: '44444444-4444-4444-8444-444444444444',
|
|
19
|
+
workspace_id: WORKSPACE_SETTINGS_TEST_WID,
|
|
20
|
+
provider: 'github',
|
|
21
|
+
external_account_id: 'installation-1',
|
|
22
|
+
slug: 'github_acme_corp',
|
|
23
|
+
display_name: 'acme-corp',
|
|
24
|
+
lifecycle_status: 'active',
|
|
25
|
+
capabilities: ['source_control'],
|
|
26
|
+
external_url: 'https://github.com/organizations/acme-corp/settings/installations/1',
|
|
27
|
+
created_at: '2026-03-12T00:00:00.000Z',
|
|
28
|
+
updated_at: '2026-03-12T00:00:00.000Z',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function fetchForIntegrations({connections = [githubConnection] as unknown[]} = {}) {
|
|
32
|
+
return vi.fn((input: RequestInfo | URL) => {
|
|
33
|
+
const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
|
|
34
|
+
if (url.includes('/integration-providers')) {
|
|
35
|
+
return Promise.resolve(jsonResponse(providers));
|
|
36
|
+
}
|
|
37
|
+
if (url.includes('/integration-connections')) {
|
|
38
|
+
return Promise.resolve(jsonResponse({connections}));
|
|
39
|
+
}
|
|
40
|
+
return Promise.resolve(jsonResponse({}, {status: 404}));
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function renderPage() {
|
|
45
|
+
return renderWorkspaceSettingsPage(
|
|
46
|
+
`/workspaces/${WORKSPACE_SETTINGS_TEST_WID}/settings/integrations`,
|
|
47
|
+
<IntegrationsSettingsPage />,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The provider is named once (icon + account name); the meta line carries only
|
|
52
|
+
// the date.
|
|
53
|
+
const ADDED_META_RE = /^Added /;
|
|
54
|
+
|
|
55
|
+
describe('IntegrationsSettingsPage', () => {
|
|
56
|
+
test('renders the settings shell and delegates to the integration gallery', async () => {
|
|
57
|
+
configureApiClient({
|
|
58
|
+
baseUrl: 'https://api.example.test',
|
|
59
|
+
fetchImpl: fetchForIntegrations(),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
renderPage();
|
|
63
|
+
|
|
64
|
+
expect(await screen.findByRole('region', {name: 'Installed integrations'})).toBeInTheDocument();
|
|
65
|
+
expect(screen.getByRole('region', {name: 'Available integrations'})).toBeInTheDocument();
|
|
66
|
+
expect(await screen.findByText('acme-corp')).toBeVisible();
|
|
67
|
+
expect(screen.queryByText('Connected')).not.toBeInTheDocument();
|
|
68
|
+
expect(screen.queryByText('Connect')).not.toBeInTheDocument();
|
|
69
|
+
expect(screen.getByRole('link', {name: 'Install GitHub'})).toBeVisible();
|
|
70
|
+
expect(screen.getByText(ADDED_META_RE)).toBeVisible();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import {IntegrationGallery} from '@shipfox/client-integrations';
|
|
2
|
+
import {WorkspaceSettingsShell} from '#components/workspace-settings-shell.js';
|
|
3
|
+
|
|
4
|
+
export function IntegrationsSettingsPage() {
|
|
5
|
+
return <WorkspaceSettingsShell>{() => <IntegrationGallery />}</WorkspaceSettingsShell>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {WorkspaceMembersSettingsSection} from '#components/members/workspace-members-section.js';
|
|
2
|
+
import {WorkspaceSettingsShell} from '#components/workspace-settings-shell.js';
|
|
3
|
+
|
|
4
|
+
export function MembersSettingsPage() {
|
|
5
|
+
return (
|
|
6
|
+
<WorkspaceSettingsShell>
|
|
7
|
+
{(workspace) => (
|
|
8
|
+
<div>
|
|
9
|
+
<WorkspaceMembersSettingsSection
|
|
10
|
+
workspaceId={workspace.id}
|
|
11
|
+
workspaceName={workspace.name}
|
|
12
|
+
/>
|
|
13
|
+
</div>
|
|
14
|
+
)}
|
|
15
|
+
</WorkspaceSettingsShell>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {configureApiClient} from '@shipfox/client-api';
|
|
2
|
+
import {screen} from '@testing-library/react';
|
|
3
|
+
import {
|
|
4
|
+
jsonResponse,
|
|
5
|
+
renderWorkspaceSettingsPage,
|
|
6
|
+
WORKSPACE_SETTINGS_TEST_WID,
|
|
7
|
+
} from '#test/pages.js';
|
|
8
|
+
import {ModelProvidersSettingsPage} from './model-providers-settings-page.js';
|
|
9
|
+
|
|
10
|
+
describe('ModelProvidersSettingsPage', () => {
|
|
11
|
+
test('renders the settings shell and model providers section', async () => {
|
|
12
|
+
const fetchImpl = vi.fn((input: RequestInfo | URL) => {
|
|
13
|
+
const url = new URL((input as Request).url);
|
|
14
|
+
if (url.pathname.endsWith('/agent/model-provider-catalog')) {
|
|
15
|
+
return Promise.resolve(
|
|
16
|
+
jsonResponse({
|
|
17
|
+
providers: [
|
|
18
|
+
{
|
|
19
|
+
id: 'anthropic',
|
|
20
|
+
label: 'Anthropic',
|
|
21
|
+
support_status: 'supported',
|
|
22
|
+
default_model: 'claude-opus-4-8',
|
|
23
|
+
credential_fields: [{key: 'api_key', label: 'API key', secret: true}],
|
|
24
|
+
unsupported_reason: null,
|
|
25
|
+
models: [{id: 'claude-opus-4-8', label: 'Claude Opus 4.8'}],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return Promise.resolve(
|
|
32
|
+
jsonResponse({configs: [], default_provider_id: null, default_harness_id: null}),
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
configureApiClient({baseUrl: 'https://api.example.test', fetchImpl});
|
|
36
|
+
|
|
37
|
+
renderWorkspaceSettingsPage(
|
|
38
|
+
`/workspaces/${WORKSPACE_SETTINGS_TEST_WID}/settings/agents`,
|
|
39
|
+
<ModelProvidersSettingsPage />,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(await screen.findByText('Harnesses')).toBeVisible();
|
|
43
|
+
expect(await screen.findByText('No providers configured')).toBeVisible();
|
|
44
|
+
expect(screen.getByText('Available providers')).toBeVisible();
|
|
45
|
+
expect(screen.getByRole('button', {name: 'Configure Anthropic'})).toBeVisible();
|
|
46
|
+
expect(screen.queryByText('Connect')).not.toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {WorkspaceHarnessesSection, WorkspaceModelProvidersSection} from '@shipfox/client-agent';
|
|
2
|
+
import {WorkspaceSettingsShell} from '#components/workspace-settings-shell.js';
|
|
3
|
+
|
|
4
|
+
export function ModelProvidersSettingsPage() {
|
|
5
|
+
return (
|
|
6
|
+
<WorkspaceSettingsShell>
|
|
7
|
+
{(workspace) => (
|
|
8
|
+
<div className="flex flex-col gap-32">
|
|
9
|
+
<WorkspaceHarnessesSection workspaceId={workspace.id} />
|
|
10
|
+
<WorkspaceModelProvidersSection workspaceId={workspace.id} />
|
|
11
|
+
</div>
|
|
12
|
+
)}
|
|
13
|
+
</WorkspaceSettingsShell>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {configureApiClient} from '@shipfox/client-api';
|
|
2
|
+
import {screen} from '@testing-library/react';
|
|
3
|
+
import {
|
|
4
|
+
jsonResponse,
|
|
5
|
+
renderWorkspaceSettingsPage,
|
|
6
|
+
WORKSPACE_SETTINGS_TEST_WID,
|
|
7
|
+
} from '#test/pages.js';
|
|
8
|
+
import {ProvisionersSettingsPage} from './provisioners-settings-page.js';
|
|
9
|
+
|
|
10
|
+
describe('ProvisionersSettingsPage', () => {
|
|
11
|
+
test('renders the settings shell and provisioner token section', async () => {
|
|
12
|
+
const fetchImpl = vi.fn((input: RequestInfo | URL) => {
|
|
13
|
+
const request = input as Request;
|
|
14
|
+
if (request.url.endsWith('/provisioners/active')) {
|
|
15
|
+
return Promise.resolve(jsonResponse({provisioners: []}));
|
|
16
|
+
}
|
|
17
|
+
return Promise.resolve(jsonResponse({tokens: []}));
|
|
18
|
+
});
|
|
19
|
+
configureApiClient({baseUrl: 'https://api.example.test', fetchImpl});
|
|
20
|
+
|
|
21
|
+
renderWorkspaceSettingsPage(
|
|
22
|
+
`/workspaces/${WORKSPACE_SETTINGS_TEST_WID}/settings/provisioners`,
|
|
23
|
+
<ProvisionersSettingsPage />,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(await screen.findByText('No usable provisioner registration tokens')).toBeVisible();
|
|
27
|
+
expect(screen.getByRole('button', {name: 'Create token'})).toBeVisible();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {WorkspaceProvisionerTokensSettingsSection} from '@shipfox/client-runners';
|
|
2
|
+
import {WorkspaceSettingsShell} from '#components/workspace-settings-shell.js';
|
|
3
|
+
|
|
4
|
+
export function ProvisionersSettingsPage() {
|
|
5
|
+
return (
|
|
6
|
+
<WorkspaceSettingsShell>
|
|
7
|
+
{(workspace) => (
|
|
8
|
+
<div>
|
|
9
|
+
<WorkspaceProvisionerTokensSettingsSection workspaceId={workspace.id} />
|
|
10
|
+
</div>
|
|
11
|
+
)}
|
|
12
|
+
</WorkspaceSettingsShell>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {configureApiClient} from '@shipfox/client-api';
|
|
2
|
+
import {screen} from '@testing-library/react';
|
|
3
|
+
import {
|
|
4
|
+
jsonResponse,
|
|
5
|
+
renderWorkspaceSettingsPage,
|
|
6
|
+
WORKSPACE_SETTINGS_TEST_WID,
|
|
7
|
+
} from '#test/pages.js';
|
|
8
|
+
import {RunnersSettingsPage} from './runners-settings-page.js';
|
|
9
|
+
|
|
10
|
+
describe('RunnersSettingsPage', () => {
|
|
11
|
+
test('renders the manual registration token section', async () => {
|
|
12
|
+
const fetchImpl = vi.fn().mockResolvedValue(jsonResponse({manual_registration_tokens: []}));
|
|
13
|
+
configureApiClient({baseUrl: 'https://api.example.test', fetchImpl});
|
|
14
|
+
|
|
15
|
+
renderWorkspaceSettingsPage(
|
|
16
|
+
`/workspaces/${WORKSPACE_SETTINGS_TEST_WID}/settings/runners`,
|
|
17
|
+
<RunnersSettingsPage />,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(await screen.findByText('No usable manual registration tokens')).toBeVisible();
|
|
21
|
+
expect(screen.getByRole('button', {name: 'Create token'})).toBeVisible();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {WorkspaceManualRegistrationTokensSettingsSection} from '@shipfox/client-runners';
|
|
2
|
+
import {WorkspaceSettingsShell} from '#components/workspace-settings-shell.js';
|
|
3
|
+
|
|
4
|
+
export function RunnersSettingsPage() {
|
|
5
|
+
return (
|
|
6
|
+
<WorkspaceSettingsShell>
|
|
7
|
+
{(workspace) => (
|
|
8
|
+
<div>
|
|
9
|
+
<WorkspaceManualRegistrationTokensSettingsSection workspaceId={workspace.id} />
|
|
10
|
+
</div>
|
|
11
|
+
)}
|
|
12
|
+
</WorkspaceSettingsShell>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {WorkspaceSecretsSection} from '@shipfox/client-secrets';
|
|
2
|
+
import {WorkspaceSettingsShell} from '#components/workspace-settings-shell.js';
|
|
3
|
+
|
|
4
|
+
export function SecretsSettingsPage() {
|
|
5
|
+
return (
|
|
6
|
+
<WorkspaceSettingsShell>
|
|
7
|
+
{(workspace) => (
|
|
8
|
+
<div>
|
|
9
|
+
<WorkspaceSecretsSection workspaceId={workspace.id} />
|
|
10
|
+
</div>
|
|
11
|
+
)}
|
|
12
|
+
</WorkspaceSettingsShell>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {WorkspaceVariablesSection} from '@shipfox/client-secrets';
|
|
2
|
+
import {WorkspaceSettingsShell} from '#components/workspace-settings-shell.js';
|
|
3
|
+
|
|
4
|
+
export function VariablesSettingsPage() {
|
|
5
|
+
return (
|
|
6
|
+
<WorkspaceSettingsShell>
|
|
7
|
+
{(workspace) => (
|
|
8
|
+
<div>
|
|
9
|
+
<WorkspaceVariablesSection workspaceId={workspace.id} />
|
|
10
|
+
</div>
|
|
11
|
+
)}
|
|
12
|
+
</WorkspaceSettingsShell>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import {defineRoute} from '@shipfox/client-shell/runtime';
|
|
2
|
+
import {validateTriggerEventsSearch} from '@shipfox/client-triggers';
|
|
3
|
+
import {EventsSettingsPage} from '#pages/events-settings-page.js';
|
|
4
|
+
export default defineRoute({
|
|
5
|
+
validateSearch: validateTriggerEventsSearch,
|
|
6
|
+
component: EventsSettingsPage,
|
|
7
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import {defineRoute} from '@shipfox/client-shell/runtime';
|
|
2
|
+
import {redirect} from '@tanstack/react-router';
|
|
3
|
+
|
|
4
|
+
export default defineRoute({
|
|
5
|
+
beforeLoad: ({params}: {params: {wid: string}}) => {
|
|
6
|
+
throw redirect({to: '/workspaces/$wid/settings/members', params: {wid: params.wid}});
|
|
7
|
+
},
|
|
8
|
+
});
|