@growthub/cli 0.14.10 → 0.14.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/[providerId]/callback/route.js +35 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/[providerId]/failure/route.js +35 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/[providerId]/schedule/route.js +423 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/connect/route.js +78 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/credentials/route.js +276 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/products/[productId]/resources/route.js +173 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/products/sync/route.js +347 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/sync/route.js +293 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/upstash/provider/connect/route.js +7 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/upstash/provider/sync/route.js +7 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/upstash/sync/route.js +197 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/apps/route.js +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +1 -49
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +3 -20
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-api-record/route.js +3 -20
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/workflow/publish/route.js +407 -290
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/workflows/[providerId]/route.js +209 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceAddOnsMarketplace.jsx +806 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryActionCard.jsx +141 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/CeoCockpit.jsx +15 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +42 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphCanvas.jsx +5 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +86 -20
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ScheduleCockpit.jsx +363 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +8 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +322 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/add-ons/add-ons-client.jsx +197 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/add-ons/page.jsx +23 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/settings-shell.jsx +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +734 -61
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +15 -10
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/env-status.js +2 -7
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +2 -19
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-serverless-flow.js +8 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/schedule-cockpit-console.js +287 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/scheduler-orchestration.js +449 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-secrets.js +77 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/serverless-readiness.js +583 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-add-on-callback.js +63 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-add-on-scheduler.js +519 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-add-ons.js +957 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-config.js +607 -63
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +21 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-operator-auth.js +32 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/provider.png +0 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/qstash.png +0 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/redis.png +0 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/search.png +0 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/vector.png +0 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/scripts/scheduler-ingress-smoke.mjs +26 -0
- package/package.json +1 -1
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ScheduleCockpit — the governed "/schedule" operations surface inside the
|
|
5
|
+
* Workspace Helper sidecar (GOVERNED_COCKPIT_ENTRY_POINT_PATTERN_V1, same
|
|
6
|
+
* primitive class as CeoCockpit).
|
|
7
|
+
*
|
|
8
|
+
* workspace schedule cockpit =
|
|
9
|
+
* pure inventory (deriveScheduleCockpit)
|
|
10
|
+
* + causation-derived readiness (scanServerlessReadiness, via the deriver)
|
|
11
|
+
* + governed action buttons over the EXISTING schedule routes
|
|
12
|
+
*
|
|
13
|
+
* One universe, same workspace truth, new command entry path. The command is not
|
|
14
|
+
* the feature — it is the canonical entry into the governed schedule universe.
|
|
15
|
+
*
|
|
16
|
+
* Read-only with respect to config: every action hands off to an existing
|
|
17
|
+
* governed schedule route (install/pause/resume/readiness/uninstall) or the
|
|
18
|
+
* Add-ons marketplace setup path. No client-side dataModel PATCH, no new route,
|
|
19
|
+
* no new object type, no second compatibility check, no new visual grammar
|
|
20
|
+
* beyond the existing dm-* primitives.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
24
|
+
import { ArrowUpRight, Search } from "lucide-react";
|
|
25
|
+
import { deriveScheduleCockpit } from "@/lib/schedule-cockpit-console";
|
|
26
|
+
|
|
27
|
+
const STATE_VARIANT = {
|
|
28
|
+
scheduled: "ok",
|
|
29
|
+
paused: "pending",
|
|
30
|
+
ready: "active",
|
|
31
|
+
blocked: "fail",
|
|
32
|
+
drifted: "canceled",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const SETUP_ROUTE = "/settings/add-ons";
|
|
36
|
+
|
|
37
|
+
function openWorkflowCanvas(card) {
|
|
38
|
+
if (!card?.objectId || !card?.name) return;
|
|
39
|
+
const params = new URLSearchParams({ object: card.objectId, row: card.name, field: "orchestrationConfig" });
|
|
40
|
+
window.location.assign(`/workflows?${params.toString()}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function matchesFilter(card, filter) {
|
|
44
|
+
switch (filter) {
|
|
45
|
+
case "all": return true;
|
|
46
|
+
case "scheduled": return card.state === "scheduled" || card.state === "paused";
|
|
47
|
+
case "ready": return card.state === "ready";
|
|
48
|
+
case "blocked": return card.state === "blocked" || card.state === "drifted";
|
|
49
|
+
case "local": return card.locality === "local";
|
|
50
|
+
case "missing-secret": return (card.readiness.deltaTags || []).includes("missing-server-secret");
|
|
51
|
+
case "qstash": return card.provider === "QStash";
|
|
52
|
+
case "custom": return card.provider === "Custom";
|
|
53
|
+
default: return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Bound the rendered list so a workspace with hundreds of workflows stays a
|
|
58
|
+
// tidy, scrollable list. The attention pick shows above; overflow is disclosed.
|
|
59
|
+
const SCHEDULE_VISIBLE_CAP = 60;
|
|
60
|
+
|
|
61
|
+
function ScheduleCard({ card, onAction, busy, onOpen, onSeed }) {
|
|
62
|
+
const action = card.nextAction;
|
|
63
|
+
const needsAgentUpgrade = (card.readiness.deltaTags || []).includes("local-agent-upgrade-required");
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
className="dm-helper-toolcall dm-swarm-card"
|
|
67
|
+
data-schedule-card={card.name}
|
|
68
|
+
data-schedule-state={card.state}
|
|
69
|
+
>
|
|
70
|
+
<div className="dm-swarm-card-head">
|
|
71
|
+
<span className="dm-run-console__tree-dot" data-variant={STATE_VARIANT[card.state] || "pending"} />
|
|
72
|
+
<span className="dm-helper-toolcall-title dm-swarm-card-title">{card.name}</span>
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
className="dm-btn-ghost dm-swarm-card-action"
|
|
76
|
+
onClick={() => onOpen(card)}
|
|
77
|
+
aria-label={`Open workflow canvas: ${card.name}`}
|
|
78
|
+
title="Open workflow"
|
|
79
|
+
>
|
|
80
|
+
<ArrowUpRight size={12} aria-hidden="true" />
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div className="dm-swarm-card-meta">
|
|
85
|
+
<span className="dm-run-console__hint dm-swarm-card-kind">{card.locality === "serverless" ? "Serverless" : "Local"}</span>
|
|
86
|
+
{card.provider && <span className="dm-run-console__hint">{card.provider}</span>}
|
|
87
|
+
{card.cron && <span className="dm-run-console__hint">{card.cron}</span>}
|
|
88
|
+
{card.region && <span className="dm-run-console__hint">{card.region}</span>}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{card.scheduleId && (
|
|
92
|
+
<div className="dm-swarm-card-meta">
|
|
93
|
+
<span className="dm-run-console__hint" title="Installed schedule id">{card.scheduleId}</span>
|
|
94
|
+
{card.lastRunStatus && (
|
|
95
|
+
<span className="dm-run-console__hint">{`Last run ${card.lastRunStatus}`}</span>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
{card.tags.length > 0 && (
|
|
101
|
+
<div className="dm-schedule-tags">
|
|
102
|
+
{card.tags.map((tag) => (
|
|
103
|
+
<span
|
|
104
|
+
key={tag}
|
|
105
|
+
className="dm-schedule-tag"
|
|
106
|
+
data-tag-tone={
|
|
107
|
+
["Blocked", "Serverless drift", "Missing secret", "Local agent upgrade required", "Last run failed"].includes(tag)
|
|
108
|
+
? "alert"
|
|
109
|
+
: tag === "Scheduled" ? "ok" : "neutral"
|
|
110
|
+
}
|
|
111
|
+
>
|
|
112
|
+
{tag}
|
|
113
|
+
</span>
|
|
114
|
+
))}
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
{!card.readiness.ok && card.readiness.helperActions.length > 0 && (
|
|
119
|
+
<div className="dm-helper-stream dm-swarm-card-desc">{card.readiness.helperActions[0]}</div>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
<div className="dm-schedule-card-actions">
|
|
123
|
+
{/* Primary state action */}
|
|
124
|
+
{action.kind === "setup-provider" ? (
|
|
125
|
+
<button type="button" className="dm-btn-ghost" onClick={() => window.location.assign(SETUP_ROUTE)} disabled={busy}>
|
|
126
|
+
{action.label}
|
|
127
|
+
</button>
|
|
128
|
+
) : action.kind === "schedule" || action.kind === "manage" ? (
|
|
129
|
+
<button type="button" className="dm-btn-ghost" onClick={() => onOpen(card)} disabled={busy}>
|
|
130
|
+
{action.label}
|
|
131
|
+
</button>
|
|
132
|
+
) : (
|
|
133
|
+
<button type="button" className="dm-btn-ghost" onClick={() => onAction(action.kind, card)} disabled={busy}>
|
|
134
|
+
{action.label}
|
|
135
|
+
</button>
|
|
136
|
+
)}
|
|
137
|
+
{/* Always-available readiness rescan */}
|
|
138
|
+
<button type="button" className="dm-btn-ghost" onClick={() => onAction("readiness", card)} disabled={busy} title="Run readiness scan">
|
|
139
|
+
Readiness
|
|
140
|
+
</button>
|
|
141
|
+
{/* Lifecycle controls for an installed schedule */}
|
|
142
|
+
{card.scheduleId && card.state !== "paused" && (
|
|
143
|
+
<button type="button" className="dm-btn-ghost" onClick={() => onAction("pause", card)} disabled={busy}>Pause</button>
|
|
144
|
+
)}
|
|
145
|
+
{card.scheduleId && (
|
|
146
|
+
<button type="button" className="dm-btn-ghost" onClick={() => onAction("uninstall", card)} disabled={busy} title="Uninstall & downgrade to local">Downgrade</button>
|
|
147
|
+
)}
|
|
148
|
+
{/* A local-agent node can't run serverless — seed a governed upgrade
|
|
149
|
+
proposal (/swarm or API-backed runtime) rather than scheduling it. */}
|
|
150
|
+
{needsAgentUpgrade && typeof onSeed === "function" && (
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
className="dm-btn-ghost"
|
|
154
|
+
onClick={() => onSeed(`Upgrade the local-agent node(s) in workflow "${card.name}" to an API-backed agent/runtime so it can run serverless:`)}
|
|
155
|
+
disabled={busy}
|
|
156
|
+
title="Seed a governed agent-upgrade proposal"
|
|
157
|
+
>
|
|
158
|
+
Upgrade agent
|
|
159
|
+
</button>
|
|
160
|
+
)}
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function ScheduleCockpit({ workspaceConfig, focus, onConfigRefresh, onOpenArtifact, onSeedSwarm, onOpenSetup }) {
|
|
167
|
+
const [configuredEnvRefs, setConfiguredEnvRefs] = useState([]);
|
|
168
|
+
const [receipts, setReceipts] = useState([]);
|
|
169
|
+
const [activeFilter, setActiveFilter] = useState("all");
|
|
170
|
+
const [search, setSearch] = useState(focus?.name ? String(focus.name) : "");
|
|
171
|
+
const [busyCard, setBusyCard] = useState("");
|
|
172
|
+
const [error, setError] = useState("");
|
|
173
|
+
const [notice, setNotice] = useState("");
|
|
174
|
+
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
let cancelled = false;
|
|
177
|
+
(async () => {
|
|
178
|
+
try {
|
|
179
|
+
const res = await fetch("/api/workspace/env-status", { cache: "no-store" });
|
|
180
|
+
const data = await res.json();
|
|
181
|
+
if (cancelled) return;
|
|
182
|
+
setConfiguredEnvRefs(Array.isArray(data?.configuredEnvRefs) ? data.configuredEnvRefs : []);
|
|
183
|
+
} catch { /* deriver still renders structure-only readiness */ }
|
|
184
|
+
})();
|
|
185
|
+
(async () => {
|
|
186
|
+
try {
|
|
187
|
+
const res = await fetch("/api/workspace/agent-outcomes");
|
|
188
|
+
const data = await res.json();
|
|
189
|
+
if (cancelled) return;
|
|
190
|
+
setReceipts(Array.isArray(data?.receipts) ? data.receipts : []);
|
|
191
|
+
} catch { /* non-fatal */ }
|
|
192
|
+
})();
|
|
193
|
+
return () => { cancelled = true; };
|
|
194
|
+
}, []);
|
|
195
|
+
|
|
196
|
+
const model = useMemo(
|
|
197
|
+
() => deriveScheduleCockpit({ workspaceConfig, configuredEnvRefs, receipts }),
|
|
198
|
+
[workspaceConfig, configuredEnvRefs, receipts],
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const providerFor = useCallback((card) => ({
|
|
202
|
+
providerId: card.providerId || model.defaultProvider?.providerId || "upstash",
|
|
203
|
+
productId: card.productId || model.defaultProvider?.productId || "upstash-qstash",
|
|
204
|
+
}), [model.defaultProvider]);
|
|
205
|
+
|
|
206
|
+
// All scheduler actions hand off to the EXISTING governed schedule routes.
|
|
207
|
+
// No client-side dataModel PATCH; failures surface the receipt-backed reason.
|
|
208
|
+
const runAction = useCallback(async (kind, card) => {
|
|
209
|
+
setError("");
|
|
210
|
+
setNotice("");
|
|
211
|
+
setBusyCard(card.cardId);
|
|
212
|
+
const { providerId, productId } = providerFor(card);
|
|
213
|
+
const base = `/api/workspace/add-ons/${providerId}/schedule`;
|
|
214
|
+
const body = { productId, objectId: card.objectId, rowId: card.name };
|
|
215
|
+
try {
|
|
216
|
+
let res;
|
|
217
|
+
if (kind === "uninstall") {
|
|
218
|
+
res = await fetch(base, { method: "DELETE", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
219
|
+
} else {
|
|
220
|
+
res = await fetch(base, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ ...body, action: kind }) });
|
|
221
|
+
}
|
|
222
|
+
const data = await res.json().catch(() => ({}));
|
|
223
|
+
if (kind === "readiness") {
|
|
224
|
+
const r = data?.readiness;
|
|
225
|
+
if (r) setNotice(r.ok ? `${card.name}: serverless-ready (${r.status}).` : `${card.name}: blocked — ${(r.blockingNodes?.[0]?.helperAction) || (r.deltaTags || []).join(", ")}`);
|
|
226
|
+
else setError(data?.error || "Readiness scan failed.");
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (!res.ok || data?.ok === false) {
|
|
230
|
+
// Resume re-runs readiness server-side; a 422 means drift blocked it.
|
|
231
|
+
const blocked = data?.readiness?.blockingNodes?.[0]?.helperAction;
|
|
232
|
+
setError(blocked || data?.error || `${kind} failed (HTTP ${res.status}).`);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
setNotice(`${card.name}: ${kind} ok.`);
|
|
236
|
+
if (typeof onConfigRefresh === "function") onConfigRefresh();
|
|
237
|
+
} catch (err) {
|
|
238
|
+
setError(err?.message || `${kind} failed.`);
|
|
239
|
+
} finally {
|
|
240
|
+
setBusyCard("");
|
|
241
|
+
}
|
|
242
|
+
}, [providerFor, onConfigRefresh]);
|
|
243
|
+
|
|
244
|
+
const onOpen = useCallback((card) => {
|
|
245
|
+
// Prefer the governed artifact handoff if the host wired one; else navigate.
|
|
246
|
+
if (typeof onOpenArtifact === "function") {
|
|
247
|
+
onOpenArtifact(card.artifact);
|
|
248
|
+
}
|
|
249
|
+
openWorkflowCanvas(card);
|
|
250
|
+
}, [onOpenArtifact]);
|
|
251
|
+
|
|
252
|
+
// ---- empty state: no scheduler product installed ----
|
|
253
|
+
if (model.schedulerSetupState === "none") {
|
|
254
|
+
return (
|
|
255
|
+
<div className="dm-swarm-cockpit" data-schedule-cockpit="" data-schedule-mode="no-provider">
|
|
256
|
+
<div className="dm-helper-toolcall dm-swarm-card">
|
|
257
|
+
<div className="dm-swarm-card-head">
|
|
258
|
+
<span className="dm-run-console__tree-dot" data-variant="pending" />
|
|
259
|
+
<span className="dm-helper-toolcall-title dm-swarm-card-title">No scheduler installed yet</span>
|
|
260
|
+
</div>
|
|
261
|
+
<div className="dm-helper-stream dm-swarm-card-desc">
|
|
262
|
+
Install a scheduler provider to activate Serverless Schedule for workflows.
|
|
263
|
+
Upstash QStash/Workflow is the first-class default; a Custom scheduler plugin works too.
|
|
264
|
+
</div>
|
|
265
|
+
<div className="dm-schedule-card-actions">
|
|
266
|
+
<button type="button" className="dm-btn-ghost" onClick={() => window.location.assign(SETUP_ROUTE)}>
|
|
267
|
+
Set up scheduler
|
|
268
|
+
</button>
|
|
269
|
+
{typeof onOpenSetup === "function" && (
|
|
270
|
+
<button type="button" className="dm-btn-ghost" onClick={onOpenSetup}>Open setup</button>
|
|
271
|
+
)}
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
{model.workflowCards.length > 0 && (
|
|
275
|
+
<p className="dm-run-console__hint">
|
|
276
|
+
{`${model.workflowCards.length} workflow${model.workflowCards.length === 1 ? "" : "s"} in this workspace will become schedulable once a provider is installed.`}
|
|
277
|
+
</p>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ---- operational state: inventory ----
|
|
284
|
+
const text = search.trim().toLowerCase();
|
|
285
|
+
const filtered = model.workflowCards.filter(
|
|
286
|
+
(c) => matchesFilter(c, activeFilter)
|
|
287
|
+
&& (!text || c.name.toLowerCase().includes(text) || (c.tags || []).some((t) => t.toLowerCase().includes(text))),
|
|
288
|
+
);
|
|
289
|
+
const attention = model.attention && matchesFilter(model.attention, activeFilter) && (!text || model.attention.name.toLowerCase().includes(text))
|
|
290
|
+
? model.attention
|
|
291
|
+
: null;
|
|
292
|
+
const others = attention ? filtered.filter((c) => c.cardId !== attention.cardId) : filtered;
|
|
293
|
+
const visible = others.slice(0, SCHEDULE_VISIBLE_CAP);
|
|
294
|
+
const overflow = others.length - visible.length;
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<div className="dm-swarm-cockpit" data-schedule-cockpit="" data-schedule-mode="operational">
|
|
298
|
+
<div className="dm-swarm-section-row">
|
|
299
|
+
<span className="dm-run-console__hint">
|
|
300
|
+
{`${model.counts.total} workflow${model.counts.total === 1 ? "" : "s"} · ${model.counts.scheduled} scheduled · ${model.counts.ready} ready · ${model.counts.blocked + model.counts.drifted} blocked`}
|
|
301
|
+
</span>
|
|
302
|
+
{model.governance.blockedAttempts > 0 && (
|
|
303
|
+
<span className="dm-run-console__hint" title="Blocked governance attempts in the outcome stream">
|
|
304
|
+
{`${model.governance.blockedAttempts} blocked attempt${model.governance.blockedAttempts === 1 ? "" : "s"}`}
|
|
305
|
+
</span>
|
|
306
|
+
)}
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
<div className="dm-schedule-search">
|
|
310
|
+
<Search size={13} aria-hidden="true" />
|
|
311
|
+
<input
|
|
312
|
+
value={search}
|
|
313
|
+
placeholder="Search workflows or tags"
|
|
314
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
315
|
+
aria-label="Search scheduled workflows"
|
|
316
|
+
/>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<div className="dm-schedule-filters" role="tablist" aria-label="Schedule filters">
|
|
320
|
+
{model.filters.map((f) => (
|
|
321
|
+
<button
|
|
322
|
+
key={f.id}
|
|
323
|
+
type="button"
|
|
324
|
+
role="tab"
|
|
325
|
+
aria-selected={activeFilter === f.id}
|
|
326
|
+
className={`dm-schedule-filter${activeFilter === f.id ? " is-active" : ""}`}
|
|
327
|
+
onClick={() => setActiveFilter(f.id)}
|
|
328
|
+
>
|
|
329
|
+
{`${f.label} ${f.count}`}
|
|
330
|
+
</button>
|
|
331
|
+
))}
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
{error && <div className="dm-helper-error" role="alert"><span>{error}</span></div>}
|
|
335
|
+
{notice && !error && <p className="dm-run-console__hint" data-schedule-notice="">{notice}</p>}
|
|
336
|
+
|
|
337
|
+
{attention && (
|
|
338
|
+
<>
|
|
339
|
+
<span className="dm-field-label">Needs your attention</span>
|
|
340
|
+
<ScheduleCard card={attention} onAction={runAction} busy={busyCard === attention.cardId} onOpen={onOpen} onSeed={onSeedSwarm} />
|
|
341
|
+
</>
|
|
342
|
+
)}
|
|
343
|
+
|
|
344
|
+
{others.length > 0 ? (
|
|
345
|
+
<>
|
|
346
|
+
<span className="dm-run-console__hint">Workflows</span>
|
|
347
|
+
<div className="dm-ceo-report-list" data-schedule-list="">
|
|
348
|
+
{visible.map((card) => (
|
|
349
|
+
<ScheduleCard key={card.cardId} card={card} onAction={runAction} busy={busyCard === card.cardId} onOpen={onOpen} onSeed={onSeedSwarm} />
|
|
350
|
+
))}
|
|
351
|
+
</div>
|
|
352
|
+
{overflow > 0 && (
|
|
353
|
+
<span className="dm-run-console__hint">{`Showing ${visible.length} of ${others.length} — refine with search or filters.`}</span>
|
|
354
|
+
)}
|
|
355
|
+
</>
|
|
356
|
+
) : (
|
|
357
|
+
!attention && <p className="dm-run-console__hint">No workflows match this filter.</p>
|
|
358
|
+
)}
|
|
359
|
+
</div>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export default ScheduleCockpit;
|
|
@@ -57,6 +57,14 @@ export const HELPER_COMMANDS = [
|
|
|
57
57
|
mutates: false,
|
|
58
58
|
view: "ceo"
|
|
59
59
|
},
|
|
60
|
+
{
|
|
61
|
+
name: "/schedule",
|
|
62
|
+
label: "Schedule",
|
|
63
|
+
description: "Manage serverless scheduled workflows and upgrade eligible local workflows",
|
|
64
|
+
scope: "workspace",
|
|
65
|
+
mutates: false,
|
|
66
|
+
view: "schedule"
|
|
67
|
+
},
|
|
60
68
|
{
|
|
61
69
|
name: "/register-api",
|
|
62
70
|
label: "Register API",
|