Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/agentstack-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@carbon/react": "catalog:",
"@carbon/styles": "^1.96.0",
"@floating-ui/react": "^0.27.16",
"@hookform/resolvers": "^5.2.2",
"@ibm/plex": "^6.4.1",
"@radix-ui/react-slot": "^1.2.4",
"@tanstack/react-query": "catalog:",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

.source {
.name {
overflow-wrap: anywhere;
}
48 changes: 48 additions & 0 deletions apps/agentstack-ui/src/components/DeleteButton/DeleteButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright 2025 © BeeAI a Series of LF Projects, LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { TrashCan } from '@carbon/icons-react';
import { IconButton } from '@carbon/react';

import { Spinner } from '#components/Spinner/Spinner.tsx';
import { useModal } from '#contexts/Modal/index.tsx';

import classes from './DeleteButton.module.scss';

interface Props {
entityName: string;
entityLabel: string;
isPending: boolean;
onSubmit: () => void;
}

export function DeleteButton({ entityName, entityLabel, isPending, onSubmit }: Props) {
const { openConfirmation } = useModal();

return (
<IconButton
label="Delete"
kind="ghost"
size="sm"
align="left"
onClick={() =>
openConfirmation({
title: (
<>
Delete <span className={classes.name}>{entityName}</span>?
</>
),
body: `Are you sure you want to delete this ${entityLabel}? It can’t be undone.`,
primaryButtonText: 'Delete',
danger: true,
onSubmit,
})
}
disabled={isPending}
>
{isPending ? <Spinner center /> : <TrashCan />}
</IconButton>
);
}
6 changes: 4 additions & 2 deletions apps/agentstack-ui/src/components/TableView/TableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
* SPDX-License-Identifier: Apache-2.0
*/

import clsx from 'clsx';
import type { PropsWithChildren } from 'react';

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

interface Props {
description?: string;
className?: string;
}

export function TableView({ description, children }: PropsWithChildren<Props>) {
export function TableView({ description, className, children }: PropsWithChildren<Props>) {
return (
<div className={classes.root}>
<div className={clsx(classes.root, className)}>
{description && <p className={classes.description}>{description}</p>}

<div className={classes.table}>{children}</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@

.root {
margin: -$spacing-03;
display: flex;
justify-content: flex-end;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙌

Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* Copyright 2025 © BeeAI a Series of LF Projects, LLC
* SPDX-License-Identifier: Apache-2.0
*/

import type { DataTableHeader } from '@carbon/react';
import {
DataTable,
DataTableSkeleton,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@carbon/react';
import type { ReactNode } from 'react';

import { useTableSearch } from '#hooks/useTableSearch.ts';

import { TableView } from './TableView.tsx';
import { TableViewToolbar } from './TableViewToolbar.tsx';

interface Props<T> {
headers: (DataTableHeader & { className?: string })[];
entries: T[];
searchFields: (keyof T)[];
toolbarButton: ReactNode;
isPending: boolean;
description?: string;
emptyText?: ReactNode;
className?: string;
}

export function TableViewWithSearch<T extends { id: string }>({
headers,
entries,
searchFields,
toolbarButton,
isPending,
description,
emptyText = 'No results found.',
className,
}: Props<T>) {
const { items: rows, onSearch } = useTableSearch({ entries, fields: searchFields });

const columnCount = headers.length;
const hasRows = rows.length > 0;

return (
<TableView description={description} className={className}>
<DataTable headers={headers} rows={rows}>
{({ rows, getTableProps, getHeaderProps, getRowProps }) => (
<>
<TableViewToolbar searchProps={{ onChange: onSearch, disabled: isPending }} button={toolbarButton} />

{isPending ? (
<DataTableSkeleton headers={headers} columnCount={columnCount} showToolbar={false} showHeader={false} />
) : (
<Table {...getTableProps()}>
<TableHead>
<TableRow>
{headers.map((header) => (
<TableHeader {...getHeaderProps({ header })} key={header.key} className={header.className}>
{header.header}
</TableHeader>
))}
</TableRow>
</TableHead>

<TableBody>
{hasRows ? (
rows.map((row) => (
<TableRow {...getRowProps({ row })} key={row.id}>
{row.cells.map((cell) => {
const header = headers.find(({ key }) => key === cell.info.header);

return (
<TableCell key={cell.id} className={header?.className}>
{cell.value}
</TableCell>
);
})}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columnCount}>{emptyText}</TableCell>
</TableRow>
)}
</TableBody>
</Table>
)}
</>
)}
</DataTable>
</TableView>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

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

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

Expand Down
66 changes: 19 additions & 47 deletions apps/agentstack-ui/src/modules/connectors/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,70 +7,42 @@ import { api } from '#api/index.ts';
import { ensureData } from '#api/utils.ts';
import { BASE_URL } from '#utils/constants.ts';

import type { CreateConnectorRequest, ListConnectorsResponse } from './types';
import type {
ConnectConnectorPath,
CreateConnectorRequest,
DeleteConnectorPath,
DisconnectConnectorPath,
} from './types';

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

export async function deleteConnector(connectorId: string) {
const response = await api.DELETE('/api/v1/connectors/{connector_id}', {
params: { path: { connector_id: connectorId } },
});
return ensureData(response);
}

export async function disconnectConnector(connectorId: string) {
const response = await api.POST('/api/v1/connectors/{connector_id}/disconnect', {
params: { path: { connector_id: connectorId } },
});
return ensureData(response);
}

export async function connectConnector(connectorId: string) {
export async function connectConnector(path: ConnectConnectorPath) {
const response = await api.POST('/api/v1/connectors/{connector_id}/connect', {
params: { path: { connector_id: connectorId } },
body: {
redirect_url: `${BASE_URL}/oauth-callback`,
},
params: { path },
body: { redirect_url: `${BASE_URL}/oauth-callback` },
});

return ensureData(response) as AuthRequired;
return ensureData(response);
}

interface AuthRequired {
id: string;
url: string;
state: 'auth_required';
auth_request: {
authorization_endpoint: string;
type: string;
};
}
export async function disconnectConnector(path: DisconnectConnectorPath) {
const response = await api.POST('/api/v1/connectors/{connector_id}/disconnect', { params: { path } });

// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Connected {
id: string;
url: string;
state: 'connected';
return ensureData(response);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Disconnected {
id: string;
url: string;
state: 'disconnected';
}
export async function deleteConnector(path: DeleteConnectorPath) {
const response = await api.DELETE('/api/v1/connectors/{connector_id}', { params: { path } });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to implement all api calls in the SDK and use them from there from now on. But we can leave that for #1692 .


// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Created {
id: string;
url: string;
state: 'created';
return ensureData(response);
}

export async function listConnectors(): Promise<ListConnectorsResponse | undefined> {
const response = await api.GET('/api/v1/connectors', {});
export async function listConnectors() {
const response = await api.GET('/api/v1/connectors');

return ensureData(response);
}
2 changes: 1 addition & 1 deletion apps/agentstack-ui/src/modules/connectors/api/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
*/

export const connectorKeys = {
all: () => ['oauth-connectors'] as const,
all: () => ['connectors'] as const,
list: () => [...connectorKeys.all(), 'list'] as const,
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
import { useMutation } from '@tanstack/react-query';

import { connectConnector } from '..';
import { connectorKeys } from '../keys';

export function useConnectConnector() {
const mutation = useMutation({
mutationFn: connectConnector,
meta: {
invalidates: [connectorKeys.list()],
errorToast: {
title: 'Failed to connect service.',
includeErrorMessage: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ import { useMutation } from '@tanstack/react-query';
import { createConnector } from '..';
import { connectorKeys } from '../keys';

export function useCreateConnector() {
interface Props {
onSuccess?: () => void;
}

export function useCreateConnector({ onSuccess }: Props = {}) {
const mutation = useMutation({
mutationFn: createConnector,
onSuccess,
meta: {
invalidates: [connectorKeys.list()],
errorToast: {
Expand Down
13 changes: 11 additions & 2 deletions apps/agentstack-ui/src/modules/connectors/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
* SPDX-License-Identifier: Apache-2.0
*/

import type { ApiRequest, ApiResponse } from '#@types/utils.ts';
import type { ApiPath, ApiRequest, ApiResponse } from '#@types/utils.ts';

export type CreateConnectorRequest = ApiRequest<'/api/v1/connectors'>;
export type ListConnectorsResponse = ApiResponse<'/api/v1/connectors', 'get', 'application/json', 200>;

export type ConnectConnectorPath = ApiPath<'/api/v1/connectors/{connector_id}/connect', 'post'>;

export type DisconnectConnectorPath = ApiPath<'/api/v1/connectors/{connector_id}/disconnect', 'post'>;

export type DeleteConnectorPath = ApiPath<'/api/v1/connectors/{connector_id}', 'delete'>;

export type ListConnectorsResponse = ApiResponse<'/api/v1/connectors'>;

export type Connector = ListConnectorsResponse['items'][number];
Loading