diff --git a/ui/src/components/sidebar-utils.ts b/ui/src/components/sidebar-utils.ts index 969b45b5..abfbe32d 100644 --- a/ui/src/components/sidebar-utils.ts +++ b/ui/src/components/sidebar-utils.ts @@ -107,6 +107,7 @@ export function buildAdminTreeNodes(opts?: { infrastructureEndpoints?: boolean } ...(showInfra ? [{ id: 'admin:database', label: 'Database', path: '/admin/database' }] : []), { id: 'admin:environments', label: 'Environments', path: '/admin/environments' }, { id: 'admin:oidc', label: 'OIDC', path: '/admin/oidc' }, + { id: 'admin:outbound-connections', label: 'Outbound Connections', path: '/admin/outbound-connections' }, { id: 'admin:sensitive-keys', label: 'Sensitive Keys', path: '/admin/sensitive-keys' }, { id: 'admin:rbac', label: 'Users & Roles', path: '/admin/rbac' }, ]; diff --git a/ui/src/pages/Admin/OutboundConnectionsPage.tsx b/ui/src/pages/Admin/OutboundConnectionsPage.tsx new file mode 100644 index 00000000..03ec73f7 --- /dev/null +++ b/ui/src/pages/Admin/OutboundConnectionsPage.tsx @@ -0,0 +1,87 @@ +import { Link } from 'react-router-dom'; +import { Button, Badge, SectionHeader, useToast } from '@cameleer/design-system'; +import { PageLoader } from '../../components/PageLoader'; +import { + useOutboundConnections, + useDeleteOutboundConnection, + type OutboundConnectionDto, + type TrustMode, +} from '../../api/queries/admin/outboundConnections'; +import sectionStyles from '../../styles/section-card.module.css'; + +export default function OutboundConnectionsPage() { + const { data, isLoading, error } = useOutboundConnections(); + const deleteMut = useDeleteOutboundConnection(); + const { toast } = useToast(); + + if (isLoading) return ; + if (error) return
Failed to load outbound connections: {String(error)}
; + + const rows = data ?? []; + + const onDelete = (c: OutboundConnectionDto) => { + if (!confirm(`Delete outbound connection "${c.name}"?`)) return; + deleteMut.mutate(c.id, { + onSuccess: () => toast({ title: 'Deleted', description: c.name, variant: 'success' }), + onError: (e) => toast({ title: 'Delete failed', description: String(e), variant: 'error' }), + }); + }; + + return ( +
+
+ Outbound Connections + + + +
+
+ {rows.length === 0 ? ( +

No outbound connections yet. Create one to enable alerting webhooks or other outbound integrations.

+ ) : ( + + + + + + + + + + + + + + {rows.map((c) => ( + + + + + + + + + + ))} + +
NameHostMethodTrustAuthEnvs
{c.name}{safeHost(c.url)}{c.method}{c.authKind}{c.allowedEnvironmentIds.length > 0 ? c.allowedEnvironmentIds.length : 'all'} + +
+ )} +
+
+ ); +} + +function safeHost(url: string): string { + try { return new URL(url).host; } + catch { return url; } +} + +function TrustBadge({ mode }: { mode: TrustMode }) { + if (mode === 'TRUST_ALL') return ; + if (mode === 'TRUST_PATHS') return ; + return ; +} diff --git a/ui/src/router.tsx b/ui/src/router.tsx index 4cddd9fa..7b2e8ed9 100644 --- a/ui/src/router.tsx +++ b/ui/src/router.tsx @@ -18,6 +18,7 @@ const OidcConfigPage = lazy(() => import('./pages/Admin/OidcConfigPage')); const DatabaseAdminPage = lazy(() => import('./pages/Admin/DatabaseAdminPage')); const ClickHouseAdminPage = lazy(() => import('./pages/Admin/ClickHouseAdminPage')); const EnvironmentsPage = lazy(() => import('./pages/Admin/EnvironmentsPage')); +const OutboundConnectionsPage = lazy(() => import('./pages/Admin/OutboundConnectionsPage')); const SensitiveKeysPage = lazy(() => import('./pages/Admin/SensitiveKeysPage')); const AppsTab = lazy(() => import('./pages/AppsTab/AppsTab')); const SwaggerPage = lazy(() => import('./pages/Swagger/SwaggerPage')); @@ -84,6 +85,7 @@ export const router = createBrowserRouter([ { path: 'rbac', element: }, { path: 'audit', element: }, { path: 'oidc', element: }, + { path: 'outbound-connections', element: }, { path: 'sensitive-keys', element: }, { path: 'database', element: }, { path: 'clickhouse', element: },