@growthub/cli 0.13.8 → 0.14.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/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/codex-sites/route.js +13 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/env-status/route.js +31 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +130 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceActivationPanel.jsx +17 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +5 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryCreationCockpit.jsx +200 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +501 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +75 -55
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ReferencePicker.jsx +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +215 -13
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/apps/codex-sites-data-model-card.jsx +81 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/apps/page.jsx +31 -14
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/apps/settings-accordion-section.jsx +50 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +176 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +137 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolver-loader.js +2 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-registry-creation-flow.js +317 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-response-profile.js +207 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/codex-sites-local-state.js +139 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/codex-sites-workspace-adapter.js +156 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/creation-error-recovery.js +103 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/env-status.js +100 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +63 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-serverless-flow.js +215 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-resolver-write.js +67 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/serverless-upgrade.js +89 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +11 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +8 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +7 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-resolver-proposal.js +200 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,8 @@ import { promises as fs } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { readWorkspaceConfig } from "@/lib/workspace-config";
|
|
5
5
|
import { AppsList } from "./apps-list.jsx";
|
|
6
|
+
import { CodexSitesDataModelCard } from "./codex-sites-data-model-card.jsx";
|
|
7
|
+
import { SettingsAccordionGroup, SettingsAccordionSection } from "./settings-accordion-section.jsx";
|
|
6
8
|
|
|
7
9
|
async function readForkMetadata() {
|
|
8
10
|
try {
|
|
@@ -82,24 +84,39 @@ async function AppsSettingsPage() {
|
|
|
82
84
|
<div className="workspace-settings-card-heading">
|
|
83
85
|
<div>
|
|
84
86
|
<h2>Apps</h2>
|
|
85
|
-
<p>
|
|
87
|
+
<p>Workspace apps discovered from the local apps directory and governed Data Model configuration.</p>
|
|
86
88
|
</div>
|
|
87
89
|
</div>
|
|
88
90
|
|
|
89
|
-
<
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
<SettingsAccordionGroup defaultOpenId="workspace-apps">
|
|
92
|
+
<SettingsAccordionSection
|
|
93
|
+
id="workspace-apps"
|
|
94
|
+
title="Workspace Apps"
|
|
95
|
+
summary={`${apps.length} app${apps.length === 1 ? "" : "s"} discovered from directory and config.`}
|
|
96
|
+
className="workspace-apps-list-section"
|
|
97
|
+
>
|
|
98
|
+
<AppsList apps={apps} />
|
|
99
|
+
</SettingsAccordionSection>
|
|
100
|
+
|
|
101
|
+
<SettingsAccordionSection
|
|
102
|
+
id="workspace-linkage"
|
|
103
|
+
title="Workspace Linkage"
|
|
104
|
+
summary="Fork, kit, and bridge identity for this workspace."
|
|
105
|
+
className="workspace-apps-linkage-section"
|
|
106
|
+
>
|
|
107
|
+
<div className="workspace-settings-kv">
|
|
108
|
+
<span>Workspace</span><code>{workspaceConfig.id || "workspace-builder-default"}</code>
|
|
109
|
+
<span>Fork</span><code>{fork?.forkId || "local fork metadata unavailable"}</code>
|
|
110
|
+
<span>Kit</span><code>{fork?.kitId || workspaceConfig.provenance?.mirrors || "growthub-custom-workspace-starter-v1"}</code>
|
|
111
|
+
<span>Bridge</span><code>{bridge?.status || bridge?.id || "not connected"}</code>
|
|
112
|
+
</div>
|
|
113
|
+
</SettingsAccordionSection>
|
|
98
114
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
<CodexSitesDataModelCard
|
|
116
|
+
apps={apps}
|
|
117
|
+
dataModel={workspaceConfig.dataModel || {}}
|
|
118
|
+
/>
|
|
119
|
+
</SettingsAccordionGroup>
|
|
103
120
|
</section>
|
|
104
121
|
</SettingsShell>;
|
|
105
122
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useState } from "react";
|
|
4
|
+
import { ChevronDown } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
const SettingsAccordionContext = createContext(null);
|
|
7
|
+
|
|
8
|
+
function SettingsAccordionGroup({ defaultOpenId, children }) {
|
|
9
|
+
const [openId, setOpenId] = useState(defaultOpenId || null);
|
|
10
|
+
return <SettingsAccordionContext.Provider value={{ openId, setOpenId }}>
|
|
11
|
+
{children}
|
|
12
|
+
</SettingsAccordionContext.Provider>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function SettingsAccordionSection({ id, title, summary, className = "", defaultOpen = false, children }) {
|
|
16
|
+
const group = useContext(SettingsAccordionContext);
|
|
17
|
+
const [localOpen, setLocalOpen] = useState(defaultOpen);
|
|
18
|
+
const open = group ? group.openId === id : localOpen;
|
|
19
|
+
const sectionClass = [
|
|
20
|
+
"workspace-settings-section",
|
|
21
|
+
"workspace-settings-accordion",
|
|
22
|
+
open ? "is-open" : "is-collapsed",
|
|
23
|
+
className
|
|
24
|
+
].filter(Boolean).join(" ");
|
|
25
|
+
const toggle = () => {
|
|
26
|
+
if (group) {
|
|
27
|
+
group.setOpenId(open ? null : id);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
setLocalOpen((value) => !value);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return <section className={sectionClass}>
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
className="workspace-settings-accordion-trigger"
|
|
37
|
+
aria-expanded={open}
|
|
38
|
+
onClick={toggle}
|
|
39
|
+
>
|
|
40
|
+
<span>
|
|
41
|
+
<h3>{title}</h3>
|
|
42
|
+
{summary ? <em>{summary}</em> : null}
|
|
43
|
+
</span>
|
|
44
|
+
<ChevronDown size={16} aria-hidden="true" />
|
|
45
|
+
</button>
|
|
46
|
+
{open ? <div className="workspace-settings-accordion-body">{children}</div> : null}
|
|
47
|
+
</section>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { SettingsAccordionGroup, SettingsAccordionSection };
|
|
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
6
6
|
import {
|
|
7
|
+
ArrowUpCircle,
|
|
7
8
|
Bot,
|
|
8
9
|
ChevronDown,
|
|
9
10
|
ChevronUp,
|
|
@@ -51,7 +52,31 @@ import { AgentSwarmPanel } from "../data-model/components/AgentSwarmPanel.jsx";
|
|
|
51
52
|
import { RunSetupPanel } from "./RunSetupPanel.jsx";
|
|
52
53
|
import { describeRunInputMetadataItems, discoverRunInputSchema } from "@/lib/orchestration-run-inputs";
|
|
53
54
|
import { selectWorkflowNodeInputSchema } from "@/lib/workspace-metadata-selectors";
|
|
54
|
-
import { deriveProvenance, hasConnectionId } from "@/lib/workspace-activation";
|
|
55
|
+
import { deriveProvenance, hasConnectionId, readUiCacheFlag } from "@/lib/workspace-activation";
|
|
56
|
+
import { ApiRegistryCreationCockpit } from "../data-model/components/ApiRegistryCreationCockpit.jsx";
|
|
57
|
+
import { deriveSandboxServerlessState } from "@/lib/sandbox-serverless-flow";
|
|
58
|
+
import { deriveServerlessUpgradeState, SERVERLESS_UPGRADE_DISMISS_FLAG } from "@/lib/serverless-upgrade";
|
|
59
|
+
|
|
60
|
+
// Set a flag on the governed workspace-ui-cache "activation" row (pure helper,
|
|
61
|
+
// same transform the rail/lens one-time dismisses use).
|
|
62
|
+
function withUiCacheFlag(workspaceConfig, flag, value) {
|
|
63
|
+
const dm = workspaceConfig?.dataModel && typeof workspaceConfig.dataModel === "object" ? workspaceConfig.dataModel : {};
|
|
64
|
+
const objects = Array.isArray(dm.objects) ? dm.objects : [];
|
|
65
|
+
const existing = objects.find((o) => o?.id === "workspace-ui-cache");
|
|
66
|
+
const baseRow = (existing?.rows || []).find((r) => r?.id === "activation") || { id: "activation" };
|
|
67
|
+
const nextRow = { ...baseRow, [flag]: value };
|
|
68
|
+
const nextCache = existing
|
|
69
|
+
? { ...existing, rows: [nextRow, ...(existing.rows || []).filter((r) => r?.id !== "activation")] }
|
|
70
|
+
: {
|
|
71
|
+
id: "workspace-ui-cache", label: "Workspace UI Cache", source: "Workspace UI Cache",
|
|
72
|
+
objectType: "custom", columns: ["id", flag], rows: [nextRow],
|
|
73
|
+
binding: { mode: "manual", source: "Workspace UI Cache" },
|
|
74
|
+
};
|
|
75
|
+
const nextObjects = existing
|
|
76
|
+
? objects.map((o) => (o?.id === "workspace-ui-cache" ? nextCache : o))
|
|
77
|
+
: [...objects, nextCache];
|
|
78
|
+
return { ...workspaceConfig, dataModel: { ...dm, objects: nextObjects } };
|
|
79
|
+
}
|
|
55
80
|
|
|
56
81
|
// Workspace Metadata Graph V1 — read-only dependency metadata for workflow
|
|
57
82
|
// sidecars. The runtime path (sandbox-run, publish, draft/live) is
|
|
@@ -63,20 +88,33 @@ const WORKFLOW_METADATA_SELECTORS = Object.freeze({
|
|
|
63
88
|
});
|
|
64
89
|
|
|
65
90
|
function resolveRegistryRowForSandbox(workspaceConfig, sandboxRow) {
|
|
91
|
+
return resolveRegistryRefForSandbox(workspaceConfig, sandboxRow)?.row || null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function resolveRegistryRefForSandbox(workspaceConfig, sandboxRow) {
|
|
66
95
|
const graph = parseOrchestrationGraph(sandboxRow?.orchestrationConfig || sandboxRow?.orchestrationGraph);
|
|
67
96
|
const apiNode = graph?.nodes?.find((n) => n?.type === "api-registry-call");
|
|
68
97
|
const registryId = String(
|
|
69
98
|
apiNode?.config?.registryId || apiNode?.config?.integrationId || sandboxRow?.schedulerRegistryId || ""
|
|
70
99
|
).trim();
|
|
71
|
-
if (!
|
|
100
|
+
if (!workspaceConfig) return null;
|
|
72
101
|
const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
|
|
102
|
+
let firstRegistryRow = null;
|
|
103
|
+
let firstRegistryObject = null;
|
|
73
104
|
for (const objectItem of objects) {
|
|
74
105
|
if (objectItem?.objectType !== "api-registry") continue;
|
|
75
106
|
const rows = Array.isArray(objectItem.rows) ? objectItem.rows : [];
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
107
|
+
const firstRow = rows.find((r) => String(r?.integrationId || "").trim());
|
|
108
|
+
if (!firstRegistryRow && firstRow) {
|
|
109
|
+
firstRegistryRow = firstRow;
|
|
110
|
+
firstRegistryObject = objectItem;
|
|
111
|
+
}
|
|
112
|
+
if (registryId) {
|
|
113
|
+
const match = rows.find((r) => String(r?.integrationId || "").trim() === registryId);
|
|
114
|
+
if (match) return { object: objectItem, row: match };
|
|
115
|
+
}
|
|
78
116
|
}
|
|
79
|
-
return null;
|
|
117
|
+
return firstRegistryRow ? { object: firstRegistryObject, row: firstRegistryRow } : null;
|
|
80
118
|
}
|
|
81
119
|
|
|
82
120
|
function patchSandboxRowInConfig(workspaceConfig, objectId, rowIndex, fields) {
|
|
@@ -349,6 +387,23 @@ export default function WorkflowSurface() {
|
|
|
349
387
|
const [orchestrationGraph, setOrchestrationGraph] = useState(null);
|
|
350
388
|
const [dirty, setDirty] = useState(false);
|
|
351
389
|
const [runSetupOpen, setRunSetupOpen] = useState(false);
|
|
390
|
+
const [upgradeOpen, setUpgradeOpen] = useState(false);
|
|
391
|
+
const [serverlessSignals, setServerlessSignals] = useState({ configuredEnvRefs: [], persistenceAdapters: [] });
|
|
392
|
+
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
let cancelled = false;
|
|
395
|
+
fetch("/api/workspace/env-status", { cache: "no-store" })
|
|
396
|
+
.then((res) => (res.ok ? res.json() : {}))
|
|
397
|
+
.then((payload) => {
|
|
398
|
+
if (cancelled) return;
|
|
399
|
+
setServerlessSignals({
|
|
400
|
+
configuredEnvRefs: Array.isArray(payload.configuredEnvRefs) ? payload.configuredEnvRefs : [],
|
|
401
|
+
persistenceAdapters: Array.isArray(payload.persistenceAdapters) ? payload.persistenceAdapters : [],
|
|
402
|
+
});
|
|
403
|
+
})
|
|
404
|
+
.catch(() => {});
|
|
405
|
+
return () => { cancelled = true; };
|
|
406
|
+
}, [objectId, rowId]);
|
|
352
407
|
|
|
353
408
|
const load = useCallback(async () => {
|
|
354
409
|
setLoading(true);
|
|
@@ -807,6 +862,73 @@ export default function WorkflowSurface() {
|
|
|
807
862
|
const showSaveDraft = dirty && !graphUnset;
|
|
808
863
|
const workflowModeLabel = isDraftMode ? "draft" : lifecycle || "live";
|
|
809
864
|
|
|
865
|
+
// Serverless upgrade — same derivation + cockpit as the sandbox/API lanes.
|
|
866
|
+
const upgradeState = deriveServerlessUpgradeState(workspaceConfig || {}, {
|
|
867
|
+
dismissed: readUiCacheFlag(workspaceConfig || {}, SERVERLESS_UPGRADE_DISMISS_FLAG),
|
|
868
|
+
});
|
|
869
|
+
const serverlessState = sandboxRow
|
|
870
|
+
? deriveSandboxServerlessState({
|
|
871
|
+
sandboxRow,
|
|
872
|
+
workspaceConfig: workspaceConfig || {},
|
|
873
|
+
configuredEnvRefs: serverlessSignals.configuredEnvRefs,
|
|
874
|
+
persistenceAdapters: serverlessSignals.persistenceAdapters,
|
|
875
|
+
})
|
|
876
|
+
: null;
|
|
877
|
+
const isServerlessWorkflow = Boolean(serverlessState?.isServerless);
|
|
878
|
+
|
|
879
|
+
async function patchSandboxAndPersist(fields) {
|
|
880
|
+
if (resolved.rowIndex < 0 || !objectId || !workspaceConfig) return;
|
|
881
|
+
try {
|
|
882
|
+
const next = patchSandboxRowInConfig(workspaceConfig, objectId, resolved.rowIndex, fields);
|
|
883
|
+
await persistWorkspace(next);
|
|
884
|
+
setSaveMessage(fields.runLocality === "serverless"
|
|
885
|
+
? "Upgraded to serverless. Link a scheduler and configure persistence to close the loop."
|
|
886
|
+
: fields.runLocality === "local"
|
|
887
|
+
? "Reverted to local execution."
|
|
888
|
+
: "Saved.");
|
|
889
|
+
} catch (err) {
|
|
890
|
+
setSaveMessage(err.message || "Failed to save");
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
async function handleUpgradeAction(action) {
|
|
895
|
+
if (!action) return;
|
|
896
|
+
if (action.id === "toggle-locality") {
|
|
897
|
+
if (isServerlessWorkflow) {
|
|
898
|
+
await patchSandboxAndPersist({ runLocality: "local" });
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
const registryRow = resolveRegistryRowForSandbox(workspaceConfig, sandboxRow);
|
|
902
|
+
const adapterId = String(sandboxRow?.adapter || "").trim();
|
|
903
|
+
await patchSandboxAndPersist({
|
|
904
|
+
runLocality: "serverless",
|
|
905
|
+
schedulerRegistryId: String(registryRow?.integrationId || "").trim(),
|
|
906
|
+
adapter: ["local-agent-host", "local-intelligence"].includes(adapterId) ? "local-process" : adapterId,
|
|
907
|
+
});
|
|
908
|
+
} else if (action.id === "open-settings") {
|
|
909
|
+
router.push(action.href || "/settings");
|
|
910
|
+
} else if (action.id === "link-scheduler") {
|
|
911
|
+
const registryRef = resolveRegistryRefForSandbox(workspaceConfig, sandboxRow);
|
|
912
|
+
if (registryRef?.object?.id && registryRef?.row?.integrationId) {
|
|
913
|
+
router.push(`/data-model?object=${encodeURIComponent(registryRef.object.id)}&row=${encodeURIComponent(registryRef.row.integrationId)}`);
|
|
914
|
+
} else {
|
|
915
|
+
router.push(`/data-model?object=${encodeURIComponent(objectId)}&row=${encodeURIComponent(rowId)}`);
|
|
916
|
+
}
|
|
917
|
+
} else if (action.id === "edit-adapter") {
|
|
918
|
+
// Full scheduler/adapter config lives on the sandbox object's drawer.
|
|
919
|
+
router.push(`/data-model?object=${encodeURIComponent(objectId)}&row=${encodeURIComponent(rowId)}`);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
async function dismissUpgradeOnboarding() {
|
|
924
|
+
if (!workspaceConfig) return;
|
|
925
|
+
try {
|
|
926
|
+
await persistWorkspace(withUiCacheFlag(workspaceConfig, SERVERLESS_UPGRADE_DISMISS_FLAG, true));
|
|
927
|
+
} catch {
|
|
928
|
+
/* non-fatal */
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
810
932
|
return (
|
|
811
933
|
<main className="workspace-builder dm-workflow-page">
|
|
812
934
|
<WorkspaceRail
|
|
@@ -854,6 +976,18 @@ export default function WorkflowSurface() {
|
|
|
854
976
|
>
|
|
855
977
|
<ChevronUp size={14} />
|
|
856
978
|
</button>
|
|
979
|
+
{sandboxRow && (
|
|
980
|
+
<button
|
|
981
|
+
type="button"
|
|
982
|
+
className={"dm-workflow-icon-btn dm-workflow-upgrade-btn" + (isServerlessWorkflow ? " is-serverless" : (upgradeState.showOnboarding ? " is-pulse" : ""))}
|
|
983
|
+
aria-label={isServerlessWorkflow ? "Serverless workflow — review persistence & scheduling" : "Upgrade to serverless environment to ensure persistence"}
|
|
984
|
+
data-tooltip={isServerlessWorkflow ? "Serverless — review persistence & scheduling" : "Upgrade to serverless environment to ensure persistence"}
|
|
985
|
+
aria-pressed={upgradeOpen}
|
|
986
|
+
onClick={() => setUpgradeOpen((open) => !open)}
|
|
987
|
+
>
|
|
988
|
+
<ArrowUpCircle size={14} />
|
|
989
|
+
</button>
|
|
990
|
+
)}
|
|
857
991
|
{showDiscardDraft && (
|
|
858
992
|
<button
|
|
859
993
|
type="button"
|
|
@@ -920,6 +1054,43 @@ export default function WorkflowSurface() {
|
|
|
920
1054
|
</div>
|
|
921
1055
|
) : null}
|
|
922
1056
|
|
|
1057
|
+
{/* One-time serverless upgrade onboarding — shows only when the operator
|
|
1058
|
+
has workflows but none are serverless, and hasn't dismissed it. */}
|
|
1059
|
+
{sandboxRow && !upgradeOpen && upgradeState.showOnboarding ? (
|
|
1060
|
+
<div className="workspace-template-context-banner dm-workflow-upgrade-nudge" role="note">
|
|
1061
|
+
<div>
|
|
1062
|
+
<strong>{upgradeState.headline}</strong>
|
|
1063
|
+
<span style={{ display: "block", marginTop: 2 }}>{upgradeState.subheadline}</span>
|
|
1064
|
+
</div>
|
|
1065
|
+
<div className="dm-workflow-upgrade-nudge-actions">
|
|
1066
|
+
<button type="button" className="dm-btn-primary-sm" onClick={() => setUpgradeOpen(true)}>
|
|
1067
|
+
<ArrowUpCircle size={13} /> Upgrade this workflow
|
|
1068
|
+
</button>
|
|
1069
|
+
<button type="button" className="dm-btn-ghost" onClick={dismissUpgradeOnboarding}>Not now</button>
|
|
1070
|
+
</div>
|
|
1071
|
+
</div>
|
|
1072
|
+
) : null}
|
|
1073
|
+
|
|
1074
|
+
{/* Serverless cockpit — same derivation + cockpit interface as the API
|
|
1075
|
+
Registry and sandbox lanes. Toggles patch the sandbox row; deep config
|
|
1076
|
+
(scheduler/adapter) routes to the object's Data Model drawer. */}
|
|
1077
|
+
{sandboxRow && upgradeOpen && serverlessState ? (
|
|
1078
|
+
<div className="dm-workflow-upgrade-panel">
|
|
1079
|
+
<div className="dm-workflow-upgrade-panel-head">
|
|
1080
|
+
<span className="dm-api-action-card-eyebrow">Persistence & scheduling</span>
|
|
1081
|
+
<button type="button" className="dm-workflow-icon-btn" aria-label="Close upgrade panel" onClick={() => { setUpgradeOpen(false); dismissUpgradeOnboarding(); }}>
|
|
1082
|
+
<X size={14} />
|
|
1083
|
+
</button>
|
|
1084
|
+
</div>
|
|
1085
|
+
<ApiRegistryCreationCockpit
|
|
1086
|
+
state={serverlessState}
|
|
1087
|
+
onAction={handleUpgradeAction}
|
|
1088
|
+
disabled={saving || publishing || running}
|
|
1089
|
+
eyebrow={isServerlessWorkflow ? "Serverless workflow" : "Upgrade to serverless"}
|
|
1090
|
+
/>
|
|
1091
|
+
</div>
|
|
1092
|
+
) : null}
|
|
1093
|
+
|
|
923
1094
|
{loading ? (
|
|
924
1095
|
<p className="dm-workflow-empty">Loading workflow…</p>
|
|
925
1096
|
) : error ? (
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
Plus,
|
|
46
46
|
Quote,
|
|
47
47
|
RefreshCw,
|
|
48
|
+
Rocket,
|
|
48
49
|
Rows3,
|
|
49
50
|
Save,
|
|
50
51
|
Search,
|
|
@@ -89,6 +90,11 @@ import {
|
|
|
89
90
|
} from "@/lib/workspace-chart-values";
|
|
90
91
|
import { selectObjectFilterableFields, selectObjectSortableFields } from "@/lib/workspace-metadata-selectors";
|
|
91
92
|
import { deriveWorkspaceActivationState } from "@/lib/workspace-activation";
|
|
93
|
+
import {
|
|
94
|
+
CODEX_SITES_OBJECT_ID,
|
|
95
|
+
ensureCodexSitesDataModel,
|
|
96
|
+
isCodexSiteUrl
|
|
97
|
+
} from "@/lib/codex-sites-workspace-adapter";
|
|
92
98
|
import { HelperSidecar } from "./data-model/components/HelperSidecar.jsx";
|
|
93
99
|
import { WorkspaceRail } from "./workspace-rail.jsx";
|
|
94
100
|
import { WorkspaceActivationPanel } from "./components/WorkspaceActivationPanel.jsx";
|
|
@@ -503,6 +509,34 @@ function listBuilderWorkflowItems(config) {
|
|
|
503
509
|
});
|
|
504
510
|
}
|
|
505
511
|
|
|
512
|
+
function listBuilderSiteItems(config) {
|
|
513
|
+
const object = getDataModelObject(config, CODEX_SITES_OBJECT_ID);
|
|
514
|
+
const rows = Array.isArray(object?.rows) ? object.rows : [];
|
|
515
|
+
return rows.flatMap((row, index) => {
|
|
516
|
+
const url = String(row?.url || "").trim();
|
|
517
|
+
if (!isCodexSiteUrl(url)) return [];
|
|
518
|
+
const title = String(row?.Name || row?.name || `Codex Site ${index + 1}`).trim();
|
|
519
|
+
const rawStatus = String(row?.status || "").trim().toLowerCase();
|
|
520
|
+
const status = rawStatus === "active" ? "live" : rawStatus || "draft";
|
|
521
|
+
return [{
|
|
522
|
+
type: "site",
|
|
523
|
+
id: String(row?.id || row?.Name || `codex-site-${index + 1}`),
|
|
524
|
+
title,
|
|
525
|
+
itemKind: "Site",
|
|
526
|
+
updatedAt: formatBuilderTimestamp(row?.lastRecordedAt || ""),
|
|
527
|
+
status,
|
|
528
|
+
site: {
|
|
529
|
+
row,
|
|
530
|
+
rowIndex: index,
|
|
531
|
+
title,
|
|
532
|
+
url,
|
|
533
|
+
app: String(row?.app || "apps/workspace").trim(),
|
|
534
|
+
client: String(row?.client || "Workspace").trim()
|
|
535
|
+
}
|
|
536
|
+
}];
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
506
540
|
function formatBuilderTimestamp(value) {
|
|
507
541
|
const raw = String(value || "").trim();
|
|
508
542
|
if (!raw || raw === "new") return raw;
|
|
@@ -4246,6 +4280,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
4246
4280
|
const canvas = config.canvas;
|
|
4247
4281
|
const dashboards = config.dashboards || [];
|
|
4248
4282
|
const workflows = useMemo(() => listBuilderWorkflowItems(config), [config]);
|
|
4283
|
+
const sites = useMemo(() => listBuilderSiteItems(config), [config]);
|
|
4249
4284
|
const builderItems = useMemo(() => {
|
|
4250
4285
|
const dashboardItems = dashboards.map((dashboard, index) => ({
|
|
4251
4286
|
type: "dashboard",
|
|
@@ -4266,13 +4301,17 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
4266
4301
|
status: workflow.lifecycleStatus || "draft",
|
|
4267
4302
|
workflow
|
|
4268
4303
|
}));
|
|
4304
|
+
const siteItems = sites.map((site) => ({
|
|
4305
|
+
...site,
|
|
4306
|
+
updatedAt: site.updatedAt || "new"
|
|
4307
|
+
}));
|
|
4269
4308
|
const q = builderListFilter.query.trim().toLowerCase();
|
|
4270
|
-
return [...dashboardItems, ...workflowItems].filter((item) => {
|
|
4309
|
+
return [...dashboardItems, ...siteItems, ...workflowItems].filter((item) => {
|
|
4271
4310
|
if (builderListFilter.type !== "all" && item.type !== builderListFilter.type) return false;
|
|
4272
4311
|
if (!q) return true;
|
|
4273
|
-
return [item.title, item.itemKind, item.status, item.type].some((part) => String(part || "").toLowerCase().includes(q));
|
|
4312
|
+
return [item.title, item.itemKind, item.status, item.type, item.site?.client, item.site?.app, item.site?.url].some((part) => String(part || "").toLowerCase().includes(q));
|
|
4274
4313
|
});
|
|
4275
|
-
}, [builderListFilter, dashboards, workflows]);
|
|
4314
|
+
}, [builderListFilter, dashboards, sites, workflows]);
|
|
4276
4315
|
const resolvedActiveDashboardId = getActiveDashboardId(dashboards, activeDashboardId);
|
|
4277
4316
|
const resolvedActiveDashboardIndex = activeDashboardIndex(dashboards, resolvedActiveDashboardId);
|
|
4278
4317
|
const widgetTypes = config.widgetTypes;
|
|
@@ -4709,6 +4748,50 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
4709
4748
|
}
|
|
4710
4749
|
}, [config, saving]);
|
|
4711
4750
|
|
|
4751
|
+
const createCodexSite = useCallback(async () => {
|
|
4752
|
+
if (saving) return;
|
|
4753
|
+
const objects = Array.isArray(config.dataModel?.objects) ? config.dataModel.objects : [];
|
|
4754
|
+
const hasCodexSitesObject = objects.some((object) => object?.id === CODEX_SITES_OBJECT_ID);
|
|
4755
|
+
if (hasCodexSitesObject) {
|
|
4756
|
+
window.open(`/data-model?object=${encodeURIComponent(CODEX_SITES_OBJECT_ID)}`, "_self");
|
|
4757
|
+
return;
|
|
4758
|
+
}
|
|
4759
|
+
const nextDataModel = ensureCodexSitesDataModel(config.dataModel, []);
|
|
4760
|
+
setSaving(true);
|
|
4761
|
+
try {
|
|
4762
|
+
const response = await fetch("/api/workspace", {
|
|
4763
|
+
method: "PATCH",
|
|
4764
|
+
headers: { "content-type": "application/json" },
|
|
4765
|
+
body: JSON.stringify({ dataModel: nextDataModel })
|
|
4766
|
+
});
|
|
4767
|
+
const payload = await response.json();
|
|
4768
|
+
if (!response.ok || !payload.workspaceConfig) {
|
|
4769
|
+
throw new Error(payload.error || "Failed to create Codex Sites object");
|
|
4770
|
+
}
|
|
4771
|
+
setConfig((prev) => ({ ...prev, dataModel: payload.workspaceConfig.dataModel }));
|
|
4772
|
+
setBuilderListFilter({ type: "site", query: "" });
|
|
4773
|
+
setConfigMessage("Created Codex Sites object");
|
|
4774
|
+
window.open(`/data-model?object=${encodeURIComponent(CODEX_SITES_OBJECT_ID)}`, "_self");
|
|
4775
|
+
} catch (error) {
|
|
4776
|
+
setConfigMessage(error.message || "Failed to create Codex Sites object");
|
|
4777
|
+
} finally {
|
|
4778
|
+
setSaving(false);
|
|
4779
|
+
}
|
|
4780
|
+
}, [config, saving]);
|
|
4781
|
+
|
|
4782
|
+
const manageSite = useCallback((site) => {
|
|
4783
|
+
const rowParam = site?.rowIndex !== undefined ? `&row=${encodeURIComponent(String(site.rowIndex))}` : "";
|
|
4784
|
+
window.open(`/data-model?object=${encodeURIComponent(CODEX_SITES_OBJECT_ID)}${rowParam}`, "_self");
|
|
4785
|
+
}, []);
|
|
4786
|
+
|
|
4787
|
+
const openSite = useCallback((site) => {
|
|
4788
|
+
if (site?.url) {
|
|
4789
|
+
window.open(site.url, "_blank", "noopener,noreferrer");
|
|
4790
|
+
return;
|
|
4791
|
+
}
|
|
4792
|
+
manageSite(site);
|
|
4793
|
+
}, [manageSite]);
|
|
4794
|
+
|
|
4712
4795
|
const selectDashboard = useCallback((index) => {
|
|
4713
4796
|
setConfig((prev) => {
|
|
4714
4797
|
const synced = syncActiveDashboard(prev, activeDashboardId);
|
|
@@ -5234,7 +5317,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5234
5317
|
}
|
|
5235
5318
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
5236
5319
|
const menuWidth = 148;
|
|
5237
|
-
const menuHeight = item?.type === "dashboard" ? 136 : 76;
|
|
5320
|
+
const menuHeight = item?.type === "dashboard" ? 136 : item?.type === "site" ? 108 : 76;
|
|
5238
5321
|
const margin = 8;
|
|
5239
5322
|
const left = Math.min(
|
|
5240
5323
|
Math.max(margin, rect.right - menuWidth),
|
|
@@ -5916,6 +5999,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5916
5999
|
</span>
|
|
5917
6000
|
) : null}
|
|
5918
6001
|
<button type="button" onClick={addDashboard}><Plus size={15} />New Dashboard</button>
|
|
6002
|
+
<button type="button" onClick={createCodexSite} disabled={saving}><Rocket size={15} />New Codex Site</button>
|
|
5919
6003
|
<button type="button" onClick={createWorkflow} disabled={saving}><GitBranch size={15} />New Workflow</button>
|
|
5920
6004
|
<button type="button" onClick={() => importInputRef.current?.click()}><Import size={15} />Import</button>
|
|
5921
6005
|
</div>}
|
|
@@ -5952,13 +6036,14 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
5952
6036
|
<section className="workspace-table" id="dashboards" aria-label="Builder">
|
|
5953
6037
|
<div className="workspace-table-heading">
|
|
5954
6038
|
<strong>Builder</strong>
|
|
5955
|
-
<span>{dashboards.length} dashboard{dashboards.length === 1 ? "" : "s"} · {workflows.length} workflow{workflows.length === 1 ? "" : "s"}</span>
|
|
6039
|
+
<span>{dashboards.length} dashboard{dashboards.length === 1 ? "" : "s"} · {sites.length} site{sites.length === 1 ? "" : "s"} · {workflows.length} workflow{workflows.length === 1 ? "" : "s"}</span>
|
|
5956
6040
|
</div>
|
|
5957
6041
|
<div className="workspace-builder-filterbar">
|
|
5958
6042
|
<div className="workspace-builder-filterbar__segments" role="group" aria-label="Builder item type">
|
|
5959
6043
|
{[
|
|
5960
6044
|
["all", "All"],
|
|
5961
6045
|
["dashboard", "Dashboards"],
|
|
6046
|
+
["site", "Sites"],
|
|
5962
6047
|
["workflow", "Workflows"]
|
|
5963
6048
|
].map(([type, label]) => (
|
|
5964
6049
|
<button
|
|
@@ -6052,6 +6137,53 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
|
|
|
6052
6137
|
</span>
|
|
6053
6138
|
)}
|
|
6054
6139
|
</span>
|
|
6140
|
+
</div> : item.type === "site" ? <div className="workspace-table-row" key={item.id}>
|
|
6141
|
+
<span className="workspace-dashboard-title">
|
|
6142
|
+
{item.site.url ? (
|
|
6143
|
+
<a href={item.site.url} target="_blank" rel="noreferrer">{item.title}</a>
|
|
6144
|
+
) : (
|
|
6145
|
+
<button
|
|
6146
|
+
className="active"
|
|
6147
|
+
onClick={() => manageSite(item.site)}
|
|
6148
|
+
type="button"
|
|
6149
|
+
>{item.title}</button>
|
|
6150
|
+
)}
|
|
6151
|
+
</span>
|
|
6152
|
+
<span>{item.itemKind}</span>
|
|
6153
|
+
<span>{item.updatedAt}</span>
|
|
6154
|
+
<span>
|
|
6155
|
+
<select
|
|
6156
|
+
aria-label={`Status for ${item.title}`}
|
|
6157
|
+
value={item.status || "draft"}
|
|
6158
|
+
disabled
|
|
6159
|
+
>
|
|
6160
|
+
<option value="draft">draft</option>
|
|
6161
|
+
<option value="review">review</option>
|
|
6162
|
+
<option value="live">live</option>
|
|
6163
|
+
<option value="paused">paused</option>
|
|
6164
|
+
</select>
|
|
6165
|
+
</span>
|
|
6166
|
+
<span className="workspace-dashboard-actions">
|
|
6167
|
+
<button
|
|
6168
|
+
type="button"
|
|
6169
|
+
className="workspace-row-action-trigger"
|
|
6170
|
+
aria-label={`Actions for ${item.title}`}
|
|
6171
|
+
onClick={(event) => openBuilderActionMenu(item, event)}
|
|
6172
|
+
>
|
|
6173
|
+
<MoreVertical size={16} aria-hidden="true" />
|
|
6174
|
+
</button>
|
|
6175
|
+
{builderActionMenuId === item.id && (
|
|
6176
|
+
<span className="workspace-row-action-menu" style={builderActionMenuPlacement || undefined}>
|
|
6177
|
+
{item.site.url ? (
|
|
6178
|
+
<a href={item.site.url} target="_blank" rel="noreferrer" onClick={closeBuilderActionMenu}>Open URL</a>
|
|
6179
|
+
) : (
|
|
6180
|
+
<button type="button" disabled>Open URL</button>
|
|
6181
|
+
)}
|
|
6182
|
+
<button type="button" onClick={() => { closeBuilderActionMenu(); manageSite(item.site); }}>Manage</button>
|
|
6183
|
+
<button type="button" onClick={() => { closeBuilderActionMenu(); window.open("/settings/apps", "_self"); }}>Apps</button>
|
|
6184
|
+
</span>
|
|
6185
|
+
)}
|
|
6186
|
+
</span>
|
|
6055
6187
|
</div> : <div className="workspace-table-row" key={item.id}>
|
|
6056
6188
|
<span className="workspace-dashboard-title">
|
|
6057
6189
|
{editingWorkflowId === item.workflow.id ? <span className="workspace-dashboard-title-editor">
|
|
@@ -28,11 +28,9 @@ import path from "node:path";
|
|
|
28
28
|
import { pathToFileURL } from "node:url";
|
|
29
29
|
|
|
30
30
|
const staticLoaded = new Set();
|
|
31
|
-
|
|
31
|
+
const nativeImport = new Function("specifier", "return import(specifier)");
|
|
32
32
|
|
|
33
33
|
async function loadStaticResolversOnce() {
|
|
34
|
-
if (staticLoadDone) return;
|
|
35
|
-
staticLoadDone = true;
|
|
36
34
|
const resolversDir = path.resolve(/*turbopackIgnore: true*/ process.cwd(), "lib/adapters/integrations/resolvers");
|
|
37
35
|
try {
|
|
38
36
|
const entries = await fs.readdir(resolversDir);
|
|
@@ -42,7 +40,7 @@ async function loadStaticResolversOnce() {
|
|
|
42
40
|
if (staticLoaded.has(file)) return;
|
|
43
41
|
try {
|
|
44
42
|
const absolutePath = path.join(resolversDir, file);
|
|
45
|
-
await
|
|
43
|
+
await nativeImport(pathToFileURL(absolutePath).href);
|
|
46
44
|
staticLoaded.add(file);
|
|
47
45
|
} catch {
|
|
48
46
|
// Malformed resolver — skip silently; operator needs to fix the file
|