@tangle-network/sandbox-ui 0.16.3 → 0.17.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/auth.d.ts +29 -0
- package/dist/auth.js +3 -1
- package/dist/chunk-IOB2PW5Z.js +55 -0
- package/dist/globals.css +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +43 -41
- package/dist/integrations.d.ts +114 -0
- package/dist/integrations.js +245 -0
- package/dist/openui.d.ts +0 -1
- package/dist/styles.css +10 -0
- package/package.json +5 -1
- package/dist/chunk-NJNME4J4.js +0 -14
package/dist/auth.d.ts
CHANGED
|
@@ -1 +1,30 @@
|
|
|
1
1
|
export { AuthHeader, AuthHeaderProps, GitHubLoginButton, GitHubLoginButtonProps, LoginLayout, LoginLayoutProps, SessionUser, UserMenu, UserMenuProps } from '@tangle-network/ui/auth';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
import { ButtonProps } from '@tangle-network/ui/primitives';
|
|
4
|
+
|
|
5
|
+
interface TangleLoginButtonProps extends Omit<ButtonProps, "onClick"> {
|
|
6
|
+
/**
|
|
7
|
+
* Consumer-app endpoint that starts the cross-site SSO flow. The
|
|
8
|
+
* consumer's server is expected to redirect this request to the
|
|
9
|
+
* platform's `/cross-site/authorize` URL (built via
|
|
10
|
+
* `PlatformAuthClient.authorizeUrl` from
|
|
11
|
+
* `@tangle-network/agent-runtime/platform`). Defaults to
|
|
12
|
+
* `/auth/tangle`.
|
|
13
|
+
*/
|
|
14
|
+
authUrl?: string;
|
|
15
|
+
/** Product variant for styling. */
|
|
16
|
+
variant?: "sandbox" | "default" | "outline";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* "Login with Tangle" button — kicks off the cross-site SSO bridge.
|
|
20
|
+
* Server-side, the consumer app should:
|
|
21
|
+
* 1. Generate a `state` for CSRF.
|
|
22
|
+
* 2. Persist `state` in a session cookie or signed JWT.
|
|
23
|
+
* 3. 302-redirect to `PlatformAuthClient.authorizeUrl({ state, redirectUri })`.
|
|
24
|
+
*
|
|
25
|
+
* This component itself only triggers the redirect to `authUrl`; the
|
|
26
|
+
* server owns the platform URL construction.
|
|
27
|
+
*/
|
|
28
|
+
declare function TangleLoginButton({ authUrl, variant, className, children, ...props }: TangleLoginButtonProps): react_jsx_runtime.JSX.Element;
|
|
29
|
+
|
|
30
|
+
export { TangleLoginButton, type TangleLoginButtonProps };
|
package/dist/auth.js
CHANGED
|
@@ -2,11 +2,13 @@ import {
|
|
|
2
2
|
AuthHeader,
|
|
3
3
|
GitHubLoginButton,
|
|
4
4
|
LoginLayout,
|
|
5
|
+
TangleLoginButton,
|
|
5
6
|
UserMenu
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IOB2PW5Z.js";
|
|
7
8
|
export {
|
|
8
9
|
AuthHeader,
|
|
9
10
|
GitHubLoginButton,
|
|
10
11
|
LoginLayout,
|
|
12
|
+
TangleLoginButton,
|
|
11
13
|
UserMenu
|
|
12
14
|
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/auth/index.ts
|
|
2
|
+
import {
|
|
3
|
+
AuthHeader,
|
|
4
|
+
GitHubLoginButton,
|
|
5
|
+
LoginLayout,
|
|
6
|
+
UserMenu
|
|
7
|
+
} from "@tangle-network/ui/auth";
|
|
8
|
+
|
|
9
|
+
// src/auth/tangle-login-button.tsx
|
|
10
|
+
import { Button } from "@tangle-network/ui/primitives";
|
|
11
|
+
import { cn } from "@tangle-network/ui/utils";
|
|
12
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
function TangleMark({ className }) {
|
|
14
|
+
return /* @__PURE__ */ jsx(
|
|
15
|
+
"svg",
|
|
16
|
+
{
|
|
17
|
+
className,
|
|
18
|
+
viewBox: "0 0 24 24",
|
|
19
|
+
fill: "currentColor",
|
|
20
|
+
"aria-hidden": "true",
|
|
21
|
+
children: /* @__PURE__ */ jsx("path", { d: "M4 5h16v3.2h-6.4V19h-3.2V8.2H4V5z" })
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
function TangleLoginButton({
|
|
26
|
+
authUrl = "/auth/tangle",
|
|
27
|
+
variant = "default",
|
|
28
|
+
className,
|
|
29
|
+
children,
|
|
30
|
+
...props
|
|
31
|
+
}) {
|
|
32
|
+
return /* @__PURE__ */ jsxs(
|
|
33
|
+
Button,
|
|
34
|
+
{
|
|
35
|
+
variant,
|
|
36
|
+
className: cn("gap-2", className),
|
|
37
|
+
onClick: () => {
|
|
38
|
+
window.location.href = authUrl;
|
|
39
|
+
},
|
|
40
|
+
...props,
|
|
41
|
+
children: [
|
|
42
|
+
/* @__PURE__ */ jsx(TangleMark, { className: "h-5 w-5" }),
|
|
43
|
+
children ?? "Sign in with Tangle"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
TangleLoginButton,
|
|
51
|
+
AuthHeader,
|
|
52
|
+
GitHubLoginButton,
|
|
53
|
+
LoginLayout,
|
|
54
|
+
UserMenu
|
|
55
|
+
};
|
package/dist/globals.css
CHANGED
|
@@ -1020,6 +1020,13 @@
|
|
|
1020
1020
|
.gap-px {
|
|
1021
1021
|
gap: 1px;
|
|
1022
1022
|
}
|
|
1023
|
+
.space-y-0 {
|
|
1024
|
+
:where(& > :not(:last-child)) {
|
|
1025
|
+
--tw-space-y-reverse: 0;
|
|
1026
|
+
margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));
|
|
1027
|
+
margin-block-end: calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)));
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1023
1030
|
.space-y-0\.5 {
|
|
1024
1031
|
:where(& > :not(:last-child)) {
|
|
1025
1032
|
--tw-space-y-reverse: 0;
|
|
@@ -1576,6 +1583,9 @@
|
|
|
1576
1583
|
.py-5 {
|
|
1577
1584
|
padding-block: calc(var(--spacing) * 5);
|
|
1578
1585
|
}
|
|
1586
|
+
.py-6 {
|
|
1587
|
+
padding-block: calc(var(--spacing) * 6);
|
|
1588
|
+
}
|
|
1579
1589
|
.py-8 {
|
|
1580
1590
|
padding-block: calc(var(--spacing) * 8);
|
|
1581
1591
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { FileArtifactPane, FileArtifactPaneProps, FileNode, FilePreview, FilePre
|
|
|
10
10
|
export { Backend, BackendConfig, BackendConfigProps, BackendSelector, BackendSelectorProps, BackendStatusData, ClusterStatusBar, ClusterStatusBarProps, ClusterStatusItem, CreditBalance, CreditBalanceProps, DashboardLayout, DashboardLayoutProps, DashboardProfile, DashboardSnapshotInfo, DashboardUser, ExposedPort, GitCommitData, GitPanel, GitPanelProps, GitStatusData, HARNESS_OPTIONS, HarnessPicker, HarnessPickerProps, HarnessType, InfoPanel, InfoPanelProps, Invoice, InvoiceTable, InvoiceTableProps, McpServer, NavItem, NetworkConfig, NetworkConfigData, NetworkConfigProps, NewSandboxCard, NewSandboxCardProps, PlanCardData, PlanCards, PlanCardsProps, PlanFeature, PortsList, PortsListProps, ProcessInfo, ProcessList, ProcessListProps, ProductVariant, ProfileAvatar, ProfileAvatarProps, ProfileComparison, ProfileComparisonProps, ProfileSelector, ProfileSelectorProps, PromoBanner, PromoBannerProps, RailButton, RailButtonProps, RailModeButton, RailModeButtonProps, RailSeparator, RailSeparatorProps, ResourceMeter, ResourceMeterProps, SIDEBAR_MOBILE_WIDTH, SIDEBAR_PANEL_WIDTH, SIDEBAR_RAIL_WIDTH, SIDEBAR_TOTAL_WIDTH, SandboxCard, SandboxCardData, SandboxCardProps, SandboxStatus, SandboxTable, SandboxTableProps, Sidebar, SidebarContent, SidebarContentProps, SidebarPanel, SidebarPanelContent, SidebarPanelContentProps, SidebarPanelHeader, SidebarPanelHeaderProps, SidebarPanelProps, SidebarProps, SidebarProvider, SidebarProviderProps, SidebarRail, SidebarRailFooter, SidebarRailFooterProps, SidebarRailHeader, SidebarRailHeaderProps, SidebarRailNav, SidebarRailNavProps, SidebarRailProps, SidebarUser, SnapshotList, SnapshotListProps, SystemLogsViewer, SystemLogsViewerProps, TeamRole, UsageSummary, UsageSummaryData, UsageSummaryProps, Variant, VariantList, VariantListProps, VariantOutcome, VariantStatus, canAdminSandbox, useSidebar } from './dashboard.js';
|
|
11
11
|
export { B as BillingBalance, a as BillingDashboard, b as BillingDashboardProps, c as BillingSubscription, d as BillingUsage, M as ModelInfo, e as ModelPicker, f as ModelPickerProps, g as ModelPickerVariant, P as PricingPage, h as PricingPageProps, i as PricingTier, T as TemplateCard, j as TemplateCardData, k as TemplateCardProps, U as UsageChart, l as UsageChartProps, m as UsageDataPoint, n as canonicalModelId, o as formatContext, p as formatPrice, q as formatPricing } from './template-card-Dufxl4hV.js';
|
|
12
12
|
export { AuthHeader, AuthHeaderProps, GitHubLoginButton, GitHubLoginButtonProps, LoginLayout, LoginLayoutProps, SessionUser, UserMenu, UserMenuProps } from '@tangle-network/ui/auth';
|
|
13
|
+
export { TangleLoginButton, TangleLoginButtonProps } from './auth.js';
|
|
13
14
|
export { AgentStreamEvent, AppendUserMessageOptions, ApplySdkEventOptions, AuthUser, AutomationStreamEvent, BeginAssistantMessageOptions, BotStreamEvent, CompleteAssistantMessageOptions, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, TaskStreamEvent, TerminalStreamEvent, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, createAuthFetcher, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useLiveTime, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useToolCallStream } from '@tangle-network/ui/hooks';
|
|
14
15
|
export { SandboxMetrics, SidecarMetricsPayload, UsePtySessionOptions, UsePtySessionReturn, UseSandboxMetricsOptions, UseSandboxMetricsResult, useCreateSession, useDeleteSession, usePtySession, useRenameSession, useSandboxMetrics, useSessions } from './hooks.js';
|
|
15
16
|
export { SessionInfo, SidecarAuth, UseSessionStreamOptions, UseSessionStreamResult, UseSidecarAuthOptions, useSessionStream, useSidecarAuth } from './sdk-hooks.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
activeSessionsAtom,
|
|
3
|
+
addMessage,
|
|
4
|
+
addParts,
|
|
5
|
+
bumpActiveSessionActivity,
|
|
6
|
+
clearChat,
|
|
7
|
+
connectSession,
|
|
8
|
+
disconnectSession,
|
|
9
|
+
getActiveSession,
|
|
10
|
+
getAllActiveSessions,
|
|
11
|
+
getAllProjectActivity,
|
|
12
|
+
getSessionsByActivity,
|
|
13
|
+
getSessionsForNavbar,
|
|
14
|
+
getSessionsForProject,
|
|
15
|
+
getTotalRunningSessionCount,
|
|
16
|
+
hasBackgroundRunningSessions,
|
|
17
|
+
isStreamingAtom,
|
|
18
|
+
messagesAtom,
|
|
19
|
+
partMapAtom,
|
|
20
|
+
registerActiveSession,
|
|
21
|
+
resetActiveSessions,
|
|
22
|
+
sessionAtom,
|
|
23
|
+
setActiveSessionAttention,
|
|
24
|
+
setActiveSessionConnection,
|
|
25
|
+
setActiveSessionError,
|
|
26
|
+
setActiveSessionRunning,
|
|
27
|
+
setForegroundActiveSession,
|
|
28
|
+
unregisterActiveSession,
|
|
29
|
+
updateActiveSessionMeta,
|
|
30
|
+
updatePart,
|
|
31
|
+
useActiveSession,
|
|
32
|
+
useActiveSessions,
|
|
33
|
+
useActiveSessionsState,
|
|
34
|
+
useHasBackgroundRunningSessions,
|
|
35
|
+
useNavbarSessions,
|
|
36
|
+
useProjectActivity,
|
|
37
|
+
useProjectSessions,
|
|
38
|
+
useSessionsByActivity,
|
|
39
|
+
useTotalRunningSessions
|
|
40
|
+
} from "./chunk-WID73FPH.js";
|
|
1
41
|
import "./chunk-2BUPSB7O.js";
|
|
2
42
|
import {
|
|
3
43
|
TOOL_CATEGORY_ICONS,
|
|
@@ -16,8 +56,9 @@ import {
|
|
|
16
56
|
AuthHeader,
|
|
17
57
|
GitHubLoginButton,
|
|
18
58
|
LoginLayout,
|
|
59
|
+
TangleLoginButton,
|
|
19
60
|
UserMenu
|
|
20
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-IOB2PW5Z.js";
|
|
21
62
|
import {
|
|
22
63
|
RealtimeSessionRegistry,
|
|
23
64
|
createAuthFetcher,
|
|
@@ -45,46 +86,6 @@ import {
|
|
|
45
86
|
useSessionStream,
|
|
46
87
|
useSidecarAuth
|
|
47
88
|
} from "./chunk-CMY7W45U.js";
|
|
48
|
-
import {
|
|
49
|
-
activeSessionsAtom,
|
|
50
|
-
addMessage,
|
|
51
|
-
addParts,
|
|
52
|
-
bumpActiveSessionActivity,
|
|
53
|
-
clearChat,
|
|
54
|
-
connectSession,
|
|
55
|
-
disconnectSession,
|
|
56
|
-
getActiveSession,
|
|
57
|
-
getAllActiveSessions,
|
|
58
|
-
getAllProjectActivity,
|
|
59
|
-
getSessionsByActivity,
|
|
60
|
-
getSessionsForNavbar,
|
|
61
|
-
getSessionsForProject,
|
|
62
|
-
getTotalRunningSessionCount,
|
|
63
|
-
hasBackgroundRunningSessions,
|
|
64
|
-
isStreamingAtom,
|
|
65
|
-
messagesAtom,
|
|
66
|
-
partMapAtom,
|
|
67
|
-
registerActiveSession,
|
|
68
|
-
resetActiveSessions,
|
|
69
|
-
sessionAtom,
|
|
70
|
-
setActiveSessionAttention,
|
|
71
|
-
setActiveSessionConnection,
|
|
72
|
-
setActiveSessionError,
|
|
73
|
-
setActiveSessionRunning,
|
|
74
|
-
setForegroundActiveSession,
|
|
75
|
-
unregisterActiveSession,
|
|
76
|
-
updateActiveSessionMeta,
|
|
77
|
-
updatePart,
|
|
78
|
-
useActiveSession,
|
|
79
|
-
useActiveSessions,
|
|
80
|
-
useActiveSessionsState,
|
|
81
|
-
useHasBackgroundRunningSessions,
|
|
82
|
-
useNavbarSessions,
|
|
83
|
-
useProjectActivity,
|
|
84
|
-
useProjectSessions,
|
|
85
|
-
useSessionsByActivity,
|
|
86
|
-
useTotalRunningSessions
|
|
87
|
-
} from "./chunk-WID73FPH.js";
|
|
88
89
|
import {
|
|
89
90
|
AgentTimeline,
|
|
90
91
|
ChatContainer,
|
|
@@ -453,6 +454,7 @@ export {
|
|
|
453
454
|
TabsList,
|
|
454
455
|
TabsTrigger,
|
|
455
456
|
TangleKnot,
|
|
457
|
+
TangleLoginButton,
|
|
456
458
|
TaskBoard,
|
|
457
459
|
TemplateCard,
|
|
458
460
|
TerminalDisplay,
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shapes for the integrations primitives. Mirrors the platform's
|
|
5
|
+
* `/v1/integrations/*` response shape so consumers can pipe payloads
|
|
6
|
+
* straight through. Defined here (rather than imported from
|
|
7
|
+
* `@tangle-network/agent-runtime/platform`) so the UI package stays
|
|
8
|
+
* leaf-level — no dependency on the server-side client.
|
|
9
|
+
*/
|
|
10
|
+
interface IntegrationConnection {
|
|
11
|
+
id: string;
|
|
12
|
+
providerId: string;
|
|
13
|
+
connectorId: string;
|
|
14
|
+
status: "connected" | "pending" | "revoked" | "expired" | (string & {});
|
|
15
|
+
grantedScopes?: string[];
|
|
16
|
+
account?: {
|
|
17
|
+
identity?: string;
|
|
18
|
+
displayName?: string;
|
|
19
|
+
} & Record<string, unknown>;
|
|
20
|
+
expiresAt?: string | null;
|
|
21
|
+
createdAt?: string;
|
|
22
|
+
updatedAt?: string;
|
|
23
|
+
}
|
|
24
|
+
interface IntegrationConnector {
|
|
25
|
+
connectorId: string;
|
|
26
|
+
displayName?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
scopes?: string[];
|
|
29
|
+
}
|
|
30
|
+
interface IntegrationProvider {
|
|
31
|
+
providerId: string;
|
|
32
|
+
displayName?: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
iconUrl?: string;
|
|
35
|
+
connectors?: IntegrationConnector[];
|
|
36
|
+
}
|
|
37
|
+
interface IntegrationHealth {
|
|
38
|
+
connectionId: string;
|
|
39
|
+
status: "ok" | "degraded" | "failing" | "unknown" | (string & {});
|
|
40
|
+
checkedAt?: string;
|
|
41
|
+
message?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface IntegrationsPanelProps {
|
|
45
|
+
catalog: IntegrationProvider[];
|
|
46
|
+
connections: IntegrationConnection[];
|
|
47
|
+
healthByConnectionId?: Record<string, IntegrationHealth>;
|
|
48
|
+
isLoading?: boolean;
|
|
49
|
+
error?: Error | null;
|
|
50
|
+
/**
|
|
51
|
+
* Invoked when the user clicks "Connect" on a catalog tile. The
|
|
52
|
+
* consumer should call its data hook's `connect(...)` action.
|
|
53
|
+
*/
|
|
54
|
+
onConnect: (input: {
|
|
55
|
+
providerId: string;
|
|
56
|
+
connectorId: string;
|
|
57
|
+
}) => void | Promise<void>;
|
|
58
|
+
/** Invoked when the user clicks "Disconnect" on a live connection. */
|
|
59
|
+
onDisconnect: (connectionId: string) => void | Promise<void>;
|
|
60
|
+
/** Empty-state message when the catalog hasn't loaded any providers. */
|
|
61
|
+
emptyCatalogLabel?: string;
|
|
62
|
+
className?: string;
|
|
63
|
+
}
|
|
64
|
+
declare function IntegrationsPanel({ catalog, connections, healthByConnectionId, isLoading, error, onConnect, onDisconnect, emptyCatalogLabel, className, }: IntegrationsPanelProps): react_jsx_runtime.JSX.Element;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Endpoint contract expected on the consumer app's server (which
|
|
68
|
+
* wraps `PlatformHubClient` from `@tangle-network/agent-runtime/platform`):
|
|
69
|
+
*
|
|
70
|
+
* GET {base}/catalog
|
|
71
|
+
* → { catalog: { providers: IntegrationProvider[] } }
|
|
72
|
+
* GET {base}/connections
|
|
73
|
+
* → { connections: IntegrationConnection[] }
|
|
74
|
+
* GET {base}/healthchecks (optional)
|
|
75
|
+
* → { healthchecks: IntegrationHealth[] }
|
|
76
|
+
* POST {base}/auth/start
|
|
77
|
+
* body { providerId, connectorId, returnUrl, requestedScopes? }
|
|
78
|
+
* → { authorizationUrl: string }
|
|
79
|
+
* DELETE {base}/connections/{connectionId}
|
|
80
|
+
* → { connection: IntegrationConnection }
|
|
81
|
+
*/
|
|
82
|
+
interface UseIntegrationsOptions {
|
|
83
|
+
/** Base URL where the consumer mounted the integrations endpoints. */
|
|
84
|
+
apiBaseUrl: string;
|
|
85
|
+
/** Custom fetch (tests / non-browser runtimes). */
|
|
86
|
+
fetchImpl?: typeof fetch;
|
|
87
|
+
/** Whether the initial load happens automatically on mount. */
|
|
88
|
+
autoLoad?: boolean;
|
|
89
|
+
}
|
|
90
|
+
interface UseIntegrationsResult {
|
|
91
|
+
catalog: IntegrationProvider[];
|
|
92
|
+
connections: IntegrationConnection[];
|
|
93
|
+
healthByConnectionId: Record<string, IntegrationHealth>;
|
|
94
|
+
isLoading: boolean;
|
|
95
|
+
error: Error | null;
|
|
96
|
+
refresh: () => Promise<void>;
|
|
97
|
+
/** Kick off OAuth — navigates the window on success. */
|
|
98
|
+
connect: (input: ConnectInput) => Promise<void>;
|
|
99
|
+
/** Revoke a connection by id; refreshes the connections list. */
|
|
100
|
+
disconnect: (connectionId: string) => Promise<void>;
|
|
101
|
+
}
|
|
102
|
+
interface ConnectInput {
|
|
103
|
+
providerId: string;
|
|
104
|
+
connectorId: string;
|
|
105
|
+
/**
|
|
106
|
+
* URL the platform redirects the user back to after OAuth. Must be
|
|
107
|
+
* allow-listed on the platform.
|
|
108
|
+
*/
|
|
109
|
+
returnUrl: string;
|
|
110
|
+
requestedScopes?: string[];
|
|
111
|
+
}
|
|
112
|
+
declare function useIntegrations({ apiBaseUrl, fetchImpl, autoLoad, }: UseIntegrationsOptions): UseIntegrationsResult;
|
|
113
|
+
|
|
114
|
+
export { type ConnectInput, type IntegrationConnection, type IntegrationConnector, type IntegrationHealth, type IntegrationProvider, IntegrationsPanel, type IntegrationsPanelProps, type UseIntegrationsOptions, type UseIntegrationsResult, useIntegrations };
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// src/integrations/integrations-panel.tsx
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {
|
|
4
|
+
Badge,
|
|
5
|
+
Button,
|
|
6
|
+
Card,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardHeader,
|
|
9
|
+
EmptyState
|
|
10
|
+
} from "@tangle-network/ui/primitives";
|
|
11
|
+
import { cn } from "@tangle-network/ui/utils";
|
|
12
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
function statusVariant(status) {
|
|
14
|
+
if (status === "connected" || status === "ok") return "default";
|
|
15
|
+
if (status === "pending") return "secondary";
|
|
16
|
+
if (status === "revoked" || status === "expired" || status === "failing")
|
|
17
|
+
return "destructive";
|
|
18
|
+
return "outline";
|
|
19
|
+
}
|
|
20
|
+
function defaultConnectorOf(provider) {
|
|
21
|
+
return provider.connectors?.[0]?.connectorId ?? provider.providerId;
|
|
22
|
+
}
|
|
23
|
+
function buildConnectionIndex(connections) {
|
|
24
|
+
const index = /* @__PURE__ */ new Map();
|
|
25
|
+
for (const conn of connections) {
|
|
26
|
+
if (conn.status === "revoked") continue;
|
|
27
|
+
index.set(`${conn.providerId}:${conn.connectorId}`, conn);
|
|
28
|
+
}
|
|
29
|
+
return index;
|
|
30
|
+
}
|
|
31
|
+
function IntegrationsPanel({
|
|
32
|
+
catalog,
|
|
33
|
+
connections,
|
|
34
|
+
healthByConnectionId,
|
|
35
|
+
isLoading,
|
|
36
|
+
error,
|
|
37
|
+
onConnect,
|
|
38
|
+
onDisconnect,
|
|
39
|
+
emptyCatalogLabel = "No integrations available yet.",
|
|
40
|
+
className
|
|
41
|
+
}) {
|
|
42
|
+
const connectionIndex = React.useMemo(
|
|
43
|
+
() => buildConnectionIndex(connections),
|
|
44
|
+
[connections]
|
|
45
|
+
);
|
|
46
|
+
if (error) {
|
|
47
|
+
return /* @__PURE__ */ jsx(Card, { className: cn("border-destructive/50", className), children: /* @__PURE__ */ jsx(CardContent, { className: "py-6", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive", children: [
|
|
48
|
+
"Failed to load integrations: ",
|
|
49
|
+
error.message
|
|
50
|
+
] }) }) });
|
|
51
|
+
}
|
|
52
|
+
if (isLoading && catalog.length === 0) {
|
|
53
|
+
return /* @__PURE__ */ jsx("div", { className: cn("grid gap-3 sm:grid-cols-2 lg:grid-cols-3", className), children: [0, 1, 2, 3].map((i) => /* @__PURE__ */ jsxs(Card, { className: "animate-pulse", children: [
|
|
54
|
+
/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx("div", { className: "h-5 w-32 rounded bg-muted" }) }),
|
|
55
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("div", { className: "h-4 w-full rounded bg-muted" }) })
|
|
56
|
+
] }, i)) });
|
|
57
|
+
}
|
|
58
|
+
if (catalog.length === 0) {
|
|
59
|
+
return /* @__PURE__ */ jsx(
|
|
60
|
+
EmptyState,
|
|
61
|
+
{
|
|
62
|
+
title: "No integrations",
|
|
63
|
+
description: emptyCatalogLabel,
|
|
64
|
+
className
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return /* @__PURE__ */ jsx("div", { className: cn("grid gap-3 sm:grid-cols-2 lg:grid-cols-3", className), children: catalog.map((provider) => {
|
|
69
|
+
const connectorId = defaultConnectorOf(provider);
|
|
70
|
+
const live = connectionIndex.get(`${provider.providerId}:${connectorId}`);
|
|
71
|
+
const health = live ? healthByConnectionId?.[live.id] : void 0;
|
|
72
|
+
const headline = provider.displayName ?? provider.providerId.replace(/[-_]/g, " ");
|
|
73
|
+
return /* @__PURE__ */ jsxs(
|
|
74
|
+
Card,
|
|
75
|
+
{
|
|
76
|
+
"data-testid": `integration-${provider.providerId}`,
|
|
77
|
+
children: [
|
|
78
|
+
/* @__PURE__ */ jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-2 space-y-0", children: [
|
|
79
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
80
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
81
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold capitalize text-foreground", children: headline }),
|
|
82
|
+
live ? /* @__PURE__ */ jsx(Badge, { variant: statusVariant(live.status), className: "text-xs", children: live.status }) : null
|
|
83
|
+
] }),
|
|
84
|
+
provider.description ? /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-xs text-muted-foreground", children: provider.description }) : null
|
|
85
|
+
] }),
|
|
86
|
+
health ? /* @__PURE__ */ jsx(
|
|
87
|
+
Badge,
|
|
88
|
+
{
|
|
89
|
+
variant: statusVariant(health.status),
|
|
90
|
+
className: "text-xs uppercase",
|
|
91
|
+
children: health.status
|
|
92
|
+
}
|
|
93
|
+
) : null
|
|
94
|
+
] }),
|
|
95
|
+
/* @__PURE__ */ jsx(CardContent, { className: "flex items-center justify-between gap-2", children: live ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
96
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: live.account?.displayName ?? live.account?.identity ?? "Connected" }),
|
|
97
|
+
/* @__PURE__ */ jsx(
|
|
98
|
+
Button,
|
|
99
|
+
{
|
|
100
|
+
size: "sm",
|
|
101
|
+
variant: "outline",
|
|
102
|
+
onClick: () => onDisconnect(live.id),
|
|
103
|
+
"data-testid": `disconnect-${provider.providerId}`,
|
|
104
|
+
children: "Disconnect"
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
] }) : /* @__PURE__ */ jsx(
|
|
108
|
+
Button,
|
|
109
|
+
{
|
|
110
|
+
size: "sm",
|
|
111
|
+
className: "ml-auto",
|
|
112
|
+
onClick: () => onConnect({ providerId: provider.providerId, connectorId }),
|
|
113
|
+
"data-testid": `connect-${provider.providerId}`,
|
|
114
|
+
children: "Connect"
|
|
115
|
+
}
|
|
116
|
+
) })
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
`${provider.providerId}:${connectorId}`
|
|
120
|
+
);
|
|
121
|
+
}) });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/integrations/use-integrations.ts
|
|
125
|
+
import * as React2 from "react";
|
|
126
|
+
function unwrap(json) {
|
|
127
|
+
if (json && typeof json === "object" && "data" in json && json.data !== void 0) {
|
|
128
|
+
return json.data;
|
|
129
|
+
}
|
|
130
|
+
return json;
|
|
131
|
+
}
|
|
132
|
+
function useIntegrations({
|
|
133
|
+
apiBaseUrl,
|
|
134
|
+
fetchImpl,
|
|
135
|
+
autoLoad = true
|
|
136
|
+
}) {
|
|
137
|
+
const fetcher = fetchImpl ?? (typeof fetch === "function" ? fetch : null);
|
|
138
|
+
if (!fetcher) {
|
|
139
|
+
throw new Error("useIntegrations: fetch is not available in this environment");
|
|
140
|
+
}
|
|
141
|
+
const base = apiBaseUrl.replace(/\/+$/, "");
|
|
142
|
+
const [catalog, setCatalog] = React2.useState([]);
|
|
143
|
+
const [connections, setConnections] = React2.useState([]);
|
|
144
|
+
const [healthByConnectionId, setHealthByConnectionId] = React2.useState({});
|
|
145
|
+
const [isLoading, setIsLoading] = React2.useState(autoLoad);
|
|
146
|
+
const [error, setError] = React2.useState(null);
|
|
147
|
+
const refresh = React2.useCallback(async () => {
|
|
148
|
+
setIsLoading(true);
|
|
149
|
+
setError(null);
|
|
150
|
+
try {
|
|
151
|
+
const [catalogRes, connectionsRes] = await Promise.all([
|
|
152
|
+
fetcher(`${base}/catalog`, { credentials: "include" }),
|
|
153
|
+
fetcher(`${base}/connections`, { credentials: "include" })
|
|
154
|
+
]);
|
|
155
|
+
if (!catalogRes.ok) {
|
|
156
|
+
throw new Error(`Failed to load integration catalog (${catalogRes.status})`);
|
|
157
|
+
}
|
|
158
|
+
if (!connectionsRes.ok) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Failed to load integration connections (${connectionsRes.status})`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const catalogJson = unwrap(await catalogRes.json());
|
|
164
|
+
const providers = catalogJson?.catalog?.providers ?? catalogJson?.providers ?? [];
|
|
165
|
+
setCatalog(providers);
|
|
166
|
+
const connectionsJson = unwrap(
|
|
167
|
+
await connectionsRes.json()
|
|
168
|
+
);
|
|
169
|
+
setConnections(connectionsJson?.connections ?? []);
|
|
170
|
+
try {
|
|
171
|
+
const healthRes = await fetcher(`${base}/healthchecks`, {
|
|
172
|
+
credentials: "include"
|
|
173
|
+
});
|
|
174
|
+
if (healthRes.ok) {
|
|
175
|
+
const healthJson = unwrap(
|
|
176
|
+
await healthRes.json()
|
|
177
|
+
);
|
|
178
|
+
const map = {};
|
|
179
|
+
for (const h of healthJson?.healthchecks ?? []) {
|
|
180
|
+
map[h.connectionId] = h;
|
|
181
|
+
}
|
|
182
|
+
setHealthByConnectionId(map);
|
|
183
|
+
}
|
|
184
|
+
} catch {
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
188
|
+
} finally {
|
|
189
|
+
setIsLoading(false);
|
|
190
|
+
}
|
|
191
|
+
}, [base, fetcher]);
|
|
192
|
+
React2.useEffect(() => {
|
|
193
|
+
if (autoLoad) {
|
|
194
|
+
void refresh();
|
|
195
|
+
}
|
|
196
|
+
}, [autoLoad, refresh]);
|
|
197
|
+
const connect = React2.useCallback(
|
|
198
|
+
async (input) => {
|
|
199
|
+
const res = await fetcher(`${base}/auth/start`, {
|
|
200
|
+
method: "POST",
|
|
201
|
+
credentials: "include",
|
|
202
|
+
headers: { "content-type": "application/json" },
|
|
203
|
+
body: JSON.stringify(input)
|
|
204
|
+
});
|
|
205
|
+
if (!res.ok) {
|
|
206
|
+
const text = await res.text().catch(() => "");
|
|
207
|
+
throw new Error(`Failed to start OAuth (${res.status}): ${text}`);
|
|
208
|
+
}
|
|
209
|
+
const json = unwrap(await res.json());
|
|
210
|
+
if (!json?.authorizationUrl) {
|
|
211
|
+
throw new Error("Platform did not return an authorizationUrl");
|
|
212
|
+
}
|
|
213
|
+
window.location.href = json.authorizationUrl;
|
|
214
|
+
},
|
|
215
|
+
[base, fetcher]
|
|
216
|
+
);
|
|
217
|
+
const disconnect = React2.useCallback(
|
|
218
|
+
async (connectionId) => {
|
|
219
|
+
const res = await fetcher(
|
|
220
|
+
`${base}/connections/${encodeURIComponent(connectionId)}`,
|
|
221
|
+
{ method: "DELETE", credentials: "include" }
|
|
222
|
+
);
|
|
223
|
+
if (!res.ok) {
|
|
224
|
+
const text = await res.text().catch(() => "");
|
|
225
|
+
throw new Error(`Failed to revoke connection (${res.status}): ${text}`);
|
|
226
|
+
}
|
|
227
|
+
await refresh();
|
|
228
|
+
},
|
|
229
|
+
[base, fetcher, refresh]
|
|
230
|
+
);
|
|
231
|
+
return {
|
|
232
|
+
catalog,
|
|
233
|
+
connections,
|
|
234
|
+
healthByConnectionId,
|
|
235
|
+
isLoading,
|
|
236
|
+
error,
|
|
237
|
+
refresh,
|
|
238
|
+
connect,
|
|
239
|
+
disconnect
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
export {
|
|
243
|
+
IntegrationsPanel,
|
|
244
|
+
useIntegrations
|
|
245
|
+
};
|
package/dist/openui.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ export { OpenUIAction, OpenUIActionsNode, OpenUIArtifactRenderer, OpenUIArtifact
|
|
|
2
2
|
import '@tangle-network/ui/chat';
|
|
3
3
|
import '@tangle-network/ui/run';
|
|
4
4
|
import '@tangle-network/ui/files';
|
|
5
|
-
import '@tangle-network/ui/auth';
|
|
6
5
|
import '@tangle-network/ui/utils';
|
|
7
6
|
import '@tangle-network/ui/editor';
|
|
8
7
|
import '@tangle-network/ui/markdown';
|
package/dist/styles.css
CHANGED
|
@@ -1020,6 +1020,13 @@
|
|
|
1020
1020
|
.gap-px {
|
|
1021
1021
|
gap: 1px;
|
|
1022
1022
|
}
|
|
1023
|
+
.space-y-0 {
|
|
1024
|
+
:where(& > :not(:last-child)) {
|
|
1025
|
+
--tw-space-y-reverse: 0;
|
|
1026
|
+
margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));
|
|
1027
|
+
margin-block-end: calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)));
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1023
1030
|
.space-y-0\.5 {
|
|
1024
1031
|
:where(& > :not(:last-child)) {
|
|
1025
1032
|
--tw-space-y-reverse: 0;
|
|
@@ -1576,6 +1583,9 @@
|
|
|
1576
1583
|
.py-5 {
|
|
1577
1584
|
padding-block: calc(var(--spacing) * 5);
|
|
1578
1585
|
}
|
|
1586
|
+
.py-6 {
|
|
1587
|
+
padding-block: calc(var(--spacing) * 6);
|
|
1588
|
+
}
|
|
1579
1589
|
.py-8 {
|
|
1580
1590
|
padding-block: calc(var(--spacing) * 8);
|
|
1581
1591
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/sandbox-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -62,6 +62,10 @@
|
|
|
62
62
|
"import": "./dist/auth.js",
|
|
63
63
|
"types": "./dist/auth.d.ts"
|
|
64
64
|
},
|
|
65
|
+
"./integrations": {
|
|
66
|
+
"import": "./dist/integrations.js",
|
|
67
|
+
"types": "./dist/integrations.d.ts"
|
|
68
|
+
},
|
|
65
69
|
"./pages": {
|
|
66
70
|
"import": "./dist/pages.js",
|
|
67
71
|
"types": "./dist/pages.d.ts"
|