@growthub/cli 0.13.5 → 0.13.6
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/assets/worker-kits/growthub-custom-workspace-starter-v1/QUICKSTART.md +19 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/.env.example +8 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/README.md +4 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/action/execute/route.js +60 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/actions/route.js +50 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/connect-session/route.js +68 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/connection-status/route.js +56 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/proxy/route.js +67 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/status/route.js +50 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +161 -50
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/NangoConnectionPanel.jsx +496 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +104 -17
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/nango/page.jsx +167 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +18 -7
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +17 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +16 -14
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/env.js +7 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/index.js +38 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-adapter.js +552 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-config-loader.js +202 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-schema.js +303 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolver-loader.js +49 -10
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/source-resolver-registry.js +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/nango.js +49 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/template-registry.js +4 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +2 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +102 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/bundles/growthub-custom-workspace-starter-v1.json +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +2 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/seeded-configs/project-management.config.json +276 -0
- package/dist/index.js +127 -44
- package/package.json +1 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { Suspense } from "react";
|
|
3
|
+
import { describeNangoAdapter, getStatus } from "@/lib/adapters/integrations/nango";
|
|
4
|
+
import { readWorkspaceConfig } from "@/lib/workspace-config";
|
|
5
|
+
import { WorkspaceRail } from "../../../workspace-rail.jsx";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Nango integration settings — inspect-only panel.
|
|
9
|
+
*
|
|
10
|
+
* Reads from the EXISTING `objectType: "api-registry"` rows in
|
|
11
|
+
* `dataModel.objects[]`. Rows that declare `connectorKind: "nango"` are
|
|
12
|
+
* Nango-backed; other connectorKinds (http, mcp, chrome, tool) are shown
|
|
13
|
+
* here only for context — they keep going through their own resolvers.
|
|
14
|
+
*
|
|
15
|
+
* Secrets are not displayed. The Nango secret lives in env (default env-ref
|
|
16
|
+
* name: NANGO_SECRET_KEY); the row's `authRef` column names the env-ref.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
function listApiRegistryObjects(workspaceConfig) {
|
|
20
|
+
const objects = workspaceConfig?.dataModel?.objects;
|
|
21
|
+
if (!Array.isArray(objects)) return [];
|
|
22
|
+
return objects.filter((object) => object?.objectType === "api-registry");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function flattenNangoRows(apiRegistryObjects) {
|
|
26
|
+
const rows = [];
|
|
27
|
+
for (const object of apiRegistryObjects) {
|
|
28
|
+
const objectRows = Array.isArray(object.rows) ? object.rows : [];
|
|
29
|
+
for (const row of objectRows) {
|
|
30
|
+
if (row?.connectorKind === "nango") {
|
|
31
|
+
rows.push({ object, row });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return rows;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function summarizeList(value) {
|
|
39
|
+
if (Array.isArray(value)) return value.filter(Boolean);
|
|
40
|
+
if (typeof value === "string" && value.trim()) {
|
|
41
|
+
return value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
42
|
+
}
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function NangoRowCard({ object, row }) {
|
|
47
|
+
const connectionIds = summarizeList(row.connectionIds);
|
|
48
|
+
const enabledActions = summarizeList(row.enabledActions);
|
|
49
|
+
const providerConfigKey = row.providerConfigKey || row.integrationId;
|
|
50
|
+
return <article className="workspace-integration-row">
|
|
51
|
+
<div className="workspace-provider-mark">{String(row.integrationId || "?").slice(0, 1).toUpperCase()}</div>
|
|
52
|
+
<div className="workspace-integration-main">
|
|
53
|
+
<strong>{row.integrationId}</strong>
|
|
54
|
+
<p>providerConfigKey: <code>{providerConfigKey || "—"}</code></p>
|
|
55
|
+
<div className="workspace-integration-meta">
|
|
56
|
+
<span>object: <code>{object.id}</code></span>
|
|
57
|
+
<span>{row.nangoMode || "cloud"}</span>
|
|
58
|
+
<span>env: {row.nangoEnvironment || "dev"}</span>
|
|
59
|
+
<span>{connectionIds.length} {connectionIds.length === 1 ? "connection" : "connections"}</span>
|
|
60
|
+
<span>{enabledActions.length} {enabledActions.length === 1 ? "action" : "actions"}</span>
|
|
61
|
+
{row.authRef ? <span><code>{row.authRef}</code></span> : null}
|
|
62
|
+
{row.endpoint ? <span>endpoint: <code>{row.endpoint}</code></span> : null}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<span className={`workspace-integration-status ${row.status || "configured"}`}>{row.status || "configured"}</span>
|
|
66
|
+
</article>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function NangoIntegrationsSettingsPage() {
|
|
70
|
+
const adapter = describeNangoAdapter();
|
|
71
|
+
const workspaceConfig = await readWorkspaceConfig();
|
|
72
|
+
const apiRegistryObjects = listApiRegistryObjects(workspaceConfig);
|
|
73
|
+
const nangoRows = flattenNangoRows(apiRegistryObjects);
|
|
74
|
+
|
|
75
|
+
let status;
|
|
76
|
+
try {
|
|
77
|
+
status = await getStatus();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
status = { status: "disconnected", reason: error?.message || "status probe failed" };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return <main className="workspace-builder workspace-settings-page">
|
|
83
|
+
<Suspense fallback={null}>
|
|
84
|
+
<WorkspaceRail
|
|
85
|
+
workspaceConfig={workspaceConfig}
|
|
86
|
+
authority={adapter.authority}
|
|
87
|
+
managementSlot={(
|
|
88
|
+
<Link className="active" href="/settings/integrations">Integrations</Link>
|
|
89
|
+
)}
|
|
90
|
+
/>
|
|
91
|
+
</Suspense>
|
|
92
|
+
|
|
93
|
+
<section className="workspace-surface">
|
|
94
|
+
<header className="workspace-toolbar">
|
|
95
|
+
<div>
|
|
96
|
+
<p>Workspace settings · Integrations</p>
|
|
97
|
+
<h1>Nango</h1>
|
|
98
|
+
</div>
|
|
99
|
+
<div className="workspace-toolbar-actions">
|
|
100
|
+
<Link href="/settings/integrations">All integrations</Link>
|
|
101
|
+
<span>{adapter.id}</span>
|
|
102
|
+
<span>{adapter.authority}</span>
|
|
103
|
+
</div>
|
|
104
|
+
</header>
|
|
105
|
+
|
|
106
|
+
<section className="workspace-integration-summary" aria-label="Nango adapter summary">
|
|
107
|
+
<article>
|
|
108
|
+
<span>Adapter</span>
|
|
109
|
+
<strong>{adapter.label}</strong>
|
|
110
|
+
<div>
|
|
111
|
+
<code>{adapter.secretEnvName}</code>
|
|
112
|
+
<span> · row-scoped via <code>connectorKind: "nango"</code></span>
|
|
113
|
+
</div>
|
|
114
|
+
</article>
|
|
115
|
+
<article>
|
|
116
|
+
<span>Mode</span>
|
|
117
|
+
<strong>{adapter.mode}</strong>
|
|
118
|
+
<div>{adapter.hostUrl ? <code>{adapter.hostUrl}</code> : <code>nango cloud</code>}</div>
|
|
119
|
+
</article>
|
|
120
|
+
<article>
|
|
121
|
+
<span>Status</span>
|
|
122
|
+
<strong>{status.status}</strong>
|
|
123
|
+
<div>{status.reason ? <span>{status.reason}</span> : <span>environment: <code>{status.environment || adapter.environment}</code></span>}</div>
|
|
124
|
+
</article>
|
|
125
|
+
</section>
|
|
126
|
+
|
|
127
|
+
<section className="workspace-integration-toolbar">
|
|
128
|
+
<div>
|
|
129
|
+
<strong>Nango-backed API Registry rows</strong>
|
|
130
|
+
<p>Rows in <code>dataModel.objects[]</code> with <code>objectType: "api-registry"</code> and <code>connectorKind: "nango"</code>. Edit <code>growthub.config.json</code> to add or remove rows — secrets stay in env, referenced by the row's <code>authRef</code>.</p>
|
|
131
|
+
</div>
|
|
132
|
+
</section>
|
|
133
|
+
|
|
134
|
+
<section className="workspace-integration-board">
|
|
135
|
+
{nangoRows.length === 0
|
|
136
|
+
? <article className="workspace-integration-section">
|
|
137
|
+
<div className="workspace-integration-section-heading">
|
|
138
|
+
<div>
|
|
139
|
+
<h2>No Nango-backed rows yet</h2>
|
|
140
|
+
<p>Add a row to an <code>api-registry</code> object with <code>connectorKind: "nango"</code>, an <code>integrationId</code>, and an optional <code>providerConfigKey</code>. Apply the <code>nango</code> resolver template from the Data Model template picker for sensible defaults.</p>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</article>
|
|
144
|
+
: <article className="workspace-integration-section">
|
|
145
|
+
<div className="workspace-integration-section-heading">
|
|
146
|
+
<div>
|
|
147
|
+
<h2>Nango providers</h2>
|
|
148
|
+
<p>{nangoRows.length} row{nangoRows.length === 1 ? "" : "s"} across {apiRegistryObjects.length} API Registry object{apiRegistryObjects.length === 1 ? "" : "s"}.</p>
|
|
149
|
+
</div>
|
|
150
|
+
<span>{nangoRows.length}</span>
|
|
151
|
+
</div>
|
|
152
|
+
<div className="workspace-integration-list">
|
|
153
|
+
{nangoRows.map(({ object, row }, index) => <NangoRowCard
|
|
154
|
+
object={object}
|
|
155
|
+
row={row}
|
|
156
|
+
key={`${object.id}:${row.integrationId || index}`}
|
|
157
|
+
/>)}
|
|
158
|
+
</div>
|
|
159
|
+
</article>}
|
|
160
|
+
</section>
|
|
161
|
+
</section>
|
|
162
|
+
</main>;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export {
|
|
166
|
+
NangoIntegrationsSettingsPage as default
|
|
167
|
+
};
|
|
@@ -77,6 +77,7 @@ async function IntegrationsSettingsPage() {
|
|
|
77
77
|
</div>
|
|
78
78
|
<div className="workspace-toolbar-actions">
|
|
79
79
|
<Link href="/api/settings/integrations">API contract</Link>
|
|
80
|
+
<Link href="/settings/integrations/nango">Nango</Link>
|
|
80
81
|
<span>{adapter.id}</span>
|
|
81
82
|
<span>{adapter.authority}</span>
|
|
82
83
|
</div>
|
|
@@ -62,7 +62,7 @@ const WORKFLOW_METADATA_SELECTORS = Object.freeze({
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
function resolveRegistryRowForSandbox(workspaceConfig, sandboxRow) {
|
|
65
|
-
const graph = parseOrchestrationGraph(sandboxRow?.orchestrationGraph);
|
|
65
|
+
const graph = parseOrchestrationGraph(sandboxRow?.orchestrationConfig || sandboxRow?.orchestrationGraph);
|
|
66
66
|
const apiNode = graph?.nodes?.find((n) => n?.type === "api-registry-call");
|
|
67
67
|
const registryId = String(
|
|
68
68
|
apiNode?.config?.registryId || apiNode?.config?.integrationId || sandboxRow?.schedulerRegistryId || ""
|
|
@@ -291,6 +291,10 @@ function isPassingRun(payload) {
|
|
|
291
291
|
return payload?.ok === true && Number(payload?.exitCode ?? payload?.response?.exitCode) === 0;
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
function graphHasNodes(graph) {
|
|
295
|
+
return Array.isArray(graph?.nodes) && graph.nodes.length > 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
294
298
|
function WorkflowAddStepPanel({ target, onSelect }) {
|
|
295
299
|
return (
|
|
296
300
|
<div className="dm-workflow-add-panel">
|
|
@@ -323,7 +327,7 @@ export default function WorkflowSurface() {
|
|
|
323
327
|
const searchParams = useSearchParams();
|
|
324
328
|
const objectId = String(searchParams.get("object") || "").trim();
|
|
325
329
|
const rowId = String(searchParams.get("row") || "").trim();
|
|
326
|
-
const fieldName = String(searchParams.get("field") || "
|
|
330
|
+
const fieldName = String(searchParams.get("field") || "orchestrationConfig").trim();
|
|
327
331
|
const runId = String(searchParams.get("run") || "").trim();
|
|
328
332
|
|
|
329
333
|
const [workspaceConfig, setWorkspaceConfig] = useState(null);
|
|
@@ -369,11 +373,14 @@ export default function WorkflowSurface() {
|
|
|
369
373
|
);
|
|
370
374
|
|
|
371
375
|
const sandboxRow = resolved.row;
|
|
372
|
-
const
|
|
376
|
+
const hasGraphValue = (value) => Boolean(parseOrchestrationGraph(value));
|
|
377
|
+
const effectiveFieldName = hasGraphValue(sandboxRow?.[fieldName])
|
|
373
378
|
? fieldName
|
|
374
|
-
: sandboxRow?.orchestrationConfig
|
|
379
|
+
: hasGraphValue(sandboxRow?.orchestrationConfig)
|
|
375
380
|
? "orchestrationConfig"
|
|
376
|
-
:
|
|
381
|
+
: hasGraphValue(sandboxRow?.orchestrationGraph)
|
|
382
|
+
? "orchestrationGraph"
|
|
383
|
+
: (fieldName || "orchestrationConfig");
|
|
377
384
|
const draftFieldName = effectiveFieldName === "orchestrationConfig" ? "orchestrationDraftConfig" : "orchestrationDraftGraph";
|
|
378
385
|
const registryRow = useMemo(
|
|
379
386
|
() => (sandboxRow && workspaceConfig ? resolveRegistryRowForSandbox(workspaceConfig, sandboxRow) : null),
|
|
@@ -386,7 +393,11 @@ export default function WorkflowSurface() {
|
|
|
386
393
|
|
|
387
394
|
useEffect(() => {
|
|
388
395
|
if (!sandboxRow) return;
|
|
389
|
-
const
|
|
396
|
+
const draftParsed = parseOrchestrationGraph(sandboxRow[draftFieldName]);
|
|
397
|
+
const publishedParsed = parseOrchestrationGraph(sandboxRow[effectiveFieldName])
|
|
398
|
+
|| parseOrchestrationGraph(sandboxRow.orchestrationConfig)
|
|
399
|
+
|| parseOrchestrationGraph(sandboxRow.orchestrationGraph);
|
|
400
|
+
const parsed = graphHasNodes(draftParsed) || !graphHasNodes(publishedParsed) ? draftParsed : publishedParsed;
|
|
390
401
|
setOrchestrationGraph(parsed);
|
|
391
402
|
setDirty(false);
|
|
392
403
|
setGraphError("");
|
|
@@ -760,7 +771,7 @@ export default function WorkflowSurface() {
|
|
|
760
771
|
const publishReady = draftPassed && String(sandboxRow?.orchestrationDraftTestedConfig || "") === currentGraphSerialized && !dirty;
|
|
761
772
|
const savedDraftValue = String(sandboxRow?.[draftFieldName] || "").trim();
|
|
762
773
|
const draftStatus = String(sandboxRow?.orchestrationDraftStatus || "").trim();
|
|
763
|
-
const hasSavedDraft = Boolean(savedDraftValue) && draftStatus !== "published";
|
|
774
|
+
const hasSavedDraft = Boolean(savedDraftValue) && draftStatus !== "published" && graphHasNodes(parseOrchestrationGraph(savedDraftValue));
|
|
764
775
|
const isDraftMode = dirty || hasSavedDraft;
|
|
765
776
|
const canTest = !graphUnset && !graphBlankShell && Boolean(sandboxRow) && !Boolean(graphError);
|
|
766
777
|
const showDiscardDraft = isDraftMode;
|
|
@@ -306,7 +306,7 @@ const GRID_COLUMNS = 12;
|
|
|
306
306
|
const GRID_ROWS = 16;
|
|
307
307
|
const GRID_CELL_COUNT = GRID_COLUMNS * GRID_ROWS;
|
|
308
308
|
const DEFAULT_TAB_ID = "tab-default";
|
|
309
|
-
const COLLAPSED_GRID_COLUMNS = "264px minmax(0, 1fr)";
|
|
309
|
+
const COLLAPSED_GRID_COLUMNS = "var(--workspace-rail-width, 264px) minmax(0, 1fr)";
|
|
310
310
|
|
|
311
311
|
function generateId(prefix) {
|
|
312
312
|
if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) {
|
|
@@ -3928,8 +3928,9 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
3928
3928
|
const resizeDragRef = useRef(null);
|
|
3929
3929
|
const moveDragRef = useRef(null);
|
|
3930
3930
|
const importInputRef = useRef(null);
|
|
3931
|
-
const addSlot = dragPreview || selectedPosition;
|
|
3932
3931
|
const selectedWidgetLookupId = selectedWidgetId || pendingSelectedWidgetId;
|
|
3932
|
+
const addSlot = dragPreview || selectedPosition;
|
|
3933
|
+
const showAddWidgetSlot = dashboardDraftMode && !selectedWidgetLookupId && panelOpen && addSlot;
|
|
3933
3934
|
const selectedWidget = activeWidgets.find((widget) => widget.id === selectedWidgetLookupId) || null;
|
|
3934
3935
|
const availableIntegrations = useMemo(() => flattenIntegrationSettings(integrationSettings), [integrationSettings]);
|
|
3935
3936
|
const dataModelTables = useMemo(
|
|
@@ -5149,6 +5150,12 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5149
5150
|
return () => window.removeEventListener("keydown", handler);
|
|
5150
5151
|
}, [addWidget, commandPaletteOpen, managementOpen, panelOpen, settingsOpen, templateGalleryOpen, workspaceView]);
|
|
5151
5152
|
|
|
5153
|
+
useEffect(() => {
|
|
5154
|
+
const openFromRail = () => setCommandPaletteOpen(true);
|
|
5155
|
+
window.addEventListener("growthub:open-command-palette", openFromRail);
|
|
5156
|
+
return () => window.removeEventListener("growthub:open-command-palette", openFromRail);
|
|
5157
|
+
}, []);
|
|
5158
|
+
|
|
5152
5159
|
const builderStyle = workspaceView === "dashboards" || !panelOpen
|
|
5153
5160
|
? { gridTemplateColumns: COLLAPSED_GRID_COLUMNS }
|
|
5154
5161
|
: undefined;
|
|
@@ -5317,6 +5324,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5317
5324
|
<WorkspaceRail
|
|
5318
5325
|
workspaceConfig={config}
|
|
5319
5326
|
authority={integrationAdapter.authority}
|
|
5327
|
+
defaultCollapsed={workspaceView === "builder"}
|
|
5320
5328
|
helperOpen={helperOpen}
|
|
5321
5329
|
onOpenHelper={() => {
|
|
5322
5330
|
if (helperOpen) { setHelperOpen(false); return; }
|
|
@@ -5354,11 +5362,11 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5354
5362
|
)}
|
|
5355
5363
|
/>
|
|
5356
5364
|
|
|
5357
|
-
<section className={`workspace-surface${workspaceView === "builder" ?
|
|
5365
|
+
<section className={`workspace-surface${workspaceView === "builder" ? ` dm-workflow-surface workspace-dashboard-surface${dashboardDraftMode ? " is-dashboard-editing" : ""}` : ""}`}>
|
|
5358
5366
|
<header className={`workspace-toolbar${workspaceView === "builder" ? " dm-workflow-toolbar" : ""}`}>
|
|
5359
5367
|
<div className={workspaceView === "builder" ? "dm-workflow-titlebar" : undefined}>
|
|
5360
5368
|
{workspaceView === "builder" ? <>
|
|
5361
|
-
<
|
|
5369
|
+
<button type="button" className="dm-workflow-breadcrumb-link" onClick={showDashboardHome}>Dashboards</button>
|
|
5362
5370
|
<span className="dm-workflow-title-separator">/</span>
|
|
5363
5371
|
<h1>{activeDashboard?.name || "Untitled"}</h1>
|
|
5364
5372
|
<span className="dm-workflow-count">({activeWidgets.length}) · v{activeDashboard?.version || "1"} · {dashboardModeLabel}</span>
|
|
@@ -5609,7 +5617,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5609
5617
|
<button type="button" onClick={duplicateTab} disabled={!dashboardDraftMode}><Copy size={15} />Duplicate Tab</button>
|
|
5610
5618
|
</div>
|
|
5611
5619
|
<div
|
|
5612
|
-
className={`workspace-grid${moveDrag ? " moving-widget" : ""}`}
|
|
5620
|
+
className={`workspace-grid${moveDrag ? " moving-widget" : ""}${dashboardDraftMode ? " is-edit-mode" : " is-view-mode"}`}
|
|
5613
5621
|
ref={gridRef}
|
|
5614
5622
|
onPointerMove={updatePointerDrag}
|
|
5615
5623
|
onPointerUp={(event) => {
|
|
@@ -5627,7 +5635,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5627
5635
|
}}
|
|
5628
5636
|
style={{ "--workspace-columns": canvas.layout.columns, "--workspace-rows": GRID_ROWS }}
|
|
5629
5637
|
>
|
|
5630
|
-
{Array.from({ length: GRID_CELL_COUNT }).map((_, index) => {
|
|
5638
|
+
{dashboardDraftMode ? Array.from({ length: GRID_CELL_COUNT }).map((_, index) => {
|
|
5631
5639
|
const x = index % GRID_COLUMNS;
|
|
5632
5640
|
const y = Math.floor(index / GRID_COLUMNS);
|
|
5633
5641
|
const isOccupied = occupiedCells.has(`${x}:${y}`);
|
|
@@ -5645,15 +5653,15 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5645
5653
|
}}
|
|
5646
5654
|
type="button"
|
|
5647
5655
|
/>;
|
|
5648
|
-
})}
|
|
5649
|
-
<button className={`workspace-add-widget${dragPreview ? " selecting" : ""}`} type="button"
|
|
5656
|
+
}) : null}
|
|
5657
|
+
{showAddWidgetSlot ? <button className={`workspace-add-widget${dragPreview ? " selecting" : ""}`} type="button" onClick={() => setPanelOpen(true)} style={{
|
|
5650
5658
|
gridColumn: `${addSlot.x + 1} / span ${addSlot.w}`,
|
|
5651
5659
|
gridRow: `${addSlot.y + 1} / span ${addSlot.h}`
|
|
5652
5660
|
}}>
|
|
5653
5661
|
<span className="workspace-widget-icon" aria-hidden="true"><span /></span>
|
|
5654
5662
|
<strong>Add widget</strong>
|
|
5655
5663
|
<small>Click to add your first widget</small>
|
|
5656
|
-
</button>
|
|
5664
|
+
</button> : null}
|
|
5657
5665
|
{activeWidgets.map((widget) => <WidgetPreview
|
|
5658
5666
|
key={widget.id}
|
|
5659
5667
|
branding={branding}
|
|
@@ -1488,6 +1488,7 @@ export function WorkspaceRail({
|
|
|
1488
1488
|
onConfigChange,
|
|
1489
1489
|
dashboardsSlot,
|
|
1490
1490
|
dataModelSlot,
|
|
1491
|
+
defaultCollapsed = false,
|
|
1491
1492
|
// `managementSlot` retained as accepted-but-ignored prop for backward
|
|
1492
1493
|
// compatibility with callers that still pass it. The Management item
|
|
1493
1494
|
// moved to the Workspace Settings → Ownership tab.
|
|
@@ -1500,7 +1501,7 @@ export function WorkspaceRail({
|
|
|
1500
1501
|
const router = useRouter();
|
|
1501
1502
|
|
|
1502
1503
|
const [activeTab, setActiveTab] = useState("home");
|
|
1503
|
-
const [railCollapsed, setRailCollapsed] = useState(
|
|
1504
|
+
const [railCollapsed, setRailCollapsed] = useState(Boolean(defaultCollapsed));
|
|
1504
1505
|
const [openMenuId, setOpenMenuId] = useState(null);
|
|
1505
1506
|
const [renamingId, setRenamingId] = useState(null);
|
|
1506
1507
|
const [renameDraft, setRenameDraft] = useState("");
|
|
@@ -1521,6 +1522,10 @@ export function WorkspaceRail({
|
|
|
1521
1522
|
|
|
1522
1523
|
const threads = useMemo(() => getHelperThreadRows(workspaceConfig), [workspaceConfig]);
|
|
1523
1524
|
|
|
1525
|
+
useEffect(() => {
|
|
1526
|
+
setRailCollapsed(Boolean(defaultCollapsed));
|
|
1527
|
+
}, [defaultCollapsed]);
|
|
1528
|
+
|
|
1524
1529
|
useEffect(() => {
|
|
1525
1530
|
if (typeof document === "undefined") return undefined;
|
|
1526
1531
|
document.body.classList.toggle("workspace-rail-collapsed", railCollapsed);
|
|
@@ -1640,6 +1645,16 @@ export function WorkspaceRail({
|
|
|
1640
1645
|
<ChevronDown size={13} className="workspace-brand-caret" aria-hidden="true" />
|
|
1641
1646
|
</button>
|
|
1642
1647
|
<div className="workspace-rail-topbar-actions">
|
|
1648
|
+
<button
|
|
1649
|
+
type="button"
|
|
1650
|
+
className="workspace-rail-icon-btn"
|
|
1651
|
+
aria-label={railCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
1652
|
+
title={railCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
1653
|
+
aria-pressed={railCollapsed}
|
|
1654
|
+
onClick={() => setRailCollapsed((v) => !v)}
|
|
1655
|
+
>
|
|
1656
|
+
<PanelLeftClose size={13} />
|
|
1657
|
+
</button>
|
|
1643
1658
|
<button
|
|
1644
1659
|
type="button"
|
|
1645
1660
|
className="workspace-rail-icon-btn"
|
|
@@ -1647,9 +1662,6 @@ export function WorkspaceRail({
|
|
|
1647
1662
|
title="Search (⌘K)"
|
|
1648
1663
|
data-rail-search=""
|
|
1649
1664
|
onClick={() => {
|
|
1650
|
-
// Surfaces with a command palette (DataModelShell) listen
|
|
1651
|
-
// for this event and open the palette in place. Other
|
|
1652
|
-
// surfaces are free to ignore it.
|
|
1653
1665
|
if (typeof window !== "undefined") {
|
|
1654
1666
|
window.dispatchEvent(new CustomEvent("growthub:open-command-palette"));
|
|
1655
1667
|
}
|
|
@@ -1657,16 +1669,6 @@ export function WorkspaceRail({
|
|
|
1657
1669
|
>
|
|
1658
1670
|
<Search size={13} />
|
|
1659
1671
|
</button>
|
|
1660
|
-
<button
|
|
1661
|
-
type="button"
|
|
1662
|
-
className="workspace-rail-icon-btn"
|
|
1663
|
-
aria-label={railCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
1664
|
-
title={railCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
1665
|
-
aria-pressed={railCollapsed}
|
|
1666
|
-
onClick={() => setRailCollapsed((v) => !v)}
|
|
1667
|
-
>
|
|
1668
|
-
<PanelLeftClose size={13} />
|
|
1669
|
-
</button>
|
|
1670
1672
|
<button
|
|
1671
1673
|
type="button"
|
|
1672
1674
|
className="workspace-rail-icon-btn"
|
package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/env.js
CHANGED
|
@@ -12,6 +12,13 @@ function readAdapterConfig() {
|
|
|
12
12
|
userId: process.env.GROWTHUB_BRIDGE_USER_ID || void 0,
|
|
13
13
|
hasAccessToken: Boolean(process.env.GROWTHUB_BRIDGE_ACCESS_TOKEN)
|
|
14
14
|
},
|
|
15
|
+
nango: {
|
|
16
|
+
mode: readEnum(["NANGO_MODE"], ["cloud", "self-hosted"], "cloud"),
|
|
17
|
+
hostUrl: process.env.NANGO_HOST_URL || void 0,
|
|
18
|
+
environment: process.env.NANGO_ENVIRONMENT || "dev",
|
|
19
|
+
secretEnvName: "NANGO_SECRET_KEY",
|
|
20
|
+
hasSecretKey: Boolean(process.env.NANGO_SECRET_KEY)
|
|
21
|
+
},
|
|
15
22
|
dataSources: {
|
|
16
23
|
hasWindsorApiKey: Boolean(process.env.WINDSOR_API_KEY)
|
|
17
24
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nango adapter barrel — server-side imports only.
|
|
3
|
+
*
|
|
4
|
+
* The browser must never import any module under this path. Routes consume
|
|
5
|
+
* this barrel; UI components hit the routes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
DEFAULT_NANGO_SECRET_ENV,
|
|
10
|
+
createConnectSession,
|
|
11
|
+
describeNangoAdapter,
|
|
12
|
+
executeAction,
|
|
13
|
+
getConnectionSummary,
|
|
14
|
+
getStatus,
|
|
15
|
+
listActions,
|
|
16
|
+
pickSafeConnectionFields,
|
|
17
|
+
projectNangoBinding,
|
|
18
|
+
proxyRequest,
|
|
19
|
+
resolveNangoEnv
|
|
20
|
+
} from "./nango-adapter.js";
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
buildNangoResolver,
|
|
24
|
+
registerNangoResolversFromConfig
|
|
25
|
+
} from "./nango-config-loader.js";
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
validateActionExecuteRequest,
|
|
29
|
+
validateActionsListInput,
|
|
30
|
+
validateConnectSessionRequest,
|
|
31
|
+
validateConnectionId,
|
|
32
|
+
validateConnectionStatusRequest,
|
|
33
|
+
validateConnectionSummaryRequest,
|
|
34
|
+
validateHostUrl,
|
|
35
|
+
validateNangoMode,
|
|
36
|
+
validateProviderConfigKey,
|
|
37
|
+
validateProxyRequest
|
|
38
|
+
} from "./nango-schema.js";
|