@agent-native/dispatch 0.31.10 → 0.31.11
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/dist/components/app-list-row.d.ts +1 -1
- package/dist/components/app-list-row.d.ts.map +1 -1
- package/dist/components/app-list-row.js +1 -1
- package/dist/components/app-list-row.js.map +1 -1
- package/dist/components/other-apps-section.d.ts.map +1 -1
- package/dist/components/other-apps-section.js +2 -1
- package/dist/components/other-apps-section.js.map +1 -1
- package/dist/routes/pages/admin.d.ts.map +1 -1
- package/dist/routes/pages/admin.js +3 -1
- package/dist/routes/pages/admin.js.map +1 -1
- package/dist/routes/pages/agent-integrations.d.ts.map +1 -1
- package/dist/routes/pages/agent-integrations.js +6 -8
- package/dist/routes/pages/agent-integrations.js.map +1 -1
- package/dist/routes/pages/integrations.d.ts +1 -1
- package/dist/routes/pages/integrations.d.ts.map +1 -1
- package/dist/routes/pages/integrations.js +7 -190
- package/dist/routes/pages/integrations.js.map +1 -1
- package/package.json +2 -2
- package/src/components/app-list-row.tsx +2 -1
- package/src/components/other-apps-section.tsx +23 -17
- package/src/routes/pages/admin.tsx +4 -1
- package/src/routes/pages/agent-integrations.tsx +15 -8
- package/src/routes/pages/integrations.tsx +17 -527
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
export declare const APP_LIST_GRID_CLASS = "w-full xl:grid xl:grid-cols-2 xl:gap-3";
|
|
2
|
+
export declare const APP_LIST_GRID_CLASS = "w-full xl:grid xl:grid-cols-2 xl:gap-3 xl:overflow-visible xl:rounded-none xl:bg-transparent";
|
|
3
3
|
export declare const APP_LIST_GRID_ROW_CLASS = "xl:rounded-2xl xl:border xl:border-transparent xl:bg-card xl:transition-[background-color,border-color] xl:hover:border-border xl:focus-within:border-ring/50";
|
|
4
4
|
export declare function AppList({ children, className, }: {
|
|
5
5
|
children: ReactNode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-list-row.d.ts","sourceRoot":"","sources":["../../src/components/app-list-row.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,eAAO,MAAM,mBAAmB,
|
|
1
|
+
{"version":3,"file":"app-list-row.d.ts","sourceRoot":"","sources":["../../src/components/app-list-row.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,eAAO,MAAM,mBAAmB,iGACgE,CAAC;AACjG,eAAO,MAAM,uBAAuB,kKAC6H,CAAC;AAElK,wBAAgB,OAAO,CAAC,EACtB,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,mDAMA;AAED,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,mDAWA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
|
-
export const APP_LIST_GRID_CLASS = "w-full xl:grid xl:grid-cols-2 xl:gap-3";
|
|
3
|
+
export const APP_LIST_GRID_CLASS = "w-full xl:grid xl:grid-cols-2 xl:gap-3 xl:overflow-visible xl:rounded-none xl:bg-transparent";
|
|
4
4
|
export const APP_LIST_GRID_ROW_CLASS = "xl:rounded-2xl xl:border xl:border-transparent xl:bg-card xl:transition-[background-color,border-color] xl:hover:border-border xl:focus-within:border-ring/50";
|
|
5
5
|
export function AppList({ children, className, }) {
|
|
6
6
|
return (_jsx("div", { className: cn("overflow-hidden rounded-2xl bg-card", className), children: children }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-list-row.js","sourceRoot":"","sources":["../../src/components/app-list-row.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAElC,MAAM,CAAC,MAAM,mBAAmB,
|
|
1
|
+
{"version":3,"file":"app-list-row.js","sourceRoot":"","sources":["../../src/components/app-list-row.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAElC,MAAM,CAAC,MAAM,mBAAmB,GAC9B,8FAA8F,CAAC;AACjG,MAAM,CAAC,MAAM,uBAAuB,GAClC,+JAA+J,CAAC;AAElK,MAAM,UAAU,OAAO,CAAC,EACtB,QAAQ,EACR,SAAS,GAIV;IACC,OAAO,CACL,cAAK,SAAS,EAAE,EAAE,CAAC,qCAAqC,EAAE,SAAS,CAAC,YACjE,QAAQ,GACL,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,EACzB,QAAQ,EACR,SAAS,GAIV;IACC,OAAO,CACL,cACE,SAAS,EAAE,EAAE,CACX,kKAAkK,EAClK,SAAS,CACV,YAEA,QAAQ,GACL,CACP,CAAC;AACJ,CAAC","sourcesContent":["import type { ReactNode } from \"react\";\n\nimport { cn } from \"../lib/utils\";\n\nexport const APP_LIST_GRID_CLASS =\n \"w-full xl:grid xl:grid-cols-2 xl:gap-3 xl:overflow-visible xl:rounded-none xl:bg-transparent\";\nexport const APP_LIST_GRID_ROW_CLASS =\n \"xl:rounded-2xl xl:border xl:border-transparent xl:bg-card xl:transition-[background-color,border-color] xl:hover:border-border xl:focus-within:border-ring/50\";\n\nexport function AppList({\n children,\n className,\n}: {\n children: ReactNode;\n className?: string;\n}) {\n return (\n <div className={cn(\"overflow-hidden rounded-2xl bg-card\", className)}>\n {children}\n </div>\n );\n}\n\nexport function AppListRow({\n children,\n className,\n}: {\n children: ReactNode;\n className?: string;\n}) {\n return (\n <div\n className={cn(\n \"group flex min-w-0 items-center gap-3 border-b px-4 py-3.5 last:border-b-0 transition-[background-color,border-color] hover:bg-muted/30 focus-within:bg-muted/30\",\n className,\n )}\n >\n {children}\n </div>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"other-apps-section.d.ts","sourceRoot":"","sources":["../../src/components/other-apps-section.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAWxD,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACpC,KAAK,uBAAuB,EAC7B,MAAM,2BAA2B,CAAC;AAEnC,KAAK,aAAa,GACd;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,wBAAwB,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAepD,wBAAgB,oBAAoB,CAAC,EACnC,SAAS,EACT,aAAa,EACb,aAAa,GACd,EAAE;IACD,SAAS,CAAC,EAAE,+BAA+B,CAAC;IAC5C,aAAa,EAAE,mBAAmB,EAAE,CAAC;IACrC,aAAa,EAAE,cAAc,EAAE,CAAC;CACjC,GAAG,aAAa,EAAE,CA4BlB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,SAAS,EACT,aAAkB,EAClB,aAAa,EACb,gBAAwB,EACxB,oBAA4B,EAC5B,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,cAAc,EACd,OAAsB,EACtB,cAAsB,EACtB,SAAS,GACV,EAAE;IACD,SAAS,CAAC,EAAE,+BAA+B,CAAC;IAC5C,aAAa,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACtC,aAAa,EAAE,cAAc,EAAE,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,cAAc,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IAC9B,kBAAkB,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAClD,cAAc,CAAC,EAAE,CACf,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,wBAAwB,KAC/B,IAAI,CAAC;IACV,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,
|
|
1
|
+
{"version":3,"file":"other-apps-section.d.ts","sourceRoot":"","sources":["../../src/components/other-apps-section.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAWxD,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACpC,KAAK,uBAAuB,EAC7B,MAAM,2BAA2B,CAAC;AAEnC,KAAK,aAAa,GACd;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,wBAAwB,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAepD,wBAAgB,oBAAoB,CAAC,EACnC,SAAS,EACT,aAAa,EACb,aAAa,GACd,EAAE;IACD,SAAS,CAAC,EAAE,+BAA+B,CAAC;IAC5C,aAAa,EAAE,mBAAmB,EAAE,CAAC;IACrC,aAAa,EAAE,cAAc,EAAE,CAAC;CACjC,GAAG,aAAa,EAAE,CA4BlB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,SAAS,EACT,aAAkB,EAClB,aAAa,EACb,gBAAwB,EACxB,oBAA4B,EAC5B,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,cAAc,EACd,OAAsB,EACtB,cAAsB,EACtB,SAAS,GACV,EAAE;IACD,SAAS,CAAC,EAAE,+BAA+B,CAAC;IAC5C,aAAa,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACtC,aAAa,EAAE,cAAc,EAAE,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,cAAc,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IAC9B,kBAAkB,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAClD,cAAc,CAAC,EAAE,CACf,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,wBAAwB,KAC/B,IAAI,CAAC;IACV,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,0DAyEA"}
|
|
@@ -52,7 +52,8 @@ export function OtherAppsSection({ templates, connectedApps = [], workspaceApps,
|
|
|
52
52
|
const hasError = Boolean(templatesError || connectedAppsError);
|
|
53
53
|
if (!isLoading && !hasError && entries.length === 0)
|
|
54
54
|
return null;
|
|
55
|
-
const
|
|
55
|
+
const entryCards = entries.map((entry) => entry.kind === "template" ? (_jsx(WorkspaceTemplateCard, { template: entry.template, labels: templateLabels, catalog: true, className: APP_LIST_GRID_ROW_CLASS, onRemixSuccess: onRemixSuccess }, `template:${templateKey(entry.template)}`)) : (_jsx(ConnectedAppCard, { app: entry.app, className: APP_LIST_GRID_ROW_CLASS }, `connected:${entry.app.id}`)));
|
|
56
|
+
const content = (_jsxs(_Fragment, { children: [heading ? (_jsx("div", { className: "flex min-w-0 items-center", children: _jsx("h2", { className: "truncate text-sm font-semibold text-foreground", children: t("dispatch.pages.otherApps", { defaultValue: heading }) }) })) : null, templatesError ? (_jsx(ActionQueryError, { error: templatesError, onRetry: () => onRetryTemplates?.() })) : null, connectedAppsError ? (_jsx(ActionQueryError, { error: connectedAppsError, onRetry: () => onRetryConnectedApps?.() })) : null, isLoading && entries.length === 0 ? (embeddedInList ? (_jsx(OtherAppsSkeletonRows, {})) : (_jsx(OtherAppsSkeletonList, {}))) : entries.length > 0 ? (embeddedInList ? (entryCards) : (_jsx(AppList, { className: APP_LIST_GRID_CLASS, children: entryCards }))) : null] }));
|
|
56
57
|
if (embeddedInList)
|
|
57
58
|
return content;
|
|
58
59
|
return _jsx("section", { className: cn("space-y-3", className), children: content });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"other-apps-section.js","sourceRoot":"","sources":["../../src/components/other-apps-section.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useT } from \"@agent-native/core/client/i18n\";\n\nimport { filterOtherApps, type ConnectedAppSummary } from \"../lib/other-apps\";\nimport type { WorkspaceAppId } from \"../lib/other-apps\";\nimport { cn } from \"../lib/utils\";\nimport { isDefaultWorkspaceAppHiddenId } from \"../lib/workspace-apps\";\nimport { ActionQueryError } from \"./action-query-error\";\nimport {\n APP_LIST_GRID_CLASS,\n APP_LIST_GRID_ROW_CLASS,\n AppList,\n} from \"./app-list-row\";\nimport { ConnectedAppCard } from \"./connected-app-card\";\nimport { Skeleton } from \"./ui/skeleton\";\nimport {\n WorkspaceTemplateCard,\n type CuratedWorkspaceTemplate,\n type CuratedWorkspaceTemplatesResult,\n type WorkspaceTemplateLabels,\n} from \"./workspace-template-card\";\n\ntype OtherAppEntry =\n | { kind: \"template\"; template: CuratedWorkspaceTemplate }\n | { kind: \"connected\"; app: ConnectedAppSummary };\n\nfunction templateKey(template: CuratedWorkspaceTemplate): string {\n return (template.templateId || template.id || template.appId || template.name)\n .trim()\n .toLowerCase();\n}\n\nfunction getTemplateItems(\n result: CuratedWorkspaceTemplatesResult | undefined,\n): CuratedWorkspaceTemplate[] {\n if (!result) return [];\n return Array.isArray(result) ? result : result.templates;\n}\n\nexport function mergeOtherAppEntries({\n templates,\n connectedApps,\n workspaceApps,\n}: {\n templates?: CuratedWorkspaceTemplatesResult;\n connectedApps: ConnectedAppSummary[];\n workspaceApps: WorkspaceAppId[];\n}): OtherAppEntry[] {\n const workspaceAppIds = new Set(\n workspaceApps.map((app) => app.id.trim().toLowerCase()),\n );\n const seen = new Set<string>();\n const entries: OtherAppEntry[] = [];\n\n for (const template of getTemplateItems(templates)) {\n const id = templateKey(template);\n if (\n !id ||\n isDefaultWorkspaceAppHiddenId(id) ||\n template.installed ||\n workspaceAppIds.has(id)\n )\n continue;\n seen.add(id);\n entries.push({ kind: \"template\", template });\n }\n\n for (const app of filterOtherApps(connectedApps, workspaceApps)) {\n const id = app.id.trim().toLowerCase();\n if (seen.has(id)) continue;\n seen.add(id);\n entries.push({ kind: \"connected\", app });\n }\n\n return entries;\n}\n\nexport function OtherAppsSection({\n templates,\n connectedApps = [],\n workspaceApps,\n templatesLoading = false,\n connectedAppsLoading = false,\n templatesError,\n connectedAppsError,\n onRetryTemplates,\n onRetryConnectedApps,\n templateLabels,\n onRemixSuccess,\n heading = \"Other apps\",\n embeddedInList = false,\n className,\n}: {\n templates?: CuratedWorkspaceTemplatesResult;\n connectedApps?: ConnectedAppSummary[];\n workspaceApps: WorkspaceAppId[];\n templatesLoading?: boolean;\n connectedAppsLoading?: boolean;\n templatesError?: Error | null;\n connectedAppsError?: Error | null;\n onRetryTemplates?: () => void;\n onRetryConnectedApps?: () => void;\n templateLabels?: Partial<WorkspaceTemplateLabels>;\n onRemixSuccess?: (\n result: unknown,\n template: CuratedWorkspaceTemplate,\n ) => void;\n heading?: string | null;\n embeddedInList?: boolean;\n className?: string;\n}) {\n const t = useT();\n const entries = mergeOtherAppEntries({\n templates,\n connectedApps,\n workspaceApps,\n });\n const isLoading = templatesLoading || connectedAppsLoading;\n const hasError = Boolean(templatesError || connectedAppsError);\n\n if (!isLoading && !hasError && entries.length === 0) return null;\n\n const content = (\n <>\n {heading ? (\n <div className=\"flex min-w-0 items-center\">\n <h2 className=\"truncate text-sm font-semibold text-foreground\">\n {t(\"dispatch.pages.otherApps\", { defaultValue: heading })}\n </h2>\n </div>\n ) : null}\n\n {templatesError ? (\n <ActionQueryError\n error={templatesError}\n onRetry={() => onRetryTemplates?.()}\n />\n ) : null}\n {connectedAppsError ? (\n <ActionQueryError\n error={connectedAppsError}\n onRetry={() => onRetryConnectedApps?.()}\n />\n ) : null}\n\n {isLoading && entries.length === 0 ? (\n embeddedInList ? (\n <OtherAppsSkeletonRows />\n ) : (\n <OtherAppsSkeletonList />\n )\n ) : entries.length > 0 ? (\n entries.map((entry) =>\n entry.kind === \"template\" ? (\n <WorkspaceTemplateCard\n key={`template:${templateKey(entry.template)}`}\n template={entry.template}\n labels={templateLabels}\n catalog\n className={APP_LIST_GRID_ROW_CLASS}\n onRemixSuccess={onRemixSuccess}\n />\n ) : (\n <ConnectedAppCard\n key={`connected:${entry.app.id}`}\n app={entry.app}\n className={APP_LIST_GRID_ROW_CLASS}\n />\n ),\n )\n ) : null}\n </>\n );\n\n if (embeddedInList) return content;\n\n return <section className={cn(\"space-y-3\", className)}>{content}</section>;\n}\n\nfunction OtherAppsSkeletonList() {\n return (\n <AppList className={APP_LIST_GRID_CLASS}>\n {Array.from({ length: 4 }).map((_, index) => (\n <div\n key={index}\n className={cn(\n \"flex min-w-0 items-center gap-3 border-b px-4 py-3.5 last:border-b-0\",\n APP_LIST_GRID_ROW_CLASS,\n )}\n >\n <Skeleton className=\"size-8 shrink-0 rounded-xl\" />\n <div className=\"min-w-0 flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-32\" />\n <Skeleton className=\"h-3 w-2/3\" />\n </div>\n <Skeleton className=\"h-9 w-24 shrink-0 rounded-md\" />\n </div>\n ))}\n </AppList>\n );\n}\n\nfunction OtherAppsSkeletonRows() {\n return (\n <>\n {Array.from({ length: 4 }).map((_, index) => (\n <div\n key={index}\n className={cn(\n \"flex min-w-0 items-center gap-3 border-b px-4 py-3.5 last:border-b-0\",\n APP_LIST_GRID_ROW_CLASS,\n )}\n >\n <Skeleton className=\"size-8 shrink-0 rounded-xl\" />\n <div className=\"min-w-0 flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-32\" />\n <Skeleton className=\"h-3 w-2/3\" />\n </div>\n <Skeleton className=\"h-9 w-24 shrink-0 rounded-md\" />\n </div>\n ))}\n </>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"other-apps-section.js","sourceRoot":"","sources":["../../src/components/other-apps-section.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useT } from \"@agent-native/core/client/i18n\";\n\nimport { filterOtherApps, type ConnectedAppSummary } from \"../lib/other-apps\";\nimport type { WorkspaceAppId } from \"../lib/other-apps\";\nimport { cn } from \"../lib/utils\";\nimport { isDefaultWorkspaceAppHiddenId } from \"../lib/workspace-apps\";\nimport { ActionQueryError } from \"./action-query-error\";\nimport {\n APP_LIST_GRID_CLASS,\n APP_LIST_GRID_ROW_CLASS,\n AppList,\n} from \"./app-list-row\";\nimport { ConnectedAppCard } from \"./connected-app-card\";\nimport { Skeleton } from \"./ui/skeleton\";\nimport {\n WorkspaceTemplateCard,\n type CuratedWorkspaceTemplate,\n type CuratedWorkspaceTemplatesResult,\n type WorkspaceTemplateLabels,\n} from \"./workspace-template-card\";\n\ntype OtherAppEntry =\n | { kind: \"template\"; template: CuratedWorkspaceTemplate }\n | { kind: \"connected\"; app: ConnectedAppSummary };\n\nfunction templateKey(template: CuratedWorkspaceTemplate): string {\n return (template.templateId || template.id || template.appId || template.name)\n .trim()\n .toLowerCase();\n}\n\nfunction getTemplateItems(\n result: CuratedWorkspaceTemplatesResult | undefined,\n): CuratedWorkspaceTemplate[] {\n if (!result) return [];\n return Array.isArray(result) ? result : result.templates;\n}\n\nexport function mergeOtherAppEntries({\n templates,\n connectedApps,\n workspaceApps,\n}: {\n templates?: CuratedWorkspaceTemplatesResult;\n connectedApps: ConnectedAppSummary[];\n workspaceApps: WorkspaceAppId[];\n}): OtherAppEntry[] {\n const workspaceAppIds = new Set(\n workspaceApps.map((app) => app.id.trim().toLowerCase()),\n );\n const seen = new Set<string>();\n const entries: OtherAppEntry[] = [];\n\n for (const template of getTemplateItems(templates)) {\n const id = templateKey(template);\n if (\n !id ||\n isDefaultWorkspaceAppHiddenId(id) ||\n template.installed ||\n workspaceAppIds.has(id)\n )\n continue;\n seen.add(id);\n entries.push({ kind: \"template\", template });\n }\n\n for (const app of filterOtherApps(connectedApps, workspaceApps)) {\n const id = app.id.trim().toLowerCase();\n if (seen.has(id)) continue;\n seen.add(id);\n entries.push({ kind: \"connected\", app });\n }\n\n return entries;\n}\n\nexport function OtherAppsSection({\n templates,\n connectedApps = [],\n workspaceApps,\n templatesLoading = false,\n connectedAppsLoading = false,\n templatesError,\n connectedAppsError,\n onRetryTemplates,\n onRetryConnectedApps,\n templateLabels,\n onRemixSuccess,\n heading = \"Other apps\",\n embeddedInList = false,\n className,\n}: {\n templates?: CuratedWorkspaceTemplatesResult;\n connectedApps?: ConnectedAppSummary[];\n workspaceApps: WorkspaceAppId[];\n templatesLoading?: boolean;\n connectedAppsLoading?: boolean;\n templatesError?: Error | null;\n connectedAppsError?: Error | null;\n onRetryTemplates?: () => void;\n onRetryConnectedApps?: () => void;\n templateLabels?: Partial<WorkspaceTemplateLabels>;\n onRemixSuccess?: (\n result: unknown,\n template: CuratedWorkspaceTemplate,\n ) => void;\n heading?: string | null;\n embeddedInList?: boolean;\n className?: string;\n}) {\n const t = useT();\n const entries = mergeOtherAppEntries({\n templates,\n connectedApps,\n workspaceApps,\n });\n const isLoading = templatesLoading || connectedAppsLoading;\n const hasError = Boolean(templatesError || connectedAppsError);\n\n if (!isLoading && !hasError && entries.length === 0) return null;\n\n const entryCards = entries.map((entry) =>\n entry.kind === \"template\" ? (\n <WorkspaceTemplateCard\n key={`template:${templateKey(entry.template)}`}\n template={entry.template}\n labels={templateLabels}\n catalog\n className={APP_LIST_GRID_ROW_CLASS}\n onRemixSuccess={onRemixSuccess}\n />\n ) : (\n <ConnectedAppCard\n key={`connected:${entry.app.id}`}\n app={entry.app}\n className={APP_LIST_GRID_ROW_CLASS}\n />\n ),\n );\n\n const content = (\n <>\n {heading ? (\n <div className=\"flex min-w-0 items-center\">\n <h2 className=\"truncate text-sm font-semibold text-foreground\">\n {t(\"dispatch.pages.otherApps\", { defaultValue: heading })}\n </h2>\n </div>\n ) : null}\n\n {templatesError ? (\n <ActionQueryError\n error={templatesError}\n onRetry={() => onRetryTemplates?.()}\n />\n ) : null}\n {connectedAppsError ? (\n <ActionQueryError\n error={connectedAppsError}\n onRetry={() => onRetryConnectedApps?.()}\n />\n ) : null}\n\n {isLoading && entries.length === 0 ? (\n embeddedInList ? (\n <OtherAppsSkeletonRows />\n ) : (\n <OtherAppsSkeletonList />\n )\n ) : entries.length > 0 ? (\n embeddedInList ? (\n entryCards\n ) : (\n <AppList className={APP_LIST_GRID_CLASS}>{entryCards}</AppList>\n )\n ) : null}\n </>\n );\n\n if (embeddedInList) return content;\n\n return <section className={cn(\"space-y-3\", className)}>{content}</section>;\n}\n\nfunction OtherAppsSkeletonList() {\n return (\n <AppList className={APP_LIST_GRID_CLASS}>\n {Array.from({ length: 4 }).map((_, index) => (\n <div\n key={index}\n className={cn(\n \"flex min-w-0 items-center gap-3 border-b px-4 py-3.5 last:border-b-0\",\n APP_LIST_GRID_ROW_CLASS,\n )}\n >\n <Skeleton className=\"size-8 shrink-0 rounded-xl\" />\n <div className=\"min-w-0 flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-32\" />\n <Skeleton className=\"h-3 w-2/3\" />\n </div>\n <Skeleton className=\"h-9 w-24 shrink-0 rounded-md\" />\n </div>\n ))}\n </AppList>\n );\n}\n\nfunction OtherAppsSkeletonRows() {\n return (\n <>\n {Array.from({ length: 4 }).map((_, index) => (\n <div\n key={index}\n className={cn(\n \"flex min-w-0 items-center gap-3 border-b px-4 py-3.5 last:border-b-0\",\n APP_LIST_GRID_ROW_CLASS,\n )}\n >\n <Skeleton className=\"size-8 shrink-0 rounded-xl\" />\n <div className=\"min-w-0 flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-32\" />\n <Skeleton className=\"h-3 w-2/3\" />\n </div>\n <Skeleton className=\"h-9 w-24 shrink-0 rounded-md\" />\n </div>\n ))}\n </>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/admin.tsx"],"names":[],"mappings":"AAIA,wBAAgB,IAAI;;IAEnB;AAED,MAAM,CAAC,OAAO,UAAU,UAAU,
|
|
1
|
+
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/admin.tsx"],"names":[],"mappings":"AAIA,wBAAgB,IAAI;;IAEnB;AAED,MAAM,CAAC,OAAO,UAAU,UAAU,oDAgBjC"}
|
|
@@ -8,7 +8,9 @@ export default function AdminRoute() {
|
|
|
8
8
|
const location = useLocation();
|
|
9
9
|
const isAutomationsRoute = location.pathname === "/admin/automations" ||
|
|
10
10
|
location.pathname.startsWith("/admin/automations/");
|
|
11
|
-
|
|
11
|
+
const isIntegrationsRoute = location.pathname === "/admin/integrations" ||
|
|
12
|
+
location.pathname.startsWith("/admin/integrations/");
|
|
13
|
+
if (isAutomationsRoute || isIntegrationsRoute)
|
|
12
14
|
return _jsx(Outlet, {});
|
|
13
15
|
return (_jsx(AdminShell, { children: _jsx(Outlet, {}) }));
|
|
14
16
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../../../src/routes/pages/admin.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAE/D,MAAM,UAAU,IAAI;IAClB,OAAO,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,UAAU;IAChC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,kBAAkB,GACtB,QAAQ,CAAC,QAAQ,KAAK,oBAAoB;QAC1C,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../../../src/routes/pages/admin.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAE/D,MAAM,UAAU,IAAI;IAClB,OAAO,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,UAAU;IAChC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,kBAAkB,GACtB,QAAQ,CAAC,QAAQ,KAAK,oBAAoB;QAC1C,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC;IACtD,MAAM,mBAAmB,GACvB,QAAQ,CAAC,QAAQ,KAAK,qBAAqB;QAC3C,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC;IAEvD,IAAI,kBAAkB,IAAI,mBAAmB;QAAE,OAAO,KAAC,MAAM,KAAG,CAAC;IAEjE,OAAO,CACL,KAAC,UAAU,cACT,KAAC,MAAM,KAAG,GACC,CACd,CAAC;AACJ,CAAC","sourcesContent":["import { Outlet, useLocation } from \"react-router\";\n\nimport { AdminShell } from \"../../components/admin-navigation\";\n\nexport function meta() {\n return [{ title: \"Admin — Dispatch\" }];\n}\n\nexport default function AdminRoute() {\n const location = useLocation();\n const isAutomationsRoute =\n location.pathname === \"/admin/automations\" ||\n location.pathname.startsWith(\"/admin/automations/\");\n const isIntegrationsRoute =\n location.pathname === \"/admin/integrations\" ||\n location.pathname.startsWith(\"/admin/integrations/\");\n\n if (isAutomationsRoute || isIntegrationsRoute) return <Outlet />;\n\n return (\n <AdminShell>\n <Outlet />\n </AdminShell>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-integrations.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/agent-integrations.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-integrations.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/agent-integrations.tsx"],"names":[],"mappings":"AAMA,wBAAgB,IAAI;;IAEnB;AAED,MAAM,CAAC,OAAO,UAAU,sBAAsB,oDAoB7C"}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
3
|
+
import { McpIntegrationsLanding } from "@agent-native/core/client/integrations";
|
|
4
|
+
import { Link } from "react-router";
|
|
3
5
|
import { DispatchShell } from "../../components/dispatch-shell.js";
|
|
4
6
|
export function meta() {
|
|
5
7
|
return [{ title: "Integrations — Dispatch" }];
|
|
6
8
|
}
|
|
7
9
|
export default function AgentIntegrationsRoute() {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
if (!integrationsTab) {
|
|
11
|
-
throw new Error("The shared Integrations settings view is unavailable.");
|
|
12
|
-
}
|
|
13
|
-
return (_jsx(DispatchShell, { title: "Integrations", description: "Connect the tools and services available to your agent.", children: _jsx("div", { className: "mx-auto w-full max-w-5xl", children: integrationsTab.content }) }));
|
|
10
|
+
const t = useT();
|
|
11
|
+
return (_jsx(DispatchShell, { title: "Integrations", description: "Connect the tools and services available to your agent.", children: _jsxs("div", { className: "mx-auto w-full max-w-5xl", children: [_jsx(McpIntegrationsLanding, { showTitle: false, showDescription: false }), _jsx("p", { className: "mt-5 text-right text-xs text-muted-foreground", children: _jsx(Link, { className: "underline-offset-4 hover:text-foreground hover:underline", to: "/integrations", children: t("integrations.connectedAccounts") }) })] }) }));
|
|
14
12
|
}
|
|
15
13
|
//# sourceMappingURL=agent-integrations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-integrations.js","sourceRoot":"","sources":["../../../src/routes/pages/agent-integrations.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"agent-integrations.js","sourceRoot":"","sources":["../../../src/routes/pages/agent-integrations.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAEhE,MAAM,UAAU,IAAI;IAClB,OAAO,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,sBAAsB;IAC5C,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,OAAO,CACL,KAAC,aAAa,IACZ,KAAK,EAAC,cAAc,EACpB,WAAW,EAAC,yDAAyD,YAErE,eAAK,SAAS,EAAC,0BAA0B,aACvC,KAAC,sBAAsB,IAAC,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,GAAI,EACpE,YAAG,SAAS,EAAC,+CAA+C,YAC1D,KAAC,IAAI,IACH,SAAS,EAAC,0DAA0D,EACpE,EAAE,EAAC,eAAe,YAEjB,CAAC,CAAC,gCAAgC,CAAC,GAC/B,GACL,IACA,GACQ,CACjB,CAAC;AACJ,CAAC","sourcesContent":["import { useT } from \"@agent-native/core/client/i18n\";\nimport { McpIntegrationsLanding } from \"@agent-native/core/client/integrations\";\nimport { Link } from \"react-router\";\n\nimport { DispatchShell } from \"../../components/dispatch-shell\";\n\nexport function meta() {\n return [{ title: \"Integrations — Dispatch\" }];\n}\n\nexport default function AgentIntegrationsRoute() {\n const t = useT();\n return (\n <DispatchShell\n title=\"Integrations\"\n description=\"Connect the tools and services available to your agent.\"\n >\n <div className=\"mx-auto w-full max-w-5xl\">\n <McpIntegrationsLanding showTitle={false} showDescription={false} />\n <p className=\"mt-5 text-right text-xs text-muted-foreground\">\n <Link\n className=\"underline-offset-4 hover:text-foreground hover:underline\"\n to=\"/integrations\"\n >\n {t(\"integrations.connectedAccounts\")}\n </Link>\n </p>\n </div>\n </DispatchShell>\n );\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare function meta(): {
|
|
2
2
|
title: string;
|
|
3
3
|
}[];
|
|
4
|
-
export default function
|
|
4
|
+
export default function IntegrationsRoute(): import("node_modules/@types/react").JSX.Element;
|
|
5
5
|
//# sourceMappingURL=integrations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/integrations.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/integrations.tsx"],"names":[],"mappings":"AAMA,wBAAgB,IAAI;;IAEnB;AAED,MAAM,CAAC,OAAO,UAAU,iBAAiB,oDAoBxC"}
|
|
@@ -1,196 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { IconChevronRight, IconLink, IconPlugConnected, IconSearch, } from "@tabler/icons-react";
|
|
5
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
6
|
-
import { useMemo, useState } from "react";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
3
|
+
import { McpIntegrationsLanding } from "@agent-native/core/client/integrations";
|
|
7
4
|
import { Link } from "react-router";
|
|
8
|
-
import { toast } from "sonner";
|
|
9
|
-
import { ActionQueryError } from "../../components/action-query-error.js";
|
|
10
5
|
import { DispatchShell } from "../../components/dispatch-shell.js";
|
|
11
|
-
import { Button } from "../../components/ui/button.js";
|
|
12
|
-
import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "../../components/ui/collapsible.js";
|
|
13
|
-
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../../components/ui/dialog.js";
|
|
14
|
-
import { Input } from "../../components/ui/input.js";
|
|
15
|
-
import { Label } from "../../components/ui/label.js";
|
|
16
|
-
import { Skeleton } from "../../components/ui/skeleton.js";
|
|
17
6
|
export function meta() {
|
|
18
|
-
return [{ title: "
|
|
7
|
+
return [{ title: "Integrations — Dispatch" }];
|
|
19
8
|
}
|
|
20
|
-
function
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
"google",
|
|
24
|
-
"slack",
|
|
25
|
-
"sendgrid",
|
|
26
|
-
"github",
|
|
27
|
-
"stripe",
|
|
28
|
-
"hubspot",
|
|
29
|
-
"jira",
|
|
30
|
-
"bigquery",
|
|
31
|
-
"anthropic",
|
|
32
|
-
"openai",
|
|
33
|
-
]) {
|
|
34
|
-
if (haystack.includes(provider))
|
|
35
|
-
return provider;
|
|
36
|
-
}
|
|
37
|
-
return "other";
|
|
38
|
-
}
|
|
39
|
-
const MCP_INTEGRATIONS_BY_ID = new Map(getDefaultMcpIntegrations().map((integration) => [
|
|
40
|
-
integration.id,
|
|
41
|
-
integration,
|
|
42
|
-
]));
|
|
43
|
-
const CREDENTIAL_PROVIDER_LOGO_IDS = {
|
|
44
|
-
google: "google-workspace",
|
|
45
|
-
bigquery: "google-workspace",
|
|
46
|
-
jira: "atlassian",
|
|
47
|
-
};
|
|
48
|
-
function credentialLogo(service) {
|
|
49
|
-
const providerId = inferProviderFromKey(service.key, service.label);
|
|
50
|
-
const integration = MCP_INTEGRATIONS_BY_ID.get(CREDENTIAL_PROVIDER_LOGO_IDS[providerId] ?? providerId);
|
|
51
|
-
if (!integration?.logoUrl) {
|
|
52
|
-
return _jsx(IconPlugConnected, { className: "size-4 text-muted-foreground" });
|
|
53
|
-
}
|
|
54
|
-
return (_jsx(McpIntegrationLogo, { name: service.label, logoUrl: integration.logoUrl, integrationId: integration.id, className: "size-full rounded-md border-0 bg-transparent", imageClassName: "size-full p-1" }));
|
|
55
|
-
}
|
|
56
|
-
function ConnectDialog({ service, open, onOpenChange, accessMode, }) {
|
|
57
|
-
const [value, setValue] = useState("");
|
|
58
|
-
const qc = useQueryClient();
|
|
59
|
-
const configuredApps = service.apps.filter((app) => app.configured);
|
|
60
|
-
const configuredAppNames = configuredApps
|
|
61
|
-
.map((app) => app.appName)
|
|
62
|
-
.join(", ");
|
|
63
|
-
const createSecret = useActionMutation("create-vault-secret", {});
|
|
64
|
-
const createGrant = useActionMutation("create-vault-grant", {});
|
|
65
|
-
const syncToApp = useActionMutation("sync-vault-to-app", {});
|
|
66
|
-
function reset() {
|
|
67
|
-
setValue("");
|
|
68
|
-
}
|
|
69
|
-
async function handleSave() {
|
|
70
|
-
const trimmed = value.trim();
|
|
71
|
-
if (!trimmed) {
|
|
72
|
-
toast.error("Enter a value to save");
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
try {
|
|
76
|
-
// 1. Create the secret (or get the existing one — server treats key as
|
|
77
|
-
// the unique identifier). The server returns { secret: { id, ... } }.
|
|
78
|
-
const created = await createSecret.mutateAsync({
|
|
79
|
-
credentialKey: service.key,
|
|
80
|
-
name: service.label,
|
|
81
|
-
value: trimmed,
|
|
82
|
-
provider: inferProviderFromKey(service.key, service.label),
|
|
83
|
-
});
|
|
84
|
-
const secretId = created?.secret?.id ??
|
|
85
|
-
created?.id;
|
|
86
|
-
if (!secretId) {
|
|
87
|
-
throw new Error("Secret created but id missing");
|
|
88
|
-
}
|
|
89
|
-
// 2. Manual mode needs grants; all-apps mode only needs sync.
|
|
90
|
-
if (accessMode === "manual") {
|
|
91
|
-
const targets = service.apps.filter((a) => !a.vaultGranted);
|
|
92
|
-
for (const app of targets) {
|
|
93
|
-
try {
|
|
94
|
-
await createGrant.mutateAsync({
|
|
95
|
-
secretId,
|
|
96
|
-
appId: app.appId,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
console.warn(`grant to ${app.appId} failed`, err);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
for (const app of service.apps) {
|
|
105
|
-
try {
|
|
106
|
-
await syncToApp.mutateAsync({ appId: app.appId });
|
|
107
|
-
}
|
|
108
|
-
catch (err) {
|
|
109
|
-
console.warn(`sync to ${app.appId} failed`, err);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
qc.invalidateQueries({
|
|
113
|
-
queryKey: ["action", "list-integrations-catalog"],
|
|
114
|
-
});
|
|
115
|
-
toast.success(`Connected ${service.label}`);
|
|
116
|
-
onOpenChange(false);
|
|
117
|
-
reset();
|
|
118
|
-
}
|
|
119
|
-
catch (err) {
|
|
120
|
-
toast.error(err?.message ?? "Failed to save credential");
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
const pending = createSecret.isPending || createGrant.isPending || syncToApp.isPending;
|
|
124
|
-
return (_jsx(Dialog, { open: open, onOpenChange: (next) => {
|
|
125
|
-
if (!next)
|
|
126
|
-
reset();
|
|
127
|
-
onOpenChange(next);
|
|
128
|
-
}, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: ["Connect ", service.label] }), _jsxs(DialogDescription, { children: [configuredApps.length > 0
|
|
129
|
-
? `Configured in ${configuredAppNames}. `
|
|
130
|
-
: "No app currently reports this key as configured. ", "Saving it creates a workspace vault entry using this exact key name. Apps need the same key name to use it."] })] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: "Key" }), _jsx("div", { className: "font-mono text-sm", children: service.key })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "connector-value", children: "Value" }), _jsx(Input, { id: "connector-value", type: "password", autoComplete: "off", value: value, onChange: (e) => setValue(e.target.value), placeholder: `Paste your ${service.label} key…`, autoFocus: true })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: pending, children: "Cancel" }), _jsx(Button, { onClick: handleSave, disabled: pending || !value.trim(), children: pending ? "Saving…" : "Connect" })] })] }) }));
|
|
131
|
-
}
|
|
132
|
-
function ConnectorCard({ service, accessMode, }) {
|
|
133
|
-
const [open, setOpen] = useState(false);
|
|
134
|
-
const configuredApps = service.apps.filter((app) => app.configured);
|
|
135
|
-
const vaultApps = service.apps.filter((app) => app.vaultGranted);
|
|
136
|
-
const isAvailable = configuredApps.length > 0 || vaultApps.length > 0;
|
|
137
|
-
const configuredAppNames = configuredApps
|
|
138
|
-
.map((app) => app.appName)
|
|
139
|
-
.join(", ");
|
|
140
|
-
const vaultAppNames = vaultApps.map((app) => app.appName).join(", ");
|
|
141
|
-
return (_jsxs(_Fragment, { children: [_jsxs("article", { className: "flex min-w-0 items-center gap-3 border-b border-border/60 py-3.5 last:border-b-0", children: [_jsx("div", { className: "flex size-8 shrink-0 items-center justify-center rounded-md bg-muted", children: credentialLogo(service) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { className: "truncate text-sm font-medium text-foreground", children: service.label }), _jsx("span", { className: isAvailable
|
|
142
|
-
? "shrink-0 text-[11px] font-medium text-emerald-600 dark:text-emerald-400"
|
|
143
|
-
: "shrink-0 text-[11px] font-medium text-muted-foreground", children: vaultApps.length > 0 ? "Connected" : "App key" })] }), _jsx("p", { className: "mt-0.5 truncate font-mono text-[11px] leading-5 text-muted-foreground/80", children: service.key }), _jsxs("p", { className: "mt-0.5 truncate text-xs leading-5 text-muted-foreground", children: ["Declared by ", service.apps.length, " ", service.apps.length === 1 ? "app" : "apps", configuredApps.length > 0 ? ` · ${configuredAppNames}` : "", vaultApps.length > 0 ? ` · Vault: ${vaultAppNames}` : ""] })] }), _jsxs("button", { type: "button", onClick: () => setOpen(true), className: "inline-flex h-8 shrink-0 items-center justify-center gap-1 rounded-md border border-border bg-background px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [isAvailable ? "Manage" : "Connect", _jsx(IconChevronRight, { className: "size-3.5 rtl:-scale-x-100" })] })] }), _jsx(ConnectDialog, { service: service, open: open, onOpenChange: setOpen, accessMode: accessMode })] }));
|
|
144
|
-
}
|
|
145
|
-
function PerAppDetailRow({ app }) {
|
|
146
|
-
const total = (app.integrations ?? []).length;
|
|
147
|
-
const ok = (app.integrations ?? []).filter((i) => i.configured).length;
|
|
148
|
-
return (_jsxs("div", { className: "flex items-center justify-between border-t px-4 py-2.5 first:border-t-0", children: [_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("div", { className: "h-5 w-5 rounded text-[10px] font-bold text-white flex items-center justify-center shrink-0", style: { backgroundColor: app.color }, children: app.appName.charAt(0).toUpperCase() }), _jsx("span", { className: "text-sm truncate", children: app.appName }), !app.reachable && (_jsx("span", { className: "text-xs text-muted-foreground", children: "offline" }))] }), _jsx("span", { className: "text-xs text-muted-foreground", children: total === 0 ? "no integrations" : `${ok}/${total}` })] }));
|
|
149
|
-
}
|
|
150
|
-
export default function ConnectionsRoute() {
|
|
151
|
-
const catalogQuery = useActionQuery("list-integrations-catalog", {});
|
|
152
|
-
const accessQuery = useActionQuery("get-vault-access-settings", {});
|
|
153
|
-
const { data: catalog } = catalogQuery;
|
|
154
|
-
const { data: accessSettings } = accessQuery;
|
|
155
|
-
const isLoading = catalogQuery.isLoading || accessQuery.isLoading;
|
|
156
|
-
const apps = catalog || [];
|
|
157
|
-
const accessMode = accessSettings?.mode === "manual" ? "manual" : "all-apps";
|
|
158
|
-
const [query, setQuery] = useState("");
|
|
159
|
-
const services = useMemo(() => {
|
|
160
|
-
const map = new Map();
|
|
161
|
-
for (const app of apps) {
|
|
162
|
-
for (const intg of app.integrations ?? []) {
|
|
163
|
-
if (!map.has(intg.key)) {
|
|
164
|
-
map.set(intg.key, {
|
|
165
|
-
key: intg.key,
|
|
166
|
-
label: intg.label,
|
|
167
|
-
apps: [],
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
map.get(intg.key).apps.push({
|
|
171
|
-
appId: app.appId,
|
|
172
|
-
appName: app.appName,
|
|
173
|
-
color: app.color,
|
|
174
|
-
configured: intg.configured,
|
|
175
|
-
vaultGranted: intg.vaultGranted,
|
|
176
|
-
vaultSecretId: intg.vaultSecretId,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return Array.from(map.values()).sort((a, b) => a.label.localeCompare(b.label));
|
|
181
|
-
}, [apps]);
|
|
182
|
-
const filteredServices = useMemo(() => {
|
|
183
|
-
const normalized = query.trim().toLowerCase();
|
|
184
|
-
if (!normalized)
|
|
185
|
-
return services;
|
|
186
|
-
return services.filter((service) => service.label.toLowerCase().includes(normalized) ||
|
|
187
|
-
service.key.toLowerCase().includes(normalized));
|
|
188
|
-
}, [query, services]);
|
|
189
|
-
const available = filteredServices.filter((s) => !s.apps.some((a) => a.configured));
|
|
190
|
-
const connected = filteredServices.filter((s) => s.apps.some((a) => a.configured));
|
|
191
|
-
return (_jsxs(DispatchShell, { title: "Connections", description: "Connect workspace credentials declared by apps and manage how they are shared.", children: [catalogQuery.isError || accessQuery.isError ? (_jsx(ActionQueryError, { error: catalogQuery.error ?? accessQuery.error, onRetry: () => {
|
|
192
|
-
void catalogQuery.refetch();
|
|
193
|
-
void accessQuery.refetch();
|
|
194
|
-
} })) : null, !catalogQuery.isError && !isLoading ? (_jsxs("div", { className: "mb-5 flex items-center gap-3", children: [_jsxs("div", { className: "relative min-w-0 flex-1", children: [_jsx(IconSearch, { className: "pointer-events-none absolute start-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: query, onChange: (event) => setQuery(event.target.value), placeholder: "Search credentials", "aria-label": "Search credentials", className: "h-9 ps-9" })] }), _jsxs("span", { className: "shrink-0 text-xs text-muted-foreground", children: [filteredServices.length, " credential", filteredServices.length === 1 ? "" : "s"] })] })) : null, !catalogQuery.isError && isLoading && services.length === 0 && (_jsx("div", { className: "overflow-hidden rounded-lg bg-card", children: Array.from({ length: 7 }).map((_, index) => (_jsxs("div", { className: "flex items-center gap-3 border-b px-4 py-3 last:border-b-0", children: [_jsx(Skeleton, { className: "size-8 rounded-md" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(Skeleton, { className: "h-4 w-40 max-w-full" }), _jsx(Skeleton, { className: "mt-1.5 h-3 w-56 max-w-full" })] }), _jsx(Skeleton, { className: "h-6 w-20 rounded-full" })] }, index))) })), !catalogQuery.isError && !isLoading && services.length === 0 && (_jsx("div", { className: "rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground", children: "No apps with declared integrations are reachable yet." })), available.length > 0 && (_jsxs("section", { children: [_jsxs("div", { className: "mb-3 flex items-baseline justify-between", children: [_jsx("h2", { className: "text-sm font-medium text-foreground", children: "Not configured in apps" }), _jsx("span", { className: "text-xs text-muted-foreground", children: available.length })] }), _jsx("div", { className: "overflow-hidden rounded-lg bg-card", children: available.map((service) => (_jsx(ConnectorCard, { service: service, accessMode: accessMode }, service.key))) })] })), connected.length > 0 && (_jsxs("section", { children: [_jsxs("div", { className: "mb-3 mt-2 flex items-baseline justify-between", children: [_jsx("h2", { className: "text-sm font-medium text-foreground", children: "Configured in apps" }), _jsx("span", { className: "text-xs text-muted-foreground", children: connected.length })] }), _jsx("div", { className: "overflow-hidden rounded-lg bg-card", children: connected.map((service) => (_jsx(ConnectorCard, { service: service, accessMode: accessMode }, service.key))) })] })), apps.length > 0 && (_jsxs(Collapsible, { className: "mt-6 rounded-2xl bg-card", children: [_jsxs(CollapsibleTrigger, { className: "group flex w-full items-center justify-between px-4 py-3 text-sm", children: [_jsxs("span", { className: "flex items-center gap-2 text-muted-foreground", children: [_jsx(IconPlugConnected, { size: 14 }), "Per-app status"] }), _jsx(IconChevronRight, { size: 14, className: "text-muted-foreground transition group-data-[state=open]:rotate-90" })] }), _jsxs(CollapsibleContent, { children: [_jsx("div", { className: "border-t", children: apps.map((app) => (_jsx(PerAppDetailRow, { app: app }, app.appId))) }), _jsxs("div", { className: "flex items-center justify-end gap-1.5 border-t px-4 py-2.5 text-xs text-muted-foreground", children: [_jsx(IconLink, { size: 12 }), _jsx(Link, { to: "/admin/vault", className: "hover:underline", children: "Open vault for advanced sharing" })] })] })] }))] }));
|
|
9
|
+
export default function IntegrationsRoute() {
|
|
10
|
+
const t = useT();
|
|
11
|
+
return (_jsx(DispatchShell, { title: "Integrations", description: "Connect the tools and services available to your agent.", children: _jsxs("div", { className: "mx-auto w-full max-w-5xl", children: [_jsx(McpIntegrationsLanding, { showTitle: false, showDescription: false }), _jsx("p", { className: "mt-5 text-right text-xs text-muted-foreground", children: _jsx(Link, { className: "underline-offset-4 hover:text-foreground hover:underline", to: "/integrations", children: t("integrations.connectedAccounts") }) })] }) }));
|
|
195
12
|
}
|
|
196
13
|
//# sourceMappingURL=integrations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integrations.js","sourceRoot":"","sources":["../../../src/routes/pages/integrations.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n useActionMutation,\n useActionQuery,\n} from \"@agent-native/core/client/hooks\";\nimport {\n getDefaultMcpIntegrations,\n McpIntegrationLogo,\n} from \"@agent-native/core/client/resources\";\nimport {\n IconChevronRight,\n IconLink,\n IconPlugConnected,\n IconSearch,\n} from \"@tabler/icons-react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useMemo, useState } from \"react\";\nimport { Link } from \"react-router\";\nimport { toast } from \"sonner\";\n\nimport { ActionQueryError } from \"../../components/action-query-error\";\nimport { DispatchShell } from \"../../components/dispatch-shell\";\nimport { Button } from \"../../components/ui/button\";\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"../../components/ui/collapsible\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"../../components/ui/dialog\";\nimport { Input } from \"../../components/ui/input\";\nimport { Label } from \"../../components/ui/label\";\nimport { Skeleton } from \"../../components/ui/skeleton\";\n\nexport function meta() {\n return [{ title: \"Connections — Dispatch\" }];\n}\n\ninterface AppRef {\n appId: string;\n appName: string;\n color: string;\n configured: boolean;\n vaultGranted: boolean;\n vaultSecretId?: string;\n}\n\ninterface Service {\n /** Credential key shared across apps (e.g. `OPENAI_API_KEY`). */\n key: string;\n /** Human label from the first app that declares it (`\"OpenAI\"`, `\"Stripe\"`). */\n label: string;\n /** Apps in the workspace that declare this credential. */\n apps: AppRef[];\n}\n\ninterface CatalogApp {\n appId: string;\n appName: string;\n color: string;\n url: string;\n reachable: boolean;\n integrations?: Array<{\n key: string;\n label: string;\n required: boolean;\n configured: boolean;\n vaultGranted: boolean;\n vaultSecretId?: string;\n }>;\n}\n\nfunction inferProviderFromKey(key: string, label: string): string {\n const haystack = `${key} ${label}`.toLowerCase();\n for (const provider of [\n \"google\",\n \"slack\",\n \"sendgrid\",\n \"github\",\n \"stripe\",\n \"hubspot\",\n \"jira\",\n \"bigquery\",\n \"anthropic\",\n \"openai\",\n ]) {\n if (haystack.includes(provider)) return provider;\n }\n return \"other\";\n}\n\nconst MCP_INTEGRATIONS_BY_ID = new Map(\n getDefaultMcpIntegrations().map((integration) => [\n integration.id,\n integration,\n ]),\n);\n\nconst CREDENTIAL_PROVIDER_LOGO_IDS: Record<string, string> = {\n google: \"google-workspace\",\n bigquery: \"google-workspace\",\n jira: \"atlassian\",\n};\n\nfunction credentialLogo(service: Service) {\n const providerId = inferProviderFromKey(service.key, service.label);\n const integration = MCP_INTEGRATIONS_BY_ID.get(\n CREDENTIAL_PROVIDER_LOGO_IDS[providerId] ?? providerId,\n );\n if (!integration?.logoUrl) {\n return <IconPlugConnected className=\"size-4 text-muted-foreground\" />;\n }\n return (\n <McpIntegrationLogo\n name={service.label}\n logoUrl={integration.logoUrl}\n integrationId={integration.id}\n className=\"size-full rounded-md border-0 bg-transparent\"\n imageClassName=\"size-full p-1\"\n />\n );\n}\n\nfunction ConnectDialog({\n service,\n open,\n onOpenChange,\n accessMode,\n}: {\n service: Service;\n open: boolean;\n onOpenChange: (next: boolean) => void;\n accessMode: \"all-apps\" | \"manual\";\n}) {\n const [value, setValue] = useState(\"\");\n const qc = useQueryClient();\n const configuredApps = service.apps.filter((app) => app.configured);\n const configuredAppNames = configuredApps\n .map((app) => app.appName)\n .join(\", \");\n\n const createSecret = useActionMutation(\"create-vault-secret\", {});\n const createGrant = useActionMutation(\"create-vault-grant\", {});\n const syncToApp = useActionMutation(\"sync-vault-to-app\", {});\n\n function reset() {\n setValue(\"\");\n }\n\n async function handleSave() {\n const trimmed = value.trim();\n if (!trimmed) {\n toast.error(\"Enter a value to save\");\n return;\n }\n try {\n // 1. Create the secret (or get the existing one — server treats key as\n // the unique identifier). The server returns { secret: { id, ... } }.\n const created = await createSecret.mutateAsync({\n credentialKey: service.key,\n name: service.label,\n value: trimmed,\n provider: inferProviderFromKey(service.key, service.label),\n });\n const secretId =\n (created as { secret?: { id?: string } })?.secret?.id ??\n (created as { id?: string })?.id;\n if (!secretId) {\n throw new Error(\"Secret created but id missing\");\n }\n\n // 2. Manual mode needs grants; all-apps mode only needs sync.\n if (accessMode === \"manual\") {\n const targets = service.apps.filter((a) => !a.vaultGranted);\n for (const app of targets) {\n try {\n await createGrant.mutateAsync({\n secretId,\n appId: app.appId,\n });\n } catch (err) {\n console.warn(`grant to ${app.appId} failed`, err);\n }\n }\n }\n for (const app of service.apps) {\n try {\n await syncToApp.mutateAsync({ appId: app.appId });\n } catch (err) {\n console.warn(`sync to ${app.appId} failed`, err);\n }\n }\n\n qc.invalidateQueries({\n queryKey: [\"action\", \"list-integrations-catalog\"],\n });\n toast.success(`Connected ${service.label}`);\n onOpenChange(false);\n reset();\n } catch (err: any) {\n toast.error(err?.message ?? \"Failed to save credential\");\n }\n }\n\n const pending =\n createSecret.isPending || createGrant.isPending || syncToApp.isPending;\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) reset();\n onOpenChange(next);\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Connect {service.label}</DialogTitle>\n <DialogDescription>\n {configuredApps.length > 0\n ? `Configured in ${configuredAppNames}. `\n : \"No app currently reports this key as configured. \"}\n Saving it creates a workspace vault entry using this exact key name.\n Apps need the same key name to use it.\n </DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n <div>\n <Label className=\"text-xs text-muted-foreground\">Key</Label>\n <div className=\"font-mono text-sm\">{service.key}</div>\n </div>\n <div>\n <Label htmlFor=\"connector-value\">Value</Label>\n <Input\n id=\"connector-value\"\n type=\"password\"\n autoComplete=\"off\"\n value={value}\n onChange={(e) => setValue(e.target.value)}\n placeholder={`Paste your ${service.label} key…`}\n autoFocus\n />\n </div>\n </div>\n <DialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={pending}\n >\n Cancel\n </Button>\n <Button onClick={handleSave} disabled={pending || !value.trim()}>\n {pending ? \"Saving…\" : \"Connect\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction ConnectorCard({\n service,\n accessMode,\n}: {\n service: Service;\n accessMode: \"all-apps\" | \"manual\";\n}) {\n const [open, setOpen] = useState(false);\n const configuredApps = service.apps.filter((app) => app.configured);\n const vaultApps = service.apps.filter((app) => app.vaultGranted);\n const isAvailable = configuredApps.length > 0 || vaultApps.length > 0;\n const configuredAppNames = configuredApps\n .map((app) => app.appName)\n .join(\", \");\n const vaultAppNames = vaultApps.map((app) => app.appName).join(\", \");\n\n return (\n <>\n <article className=\"flex min-w-0 items-center gap-3 border-b border-border/60 py-3.5 last:border-b-0\">\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-md bg-muted\">\n {credentialLogo(service)}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"truncate text-sm font-medium text-foreground\">\n {service.label}\n </span>\n <span\n className={\n isAvailable\n ? \"shrink-0 text-[11px] font-medium text-emerald-600 dark:text-emerald-400\"\n : \"shrink-0 text-[11px] font-medium text-muted-foreground\"\n }\n >\n {vaultApps.length > 0 ? \"Connected\" : \"App key\"}\n </span>\n </div>\n <p className=\"mt-0.5 truncate font-mono text-[11px] leading-5 text-muted-foreground/80\">\n {service.key}\n </p>\n <p className=\"mt-0.5 truncate text-xs leading-5 text-muted-foreground\">\n Declared by {service.apps.length}{\" \"}\n {service.apps.length === 1 ? \"app\" : \"apps\"}\n {configuredApps.length > 0 ? ` · ${configuredAppNames}` : \"\"}\n {vaultApps.length > 0 ? ` · Vault: ${vaultAppNames}` : \"\"}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"inline-flex h-8 shrink-0 items-center justify-center gap-1 rounded-md border border-border bg-background px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {isAvailable ? \"Manage\" : \"Connect\"}\n <IconChevronRight className=\"size-3.5 rtl:-scale-x-100\" />\n </button>\n </article>\n <ConnectDialog\n service={service}\n open={open}\n onOpenChange={setOpen}\n accessMode={accessMode}\n />\n </>\n );\n}\n\nfunction PerAppDetailRow({ app }: { app: CatalogApp }) {\n const total = (app.integrations ?? []).length;\n const ok = (app.integrations ?? []).filter((i) => i.configured).length;\n return (\n <div className=\"flex items-center justify-between border-t px-4 py-2.5 first:border-t-0\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <div\n className=\"h-5 w-5 rounded text-[10px] font-bold text-white flex items-center justify-center shrink-0\"\n style={{ backgroundColor: app.color }}\n >\n {app.appName.charAt(0).toUpperCase()}\n </div>\n <span className=\"text-sm truncate\">{app.appName}</span>\n {!app.reachable && (\n <span className=\"text-xs text-muted-foreground\">offline</span>\n )}\n </div>\n <span className=\"text-xs text-muted-foreground\">\n {total === 0 ? \"no integrations\" : `${ok}/${total}`}\n </span>\n </div>\n );\n}\n\nexport default function ConnectionsRoute() {\n const catalogQuery = useActionQuery(\"list-integrations-catalog\", {});\n const accessQuery = useActionQuery(\"get-vault-access-settings\", {});\n const { data: catalog } = catalogQuery;\n const { data: accessSettings } = accessQuery;\n const isLoading = catalogQuery.isLoading || accessQuery.isLoading;\n const apps = (catalog as CatalogApp[]) || [];\n const accessMode =\n (accessSettings as any)?.mode === \"manual\" ? \"manual\" : \"all-apps\";\n const [query, setQuery] = useState(\"\");\n\n const services = useMemo<Service[]>(() => {\n const map = new Map<string, Service>();\n for (const app of apps) {\n for (const intg of app.integrations ?? []) {\n if (!map.has(intg.key)) {\n map.set(intg.key, {\n key: intg.key,\n label: intg.label,\n apps: [],\n });\n }\n map.get(intg.key)!.apps.push({\n appId: app.appId,\n appName: app.appName,\n color: app.color,\n configured: intg.configured,\n vaultGranted: intg.vaultGranted,\n vaultSecretId: intg.vaultSecretId,\n });\n }\n }\n return Array.from(map.values()).sort((a, b) =>\n a.label.localeCompare(b.label),\n );\n }, [apps]);\n\n const filteredServices = useMemo(() => {\n const normalized = query.trim().toLowerCase();\n if (!normalized) return services;\n return services.filter(\n (service) =>\n service.label.toLowerCase().includes(normalized) ||\n service.key.toLowerCase().includes(normalized),\n );\n }, [query, services]);\n const available = filteredServices.filter(\n (s) => !s.apps.some((a) => a.configured),\n );\n const connected = filteredServices.filter((s) =>\n s.apps.some((a) => a.configured),\n );\n\n return (\n <DispatchShell\n title=\"Connections\"\n description=\"Connect workspace credentials declared by apps and manage how they are shared.\"\n >\n {catalogQuery.isError || accessQuery.isError ? (\n <ActionQueryError\n error={catalogQuery.error ?? accessQuery.error}\n onRetry={() => {\n void catalogQuery.refetch();\n void accessQuery.refetch();\n }}\n />\n ) : null}\n\n {!catalogQuery.isError && !isLoading ? (\n <div className=\"mb-5 flex items-center gap-3\">\n <div className=\"relative min-w-0 flex-1\">\n <IconSearch className=\"pointer-events-none absolute start-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder=\"Search credentials\"\n aria-label=\"Search credentials\"\n className=\"h-9 ps-9\"\n />\n </div>\n <span className=\"shrink-0 text-xs text-muted-foreground\">\n {filteredServices.length} credential\n {filteredServices.length === 1 ? \"\" : \"s\"}\n </span>\n </div>\n ) : null}\n\n {!catalogQuery.isError && isLoading && services.length === 0 && (\n <div className=\"overflow-hidden rounded-lg bg-card\">\n {Array.from({ length: 7 }).map((_, index) => (\n <div\n key={index}\n className=\"flex items-center gap-3 border-b px-4 py-3 last:border-b-0\"\n >\n <Skeleton className=\"size-8 rounded-md\" />\n <div className=\"min-w-0 flex-1\">\n <Skeleton className=\"h-4 w-40 max-w-full\" />\n <Skeleton className=\"mt-1.5 h-3 w-56 max-w-full\" />\n </div>\n <Skeleton className=\"h-6 w-20 rounded-full\" />\n </div>\n ))}\n </div>\n )}\n\n {!catalogQuery.isError && !isLoading && services.length === 0 && (\n <div className=\"rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground\">\n No apps with declared integrations are reachable yet.\n </div>\n )}\n\n {available.length > 0 && (\n <section>\n <div className=\"mb-3 flex items-baseline justify-between\">\n <h2 className=\"text-sm font-medium text-foreground\">\n Not configured in apps\n </h2>\n <span className=\"text-xs text-muted-foreground\">\n {available.length}\n </span>\n </div>\n <div className=\"overflow-hidden rounded-lg bg-card\">\n {available.map((service) => (\n <ConnectorCard\n key={service.key}\n service={service}\n accessMode={accessMode}\n />\n ))}\n </div>\n </section>\n )}\n\n {connected.length > 0 && (\n <section>\n <div className=\"mb-3 mt-2 flex items-baseline justify-between\">\n <h2 className=\"text-sm font-medium text-foreground\">\n Configured in apps\n </h2>\n <span className=\"text-xs text-muted-foreground\">\n {connected.length}\n </span>\n </div>\n <div className=\"overflow-hidden rounded-lg bg-card\">\n {connected.map((service) => (\n <ConnectorCard\n key={service.key}\n service={service}\n accessMode={accessMode}\n />\n ))}\n </div>\n </section>\n )}\n\n {apps.length > 0 && (\n <Collapsible className=\"mt-6 rounded-2xl bg-card\">\n <CollapsibleTrigger className=\"group flex w-full items-center justify-between px-4 py-3 text-sm\">\n <span className=\"flex items-center gap-2 text-muted-foreground\">\n <IconPlugConnected size={14} />\n Per-app status\n </span>\n <IconChevronRight\n size={14}\n className=\"text-muted-foreground transition group-data-[state=open]:rotate-90\"\n />\n </CollapsibleTrigger>\n <CollapsibleContent>\n <div className=\"border-t\">\n {apps.map((app) => (\n <PerAppDetailRow key={app.appId} app={app} />\n ))}\n </div>\n <div className=\"flex items-center justify-end gap-1.5 border-t px-4 py-2.5 text-xs text-muted-foreground\">\n <IconLink size={12} />\n <Link to=\"/admin/vault\" className=\"hover:underline\">\n Open vault for advanced sharing\n </Link>\n </div>\n </CollapsibleContent>\n </Collapsible>\n )}\n </DispatchShell>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"integrations.js","sourceRoot":"","sources":["../../../src/routes/pages/integrations.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAEhE,MAAM,UAAU,IAAI;IAClB,OAAO,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,iBAAiB;IACvC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,OAAO,CACL,KAAC,aAAa,IACZ,KAAK,EAAC,cAAc,EACpB,WAAW,EAAC,yDAAyD,YAErE,eAAK,SAAS,EAAC,0BAA0B,aACvC,KAAC,sBAAsB,IAAC,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,GAAI,EACpE,YAAG,SAAS,EAAC,+CAA+C,YAC1D,KAAC,IAAI,IACH,SAAS,EAAC,0DAA0D,EACpE,EAAE,EAAC,eAAe,YAEjB,CAAC,CAAC,gCAAgC,CAAC,GAC/B,GACL,IACA,GACQ,CACjB,CAAC;AACJ,CAAC","sourcesContent":["import { useT } from \"@agent-native/core/client/i18n\";\nimport { McpIntegrationsLanding } from \"@agent-native/core/client/integrations\";\nimport { Link } from \"react-router\";\n\nimport { DispatchShell } from \"../../components/dispatch-shell\";\n\nexport function meta() {\n return [{ title: \"Integrations — Dispatch\" }];\n}\n\nexport default function IntegrationsRoute() {\n const t = useT();\n return (\n <DispatchShell\n title=\"Integrations\"\n description=\"Connect the tools and services available to your agent.\"\n >\n <div className=\"mx-auto w-full max-w-5xl\">\n <McpIntegrationsLanding showTitle={false} showDescription={false} />\n <p className=\"mt-5 text-right text-xs text-muted-foreground\">\n <Link\n className=\"underline-offset-4 hover:text-foreground hover:underline\"\n to=\"/integrations\"\n >\n {t(\"integrations.connectedAccounts\")}\n </Link>\n </p>\n </div>\n </DispatchShell>\n );\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/dispatch",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.11",
|
|
4
4
|
"description": "Dispatch — workspace control plane for agent-native apps. Vault, integrations, destinations, scheduled jobs, and cross-app delegation, shipped as a single drop-in package.",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
102
102
|
"vite": "8.1.0",
|
|
103
103
|
"vitest": "^4.1.5",
|
|
104
|
-
"@agent-native/core": "0.168.
|
|
104
|
+
"@agent-native/core": "0.168.9"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
107
|
"@agent-native/core": ">=0.8.0",
|
|
@@ -2,7 +2,8 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { cn } from "../lib/utils";
|
|
4
4
|
|
|
5
|
-
export const APP_LIST_GRID_CLASS =
|
|
5
|
+
export const APP_LIST_GRID_CLASS =
|
|
6
|
+
"w-full xl:grid xl:grid-cols-2 xl:gap-3 xl:overflow-visible xl:rounded-none xl:bg-transparent";
|
|
6
7
|
export const APP_LIST_GRID_ROW_CLASS =
|
|
7
8
|
"xl:rounded-2xl xl:border xl:border-transparent xl:bg-card xl:transition-[background-color,border-color] xl:hover:border-border xl:focus-within:border-ring/50";
|
|
8
9
|
|
|
@@ -119,6 +119,25 @@ export function OtherAppsSection({
|
|
|
119
119
|
|
|
120
120
|
if (!isLoading && !hasError && entries.length === 0) return null;
|
|
121
121
|
|
|
122
|
+
const entryCards = entries.map((entry) =>
|
|
123
|
+
entry.kind === "template" ? (
|
|
124
|
+
<WorkspaceTemplateCard
|
|
125
|
+
key={`template:${templateKey(entry.template)}`}
|
|
126
|
+
template={entry.template}
|
|
127
|
+
labels={templateLabels}
|
|
128
|
+
catalog
|
|
129
|
+
className={APP_LIST_GRID_ROW_CLASS}
|
|
130
|
+
onRemixSuccess={onRemixSuccess}
|
|
131
|
+
/>
|
|
132
|
+
) : (
|
|
133
|
+
<ConnectedAppCard
|
|
134
|
+
key={`connected:${entry.app.id}`}
|
|
135
|
+
app={entry.app}
|
|
136
|
+
className={APP_LIST_GRID_ROW_CLASS}
|
|
137
|
+
/>
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
|
|
122
141
|
const content = (
|
|
123
142
|
<>
|
|
124
143
|
{heading ? (
|
|
@@ -149,23 +168,10 @@ export function OtherAppsSection({
|
|
|
149
168
|
<OtherAppsSkeletonList />
|
|
150
169
|
)
|
|
151
170
|
) : entries.length > 0 ? (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
template={entry.template}
|
|
157
|
-
labels={templateLabels}
|
|
158
|
-
catalog
|
|
159
|
-
className={APP_LIST_GRID_ROW_CLASS}
|
|
160
|
-
onRemixSuccess={onRemixSuccess}
|
|
161
|
-
/>
|
|
162
|
-
) : (
|
|
163
|
-
<ConnectedAppCard
|
|
164
|
-
key={`connected:${entry.app.id}`}
|
|
165
|
-
app={entry.app}
|
|
166
|
-
className={APP_LIST_GRID_ROW_CLASS}
|
|
167
|
-
/>
|
|
168
|
-
),
|
|
171
|
+
embeddedInList ? (
|
|
172
|
+
entryCards
|
|
173
|
+
) : (
|
|
174
|
+
<AppList className={APP_LIST_GRID_CLASS}>{entryCards}</AppList>
|
|
169
175
|
)
|
|
170
176
|
) : null}
|
|
171
177
|
</>
|
|
@@ -11,8 +11,11 @@ export default function AdminRoute() {
|
|
|
11
11
|
const isAutomationsRoute =
|
|
12
12
|
location.pathname === "/admin/automations" ||
|
|
13
13
|
location.pathname.startsWith("/admin/automations/");
|
|
14
|
+
const isIntegrationsRoute =
|
|
15
|
+
location.pathname === "/admin/integrations" ||
|
|
16
|
+
location.pathname.startsWith("/admin/integrations/");
|
|
14
17
|
|
|
15
|
-
if (isAutomationsRoute) return <Outlet />;
|
|
18
|
+
if (isAutomationsRoute || isIntegrationsRoute) return <Outlet />;
|
|
16
19
|
|
|
17
20
|
return (
|
|
18
21
|
<AdminShell>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import { McpIntegrationsLanding } from "@agent-native/core/client/integrations";
|
|
3
|
+
import { Link } from "react-router";
|
|
2
4
|
|
|
3
5
|
import { DispatchShell } from "../../components/dispatch-shell";
|
|
4
6
|
|
|
@@ -7,18 +9,23 @@ export function meta() {
|
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
export default function AgentIntegrationsRoute() {
|
|
10
|
-
const
|
|
11
|
-
const integrationsTab = settingsTabs.find((tab) => tab.id === "integrations");
|
|
12
|
-
if (!integrationsTab) {
|
|
13
|
-
throw new Error("The shared Integrations settings view is unavailable.");
|
|
14
|
-
}
|
|
15
|
-
|
|
12
|
+
const t = useT();
|
|
16
13
|
return (
|
|
17
14
|
<DispatchShell
|
|
18
15
|
title="Integrations"
|
|
19
16
|
description="Connect the tools and services available to your agent."
|
|
20
17
|
>
|
|
21
|
-
<div className="mx-auto w-full max-w-5xl">
|
|
18
|
+
<div className="mx-auto w-full max-w-5xl">
|
|
19
|
+
<McpIntegrationsLanding showTitle={false} showDescription={false} />
|
|
20
|
+
<p className="mt-5 text-right text-xs text-muted-foreground">
|
|
21
|
+
<Link
|
|
22
|
+
className="underline-offset-4 hover:text-foreground hover:underline"
|
|
23
|
+
to="/integrations"
|
|
24
|
+
>
|
|
25
|
+
{t("integrations.connectedAccounts")}
|
|
26
|
+
</Link>
|
|
27
|
+
</p>
|
|
28
|
+
</div>
|
|
22
29
|
</DispatchShell>
|
|
23
30
|
);
|
|
24
31
|
}
|
|
@@ -1,541 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
useActionQuery,
|
|
4
|
-
} from "@agent-native/core/client/hooks";
|
|
5
|
-
import {
|
|
6
|
-
getDefaultMcpIntegrations,
|
|
7
|
-
McpIntegrationLogo,
|
|
8
|
-
} from "@agent-native/core/client/resources";
|
|
9
|
-
import {
|
|
10
|
-
IconChevronRight,
|
|
11
|
-
IconLink,
|
|
12
|
-
IconPlugConnected,
|
|
13
|
-
IconSearch,
|
|
14
|
-
} from "@tabler/icons-react";
|
|
15
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
16
|
-
import { useMemo, useState } from "react";
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import { McpIntegrationsLanding } from "@agent-native/core/client/integrations";
|
|
17
3
|
import { Link } from "react-router";
|
|
18
|
-
import { toast } from "sonner";
|
|
19
4
|
|
|
20
|
-
import { ActionQueryError } from "../../components/action-query-error";
|
|
21
5
|
import { DispatchShell } from "../../components/dispatch-shell";
|
|
22
|
-
import { Button } from "../../components/ui/button";
|
|
23
|
-
import {
|
|
24
|
-
Collapsible,
|
|
25
|
-
CollapsibleContent,
|
|
26
|
-
CollapsibleTrigger,
|
|
27
|
-
} from "../../components/ui/collapsible";
|
|
28
|
-
import {
|
|
29
|
-
Dialog,
|
|
30
|
-
DialogContent,
|
|
31
|
-
DialogDescription,
|
|
32
|
-
DialogFooter,
|
|
33
|
-
DialogHeader,
|
|
34
|
-
DialogTitle,
|
|
35
|
-
} from "../../components/ui/dialog";
|
|
36
|
-
import { Input } from "../../components/ui/input";
|
|
37
|
-
import { Label } from "../../components/ui/label";
|
|
38
|
-
import { Skeleton } from "../../components/ui/skeleton";
|
|
39
6
|
|
|
40
7
|
export function meta() {
|
|
41
|
-
return [{ title: "
|
|
8
|
+
return [{ title: "Integrations — Dispatch" }];
|
|
42
9
|
}
|
|
43
10
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
appName: string;
|
|
47
|
-
color: string;
|
|
48
|
-
configured: boolean;
|
|
49
|
-
vaultGranted: boolean;
|
|
50
|
-
vaultSecretId?: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface Service {
|
|
54
|
-
/** Credential key shared across apps (e.g. `OPENAI_API_KEY`). */
|
|
55
|
-
key: string;
|
|
56
|
-
/** Human label from the first app that declares it (`"OpenAI"`, `"Stripe"`). */
|
|
57
|
-
label: string;
|
|
58
|
-
/** Apps in the workspace that declare this credential. */
|
|
59
|
-
apps: AppRef[];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
interface CatalogApp {
|
|
63
|
-
appId: string;
|
|
64
|
-
appName: string;
|
|
65
|
-
color: string;
|
|
66
|
-
url: string;
|
|
67
|
-
reachable: boolean;
|
|
68
|
-
integrations?: Array<{
|
|
69
|
-
key: string;
|
|
70
|
-
label: string;
|
|
71
|
-
required: boolean;
|
|
72
|
-
configured: boolean;
|
|
73
|
-
vaultGranted: boolean;
|
|
74
|
-
vaultSecretId?: string;
|
|
75
|
-
}>;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function inferProviderFromKey(key: string, label: string): string {
|
|
79
|
-
const haystack = `${key} ${label}`.toLowerCase();
|
|
80
|
-
for (const provider of [
|
|
81
|
-
"google",
|
|
82
|
-
"slack",
|
|
83
|
-
"sendgrid",
|
|
84
|
-
"github",
|
|
85
|
-
"stripe",
|
|
86
|
-
"hubspot",
|
|
87
|
-
"jira",
|
|
88
|
-
"bigquery",
|
|
89
|
-
"anthropic",
|
|
90
|
-
"openai",
|
|
91
|
-
]) {
|
|
92
|
-
if (haystack.includes(provider)) return provider;
|
|
93
|
-
}
|
|
94
|
-
return "other";
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const MCP_INTEGRATIONS_BY_ID = new Map(
|
|
98
|
-
getDefaultMcpIntegrations().map((integration) => [
|
|
99
|
-
integration.id,
|
|
100
|
-
integration,
|
|
101
|
-
]),
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
const CREDENTIAL_PROVIDER_LOGO_IDS: Record<string, string> = {
|
|
105
|
-
google: "google-workspace",
|
|
106
|
-
bigquery: "google-workspace",
|
|
107
|
-
jira: "atlassian",
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
function credentialLogo(service: Service) {
|
|
111
|
-
const providerId = inferProviderFromKey(service.key, service.label);
|
|
112
|
-
const integration = MCP_INTEGRATIONS_BY_ID.get(
|
|
113
|
-
CREDENTIAL_PROVIDER_LOGO_IDS[providerId] ?? providerId,
|
|
114
|
-
);
|
|
115
|
-
if (!integration?.logoUrl) {
|
|
116
|
-
return <IconPlugConnected className="size-4 text-muted-foreground" />;
|
|
117
|
-
}
|
|
11
|
+
export default function IntegrationsRoute() {
|
|
12
|
+
const t = useT();
|
|
118
13
|
return (
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
integrationId={integration.id}
|
|
123
|
-
className="size-full rounded-md border-0 bg-transparent"
|
|
124
|
-
imageClassName="size-full p-1"
|
|
125
|
-
/>
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function ConnectDialog({
|
|
130
|
-
service,
|
|
131
|
-
open,
|
|
132
|
-
onOpenChange,
|
|
133
|
-
accessMode,
|
|
134
|
-
}: {
|
|
135
|
-
service: Service;
|
|
136
|
-
open: boolean;
|
|
137
|
-
onOpenChange: (next: boolean) => void;
|
|
138
|
-
accessMode: "all-apps" | "manual";
|
|
139
|
-
}) {
|
|
140
|
-
const [value, setValue] = useState("");
|
|
141
|
-
const qc = useQueryClient();
|
|
142
|
-
const configuredApps = service.apps.filter((app) => app.configured);
|
|
143
|
-
const configuredAppNames = configuredApps
|
|
144
|
-
.map((app) => app.appName)
|
|
145
|
-
.join(", ");
|
|
146
|
-
|
|
147
|
-
const createSecret = useActionMutation("create-vault-secret", {});
|
|
148
|
-
const createGrant = useActionMutation("create-vault-grant", {});
|
|
149
|
-
const syncToApp = useActionMutation("sync-vault-to-app", {});
|
|
150
|
-
|
|
151
|
-
function reset() {
|
|
152
|
-
setValue("");
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function handleSave() {
|
|
156
|
-
const trimmed = value.trim();
|
|
157
|
-
if (!trimmed) {
|
|
158
|
-
toast.error("Enter a value to save");
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
try {
|
|
162
|
-
// 1. Create the secret (or get the existing one — server treats key as
|
|
163
|
-
// the unique identifier). The server returns { secret: { id, ... } }.
|
|
164
|
-
const created = await createSecret.mutateAsync({
|
|
165
|
-
credentialKey: service.key,
|
|
166
|
-
name: service.label,
|
|
167
|
-
value: trimmed,
|
|
168
|
-
provider: inferProviderFromKey(service.key, service.label),
|
|
169
|
-
});
|
|
170
|
-
const secretId =
|
|
171
|
-
(created as { secret?: { id?: string } })?.secret?.id ??
|
|
172
|
-
(created as { id?: string })?.id;
|
|
173
|
-
if (!secretId) {
|
|
174
|
-
throw new Error("Secret created but id missing");
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// 2. Manual mode needs grants; all-apps mode only needs sync.
|
|
178
|
-
if (accessMode === "manual") {
|
|
179
|
-
const targets = service.apps.filter((a) => !a.vaultGranted);
|
|
180
|
-
for (const app of targets) {
|
|
181
|
-
try {
|
|
182
|
-
await createGrant.mutateAsync({
|
|
183
|
-
secretId,
|
|
184
|
-
appId: app.appId,
|
|
185
|
-
});
|
|
186
|
-
} catch (err) {
|
|
187
|
-
console.warn(`grant to ${app.appId} failed`, err);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
for (const app of service.apps) {
|
|
192
|
-
try {
|
|
193
|
-
await syncToApp.mutateAsync({ appId: app.appId });
|
|
194
|
-
} catch (err) {
|
|
195
|
-
console.warn(`sync to ${app.appId} failed`, err);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
qc.invalidateQueries({
|
|
200
|
-
queryKey: ["action", "list-integrations-catalog"],
|
|
201
|
-
});
|
|
202
|
-
toast.success(`Connected ${service.label}`);
|
|
203
|
-
onOpenChange(false);
|
|
204
|
-
reset();
|
|
205
|
-
} catch (err: any) {
|
|
206
|
-
toast.error(err?.message ?? "Failed to save credential");
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const pending =
|
|
211
|
-
createSecret.isPending || createGrant.isPending || syncToApp.isPending;
|
|
212
|
-
|
|
213
|
-
return (
|
|
214
|
-
<Dialog
|
|
215
|
-
open={open}
|
|
216
|
-
onOpenChange={(next) => {
|
|
217
|
-
if (!next) reset();
|
|
218
|
-
onOpenChange(next);
|
|
219
|
-
}}
|
|
14
|
+
<DispatchShell
|
|
15
|
+
title="Integrations"
|
|
16
|
+
description="Connect the tools and services available to your agent."
|
|
220
17
|
>
|
|
221
|
-
<
|
|
222
|
-
<
|
|
223
|
-
|
|
224
|
-
<
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
: "No app currently reports this key as configured. "}
|
|
228
|
-
Saving it creates a workspace vault entry using this exact key name.
|
|
229
|
-
Apps need the same key name to use it.
|
|
230
|
-
</DialogDescription>
|
|
231
|
-
</DialogHeader>
|
|
232
|
-
<div className="space-y-3">
|
|
233
|
-
<div>
|
|
234
|
-
<Label className="text-xs text-muted-foreground">Key</Label>
|
|
235
|
-
<div className="font-mono text-sm">{service.key}</div>
|
|
236
|
-
</div>
|
|
237
|
-
<div>
|
|
238
|
-
<Label htmlFor="connector-value">Value</Label>
|
|
239
|
-
<Input
|
|
240
|
-
id="connector-value"
|
|
241
|
-
type="password"
|
|
242
|
-
autoComplete="off"
|
|
243
|
-
value={value}
|
|
244
|
-
onChange={(e) => setValue(e.target.value)}
|
|
245
|
-
placeholder={`Paste your ${service.label} key…`}
|
|
246
|
-
autoFocus
|
|
247
|
-
/>
|
|
248
|
-
</div>
|
|
249
|
-
</div>
|
|
250
|
-
<DialogFooter>
|
|
251
|
-
<Button
|
|
252
|
-
variant="outline"
|
|
253
|
-
onClick={() => onOpenChange(false)}
|
|
254
|
-
disabled={pending}
|
|
18
|
+
<div className="mx-auto w-full max-w-5xl">
|
|
19
|
+
<McpIntegrationsLanding showTitle={false} showDescription={false} />
|
|
20
|
+
<p className="mt-5 text-right text-xs text-muted-foreground">
|
|
21
|
+
<Link
|
|
22
|
+
className="underline-offset-4 hover:text-foreground hover:underline"
|
|
23
|
+
to="/integrations"
|
|
255
24
|
>
|
|
256
|
-
|
|
257
|
-
</
|
|
258
|
-
|
|
259
|
-
{pending ? "Saving…" : "Connect"}
|
|
260
|
-
</Button>
|
|
261
|
-
</DialogFooter>
|
|
262
|
-
</DialogContent>
|
|
263
|
-
</Dialog>
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function ConnectorCard({
|
|
268
|
-
service,
|
|
269
|
-
accessMode,
|
|
270
|
-
}: {
|
|
271
|
-
service: Service;
|
|
272
|
-
accessMode: "all-apps" | "manual";
|
|
273
|
-
}) {
|
|
274
|
-
const [open, setOpen] = useState(false);
|
|
275
|
-
const configuredApps = service.apps.filter((app) => app.configured);
|
|
276
|
-
const vaultApps = service.apps.filter((app) => app.vaultGranted);
|
|
277
|
-
const isAvailable = configuredApps.length > 0 || vaultApps.length > 0;
|
|
278
|
-
const configuredAppNames = configuredApps
|
|
279
|
-
.map((app) => app.appName)
|
|
280
|
-
.join(", ");
|
|
281
|
-
const vaultAppNames = vaultApps.map((app) => app.appName).join(", ");
|
|
282
|
-
|
|
283
|
-
return (
|
|
284
|
-
<>
|
|
285
|
-
<article className="flex min-w-0 items-center gap-3 border-b border-border/60 py-3.5 last:border-b-0">
|
|
286
|
-
<div className="flex size-8 shrink-0 items-center justify-center rounded-md bg-muted">
|
|
287
|
-
{credentialLogo(service)}
|
|
288
|
-
</div>
|
|
289
|
-
<div className="min-w-0 flex-1">
|
|
290
|
-
<div className="flex min-w-0 items-center gap-2">
|
|
291
|
-
<span className="truncate text-sm font-medium text-foreground">
|
|
292
|
-
{service.label}
|
|
293
|
-
</span>
|
|
294
|
-
<span
|
|
295
|
-
className={
|
|
296
|
-
isAvailable
|
|
297
|
-
? "shrink-0 text-[11px] font-medium text-emerald-600 dark:text-emerald-400"
|
|
298
|
-
: "shrink-0 text-[11px] font-medium text-muted-foreground"
|
|
299
|
-
}
|
|
300
|
-
>
|
|
301
|
-
{vaultApps.length > 0 ? "Connected" : "App key"}
|
|
302
|
-
</span>
|
|
303
|
-
</div>
|
|
304
|
-
<p className="mt-0.5 truncate font-mono text-[11px] leading-5 text-muted-foreground/80">
|
|
305
|
-
{service.key}
|
|
306
|
-
</p>
|
|
307
|
-
<p className="mt-0.5 truncate text-xs leading-5 text-muted-foreground">
|
|
308
|
-
Declared by {service.apps.length}{" "}
|
|
309
|
-
{service.apps.length === 1 ? "app" : "apps"}
|
|
310
|
-
{configuredApps.length > 0 ? ` · ${configuredAppNames}` : ""}
|
|
311
|
-
{vaultApps.length > 0 ? ` · Vault: ${vaultAppNames}` : ""}
|
|
312
|
-
</p>
|
|
313
|
-
</div>
|
|
314
|
-
<button
|
|
315
|
-
type="button"
|
|
316
|
-
onClick={() => setOpen(true)}
|
|
317
|
-
className="inline-flex h-8 shrink-0 items-center justify-center gap-1 rounded-md border border-border bg-background px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
318
|
-
>
|
|
319
|
-
{isAvailable ? "Manage" : "Connect"}
|
|
320
|
-
<IconChevronRight className="size-3.5 rtl:-scale-x-100" />
|
|
321
|
-
</button>
|
|
322
|
-
</article>
|
|
323
|
-
<ConnectDialog
|
|
324
|
-
service={service}
|
|
325
|
-
open={open}
|
|
326
|
-
onOpenChange={setOpen}
|
|
327
|
-
accessMode={accessMode}
|
|
328
|
-
/>
|
|
329
|
-
</>
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function PerAppDetailRow({ app }: { app: CatalogApp }) {
|
|
334
|
-
const total = (app.integrations ?? []).length;
|
|
335
|
-
const ok = (app.integrations ?? []).filter((i) => i.configured).length;
|
|
336
|
-
return (
|
|
337
|
-
<div className="flex items-center justify-between border-t px-4 py-2.5 first:border-t-0">
|
|
338
|
-
<div className="flex items-center gap-2 min-w-0">
|
|
339
|
-
<div
|
|
340
|
-
className="h-5 w-5 rounded text-[10px] font-bold text-white flex items-center justify-center shrink-0"
|
|
341
|
-
style={{ backgroundColor: app.color }}
|
|
342
|
-
>
|
|
343
|
-
{app.appName.charAt(0).toUpperCase()}
|
|
344
|
-
</div>
|
|
345
|
-
<span className="text-sm truncate">{app.appName}</span>
|
|
346
|
-
{!app.reachable && (
|
|
347
|
-
<span className="text-xs text-muted-foreground">offline</span>
|
|
348
|
-
)}
|
|
25
|
+
{t("integrations.connectedAccounts")}
|
|
26
|
+
</Link>
|
|
27
|
+
</p>
|
|
349
28
|
</div>
|
|
350
|
-
<span className="text-xs text-muted-foreground">
|
|
351
|
-
{total === 0 ? "no integrations" : `${ok}/${total}`}
|
|
352
|
-
</span>
|
|
353
|
-
</div>
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
export default function ConnectionsRoute() {
|
|
358
|
-
const catalogQuery = useActionQuery("list-integrations-catalog", {});
|
|
359
|
-
const accessQuery = useActionQuery("get-vault-access-settings", {});
|
|
360
|
-
const { data: catalog } = catalogQuery;
|
|
361
|
-
const { data: accessSettings } = accessQuery;
|
|
362
|
-
const isLoading = catalogQuery.isLoading || accessQuery.isLoading;
|
|
363
|
-
const apps = (catalog as CatalogApp[]) || [];
|
|
364
|
-
const accessMode =
|
|
365
|
-
(accessSettings as any)?.mode === "manual" ? "manual" : "all-apps";
|
|
366
|
-
const [query, setQuery] = useState("");
|
|
367
|
-
|
|
368
|
-
const services = useMemo<Service[]>(() => {
|
|
369
|
-
const map = new Map<string, Service>();
|
|
370
|
-
for (const app of apps) {
|
|
371
|
-
for (const intg of app.integrations ?? []) {
|
|
372
|
-
if (!map.has(intg.key)) {
|
|
373
|
-
map.set(intg.key, {
|
|
374
|
-
key: intg.key,
|
|
375
|
-
label: intg.label,
|
|
376
|
-
apps: [],
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
map.get(intg.key)!.apps.push({
|
|
380
|
-
appId: app.appId,
|
|
381
|
-
appName: app.appName,
|
|
382
|
-
color: app.color,
|
|
383
|
-
configured: intg.configured,
|
|
384
|
-
vaultGranted: intg.vaultGranted,
|
|
385
|
-
vaultSecretId: intg.vaultSecretId,
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
return Array.from(map.values()).sort((a, b) =>
|
|
390
|
-
a.label.localeCompare(b.label),
|
|
391
|
-
);
|
|
392
|
-
}, [apps]);
|
|
393
|
-
|
|
394
|
-
const filteredServices = useMemo(() => {
|
|
395
|
-
const normalized = query.trim().toLowerCase();
|
|
396
|
-
if (!normalized) return services;
|
|
397
|
-
return services.filter(
|
|
398
|
-
(service) =>
|
|
399
|
-
service.label.toLowerCase().includes(normalized) ||
|
|
400
|
-
service.key.toLowerCase().includes(normalized),
|
|
401
|
-
);
|
|
402
|
-
}, [query, services]);
|
|
403
|
-
const available = filteredServices.filter(
|
|
404
|
-
(s) => !s.apps.some((a) => a.configured),
|
|
405
|
-
);
|
|
406
|
-
const connected = filteredServices.filter((s) =>
|
|
407
|
-
s.apps.some((a) => a.configured),
|
|
408
|
-
);
|
|
409
|
-
|
|
410
|
-
return (
|
|
411
|
-
<DispatchShell
|
|
412
|
-
title="Connections"
|
|
413
|
-
description="Connect workspace credentials declared by apps and manage how they are shared."
|
|
414
|
-
>
|
|
415
|
-
{catalogQuery.isError || accessQuery.isError ? (
|
|
416
|
-
<ActionQueryError
|
|
417
|
-
error={catalogQuery.error ?? accessQuery.error}
|
|
418
|
-
onRetry={() => {
|
|
419
|
-
void catalogQuery.refetch();
|
|
420
|
-
void accessQuery.refetch();
|
|
421
|
-
}}
|
|
422
|
-
/>
|
|
423
|
-
) : null}
|
|
424
|
-
|
|
425
|
-
{!catalogQuery.isError && !isLoading ? (
|
|
426
|
-
<div className="mb-5 flex items-center gap-3">
|
|
427
|
-
<div className="relative min-w-0 flex-1">
|
|
428
|
-
<IconSearch className="pointer-events-none absolute start-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
429
|
-
<Input
|
|
430
|
-
value={query}
|
|
431
|
-
onChange={(event) => setQuery(event.target.value)}
|
|
432
|
-
placeholder="Search credentials"
|
|
433
|
-
aria-label="Search credentials"
|
|
434
|
-
className="h-9 ps-9"
|
|
435
|
-
/>
|
|
436
|
-
</div>
|
|
437
|
-
<span className="shrink-0 text-xs text-muted-foreground">
|
|
438
|
-
{filteredServices.length} credential
|
|
439
|
-
{filteredServices.length === 1 ? "" : "s"}
|
|
440
|
-
</span>
|
|
441
|
-
</div>
|
|
442
|
-
) : null}
|
|
443
|
-
|
|
444
|
-
{!catalogQuery.isError && isLoading && services.length === 0 && (
|
|
445
|
-
<div className="overflow-hidden rounded-lg bg-card">
|
|
446
|
-
{Array.from({ length: 7 }).map((_, index) => (
|
|
447
|
-
<div
|
|
448
|
-
key={index}
|
|
449
|
-
className="flex items-center gap-3 border-b px-4 py-3 last:border-b-0"
|
|
450
|
-
>
|
|
451
|
-
<Skeleton className="size-8 rounded-md" />
|
|
452
|
-
<div className="min-w-0 flex-1">
|
|
453
|
-
<Skeleton className="h-4 w-40 max-w-full" />
|
|
454
|
-
<Skeleton className="mt-1.5 h-3 w-56 max-w-full" />
|
|
455
|
-
</div>
|
|
456
|
-
<Skeleton className="h-6 w-20 rounded-full" />
|
|
457
|
-
</div>
|
|
458
|
-
))}
|
|
459
|
-
</div>
|
|
460
|
-
)}
|
|
461
|
-
|
|
462
|
-
{!catalogQuery.isError && !isLoading && services.length === 0 && (
|
|
463
|
-
<div className="rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground">
|
|
464
|
-
No apps with declared integrations are reachable yet.
|
|
465
|
-
</div>
|
|
466
|
-
)}
|
|
467
|
-
|
|
468
|
-
{available.length > 0 && (
|
|
469
|
-
<section>
|
|
470
|
-
<div className="mb-3 flex items-baseline justify-between">
|
|
471
|
-
<h2 className="text-sm font-medium text-foreground">
|
|
472
|
-
Not configured in apps
|
|
473
|
-
</h2>
|
|
474
|
-
<span className="text-xs text-muted-foreground">
|
|
475
|
-
{available.length}
|
|
476
|
-
</span>
|
|
477
|
-
</div>
|
|
478
|
-
<div className="overflow-hidden rounded-lg bg-card">
|
|
479
|
-
{available.map((service) => (
|
|
480
|
-
<ConnectorCard
|
|
481
|
-
key={service.key}
|
|
482
|
-
service={service}
|
|
483
|
-
accessMode={accessMode}
|
|
484
|
-
/>
|
|
485
|
-
))}
|
|
486
|
-
</div>
|
|
487
|
-
</section>
|
|
488
|
-
)}
|
|
489
|
-
|
|
490
|
-
{connected.length > 0 && (
|
|
491
|
-
<section>
|
|
492
|
-
<div className="mb-3 mt-2 flex items-baseline justify-between">
|
|
493
|
-
<h2 className="text-sm font-medium text-foreground">
|
|
494
|
-
Configured in apps
|
|
495
|
-
</h2>
|
|
496
|
-
<span className="text-xs text-muted-foreground">
|
|
497
|
-
{connected.length}
|
|
498
|
-
</span>
|
|
499
|
-
</div>
|
|
500
|
-
<div className="overflow-hidden rounded-lg bg-card">
|
|
501
|
-
{connected.map((service) => (
|
|
502
|
-
<ConnectorCard
|
|
503
|
-
key={service.key}
|
|
504
|
-
service={service}
|
|
505
|
-
accessMode={accessMode}
|
|
506
|
-
/>
|
|
507
|
-
))}
|
|
508
|
-
</div>
|
|
509
|
-
</section>
|
|
510
|
-
)}
|
|
511
|
-
|
|
512
|
-
{apps.length > 0 && (
|
|
513
|
-
<Collapsible className="mt-6 rounded-2xl bg-card">
|
|
514
|
-
<CollapsibleTrigger className="group flex w-full items-center justify-between px-4 py-3 text-sm">
|
|
515
|
-
<span className="flex items-center gap-2 text-muted-foreground">
|
|
516
|
-
<IconPlugConnected size={14} />
|
|
517
|
-
Per-app status
|
|
518
|
-
</span>
|
|
519
|
-
<IconChevronRight
|
|
520
|
-
size={14}
|
|
521
|
-
className="text-muted-foreground transition group-data-[state=open]:rotate-90"
|
|
522
|
-
/>
|
|
523
|
-
</CollapsibleTrigger>
|
|
524
|
-
<CollapsibleContent>
|
|
525
|
-
<div className="border-t">
|
|
526
|
-
{apps.map((app) => (
|
|
527
|
-
<PerAppDetailRow key={app.appId} app={app} />
|
|
528
|
-
))}
|
|
529
|
-
</div>
|
|
530
|
-
<div className="flex items-center justify-end gap-1.5 border-t px-4 py-2.5 text-xs text-muted-foreground">
|
|
531
|
-
<IconLink size={12} />
|
|
532
|
-
<Link to="/admin/vault" className="hover:underline">
|
|
533
|
-
Open vault for advanced sharing
|
|
534
|
-
</Link>
|
|
535
|
-
</div>
|
|
536
|
-
</CollapsibleContent>
|
|
537
|
-
</Collapsible>
|
|
538
|
-
)}
|
|
539
29
|
</DispatchShell>
|
|
540
30
|
);
|
|
541
31
|
}
|