Skip to content

Commit eaa37db

Browse files
authored
feat(ui): add temporary ui for connectors (#1702)
1 parent dd0ecb2 commit eaa37db

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2876
-2396
lines changed

apps/agentstack-ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@carbon/react": "catalog:",
2020
"@carbon/styles": "^1.96.0",
2121
"@floating-ui/react": "^0.27.16",
22+
"@hookform/resolvers": "^5.2.2",
2223
"@ibm/plex": "^6.4.1",
2324
"@radix-ui/react-slot": "^1.2.4",
2425
"@tanstack/react-query": "catalog:",

apps/agentstack-ui/src/modules/providers/components/DeleteProviderButton.module.scss renamed to apps/agentstack-ui/src/components/DeleteButton/DeleteButton.module.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
.source {
6+
.name {
77
overflow-wrap: anywhere;
88
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Copyright 2025 © BeeAI a Series of LF Projects, LLC
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { TrashCan } from '@carbon/icons-react';
7+
import { IconButton } from '@carbon/react';
8+
9+
import { Spinner } from '#components/Spinner/Spinner.tsx';
10+
import { useModal } from '#contexts/Modal/index.tsx';
11+
12+
import classes from './DeleteButton.module.scss';
13+
14+
interface Props {
15+
entityName: string;
16+
entityLabel: string;
17+
isPending: boolean;
18+
onSubmit: () => void;
19+
}
20+
21+
export function DeleteButton({ entityName, entityLabel, isPending, onSubmit }: Props) {
22+
const { openConfirmation } = useModal();
23+
24+
return (
25+
<IconButton
26+
label="Delete"
27+
kind="ghost"
28+
size="sm"
29+
align="left"
30+
onClick={() =>
31+
openConfirmation({
32+
title: (
33+
<>
34+
Delete <span className={classes.name}>{entityName}</span>?
35+
</>
36+
),
37+
body: `Are you sure you want to delete this ${entityLabel}? It can’t be undone.`,
38+
primaryButtonText: 'Delete',
39+
danger: true,
40+
onSubmit,
41+
})
42+
}
43+
disabled={isPending}
44+
>
45+
{isPending ? <Spinner center /> : <TrashCan />}
46+
</IconButton>
47+
);
48+
}

apps/agentstack-ui/src/components/TableView/TableView.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6+
import clsx from 'clsx';
67
import type { PropsWithChildren } from 'react';
78

89
import classes from './TableView.module.scss';
910

1011
interface Props {
1112
description?: string;
13+
className?: string;
1214
}
1315

14-
export function TableView({ description, children }: PropsWithChildren<Props>) {
16+
export function TableView({ description, className, children }: PropsWithChildren<Props>) {
1517
return (
16-
<div className={classes.root}>
18+
<div className={clsx(classes.root, className)}>
1719
{description && <p className={classes.description}>{description}</p>}
1820

1921
<div className={classes.table}>{children}</div>

apps/agentstack-ui/src/components/TableView/TableViewActions.module.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@
55

66
.root {
77
margin: -$spacing-03;
8+
display: flex;
9+
justify-content: flex-end;
810
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* Copyright 2025 © BeeAI a Series of LF Projects, LLC
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import type { DataTableHeader } from '@carbon/react';
7+
import {
8+
DataTable,
9+
DataTableSkeleton,
10+
Table,
11+
TableBody,
12+
TableCell,
13+
TableHead,
14+
TableHeader,
15+
TableRow,
16+
} from '@carbon/react';
17+
import type { ReactNode } from 'react';
18+
19+
import { useTableSearch } from '#hooks/useTableSearch.ts';
20+
21+
import { TableView } from './TableView.tsx';
22+
import { TableViewToolbar } from './TableViewToolbar.tsx';
23+
24+
interface Props<T> {
25+
headers: (DataTableHeader & { className?: string })[];
26+
entries: T[];
27+
searchFields: (keyof T)[];
28+
toolbarButton: ReactNode;
29+
isPending: boolean;
30+
description?: string;
31+
emptyText?: ReactNode;
32+
className?: string;
33+
}
34+
35+
export function TableViewWithSearch<T extends { id: string }>({
36+
headers,
37+
entries,
38+
searchFields,
39+
toolbarButton,
40+
isPending,
41+
description,
42+
emptyText = 'No results found.',
43+
className,
44+
}: Props<T>) {
45+
const { items: rows, onSearch } = useTableSearch({ entries, fields: searchFields });
46+
47+
const columnCount = headers.length;
48+
const hasRows = rows.length > 0;
49+
50+
return (
51+
<TableView description={description} className={className}>
52+
<DataTable headers={headers} rows={rows}>
53+
{({ rows, getTableProps, getHeaderProps, getRowProps }) => (
54+
<>
55+
<TableViewToolbar searchProps={{ onChange: onSearch, disabled: isPending }} button={toolbarButton} />
56+
57+
{isPending ? (
58+
<DataTableSkeleton headers={headers} columnCount={columnCount} showToolbar={false} showHeader={false} />
59+
) : (
60+
<Table {...getTableProps()}>
61+
<TableHead>
62+
<TableRow>
63+
{headers.map((header) => (
64+
<TableHeader {...getHeaderProps({ header })} key={header.key} className={header.className}>
65+
{header.header}
66+
</TableHeader>
67+
))}
68+
</TableRow>
69+
</TableHead>
70+
71+
<TableBody>
72+
{hasRows ? (
73+
rows.map((row) => (
74+
<TableRow {...getRowProps({ row })} key={row.id}>
75+
{row.cells.map((cell) => {
76+
const header = headers.find(({ key }) => key === cell.info.header);
77+
78+
return (
79+
<TableCell key={cell.id} className={header?.className}>
80+
{cell.value}
81+
</TableCell>
82+
);
83+
})}
84+
</TableRow>
85+
))
86+
) : (
87+
<TableRow>
88+
<TableCell colSpan={columnCount}>{emptyText}</TableCell>
89+
</TableRow>
90+
)}
91+
</TableBody>
92+
</Table>
93+
)}
94+
</>
95+
)}
96+
</DataTable>
97+
</TableView>
98+
);
99+
}

apps/agentstack-ui/src/modules/agents/components/detail/AgentSecrets.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { NoItemsMessage } from '#components/NoItemsMessage/NoItemsMessage.tsx';
77
import { useAgentSecrets } from '#modules/runs/contexts/agent-secrets/index.ts';
8-
import { SecretCard } from '#modules/runs/secrets/SecretCard.tsx';
8+
import { SecretCard } from '#modules/runs/secrets/components/SecretCard.tsx';
99

1010
import classes from './AgentSecrets.module.scss';
1111

apps/agentstack-ui/src/modules/connectors/api/index.ts

Lines changed: 19 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,70 +7,42 @@ import { api } from '#api/index.ts';
77
import { ensureData } from '#api/utils.ts';
88
import { BASE_URL } from '#utils/constants.ts';
99

10-
import type { CreateConnectorRequest, ListConnectorsResponse } from './types';
10+
import type {
11+
ConnectConnectorPath,
12+
CreateConnectorRequest,
13+
DeleteConnectorPath,
14+
DisconnectConnectorPath,
15+
} from './types';
1116

1217
export async function createConnector(body: CreateConnectorRequest) {
1318
const response = await api.POST('/api/v1/connectors', { body });
14-
return ensureData(response);
15-
}
1619

17-
export async function deleteConnector(connectorId: string) {
18-
const response = await api.DELETE('/api/v1/connectors/{connector_id}', {
19-
params: { path: { connector_id: connectorId } },
20-
});
21-
return ensureData(response);
22-
}
23-
24-
export async function disconnectConnector(connectorId: string) {
25-
const response = await api.POST('/api/v1/connectors/{connector_id}/disconnect', {
26-
params: { path: { connector_id: connectorId } },
27-
});
2820
return ensureData(response);
2921
}
3022

31-
export async function connectConnector(connectorId: string) {
23+
export async function connectConnector(path: ConnectConnectorPath) {
3224
const response = await api.POST('/api/v1/connectors/{connector_id}/connect', {
33-
params: { path: { connector_id: connectorId } },
34-
body: {
35-
redirect_url: `${BASE_URL}/oauth-callback`,
36-
},
25+
params: { path },
26+
body: { redirect_url: `${BASE_URL}/oauth-callback` },
3727
});
3828

39-
return ensureData(response) as AuthRequired;
29+
return ensureData(response);
4030
}
4131

42-
interface AuthRequired {
43-
id: string;
44-
url: string;
45-
state: 'auth_required';
46-
auth_request: {
47-
authorization_endpoint: string;
48-
type: string;
49-
};
50-
}
32+
export async function disconnectConnector(path: DisconnectConnectorPath) {
33+
const response = await api.POST('/api/v1/connectors/{connector_id}/disconnect', { params: { path } });
5134

52-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
53-
interface Connected {
54-
id: string;
55-
url: string;
56-
state: 'connected';
35+
return ensureData(response);
5736
}
5837

59-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
60-
interface Disconnected {
61-
id: string;
62-
url: string;
63-
state: 'disconnected';
64-
}
38+
export async function deleteConnector(path: DeleteConnectorPath) {
39+
const response = await api.DELETE('/api/v1/connectors/{connector_id}', { params: { path } });
6540

66-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
67-
interface Created {
68-
id: string;
69-
url: string;
70-
state: 'created';
41+
return ensureData(response);
7142
}
7243

73-
export async function listConnectors(): Promise<ListConnectorsResponse | undefined> {
74-
const response = await api.GET('/api/v1/connectors', {});
44+
export async function listConnectors() {
45+
const response = await api.GET('/api/v1/connectors');
46+
7547
return ensureData(response);
7648
}

apps/agentstack-ui/src/modules/connectors/api/keys.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
*/
55

66
export const connectorKeys = {
7-
all: () => ['oauth-connectors'] as const,
7+
all: () => ['connectors'] as const,
88
list: () => [...connectorKeys.all(), 'list'] as const,
99
};

apps/agentstack-ui/src/modules/connectors/api/mutations/useConnectConnector.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
import { useMutation } from '@tanstack/react-query';
77

88
import { connectConnector } from '..';
9+
import { connectorKeys } from '../keys';
910

1011
export function useConnectConnector() {
1112
const mutation = useMutation({
1213
mutationFn: connectConnector,
1314
meta: {
15+
invalidates: [connectorKeys.list()],
1416
errorToast: {
1517
title: 'Failed to connect service.',
1618
includeErrorMessage: true,

0 commit comments

Comments
 (0)