From 11a9c0d98424fbbc67a337356eeb1d28dc046808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Bula=CC=81nek?= Date: Fri, 12 Dec 2025 11:27:13 +0100 Subject: [PATCH 1/2] feat(ui): add connector presets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Petr Bulánek --- .../src/modules/connectors/api/index.ts | 10 ++- .../src/modules/connectors/api/keys.ts | 1 + .../api/mutations/useCreateConnector.ts | 3 +- .../api/queries/useListConnectorPresets.ts | 18 ++++ .../src/modules/connectors/api/types.ts | 13 ++- .../AddConnectorFormFields.module.scss | 10 +++ .../components/AddConnectorFormFields.tsx | 56 ++++++++++++ .../components/AddConnectorModal.module.scss | 7 +- .../components/AddConnectorModal.tsx | 90 +++++++++---------- .../ConnectPresetButton.module.scss | 8 ++ .../components/ConnectPresetButton.tsx | 77 ++++++++++++++++ .../ConnectorPresetItem.module.scss | 40 +++++++++ .../components/ConnectorPresetItem.tsx | 39 ++++++++ .../ConnectorPresetsList.module.scss | 10 +++ .../components/ConnectorPresetsList.tsx | 35 ++++++++ .../connectors/components/ConnectorsView.tsx | 14 +-- 16 files changed, 370 insertions(+), 61 deletions(-) create mode 100644 apps/agentstack-ui/src/modules/connectors/api/queries/useListConnectorPresets.ts create mode 100644 apps/agentstack-ui/src/modules/connectors/components/AddConnectorFormFields.module.scss create mode 100644 apps/agentstack-ui/src/modules/connectors/components/AddConnectorFormFields.tsx create mode 100644 apps/agentstack-ui/src/modules/connectors/components/ConnectPresetButton.module.scss create mode 100644 apps/agentstack-ui/src/modules/connectors/components/ConnectPresetButton.tsx create mode 100644 apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetItem.module.scss create mode 100644 apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetItem.tsx create mode 100644 apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetsList.module.scss create mode 100644 apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetsList.tsx diff --git a/apps/agentstack-ui/src/modules/connectors/api/index.ts b/apps/agentstack-ui/src/modules/connectors/api/index.ts index 1d971bfea..1104a364a 100644 --- a/apps/agentstack-ui/src/modules/connectors/api/index.ts +++ b/apps/agentstack-ui/src/modules/connectors/api/index.ts @@ -20,6 +20,12 @@ export async function createConnector(body: CreateConnectorRequest) { return ensureData(response); } +export async function deleteConnector(path: DeleteConnectorPath) { + const response = await api.DELETE('/api/v1/connectors/{connector_id}', { params: { path } }); + + return ensureData(response); +} + export async function connectConnector(path: ConnectConnectorPath) { const response = await api.POST('/api/v1/connectors/{connector_id}/connect', { params: { path }, @@ -35,8 +41,8 @@ export async function disconnectConnector(path: DisconnectConnectorPath) { return ensureData(response); } -export async function deleteConnector(path: DeleteConnectorPath) { - const response = await api.DELETE('/api/v1/connectors/{connector_id}', { params: { path } }); +export async function listConnectorPresets() { + const response = await api.GET('/api/v1/connectors/presets'); return ensureData(response); } diff --git a/apps/agentstack-ui/src/modules/connectors/api/keys.ts b/apps/agentstack-ui/src/modules/connectors/api/keys.ts index b12da135f..82757a1e3 100644 --- a/apps/agentstack-ui/src/modules/connectors/api/keys.ts +++ b/apps/agentstack-ui/src/modules/connectors/api/keys.ts @@ -6,4 +6,5 @@ export const connectorKeys = { all: () => ['connectors'] as const, list: () => [...connectorKeys.all(), 'list'] as const, + presetsList: () => [...connectorKeys.all(), 'presets'] as const, }; diff --git a/apps/agentstack-ui/src/modules/connectors/api/mutations/useCreateConnector.ts b/apps/agentstack-ui/src/modules/connectors/api/mutations/useCreateConnector.ts index 3651b07ca..4f1da969e 100644 --- a/apps/agentstack-ui/src/modules/connectors/api/mutations/useCreateConnector.ts +++ b/apps/agentstack-ui/src/modules/connectors/api/mutations/useCreateConnector.ts @@ -7,9 +7,10 @@ import { useMutation } from '@tanstack/react-query'; import { createConnector } from '..'; import { connectorKeys } from '../keys'; +import type { Connector } from '../types'; interface Props { - onSuccess?: () => void; + onSuccess?: (connector: Connector | undefined) => void; } export function useCreateConnector({ onSuccess }: Props = {}) { diff --git a/apps/agentstack-ui/src/modules/connectors/api/queries/useListConnectorPresets.ts b/apps/agentstack-ui/src/modules/connectors/api/queries/useListConnectorPresets.ts new file mode 100644 index 000000000..145462846 --- /dev/null +++ b/apps/agentstack-ui/src/modules/connectors/api/queries/useListConnectorPresets.ts @@ -0,0 +1,18 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useQuery } from '@tanstack/react-query'; + +import { listConnectorPresets } from '..'; +import { connectorKeys } from '../keys'; + +export function useListConnectorPresets() { + const query = useQuery({ + queryKey: connectorKeys.presetsList(), + queryFn: listConnectorPresets, + }); + + return query; +} diff --git a/apps/agentstack-ui/src/modules/connectors/api/types.ts b/apps/agentstack-ui/src/modules/connectors/api/types.ts index 5c43f9446..30202a5ab 100644 --- a/apps/agentstack-ui/src/modules/connectors/api/types.ts +++ b/apps/agentstack-ui/src/modules/connectors/api/types.ts @@ -3,7 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { ApiPath, ApiRequest } from '#@types/utils.ts'; +import type { ApiPath, ApiRequest, ApiResponse } from '#@types/utils.ts'; + +export type { Connector, ListConnectorsResponse } from 'agentstack-sdk'; export type CreateConnectorRequest = ApiRequest<'/api/v1/connectors'>; @@ -13,4 +15,11 @@ export type DisconnectConnectorPath = ApiPath<'/api/v1/connectors/{connector_id} export type DeleteConnectorPath = ApiPath<'/api/v1/connectors/{connector_id}', 'delete'>; -export type { Connector, ListConnectorsResponse } from 'agentstack-sdk'; +export type ListConnectorPresetsResponse = ApiResponse<'/api/v1/connectors/presets'>; + +export type ConnectorPreset = Omit & { + metadata: { + name?: string; + description?: string; + } | null; +}; diff --git a/apps/agentstack-ui/src/modules/connectors/components/AddConnectorFormFields.module.scss b/apps/agentstack-ui/src/modules/connectors/components/AddConnectorFormFields.module.scss new file mode 100644 index 000000000..7c8dfee25 --- /dev/null +++ b/apps/agentstack-ui/src/modules/connectors/components/AddConnectorFormFields.module.scss @@ -0,0 +1,10 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +.root { + display: flex; + flex-direction: column; + row-gap: $spacing-05; +} diff --git a/apps/agentstack-ui/src/modules/connectors/components/AddConnectorFormFields.tsx b/apps/agentstack-ui/src/modules/connectors/components/AddConnectorFormFields.tsx new file mode 100644 index 000000000..2bb7dc536 --- /dev/null +++ b/apps/agentstack-ui/src/modules/connectors/components/AddConnectorFormFields.tsx @@ -0,0 +1,56 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { TextInput } from '@carbon/react'; +import { useId } from 'react'; +import { useFormContext } from 'react-hook-form'; + +import type { AddConnectorForm } from '../types'; +import classes from './AddConnectorFormFields.module.scss'; + +export function AddConnectorFormFields() { + const id = useId(); + + const { + register, + formState: { errors }, + } = useFormContext(); + + return ( +
+ + + + + + + +
+ ); +} diff --git a/apps/agentstack-ui/src/modules/connectors/components/AddConnectorModal.module.scss b/apps/agentstack-ui/src/modules/connectors/components/AddConnectorModal.module.scss index 6b479fa7d..158bdec14 100644 --- a/apps/agentstack-ui/src/modules/connectors/components/AddConnectorModal.module.scss +++ b/apps/agentstack-ui/src/modules/connectors/components/AddConnectorModal.module.scss @@ -3,8 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -.stack { +.body { + min-block-size: rem(300px); display: flex; flex-direction: column; row-gap: $spacing-05; } + +.toggleBtn { + padding-inline-end: rem(15px); +} diff --git a/apps/agentstack-ui/src/modules/connectors/components/AddConnectorModal.tsx b/apps/agentstack-ui/src/modules/connectors/components/AddConnectorModal.tsx index e9a62e555..b87ecd4c6 100644 --- a/apps/agentstack-ui/src/modules/connectors/components/AddConnectorModal.tsx +++ b/apps/agentstack-ui/src/modules/connectors/components/AddConnectorModal.tsx @@ -3,29 +3,27 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Button, InlineLoading, ModalBody, ModalFooter, ModalHeader, TextInput } from '@carbon/react'; +import { Button, InlineLoading, ModalBody, ModalFooter, ModalHeader } from '@carbon/react'; import { zodResolver } from '@hookform/resolvers/zod'; -import { useCallback, useId } from 'react'; +import { useCallback, useState } from 'react'; import type { SubmitHandler } from 'react-hook-form'; -import { useForm } from 'react-hook-form'; +import { FormProvider, useForm } from 'react-hook-form'; import { Modal } from '#components/Modal/Modal.tsx'; import type { ModalProps } from '#contexts/Modal/modal-context.ts'; import { useCreateConnector } from '../api/mutations/useCreateConnector'; import { type AddConnectorForm, addConnectorFormSchema } from '../types'; +import { AddConnectorFormFields } from './AddConnectorFormFields'; import classes from './AddConnectorModal.module.scss'; +import { ConnectorPresetsList } from './ConnectorPresetsList'; export function AddConnectorModal({ onRequestClose, ...modalProps }: ModalProps) { - const id = useId(); + const [view, setView] = useState(View.Browse); - const { mutate: createConnector, isPending } = useCreateConnector({ onSuccess: onRequestClose }); + const { mutate: createConnector, isPending } = useCreateConnector({ onSuccess: () => onRequestClose() }); - const { - register, - handleSubmit, - formState: { isValid, errors }, - } = useForm({ + const form = useForm({ mode: 'onChange', resolver: zodResolver(addConnectorFormSchema), }); @@ -43,6 +41,15 @@ export function AddConnectorModal({ onRequestClose, ...modalProps }: ModalProps) [createConnector], ); + const toggleView = useCallback(() => setView((view) => (view === View.Add ? View.Browse : View.Add)), []); + + const { + handleSubmit, + formState: { isValid }, + } = form; + + const isAddView = view === View.Add; + return ( onRequestClose()}> @@ -50,41 +57,21 @@ export function AddConnectorModal({ onRequestClose, ...modalProps }: ModalProps) -
-
- - - - - - - -
-
+
+ + + {isAddView ? ( + +
+ + +
+ ) : ( + + )} +
@@ -92,10 +79,17 @@ export function AddConnectorModal({ onRequestClose, ...modalProps }: ModalProps) Cancel - + {isAddView && ( + + )}
); } + +enum View { + Add = 'add', + Browse = 'browse', +} diff --git a/apps/agentstack-ui/src/modules/connectors/components/ConnectPresetButton.module.scss b/apps/agentstack-ui/src/modules/connectors/components/ConnectPresetButton.module.scss new file mode 100644 index 000000000..e50b60656 --- /dev/null +++ b/apps/agentstack-ui/src/modules/connectors/components/ConnectPresetButton.module.scss @@ -0,0 +1,8 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +.root { + padding-inline: rem(7px); +} diff --git a/apps/agentstack-ui/src/modules/connectors/components/ConnectPresetButton.tsx b/apps/agentstack-ui/src/modules/connectors/components/ConnectPresetButton.tsx new file mode 100644 index 000000000..33bf6c76d --- /dev/null +++ b/apps/agentstack-ui/src/modules/connectors/components/ConnectPresetButton.tsx @@ -0,0 +1,77 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Button, InlineLoading } from '@carbon/react'; +import clsx from 'clsx'; +import { useCallback, useMemo } from 'react'; + +import { useCreateConnector } from '../api/mutations/useCreateConnector'; +import { useListConnectors } from '../api/queries/useListConnectors'; +import type { ConnectorPreset } from '../api/types'; +import { useConnect, useDisconnect } from '../hooks/useConnectors'; +import classes from './ConnectPresetButton.module.scss'; + +interface Props { + preset: ConnectorPreset; + className: string; +} + +export function ConnectPresetButton({ preset, className }: Props) { + const { url } = preset; + + const { data: connectors, isPending: isConnectorsPending } = useListConnectors(); + const { mutate: createConnector, isPending: isCreatePending } = useCreateConnector({ + onSuccess: (connector) => { + if (!connector) { + return; + } + + connect(connector.id); + }, + }); + const { connect, isPending: isConnectPending } = useConnect(); + const { disconnect, isPending: isDisconnectPending } = useDisconnect(); + + const connector = useMemo( + () => connectors?.items.find((connector) => connector.url === url), + [connectors?.items, url], + ); + + const handleClick = useCallback(() => { + if (connector) { + if (connector.state === 'connected') { + disconnect(connector.id); + } else { + connect(connector.id); + } + } else { + createConnector({ url, match_preset: true }); + } + }, [connector, url, createConnector, connect, disconnect]); + + const isPending = isCreatePending || isConnectPending || isDisconnectPending; + + if (isConnectorsPending) { + return null; + } + + return ( + + ); +} diff --git a/apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetItem.module.scss b/apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetItem.module.scss new file mode 100644 index 000000000..6e23d3dea --- /dev/null +++ b/apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetItem.module.scss @@ -0,0 +1,40 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +.root { + display: grid; + grid-template-areas: + 'name connectBtn' + 'description description'; + grid-template-columns: 1fr auto; + column-gap: $spacing-03; + row-gap: $spacing-02; + border: 1px solid $border-subtle-00; + border-radius: $border-radius; + padding: $spacing-04; +} + +.name { + @include type-style(heading-02); + grid-area: name; + + &:global(:not(.cds--skeleton__text)) { + @include line-clamp(); + } +} + +.description { + @include type-style(body-01); + color: $text-secondary; + grid-area: description; + + &:global(:not(.cds--skeleton__text)) { + @include line-clamp(2); + } +} + +.connectBtn { + grid-area: connectBtn; +} diff --git a/apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetItem.tsx b/apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetItem.tsx new file mode 100644 index 000000000..b7ff21b26 --- /dev/null +++ b/apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetItem.tsx @@ -0,0 +1,39 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SkeletonText } from '@carbon/react'; + +import type { ConnectorPreset } from '../api/types'; +import classes from './ConnectorPresetItem.module.scss'; +import { ConnectPresetButton } from './ConnectPresetButton'; + +interface Props { + preset: ConnectorPreset; +} + +export function ConnectorPresetItem({ preset }: Props) { + const { url, metadata } = preset; + const { name, description } = metadata ?? {}; + + return ( +
+

{name ?? url}

+ + {description &&

{description}

} + + +
+ ); +} + +ConnectorPresetItem.Skeleton = function ConnectorPresetItemSkeleton() { + return ( +
+ + + +
+ ); +}; diff --git a/apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetsList.module.scss b/apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetsList.module.scss new file mode 100644 index 000000000..f2f749485 --- /dev/null +++ b/apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetsList.module.scss @@ -0,0 +1,10 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +.root { + display: flex; + flex-direction: column; + row-gap: $spacing-04; +} diff --git a/apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetsList.tsx b/apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetsList.tsx new file mode 100644 index 000000000..8013179d6 --- /dev/null +++ b/apps/agentstack-ui/src/modules/connectors/components/ConnectorPresetsList.tsx @@ -0,0 +1,35 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SkeletonItems } from '#components/SkeletonItems/SkeletonItems.tsx'; + +import { useListConnectorPresets } from '../api/queries/useListConnectorPresets'; +import { ConnectorPresetItem } from './ConnectorPresetItem'; +import classes from './ConnectorPresetsList.module.scss'; + +export function ConnectorPresetsList() { + const { data: presets, isPending } = useListConnectorPresets(); + + return ( +
    + {!isPending ? ( + presets?.items.map((preset) => ( +
  • + +
  • + )) + ) : ( + ( +
  • + +
  • + )} + /> + )} +
+ ); +} diff --git a/apps/agentstack-ui/src/modules/connectors/components/ConnectorsView.tsx b/apps/agentstack-ui/src/modules/connectors/components/ConnectorsView.tsx index a54a362e4..295ebfb94 100644 --- a/apps/agentstack-ui/src/modules/connectors/components/ConnectorsView.tsx +++ b/apps/agentstack-ui/src/modules/connectors/components/ConnectorsView.tsx @@ -19,7 +19,7 @@ import { DeleteConnectorButton } from './DeleteConnectorButton'; export function ConnectorsView() { const { openModal } = useModal(); - const { data, isPending } = useListConnectors(); + const { data: connectors, isPending } = useListConnectors(); const headers = useMemo( () => [ @@ -31,12 +31,12 @@ export function ConnectorsView() { ); const entries = useMemo(() => { - if (!data) { + if (!connectors) { return []; } - return data.items.map((item) => { - const { id, url, state } = item; + return connectors.items.map((connector) => { + const { id, url, state } = connector; return { id, @@ -44,14 +44,14 @@ export function ConnectorsView() { state, actions: ( - + - + ), }; }); - }, [data]); + }, [connectors]); return ( Date: Fri, 12 Dec 2025 14:19:27 +0100 Subject: [PATCH 2/2] feat(ui): add extended metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Petr Bulánek --- .../src/api/agentstack-client.ts | 12 +++++++++- .../src/modules/connectors/api/types.ts | 24 ++++++++++++++++++- .../components/ConnectorsView.module.scss | 6 ++++- .../connectors/components/ConnectorsView.tsx | 4 +++- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/apps/agentstack-ui/src/api/agentstack-client.ts b/apps/agentstack-ui/src/api/agentstack-client.ts index 2e305301d..dde68b642 100644 --- a/apps/agentstack-ui/src/api/agentstack-client.ts +++ b/apps/agentstack-ui/src/api/agentstack-client.ts @@ -7,6 +7,7 @@ import { buildApiClient } from 'agentstack-sdk'; import { ensureToken } from '#app/(auth)/rsc.tsx'; import { runtimeConfig } from '#contexts/App/runtime-config.ts'; +import { listConnectorsResponseSchema } from '#modules/connectors/api/types.ts'; import { getBaseUrl } from '#utils/api/getBaseUrl.ts'; import { getProxyHeaders, handleFailedResponse } from './utils'; @@ -50,4 +51,13 @@ function buildAuthenticatedAgentstackClient() { return client; } -export const agentstackClient = buildAuthenticatedAgentstackClient(); +const baseAgentstackClient = buildAuthenticatedAgentstackClient(); + +export const agentstackClient = { + ...baseAgentstackClient, + listConnectors: async () => { + const response = await baseAgentstackClient.listConnectors(); + + return listConnectorsResponseSchema.parse(response); + }, +}; diff --git a/apps/agentstack-ui/src/modules/connectors/api/types.ts b/apps/agentstack-ui/src/modules/connectors/api/types.ts index 30202a5ab..15c07fbc2 100644 --- a/apps/agentstack-ui/src/modules/connectors/api/types.ts +++ b/apps/agentstack-ui/src/modules/connectors/api/types.ts @@ -3,9 +3,31 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { + connectorSchema as sdkConnectorSchema, + listConnectorsResponseSchema as sdkListConnectorsResponseSchema, +} from 'agentstack-sdk'; +import z from 'zod'; + import type { ApiPath, ApiRequest, ApiResponse } from '#@types/utils.ts'; -export type { Connector, ListConnectorsResponse } from 'agentstack-sdk'; +const connectorMetadataSchema = z + .object({ + name: z.string().optional(), + }) + .nullable(); + +export const connectorSchema = sdkConnectorSchema.extend({ + metadata: connectorMetadataSchema, +}); + +export const listConnectorsResponseSchema = sdkListConnectorsResponseSchema.extend({ + items: z.array(connectorSchema), +}); + +export type Connector = z.infer; + +export type ListConnectorsResponse = z.infer; export type CreateConnectorRequest = ApiRequest<'/api/v1/connectors'>; diff --git a/apps/agentstack-ui/src/modules/connectors/components/ConnectorsView.module.scss b/apps/agentstack-ui/src/modules/connectors/components/ConnectorsView.module.scss index b04884023..0ed9d7e82 100644 --- a/apps/agentstack-ui/src/modules/connectors/components/ConnectorsView.module.scss +++ b/apps/agentstack-ui/src/modules/connectors/components/ConnectorsView.module.scss @@ -9,11 +9,14 @@ th { &:nth-child(1) { - inline-size: 60%; + inline-size: 40%; } &:nth-child(2) { inline-size: 30%; } + &:nth-child(3) { + inline-size: 20%; + } } &:global(.cds--skeleton) { @@ -24,6 +27,7 @@ } } +.name, .url { @at-root td#{&} { overflow-wrap: anywhere; diff --git a/apps/agentstack-ui/src/modules/connectors/components/ConnectorsView.tsx b/apps/agentstack-ui/src/modules/connectors/components/ConnectorsView.tsx index 295ebfb94..5ccf6ad78 100644 --- a/apps/agentstack-ui/src/modules/connectors/components/ConnectorsView.tsx +++ b/apps/agentstack-ui/src/modules/connectors/components/ConnectorsView.tsx @@ -24,6 +24,7 @@ export function ConnectorsView() { const headers = useMemo( () => [ { key: 'url', header: 'URL', className: classes.url }, + { key: 'name', header: 'Name', className: classes.name }, { key: 'state', header: 'Status' }, { key: 'actions', header: '' }, ], @@ -36,10 +37,11 @@ export function ConnectorsView() { } return connectors.items.map((connector) => { - const { id, url, state } = connector; + const { id, url, state, metadata } = connector; return { id, + name: metadata?.name ?? '', url, state, actions: (