@fayz-ai/admin 0.61.0 → 0.63.0
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/{CommandPalette-B5PMQFDG.js → CommandPalette-XDJJ5E3O.js} +3 -3
- package/dist/{CommandPalette-B5PMQFDG.js.map → CommandPalette-XDJJ5E3O.js.map} +1 -1
- package/dist/ConnectedBrandingSettings-43ZM3HCI.js +680 -0
- package/dist/ConnectedBrandingSettings-43ZM3HCI.js.map +1 -0
- package/dist/ConnectedCompanySettings-I4MALI2Q.js +197 -0
- package/dist/ConnectedCompanySettings-I4MALI2Q.js.map +1 -0
- package/dist/ConnectedModulesSettings-A2VJ5BMJ.js +563 -0
- package/dist/ConnectedModulesSettings-A2VJ5BMJ.js.map +1 -0
- package/dist/ConnectedSecuritySettings-25AHA4HJ.js +189 -0
- package/dist/ConnectedSecuritySettings-25AHA4HJ.js.map +1 -0
- package/dist/ConnectedUserProfile-E7USJRUE.js +424 -0
- package/dist/ConnectedUserProfile-E7USJRUE.js.map +1 -0
- package/dist/FieldRulesEditor-YGIPKI5T.js +129 -0
- package/dist/FieldRulesEditor-YGIPKI5T.js.map +1 -0
- package/dist/GettingStartedWidget-HMIJ3Y24.js +12 -0
- package/dist/GettingStartedWidget-HMIJ3Y24.js.map +1 -0
- package/dist/PermissionProfilesTab-3ZPRCHMZ.js +548 -0
- package/dist/PermissionProfilesTab-3ZPRCHMZ.js.map +1 -0
- package/dist/PersonTimeline-HQZT527S.js +3 -0
- package/dist/{PersonTimeline-X7JTPUWJ.js.map → PersonTimeline-HQZT527S.js.map} +1 -1
- package/dist/ProjectsSettings-7O2F27MD.js +5 -0
- package/dist/ProjectsSettings-7O2F27MD.js.map +1 -0
- package/dist/ScheduleEditor-54ELWR45.js +8 -0
- package/dist/{ScheduleEditor-L2UJXKMY.js.map → ScheduleEditor-54ELWR45.js.map} +1 -1
- package/dist/SeuSiteWidget-QLSUVC63.js +6 -0
- package/dist/SeuSiteWidget-QLSUVC63.js.map +1 -0
- package/dist/SubscriptionPage-5GE5HFJK.js +17 -0
- package/dist/{SubscriptionPage-IDVEFLZ5.js.map → SubscriptionPage-5GE5HFJK.js.map} +1 -1
- package/dist/TeamTab-6RAULFLW.js +502 -0
- package/dist/TeamTab-6RAULFLW.js.map +1 -0
- package/dist/TrialWidget-Z6CLUCU3.js +7 -0
- package/dist/TrialWidget-Z6CLUCU3.js.map +1 -0
- package/dist/app/AdminShell.d.ts.map +1 -1
- package/dist/app/admin-app.d.ts.map +1 -1
- package/dist/app/config.d.ts +14 -0
- package/dist/app/config.d.ts.map +1 -1
- package/dist/app/routing.d.ts +6 -0
- package/dist/app/routing.d.ts.map +1 -1
- package/dist/app/scaffold.d.ts.map +1 -1
- package/dist/billing/index.js +9 -4
- package/dist/chunk-2H46WDG5.js +53 -0
- package/dist/chunk-2H46WDG5.js.map +1 -0
- package/dist/chunk-3HLX7YB2.js +94 -0
- package/dist/chunk-3HLX7YB2.js.map +1 -0
- package/dist/chunk-3OUD4XBK.js +3 -0
- package/dist/chunk-3OUD4XBK.js.map +1 -0
- package/dist/{chunk-IXSY4GPK.js → chunk-5D5QLMOQ.js} +5 -3
- package/dist/chunk-5D5QLMOQ.js.map +1 -0
- package/dist/chunk-5WR62X2R.js +1081 -0
- package/dist/chunk-5WR62X2R.js.map +1 -0
- package/dist/{chunk-KPYRTOCK.js → chunk-6CQFGMEO.js} +4 -140
- package/dist/chunk-6CQFGMEO.js.map +1 -0
- package/dist/chunk-6MMSYC2I.js +116 -0
- package/dist/chunk-6MMSYC2I.js.map +1 -0
- package/dist/chunk-6QFOE3UD.js +1466 -0
- package/dist/chunk-6QFOE3UD.js.map +1 -0
- package/dist/chunk-6UUFVQ2E.js +283 -0
- package/dist/chunk-6UUFVQ2E.js.map +1 -0
- package/dist/chunk-7WNLL3F6.js +7889 -0
- package/dist/chunk-7WNLL3F6.js.map +1 -0
- package/dist/chunk-A74IHAQE.js +439 -0
- package/dist/chunk-A74IHAQE.js.map +1 -0
- package/dist/{chunk-JOBRLBIQ.js → chunk-AFA6IO23.js} +4 -3
- package/dist/chunk-AFA6IO23.js.map +1 -0
- package/dist/{chunk-P5UIGQFY.js → chunk-ALCSIN3Z.js} +5 -5
- package/dist/chunk-ALCSIN3Z.js.map +1 -0
- package/dist/chunk-AWM4AMBS.js +39 -0
- package/dist/chunk-AWM4AMBS.js.map +1 -0
- package/dist/{chunk-OQARLKZN.js → chunk-BG5Z6KLN.js} +6 -18
- package/dist/chunk-BG5Z6KLN.js.map +1 -0
- package/dist/chunk-DPNFZONC.js +19 -0
- package/dist/chunk-DPNFZONC.js.map +1 -0
- package/dist/chunk-DRCV643L.js +193 -0
- package/dist/chunk-DRCV643L.js.map +1 -0
- package/dist/chunk-E6DF3VR6.js +206 -0
- package/dist/chunk-E6DF3VR6.js.map +1 -0
- package/dist/chunk-ENHCVGXZ.js +120 -0
- package/dist/chunk-ENHCVGXZ.js.map +1 -0
- package/dist/chunk-FK6RHGI7.js +56 -0
- package/dist/chunk-FK6RHGI7.js.map +1 -0
- package/dist/chunk-G5HNA5QK.js +233 -0
- package/dist/chunk-G5HNA5QK.js.map +1 -0
- package/dist/chunk-HCFO4Z6A.js +53 -0
- package/dist/chunk-HCFO4Z6A.js.map +1 -0
- package/dist/chunk-JFIU6NQP.js +51 -0
- package/dist/chunk-JFIU6NQP.js.map +1 -0
- package/dist/chunk-JRZJBMOY.js +3 -0
- package/dist/chunk-JRZJBMOY.js.map +1 -0
- package/dist/{chunk-3YP7KW47.js → chunk-JVVOL4BB.js} +41 -512
- package/dist/chunk-JVVOL4BB.js.map +1 -0
- package/dist/chunk-KAQ3UIAA.js +415 -0
- package/dist/chunk-KAQ3UIAA.js.map +1 -0
- package/dist/{chunk-P6XUDT2P.js → chunk-KJELMULA.js} +4 -51
- package/dist/chunk-KJELMULA.js.map +1 -0
- package/dist/chunk-KLO7DKXX.js +1651 -0
- package/dist/chunk-KLO7DKXX.js.map +1 -0
- package/dist/chunk-M2CMDWUI.js +75 -0
- package/dist/chunk-M2CMDWUI.js.map +1 -0
- package/dist/chunk-PQ47MLTH.js +255 -0
- package/dist/chunk-PQ47MLTH.js.map +1 -0
- package/dist/{chunk-VX3WJLKK.js → chunk-QD7BMUDF.js} +17 -8
- package/dist/chunk-QD7BMUDF.js.map +1 -0
- package/dist/{chunk-E4MESMNR.js → chunk-QTSKCPSW.js} +7 -10
- package/dist/chunk-QTSKCPSW.js.map +1 -0
- package/dist/chunk-S56FQKPL.js +186 -0
- package/dist/chunk-S56FQKPL.js.map +1 -0
- package/dist/chunk-SGAFPNMA.js +75 -0
- package/dist/chunk-SGAFPNMA.js.map +1 -0
- package/dist/{chunk-OD7P4L3D.js → chunk-SOKCLVM4.js} +3 -3
- package/dist/{chunk-OD7P4L3D.js.map → chunk-SOKCLVM4.js.map} +1 -1
- package/dist/chunk-TL42NKTH.js +3 -0
- package/dist/chunk-TL42NKTH.js.map +1 -0
- package/dist/chunk-U6ZCO2PS.js +2964 -0
- package/dist/chunk-U6ZCO2PS.js.map +1 -0
- package/dist/chunk-VSXNROYH.js +16 -0
- package/dist/chunk-VSXNROYH.js.map +1 -0
- package/dist/chunk-WPFZCYZL.js +619 -0
- package/dist/chunk-WPFZCYZL.js.map +1 -0
- package/dist/chunk-XFTNIBDW.js +23 -0
- package/dist/chunk-XFTNIBDW.js.map +1 -0
- package/dist/chunk-Y66JDL6I.js +1020 -0
- package/dist/chunk-Y66JDL6I.js.map +1 -0
- package/dist/chunk-YEHIBPIX.js +137 -0
- package/dist/chunk-YEHIBPIX.js.map +1 -0
- package/dist/chunk-YKPPCV2Z.js +53 -0
- package/dist/chunk-YKPPCV2Z.js.map +1 -0
- package/dist/components/chat/markdown.d.ts.map +1 -1
- package/dist/components/plugins/PluginSettingsPanel.d.ts.map +1 -1
- package/dist/components/settings/SettingsPage.d.ts.map +1 -1
- package/dist/components/shared/ConnectionIndicator.d.ts.map +1 -1
- package/dist/connect-XKQJOWR7.js +18 -0
- package/dist/connect-XKQJOWR7.js.map +1 -0
- package/dist/crud/CrudDetailPage.d.ts.map +1 -1
- package/dist/crud/CrudListView.d.ts.map +1 -1
- package/dist/crud/CrudPage.d.ts.map +1 -1
- package/dist/crud/RelatedListTab.d.ts.map +1 -1
- package/dist/crud/archetypes/EntityLink.d.ts +1 -1
- package/dist/crud/archetypes/EntityLink.d.ts.map +1 -1
- package/dist/crud/record-chrome.d.ts.map +1 -1
- package/dist/entity-routes-PGU7EHAD.js +4 -0
- package/dist/{entity-routes-LXOBW5IB.js.map → entity-routes-PGU7EHAD.js.map} +1 -1
- package/dist/hooks/useModuleNavigation.d.ts.map +1 -1
- package/dist/impersonation/ImpersonateDialog.d.ts.map +1 -1
- package/dist/impersonation/session.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11922 -33564
- package/dist/index.js.map +1 -1
- package/dist/lib/connection.d.ts +14 -0
- package/dist/lib/connection.d.ts.map +1 -1
- package/dist/lib/entity-routes.d.ts.map +1 -1
- package/dist/org/index.js +4 -2
- package/dist/plugins/PluginRegistryManager.d.ts.map +1 -1
- package/dist/settings/MessageTemplateRow.d.ts.map +1 -1
- package/dist/timeline/PersonTimeline.d.ts.map +1 -1
- package/dist/timeline/TimelineList.d.ts.map +1 -1
- package/dist/units/UnitRailPage.d.ts.map +1 -1
- package/dist/units/useUnitFilter.d.ts.map +1 -1
- package/package.json +5 -5
- package/dist/PersonTimeline-X7JTPUWJ.js +0 -3
- package/dist/ScheduleEditor-L2UJXKMY.js +0 -6
- package/dist/SubscriptionPage-IDVEFLZ5.js +0 -8
- package/dist/chunk-3YP7KW47.js.map +0 -1
- package/dist/chunk-E4MESMNR.js.map +0 -1
- package/dist/chunk-IXSY4GPK.js.map +0 -1
- package/dist/chunk-JOBRLBIQ.js.map +0 -1
- package/dist/chunk-KPYRTOCK.js.map +0 -1
- package/dist/chunk-OQARLKZN.js.map +0 -1
- package/dist/chunk-P5UIGQFY.js.map +0 -1
- package/dist/chunk-P6XUDT2P.js.map +0 -1
- package/dist/chunk-VX3WJLKK.js.map +0 -1
- package/dist/entity-routes-LXOBW5IB.js +0 -4
|
@@ -0,0 +1,2964 @@
|
|
|
1
|
+
import { RightRailPage } from './chunk-JFIU6NQP.js';
|
|
2
|
+
import { SettingsGroup, TextRow, MultiSelectRow, SelectRow, ToggleRow } from './chunk-ENHCVGXZ.js';
|
|
3
|
+
import { useRightRailStore, useRightRailHosted, useRightRailDrill, PageActions } from './chunk-PQ47MLTH.js';
|
|
4
|
+
import { useTranslation } from './chunk-YUHCC7JY.js';
|
|
5
|
+
import { toast } from './chunk-3OUD4XBK.js';
|
|
6
|
+
import { SDK_INTEGRATIONS, knownClientMark, ConnectorMark } from './chunk-WPFZCYZL.js';
|
|
7
|
+
import { useChatStore } from './chunk-KLO7DKXX.js';
|
|
8
|
+
import { useTenantOptional } from './chunk-5D5QLMOQ.js';
|
|
9
|
+
import { getFayzSupabaseClientOptional } from './chunk-KJELMULA.js';
|
|
10
|
+
import React6 from 'react';
|
|
11
|
+
import { createFayzAgentClient } from '@fayz-ai/sdk/agent';
|
|
12
|
+
import { Button, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Skeleton, Badge, ConfirmDialog, Tabs, TabsList, TabsTrigger, TabsContent, SheetBody, Input, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, PageHeaderActions, toast as toast$1 } from '@fayz-ai/ui';
|
|
13
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
14
|
+
import { Sparkles, RefreshCw, ArrowDownLeft, ArrowUpRight, ChevronRight, Loader2, ArrowDownToLine, ArrowUpFromLine, CheckCircle2, XCircle, AlertTriangle, Power, Plug, ChevronLeft, Search, Plus, Clock, Database, Radio, Zap, ExternalLink, Save, Info, ShieldAlert } from 'lucide-react';
|
|
15
|
+
import { isConnectorCategory, connectorSubscribesTo, eventKeyMatches, connectorSettings, usePluginRuntimeOptional, getActiveTenantId, connectorPlanes, connectorPermissions, connectorEntities, preferredPlane, connectorDirection, groupedPermissions, invocationPlane, missingRequiredSettings, connectionKey, isSettingSet, listConnections } from '@fayz-ai/core';
|
|
16
|
+
import { create } from 'zustand';
|
|
17
|
+
import { getConnectorCredentialSink, ConnectorOAuthStarterMissingError } from '@fayz-ai/sdk';
|
|
18
|
+
|
|
19
|
+
function env(key) {
|
|
20
|
+
const value = import.meta.env[key];
|
|
21
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
22
|
+
}
|
|
23
|
+
function resolveFayzApiBaseUrl(override) {
|
|
24
|
+
return override ?? env("VITE_FAYZ_API_BASE_URL");
|
|
25
|
+
}
|
|
26
|
+
function resolveFayzAgentConnection(override) {
|
|
27
|
+
if (override === false) return null;
|
|
28
|
+
const projectId = override?.projectId ?? env("VITE_FAYZ_PROJECT_ID");
|
|
29
|
+
const apiBaseUrl = override?.apiBaseUrl ?? env("VITE_FAYZ_API_BASE_URL");
|
|
30
|
+
const publishableKey = override?.publishableKey ?? env("VITE_FAYZ_AGENT_KEY");
|
|
31
|
+
if (!projectId || !apiBaseUrl || !publishableKey) return null;
|
|
32
|
+
return { projectId, apiBaseUrl, publishableKey };
|
|
33
|
+
}
|
|
34
|
+
function describeFayzAgentGap(override) {
|
|
35
|
+
if (override === false) return null;
|
|
36
|
+
const missing = [];
|
|
37
|
+
if (!(override?.projectId ?? env("VITE_FAYZ_PROJECT_ID"))) missing.push("VITE_FAYZ_PROJECT_ID");
|
|
38
|
+
if (!(override?.apiBaseUrl ?? env("VITE_FAYZ_API_BASE_URL"))) missing.push("VITE_FAYZ_API_BASE_URL");
|
|
39
|
+
if (!(override?.publishableKey ?? env("VITE_FAYZ_AGENT_KEY"))) missing.push("VITE_FAYZ_AGENT_KEY");
|
|
40
|
+
return missing.length ? { missing } : null;
|
|
41
|
+
}
|
|
42
|
+
var cached = null;
|
|
43
|
+
function getFayzAgentClient(connection) {
|
|
44
|
+
const key = `${connection.apiBaseUrl}|${connection.projectId}|${connection.publishableKey}`;
|
|
45
|
+
if (cached?.key === key) return cached.client;
|
|
46
|
+
const client = createFayzAgentClient({
|
|
47
|
+
baseUrl: connection.apiBaseUrl,
|
|
48
|
+
projectId: connection.projectId,
|
|
49
|
+
publishableKey: connection.publishableKey
|
|
50
|
+
});
|
|
51
|
+
cached = { key, client };
|
|
52
|
+
return client;
|
|
53
|
+
}
|
|
54
|
+
function ConnectConsentPage({ appName, logo }) {
|
|
55
|
+
const [state, setState] = React6.useState({ status: "loading" });
|
|
56
|
+
const [tenantId, setTenantId] = React6.useState("");
|
|
57
|
+
const request = React6.useMemo(
|
|
58
|
+
() => new URLSearchParams(window.location.search).get("request") ?? "",
|
|
59
|
+
[]
|
|
60
|
+
);
|
|
61
|
+
const connection = React6.useMemo(() => resolveFayzAgentConnection(), []);
|
|
62
|
+
const product = appName ?? document.title ?? "este app";
|
|
63
|
+
const authorizedFetch = React6.useCallback(
|
|
64
|
+
async (path, init) => {
|
|
65
|
+
if (!connection) throw new Error("Este app n\xE3o est\xE1 ligado a um projeto Fayz.");
|
|
66
|
+
const supabase = getFayzSupabaseClientOptional();
|
|
67
|
+
const session = (await supabase?.auth.getSession())?.data.session;
|
|
68
|
+
if (!session?.access_token) {
|
|
69
|
+
const back = encodeURIComponent(window.location.pathname + window.location.search);
|
|
70
|
+
window.location.href = `/login?redirect=${back}`;
|
|
71
|
+
throw new Error("redirecting");
|
|
72
|
+
}
|
|
73
|
+
return fetch(
|
|
74
|
+
`${connection.apiBaseUrl}/public/projects/${connection.projectId}/agents/oauth${path}`,
|
|
75
|
+
{
|
|
76
|
+
...init,
|
|
77
|
+
headers: {
|
|
78
|
+
...init?.headers ?? {},
|
|
79
|
+
Authorization: `Bearer ${session.access_token}`,
|
|
80
|
+
"Content-Type": "application/json"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
},
|
|
85
|
+
[connection]
|
|
86
|
+
);
|
|
87
|
+
React6.useEffect(() => {
|
|
88
|
+
let cancelled = false;
|
|
89
|
+
(async () => {
|
|
90
|
+
if (!request) {
|
|
91
|
+
setState({ status: "error", message: "Link de autoriza\xE7\xE3o inv\xE1lido ou incompleto." });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const response = await authorizedFetch(`/consent/request?request=${encodeURIComponent(request)}`);
|
|
96
|
+
const body = await response.json();
|
|
97
|
+
if (cancelled) return;
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
setState({ status: "error", message: body.message ?? "N\xE3o foi poss\xEDvel ler o pedido." });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
setState({ status: "ready", payload: body });
|
|
103
|
+
if (body.organizations.length === 1) setTenantId(body.organizations[0].tenantId);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (cancelled || error.message === "redirecting") return;
|
|
106
|
+
setState({ status: "error", message: error.message });
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
return () => {
|
|
110
|
+
cancelled = true;
|
|
111
|
+
};
|
|
112
|
+
}, [request, authorizedFetch]);
|
|
113
|
+
async function approve() {
|
|
114
|
+
if (!tenantId) return;
|
|
115
|
+
const previous = state;
|
|
116
|
+
setState({ status: "approving" });
|
|
117
|
+
try {
|
|
118
|
+
const response = await authorizedFetch("/consent", {
|
|
119
|
+
method: "POST",
|
|
120
|
+
body: JSON.stringify({ request, tenantId })
|
|
121
|
+
});
|
|
122
|
+
const body = await response.json();
|
|
123
|
+
if (!response.ok || !body.redirectTo) {
|
|
124
|
+
setState({ status: "error", message: body.message ?? "N\xE3o foi poss\xEDvel autorizar." });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
window.location.href = body.redirectTo;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (error.message === "redirecting") return;
|
|
130
|
+
setState(previous);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function cancel() {
|
|
134
|
+
window.history.length > 1 ? window.history.back() : window.location.href = "/";
|
|
135
|
+
}
|
|
136
|
+
return /* @__PURE__ */ jsx("div", { className: "flex min-h-screen items-center justify-center bg-sidebar p-4", children: /* @__PURE__ */ jsxs("div", { className: "relative w-full max-w-md rounded-2xl border border-border/50 bg-card p-8 shadow-2xl", children: [
|
|
137
|
+
state.status === "loading" && /* @__PURE__ */ jsx(ConsentSkeleton, {}),
|
|
138
|
+
state.status === "error" && /* @__PURE__ */ jsxs("div", { className: "py-6 text-center", children: [
|
|
139
|
+
/* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold text-foreground", children: "N\xE3o deu para autorizar" }),
|
|
140
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: state.message }),
|
|
141
|
+
/* @__PURE__ */ jsx(
|
|
142
|
+
Button,
|
|
143
|
+
{
|
|
144
|
+
variant: "bare",
|
|
145
|
+
size: "bare",
|
|
146
|
+
onClick: cancel,
|
|
147
|
+
className: "mt-6 rounded-button border border-border px-4 py-2 text-sm font-medium hover:bg-muted",
|
|
148
|
+
children: "Voltar"
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
] }),
|
|
152
|
+
(state.status === "ready" || state.status === "approving") && /* @__PURE__ */ jsx(
|
|
153
|
+
ConsentBody,
|
|
154
|
+
{
|
|
155
|
+
payload: state.status === "ready" ? state.payload : void 0,
|
|
156
|
+
product,
|
|
157
|
+
logo,
|
|
158
|
+
tenantId,
|
|
159
|
+
onTenantChange: setTenantId,
|
|
160
|
+
onApprove: approve,
|
|
161
|
+
onCancel: cancel,
|
|
162
|
+
busy: state.status === "approving"
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
] }) });
|
|
166
|
+
}
|
|
167
|
+
function ConsentBody({
|
|
168
|
+
payload,
|
|
169
|
+
product,
|
|
170
|
+
logo,
|
|
171
|
+
tenantId,
|
|
172
|
+
onTenantChange,
|
|
173
|
+
onApprove,
|
|
174
|
+
onCancel,
|
|
175
|
+
busy
|
|
176
|
+
}) {
|
|
177
|
+
if (!payload) return /* @__PURE__ */ jsx(ConsentSkeleton, {});
|
|
178
|
+
const { client, access, organizations } = payload;
|
|
179
|
+
const org = organizations.find((o) => o.tenantId === tenantId);
|
|
180
|
+
const mark = client.logoUri ?? knownClientMark(client.website, client.identityVerified === true, client.name);
|
|
181
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
182
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-center gap-3", children: [
|
|
183
|
+
/* @__PURE__ */ jsx(MarkBadge, { fallback: client.name, children: mark ? /* @__PURE__ */ jsx("img", { src: mark, alt: "", className: "h-full w-full object-cover" }) : null }),
|
|
184
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u21C4" }),
|
|
185
|
+
/* @__PURE__ */ jsx(MarkBadge, { fallback: product, children: logo })
|
|
186
|
+
] }),
|
|
187
|
+
/* @__PURE__ */ jsxs("h1", { className: "text-center text-xl font-semibold text-foreground", children: [
|
|
188
|
+
client.name,
|
|
189
|
+
" quer acessar seus dados"
|
|
190
|
+
] }),
|
|
191
|
+
/* @__PURE__ */ jsxs("dl", { className: "mt-6 divide-y divide-border overflow-hidden rounded-xl border border-border text-sm", children: [
|
|
192
|
+
/* @__PURE__ */ jsx(
|
|
193
|
+
Row,
|
|
194
|
+
{
|
|
195
|
+
label: "Aplicativo",
|
|
196
|
+
value: /* @__PURE__ */ jsxs("span", { children: [
|
|
197
|
+
client.name,
|
|
198
|
+
client.identityVerified !== true && // Not an alarm — most CLI clients register themselves and are
|
|
199
|
+
// perfectly legitimate. But the user is about to hand over an
|
|
200
|
+
// organization's data, and "this name is self-declared" is a
|
|
201
|
+
// fact they are entitled to before deciding.
|
|
202
|
+
/* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: "nome informado pelo pr\xF3prio aplicativo" })
|
|
203
|
+
] })
|
|
204
|
+
}
|
|
205
|
+
),
|
|
206
|
+
client.website && /* @__PURE__ */ jsx(
|
|
207
|
+
Row,
|
|
208
|
+
{
|
|
209
|
+
label: "Site",
|
|
210
|
+
value: /* @__PURE__ */ jsx("a", { href: client.website, target: "_blank", rel: "noreferrer noopener", className: "text-primary hover:underline", children: client.website })
|
|
211
|
+
}
|
|
212
|
+
),
|
|
213
|
+
/* @__PURE__ */ jsx(Row, { label: "Retorna para", value: /* @__PURE__ */ jsx("span", { className: "break-all font-mono text-xs", children: client.redirectUri }) }),
|
|
214
|
+
/* @__PURE__ */ jsx(
|
|
215
|
+
Row,
|
|
216
|
+
{
|
|
217
|
+
label: "Acesso",
|
|
218
|
+
value: /* @__PURE__ */ jsxs("span", { children: [
|
|
219
|
+
access.readOnly ? "Somente leitura" : "Leitura e escrita",
|
|
220
|
+
/* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: access.summary })
|
|
221
|
+
] })
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
] }),
|
|
225
|
+
access.entities && access.entities.length > 0 && /* @__PURE__ */ jsx("ul", { className: "mt-4 space-y-2 rounded-lg border border-border bg-muted/30 p-3", children: access.entities.map((entity) => {
|
|
226
|
+
const writes = entity.operations.some((operation) => operation !== "read");
|
|
227
|
+
return /* @__PURE__ */ jsxs("li", { className: "text-sm", children: [
|
|
228
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: entity.label }),
|
|
229
|
+
/* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: writes ? "ler e escrever" : "somente ler" }),
|
|
230
|
+
/* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: entity.reason })
|
|
231
|
+
] }, entity.label);
|
|
232
|
+
}) }),
|
|
233
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-5", children: [
|
|
234
|
+
/* @__PURE__ */ jsx("label", { className: "mb-1.5 block text-sm font-medium text-foreground", children: "Organiza\xE7\xE3o que est\xE1 liberando" }),
|
|
235
|
+
organizations.length === 0 ? /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-border bg-muted/40 p-3 text-sm text-muted-foreground", children: "Sua conta n\xE3o pertence a nenhuma organiza\xE7\xE3o neste app, ent\xE3o n\xE3o h\xE1 dados para liberar." }) : organizations.length === 1 ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border bg-muted/40 px-3 py-2.5 text-sm", children: [
|
|
236
|
+
organizations[0].name,
|
|
237
|
+
organizations[0].role && /* @__PURE__ */ jsxs("span", { className: "ml-2 text-xs text-muted-foreground", children: [
|
|
238
|
+
"(",
|
|
239
|
+
organizations[0].role,
|
|
240
|
+
")"
|
|
241
|
+
] })
|
|
242
|
+
] }) : /* @__PURE__ */ jsxs(Select, { value: tenantId || void 0, onValueChange: onTenantChange, children: [
|
|
243
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "h-auto rounded-lg border-border py-2.5 shadow-none", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Escolha uma organiza\xE7\xE3o\u2026" }) }),
|
|
244
|
+
/* @__PURE__ */ jsx(SelectContent, { children: organizations.map((o) => /* @__PURE__ */ jsxs(SelectItem, { value: o.tenantId, children: [
|
|
245
|
+
o.name,
|
|
246
|
+
o.role ? ` (${o.role})` : ""
|
|
247
|
+
] }, o.tenantId)) })
|
|
248
|
+
] })
|
|
249
|
+
] }),
|
|
250
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-5 text-xs leading-relaxed text-muted-foreground", children: [
|
|
251
|
+
"Ao aprovar, ",
|
|
252
|
+
client.name,
|
|
253
|
+
" poder\xE1 ler os dados",
|
|
254
|
+
org ? ` de ${org.name}` : " da organiza\xE7\xE3o escolhida",
|
|
255
|
+
" at\xE9 voc\xEA desconectar. Voc\xEA pode desfazer isso a qualquer momento em ",
|
|
256
|
+
/* @__PURE__ */ jsx("strong", { children: "Configura\xE7\xF5es \u2192 Conectores" }),
|
|
257
|
+
"."
|
|
258
|
+
] }),
|
|
259
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex justify-end gap-2", children: [
|
|
260
|
+
/* @__PURE__ */ jsx(
|
|
261
|
+
Button,
|
|
262
|
+
{
|
|
263
|
+
variant: "bare",
|
|
264
|
+
size: "bare",
|
|
265
|
+
onClick: onCancel,
|
|
266
|
+
disabled: busy,
|
|
267
|
+
className: "rounded-button border border-border px-4 py-2 text-sm font-medium hover:bg-muted disabled:opacity-50",
|
|
268
|
+
children: "Cancelar"
|
|
269
|
+
}
|
|
270
|
+
),
|
|
271
|
+
/* @__PURE__ */ jsx(
|
|
272
|
+
Button,
|
|
273
|
+
{
|
|
274
|
+
variant: "bare",
|
|
275
|
+
size: "bare",
|
|
276
|
+
onClick: onApprove,
|
|
277
|
+
disabled: busy || !tenantId,
|
|
278
|
+
className: "rounded-lg bg-primary px-5 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 disabled:opacity-50",
|
|
279
|
+
children: busy ? "Aprovando\u2026" : "Aprovar"
|
|
280
|
+
}
|
|
281
|
+
)
|
|
282
|
+
] })
|
|
283
|
+
] });
|
|
284
|
+
}
|
|
285
|
+
function ConsentSkeleton() {
|
|
286
|
+
return /* @__PURE__ */ jsxs("div", { "aria-hidden": true, children: [
|
|
287
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-center gap-3", children: [
|
|
288
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-12 rounded-xl" }),
|
|
289
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded-full" }),
|
|
290
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-12 rounded-xl" })
|
|
291
|
+
] }),
|
|
292
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "mx-auto h-6 w-64" }),
|
|
293
|
+
/* @__PURE__ */ jsx("div", { className: "mt-6 divide-y divide-border overflow-hidden rounded-xl border border-border", children: [0, 1, 2, 3].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex gap-4 px-4 py-3", children: [
|
|
294
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24 shrink-0" }),
|
|
295
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 flex-1" })
|
|
296
|
+
] }, i)) }),
|
|
297
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "mt-5 h-4 w-52" }),
|
|
298
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-11 w-full rounded-lg" }),
|
|
299
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex justify-end gap-2", children: [
|
|
300
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-24 rounded-lg" }),
|
|
301
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-24 rounded-lg" })
|
|
302
|
+
] })
|
|
303
|
+
] });
|
|
304
|
+
}
|
|
305
|
+
function MarkBadge({
|
|
306
|
+
children,
|
|
307
|
+
fallback
|
|
308
|
+
}) {
|
|
309
|
+
if (children) {
|
|
310
|
+
return (
|
|
311
|
+
// The node is the app's own, sized for wherever it normally lives (the
|
|
312
|
+
// sidebar rail sizes it too, via cloneElement). It is stretched to the
|
|
313
|
+
// badge here rather than by changing the component, which would follow it
|
|
314
|
+
// everywhere else it is used.
|
|
315
|
+
/* @__PURE__ */ jsx("div", { className: "h-12 w-12 shrink-0 overflow-hidden rounded-xl [&>*]:h-full [&>*]:w-full [&_img]:h-full [&_img]:w-full [&_img]:rounded-xl [&_img]:object-cover [&_svg]:h-full [&_svg]:w-full", children })
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
return /* @__PURE__ */ jsx("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-xl border border-border bg-muted text-lg font-semibold", children: fallback.slice(0, 1).toUpperCase() });
|
|
319
|
+
}
|
|
320
|
+
function Row({ label, value }) {
|
|
321
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex gap-4 px-4 py-3", children: [
|
|
322
|
+
/* @__PURE__ */ jsx("dt", { className: "w-28 shrink-0 text-muted-foreground", children: label }),
|
|
323
|
+
/* @__PURE__ */ jsx("dd", { className: "min-w-0 flex-1 text-foreground", children: value })
|
|
324
|
+
] });
|
|
325
|
+
}
|
|
326
|
+
var CATEGORY_LABELS = {
|
|
327
|
+
database: "Dados",
|
|
328
|
+
payments: "Pagamentos",
|
|
329
|
+
banking: "Banco",
|
|
330
|
+
erp: "ERP",
|
|
331
|
+
logistics: "Log\xEDstica",
|
|
332
|
+
marketplace: "Marketplace",
|
|
333
|
+
ecommerce: "Loja",
|
|
334
|
+
crm: "CRM",
|
|
335
|
+
courses: "Cursos",
|
|
336
|
+
scheduling: "Agenda",
|
|
337
|
+
messaging: "Mensagens",
|
|
338
|
+
analytics: "An\xE1lise",
|
|
339
|
+
local: "Presen\xE7a local",
|
|
340
|
+
ai: "IA",
|
|
341
|
+
automation: "Automa\xE7\xE3o"
|
|
342
|
+
};
|
|
343
|
+
function categoryLabel(category) {
|
|
344
|
+
return category && isConnectorCategory(category) ? CATEGORY_LABELS[category] : void 0;
|
|
345
|
+
}
|
|
346
|
+
var CATEGORY_ORDER = [
|
|
347
|
+
"payments",
|
|
348
|
+
"banking",
|
|
349
|
+
"erp",
|
|
350
|
+
"marketplace",
|
|
351
|
+
"ecommerce",
|
|
352
|
+
"logistics",
|
|
353
|
+
"crm",
|
|
354
|
+
"messaging",
|
|
355
|
+
"scheduling",
|
|
356
|
+
"courses",
|
|
357
|
+
"analytics",
|
|
358
|
+
"local",
|
|
359
|
+
"ai",
|
|
360
|
+
"automation",
|
|
361
|
+
"database"
|
|
362
|
+
];
|
|
363
|
+
function shelveByCategory(items) {
|
|
364
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
365
|
+
for (const item of items) {
|
|
366
|
+
const key = item.category ?? "_other";
|
|
367
|
+
const bucket = byCategory.get(key);
|
|
368
|
+
if (bucket) bucket.push(item);
|
|
369
|
+
else byCategory.set(key, [item]);
|
|
370
|
+
}
|
|
371
|
+
const shelves = [];
|
|
372
|
+
for (const category of CATEGORY_ORDER) {
|
|
373
|
+
const bucket = byCategory.get(category);
|
|
374
|
+
if (!bucket) continue;
|
|
375
|
+
shelves.push({ key: category, label: categoryLabel(category) || "", items: bucket });
|
|
376
|
+
byCategory.delete(category);
|
|
377
|
+
}
|
|
378
|
+
for (const [key, bucket] of byCategory) {
|
|
379
|
+
shelves.push({ key, label: key === "_other" ? "" : categoryLabel(key) || "", items: bucket });
|
|
380
|
+
}
|
|
381
|
+
return shelves;
|
|
382
|
+
}
|
|
383
|
+
function groupConnectors(connectors) {
|
|
384
|
+
const granted = connectors.filter((c) => c.granted);
|
|
385
|
+
const reached = connectors.filter((c) => !c.granted);
|
|
386
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
387
|
+
for (const connector of reached) {
|
|
388
|
+
const key = connector.category && isConnectorCategory(connector.category) ? connector.category : "_other";
|
|
389
|
+
const bucket = byCategory.get(key);
|
|
390
|
+
if (bucket) bucket.push(connector);
|
|
391
|
+
else byCategory.set(key, [connector]);
|
|
392
|
+
}
|
|
393
|
+
const groups = [];
|
|
394
|
+
if (granted.length > 0) {
|
|
395
|
+
groups.push({ key: "granted", label: "Conectados a voc\xEA", connectors: granted });
|
|
396
|
+
}
|
|
397
|
+
for (const category of CATEGORY_ORDER) {
|
|
398
|
+
const bucket = byCategory.get(category);
|
|
399
|
+
if (bucket) groups.push({ key: category, label: CATEGORY_LABELS[category], connectors: bucket });
|
|
400
|
+
}
|
|
401
|
+
const other = byCategory.get("_other");
|
|
402
|
+
if (other) groups.push({ key: "other", label: "Outros", connectors: other });
|
|
403
|
+
return groups;
|
|
404
|
+
}
|
|
405
|
+
function connectorSlug(def) {
|
|
406
|
+
return def.catalogSlug ?? def.id;
|
|
407
|
+
}
|
|
408
|
+
var MARK_BY_SLUG = /* @__PURE__ */ new Map();
|
|
409
|
+
for (const row of SDK_INTEGRATIONS) {
|
|
410
|
+
if (row.logoUrl && !MARK_BY_SLUG.has(row.slug)) MARK_BY_SLUG.set(row.slug, row.logoUrl);
|
|
411
|
+
}
|
|
412
|
+
function fromInstalled(entry, catalog) {
|
|
413
|
+
const def = entry.definition;
|
|
414
|
+
const planes = connectorPlanes(def);
|
|
415
|
+
return {
|
|
416
|
+
id: def.id,
|
|
417
|
+
slug: connectorSlug(def),
|
|
418
|
+
name: def.name,
|
|
419
|
+
// The catalogue's copy wins: it is served in the user's locale, while a
|
|
420
|
+
// definition carries whatever language its plugin author wrote it in.
|
|
421
|
+
description: catalog?.description ?? def.description,
|
|
422
|
+
direction: connectorDirection(def),
|
|
423
|
+
planes,
|
|
424
|
+
activePlane: entry.connected ? entry.plane ?? preferredPlane(def) : void 0,
|
|
425
|
+
state: entry.connected ? entry.needsSetup ? "needs_setup" : "connected" : "ready",
|
|
426
|
+
missingSettings: entry.needsSetup ? entry.missingSettings : void 0,
|
|
427
|
+
hostPluginId: def.hostPluginId,
|
|
428
|
+
category: def.category ?? catalog?.category,
|
|
429
|
+
icon: def.icon,
|
|
430
|
+
logoUrl: def.logoUrl ?? catalog?.logoUrl ?? catalog?.iconUrl ?? MARK_BY_SLUG.get(connectorSlug(def)),
|
|
431
|
+
// Derived, never read off the field: a connector that declares permissions
|
|
432
|
+
// does not also keep a parallel list of names for this to pick between.
|
|
433
|
+
entities: orUndefined(connectorEntities(def)),
|
|
434
|
+
permissions: orUndefined(connectorPermissions(def)),
|
|
435
|
+
agentTools: def.agentTools,
|
|
436
|
+
detail: entry.detail,
|
|
437
|
+
issues: orUndefined(entry.issues ?? []),
|
|
438
|
+
docsUrl: def.docsUrl,
|
|
439
|
+
definition: def
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function fromGrant(grant) {
|
|
443
|
+
const tools = grant.capabilities?.tools ?? [];
|
|
444
|
+
return {
|
|
445
|
+
id: grant.id,
|
|
446
|
+
slug: grant.kind || grant.id,
|
|
447
|
+
name: grant.clientName,
|
|
448
|
+
description: grant.description ?? void 0,
|
|
449
|
+
direction: grant.direction,
|
|
450
|
+
granted: true,
|
|
451
|
+
planes: ["agentic"],
|
|
452
|
+
activePlane: "agentic",
|
|
453
|
+
state: "connected",
|
|
454
|
+
logoUrl: grant.clientLogoUri,
|
|
455
|
+
agentTools: tools.map((name) => ({ name })),
|
|
456
|
+
entityCount: grant.capabilities?.entities,
|
|
457
|
+
builtIn: grant.builtIn,
|
|
458
|
+
revocable: grant.revocable,
|
|
459
|
+
readOnly: grant.readOnly,
|
|
460
|
+
scopes: grant.scopes,
|
|
461
|
+
grantedByMe: grant.grantedByMe,
|
|
462
|
+
identityVerified: grant.identityVerified,
|
|
463
|
+
clientWebsite: grant.clientWebsite,
|
|
464
|
+
createdAt: grant.createdAt,
|
|
465
|
+
lastUsedAt: grant.lastUsedAt,
|
|
466
|
+
agentConfig: grant.config
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function buildConnectorList(input) {
|
|
470
|
+
const catalogBySlug = new Map((input.catalog ?? []).map((c) => [c.slug, c]));
|
|
471
|
+
const built = (input.installed ?? []).map((entry) => ({
|
|
472
|
+
entry,
|
|
473
|
+
row: fromInstalled(entry, catalogBySlug.get(connectorSlug(entry.definition)))
|
|
474
|
+
}));
|
|
475
|
+
const perSlug = /* @__PURE__ */ new Map();
|
|
476
|
+
for (const { row } of built) perSlug.set(row.slug, (perSlug.get(row.slug) ?? 0) + 1);
|
|
477
|
+
const installed = built.map(
|
|
478
|
+
({ entry, row }) => (perSlug.get(row.slug) ?? 0) > 1 && entry.definition.description ? { ...row, description: entry.definition.description } : row
|
|
479
|
+
);
|
|
480
|
+
const grants = (input.grants ?? []).map(fromGrant);
|
|
481
|
+
return sortConnectors([...grants, ...installed]);
|
|
482
|
+
}
|
|
483
|
+
var STATE_RANK = { needs_setup: 0, connected: 1, ready: 2 };
|
|
484
|
+
function sortConnectors(list) {
|
|
485
|
+
return [...list].sort((a, b) => {
|
|
486
|
+
const byState = STATE_RANK[a.state] - STATE_RANK[b.state];
|
|
487
|
+
if (byState !== 0) return byState;
|
|
488
|
+
return a.name.localeCompare(b.name);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
function connectorSubscriptions(connector) {
|
|
492
|
+
if (!isLive(connector.state)) return [];
|
|
493
|
+
return declaredSubscriptions(connector.definition);
|
|
494
|
+
}
|
|
495
|
+
function isLive(state) {
|
|
496
|
+
return state === "connected" || state === "needs_setup";
|
|
497
|
+
}
|
|
498
|
+
function orUndefined(list) {
|
|
499
|
+
return list.length > 0 ? list : void 0;
|
|
500
|
+
}
|
|
501
|
+
function declaredSubscriptions(def) {
|
|
502
|
+
const automations = def?.managedAutomations ?? [];
|
|
503
|
+
const out = [];
|
|
504
|
+
for (const pattern of connectorSubscribesTo(def)) {
|
|
505
|
+
const covered = automations.filter((a) => eventKeyMatches(pattern, a.eventKey));
|
|
506
|
+
if (covered.length === 0) out.push({ key: pattern });
|
|
507
|
+
else for (const a of covered) out.push({ key: a.eventKey, label: a.label });
|
|
508
|
+
}
|
|
509
|
+
return out.filter((s, i) => out.findIndex((x) => x.key === s.key) === i);
|
|
510
|
+
}
|
|
511
|
+
function connectorMoments(connector) {
|
|
512
|
+
return {
|
|
513
|
+
calls: connector.definition?.invokedAt ?? [],
|
|
514
|
+
events: declaredSubscriptions(connector.definition)
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function addableConnectors(list) {
|
|
518
|
+
return list.filter((c) => c.definition != null || c.direction === "outbound");
|
|
519
|
+
}
|
|
520
|
+
function inCategories(list, categories) {
|
|
521
|
+
if (!categories?.length) return list;
|
|
522
|
+
const scope = new Set(categories);
|
|
523
|
+
return list.filter((item) => !!item.category && scope.has(item.category));
|
|
524
|
+
}
|
|
525
|
+
function shelfIntegrations(input) {
|
|
526
|
+
const here = new Set(input.present.map((c) => c.slug));
|
|
527
|
+
const modules = new Set(input.installedModules ?? []);
|
|
528
|
+
const catalogBySlug = new Map((input.catalog ?? []).map((c) => [c.slug, c]));
|
|
529
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
530
|
+
for (const row of input.map ?? SDK_INTEGRATIONS) {
|
|
531
|
+
if (here.has(row.slug)) continue;
|
|
532
|
+
const installed = modules.has(row.moduleId) && modules.has(row.declaredBy.pluginId);
|
|
533
|
+
const catalog = catalogBySlug.get(row.slug);
|
|
534
|
+
const candidate = {
|
|
535
|
+
id: row.slug,
|
|
536
|
+
slug: row.slug,
|
|
537
|
+
name: row.name,
|
|
538
|
+
description: row.description,
|
|
539
|
+
category: row.category,
|
|
540
|
+
icon: row.icon,
|
|
541
|
+
logoUrl: row.logoUrl ?? catalog?.logoUrl ?? catalog?.iconUrl,
|
|
542
|
+
planes: row.planes,
|
|
543
|
+
direction: row.direction,
|
|
544
|
+
moduleId: row.moduleId,
|
|
545
|
+
moduleInstalled: installed,
|
|
546
|
+
status: row.status
|
|
547
|
+
};
|
|
548
|
+
const previous = bySlug.get(row.slug);
|
|
549
|
+
if (!previous || rank(candidate) < rank(previous)) bySlug.set(row.slug, candidate);
|
|
550
|
+
}
|
|
551
|
+
return [...bySlug.values()].sort(
|
|
552
|
+
(a, b) => a.moduleId.localeCompare(b.moduleId) || a.name.localeCompare(b.name)
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
function rank(row) {
|
|
556
|
+
return (row.moduleInstalled ? 0 : 2) + (row.status === "available" ? 0 : 1);
|
|
557
|
+
}
|
|
558
|
+
function comingSoonHere(list) {
|
|
559
|
+
return list.filter((row) => row.moduleInstalled);
|
|
560
|
+
}
|
|
561
|
+
function withAnotherModule(list) {
|
|
562
|
+
return list.filter((row) => !row.moduleInstalled);
|
|
563
|
+
}
|
|
564
|
+
function filterAvailable(list, query = "") {
|
|
565
|
+
const q = query.trim().toLowerCase();
|
|
566
|
+
if (!q) return list;
|
|
567
|
+
return list.filter((c) => `${c.name} ${c.slug} ${c.description ?? ""}`.toLowerCase().includes(q));
|
|
568
|
+
}
|
|
569
|
+
function filterConnectors(list, filter, query = "") {
|
|
570
|
+
const q = query.trim().toLowerCase();
|
|
571
|
+
return list.filter((c) => {
|
|
572
|
+
if (q && !`${c.name} ${c.slug} ${c.description ?? ""}`.toLowerCase().includes(q)) return false;
|
|
573
|
+
switch (filter) {
|
|
574
|
+
case "connected":
|
|
575
|
+
return isLive(c.state);
|
|
576
|
+
case "agentic":
|
|
577
|
+
return c.planes.includes("agentic");
|
|
578
|
+
case "sync":
|
|
579
|
+
return c.planes.includes("sync");
|
|
580
|
+
case "inbound":
|
|
581
|
+
return c.direction === "inbound";
|
|
582
|
+
case "outbound":
|
|
583
|
+
return c.direction === "outbound";
|
|
584
|
+
default:
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
function connectorRoute(connector) {
|
|
590
|
+
return connector.hostPluginId ? `/settings/${connector.hostPluginId}/_integrations` : "/settings/connectors";
|
|
591
|
+
}
|
|
592
|
+
function PlaneBadge({ plane, active }) {
|
|
593
|
+
const { t } = useTranslation();
|
|
594
|
+
const Icon = plane === "agentic" ? Sparkles : RefreshCw;
|
|
595
|
+
return /* @__PURE__ */ jsxs(
|
|
596
|
+
"span",
|
|
597
|
+
{
|
|
598
|
+
className: "inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] " + (active ? plane === "agentic" ? "bg-primary/10 text-primary" : "bg-muted text-foreground" : "border border-border text-muted-foreground"),
|
|
599
|
+
children: [
|
|
600
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3 w-3" }),
|
|
601
|
+
t(`connectors.plane.${plane}`)
|
|
602
|
+
]
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
function DirectionBadge({ direction }) {
|
|
607
|
+
const { t } = useTranslation();
|
|
608
|
+
const Icon = direction === "inbound" ? ArrowDownLeft : ArrowUpRight;
|
|
609
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full border border-border px-2 py-0.5 text-[11px] text-muted-foreground", children: [
|
|
610
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3 w-3" }),
|
|
611
|
+
t(`connectors.direction.${direction}`)
|
|
612
|
+
] });
|
|
613
|
+
}
|
|
614
|
+
var STATE_VARIANT = {
|
|
615
|
+
connected: "success",
|
|
616
|
+
needs_setup: "warning",
|
|
617
|
+
ready: "outline"
|
|
618
|
+
};
|
|
619
|
+
function StateChip({ state, className }) {
|
|
620
|
+
const { t } = useTranslation();
|
|
621
|
+
return /* @__PURE__ */ jsx(Badge, { variant: STATE_VARIANT[state], className, children: t(`connectors.state.${state}`) });
|
|
622
|
+
}
|
|
623
|
+
function ConnectorRow({
|
|
624
|
+
connector,
|
|
625
|
+
onOpen
|
|
626
|
+
}) {
|
|
627
|
+
const { t } = useTranslation();
|
|
628
|
+
const category = categoryLabel(connector.category);
|
|
629
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
630
|
+
Button,
|
|
631
|
+
{
|
|
632
|
+
variant: "bare",
|
|
633
|
+
size: "bare",
|
|
634
|
+
onClick: () => onOpen(connector),
|
|
635
|
+
className: "flex w-full items-start gap-4 p-4 text-left transition-colors hover:bg-muted/40 justify-start whitespace-normal text-[length:inherit] font-normal rounded-none",
|
|
636
|
+
children: [
|
|
637
|
+
/* @__PURE__ */ jsx(ConnectorMark, { connector }),
|
|
638
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
639
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
640
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: connector.name }),
|
|
641
|
+
connector.builtIn && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-medium text-primary", children: t("connectors.builtIn") }),
|
|
642
|
+
connector.activePlane && /* @__PURE__ */ jsx(PlaneBadge, { plane: connector.activePlane, active: true }),
|
|
643
|
+
category && /* @__PURE__ */ jsx("span", { className: "rounded-full border border-border px-2 py-0.5 text-[11px] text-muted-foreground", children: category })
|
|
644
|
+
] }),
|
|
645
|
+
connector.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 line-clamp-1 text-xs text-muted-foreground", children: connector.description }),
|
|
646
|
+
isLive(connector.state) && !connector.description && connector.detail && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: connector.detail })
|
|
647
|
+
] }),
|
|
648
|
+
/* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-2 pt-1", children: [
|
|
649
|
+
/* @__PURE__ */ jsx(StateChip, { state: connector.state }),
|
|
650
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground" })
|
|
651
|
+
] })
|
|
652
|
+
]
|
|
653
|
+
}
|
|
654
|
+
) });
|
|
655
|
+
}
|
|
656
|
+
var CredentialSinkMissingError = class extends Error {
|
|
657
|
+
constructor() {
|
|
658
|
+
super("No connector credential sink configured");
|
|
659
|
+
this.name = "CredentialSinkMissingError";
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
function isSecretField(field) {
|
|
663
|
+
return field.type === "password" || field.secret === true;
|
|
664
|
+
}
|
|
665
|
+
function needsCredentialSink(definition) {
|
|
666
|
+
return (definition.fields ?? []).some(isSecretField);
|
|
667
|
+
}
|
|
668
|
+
function splitConnectorValues(fields, values) {
|
|
669
|
+
const safe = {};
|
|
670
|
+
const secrets = {};
|
|
671
|
+
for (const field of fields ?? []) {
|
|
672
|
+
const value = values[field.key];
|
|
673
|
+
if (value === void 0) continue;
|
|
674
|
+
(isSecretField(field) ? secrets : safe)[field.key] = value;
|
|
675
|
+
}
|
|
676
|
+
return { safe, secrets };
|
|
677
|
+
}
|
|
678
|
+
async function probeConnection(input) {
|
|
679
|
+
const { safe } = splitConnectorValues(input.definition.fields, input.values);
|
|
680
|
+
return input.definition.testConnection(safe);
|
|
681
|
+
}
|
|
682
|
+
async function submitConnection(input) {
|
|
683
|
+
const { safe, secrets } = splitConnectorValues(input.definition.fields, input.values);
|
|
684
|
+
if (Object.keys(secrets).length > 0) {
|
|
685
|
+
const sink = input.sink === void 0 ? getConnectorCredentialSink() : input.sink;
|
|
686
|
+
if (!sink) throw new CredentialSinkMissingError();
|
|
687
|
+
await sink.store({
|
|
688
|
+
connectorId: input.definition.id,
|
|
689
|
+
tenantId: input.tenantId,
|
|
690
|
+
authKind: input.definition.authKind,
|
|
691
|
+
secrets
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
await input.definition.saveConnection?.(safe);
|
|
695
|
+
}
|
|
696
|
+
function DisclosureRow({
|
|
697
|
+
name,
|
|
698
|
+
chips,
|
|
699
|
+
detail
|
|
700
|
+
}) {
|
|
701
|
+
const line = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
702
|
+
/* @__PURE__ */ jsx(
|
|
703
|
+
"code",
|
|
704
|
+
{
|
|
705
|
+
className: "font-mono text-[11.5px] text-foreground" + (detail ? " underline decoration-dotted decoration-foreground/40 underline-offset-4" : ""),
|
|
706
|
+
children: name
|
|
707
|
+
}
|
|
708
|
+
),
|
|
709
|
+
chips
|
|
710
|
+
] });
|
|
711
|
+
if (!detail) return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-1.5 py-1", children: line });
|
|
712
|
+
return /* @__PURE__ */ jsxs("details", { className: "group", children: [
|
|
713
|
+
/* @__PURE__ */ jsxs("summary", { className: "flex cursor-pointer list-none flex-wrap items-center gap-1.5 py-1 hover:bg-foreground/5", children: [
|
|
714
|
+
line,
|
|
715
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 text-muted-foreground transition-transform group-open:rotate-90" })
|
|
716
|
+
] }),
|
|
717
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 pl-1 text-xs leading-relaxed text-foreground/80", children: detail })
|
|
718
|
+
] });
|
|
719
|
+
}
|
|
720
|
+
function DisclosureNote({ label, children }) {
|
|
721
|
+
return /* @__PURE__ */ jsxs("details", { className: "group", children: [
|
|
722
|
+
/* @__PURE__ */ jsxs("summary", { className: "flex w-fit cursor-pointer list-none items-center gap-1 text-[11px] font-medium text-muted-foreground hover:text-foreground", children: [
|
|
723
|
+
/* @__PURE__ */ jsx(Info, { className: "h-3.5 w-3.5" }),
|
|
724
|
+
label,
|
|
725
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3 transition-transform group-open:rotate-90" })
|
|
726
|
+
] }),
|
|
727
|
+
/* @__PURE__ */ jsx("div", { className: "mt-1 border-l-2 border-border pl-2 text-[11px] leading-relaxed text-foreground/70", children })
|
|
728
|
+
] });
|
|
729
|
+
}
|
|
730
|
+
function ConnectorPermissions({ connector }) {
|
|
731
|
+
const { t } = useTranslation();
|
|
732
|
+
const { data, events } = groupedPermissions(connector.definition ?? connector);
|
|
733
|
+
const { calls, events: subscriptions } = connectorMoments(connector);
|
|
734
|
+
const declared = new Set(events.map((permission) => permission.resource));
|
|
735
|
+
const extraEvents = subscriptions.filter((subscription) => !declared.has(subscription.key));
|
|
736
|
+
const empty = data.length === 0 && events.length === 0 && extraEvents.length === 0 && calls.length === 0;
|
|
737
|
+
if (empty && !connector.definition) return null;
|
|
738
|
+
return (
|
|
739
|
+
// Filled, not outlined: on a white drawer an outlined box of grey type is a
|
|
740
|
+
// wall with a line around it. The fill is the one step 2's card carries, so
|
|
741
|
+
// step 1 reads as a panel and not as leftover page.
|
|
742
|
+
/* @__PURE__ */ jsxs("section", { className: "rounded-lg border border-border bg-muted/40", children: [
|
|
743
|
+
/* @__PURE__ */ jsx("p", { className: "border-b border-border bg-muted/60 px-3 py-2 text-sm font-semibold text-foreground", children: t("connectors.permissions.title") }),
|
|
744
|
+
empty ? /* @__PURE__ */ jsx("p", { className: "p-3 text-xs text-foreground/80", children: t("connectors.permissions.undeclared") }) : /* @__PURE__ */ jsxs("div", { className: "space-y-3 p-3", children: [
|
|
745
|
+
data.length > 0 && /* @__PURE__ */ jsx(Group, { icon: /* @__PURE__ */ jsx(Database, { className: "h-3.5 w-3.5 text-foreground/60" }), title: t("connectors.permissions.data"), children: data.map((permission) => /* @__PURE__ */ jsx(PermissionRow, { permission }, `data:${permission.resource}`)) }),
|
|
746
|
+
(events.length > 0 || extraEvents.length > 0) && /* @__PURE__ */ jsxs(
|
|
747
|
+
Group,
|
|
748
|
+
{
|
|
749
|
+
icon: /* @__PURE__ */ jsx(Radio, { className: "h-3.5 w-3.5 text-foreground/60" }),
|
|
750
|
+
title: t("connectors.permissions.events"),
|
|
751
|
+
children: [
|
|
752
|
+
events.map((permission) => /* @__PURE__ */ jsx(PermissionRow, { permission }, `event:${permission.resource}`)),
|
|
753
|
+
extraEvents.map((subscription) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
754
|
+
DisclosureRow,
|
|
755
|
+
{
|
|
756
|
+
name: subscription.key,
|
|
757
|
+
detail: subscription.label,
|
|
758
|
+
chips: /* @__PURE__ */ jsx(ActionChip, { action: "listen" })
|
|
759
|
+
}
|
|
760
|
+
) }, `sub:${subscription.key}`))
|
|
761
|
+
]
|
|
762
|
+
}
|
|
763
|
+
),
|
|
764
|
+
calls.length > 0 && /* @__PURE__ */ jsx(
|
|
765
|
+
Group,
|
|
766
|
+
{
|
|
767
|
+
icon: /* @__PURE__ */ jsx(Zap, { className: "h-3.5 w-3.5 text-foreground/60" }),
|
|
768
|
+
title: t("connectors.moments.calls"),
|
|
769
|
+
children: calls.map((moment) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
770
|
+
DisclosureRow,
|
|
771
|
+
{
|
|
772
|
+
name: moment.key,
|
|
773
|
+
detail: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
774
|
+
moment.label,
|
|
775
|
+
moment.touches && moment.touches.length > 0 && /* @__PURE__ */ jsxs("span", { className: "mt-0.5 block", children: [
|
|
776
|
+
t("connectors.moments.touches"),
|
|
777
|
+
":",
|
|
778
|
+
" ",
|
|
779
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: moment.touches.join(", ") })
|
|
780
|
+
] })
|
|
781
|
+
] }),
|
|
782
|
+
chips: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
783
|
+
/* @__PURE__ */ jsx(Chip, { children: t(`connectors.plane.${invocationPlane(moment)}`) }),
|
|
784
|
+
moment.trigger && /* @__PURE__ */ jsx(Chip, { children: t(`connectors.trigger.${moment.trigger}`) })
|
|
785
|
+
] })
|
|
786
|
+
}
|
|
787
|
+
) }, `call:${moment.key}`))
|
|
788
|
+
}
|
|
789
|
+
),
|
|
790
|
+
/* @__PURE__ */ jsxs(DisclosureNote, { label: t("connectors.permissions.noteLabel"), children: [
|
|
791
|
+
/* @__PURE__ */ jsx("p", { children: t("connectors.permissions.notEnforced") }),
|
|
792
|
+
calls.length > 0 && /* @__PURE__ */ jsx("p", { className: "mt-1", children: t("connectors.moments.callsHint") }),
|
|
793
|
+
(events.length > 0 || extraEvents.length > 0) && /* @__PURE__ */ jsx("p", { className: "mt-1", children: t("connectors.moments.eventsHint") })
|
|
794
|
+
] })
|
|
795
|
+
] })
|
|
796
|
+
] })
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
function Group({ icon, title, children }) {
|
|
800
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
801
|
+
/* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 text-xs font-semibold text-foreground", children: [
|
|
802
|
+
icon,
|
|
803
|
+
title
|
|
804
|
+
] }),
|
|
805
|
+
/* @__PURE__ */ jsx("ul", { className: "mt-1 divide-y divide-border/60", children })
|
|
806
|
+
] });
|
|
807
|
+
}
|
|
808
|
+
function PermissionRow({ permission }) {
|
|
809
|
+
const { t } = useTranslation();
|
|
810
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
811
|
+
DisclosureRow,
|
|
812
|
+
{
|
|
813
|
+
name: permission.resource,
|
|
814
|
+
detail: permission.reason,
|
|
815
|
+
chips: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
816
|
+
permission.actions.map((action) => /* @__PURE__ */ jsx(ActionChip, { action }, action)),
|
|
817
|
+
permission.sensitive && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full border border-warning/40 bg-warning/10 px-1.5 py-px text-[10px] font-medium text-warning", children: [
|
|
818
|
+
/* @__PURE__ */ jsx(ShieldAlert, { className: "h-2.5 w-2.5" }),
|
|
819
|
+
t("connectors.permissions.sensitive")
|
|
820
|
+
] })
|
|
821
|
+
] })
|
|
822
|
+
}
|
|
823
|
+
) });
|
|
824
|
+
}
|
|
825
|
+
function ActionChip({ action }) {
|
|
826
|
+
const { t } = useTranslation();
|
|
827
|
+
const mutating = action === "write" || action === "emit";
|
|
828
|
+
return /* @__PURE__ */ jsx(
|
|
829
|
+
"span",
|
|
830
|
+
{
|
|
831
|
+
className: "rounded-full px-1.5 py-px text-[10px] " + (mutating ? "border border-destructive/40 bg-destructive/5 font-medium text-destructive" : "border border-border bg-background font-medium text-foreground/70"),
|
|
832
|
+
children: t(`connectors.permissions.action.${action}`)
|
|
833
|
+
}
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
function Chip({ children }) {
|
|
837
|
+
return /* @__PURE__ */ jsx("span", { className: "rounded-full border border-border bg-background px-1.5 py-px text-[10px] font-medium text-foreground/70", children });
|
|
838
|
+
}
|
|
839
|
+
function ConnectorSyncActions({
|
|
840
|
+
actions,
|
|
841
|
+
onRan
|
|
842
|
+
}) {
|
|
843
|
+
const [running, setRunning] = React6.useState(null);
|
|
844
|
+
const [open, setOpen] = React6.useState(null);
|
|
845
|
+
const [pending, setPending] = React6.useState(null);
|
|
846
|
+
const [hidden, setHidden] = React6.useState(() => /* @__PURE__ */ new Set());
|
|
847
|
+
const [queued, setQueued] = React6.useState({});
|
|
848
|
+
const [nonce, setNonce] = React6.useState(0);
|
|
849
|
+
const [expanded, setExpanded] = React6.useState(false);
|
|
850
|
+
const [resolved, setResolved] = React6.useState(false);
|
|
851
|
+
React6.useEffect(() => {
|
|
852
|
+
let cancelled = false;
|
|
853
|
+
setResolved(false);
|
|
854
|
+
void Promise.all(
|
|
855
|
+
actions.map(async (action) => {
|
|
856
|
+
const show = action.available ? await Promise.resolve(action.available()).catch(() => true) : true;
|
|
857
|
+
const waiting = action.pending ? await Promise.resolve(action.pending()).catch(() => null) : null;
|
|
858
|
+
return { key: action.key, show, waiting };
|
|
859
|
+
})
|
|
860
|
+
).then((rows) => {
|
|
861
|
+
if (cancelled) return;
|
|
862
|
+
setHidden(new Set(rows.filter((r) => !r.show).map((r) => r.key)));
|
|
863
|
+
setQueued(Object.fromEntries(rows.map((r) => [r.key, r.waiting])));
|
|
864
|
+
setResolved(true);
|
|
865
|
+
});
|
|
866
|
+
return () => {
|
|
867
|
+
cancelled = true;
|
|
868
|
+
};
|
|
869
|
+
}, [actions, nonce]);
|
|
870
|
+
async function perform(action) {
|
|
871
|
+
setRunning(action.key);
|
|
872
|
+
try {
|
|
873
|
+
const outcome = await action.run();
|
|
874
|
+
const message = (typeof outcome === "string" ? outcome : outcome?.message) || `${action.label} \u2014 conclu\xEDdo`;
|
|
875
|
+
const status = typeof outcome === "object" && outcome ? outcome.status ?? "success" : "success";
|
|
876
|
+
if (status === "error") toast$1.error(message);
|
|
877
|
+
else if (status === "partial") toast$1.warning(message);
|
|
878
|
+
else toast$1.success(message);
|
|
879
|
+
setNonce((n) => n + 1);
|
|
880
|
+
onRan?.();
|
|
881
|
+
} catch (error) {
|
|
882
|
+
toast$1.error(error?.message ?? `${action.label} n\xE3o foi conclu\xEDdo`);
|
|
883
|
+
setNonce((n) => n + 1);
|
|
884
|
+
onRan?.();
|
|
885
|
+
} finally {
|
|
886
|
+
setRunning(null);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
const visible = actions.filter((a) => !hidden.has(a.key));
|
|
890
|
+
if (open?.Component) {
|
|
891
|
+
const Screen = open.Component;
|
|
892
|
+
return /* @__PURE__ */ jsx(Screen, { onClose: () => setOpen(null), onRan });
|
|
893
|
+
}
|
|
894
|
+
if (!resolved || visible.length === 0) return null;
|
|
895
|
+
function start(action) {
|
|
896
|
+
if (action.Component) {
|
|
897
|
+
setOpen(action);
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
if (action.confirm) {
|
|
901
|
+
setPending(action);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
void perform(action);
|
|
905
|
+
}
|
|
906
|
+
const primary = visible.find((a) => a.primary) ?? visible[0];
|
|
907
|
+
const rest = visible.filter((a) => a !== primary);
|
|
908
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
909
|
+
/* @__PURE__ */ jsx(
|
|
910
|
+
ActionRow,
|
|
911
|
+
{
|
|
912
|
+
action: primary,
|
|
913
|
+
busy: running === primary.key,
|
|
914
|
+
blocked: running !== null,
|
|
915
|
+
pending: queued[primary.key] ?? null,
|
|
916
|
+
onStart: start,
|
|
917
|
+
emphasis: true
|
|
918
|
+
}
|
|
919
|
+
),
|
|
920
|
+
rest.length > 0 && /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-lg border border-border", children: [
|
|
921
|
+
/* @__PURE__ */ jsxs(
|
|
922
|
+
Button,
|
|
923
|
+
{
|
|
924
|
+
variant: "bare",
|
|
925
|
+
size: "bare",
|
|
926
|
+
type: "button",
|
|
927
|
+
onClick: () => setExpanded(!expanded),
|
|
928
|
+
className: "flex w-full items-center gap-2 px-3 py-2 text-left transition-colors hover:bg-muted/40 justify-start whitespace-normal text-[length:inherit] font-normal rounded-none",
|
|
929
|
+
"aria-expanded": expanded,
|
|
930
|
+
children: [
|
|
931
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: `h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform ${expanded ? "rotate-90" : ""}` }),
|
|
932
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: "Outras a\xE7\xF5es" }),
|
|
933
|
+
/* @__PURE__ */ jsx("span", { className: "ml-auto text-[11px] text-muted-foreground", children: rest.length })
|
|
934
|
+
]
|
|
935
|
+
}
|
|
936
|
+
),
|
|
937
|
+
expanded && /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border border-t border-border", children: rest.map((action) => /* @__PURE__ */ jsx("li", { className: "px-3 py-2.5", children: /* @__PURE__ */ jsx(
|
|
938
|
+
ActionRow,
|
|
939
|
+
{
|
|
940
|
+
action,
|
|
941
|
+
busy: running === action.key,
|
|
942
|
+
blocked: running !== null,
|
|
943
|
+
pending: queued[action.key] ?? null,
|
|
944
|
+
onStart: start
|
|
945
|
+
}
|
|
946
|
+
) }, action.key)) })
|
|
947
|
+
] }),
|
|
948
|
+
pending?.confirm && /* @__PURE__ */ jsx(
|
|
949
|
+
ConfirmDialog,
|
|
950
|
+
{
|
|
951
|
+
open: true,
|
|
952
|
+
title: pending.confirm.title,
|
|
953
|
+
description: pending.confirm.body,
|
|
954
|
+
confirmLabel: pending.confirm.cta,
|
|
955
|
+
onCancel: () => setPending(null),
|
|
956
|
+
onConfirm: () => {
|
|
957
|
+
const action = pending;
|
|
958
|
+
setPending(null);
|
|
959
|
+
void perform(action);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
)
|
|
963
|
+
] });
|
|
964
|
+
}
|
|
965
|
+
function ActionRow({
|
|
966
|
+
action,
|
|
967
|
+
busy,
|
|
968
|
+
blocked,
|
|
969
|
+
pending,
|
|
970
|
+
onStart,
|
|
971
|
+
emphasis = false
|
|
972
|
+
}) {
|
|
973
|
+
const inbound = action.direction === "inbound";
|
|
974
|
+
const Icon = inbound ? ArrowDownToLine : ArrowUpFromLine;
|
|
975
|
+
const done = pending === 0;
|
|
976
|
+
if (emphasis) {
|
|
977
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
978
|
+
/* @__PURE__ */ jsxs(Button, { size: "sm", disabled: blocked, onClick: () => onStart(action), children: [
|
|
979
|
+
busy ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }),
|
|
980
|
+
busy ? "Sincronizando\u2026" : action.label
|
|
981
|
+
] }),
|
|
982
|
+
action.description && /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: action.description })
|
|
983
|
+
] });
|
|
984
|
+
}
|
|
985
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
986
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
987
|
+
/* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 text-sm font-medium", children: [
|
|
988
|
+
done ? /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3.5 w-3.5 shrink-0 text-success", "aria-hidden": true }) : /* @__PURE__ */ jsx(
|
|
989
|
+
Icon,
|
|
990
|
+
{
|
|
991
|
+
className: `h-3.5 w-3.5 shrink-0 ${inbound ? "text-info" : "text-muted-foreground"}`,
|
|
992
|
+
"aria-hidden": true
|
|
993
|
+
}
|
|
994
|
+
),
|
|
995
|
+
/* @__PURE__ */ jsx("span", { className: done ? "text-muted-foreground" : void 0, children: action.label }),
|
|
996
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: inbound ? "(traz dados para c\xE1)" : "(envia dados daqui)" }),
|
|
997
|
+
pending !== null && pending > 0 && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-1.5 text-[11px] tabular-nums text-muted-foreground", children: pending })
|
|
998
|
+
] }),
|
|
999
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs leading-relaxed text-muted-foreground", children: done ? "Tudo enviado \u2014 nada pendente." : action.description })
|
|
1000
|
+
] }),
|
|
1001
|
+
/* @__PURE__ */ jsxs(
|
|
1002
|
+
Button,
|
|
1003
|
+
{
|
|
1004
|
+
variant: "outline",
|
|
1005
|
+
size: "sm",
|
|
1006
|
+
className: "shrink-0",
|
|
1007
|
+
disabled: blocked || done,
|
|
1008
|
+
onClick: () => onStart(action),
|
|
1009
|
+
children: [
|
|
1010
|
+
busy && /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
|
|
1011
|
+
busy ? "Executando\u2026" : done ? "Em dia" : inbound ? "Trazer" : "Enviar"
|
|
1012
|
+
]
|
|
1013
|
+
}
|
|
1014
|
+
)
|
|
1015
|
+
] });
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// src/components/connect/when.ts
|
|
1019
|
+
function formatDate(iso) {
|
|
1020
|
+
return new Date(iso).toLocaleDateString(void 0, { day: "2-digit", month: "short", year: "numeric" });
|
|
1021
|
+
}
|
|
1022
|
+
function formatDateTime(iso) {
|
|
1023
|
+
return new Date(iso).toLocaleString();
|
|
1024
|
+
}
|
|
1025
|
+
function formatRelative(iso, t) {
|
|
1026
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
1027
|
+
const minutes = Math.round(diff / 6e4);
|
|
1028
|
+
if (minutes < 1) return t("connectors.when.now");
|
|
1029
|
+
if (minutes < 60) return t("connectors.when.minutes", { count: minutes });
|
|
1030
|
+
const hours = Math.round(minutes / 60);
|
|
1031
|
+
if (hours < 24) return t("connectors.when.hours", { count: hours });
|
|
1032
|
+
const days = Math.round(hours / 24);
|
|
1033
|
+
if (days < 30) return t("connectors.when.days", { count: days });
|
|
1034
|
+
return formatDate(iso);
|
|
1035
|
+
}
|
|
1036
|
+
function useConnectorRuns(connector) {
|
|
1037
|
+
const listRuns = connector.definition?.listRuns;
|
|
1038
|
+
const [runs, setRuns] = React6.useState([]);
|
|
1039
|
+
const [loading, setLoading] = React6.useState(!!listRuns);
|
|
1040
|
+
const [nonce, setNonce] = React6.useState(0);
|
|
1041
|
+
React6.useEffect(() => {
|
|
1042
|
+
if (!listRuns) {
|
|
1043
|
+
setRuns([]);
|
|
1044
|
+
setLoading(false);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
let cancelled = false;
|
|
1048
|
+
setLoading(true);
|
|
1049
|
+
void listRuns({ limit: 20 }).then((rows) => {
|
|
1050
|
+
if (!cancelled) setRuns(rows);
|
|
1051
|
+
}).catch(() => {
|
|
1052
|
+
if (!cancelled) setRuns([]);
|
|
1053
|
+
}).finally(() => {
|
|
1054
|
+
if (!cancelled) setLoading(false);
|
|
1055
|
+
});
|
|
1056
|
+
return () => {
|
|
1057
|
+
cancelled = true;
|
|
1058
|
+
};
|
|
1059
|
+
}, [connector.id, listRuns, nonce]);
|
|
1060
|
+
return {
|
|
1061
|
+
runs,
|
|
1062
|
+
loading,
|
|
1063
|
+
reload: React6.useCallback(() => setNonce((n) => n + 1), []),
|
|
1064
|
+
failing: isFailing(runs)
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
function isFailing(runs) {
|
|
1068
|
+
return runs[0]?.status === "error";
|
|
1069
|
+
}
|
|
1070
|
+
function hasRunHistory(connector) {
|
|
1071
|
+
const def = connector.definition;
|
|
1072
|
+
return !!def && (!!def.listRuns || !!def.RunActions || !!def.syncActions?.length);
|
|
1073
|
+
}
|
|
1074
|
+
var RUN_ICON = {
|
|
1075
|
+
success: { Icon: CheckCircle2, className: "text-success" },
|
|
1076
|
+
partial: { Icon: AlertTriangle, className: "text-warning" },
|
|
1077
|
+
error: { Icon: XCircle, className: "text-destructive" },
|
|
1078
|
+
running: { Icon: Loader2, className: "text-muted-foreground animate-spin" }
|
|
1079
|
+
};
|
|
1080
|
+
function ConnectorRuns({
|
|
1081
|
+
connector,
|
|
1082
|
+
state
|
|
1083
|
+
}) {
|
|
1084
|
+
const { t } = useTranslation();
|
|
1085
|
+
const def = connector.definition;
|
|
1086
|
+
const { runs, loading, reload } = state;
|
|
1087
|
+
const firstLoad = def?.firstLoad;
|
|
1088
|
+
const backfilling = !!firstLoad && runs.some((r) => r.status === "running");
|
|
1089
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1090
|
+
def?.syncActions?.length ? /* @__PURE__ */ jsx(ConnectorSyncActions, { actions: def.syncActions, onRan: reload }) : null,
|
|
1091
|
+
def?.RunActions && /* @__PURE__ */ jsx(def.RunActions, { onRan: reload }),
|
|
1092
|
+
backfilling && /* @__PURE__ */ jsxs("p", { className: "flex items-start gap-2 text-xs text-muted-foreground", children: [
|
|
1093
|
+
/* @__PURE__ */ jsx(Loader2, { className: "mt-px h-3.5 w-3.5 shrink-0 animate-spin" }),
|
|
1094
|
+
/* @__PURE__ */ jsx("span", { children: t("connectors.runs.backfilling") })
|
|
1095
|
+
] }),
|
|
1096
|
+
loading ? /* @__PURE__ */ jsx("p", { className: "flex justify-center py-6", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) }) : runs.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 rounded-lg border border-dashed border-border p-3", children: [
|
|
1097
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: t("connectors.runs.empty") }),
|
|
1098
|
+
firstLoad && /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: t("connectors.runs.emptyFirstLoad", { count: firstLoad.sampleSize }) })
|
|
1099
|
+
] }) : /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border overflow-hidden rounded-lg border border-border", children: runs.map((run, index) => /* @__PURE__ */ jsx(
|
|
1100
|
+
RunRow,
|
|
1101
|
+
{
|
|
1102
|
+
run,
|
|
1103
|
+
providerName: connector.name,
|
|
1104
|
+
explainFailure: index === 0
|
|
1105
|
+
},
|
|
1106
|
+
run.id ?? `${run.startedAt}-${index}`
|
|
1107
|
+
)) })
|
|
1108
|
+
] });
|
|
1109
|
+
}
|
|
1110
|
+
function RunRow({
|
|
1111
|
+
run,
|
|
1112
|
+
providerName,
|
|
1113
|
+
explainFailure
|
|
1114
|
+
}) {
|
|
1115
|
+
const { t } = useTranslation();
|
|
1116
|
+
const { Icon, className } = RUN_ICON[run.status] ?? RUN_ICON.success;
|
|
1117
|
+
const inbound = run.direction === "inbound";
|
|
1118
|
+
const DirectionIcon = inbound ? ArrowDownToLine : ArrowUpFromLine;
|
|
1119
|
+
const failed = run.status === "error";
|
|
1120
|
+
return /* @__PURE__ */ jsxs("li", { className: "px-3 py-2.5", children: [
|
|
1121
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1122
|
+
/* @__PURE__ */ jsx(Icon, { className: `h-3.5 w-3.5 shrink-0 ${className}`, "aria-hidden": true }),
|
|
1123
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t(`connectors.runs.status.${run.status}`) }),
|
|
1124
|
+
/* @__PURE__ */ jsx(
|
|
1125
|
+
"span",
|
|
1126
|
+
{
|
|
1127
|
+
className: "text-xs tabular-nums text-muted-foreground",
|
|
1128
|
+
title: run.startedAt ? formatDateTime(run.startedAt) : void 0,
|
|
1129
|
+
children: run.startedAt ? formatRelative(run.startedAt, t) : "\u2014"
|
|
1130
|
+
}
|
|
1131
|
+
),
|
|
1132
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground/70", children: [
|
|
1133
|
+
"\xB7 ",
|
|
1134
|
+
t(`connectors.trigger.${run.triggerKind}`)
|
|
1135
|
+
] }),
|
|
1136
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-1 text-[11px] text-muted-foreground", children: [
|
|
1137
|
+
/* @__PURE__ */ jsx(DirectionIcon, { className: "h-3 w-3", "aria-hidden": true }),
|
|
1138
|
+
t(inbound ? "connectors.runs.from" : "connectors.runs.to", { name: providerName })
|
|
1139
|
+
] })
|
|
1140
|
+
] }),
|
|
1141
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 pl-5 text-xs leading-relaxed text-foreground", children: runLine(run, t) }),
|
|
1142
|
+
failed && run.error && /* @__PURE__ */ jsx(RunFailure, { run }),
|
|
1143
|
+
failed && explainFailure && /* @__PURE__ */ jsx("p", { className: "mt-0.5 pl-5 text-[11px] leading-relaxed text-muted-foreground", children: t("connectors.runs.failedHint") })
|
|
1144
|
+
] });
|
|
1145
|
+
}
|
|
1146
|
+
function RunFailure({ run }) {
|
|
1147
|
+
const { t } = useTranslation();
|
|
1148
|
+
const facts = callFacts(run, t);
|
|
1149
|
+
const extra = extraStats(run);
|
|
1150
|
+
return /* @__PURE__ */ jsxs("details", { className: "group mt-1 ml-5 rounded-md border border-border bg-muted/40", children: [
|
|
1151
|
+
/* @__PURE__ */ jsxs("summary", { className: "flex cursor-pointer list-none items-start gap-1.5 px-2 py-1.5 text-xs leading-relaxed text-muted-foreground hover:text-foreground", children: [
|
|
1152
|
+
/* @__PURE__ */ jsx(XCircle, { className: "mt-px h-3.5 w-3.5 shrink-0 text-destructive/70", "aria-hidden": true }),
|
|
1153
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 line-clamp-2 group-open:line-clamp-none", children: run.error }),
|
|
1154
|
+
/* @__PURE__ */ jsx(
|
|
1155
|
+
ChevronRight,
|
|
1156
|
+
{
|
|
1157
|
+
className: "mt-px h-3.5 w-3.5 shrink-0 transition-transform group-open:rotate-90",
|
|
1158
|
+
"aria-hidden": true
|
|
1159
|
+
}
|
|
1160
|
+
),
|
|
1161
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t("connectors.runs.seeDetails") })
|
|
1162
|
+
] }),
|
|
1163
|
+
facts.length > 0 && /* @__PURE__ */ jsx("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 border-t border-border px-2 py-2 text-[11px] leading-relaxed", children: facts.map(([label, value]) => /* @__PURE__ */ jsxs(React6.Fragment, { children: [
|
|
1164
|
+
/* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: label }),
|
|
1165
|
+
/* @__PURE__ */ jsx("dd", { className: "min-w-0 break-words font-mono text-foreground/80", children: value })
|
|
1166
|
+
] }, label)) }),
|
|
1167
|
+
extra && /* @__PURE__ */ jsxs("div", { className: "border-t border-border px-2 py-2", children: [
|
|
1168
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("connectors.runs.detail.raw") }),
|
|
1169
|
+
/* @__PURE__ */ jsx("pre", { className: "mt-1 max-h-40 overflow-auto whitespace-pre-wrap break-words text-[11px] leading-relaxed text-foreground/70", children: extra })
|
|
1170
|
+
] })
|
|
1171
|
+
] });
|
|
1172
|
+
}
|
|
1173
|
+
function callFacts(run, t) {
|
|
1174
|
+
const facts = [];
|
|
1175
|
+
if (run.stream) facts.push([t("connectors.runs.detail.stream"), run.stream]);
|
|
1176
|
+
if (run.startedAt) facts.push([t("connectors.runs.detail.started"), formatDateTime(run.startedAt)]);
|
|
1177
|
+
if (run.finishedAt) facts.push([t("connectors.runs.detail.finished"), formatDateTime(run.finishedAt)]);
|
|
1178
|
+
const took = elapsed(run, t);
|
|
1179
|
+
if (took) facts.push([t("connectors.runs.detail.duration"), took]);
|
|
1180
|
+
const attempt = run.stats?.attempt;
|
|
1181
|
+
if (typeof attempt === "number") facts.push([t("connectors.runs.detail.attempt"), String(attempt)]);
|
|
1182
|
+
if (run.id) facts.push([t("connectors.runs.detail.runId"), run.id]);
|
|
1183
|
+
return facts;
|
|
1184
|
+
}
|
|
1185
|
+
function elapsed(run, t) {
|
|
1186
|
+
if (!run.startedAt || !run.finishedAt) return null;
|
|
1187
|
+
const ms = new Date(run.finishedAt).getTime() - new Date(run.startedAt).getTime();
|
|
1188
|
+
if (!Number.isFinite(ms) || ms < 0) return null;
|
|
1189
|
+
const seconds = Math.round(ms / 1e3);
|
|
1190
|
+
if (seconds < 90) return t("connectors.runs.detail.seconds", { count: seconds });
|
|
1191
|
+
return t("connectors.runs.detail.minutes", { count: Math.round(seconds / 60) });
|
|
1192
|
+
}
|
|
1193
|
+
function extraStats(run) {
|
|
1194
|
+
const stats = run.stats ?? {};
|
|
1195
|
+
const rest = Object.fromEntries(Object.entries(stats).filter(([key]) => key !== "attempt"));
|
|
1196
|
+
if (Object.keys(rest).length === 0) return null;
|
|
1197
|
+
try {
|
|
1198
|
+
return JSON.stringify(rest, null, 2);
|
|
1199
|
+
} catch {
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
function runLine(run, t) {
|
|
1204
|
+
if (run.message) return run.message;
|
|
1205
|
+
const fetched = run.fetched ?? 0;
|
|
1206
|
+
const written = run.written ?? 0;
|
|
1207
|
+
if (fetched > 0 || written > 0) return t("connectors.runs.counts", { written, fetched });
|
|
1208
|
+
if (run.status === "running") return t("connectors.runs.status.running");
|
|
1209
|
+
return t("connectors.runs.nothingToDo");
|
|
1210
|
+
}
|
|
1211
|
+
function ConnectorSettingsPanel({
|
|
1212
|
+
connector,
|
|
1213
|
+
onSaved,
|
|
1214
|
+
bare
|
|
1215
|
+
}) {
|
|
1216
|
+
const { t } = useTranslation();
|
|
1217
|
+
const def = connector.definition;
|
|
1218
|
+
const settings = def ? connectorSettings(def) : [];
|
|
1219
|
+
const [values, setValues] = React6.useState(
|
|
1220
|
+
() => initialValues(settings, {})
|
|
1221
|
+
);
|
|
1222
|
+
const [loaded, setLoaded] = React6.useState(false);
|
|
1223
|
+
const [saving, setSaving] = React6.useState(false);
|
|
1224
|
+
const [dirty, setDirty] = React6.useState(false);
|
|
1225
|
+
React6.useEffect(() => {
|
|
1226
|
+
if (settings.length === 0 || !def?.getSettings) return;
|
|
1227
|
+
let cancelled = false;
|
|
1228
|
+
setLoaded(false);
|
|
1229
|
+
void def.getSettings().catch(() => ({})).then((stored) => {
|
|
1230
|
+
if (cancelled) return;
|
|
1231
|
+
setValues(initialValues(settings, stored));
|
|
1232
|
+
setDirty(false);
|
|
1233
|
+
setLoaded(true);
|
|
1234
|
+
});
|
|
1235
|
+
return () => {
|
|
1236
|
+
cancelled = true;
|
|
1237
|
+
};
|
|
1238
|
+
}, [connector.id, settings.length]);
|
|
1239
|
+
if (settings.length === 0 || !def?.saveSettings) return null;
|
|
1240
|
+
const invalid = settings.filter((s) => !matchesPattern(s, values[s.key]));
|
|
1241
|
+
missingRequiredSettings(settings, values);
|
|
1242
|
+
function set(key, value) {
|
|
1243
|
+
setValues((current) => ({ ...current, [key]: value }));
|
|
1244
|
+
setDirty(true);
|
|
1245
|
+
}
|
|
1246
|
+
async function save() {
|
|
1247
|
+
setSaving(true);
|
|
1248
|
+
try {
|
|
1249
|
+
await def.saveSettings(values);
|
|
1250
|
+
setDirty(false);
|
|
1251
|
+
toast.success(t("connectors.settings.saved"));
|
|
1252
|
+
onSaved?.();
|
|
1253
|
+
} catch (e) {
|
|
1254
|
+
toast.error(e?.message ?? t("common.error"));
|
|
1255
|
+
} finally {
|
|
1256
|
+
setSaving(false);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1260
|
+
/* @__PURE__ */ jsx(
|
|
1261
|
+
SettingsGroup,
|
|
1262
|
+
{
|
|
1263
|
+
title: bare ? void 0 : t("connectors.settings.title"),
|
|
1264
|
+
description: bare ? void 0 : t("connectors.settings.subtitle"),
|
|
1265
|
+
children: settings.map((setting) => /* @__PURE__ */ jsx(
|
|
1266
|
+
SettingRow,
|
|
1267
|
+
{
|
|
1268
|
+
setting,
|
|
1269
|
+
value: values[setting.key],
|
|
1270
|
+
disabled: !loaded,
|
|
1271
|
+
onChange: set
|
|
1272
|
+
},
|
|
1273
|
+
setting.key
|
|
1274
|
+
))
|
|
1275
|
+
}
|
|
1276
|
+
),
|
|
1277
|
+
/* @__PURE__ */ jsxs(Button, { size: "sm", disabled: saving || !loaded || !dirty || invalid.length > 0, onClick: () => void save(), children: [
|
|
1278
|
+
saving ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Save, { className: "h-3.5 w-3.5" }),
|
|
1279
|
+
t("connectors.settings.save")
|
|
1280
|
+
] })
|
|
1281
|
+
] });
|
|
1282
|
+
}
|
|
1283
|
+
function SettingRow({
|
|
1284
|
+
setting,
|
|
1285
|
+
value,
|
|
1286
|
+
disabled,
|
|
1287
|
+
onChange
|
|
1288
|
+
}) {
|
|
1289
|
+
const { t } = useTranslation();
|
|
1290
|
+
const description = [setting.description, setting.requiredToOperate ? t("connectors.settings.required") : null].filter(Boolean).join(" ");
|
|
1291
|
+
switch (setting.type) {
|
|
1292
|
+
case "toggle":
|
|
1293
|
+
return /* @__PURE__ */ jsx(
|
|
1294
|
+
ToggleRow,
|
|
1295
|
+
{
|
|
1296
|
+
label: setting.label,
|
|
1297
|
+
description: description || void 0,
|
|
1298
|
+
checked: value === true,
|
|
1299
|
+
disabled,
|
|
1300
|
+
onChange: (next) => onChange(setting.key, next)
|
|
1301
|
+
}
|
|
1302
|
+
);
|
|
1303
|
+
case "select":
|
|
1304
|
+
return /* @__PURE__ */ jsx(
|
|
1305
|
+
SelectRow,
|
|
1306
|
+
{
|
|
1307
|
+
label: setting.label,
|
|
1308
|
+
description: description || void 0,
|
|
1309
|
+
value: typeof value === "string" ? value : "",
|
|
1310
|
+
options: setting.options ?? [],
|
|
1311
|
+
disabled,
|
|
1312
|
+
onChange: (next) => onChange(setting.key, next)
|
|
1313
|
+
}
|
|
1314
|
+
);
|
|
1315
|
+
case "multi-select":
|
|
1316
|
+
return /* @__PURE__ */ jsx(
|
|
1317
|
+
MultiSelectRow,
|
|
1318
|
+
{
|
|
1319
|
+
label: setting.label,
|
|
1320
|
+
description: description || void 0,
|
|
1321
|
+
value: Array.isArray(value) ? value : [],
|
|
1322
|
+
options: setting.options ?? [],
|
|
1323
|
+
disabled,
|
|
1324
|
+
onChange: (next) => onChange(setting.key, next)
|
|
1325
|
+
}
|
|
1326
|
+
);
|
|
1327
|
+
default: {
|
|
1328
|
+
const text = typeof value === "string" ? value : "";
|
|
1329
|
+
const bad = !matchesPattern(setting, text);
|
|
1330
|
+
return /* @__PURE__ */ jsx(
|
|
1331
|
+
TextRow,
|
|
1332
|
+
{
|
|
1333
|
+
label: setting.label,
|
|
1334
|
+
description: description || void 0,
|
|
1335
|
+
value: text,
|
|
1336
|
+
placeholder: setting.placeholder,
|
|
1337
|
+
hint: setting.patternHint,
|
|
1338
|
+
invalid: bad,
|
|
1339
|
+
disabled,
|
|
1340
|
+
onChange: (next) => onChange(setting.key, next)
|
|
1341
|
+
}
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
function matchesPattern(setting, value) {
|
|
1347
|
+
if (!setting.pattern || setting.type !== "text") return true;
|
|
1348
|
+
if (!isSettingSet(value)) return true;
|
|
1349
|
+
try {
|
|
1350
|
+
return new RegExp(setting.pattern).test(String(value).trim());
|
|
1351
|
+
} catch {
|
|
1352
|
+
return true;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
function initialValues(settings, stored) {
|
|
1356
|
+
const out = {};
|
|
1357
|
+
for (const setting of settings) {
|
|
1358
|
+
const value = stored[setting.key];
|
|
1359
|
+
if (isSettingSet(value) || typeof value === "boolean") {
|
|
1360
|
+
out[setting.key] = value;
|
|
1361
|
+
} else if (setting.defaultValue !== void 0) {
|
|
1362
|
+
out[setting.key] = setting.defaultValue;
|
|
1363
|
+
} else {
|
|
1364
|
+
out[setting.key] = setting.type === "multi-select" ? [] : setting.type === "toggle" ? false : "";
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
return out;
|
|
1368
|
+
}
|
|
1369
|
+
function Step({
|
|
1370
|
+
n,
|
|
1371
|
+
title,
|
|
1372
|
+
children,
|
|
1373
|
+
muted
|
|
1374
|
+
}) {
|
|
1375
|
+
return /* @__PURE__ */ jsxs("section", { className: muted ? "opacity-60" : void 0, children: [
|
|
1376
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1377
|
+
/* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-muted text-[11px] font-semibold text-muted-foreground", children: n }),
|
|
1378
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-foreground", children: title })
|
|
1379
|
+
] }),
|
|
1380
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 pl-7", children })
|
|
1381
|
+
] });
|
|
1382
|
+
}
|
|
1383
|
+
function PlanePicker({
|
|
1384
|
+
planes,
|
|
1385
|
+
value,
|
|
1386
|
+
onChange
|
|
1387
|
+
}) {
|
|
1388
|
+
const { t } = useTranslation();
|
|
1389
|
+
if (planes.length === 0) {
|
|
1390
|
+
return /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed border-border p-3 text-xs text-muted-foreground", children: t("connectors.plane.undecided") });
|
|
1391
|
+
}
|
|
1392
|
+
return /* @__PURE__ */ jsx("div", { className: "grid gap-2 sm:grid-cols-2", children: planes.map((plane) => {
|
|
1393
|
+
const Icon = plane === "agentic" ? Sparkles : RefreshCw;
|
|
1394
|
+
const selected = value === plane;
|
|
1395
|
+
const only = planes.length === 1;
|
|
1396
|
+
return /* @__PURE__ */ jsxs(
|
|
1397
|
+
Button,
|
|
1398
|
+
{
|
|
1399
|
+
variant: "bare",
|
|
1400
|
+
size: "bare",
|
|
1401
|
+
type: "button",
|
|
1402
|
+
disabled: only,
|
|
1403
|
+
onClick: () => onChange?.(plane),
|
|
1404
|
+
className: "rounded-lg border p-3 text-left transition-colors inline-block whitespace-normal text-[length:inherit] font-normal" + (selected ? "border-primary bg-primary/5" : "border-border hover:bg-muted/40") + (only ? " cursor-default" : ""),
|
|
1405
|
+
children: [
|
|
1406
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-sm font-medium text-foreground", children: [
|
|
1407
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }),
|
|
1408
|
+
t(`connectors.plane.${plane}`),
|
|
1409
|
+
plane === "agentic" && !only && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-1.5 py-px text-[10px] font-normal text-primary", children: t("connectors.plane.recommended") })
|
|
1410
|
+
] }),
|
|
1411
|
+
/* @__PURE__ */ jsx("span", { className: "mt-1 block text-xs text-muted-foreground", children: t(`connectors.plane.${plane}.desc`) })
|
|
1412
|
+
]
|
|
1413
|
+
},
|
|
1414
|
+
plane
|
|
1415
|
+
);
|
|
1416
|
+
}) });
|
|
1417
|
+
}
|
|
1418
|
+
function ConnectorHeading({ connector }) {
|
|
1419
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
1420
|
+
/* @__PURE__ */ jsx(ConnectorMark, { connector }),
|
|
1421
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1422
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
1423
|
+
/* @__PURE__ */ jsx("p", { className: "min-w-0 font-semibold leading-tight text-foreground", children: connector.name }),
|
|
1424
|
+
/* @__PURE__ */ jsx(StateChip, { state: connector.state, className: "shrink-0" })
|
|
1425
|
+
] }),
|
|
1426
|
+
connector.description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs leading-relaxed text-muted-foreground", children: connector.description })
|
|
1427
|
+
] })
|
|
1428
|
+
] });
|
|
1429
|
+
}
|
|
1430
|
+
function Section({ title, children }) {
|
|
1431
|
+
return /* @__PURE__ */ jsxs("section", { className: "space-y-1.5", children: [
|
|
1432
|
+
/* @__PURE__ */ jsx("h4", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60", children: title }),
|
|
1433
|
+
children
|
|
1434
|
+
] });
|
|
1435
|
+
}
|
|
1436
|
+
function hasOperatingSettings(connector) {
|
|
1437
|
+
const def = connector.definition;
|
|
1438
|
+
if (!def) return false;
|
|
1439
|
+
return connectorSettings(def).length > 0 || (def.fields ?? []).length > 0 || !!def.ExtraPanel || !!def.setPlane;
|
|
1440
|
+
}
|
|
1441
|
+
function missingSettingLabels(connector) {
|
|
1442
|
+
const def = connector.definition;
|
|
1443
|
+
if (!def || !connector.missingSettings?.length) return [];
|
|
1444
|
+
const declared = new Map(connectorSettings(def).map((s) => [s.key, s.label]));
|
|
1445
|
+
return connector.missingSettings.map((key) => declared.get(key) ?? key);
|
|
1446
|
+
}
|
|
1447
|
+
function DocsLink({ connector }) {
|
|
1448
|
+
const { t } = useTranslation();
|
|
1449
|
+
if (!connector.docsUrl) return null;
|
|
1450
|
+
return /* @__PURE__ */ jsxs(
|
|
1451
|
+
"a",
|
|
1452
|
+
{
|
|
1453
|
+
href: connector.docsUrl,
|
|
1454
|
+
target: "_blank",
|
|
1455
|
+
rel: "noreferrer",
|
|
1456
|
+
className: "inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground",
|
|
1457
|
+
children: [
|
|
1458
|
+
/* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" }),
|
|
1459
|
+
" ",
|
|
1460
|
+
t("connectors.docs")
|
|
1461
|
+
]
|
|
1462
|
+
}
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
function ConnectorJourney({
|
|
1466
|
+
connector,
|
|
1467
|
+
onChanged,
|
|
1468
|
+
onRevoke
|
|
1469
|
+
}) {
|
|
1470
|
+
return isLive(connector.state) ? /* @__PURE__ */ jsx(ConnectorHome, { connector, onChanged, onRevoke }) : /* @__PURE__ */ jsx(ConnectorInstall, { connector, onChanged });
|
|
1471
|
+
}
|
|
1472
|
+
function ConnectorInstall({
|
|
1473
|
+
connector,
|
|
1474
|
+
onChanged
|
|
1475
|
+
}) {
|
|
1476
|
+
const { t } = useTranslation();
|
|
1477
|
+
const def = connector.definition;
|
|
1478
|
+
const [plane, setPlane] = React6.useState(
|
|
1479
|
+
connector.planes.includes("agentic") ? "agentic" : connector.planes[0]
|
|
1480
|
+
);
|
|
1481
|
+
const [values, setValues] = React6.useState({});
|
|
1482
|
+
const secrets = React6.useRef({});
|
|
1483
|
+
const [busy, setBusy] = React6.useState(null);
|
|
1484
|
+
React6.useEffect(() => {
|
|
1485
|
+
setValues({});
|
|
1486
|
+
secrets.current = {};
|
|
1487
|
+
setBusy(null);
|
|
1488
|
+
setPlane(connector.planes.includes("agentic") ? "agentic" : connector.planes[0]);
|
|
1489
|
+
}, [connector.id]);
|
|
1490
|
+
const run = useRunner(setBusy, onChanged);
|
|
1491
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
1492
|
+
/* @__PURE__ */ jsx(Step, { n: 1, title: t("connectors.journey.what"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1493
|
+
/* @__PURE__ */ jsx(Capabilities, { connector }),
|
|
1494
|
+
/* @__PURE__ */ jsx(ConnectorPermissions, { connector })
|
|
1495
|
+
] }) }),
|
|
1496
|
+
/* @__PURE__ */ jsx(Step, { n: 2, title: t("connectors.journey.how"), children: /* @__PURE__ */ jsx(PlanePicker, { planes: connector.planes, value: plane, onChange: setPlane }) }),
|
|
1497
|
+
/* @__PURE__ */ jsx(Step, { n: 3, title: t("connectors.journey.connectAccount"), children: def ? /* @__PURE__ */ jsx(
|
|
1498
|
+
ConnectPanel,
|
|
1499
|
+
{
|
|
1500
|
+
connector,
|
|
1501
|
+
values,
|
|
1502
|
+
setValues,
|
|
1503
|
+
secrets,
|
|
1504
|
+
busy,
|
|
1505
|
+
run
|
|
1506
|
+
}
|
|
1507
|
+
) : /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("connectors.authorize.elsewhere") }) }),
|
|
1508
|
+
/* @__PURE__ */ jsxs("p", { className: "rounded-lg border border-dashed border-border p-3 text-xs text-muted-foreground", children: [
|
|
1509
|
+
t(plane === "agentic" ? "connectors.after.willAgentic" : "connectors.after.willSync"),
|
|
1510
|
+
hasOperatingSettings(connector) ? ` ${t("connectors.after.settingsLater")}` : ""
|
|
1511
|
+
] }),
|
|
1512
|
+
/* @__PURE__ */ jsx(DocsLink, { connector })
|
|
1513
|
+
] });
|
|
1514
|
+
}
|
|
1515
|
+
function ConnectorHome({
|
|
1516
|
+
connector,
|
|
1517
|
+
onChanged,
|
|
1518
|
+
onRevoke
|
|
1519
|
+
}) {
|
|
1520
|
+
const { t } = useTranslation();
|
|
1521
|
+
const def = connector.definition;
|
|
1522
|
+
const needsSetup = connector.state === "needs_setup";
|
|
1523
|
+
const settingsAvailable = hasOperatingSettings(connector);
|
|
1524
|
+
const runsAvailable = hasRunHistory(connector);
|
|
1525
|
+
const [tab, setTab] = React6.useState(
|
|
1526
|
+
needsSetup && settingsAvailable ? "settings" : "overview"
|
|
1527
|
+
);
|
|
1528
|
+
const [busy, setBusy] = React6.useState(null);
|
|
1529
|
+
const run = useRunner(setBusy, onChanged);
|
|
1530
|
+
const [values, setValues] = React6.useState({});
|
|
1531
|
+
const secrets = React6.useRef({});
|
|
1532
|
+
const runs = useConnectorRuns(connector);
|
|
1533
|
+
React6.useEffect(() => {
|
|
1534
|
+
setTab(needsSetup && settingsAvailable ? "settings" : "overview");
|
|
1535
|
+
setBusy(null);
|
|
1536
|
+
setValues({});
|
|
1537
|
+
secrets.current = {};
|
|
1538
|
+
}, [connector.id]);
|
|
1539
|
+
const missing = missingSettingLabels(connector);
|
|
1540
|
+
if (!def) {
|
|
1541
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
|
|
1542
|
+
/* @__PURE__ */ jsx(GrantFacts, { connector }),
|
|
1543
|
+
/* @__PURE__ */ jsx(Section, { title: t("connectors.overview.reaches"), children: /* @__PURE__ */ jsx(Capabilities, { connector }) }),
|
|
1544
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3 border-t border-border pt-3", children: [
|
|
1545
|
+
/* @__PURE__ */ jsx(DocsLink, { connector }),
|
|
1546
|
+
/* @__PURE__ */ jsx(GrantRevoke, { connector, onRevoke })
|
|
1547
|
+
] })
|
|
1548
|
+
] });
|
|
1549
|
+
}
|
|
1550
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxs(Tabs, { value: tab, onValueChange: (next) => setTab(next), children: [
|
|
1551
|
+
/* @__PURE__ */ jsxs(TabsList, { children: [
|
|
1552
|
+
/* @__PURE__ */ jsx(TabsTrigger, { value: "overview", children: t("connectors.tab.overview") }),
|
|
1553
|
+
def.OperatePanel && /* @__PURE__ */ jsx(TabsTrigger, { value: "operate", children: def.operateLabel ?? t("connectors.tab.operate") }),
|
|
1554
|
+
runsAvailable && /* @__PURE__ */ jsxs(TabsTrigger, { value: "runs", children: [
|
|
1555
|
+
t("connectors.tab.runs"),
|
|
1556
|
+
runs.failing && /* @__PURE__ */ jsx("span", { className: "ml-1.5 h-1.5 w-1.5 rounded-full bg-destructive", "aria-hidden": true })
|
|
1557
|
+
] }),
|
|
1558
|
+
settingsAvailable && /* @__PURE__ */ jsxs(TabsTrigger, { value: "settings", children: [
|
|
1559
|
+
t("connectors.tab.settings"),
|
|
1560
|
+
needsSetup && /* @__PURE__ */ jsx("span", { className: "ml-1.5 h-1.5 w-1.5 rounded-full bg-warning", "aria-hidden": true })
|
|
1561
|
+
] })
|
|
1562
|
+
] }),
|
|
1563
|
+
/* @__PURE__ */ jsxs(TabsContent, { value: "overview", className: "space-y-5 pt-4", children: [
|
|
1564
|
+
/* @__PURE__ */ jsx(ConnectionReport, { connector }),
|
|
1565
|
+
/* @__PURE__ */ jsx(Section, { title: t("connectors.overview.resolves"), children: /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: t(`connectors.plane.${connector.activePlane ?? connector.planes[0] ?? "sync"}.desc`) }) }),
|
|
1566
|
+
def.howItSyncs && def.howItSyncs.length > 0 && /* @__PURE__ */ jsx(Section, { title: t("connectors.overview.whenItSyncs"), children: /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: def.howItSyncs.map((line, index) => /* @__PURE__ */ jsxs("li", { className: "flex gap-1.5 text-xs leading-relaxed text-muted-foreground", children: [
|
|
1567
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\xB7" }),
|
|
1568
|
+
/* @__PURE__ */ jsx("span", { children: line })
|
|
1569
|
+
] }, index)) }) }),
|
|
1570
|
+
/* @__PURE__ */ jsx(Section, { title: t("connectors.overview.reaches"), children: /* @__PURE__ */ jsx(Capabilities, { connector }) }),
|
|
1571
|
+
/* @__PURE__ */ jsx(ConnectorPermissions, { connector }),
|
|
1572
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3 border-t border-border pt-3", children: [
|
|
1573
|
+
/* @__PURE__ */ jsx(DocsLink, { connector }),
|
|
1574
|
+
def.disconnect && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
1575
|
+
/* @__PURE__ */ jsxs(
|
|
1576
|
+
Button,
|
|
1577
|
+
{
|
|
1578
|
+
variant: "bare",
|
|
1579
|
+
size: "bare",
|
|
1580
|
+
type: "button",
|
|
1581
|
+
disabled: busy === "off",
|
|
1582
|
+
onClick: () => run("off", () => def.disconnect(), t("connectors.disconnect")),
|
|
1583
|
+
className: "flex items-center gap-1.5 text-xs text-destructive transition-colors hover:text-destructive/80 disabled:opacity-50 font-normal",
|
|
1584
|
+
children: [
|
|
1585
|
+
busy === "off" ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Power, { className: "h-3.5 w-3.5" }),
|
|
1586
|
+
t("connectors.disconnect")
|
|
1587
|
+
]
|
|
1588
|
+
}
|
|
1589
|
+
),
|
|
1590
|
+
def.disconnectNote && /* @__PURE__ */ jsx("p", { className: "text-[11px] leading-relaxed text-muted-foreground", children: def.disconnectNote })
|
|
1591
|
+
] })
|
|
1592
|
+
] })
|
|
1593
|
+
] }),
|
|
1594
|
+
def.OperatePanel && /* @__PURE__ */ jsx(TabsContent, { value: "operate", className: "pt-4", children: /* @__PURE__ */ jsx(def.OperatePanel, {}) }),
|
|
1595
|
+
runsAvailable && /* @__PURE__ */ jsx(TabsContent, { value: "runs", className: "pt-4", children: /* @__PURE__ */ jsx(ConnectorRuns, { connector, state: runs }) }),
|
|
1596
|
+
settingsAvailable && /* @__PURE__ */ jsxs(TabsContent, { value: "settings", className: "space-y-5 pt-4", children: [
|
|
1597
|
+
needsSetup && missing.length > 0 && /* @__PURE__ */ jsxs("p", { className: "flex items-start gap-1.5 text-xs text-warning", children: [
|
|
1598
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "mt-px h-3.5 w-3.5 shrink-0" }),
|
|
1599
|
+
/* @__PURE__ */ jsx("span", { children: t("connectors.missingNamed", { list: missing.join(", ") }) })
|
|
1600
|
+
] }),
|
|
1601
|
+
(def.fields ?? []).length > 0 && /* @__PURE__ */ jsx(Section, { title: t("connectors.credentials.section"), children: /* @__PURE__ */ jsx(
|
|
1602
|
+
ConnectPanel,
|
|
1603
|
+
{
|
|
1604
|
+
connector,
|
|
1605
|
+
values,
|
|
1606
|
+
setValues,
|
|
1607
|
+
secrets,
|
|
1608
|
+
busy,
|
|
1609
|
+
run
|
|
1610
|
+
}
|
|
1611
|
+
) }),
|
|
1612
|
+
/* @__PURE__ */ jsx(ConnectorSettingsPanel, { connector, onSaved: onChanged, bare: true }),
|
|
1613
|
+
def.setPlane && connector.planes.length > 1 && /* @__PURE__ */ jsx(Section, { title: t("connectors.overview.resolves"), children: /* @__PURE__ */ jsx(
|
|
1614
|
+
PlanePicker,
|
|
1615
|
+
{
|
|
1616
|
+
planes: connector.planes,
|
|
1617
|
+
value: connector.activePlane,
|
|
1618
|
+
onChange: (next) => run("plane", () => def.setPlane(next), t("connectors.plane.switched"))
|
|
1619
|
+
}
|
|
1620
|
+
) }),
|
|
1621
|
+
def.ExtraPanel && /* @__PURE__ */ jsx(Section, { title: t("connectors.overview.provider", { name: connector.name }), children: /* @__PURE__ */ jsx(def.ExtraPanel, {}) })
|
|
1622
|
+
] })
|
|
1623
|
+
] }) });
|
|
1624
|
+
}
|
|
1625
|
+
function useRunner(setBusy, onChanged) {
|
|
1626
|
+
const { t } = useTranslation();
|
|
1627
|
+
return React6.useCallback(
|
|
1628
|
+
async (kind, fn, okMessage) => {
|
|
1629
|
+
setBusy(kind);
|
|
1630
|
+
try {
|
|
1631
|
+
await fn();
|
|
1632
|
+
if (okMessage) toast.success(okMessage);
|
|
1633
|
+
onChanged?.();
|
|
1634
|
+
} catch (e) {
|
|
1635
|
+
toast.error(
|
|
1636
|
+
e instanceof CredentialSinkMissingError ? t("connectors.credentials.noSink") : e instanceof ConnectorOAuthStarterMissingError ? t("connectors.oauth.noStarter") : e?.message ?? t("common.error")
|
|
1637
|
+
);
|
|
1638
|
+
} finally {
|
|
1639
|
+
setBusy(null);
|
|
1640
|
+
}
|
|
1641
|
+
},
|
|
1642
|
+
[onChanged, t]
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
function ConnectorDetail({
|
|
1646
|
+
connector,
|
|
1647
|
+
open,
|
|
1648
|
+
onOpenChange,
|
|
1649
|
+
onChanged,
|
|
1650
|
+
onRevoke
|
|
1651
|
+
}) {
|
|
1652
|
+
if (!connector) return null;
|
|
1653
|
+
return /* @__PURE__ */ jsxs(
|
|
1654
|
+
RightRailPage,
|
|
1655
|
+
{
|
|
1656
|
+
title: connector.name,
|
|
1657
|
+
open,
|
|
1658
|
+
onClose: () => onOpenChange(false),
|
|
1659
|
+
width: "max-w-lg",
|
|
1660
|
+
children: [
|
|
1661
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 px-5 pt-4", children: /* @__PURE__ */ jsx(ConnectorHeading, { connector }) }),
|
|
1662
|
+
/* @__PURE__ */ jsx(SheetBody, { children: /* @__PURE__ */ jsx(ConnectorJourney, { connector, onChanged, onRevoke }) })
|
|
1663
|
+
]
|
|
1664
|
+
}
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
function ConnectionReport({ connector }) {
|
|
1668
|
+
const issues = connector.issues ?? [];
|
|
1669
|
+
if (issues.length > 0) {
|
|
1670
|
+
return /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: issues.map((issue) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-1.5 text-xs leading-relaxed text-warning", children: [
|
|
1671
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "mt-px h-3.5 w-3.5 shrink-0" }),
|
|
1672
|
+
/* @__PURE__ */ jsx("span", { children: issue })
|
|
1673
|
+
] }, issue)) });
|
|
1674
|
+
}
|
|
1675
|
+
if (!connector.detail) return null;
|
|
1676
|
+
return /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: connector.detail });
|
|
1677
|
+
}
|
|
1678
|
+
function Note({ children }) {
|
|
1679
|
+
return /* @__PURE__ */ jsxs("p", { className: "flex items-start gap-2 rounded-lg border border-dashed border-border p-3 text-xs text-muted-foreground", children: [
|
|
1680
|
+
/* @__PURE__ */ jsx(Info, { className: "mt-px h-3.5 w-3.5 shrink-0" }),
|
|
1681
|
+
/* @__PURE__ */ jsx("span", { children })
|
|
1682
|
+
] });
|
|
1683
|
+
}
|
|
1684
|
+
function Capabilities({ connector }) {
|
|
1685
|
+
const { t } = useTranslation();
|
|
1686
|
+
const entityCount = connector.entityCount ?? connector.entities?.length;
|
|
1687
|
+
const tools = connector.agentTools ?? [];
|
|
1688
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2 text-xs text-muted-foreground", children: [
|
|
1689
|
+
connector.direction === "inbound" && /* @__PURE__ */ jsxs("p", { children: [
|
|
1690
|
+
connector.readOnly ? t("connectors.readOnly") : t("connectors.readWrite"),
|
|
1691
|
+
typeof entityCount === "number" && entityCount > 0 ? ` \xB7 ${t("connectors.entitiesCount", { count: entityCount })}` : "",
|
|
1692
|
+
connector.agentConfig?.model ? ` \xB7 ${connector.agentConfig.model}` : ""
|
|
1693
|
+
] }),
|
|
1694
|
+
connector.direction === "outbound" && !connector.permissions?.length && connector.entities?.length ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: connector.entities.map((entity) => /* @__PURE__ */ jsx("span", { className: "rounded-full border border-border px-2 py-0.5 text-[11px]", children: entityLabel(t, entity) }, entity)) }) : null,
|
|
1695
|
+
tools.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
1696
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1", children: t("connectors.tools") }),
|
|
1697
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: tools.map((tool) => /* @__PURE__ */ jsx(
|
|
1698
|
+
"span",
|
|
1699
|
+
{
|
|
1700
|
+
title: tool.description,
|
|
1701
|
+
className: "rounded border border-border px-1.5 py-0.5 font-mono text-[10px]",
|
|
1702
|
+
children: tool.name
|
|
1703
|
+
},
|
|
1704
|
+
tool.name
|
|
1705
|
+
)) })
|
|
1706
|
+
] }),
|
|
1707
|
+
connector.scopes && connector.scopes.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: connector.scopes.map((s) => /* @__PURE__ */ jsx("span", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-[10px]", children: s }, s)) }),
|
|
1708
|
+
tools.length === 0 && !connector.entities?.length && !connector.scopes?.length && typeof entityCount !== "number" && connector.direction !== "inbound" && /* @__PURE__ */ jsx("p", { children: t("connectors.capabilitiesUnknown") })
|
|
1709
|
+
] });
|
|
1710
|
+
}
|
|
1711
|
+
function entityLabel(t, entity) {
|
|
1712
|
+
const key = `connectors.entity.${entity}`;
|
|
1713
|
+
const translated = t(key);
|
|
1714
|
+
return translated && translated !== key ? translated : entity;
|
|
1715
|
+
}
|
|
1716
|
+
function ProviderInstallStep({ connector }) {
|
|
1717
|
+
const { t } = useTranslation();
|
|
1718
|
+
const install = connector.providerInstall;
|
|
1719
|
+
if (!install) return null;
|
|
1720
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1721
|
+
/* @__PURE__ */ jsx(Note, { children: t("connectors.authorize.installFirst", { name: connector.name }) }),
|
|
1722
|
+
/* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsxs("a", { href: install.url, target: "_blank", rel: "noreferrer", children: [
|
|
1723
|
+
/* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5" }),
|
|
1724
|
+
t("connectors.authorize.installAt", { name: connector.name })
|
|
1725
|
+
] }) })
|
|
1726
|
+
] });
|
|
1727
|
+
}
|
|
1728
|
+
function ConnectPanel({
|
|
1729
|
+
connector,
|
|
1730
|
+
values,
|
|
1731
|
+
setValues,
|
|
1732
|
+
secrets,
|
|
1733
|
+
busy,
|
|
1734
|
+
run
|
|
1735
|
+
}) {
|
|
1736
|
+
const { t } = useTranslation();
|
|
1737
|
+
const def = connector.definition;
|
|
1738
|
+
const tenantId = useTenantOptional()?.org?.id;
|
|
1739
|
+
const needsSink = needsCredentialSink(def);
|
|
1740
|
+
const sinkMissing = needsSink && !getConnectorCredentialSink();
|
|
1741
|
+
const secretFieldCount = (def.fields ?? []).filter(isSecretField).length;
|
|
1742
|
+
const hasStoredCredential = connector.state === "connected" || connector.state === "needs_setup";
|
|
1743
|
+
const [formKey, setFormKey] = React6.useState(0);
|
|
1744
|
+
const fields = def.fields ?? [];
|
|
1745
|
+
const oauthDoor = def.authKind === "oauth" ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1746
|
+
/* @__PURE__ */ jsx(Note, { children: t(fields.length > 0 ? "connectors.authorize.oauthOrKey" : "connectors.authorize.oauthOnly") }),
|
|
1747
|
+
/* @__PURE__ */ jsxs(
|
|
1748
|
+
Button,
|
|
1749
|
+
{
|
|
1750
|
+
disabled: busy === "oauth" || !def.startOAuth,
|
|
1751
|
+
onClick: () => run("oauth", async () => {
|
|
1752
|
+
const url = await def.startOAuth(window.location.href, { tenantId });
|
|
1753
|
+
window.location.href = url;
|
|
1754
|
+
}),
|
|
1755
|
+
children: [
|
|
1756
|
+
busy === "oauth" ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Plug, { className: "h-4 w-4" }),
|
|
1757
|
+
`${t("connectors.connect")} ${def.name}`
|
|
1758
|
+
]
|
|
1759
|
+
}
|
|
1760
|
+
)
|
|
1761
|
+
] }) : null;
|
|
1762
|
+
if (oauthDoor && fields.length === 0) return oauthDoor;
|
|
1763
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1764
|
+
oauthDoor,
|
|
1765
|
+
/* @__PURE__ */ jsx(ProviderInstallStep, { connector: def }),
|
|
1766
|
+
needsSink && /* @__PURE__ */ jsx(Note, { children: sinkMissing ? t("connectors.credentials.noSink") : t("connectors.credentials.held") }),
|
|
1767
|
+
needsSink && !sinkMissing && secretFieldCount > 1 && hasStoredCredential && /* @__PURE__ */ jsx(Note, { children: t("connectors.credentials.replaces") }),
|
|
1768
|
+
fields.length === 0 && !def.providerInstall && !oauthDoor && /* @__PURE__ */ jsx(Note, { children: t("connectors.authorize.nothingToFill") }),
|
|
1769
|
+
/* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: fields.map(
|
|
1770
|
+
(f) => isSecretField(f) ? /* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
1771
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: f.label }),
|
|
1772
|
+
/* @__PURE__ */ jsx(
|
|
1773
|
+
Input,
|
|
1774
|
+
{
|
|
1775
|
+
type: f.type,
|
|
1776
|
+
defaultValue: "",
|
|
1777
|
+
onChange: (e) => {
|
|
1778
|
+
secrets.current[f.key] = e.target.value;
|
|
1779
|
+
},
|
|
1780
|
+
placeholder: f.placeholder,
|
|
1781
|
+
autoComplete: "off",
|
|
1782
|
+
className: "mt-1"
|
|
1783
|
+
}
|
|
1784
|
+
)
|
|
1785
|
+
] }, `${f.key}:${formKey}`) : /* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
1786
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: f.label }),
|
|
1787
|
+
/* @__PURE__ */ jsx(
|
|
1788
|
+
Input,
|
|
1789
|
+
{
|
|
1790
|
+
type: f.type,
|
|
1791
|
+
value: values[f.key] ?? "",
|
|
1792
|
+
onChange: (e) => setValues((v) => ({ ...v, [f.key]: e.target.value })),
|
|
1793
|
+
placeholder: f.placeholder,
|
|
1794
|
+
className: "mt-1"
|
|
1795
|
+
}
|
|
1796
|
+
)
|
|
1797
|
+
] }, f.key)
|
|
1798
|
+
) }),
|
|
1799
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1800
|
+
def.testConnection && !needsSink && /* @__PURE__ */ jsxs(
|
|
1801
|
+
Button,
|
|
1802
|
+
{
|
|
1803
|
+
variant: "outline",
|
|
1804
|
+
size: "sm",
|
|
1805
|
+
disabled: busy === "test",
|
|
1806
|
+
onClick: () => run("test", async () => {
|
|
1807
|
+
const res = await probeConnection({ definition: def, values });
|
|
1808
|
+
if (!res.ok) throw new Error(res.message ?? t("common.error"));
|
|
1809
|
+
toast.success(res.message ?? t("connectors.connected"));
|
|
1810
|
+
}),
|
|
1811
|
+
children: [
|
|
1812
|
+
busy === "test" ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Plug, { className: "h-3.5 w-3.5" }),
|
|
1813
|
+
t("connectors.test")
|
|
1814
|
+
]
|
|
1815
|
+
}
|
|
1816
|
+
),
|
|
1817
|
+
def.saveConnection && /* @__PURE__ */ jsxs(
|
|
1818
|
+
Button,
|
|
1819
|
+
{
|
|
1820
|
+
size: "sm",
|
|
1821
|
+
disabled: busy === "save" || sinkMissing,
|
|
1822
|
+
onClick: () => run("save", async () => {
|
|
1823
|
+
try {
|
|
1824
|
+
await submitConnection({
|
|
1825
|
+
definition: def,
|
|
1826
|
+
values: { ...values, ...secrets.current },
|
|
1827
|
+
tenantId
|
|
1828
|
+
});
|
|
1829
|
+
} finally {
|
|
1830
|
+
secrets.current = {};
|
|
1831
|
+
setFormKey((k) => k + 1);
|
|
1832
|
+
}
|
|
1833
|
+
}, t("connectors.save")),
|
|
1834
|
+
children: [
|
|
1835
|
+
busy === "save" ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3.5 w-3.5" }),
|
|
1836
|
+
fields.length === 0 ? `${t("connectors.connect")} ${def.name}` : t("connectors.save")
|
|
1837
|
+
]
|
|
1838
|
+
}
|
|
1839
|
+
)
|
|
1840
|
+
] })
|
|
1841
|
+
] });
|
|
1842
|
+
}
|
|
1843
|
+
function DisconnectAction({
|
|
1844
|
+
onDisconnect,
|
|
1845
|
+
busy
|
|
1846
|
+
}) {
|
|
1847
|
+
const { t } = useTranslation();
|
|
1848
|
+
return /* @__PURE__ */ jsxs(
|
|
1849
|
+
Button,
|
|
1850
|
+
{
|
|
1851
|
+
variant: "bare",
|
|
1852
|
+
size: "bare",
|
|
1853
|
+
type: "button",
|
|
1854
|
+
disabled: busy,
|
|
1855
|
+
onClick: onDisconnect,
|
|
1856
|
+
className: "flex items-center gap-1.5 text-xs text-destructive transition-colors hover:text-destructive/80 disabled:opacity-50 font-normal",
|
|
1857
|
+
children: [
|
|
1858
|
+
busy ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Power, { className: "h-3.5 w-3.5" }),
|
|
1859
|
+
t("connectors.disconnect")
|
|
1860
|
+
]
|
|
1861
|
+
}
|
|
1862
|
+
);
|
|
1863
|
+
}
|
|
1864
|
+
function GrantFacts({ connector }) {
|
|
1865
|
+
const { t } = useTranslation();
|
|
1866
|
+
const facts = [
|
|
1867
|
+
connector.createdAt ? t("connectors.connectedOn", { date: formatDate(connector.createdAt) }) : null,
|
|
1868
|
+
connector.lastUsedAt ? t("connectors.lastUsed", { when: formatRelative(connector.lastUsedAt, t) }) : t("connectors.neverUsed"),
|
|
1869
|
+
connector.grantedByMe === false ? t("connectors.grantedByOther") : null
|
|
1870
|
+
].filter(Boolean);
|
|
1871
|
+
return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: facts.join(" \xB7 ") });
|
|
1872
|
+
}
|
|
1873
|
+
function GrantRevoke({
|
|
1874
|
+
connector,
|
|
1875
|
+
onRevoke
|
|
1876
|
+
}) {
|
|
1877
|
+
const { t } = useTranslation();
|
|
1878
|
+
const [revoking, setRevoking] = React6.useState(false);
|
|
1879
|
+
if (connector.revocable === false) {
|
|
1880
|
+
return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("connectors.partOfApp") });
|
|
1881
|
+
}
|
|
1882
|
+
return /* @__PURE__ */ jsx(
|
|
1883
|
+
DisconnectAction,
|
|
1884
|
+
{
|
|
1885
|
+
busy: revoking,
|
|
1886
|
+
onDisconnect: async () => {
|
|
1887
|
+
setRevoking(true);
|
|
1888
|
+
try {
|
|
1889
|
+
await onRevoke?.(connector);
|
|
1890
|
+
} finally {
|
|
1891
|
+
setRevoking(false);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
function toInstalled(definition, status) {
|
|
1898
|
+
return {
|
|
1899
|
+
definition,
|
|
1900
|
+
connected: status.connected,
|
|
1901
|
+
detail: status.detail,
|
|
1902
|
+
issues: status.issues,
|
|
1903
|
+
plane: status.plane,
|
|
1904
|
+
needsSetup: status.needsSetup,
|
|
1905
|
+
missingSettings: status.missingSettings
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
var connectionsFlight = null;
|
|
1909
|
+
var statusFlights = /* @__PURE__ */ new Map();
|
|
1910
|
+
function loadConnections(tenantId, ids, client) {
|
|
1911
|
+
const flight = connectionsFlight;
|
|
1912
|
+
if (flight && flight.tenantId === tenantId && ids.every((id) => flight.ids.has(id))) return flight.promise;
|
|
1913
|
+
const promise = listConnections(ids, { tenantId, client }).catch(() => /* @__PURE__ */ new Map());
|
|
1914
|
+
const entry = { tenantId, ids: new Set(ids), promise };
|
|
1915
|
+
connectionsFlight = entry;
|
|
1916
|
+
void promise.finally(() => {
|
|
1917
|
+
if (connectionsFlight === entry) connectionsFlight = null;
|
|
1918
|
+
});
|
|
1919
|
+
return promise;
|
|
1920
|
+
}
|
|
1921
|
+
function batchable(definition) {
|
|
1922
|
+
return typeof definition.statusFromConnection === "function";
|
|
1923
|
+
}
|
|
1924
|
+
function loadStatus(definition, tenantId) {
|
|
1925
|
+
const key = `${tenantId ?? ""} ${definition.id}`;
|
|
1926
|
+
let flight = statusFlights.get(key);
|
|
1927
|
+
if (!flight) {
|
|
1928
|
+
flight = definition.getStatus().then((status) => toInstalled(definition, status)).catch(() => ({ definition, connected: false }));
|
|
1929
|
+
statusFlights.set(key, flight);
|
|
1930
|
+
void flight.finally(() => statusFlights.delete(key));
|
|
1931
|
+
}
|
|
1932
|
+
return flight;
|
|
1933
|
+
}
|
|
1934
|
+
function loadConnectorStatuses(definitions, tenantId, options = {}) {
|
|
1935
|
+
const batched = tenantId ? definitions.filter(batchable) : [];
|
|
1936
|
+
const shared = batched.length > 0 ? loadConnections(tenantId, batched.map((d) => d.id), options.client) : null;
|
|
1937
|
+
return Promise.all(
|
|
1938
|
+
definitions.map(async (definition) => {
|
|
1939
|
+
if (!shared || !batchable(definition)) return loadStatus(definition, tenantId);
|
|
1940
|
+
const rows = await shared;
|
|
1941
|
+
try {
|
|
1942
|
+
return toInstalled(
|
|
1943
|
+
definition,
|
|
1944
|
+
definition.statusFromConnection(
|
|
1945
|
+
rows.get(connectionKey(definition.id, definition.instanceKey ?? "")) ?? null
|
|
1946
|
+
)
|
|
1947
|
+
);
|
|
1948
|
+
} catch {
|
|
1949
|
+
return { definition, connected: false };
|
|
1950
|
+
}
|
|
1951
|
+
})
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
// src/components/connect/useConnectors.ts
|
|
1956
|
+
var catalogPromise = null;
|
|
1957
|
+
function loadCatalog() {
|
|
1958
|
+
if (catalogPromise) return catalogPromise;
|
|
1959
|
+
const connection = resolveFayzAgentConnection();
|
|
1960
|
+
if (!connection) return Promise.resolve([]);
|
|
1961
|
+
catalogPromise = fetch(`${connection.apiBaseUrl}/connectors?locale=pt-BR`).then(async (response) => {
|
|
1962
|
+
if (!response.ok) throw new Error(`catalog HTTP ${response.status}`);
|
|
1963
|
+
const body = await response.json();
|
|
1964
|
+
return Array.isArray(body) ? body : body.connectors ?? [];
|
|
1965
|
+
}).catch(() => {
|
|
1966
|
+
catalogPromise = null;
|
|
1967
|
+
return [];
|
|
1968
|
+
});
|
|
1969
|
+
return catalogPromise;
|
|
1970
|
+
}
|
|
1971
|
+
function useConnectorCatalog() {
|
|
1972
|
+
const [catalog, setCatalog] = React6.useState(null);
|
|
1973
|
+
React6.useEffect(() => {
|
|
1974
|
+
let cancelled = false;
|
|
1975
|
+
void loadCatalog().then((list) => {
|
|
1976
|
+
if (!cancelled) setCatalog(list);
|
|
1977
|
+
});
|
|
1978
|
+
return () => {
|
|
1979
|
+
cancelled = true;
|
|
1980
|
+
};
|
|
1981
|
+
}, []);
|
|
1982
|
+
return catalog;
|
|
1983
|
+
}
|
|
1984
|
+
var GRANTS_TTL_MS = 3e4;
|
|
1985
|
+
var grantsCache = /* @__PURE__ */ new Map();
|
|
1986
|
+
function invalidateGrants() {
|
|
1987
|
+
grantsCache.clear();
|
|
1988
|
+
}
|
|
1989
|
+
var _revision = 0;
|
|
1990
|
+
var _revisionListeners = /* @__PURE__ */ new Set();
|
|
1991
|
+
function refreshConnectors() {
|
|
1992
|
+
invalidateGrants();
|
|
1993
|
+
_revision += 1;
|
|
1994
|
+
_revisionListeners.forEach((listener) => listener());
|
|
1995
|
+
}
|
|
1996
|
+
function useConnectorsRevision() {
|
|
1997
|
+
return React6.useSyncExternalStore(
|
|
1998
|
+
(listener) => {
|
|
1999
|
+
_revisionListeners.add(listener);
|
|
2000
|
+
return () => _revisionListeners.delete(listener);
|
|
2001
|
+
},
|
|
2002
|
+
() => _revision,
|
|
2003
|
+
() => _revision
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
function loadGrants(key, fetcher) {
|
|
2007
|
+
const hit = grantsCache.get(key);
|
|
2008
|
+
if (hit && Date.now() - hit.at < GRANTS_TTL_MS) return hit.promise;
|
|
2009
|
+
const promise = fetcher();
|
|
2010
|
+
promise.catch(() => grantsCache.delete(key));
|
|
2011
|
+
grantsCache.set(key, { at: Date.now(), promise });
|
|
2012
|
+
return promise;
|
|
2013
|
+
}
|
|
2014
|
+
function useInstalledModuleIds() {
|
|
2015
|
+
const runtime = usePluginRuntimeOptional();
|
|
2016
|
+
return React6.useMemo(() => (runtime?.plugins ?? []).map((p) => p.id), [runtime]);
|
|
2017
|
+
}
|
|
2018
|
+
function useInstalledDefinitions(hostPluginId) {
|
|
2019
|
+
const runtime = usePluginRuntimeOptional();
|
|
2020
|
+
return React6.useMemo(() => {
|
|
2021
|
+
const byHost = runtime?.connectorsByHost;
|
|
2022
|
+
if (!byHost) return [];
|
|
2023
|
+
if (hostPluginId) return byHost.get(hostPluginId) ?? [];
|
|
2024
|
+
return [...byHost.values()].flat();
|
|
2025
|
+
}, [runtime, hostPluginId]);
|
|
2026
|
+
}
|
|
2027
|
+
function useConnectors(options = {}) {
|
|
2028
|
+
const { hostPluginId, includeGrants = true, includeCatalog = true } = options;
|
|
2029
|
+
const definitions = useInstalledDefinitions(hostPluginId);
|
|
2030
|
+
const catalog = useConnectorCatalog();
|
|
2031
|
+
const tenant = useTenantOptional();
|
|
2032
|
+
const tenantId = tenant?.org?.id;
|
|
2033
|
+
const connection = React6.useMemo(() => resolveFayzAgentConnection(), []);
|
|
2034
|
+
const [installed, setInstalled] = React6.useState(null);
|
|
2035
|
+
const [grants, setGrants] = React6.useState(null);
|
|
2036
|
+
const [error, setError] = React6.useState(null);
|
|
2037
|
+
const nonce = useConnectorsRevision();
|
|
2038
|
+
const call = React6.useCallback(
|
|
2039
|
+
async (path, init) => {
|
|
2040
|
+
if (!connection) throw new Error("Este app n\xE3o est\xE1 ligado a um projeto Fayz.");
|
|
2041
|
+
const supabase = getFayzSupabaseClientOptional();
|
|
2042
|
+
const session = (await supabase?.auth.getSession())?.data.session;
|
|
2043
|
+
if (!session?.access_token) throw new Error("Sess\xE3o expirada \u2014 entre de novo.");
|
|
2044
|
+
return fetch(
|
|
2045
|
+
`${connection.apiBaseUrl}/public/projects/${connection.projectId}/agents/connections${path}`,
|
|
2046
|
+
{
|
|
2047
|
+
...init,
|
|
2048
|
+
headers: {
|
|
2049
|
+
...init?.headers ?? {},
|
|
2050
|
+
Authorization: `Bearer ${session.access_token}`,
|
|
2051
|
+
"Content-Type": "application/json"
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
);
|
|
2055
|
+
},
|
|
2056
|
+
[connection]
|
|
2057
|
+
);
|
|
2058
|
+
React6.useEffect(() => {
|
|
2059
|
+
let cancelled = false;
|
|
2060
|
+
void loadConnectorStatuses(definitions, tenantId).then((list) => {
|
|
2061
|
+
if (!cancelled) setInstalled(list);
|
|
2062
|
+
});
|
|
2063
|
+
return () => {
|
|
2064
|
+
cancelled = true;
|
|
2065
|
+
};
|
|
2066
|
+
}, [definitions, tenantId, nonce]);
|
|
2067
|
+
React6.useEffect(() => {
|
|
2068
|
+
if (!includeGrants || !connection || !tenantId) {
|
|
2069
|
+
setGrants(includeGrants ? null : []);
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
let cancelled = false;
|
|
2073
|
+
void loadGrants(`${connection.projectId}:${tenantId}`, async () => {
|
|
2074
|
+
const response = await call(`?tenantId=${encodeURIComponent(tenantId)}`);
|
|
2075
|
+
const body = await response.json();
|
|
2076
|
+
if (!response.ok) throw new Error(body.message ?? "N\xE3o foi poss\xEDvel carregar as conex\xF5es.");
|
|
2077
|
+
return body.connections ?? [];
|
|
2078
|
+
}).then((list) => {
|
|
2079
|
+
if (cancelled) return;
|
|
2080
|
+
setGrants(list);
|
|
2081
|
+
setError(null);
|
|
2082
|
+
}).catch((e) => {
|
|
2083
|
+
if (cancelled) return;
|
|
2084
|
+
setError(e.message);
|
|
2085
|
+
setGrants([]);
|
|
2086
|
+
});
|
|
2087
|
+
return () => {
|
|
2088
|
+
cancelled = true;
|
|
2089
|
+
};
|
|
2090
|
+
}, [call, tenantId, includeGrants, connection, nonce]);
|
|
2091
|
+
const revokeGrant = React6.useCallback(
|
|
2092
|
+
async (connector) => {
|
|
2093
|
+
if (!tenantId) throw new Error("Nenhuma conta selecionada.");
|
|
2094
|
+
const response = await call(`/${connector.id}?tenantId=${encodeURIComponent(tenantId)}`, { method: "DELETE" });
|
|
2095
|
+
if (!response.ok) {
|
|
2096
|
+
const body = await response.json().catch(() => ({}));
|
|
2097
|
+
throw new Error(body.message ?? "N\xE3o foi poss\xEDvel desconectar.");
|
|
2098
|
+
}
|
|
2099
|
+
invalidateGrants();
|
|
2100
|
+
setGrants((current) => (current ?? []).filter((g) => g.id !== connector.id));
|
|
2101
|
+
},
|
|
2102
|
+
[call, tenantId]
|
|
2103
|
+
);
|
|
2104
|
+
const waitingGrants = includeGrants && !!connection && !!tenantId && grants === null;
|
|
2105
|
+
const connectors = React6.useMemo(() => {
|
|
2106
|
+
if (installed === null || waitingGrants) return null;
|
|
2107
|
+
return buildConnectorList({
|
|
2108
|
+
installed,
|
|
2109
|
+
grants: includeGrants ? grants ?? [] : [],
|
|
2110
|
+
catalog: includeCatalog ? catalog ?? [] : []
|
|
2111
|
+
});
|
|
2112
|
+
}, [installed, grants, catalog, includeGrants, includeCatalog, waitingGrants]);
|
|
2113
|
+
return {
|
|
2114
|
+
connectors,
|
|
2115
|
+
error,
|
|
2116
|
+
reload: refreshConnectors,
|
|
2117
|
+
revokeGrant,
|
|
2118
|
+
agentAvailable: !!connection,
|
|
2119
|
+
tenantReady: !!tenantId
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
var useConnectorsSurface = create((set) => ({
|
|
2123
|
+
selected: null,
|
|
2124
|
+
pending: false,
|
|
2125
|
+
select: (selected) => set({ selected }),
|
|
2126
|
+
open: (selected) => set({ selected, pending: true }),
|
|
2127
|
+
consume: () => set({ pending: false })
|
|
2128
|
+
}));
|
|
2129
|
+
function openConnectorsPanel(connector) {
|
|
2130
|
+
useConnectorsSurface.getState().open(connector?.id ?? null);
|
|
2131
|
+
useRightRailStore.getState().openPanel("chat");
|
|
2132
|
+
useChatStore.getState().setOpen(true);
|
|
2133
|
+
}
|
|
2134
|
+
function useConnectorsPanelAvailable() {
|
|
2135
|
+
return useRightRailStore((s) => s.mounted && s.panels.some((p) => p.id === "chat"));
|
|
2136
|
+
}
|
|
2137
|
+
function ConnectorsPanel({ onClose }) {
|
|
2138
|
+
const { t } = useTranslation();
|
|
2139
|
+
const { connectors, reload, revokeGrant, agentAvailable, tenantReady } = useConnectors();
|
|
2140
|
+
const selectedId = useConnectorsSurface((s) => s.selected);
|
|
2141
|
+
const select = useConnectorsSurface((s) => s.select);
|
|
2142
|
+
const [query, setQuery] = React6.useState("");
|
|
2143
|
+
const [failure, setFailure] = React6.useState(null);
|
|
2144
|
+
const [revoking, setRevoking] = React6.useState(null);
|
|
2145
|
+
const selected = React6.useMemo(
|
|
2146
|
+
() => (connectors ?? []).find((c) => c.id === selectedId) ?? null,
|
|
2147
|
+
[connectors, selectedId]
|
|
2148
|
+
);
|
|
2149
|
+
const filtered = React6.useMemo(
|
|
2150
|
+
() => filterConnectors(connectors ?? [], "outbound", query),
|
|
2151
|
+
[connectors, query]
|
|
2152
|
+
);
|
|
2153
|
+
const hosted = useRightRailHosted();
|
|
2154
|
+
useRightRailDrill(
|
|
2155
|
+
hosted ? {
|
|
2156
|
+
title: selected?.name ?? t("settings.connectors"),
|
|
2157
|
+
back: onClose
|
|
2158
|
+
} : null
|
|
2159
|
+
);
|
|
2160
|
+
const closeRef = React6.useRef(onClose);
|
|
2161
|
+
closeRef.current = onClose;
|
|
2162
|
+
React6.useEffect(() => {
|
|
2163
|
+
if (hosted && !selectedId) closeRef.current();
|
|
2164
|
+
}, [hosted, selectedId]);
|
|
2165
|
+
return /* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto", children: [
|
|
2166
|
+
!hosted && /* @__PURE__ */ jsxs("div", { className: "sticky top-0 z-10 flex items-center justify-between gap-2 border-b border-border/40 bg-card/95 px-3 py-2 backdrop-blur", children: [
|
|
2167
|
+
selected ? /* @__PURE__ */ jsxs(
|
|
2168
|
+
Button,
|
|
2169
|
+
{
|
|
2170
|
+
variant: "bare",
|
|
2171
|
+
size: "bare",
|
|
2172
|
+
onClick: () => select(null),
|
|
2173
|
+
className: "inline-flex min-w-0 items-center gap-1 rounded-md px-1 py-0.5 text-[11px] text-muted-foreground transition-colors hover:bg-muted hover:text-foreground justify-start whitespace-normal font-normal",
|
|
2174
|
+
children: [
|
|
2175
|
+
/* @__PURE__ */ jsx(ChevronLeft, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
2176
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: t("settings.connectors") })
|
|
2177
|
+
]
|
|
2178
|
+
}
|
|
2179
|
+
) : /* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold text-foreground", children: t("settings.connectors") }),
|
|
2180
|
+
/* @__PURE__ */ jsx(
|
|
2181
|
+
Button,
|
|
2182
|
+
{
|
|
2183
|
+
variant: "bare",
|
|
2184
|
+
size: "bare",
|
|
2185
|
+
onClick: onClose,
|
|
2186
|
+
className: "rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground transition-colors hover:bg-muted hover:text-foreground font-normal",
|
|
2187
|
+
children: t("common.done")
|
|
2188
|
+
}
|
|
2189
|
+
)
|
|
2190
|
+
] }),
|
|
2191
|
+
!agentAvailable || !tenantReady ? /* @__PURE__ */ jsx("p", { className: "p-4 text-sm text-muted-foreground", children: t(agentAvailable ? "connectors.noOrgBody" : "connectors.noProjectBody") }) : selected ? /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-3", children: [
|
|
2192
|
+
/* @__PURE__ */ jsx(ConnectorHeading, { connector: selected }),
|
|
2193
|
+
failure && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: failure }),
|
|
2194
|
+
/* @__PURE__ */ jsx(
|
|
2195
|
+
ConnectorJourney,
|
|
2196
|
+
{
|
|
2197
|
+
connector: selected,
|
|
2198
|
+
onChanged: reload,
|
|
2199
|
+
onRevoke: (connector) => setRevoking(connector)
|
|
2200
|
+
}
|
|
2201
|
+
),
|
|
2202
|
+
/* @__PURE__ */ jsx(
|
|
2203
|
+
ConfirmDialog,
|
|
2204
|
+
{
|
|
2205
|
+
open: !!revoking,
|
|
2206
|
+
title: t("connectors.revoke"),
|
|
2207
|
+
description: revoking ? t("connectors.revokeConfirm", { name: revoking.name }) : void 0,
|
|
2208
|
+
variant: "destructive",
|
|
2209
|
+
confirmLabel: t("connectors.revoke"),
|
|
2210
|
+
onConfirm: async () => {
|
|
2211
|
+
const connector = revoking;
|
|
2212
|
+
setRevoking(null);
|
|
2213
|
+
if (!connector) return;
|
|
2214
|
+
try {
|
|
2215
|
+
await revokeGrant(connector);
|
|
2216
|
+
select(null);
|
|
2217
|
+
} catch (e) {
|
|
2218
|
+
setFailure(e.message);
|
|
2219
|
+
}
|
|
2220
|
+
},
|
|
2221
|
+
onCancel: () => setRevoking(null)
|
|
2222
|
+
}
|
|
2223
|
+
)
|
|
2224
|
+
] }) : hosted ? (
|
|
2225
|
+
// Requested but not resolved yet — holding the slot, not offering the
|
|
2226
|
+
// catalogue as a consolation.
|
|
2227
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3 p-3", "aria-hidden": true, children: [
|
|
2228
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2229
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-9 shrink-0 rounded-lg" }),
|
|
2230
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1.5", children: [
|
|
2231
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-28" }),
|
|
2232
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-40" })
|
|
2233
|
+
] })
|
|
2234
|
+
] }),
|
|
2235
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-20 w-full rounded-xl" })
|
|
2236
|
+
] })
|
|
2237
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2238
|
+
/* @__PURE__ */ jsx("div", { className: "p-3 pb-2", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
2239
|
+
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
|
|
2240
|
+
/* @__PURE__ */ jsx(
|
|
2241
|
+
Input,
|
|
2242
|
+
{
|
|
2243
|
+
value: query,
|
|
2244
|
+
onChange: (e) => setQuery(e.target.value),
|
|
2245
|
+
placeholder: t("connectors.search"),
|
|
2246
|
+
className: "h-8 rounded-lg border-border pl-8 pr-2.5 text-[13px]"
|
|
2247
|
+
}
|
|
2248
|
+
)
|
|
2249
|
+
] }) }),
|
|
2250
|
+
connectors === null ? /* @__PURE__ */ jsx("div", { className: "space-y-3 p-3", "aria-hidden": true, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2251
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-9 shrink-0 rounded-lg" }),
|
|
2252
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1.5", children: [
|
|
2253
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-28" }),
|
|
2254
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-40" })
|
|
2255
|
+
] })
|
|
2256
|
+
] }, i)) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2257
|
+
/* @__PURE__ */ jsx(Section2, { title: t("connectors.groupMine"), items: filtered, onSelect: (c) => select(c.id) }),
|
|
2258
|
+
filtered.length === 0 && /* @__PURE__ */ jsx("p", { className: "p-4 text-sm text-muted-foreground", children: t("connectors.emptyBody") })
|
|
2259
|
+
] })
|
|
2260
|
+
] })
|
|
2261
|
+
] });
|
|
2262
|
+
}
|
|
2263
|
+
function Section2({
|
|
2264
|
+
title,
|
|
2265
|
+
items,
|
|
2266
|
+
onSelect
|
|
2267
|
+
}) {
|
|
2268
|
+
if (items.length === 0) return null;
|
|
2269
|
+
return /* @__PURE__ */ jsxs("section", { className: "pb-2", children: [
|
|
2270
|
+
/* @__PURE__ */ jsx("h3", { className: "px-3 pb-1 pt-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/50", children: title }),
|
|
2271
|
+
/* @__PURE__ */ jsx("ul", { children: items.map((c) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
2272
|
+
Button,
|
|
2273
|
+
{
|
|
2274
|
+
variant: "bare",
|
|
2275
|
+
size: "bare",
|
|
2276
|
+
onClick: () => onSelect(c),
|
|
2277
|
+
className: "flex w-full items-center gap-2.5 px-3 py-1.5 text-left transition-colors hover:bg-muted/50 justify-start whitespace-normal text-[length:inherit] font-normal rounded-none",
|
|
2278
|
+
children: [
|
|
2279
|
+
/* @__PURE__ */ jsx(ConnectorMark, { connector: c, size: "sm" }),
|
|
2280
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
2281
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-[13px] font-medium text-foreground", children: c.name }),
|
|
2282
|
+
/* @__PURE__ */ jsx("span", { className: "mt-0.5 block", children: /* @__PURE__ */ jsx(StateChip, { state: c.state }) })
|
|
2283
|
+
] })
|
|
2284
|
+
]
|
|
2285
|
+
}
|
|
2286
|
+
) }, c.id)) })
|
|
2287
|
+
] });
|
|
2288
|
+
}
|
|
2289
|
+
function AddConnectorPanel({
|
|
2290
|
+
open,
|
|
2291
|
+
onOpenChange,
|
|
2292
|
+
connectors,
|
|
2293
|
+
categories,
|
|
2294
|
+
onSelect
|
|
2295
|
+
}) {
|
|
2296
|
+
const { t } = useTranslation();
|
|
2297
|
+
const [query, setQuery] = React6.useState("");
|
|
2298
|
+
const modules = useInstalledModuleIds();
|
|
2299
|
+
const catalog = useConnectorCatalog();
|
|
2300
|
+
const scoped = !!categories?.length;
|
|
2301
|
+
const addable = React6.useMemo(
|
|
2302
|
+
() => inCategories(addableConnectors(connectors ?? []), categories),
|
|
2303
|
+
[connectors, categories]
|
|
2304
|
+
);
|
|
2305
|
+
const shelf = React6.useMemo(() => {
|
|
2306
|
+
if (connectors === null) return [];
|
|
2307
|
+
return inCategories(
|
|
2308
|
+
shelfIntegrations({ present: connectors, installedModules: modules, catalog: catalog ?? [] }),
|
|
2309
|
+
categories
|
|
2310
|
+
);
|
|
2311
|
+
}, [connectors, modules, catalog, categories]);
|
|
2312
|
+
const filtered = React6.useMemo(() => filterConnectors(addable, "all", query), [addable, query]);
|
|
2313
|
+
const filteredShelf = React6.useMemo(() => filterAvailable(shelf, query), [shelf, query]);
|
|
2314
|
+
const soon = React6.useMemo(() => comingSoonHere(filteredShelf), [filteredShelf]);
|
|
2315
|
+
const elsewhere = React6.useMemo(() => withAnotherModule(filteredShelf), [filteredShelf]);
|
|
2316
|
+
const searchable = addable.length + shelf.length > 6;
|
|
2317
|
+
return /* @__PURE__ */ jsx(Modal, { open, onOpenChange, children: /* @__PURE__ */ jsxs(ModalContent, { size: "2xl", children: [
|
|
2318
|
+
/* @__PURE__ */ jsxs(ModalHeader, { children: [
|
|
2319
|
+
/* @__PURE__ */ jsx(ModalTitle, { children: t("connectors.add") }),
|
|
2320
|
+
/* @__PURE__ */ jsx(ModalDescription, { children: t(scoped ? "connectors.addSubtitleScoped" : "connectors.addSubtitle") })
|
|
2321
|
+
] }),
|
|
2322
|
+
searchable && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
2323
|
+
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
|
|
2324
|
+
/* @__PURE__ */ jsx(
|
|
2325
|
+
Input,
|
|
2326
|
+
{
|
|
2327
|
+
value: query,
|
|
2328
|
+
onChange: (e) => setQuery(e.target.value),
|
|
2329
|
+
placeholder: t("connectors.search"),
|
|
2330
|
+
className: "rounded-lg border-border bg-background pl-9"
|
|
2331
|
+
}
|
|
2332
|
+
)
|
|
2333
|
+
] }),
|
|
2334
|
+
/* @__PURE__ */ jsxs("div", { className: "-mx-1 max-h-[55vh] overflow-y-auto px-1", children: [
|
|
2335
|
+
connectors === null && /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border", "aria-hidden": true, children: [0, 1, 2, 3, 4].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 py-3", children: [
|
|
2336
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-9 shrink-0 rounded-lg" }),
|
|
2337
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-2", children: [
|
|
2338
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-32" }),
|
|
2339
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-56" })
|
|
2340
|
+
] }),
|
|
2341
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-24 rounded-lg" })
|
|
2342
|
+
] }, i)) }),
|
|
2343
|
+
connectors !== null && filtered.length === 0 && filteredShelf.length === 0 && /* @__PURE__ */ jsxs("div", { className: "py-8 text-center", children: [
|
|
2344
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: t(query ? "connectors.noneFoundTitle" : "connectors.addEmptyTitle") }),
|
|
2345
|
+
/* @__PURE__ */ jsx("p", { className: "mx-auto mt-1 max-w-sm text-sm text-muted-foreground", children: query ? t("connectors.noneFound", { query }) : t("connectors.addEmptyBody") })
|
|
2346
|
+
] }),
|
|
2347
|
+
filtered.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2348
|
+
filteredShelf.length > 0 && /* @__PURE__ */ jsx(SectionHeading, { children: t("connectors.sectionInstalled") }),
|
|
2349
|
+
/* @__PURE__ */ jsx("ul", { className: "divide-y divide-border", children: filtered.map((c) => /* @__PURE__ */ jsx(CatalogRow, { connector: c, onSelect }, c.id)) })
|
|
2350
|
+
] }),
|
|
2351
|
+
/* @__PURE__ */ jsx(ComingSoonSection, { integrations: soon }),
|
|
2352
|
+
/* @__PURE__ */ jsx(AvailableSection, { connectors: elsewhere })
|
|
2353
|
+
] }),
|
|
2354
|
+
connectors !== null && /* @__PURE__ */ jsx(AskForMore, { onAsked: () => onOpenChange(false) })
|
|
2355
|
+
] }) });
|
|
2356
|
+
}
|
|
2357
|
+
function AskForMore({ onAsked }) {
|
|
2358
|
+
const { t } = useTranslation();
|
|
2359
|
+
const available = useConnectorsPanelAvailable();
|
|
2360
|
+
const setDraft = useChatStore((s) => s.setDraft);
|
|
2361
|
+
const setOpen = useChatStore((s) => s.setOpen);
|
|
2362
|
+
const openPanel = useRightRailStore((s) => s.openPanel);
|
|
2363
|
+
if (!available) return null;
|
|
2364
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t border-border pt-3 text-sm text-muted-foreground", children: [
|
|
2365
|
+
/* @__PURE__ */ jsx("span", { children: t("connectors.askMore") }),
|
|
2366
|
+
/* @__PURE__ */ jsxs(
|
|
2367
|
+
Button,
|
|
2368
|
+
{
|
|
2369
|
+
variant: "bare",
|
|
2370
|
+
size: "bare",
|
|
2371
|
+
onClick: () => {
|
|
2372
|
+
setDraft(t("connectors.askMoreDraft"));
|
|
2373
|
+
openPanel("chat");
|
|
2374
|
+
setOpen(true);
|
|
2375
|
+
onAsked();
|
|
2376
|
+
},
|
|
2377
|
+
className: "inline-flex items-center gap-1.5 rounded-lg border border-border px-2.5 py-1 font-medium text-foreground transition-colors hover:bg-muted/50 text-[length:inherit]",
|
|
2378
|
+
children: [
|
|
2379
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5" }),
|
|
2380
|
+
t("connectors.askMoreCta")
|
|
2381
|
+
]
|
|
2382
|
+
}
|
|
2383
|
+
)
|
|
2384
|
+
] });
|
|
2385
|
+
}
|
|
2386
|
+
function CatalogRow({
|
|
2387
|
+
connector,
|
|
2388
|
+
onSelect
|
|
2389
|
+
}) {
|
|
2390
|
+
const category = categoryLabel(connector.category);
|
|
2391
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
2392
|
+
Button,
|
|
2393
|
+
{
|
|
2394
|
+
variant: "bare",
|
|
2395
|
+
size: "bare",
|
|
2396
|
+
onClick: () => onSelect(connector),
|
|
2397
|
+
className: "flex w-full items-center gap-3 py-3 text-left transition-colors hover:bg-muted/40 justify-start whitespace-normal text-[length:inherit] font-normal rounded-none",
|
|
2398
|
+
children: [
|
|
2399
|
+
/* @__PURE__ */ jsx(ConnectorMark, { connector, size: "sm" }),
|
|
2400
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
2401
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2402
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: connector.name }),
|
|
2403
|
+
category && /* @__PURE__ */ jsx("span", { className: "rounded-full border border-border px-2 py-0.5 text-[11px] text-muted-foreground", children: category }),
|
|
2404
|
+
connector.planes.map((p) => /* @__PURE__ */ jsx(PlaneBadge, { plane: p, active: connector.activePlane === p }, p))
|
|
2405
|
+
] }),
|
|
2406
|
+
connector.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 line-clamp-1 text-xs text-muted-foreground", children: connector.description })
|
|
2407
|
+
] }),
|
|
2408
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0", children: /* @__PURE__ */ jsx(StateChip, { state: connector.state }) })
|
|
2409
|
+
]
|
|
2410
|
+
}
|
|
2411
|
+
) });
|
|
2412
|
+
}
|
|
2413
|
+
function SectionHeading({ children }) {
|
|
2414
|
+
return /* @__PURE__ */ jsx("h3", { className: "mt-4 pb-1 pt-3 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground first:mt-0 first:pt-0", children });
|
|
2415
|
+
}
|
|
2416
|
+
function ComingSoonSection({ integrations }) {
|
|
2417
|
+
const { t } = useTranslation();
|
|
2418
|
+
if (integrations.length === 0) return null;
|
|
2419
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2420
|
+
/* @__PURE__ */ jsx(SectionHeading, { children: t("connectors.sectionSoon") }),
|
|
2421
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-xs text-muted-foreground", children: t("connectors.soonBody") }),
|
|
2422
|
+
/* @__PURE__ */ jsx("ul", { className: "divide-y divide-border", children: integrations.map((row) => /* @__PURE__ */ jsx(ShelfRow, { integration: row, note: t("connectors.soonNote") }, row.id)) })
|
|
2423
|
+
] });
|
|
2424
|
+
}
|
|
2425
|
+
function AvailableSection({ connectors }) {
|
|
2426
|
+
const { t } = useTranslation();
|
|
2427
|
+
if (connectors.length === 0) return null;
|
|
2428
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2429
|
+
/* @__PURE__ */ jsx(SectionHeading, { children: t("connectors.sectionOtherModule") }),
|
|
2430
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-xs text-muted-foreground", children: t("connectors.otherModuleBody") }),
|
|
2431
|
+
/* @__PURE__ */ jsx("ul", { className: "divide-y divide-border", children: connectors.map((c) => /* @__PURE__ */ jsx(
|
|
2432
|
+
ShelfRow,
|
|
2433
|
+
{
|
|
2434
|
+
integration: c,
|
|
2435
|
+
note: t("connectors.requiresModule", { module: moduleLabel(t, c.moduleId) })
|
|
2436
|
+
},
|
|
2437
|
+
c.id
|
|
2438
|
+
)) })
|
|
2439
|
+
] });
|
|
2440
|
+
}
|
|
2441
|
+
function ShelfRow({ integration, note }) {
|
|
2442
|
+
const { t } = useTranslation();
|
|
2443
|
+
const category = categoryLabel(integration.category);
|
|
2444
|
+
return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 py-3", children: [
|
|
2445
|
+
/* @__PURE__ */ jsx(ConnectorMark, { connector: integration, size: "sm" }),
|
|
2446
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
2447
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2448
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: integration.name }),
|
|
2449
|
+
category && /* @__PURE__ */ jsx("span", { className: "rounded-full border border-border px-2 py-0.5 text-[11px] text-muted-foreground", children: category }),
|
|
2450
|
+
integration.status === "soon" && !integration.moduleInstalled && /* @__PURE__ */ jsx("span", { className: "rounded-full border border-dashed border-border px-2 py-0.5 text-[11px] text-muted-foreground", children: t("connectors.soonChip") })
|
|
2451
|
+
] }),
|
|
2452
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: note })
|
|
2453
|
+
] })
|
|
2454
|
+
] });
|
|
2455
|
+
}
|
|
2456
|
+
function moduleLabel(t, moduleId) {
|
|
2457
|
+
const key = `modules.${moduleId}`;
|
|
2458
|
+
const label = t(key);
|
|
2459
|
+
return label === key ? moduleId : label;
|
|
2460
|
+
}
|
|
2461
|
+
function AddConnectorButton({ onClick }) {
|
|
2462
|
+
const { t } = useTranslation();
|
|
2463
|
+
return /* @__PURE__ */ jsxs(
|
|
2464
|
+
Button,
|
|
2465
|
+
{
|
|
2466
|
+
variant: "bare",
|
|
2467
|
+
size: "bare",
|
|
2468
|
+
onClick,
|
|
2469
|
+
className: "inline-flex shrink-0 items-center gap-1.5 rounded-lg bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90",
|
|
2470
|
+
children: [
|
|
2471
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
|
|
2472
|
+
t("connectors.add")
|
|
2473
|
+
]
|
|
2474
|
+
}
|
|
2475
|
+
);
|
|
2476
|
+
}
|
|
2477
|
+
function ConnectorsTab() {
|
|
2478
|
+
const { t } = useTranslation();
|
|
2479
|
+
const { connectors, error, reload, revokeGrant, tenantReady } = useConnectors();
|
|
2480
|
+
const [adding, setAdding] = React6.useState(false);
|
|
2481
|
+
const [selected, setSelected] = React6.useState(null);
|
|
2482
|
+
const [filter, setFilter] = React6.useState("all");
|
|
2483
|
+
const [failure, setFailure] = React6.useState(null);
|
|
2484
|
+
const [revoking, setRevoking] = React6.useState(null);
|
|
2485
|
+
const assistantAvailable = useConnectorsPanelAvailable();
|
|
2486
|
+
const openJourney = React6.useCallback(
|
|
2487
|
+
(connector) => {
|
|
2488
|
+
if (assistantAvailable) openConnectorsPanel(connector);
|
|
2489
|
+
else setSelected(connector);
|
|
2490
|
+
},
|
|
2491
|
+
[assistantAvailable]
|
|
2492
|
+
);
|
|
2493
|
+
const visible = React6.useMemo(() => filterConnectors(connectors ?? [], filter), [connectors, filter]);
|
|
2494
|
+
const groups = React6.useMemo(() => groupConnectors(visible), [visible]);
|
|
2495
|
+
React6.useEffect(() => {
|
|
2496
|
+
if (!selected || !connectors) return;
|
|
2497
|
+
const fresh = connectors.find((c) => c.id === selected.id);
|
|
2498
|
+
if (fresh && fresh !== selected) setSelected(fresh);
|
|
2499
|
+
if (!fresh) setSelected(null);
|
|
2500
|
+
}, [connectors]);
|
|
2501
|
+
if (!tenantReady) {
|
|
2502
|
+
return /* @__PURE__ */ jsx(Empty, { title: t("connectors.noOrg"), body: t("connectors.noOrgBody") });
|
|
2503
|
+
}
|
|
2504
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
2505
|
+
/* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(
|
|
2506
|
+
PageActions,
|
|
2507
|
+
{
|
|
2508
|
+
actions: [{ id: "add-connector", label: t("connectors.add"), icon: Plus, intent: "primary", onSelect: () => setAdding(true) }]
|
|
2509
|
+
}
|
|
2510
|
+
) }),
|
|
2511
|
+
/* @__PURE__ */ jsx(
|
|
2512
|
+
AddConnectorPanel,
|
|
2513
|
+
{
|
|
2514
|
+
open: adding,
|
|
2515
|
+
onOpenChange: setAdding,
|
|
2516
|
+
connectors,
|
|
2517
|
+
onSelect: (connector) => {
|
|
2518
|
+
setAdding(false);
|
|
2519
|
+
openJourney(connector);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
),
|
|
2523
|
+
(error || failure) && /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-destructive/30 bg-destructive/10 p-3 text-sm text-destructive", children: failure ?? error }),
|
|
2524
|
+
(connectors?.length ?? 0) > 4 && /* @__PURE__ */ jsx(Filters, { value: filter, onChange: setFilter }),
|
|
2525
|
+
connectors === null && /* @__PURE__ */ jsx(ConnectionsSkeleton, {}),
|
|
2526
|
+
connectors !== null && visible.length === 0 && /* @__PURE__ */ jsx(Empty, { title: t("connectors.emptyTitle"), body: t("connectors.emptyBody") }),
|
|
2527
|
+
visible.length > 0 && /* @__PURE__ */ jsx("ul", { className: "fayz-glass-surface divide-y divide-border overflow-hidden rounded-card border bg-card text-card-foreground shadow-sm", children: groups.map((group) => /* @__PURE__ */ jsxs(React6.Fragment, { children: [
|
|
2528
|
+
groups.length > 1 && /* @__PURE__ */ jsx(
|
|
2529
|
+
"li",
|
|
2530
|
+
{
|
|
2531
|
+
className: "bg-muted/30 px-4 py-1.5 text-[0.7rem] font-medium uppercase tracking-wider text-muted-foreground",
|
|
2532
|
+
role: "presentation",
|
|
2533
|
+
children: group.label
|
|
2534
|
+
}
|
|
2535
|
+
),
|
|
2536
|
+
group.connectors.map((c) => /* @__PURE__ */ jsx(ConnectorRow, { connector: c, onOpen: openJourney }, c.id))
|
|
2537
|
+
] }, group.key)) }),
|
|
2538
|
+
/* @__PURE__ */ jsx(
|
|
2539
|
+
ConnectorDetail,
|
|
2540
|
+
{
|
|
2541
|
+
connector: selected,
|
|
2542
|
+
open: !!selected,
|
|
2543
|
+
onOpenChange: (open) => !open && setSelected(null),
|
|
2544
|
+
onChanged: reload,
|
|
2545
|
+
onRevoke: (connector) => setRevoking(connector)
|
|
2546
|
+
}
|
|
2547
|
+
),
|
|
2548
|
+
/* @__PURE__ */ jsx(
|
|
2549
|
+
ConfirmDialog,
|
|
2550
|
+
{
|
|
2551
|
+
open: !!revoking,
|
|
2552
|
+
title: t("connectors.revoke"),
|
|
2553
|
+
description: revoking ? t("connectors.revokeConfirm", { name: revoking.name }) : void 0,
|
|
2554
|
+
variant: "destructive",
|
|
2555
|
+
confirmLabel: t("connectors.revoke"),
|
|
2556
|
+
onConfirm: async () => {
|
|
2557
|
+
const connector = revoking;
|
|
2558
|
+
setRevoking(null);
|
|
2559
|
+
if (!connector) return;
|
|
2560
|
+
try {
|
|
2561
|
+
await revokeGrant(connector);
|
|
2562
|
+
setSelected(null);
|
|
2563
|
+
} catch (e) {
|
|
2564
|
+
setFailure(e.message);
|
|
2565
|
+
}
|
|
2566
|
+
},
|
|
2567
|
+
onCancel: () => setRevoking(null)
|
|
2568
|
+
}
|
|
2569
|
+
)
|
|
2570
|
+
] });
|
|
2571
|
+
}
|
|
2572
|
+
var FILTERS = ["all", "connected", "agentic", "sync"];
|
|
2573
|
+
function Filters({ value, onChange }) {
|
|
2574
|
+
const { t } = useTranslation();
|
|
2575
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: FILTERS.map((f) => /* @__PURE__ */ jsx(
|
|
2576
|
+
Button,
|
|
2577
|
+
{
|
|
2578
|
+
variant: "bare",
|
|
2579
|
+
size: "bare",
|
|
2580
|
+
onClick: () => onChange(f),
|
|
2581
|
+
className: "rounded-full px-3 py-1 text-xs font-medium transition-colors " + (f === value ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/70"),
|
|
2582
|
+
children: t(`connectors.filter.${f}`)
|
|
2583
|
+
},
|
|
2584
|
+
f
|
|
2585
|
+
)) });
|
|
2586
|
+
}
|
|
2587
|
+
function ConnectionsSkeleton() {
|
|
2588
|
+
return /* @__PURE__ */ jsx("ul", { className: "fayz-glass-surface divide-y divide-border overflow-hidden rounded-card border bg-card text-card-foreground shadow-sm", "aria-hidden": true, children: [0, 1].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-4 p-4", children: [
|
|
2589
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-10 shrink-0 rounded-lg" }),
|
|
2590
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-2 py-0.5", children: [
|
|
2591
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-40" }),
|
|
2592
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-64" })
|
|
2593
|
+
] }),
|
|
2594
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-28 shrink-0 rounded-lg" })
|
|
2595
|
+
] }, i)) });
|
|
2596
|
+
}
|
|
2597
|
+
function Empty({ title, body }) {
|
|
2598
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-dashed border-border p-8 text-center", children: [
|
|
2599
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: title }),
|
|
2600
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: body })
|
|
2601
|
+
] });
|
|
2602
|
+
}
|
|
2603
|
+
var CONNECTOR_PARAM = "fayzConnector";
|
|
2604
|
+
var STATUS_PARAM = "fayzConnectorStatus";
|
|
2605
|
+
var LEGACY_PARAMS = {
|
|
2606
|
+
googleCalendar: "google-calendar"
|
|
2607
|
+
};
|
|
2608
|
+
var OAUTH_FAILURE_REASONS = [
|
|
2609
|
+
"config_missing",
|
|
2610
|
+
"invalid_state",
|
|
2611
|
+
"expired_state",
|
|
2612
|
+
"not_a_member",
|
|
2613
|
+
"provider_denied",
|
|
2614
|
+
"missing_code",
|
|
2615
|
+
"token_exchange_failed",
|
|
2616
|
+
"token_incomplete",
|
|
2617
|
+
"store_failed",
|
|
2618
|
+
"unexpected"
|
|
2619
|
+
];
|
|
2620
|
+
var REASON_SET = new Set(OAUTH_FAILURE_REASONS);
|
|
2621
|
+
function readConnectorOAuthReturn(search) {
|
|
2622
|
+
let params;
|
|
2623
|
+
try {
|
|
2624
|
+
params = new URLSearchParams(search);
|
|
2625
|
+
} catch {
|
|
2626
|
+
return null;
|
|
2627
|
+
}
|
|
2628
|
+
const generic = params.get(CONNECTOR_PARAM);
|
|
2629
|
+
if (generic) {
|
|
2630
|
+
const status = params.get(STATUS_PARAM);
|
|
2631
|
+
if (status !== "connected" && status !== "error") return null;
|
|
2632
|
+
const raw = params.get(`${CONNECTOR_PARAM}Message`) ?? "";
|
|
2633
|
+
return {
|
|
2634
|
+
connectorId: generic,
|
|
2635
|
+
status,
|
|
2636
|
+
reason: REASON_SET.has(raw) ? raw : void 0
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
for (const [param, connectorId] of Object.entries(LEGACY_PARAMS)) {
|
|
2640
|
+
const status = params.get(param);
|
|
2641
|
+
if (status !== "connected" && status !== "error") continue;
|
|
2642
|
+
return { connectorId, status, message: params.get(`${param}Message`) ?? void 0 };
|
|
2643
|
+
}
|
|
2644
|
+
return null;
|
|
2645
|
+
}
|
|
2646
|
+
function oauthReturnParams() {
|
|
2647
|
+
const keys = [CONNECTOR_PARAM, STATUS_PARAM, `${CONNECTOR_PARAM}Message`];
|
|
2648
|
+
for (const param of Object.keys(LEGACY_PARAMS)) keys.push(param, `${param}Message`);
|
|
2649
|
+
return keys;
|
|
2650
|
+
}
|
|
2651
|
+
function urlWithoutOAuthReturn(href) {
|
|
2652
|
+
const url = new URL(href);
|
|
2653
|
+
for (const key of oauthReturnParams()) url.searchParams.delete(key);
|
|
2654
|
+
return url.toString();
|
|
2655
|
+
}
|
|
2656
|
+
function oauthFailureMessage(t, result) {
|
|
2657
|
+
if (result.reason) {
|
|
2658
|
+
const key = `connectors.oauth.error.${result.reason}`;
|
|
2659
|
+
const translated = t(key);
|
|
2660
|
+
if (translated && translated !== key) return translated;
|
|
2661
|
+
}
|
|
2662
|
+
return result.message || t("connectors.oauth.failed");
|
|
2663
|
+
}
|
|
2664
|
+
function useConnectorOAuthReturn() {
|
|
2665
|
+
const { t } = useTranslation();
|
|
2666
|
+
const runtime = usePluginRuntimeOptional();
|
|
2667
|
+
React6.useEffect(() => {
|
|
2668
|
+
if (typeof window === "undefined") return;
|
|
2669
|
+
const result = readConnectorOAuthReturn(window.location.search);
|
|
2670
|
+
if (!result) return;
|
|
2671
|
+
window.history.replaceState(null, "", urlWithoutOAuthReturn(window.location.href));
|
|
2672
|
+
if (result.status === "connected") {
|
|
2673
|
+
toast.success(t("connectors.oauth.returned"));
|
|
2674
|
+
void completeConnection(runtime, result.connectorId);
|
|
2675
|
+
} else {
|
|
2676
|
+
toast.error(oauthFailureMessage(t, result));
|
|
2677
|
+
}
|
|
2678
|
+
openConnectorsPanel({ id: result.connectorId });
|
|
2679
|
+
}, []);
|
|
2680
|
+
}
|
|
2681
|
+
async function completeConnection(runtime, connectorId) {
|
|
2682
|
+
const definitions = [...runtime?.connectorsByHost?.values() ?? []].flat();
|
|
2683
|
+
const definition = definitions.find((d) => d.id === connectorId);
|
|
2684
|
+
if (!definition?.saveConnection) return;
|
|
2685
|
+
try {
|
|
2686
|
+
await definition.saveConnection({});
|
|
2687
|
+
} finally {
|
|
2688
|
+
refreshConnectors();
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
var ICON = {
|
|
2692
|
+
delivered: { Icon: CheckCircle2, className: "text-success", label: "Enviado" },
|
|
2693
|
+
pending: { Icon: Clock, className: "text-muted-foreground", label: "Na fila" },
|
|
2694
|
+
failed: { Icon: XCircle, className: "text-destructive", label: "N\xE3o foi" }
|
|
2695
|
+
};
|
|
2696
|
+
function ConnectorDeliveries({
|
|
2697
|
+
load,
|
|
2698
|
+
providerName,
|
|
2699
|
+
limit = 10
|
|
2700
|
+
}) {
|
|
2701
|
+
const { t } = useTranslation();
|
|
2702
|
+
const [rows, setRows] = React6.useState(null);
|
|
2703
|
+
React6.useEffect(() => {
|
|
2704
|
+
let cancelled = false;
|
|
2705
|
+
void load({ limit }).then((r) => {
|
|
2706
|
+
if (!cancelled) setRows(r);
|
|
2707
|
+
}).catch(() => {
|
|
2708
|
+
if (!cancelled) setRows([]);
|
|
2709
|
+
});
|
|
2710
|
+
return () => {
|
|
2711
|
+
cancelled = true;
|
|
2712
|
+
};
|
|
2713
|
+
}, [load, limit]);
|
|
2714
|
+
if (rows === null) {
|
|
2715
|
+
return /* @__PURE__ */ jsx("p", { className: "flex justify-center py-4", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) });
|
|
2716
|
+
}
|
|
2717
|
+
if (rows.length === 0) {
|
|
2718
|
+
return /* @__PURE__ */ jsxs("p", { className: "rounded-lg border border-dashed border-border p-3 text-xs text-muted-foreground", children: [
|
|
2719
|
+
"Nada foi enviado ainda para o ",
|
|
2720
|
+
providerName,
|
|
2721
|
+
"."
|
|
2722
|
+
] });
|
|
2723
|
+
}
|
|
2724
|
+
return /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border overflow-hidden rounded-lg border border-border", children: rows.map((row, index) => {
|
|
2725
|
+
const { Icon, className, label } = ICON[row.status] ?? ICON.pending;
|
|
2726
|
+
return /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2 px-3 py-2", children: [
|
|
2727
|
+
/* @__PURE__ */ jsx(Icon, { className: `mt-0.5 h-3.5 w-3.5 shrink-0 ${className}`, "aria-hidden": true }),
|
|
2728
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
|
|
2729
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
2730
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs font-medium", children: [
|
|
2731
|
+
row.subject,
|
|
2732
|
+
row.externalId && /* @__PURE__ */ jsxs("span", { className: "ml-1.5 font-normal text-muted-foreground", children: [
|
|
2733
|
+
"\u2192 ",
|
|
2734
|
+
providerName,
|
|
2735
|
+
" #",
|
|
2736
|
+
row.externalId
|
|
2737
|
+
] })
|
|
2738
|
+
] }),
|
|
2739
|
+
row.detail && /* @__PURE__ */ jsx("p", { className: "text-[11px] leading-relaxed text-muted-foreground", children: row.detail })
|
|
2740
|
+
] }),
|
|
2741
|
+
row.at && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] tabular-nums text-muted-foreground", children: formatRelative(row.at, t) })
|
|
2742
|
+
] }, row.subjectId ?? `${row.subject}-${index}`);
|
|
2743
|
+
}) });
|
|
2744
|
+
}
|
|
2745
|
+
async function loadConnectorIndex(input) {
|
|
2746
|
+
const [installed, catalog] = await Promise.all([
|
|
2747
|
+
loadConnectorStatuses(input.definitions, input.tenantId ?? getActiveTenantId()),
|
|
2748
|
+
loadCatalog()
|
|
2749
|
+
]);
|
|
2750
|
+
const connectors = buildConnectorList({ installed, catalog });
|
|
2751
|
+
const shelf = shelfIntegrations({
|
|
2752
|
+
present: connectors,
|
|
2753
|
+
installedModules: input.installedModules,
|
|
2754
|
+
catalog
|
|
2755
|
+
});
|
|
2756
|
+
return { connectors, shelf };
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
// src/components/connect/connector-search.ts
|
|
2760
|
+
var STATE_RANK2 = {
|
|
2761
|
+
needs_setup: 0,
|
|
2762
|
+
connected: 1,
|
|
2763
|
+
ready: 2,
|
|
2764
|
+
soon: 3,
|
|
2765
|
+
needs_module: 4
|
|
2766
|
+
};
|
|
2767
|
+
var NEED_CATEGORIES = {
|
|
2768
|
+
payments: [
|
|
2769
|
+
"pagamento",
|
|
2770
|
+
"pagamentos",
|
|
2771
|
+
"pagar",
|
|
2772
|
+
"pagto",
|
|
2773
|
+
"receber",
|
|
2774
|
+
"recebimento",
|
|
2775
|
+
"recebe",
|
|
2776
|
+
"cobranca",
|
|
2777
|
+
"cobrar",
|
|
2778
|
+
"checkout",
|
|
2779
|
+
"pix",
|
|
2780
|
+
"cartao",
|
|
2781
|
+
"credito",
|
|
2782
|
+
"debito",
|
|
2783
|
+
"boleto",
|
|
2784
|
+
"parcelado",
|
|
2785
|
+
"parcelamento",
|
|
2786
|
+
"gateway",
|
|
2787
|
+
"payment",
|
|
2788
|
+
"payments",
|
|
2789
|
+
"pay",
|
|
2790
|
+
"card",
|
|
2791
|
+
"billing"
|
|
2792
|
+
],
|
|
2793
|
+
logistics: [
|
|
2794
|
+
"frete",
|
|
2795
|
+
"fretes",
|
|
2796
|
+
"entrega",
|
|
2797
|
+
"entregas",
|
|
2798
|
+
"envio",
|
|
2799
|
+
"envios",
|
|
2800
|
+
"transportadora",
|
|
2801
|
+
"correios",
|
|
2802
|
+
"logistica",
|
|
2803
|
+
"rastreio",
|
|
2804
|
+
"shipping",
|
|
2805
|
+
"delivery",
|
|
2806
|
+
"logistics"
|
|
2807
|
+
],
|
|
2808
|
+
erp: ["nota", "notas", "fiscal", "nfe", "nfse", "nfce", "danfe", "erp", "faturamento", "invoice"],
|
|
2809
|
+
banking: ["banco", "bancaria", "bancario", "conta", "extrato", "conciliacao", "openbanking", "banking"],
|
|
2810
|
+
crm: ["crm", "lead", "leads", "funil", "pipeline", "prospect"],
|
|
2811
|
+
scheduling: ["agenda", "agendamento", "calendario", "calendar", "scheduling", "horario"],
|
|
2812
|
+
messaging: ["whatsapp", "mensagem", "mensagens", "sms", "messaging", "zap"],
|
|
2813
|
+
marketplace: ["marketplace", "marketplaces"],
|
|
2814
|
+
ecommerce: ["loja", "ecommerce", "carrinho", "vitrine", "storefront"],
|
|
2815
|
+
analytics: ["analytics", "analise", "metricas", "metrica", "dados"],
|
|
2816
|
+
local: ["mapa", "mapas", "local", "maps"],
|
|
2817
|
+
ai: ["inteligencia"],
|
|
2818
|
+
automation: ["automacao", "automacoes", "webhook", "automation", "zapier"],
|
|
2819
|
+
courses: ["curso", "cursos", "aluno", "alunos"],
|
|
2820
|
+
database: ["database"]
|
|
2821
|
+
};
|
|
2822
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
2823
|
+
"que",
|
|
2824
|
+
"com",
|
|
2825
|
+
"para",
|
|
2826
|
+
"pra",
|
|
2827
|
+
"por",
|
|
2828
|
+
"uma",
|
|
2829
|
+
"uns",
|
|
2830
|
+
"umas",
|
|
2831
|
+
"dos",
|
|
2832
|
+
"das",
|
|
2833
|
+
"nos",
|
|
2834
|
+
"nas",
|
|
2835
|
+
"quero",
|
|
2836
|
+
"queria",
|
|
2837
|
+
"gostaria",
|
|
2838
|
+
"preciso",
|
|
2839
|
+
"precisa",
|
|
2840
|
+
"tivesse",
|
|
2841
|
+
"tem",
|
|
2842
|
+
"ter",
|
|
2843
|
+
"tenho",
|
|
2844
|
+
"como",
|
|
2845
|
+
"onde",
|
|
2846
|
+
"qual",
|
|
2847
|
+
"quais",
|
|
2848
|
+
"sobre",
|
|
2849
|
+
"meu",
|
|
2850
|
+
"minha",
|
|
2851
|
+
"meus",
|
|
2852
|
+
"minhas",
|
|
2853
|
+
"aqui",
|
|
2854
|
+
"the",
|
|
2855
|
+
"and",
|
|
2856
|
+
"for",
|
|
2857
|
+
"with",
|
|
2858
|
+
"want",
|
|
2859
|
+
"need",
|
|
2860
|
+
"have",
|
|
2861
|
+
"can",
|
|
2862
|
+
"app",
|
|
2863
|
+
"usar",
|
|
2864
|
+
"fazer",
|
|
2865
|
+
"integrar",
|
|
2866
|
+
"integracao",
|
|
2867
|
+
"integracoes",
|
|
2868
|
+
"conectar",
|
|
2869
|
+
"conexao",
|
|
2870
|
+
"ligar",
|
|
2871
|
+
"instalar"
|
|
2872
|
+
]);
|
|
2873
|
+
function fold(value) {
|
|
2874
|
+
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim();
|
|
2875
|
+
}
|
|
2876
|
+
function parseConnectorQuery(query) {
|
|
2877
|
+
const raw = fold(query);
|
|
2878
|
+
const terms = raw.split(/[^a-z0-9]+/).filter((term) => term.length >= 3 && !STOPWORDS.has(term));
|
|
2879
|
+
const categories = /* @__PURE__ */ new Set();
|
|
2880
|
+
for (const [category, words] of Object.entries(NEED_CATEGORIES)) {
|
|
2881
|
+
if (words.some((word) => terms.includes(word))) categories.add(category);
|
|
2882
|
+
}
|
|
2883
|
+
if (raw.includes("nota fiscal")) categories.add("erp");
|
|
2884
|
+
return { raw, terms, categories };
|
|
2885
|
+
}
|
|
2886
|
+
function score(row, query) {
|
|
2887
|
+
if (!query.raw) return 0;
|
|
2888
|
+
const name = fold(row.name);
|
|
2889
|
+
const slug = fold(row.slug);
|
|
2890
|
+
const description = fold(row.description ?? "");
|
|
2891
|
+
const category = row.category ?? "";
|
|
2892
|
+
const categoryWord = fold(CATEGORY_LABELS[category] ?? category);
|
|
2893
|
+
let total = 0;
|
|
2894
|
+
if (name === query.raw || slug === query.raw) total += 120;
|
|
2895
|
+
else if (query.raw.includes(name) || name.includes(query.raw)) total += 80;
|
|
2896
|
+
else if (query.terms.some((term) => term === name || term === slug)) total += 70;
|
|
2897
|
+
else if (query.terms.some((term) => term.length >= 4 && (name.includes(term) || slug.includes(term)))) total += 50;
|
|
2898
|
+
if (category && query.categories.has(category)) total += 45;
|
|
2899
|
+
if (categoryWord && query.terms.includes(categoryWord)) total += 30;
|
|
2900
|
+
if (description && query.terms.some((term) => description.includes(term))) total += 25;
|
|
2901
|
+
return total;
|
|
2902
|
+
}
|
|
2903
|
+
function fromConnector(connector) {
|
|
2904
|
+
return {
|
|
2905
|
+
slug: connector.slug,
|
|
2906
|
+
id: connector.id,
|
|
2907
|
+
name: connector.name,
|
|
2908
|
+
description: connector.description,
|
|
2909
|
+
category: connector.category,
|
|
2910
|
+
icon: connector.icon,
|
|
2911
|
+
logoUrl: connector.logoUrl,
|
|
2912
|
+
state: connector.state,
|
|
2913
|
+
moduleId: connector.hostPluginId,
|
|
2914
|
+
planes: connector.planes,
|
|
2915
|
+
// The one place a path is built, and it is the screen's own helper.
|
|
2916
|
+
route: connectorRoute(connector),
|
|
2917
|
+
score: 0
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
function fromShelf(row) {
|
|
2921
|
+
return {
|
|
2922
|
+
slug: row.slug,
|
|
2923
|
+
name: row.name,
|
|
2924
|
+
description: row.description,
|
|
2925
|
+
category: row.category,
|
|
2926
|
+
icon: row.icon,
|
|
2927
|
+
logoUrl: row.logoUrl,
|
|
2928
|
+
// Same split the panel renders: `moduleInstalled` is what tells "em breve"
|
|
2929
|
+
// from "requer outro módulo", not `status`.
|
|
2930
|
+
state: row.moduleInstalled ? "soon" : "needs_module",
|
|
2931
|
+
moduleId: row.moduleId,
|
|
2932
|
+
planes: row.planes,
|
|
2933
|
+
score: 0
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
function searchConnectors(input) {
|
|
2937
|
+
const query = parseConnectorQuery(input.query);
|
|
2938
|
+
if (!query.raw) return [];
|
|
2939
|
+
const rows = [
|
|
2940
|
+
...input.connectors.map(fromConnector),
|
|
2941
|
+
...input.shelf.map(fromShelf)
|
|
2942
|
+
];
|
|
2943
|
+
return rows.map((row) => ({ ...row, score: score(row, query) })).filter((row) => row.score > 0).sort(
|
|
2944
|
+
(a, b) => b.score - a.score || STATE_RANK2[a.state] - STATE_RANK2[b.state] || a.name.localeCompare(b.name)
|
|
2945
|
+
).slice(0, input.limit ?? 4);
|
|
2946
|
+
}
|
|
2947
|
+
function isConnectorState(state) {
|
|
2948
|
+
switch (state) {
|
|
2949
|
+
case "connected":
|
|
2950
|
+
case "needs_setup":
|
|
2951
|
+
case "ready":
|
|
2952
|
+
return true;
|
|
2953
|
+
case "soon":
|
|
2954
|
+
case "needs_module":
|
|
2955
|
+
return false;
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
function hasDestination(suggestion) {
|
|
2959
|
+
return isConnectorState(suggestion.state);
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
export { AddConnectorButton, AddConnectorPanel, CATEGORY_LABELS, CATEGORY_ORDER, ConnectConsentPage, ConnectorDeliveries, ConnectorDetail, ConnectorHeading, ConnectorJourney, ConnectorRow, ConnectorRuns, ConnectorSyncActions, ConnectorsPanel, ConnectorsTab, CredentialSinkMissingError, DirectionBadge, OAUTH_FAILURE_REASONS, PlaneBadge, StateChip, buildConnectorList, categoryLabel, connectorRoute, connectorSubscriptions, describeFayzAgentGap, filterConnectors, getFayzAgentClient, hasDestination, hasOperatingSettings, hasRunHistory, inCategories, isConnectorState, isSecretField, loadConnectorIndex, needsCredentialSink, oauthFailureMessage, openConnectorsPanel, probeConnection, readConnectorOAuthReturn, resolveFayzAgentConnection, resolveFayzApiBaseUrl, searchConnectors, shelveByCategory, sortConnectors, splitConnectorValues, submitConnection, urlWithoutOAuthReturn, useConnectorCatalog, useConnectorOAuthReturn, useConnectorRuns, useConnectors, useConnectorsPanelAvailable, useConnectorsSurface };
|
|
2963
|
+
//# sourceMappingURL=chunk-U6ZCO2PS.js.map
|
|
2964
|
+
//# sourceMappingURL=chunk-U6ZCO2PS.js.map
|