@truefoundry/trueforge-ui 0.3.0-rc.0 → 0.3.0-rc.2
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/CHANGELOG.md +68 -0
- package/README.md +21 -6
- package/dist/AgentCodeSnippets-637HQUXD.js +74 -0
- package/dist/AgentCodeSnippets-637HQUXD.js.map +1 -0
- package/dist/AgentMetricChart-4FYECRIN.js +15 -0
- package/dist/AgentOverview-3FJ5EE6N.js +167 -0
- package/dist/AgentOverview-3FJ5EE6N.js.map +1 -0
- package/dist/AgentSessionEventTimeline-VELWUYPR.js +88 -0
- package/dist/AgentSessionEventTimeline-VELWUYPR.js.map +1 -0
- package/dist/AgentSessionEventTimelineChart-CUKETHWM.js +591 -0
- package/dist/AgentSessionEventTimelineChart-CUKETHWM.js.map +1 -0
- package/dist/AgentSessions-T7MOGOTZ.js +275 -0
- package/dist/AgentSessions-T7MOGOTZ.js.map +1 -0
- package/dist/{ConnectorSettings-FXQ5B7TN.js → ConnectorSettings-ESZ2RSRG.js} +19 -21
- package/dist/ConnectorSettings-ESZ2RSRG.js.map +1 -0
- package/dist/{DockLayout-LBNQJZNF.js → DockLayout-NLFNU6F2.js} +11 -10
- package/dist/{DockLayout-LBNQJZNF.js.map → DockLayout-NLFNU6F2.js.map} +1 -1
- package/dist/DrawerLayout-PR3SMKA6.js +184 -0
- package/dist/DrawerLayout-PR3SMKA6.js.map +1 -0
- package/dist/{ModelSettings-4L4LOQJA.js → ModelSettings-LYW64IRR.js} +20 -24
- package/dist/ModelSettings-LYW64IRR.js.map +1 -0
- package/dist/OpenUiFenceBlock-JEPNPJ7U.js +9 -0
- package/dist/OpenUiFenceBlock-JEPNPJ7U.js.map +1 -0
- package/dist/{SandboxSettings-Z4ZG6ZX4.js → SandboxSettings-DF64LHYI.js} +16 -18
- package/dist/SandboxSettings-DF64LHYI.js.map +1 -0
- package/dist/SchedulesPage-WQU4H25F.js +15 -0
- package/dist/SchedulesPage-WQU4H25F.js.map +1 -0
- package/dist/{SettingsBuilder-WF7ZWKQW.js → SettingsBuilder-LSXK2VN4.js} +26 -22
- package/dist/SettingsBuilder-LSXK2VN4.js.map +1 -0
- package/dist/ShellRouteSync-B22IAV2W.js +255 -0
- package/dist/ShellRouteSync-B22IAV2W.js.map +1 -0
- package/dist/SidebarLayout-XW6LXB7R.js +309 -0
- package/dist/SidebarLayout-XW6LXB7R.js.map +1 -0
- package/dist/{SkillSettings-3EPPC3IT.js → SkillSettings-64VXUO6J.js} +14 -15
- package/dist/SkillSettings-64VXUO6J.js.map +1 -0
- package/dist/{TrueForgeUIWithRouter-NZ4FGPE4.js → TrueForgeUIWithRouter-3TSLV22Q.js} +14 -10
- package/dist/{TrueForgeUIWithRouter-NZ4FGPE4.js.map → TrueForgeUIWithRouter-3TSLV22Q.js.map} +1 -1
- package/dist/{WidgetLayout-3QZ357FL.js → WidgetLayout-Q5P7PYSJ.js} +17 -13
- package/dist/WidgetLayout-Q5P7PYSJ.js.map +1 -0
- package/dist/chunk-5I2COQNL.js +119 -0
- package/dist/chunk-5I2COQNL.js.map +1 -0
- package/dist/chunk-BWHOZFIV.js +587 -0
- package/dist/chunk-BWHOZFIV.js.map +1 -0
- package/dist/chunk-DJUFS2BW.js +27 -0
- package/dist/chunk-DJUFS2BW.js.map +1 -0
- package/dist/chunk-FQK2XVVE.js +880 -0
- package/dist/chunk-FQK2XVVE.js.map +1 -0
- package/dist/chunk-G2TUAD65.js +555 -0
- package/dist/chunk-G2TUAD65.js.map +1 -0
- package/dist/chunk-GXYAOAK5.js +1357 -0
- package/dist/chunk-GXYAOAK5.js.map +1 -0
- package/dist/{chunk-FJ5RSSMA.js → chunk-H6WTY5PM.js} +196 -32
- package/dist/chunk-H6WTY5PM.js.map +1 -0
- package/dist/chunk-I3AL54MA.js +180 -0
- package/dist/chunk-I3AL54MA.js.map +1 -0
- package/dist/{chunk-WREC7D54.js → chunk-LGRHHFHN.js} +120 -32
- package/dist/chunk-LGRHHFHN.js.map +1 -0
- package/dist/chunk-NHHSZB5N.js +74 -0
- package/dist/chunk-NHHSZB5N.js.map +1 -0
- package/dist/{chunk-CFLVPV5M.js → chunk-QC42MLAK.js} +7 -2
- package/dist/chunk-QC42MLAK.js.map +1 -0
- package/dist/chunk-RDXBMTP5.js +32 -0
- package/dist/chunk-RDXBMTP5.js.map +1 -0
- package/dist/chunk-SFZNIO6U.js +18 -0
- package/dist/chunk-SFZNIO6U.js.map +1 -0
- package/dist/chunk-SQGT43Z3.js +200 -0
- package/dist/chunk-SQGT43Z3.js.map +1 -0
- package/dist/{chunk-B52K63AW.js → chunk-VJ7BZGAF.js} +2 -2
- package/dist/chunk-VUMGMGKZ.js +252 -0
- package/dist/chunk-VUMGMGKZ.js.map +1 -0
- package/dist/chunk-Y6S4OVVQ.js +16 -0
- package/dist/chunk-Y6S4OVVQ.js.map +1 -0
- package/dist/chunk-YQO2NMJ3.js +13341 -0
- package/dist/chunk-YQO2NMJ3.js.map +1 -0
- package/dist/{createTrueFoundryServer-B72bU-J7.d.ts → createTrueFoundryServer-CmZnBh_l.d.ts} +15 -4
- package/dist/index.d.ts +1024 -24
- package/dist/index.js +691 -67
- package/dist/index.js.map +1 -1
- package/dist/plugins/trueforge-agent-server-adapter/index.d.ts +552 -8
- package/dist/plugins/trueforge-agent-server-adapter/index.js +504 -188
- package/dist/plugins/trueforge-agent-server-adapter/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +9 -8
- package/dist/ConnectorSettings-FXQ5B7TN.js.map +0 -1
- package/dist/DrawerLayout-Q6UK4VMI.js +0 -159
- package/dist/DrawerLayout-Q6UK4VMI.js.map +0 -1
- package/dist/ModelSettings-4L4LOQJA.js.map +0 -1
- package/dist/OpenUiFenceBlock-6UIIAVBX.js +0 -9
- package/dist/SandboxSettings-Z4ZG6ZX4.js.map +0 -1
- package/dist/SettingsBuilder-WF7ZWKQW.js.map +0 -1
- package/dist/ShellRouteSync-T4WLY57Q.js +0 -172
- package/dist/ShellRouteSync-T4WLY57Q.js.map +0 -1
- package/dist/SidebarLayout-3WZ3CVON.js +0 -256
- package/dist/SidebarLayout-3WZ3CVON.js.map +0 -1
- package/dist/SkillSettings-3EPPC3IT.js.map +0 -1
- package/dist/WidgetLayout-3QZ357FL.js.map +0 -1
- package/dist/chunk-36KOSC2J.js +0 -567
- package/dist/chunk-36KOSC2J.js.map +0 -1
- package/dist/chunk-3S6KHO7H.js +0 -2121
- package/dist/chunk-3S6KHO7H.js.map +0 -1
- package/dist/chunk-7L63OK3O.js +0 -91
- package/dist/chunk-7L63OK3O.js.map +0 -1
- package/dist/chunk-CFLVPV5M.js.map +0 -1
- package/dist/chunk-FJ5RSSMA.js.map +0 -1
- package/dist/chunk-OSV3HI5M.js +0 -6891
- package/dist/chunk-OSV3HI5M.js.map +0 -1
- package/dist/chunk-WQULF5AT.js +0 -460
- package/dist/chunk-WQULF5AT.js.map +0 -1
- package/dist/chunk-WREC7D54.js.map +0 -1
- package/dist/chunk-YIWUG6MC.js +0 -101
- package/dist/chunk-YIWUG6MC.js.map +0 -1
- package/dist/chunk-ZXOADNF3.js +0 -75
- package/dist/chunk-ZXOADNF3.js.map +0 -1
- /package/dist/{OpenUiFenceBlock-6UIIAVBX.js.map → AgentMetricChart-4FYECRIN.js.map} +0 -0
- /package/dist/{chunk-B52K63AW.js.map → chunk-VJ7BZGAF.js.map} +0 -0
|
@@ -0,0 +1,1357 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
ConnectorConnectButton,
|
|
4
|
+
DEFAULT_TABLE_PAGE_SIZE,
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogContent,
|
|
7
|
+
DialogFooter,
|
|
8
|
+
DialogHeader,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
DropdownMenu,
|
|
11
|
+
DropdownMenuItem,
|
|
12
|
+
EmptyScreen,
|
|
13
|
+
PopoverSelect,
|
|
14
|
+
SearchInput_default,
|
|
15
|
+
SideDrawer,
|
|
16
|
+
Skeleton,
|
|
17
|
+
Table,
|
|
18
|
+
TableBody,
|
|
19
|
+
TableCell,
|
|
20
|
+
TableHead,
|
|
21
|
+
TableHeader,
|
|
22
|
+
TableRow,
|
|
23
|
+
TableTokenPagination,
|
|
24
|
+
Tooltip,
|
|
25
|
+
auiInputClass,
|
|
26
|
+
mountName,
|
|
27
|
+
searchAllAgents,
|
|
28
|
+
useToasterOptional
|
|
29
|
+
} from "./chunk-YQO2NMJ3.js";
|
|
30
|
+
import {
|
|
31
|
+
readScheduleShareSearch,
|
|
32
|
+
replaceScheduleShareSearch
|
|
33
|
+
} from "./chunk-NHHSZB5N.js";
|
|
34
|
+
import {
|
|
35
|
+
DraftCatalogProvider,
|
|
36
|
+
Icon,
|
|
37
|
+
PageHeader,
|
|
38
|
+
auiButtonClass,
|
|
39
|
+
useDraftCatalog
|
|
40
|
+
} from "./chunk-H6WTY5PM.js";
|
|
41
|
+
import {
|
|
42
|
+
libraryAgentId,
|
|
43
|
+
useOptionalCatalogServer,
|
|
44
|
+
useScheduleServer,
|
|
45
|
+
useServer
|
|
46
|
+
} from "./chunk-FQK2XVVE.js";
|
|
47
|
+
import {
|
|
48
|
+
cn
|
|
49
|
+
} from "./chunk-BWHOZFIV.js";
|
|
50
|
+
|
|
51
|
+
// src/atoms/schedules/SchedulesPage.tsx
|
|
52
|
+
import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useRef, useState as useState3 } from "react";
|
|
53
|
+
|
|
54
|
+
// src/atoms/schedules/cadence.ts
|
|
55
|
+
var TIMEZONE_OPTIONS = [
|
|
56
|
+
{ value: "America/New_York", label: "America/New_York (ET)" },
|
|
57
|
+
{ value: "America/Chicago", label: "America/Chicago (CT)" },
|
|
58
|
+
{ value: "America/Denver", label: "America/Denver (MT)" },
|
|
59
|
+
{ value: "America/Los_Angeles", label: "America/Los_Angeles (PT)" },
|
|
60
|
+
{ value: "UTC", label: "UTC" },
|
|
61
|
+
{ value: "Europe/London", label: "Europe/London (GMT/BST)" },
|
|
62
|
+
{ value: "Europe/Berlin", label: "Europe/Berlin (CET)" },
|
|
63
|
+
{ value: "Asia/Kolkata", label: "Asia/Kolkata (IST)" },
|
|
64
|
+
{ value: "Asia/Tokyo", label: "Asia/Tokyo (JST)" }
|
|
65
|
+
];
|
|
66
|
+
function resolveLocalTimezone() {
|
|
67
|
+
try {
|
|
68
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
69
|
+
} catch {
|
|
70
|
+
return "UTC";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function getTimezoneOptions(selectedTimezone) {
|
|
74
|
+
const localTimezone = resolveLocalTimezone();
|
|
75
|
+
const knownTimezones = new Set(TIMEZONE_OPTIONS.map((option) => option.value));
|
|
76
|
+
const additionalTimezones = [localTimezone, selectedTimezone].flatMap((timezone) => {
|
|
77
|
+
if (timezone == null || timezone.length === 0 || knownTimezones.has(timezone)) return [];
|
|
78
|
+
knownTimezones.add(timezone);
|
|
79
|
+
return [{ value: timezone, label: timezone === localTimezone ? `${timezone} (Local)` : timezone }];
|
|
80
|
+
});
|
|
81
|
+
return [...additionalTimezones, ...TIMEZONE_OPTIONS];
|
|
82
|
+
}
|
|
83
|
+
var WEEKDAY_OPTIONS = [
|
|
84
|
+
{ value: 1, label: "Mon" },
|
|
85
|
+
{ value: 2, label: "Tue" },
|
|
86
|
+
{ value: 3, label: "Wed" },
|
|
87
|
+
{ value: 4, label: "Thu" },
|
|
88
|
+
{ value: 5, label: "Fri" },
|
|
89
|
+
{ value: 6, label: "Sat" },
|
|
90
|
+
{ value: 0, label: "Sun" }
|
|
91
|
+
];
|
|
92
|
+
var TZ_ABBR = {
|
|
93
|
+
"America/New_York": "ET",
|
|
94
|
+
"America/Chicago": "CT",
|
|
95
|
+
"America/Denver": "MT",
|
|
96
|
+
"America/Los_Angeles": "PT",
|
|
97
|
+
UTC: "UTC",
|
|
98
|
+
"Europe/London": "GMT",
|
|
99
|
+
"Europe/Berlin": "CET",
|
|
100
|
+
"Asia/Kolkata": "IST",
|
|
101
|
+
"Asia/Tokyo": "JST"
|
|
102
|
+
};
|
|
103
|
+
function defaultScheduleFormValues() {
|
|
104
|
+
return {
|
|
105
|
+
name: "",
|
|
106
|
+
task: "",
|
|
107
|
+
recurrence: "daily",
|
|
108
|
+
hour: 9,
|
|
109
|
+
minute: 0,
|
|
110
|
+
weekdays: [1],
|
|
111
|
+
customCron: "0 9 * * *",
|
|
112
|
+
timezone: resolveLocalTimezone()
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function timezoneAbbrev(timezone) {
|
|
116
|
+
return TZ_ABBR[timezone] ?? timezone.split("/").pop() ?? timezone;
|
|
117
|
+
}
|
|
118
|
+
function pad2(n) {
|
|
119
|
+
return n.toString().padStart(2, "0");
|
|
120
|
+
}
|
|
121
|
+
function formatClock({ hour, minute }) {
|
|
122
|
+
const period = hour >= 12 ? "PM" : "AM";
|
|
123
|
+
const h12 = hour % 12 === 0 ? 12 : hour % 12;
|
|
124
|
+
return `${String(h12)}:${pad2(minute)} ${period}`;
|
|
125
|
+
}
|
|
126
|
+
function valuesToCron(values) {
|
|
127
|
+
const minute = values.minute;
|
|
128
|
+
const hour = values.hour;
|
|
129
|
+
switch (values.recurrence) {
|
|
130
|
+
case "hourly":
|
|
131
|
+
return `${String(minute)} * * * *`;
|
|
132
|
+
case "daily":
|
|
133
|
+
return `${String(minute)} ${String(hour)} * * *`;
|
|
134
|
+
case "weekly": {
|
|
135
|
+
const days = [...values.weekdays].sort((a, b) => a - b);
|
|
136
|
+
const dow = days.length > 0 ? days.join(",") : "*";
|
|
137
|
+
return `${String(minute)} ${String(hour)} * * ${dow}`;
|
|
138
|
+
}
|
|
139
|
+
case "custom":
|
|
140
|
+
return values.customCron.trim();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function formatCadenceSummary(input) {
|
|
144
|
+
const abbr = timezoneAbbrev(input.timezone);
|
|
145
|
+
const parts = input.cron.trim().split(/\s+/);
|
|
146
|
+
if (parts.length !== 5) return input.cron;
|
|
147
|
+
const [minuteRaw, hourRaw, dayOfMonthRaw, monthRaw, dowRaw] = parts;
|
|
148
|
+
if (dayOfMonthRaw !== "*" || monthRaw !== "*") return input.cron;
|
|
149
|
+
const minute = Number(minuteRaw);
|
|
150
|
+
const hour = Number(hourRaw);
|
|
151
|
+
if (hourRaw === "*" && dowRaw === "*" && minuteRaw !== void 0 && !Number.isNaN(minute)) {
|
|
152
|
+
return `Hourly at :${pad2(minute)} ${abbr}`;
|
|
153
|
+
}
|
|
154
|
+
if (dowRaw === "*" && hourRaw !== void 0 && minuteRaw !== void 0 && !Number.isNaN(hour) && !Number.isNaN(minute)) {
|
|
155
|
+
return `Daily ${formatClock({ hour, minute })} ${abbr}`;
|
|
156
|
+
}
|
|
157
|
+
if (dowRaw === "1-5" && hourRaw !== void 0 && minuteRaw !== void 0 && !Number.isNaN(hour) && !Number.isNaN(minute)) {
|
|
158
|
+
return `Weekdays ${formatClock({ hour, minute })} ${abbr}`;
|
|
159
|
+
}
|
|
160
|
+
if (dowRaw != null && /^\d(?:,\d)*$/.test(dowRaw) && hourRaw !== void 0 && minuteRaw !== void 0 && !Number.isNaN(hour) && !Number.isNaN(minute)) {
|
|
161
|
+
const labels = dowRaw.split(",").map((d) => WEEKDAY_OPTIONS.find((w) => w.value === Number(d))?.label ?? d);
|
|
162
|
+
if (labels.length === 1) {
|
|
163
|
+
const day = labels[0] === "Mon" ? "Monday" : labels[0];
|
|
164
|
+
return `Every ${day} ${formatClock({ hour, minute })} ${abbr}`;
|
|
165
|
+
}
|
|
166
|
+
return `${labels.join(", ")} ${formatClock({ hour, minute })} ${abbr}`;
|
|
167
|
+
}
|
|
168
|
+
return input.cron;
|
|
169
|
+
}
|
|
170
|
+
function cronToFormValues(input) {
|
|
171
|
+
const base = defaultScheduleFormValues();
|
|
172
|
+
const parts = input.cron.trim().split(/\s+/);
|
|
173
|
+
if (parts.length !== 5) {
|
|
174
|
+
return {
|
|
175
|
+
...base,
|
|
176
|
+
name: input.name,
|
|
177
|
+
task: input.task,
|
|
178
|
+
timezone: input.timezone,
|
|
179
|
+
recurrence: "custom",
|
|
180
|
+
customCron: input.cron
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const [minuteRaw, hourRaw, dom, month, dowRaw] = parts;
|
|
184
|
+
const minute = Number(minuteRaw);
|
|
185
|
+
const hour = Number(hourRaw);
|
|
186
|
+
if (dom !== "*" || month !== "*") {
|
|
187
|
+
return {
|
|
188
|
+
...base,
|
|
189
|
+
name: input.name,
|
|
190
|
+
task: input.task,
|
|
191
|
+
timezone: input.timezone,
|
|
192
|
+
recurrence: "custom",
|
|
193
|
+
customCron: input.cron
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (hourRaw === "*" && dowRaw === "*" && !Number.isNaN(minute)) {
|
|
197
|
+
return {
|
|
198
|
+
...base,
|
|
199
|
+
name: input.name,
|
|
200
|
+
task: input.task,
|
|
201
|
+
timezone: input.timezone,
|
|
202
|
+
recurrence: "hourly",
|
|
203
|
+
minute,
|
|
204
|
+
customCron: input.cron
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (dowRaw === "*" && !Number.isNaN(hour) && !Number.isNaN(minute)) {
|
|
208
|
+
return {
|
|
209
|
+
...base,
|
|
210
|
+
name: input.name,
|
|
211
|
+
task: input.task,
|
|
212
|
+
timezone: input.timezone,
|
|
213
|
+
recurrence: "daily",
|
|
214
|
+
hour,
|
|
215
|
+
minute,
|
|
216
|
+
customCron: input.cron
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (dowRaw != null && /^\d(?:,\d)*$/.test(dowRaw) && !Number.isNaN(hour) && !Number.isNaN(minute)) {
|
|
220
|
+
return {
|
|
221
|
+
...base,
|
|
222
|
+
name: input.name,
|
|
223
|
+
task: input.task,
|
|
224
|
+
timezone: input.timezone,
|
|
225
|
+
recurrence: "weekly",
|
|
226
|
+
hour,
|
|
227
|
+
minute,
|
|
228
|
+
weekdays: dowRaw.split(",").map(Number),
|
|
229
|
+
customCron: input.cron
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
...base,
|
|
234
|
+
name: input.name,
|
|
235
|
+
task: input.task,
|
|
236
|
+
timezone: input.timezone,
|
|
237
|
+
recurrence: "custom",
|
|
238
|
+
customCron: input.cron,
|
|
239
|
+
hour: Number.isNaN(hour) ? base.hour : hour,
|
|
240
|
+
minute: Number.isNaN(minute) ? base.minute : minute
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function formatRelativeTime(iso, nowMs = Date.now()) {
|
|
244
|
+
if (iso == null) return "Never";
|
|
245
|
+
const then = Date.parse(iso);
|
|
246
|
+
if (Number.isNaN(then)) return "Never";
|
|
247
|
+
const deltaSec = Math.round((nowMs - then) / 1e3);
|
|
248
|
+
if (deltaSec < 60) return "just now";
|
|
249
|
+
const mins = Math.round(deltaSec / 60);
|
|
250
|
+
if (mins < 60) return `${String(mins)} min ago`;
|
|
251
|
+
const hours = Math.round(mins / 60);
|
|
252
|
+
if (hours < 48) return `${String(hours)} hour${hours === 1 ? "" : "s"} ago`;
|
|
253
|
+
const days = Math.round(hours / 24);
|
|
254
|
+
return `${String(days)} day${days === 1 ? "" : "s"} ago`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/atoms/schedules/ScheduleFormDrawer.tsx
|
|
258
|
+
import { useEffect as useEffect2, useMemo, useState as useState2 } from "react";
|
|
259
|
+
|
|
260
|
+
// src/atoms/schedules/ScheduleFormFields.tsx
|
|
261
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
262
|
+
var RECURRENCE_OPTIONS = [
|
|
263
|
+
{ value: "hourly", label: "Hourly" },
|
|
264
|
+
{ value: "daily", label: "Daily" },
|
|
265
|
+
{ value: "weekly", label: "Weekly" }
|
|
266
|
+
];
|
|
267
|
+
var HOUR_OPTIONS = Array.from({ length: 24 }, (_, hour) => ({
|
|
268
|
+
value: String(hour),
|
|
269
|
+
label: hour.toString().padStart(2, "0")
|
|
270
|
+
}));
|
|
271
|
+
var MINUTE_OPTIONS = [0, 15, 30, 45].map((minute) => ({
|
|
272
|
+
value: String(minute),
|
|
273
|
+
label: `:${minute.toString().padStart(2, "0")}`
|
|
274
|
+
}));
|
|
275
|
+
function ScheduleFormFields({
|
|
276
|
+
values,
|
|
277
|
+
onChange,
|
|
278
|
+
agentId,
|
|
279
|
+
onAgentIdChange,
|
|
280
|
+
agentOptions,
|
|
281
|
+
agentPickerDisabled = false
|
|
282
|
+
}) {
|
|
283
|
+
const cron = valuesToCron(values);
|
|
284
|
+
const cadence = formatCadenceSummary({ cron, timezone: values.timezone });
|
|
285
|
+
const timezoneOptions = getTimezoneOptions(values.timezone);
|
|
286
|
+
const set = (key, value) => {
|
|
287
|
+
onChange({ ...values, [key]: value });
|
|
288
|
+
};
|
|
289
|
+
const toggleWeekday = (day) => {
|
|
290
|
+
const has = values.weekdays.includes(day);
|
|
291
|
+
const weekdays = has ? values.weekdays.filter((d) => d !== day) : [...values.weekdays, day];
|
|
292
|
+
onChange({ ...values, weekdays: weekdays.length > 0 ? weekdays : [day] });
|
|
293
|
+
};
|
|
294
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 px-5 py-4", children: [
|
|
295
|
+
/* @__PURE__ */ jsxs("div", { className: "block", children: [
|
|
296
|
+
/* @__PURE__ */ jsx("span", { className: "mb-1.5 block text-sm font-medium", children: "Agent" }),
|
|
297
|
+
/* @__PURE__ */ jsx(
|
|
298
|
+
PopoverSelect,
|
|
299
|
+
{
|
|
300
|
+
"aria-label": "Agent",
|
|
301
|
+
placeholder: "Select an agent",
|
|
302
|
+
value: agentId,
|
|
303
|
+
options: agentOptions.map((agent) => ({ value: agent.agentId, label: agent.name })),
|
|
304
|
+
onValueChange: (value) => onAgentIdChange?.(value),
|
|
305
|
+
disabled: agentPickerDisabled || onAgentIdChange == null
|
|
306
|
+
}
|
|
307
|
+
)
|
|
308
|
+
] }),
|
|
309
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
310
|
+
/* @__PURE__ */ jsx("span", { className: "mb-1.5 block text-sm font-medium", children: "Name" }),
|
|
311
|
+
/* @__PURE__ */ jsx(
|
|
312
|
+
"input",
|
|
313
|
+
{
|
|
314
|
+
value: values.name,
|
|
315
|
+
onChange: (e) => set("name", e.target.value),
|
|
316
|
+
placeholder: "harness-daily-digest",
|
|
317
|
+
className: auiInputClass("h-8"),
|
|
318
|
+
required: true
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
] }),
|
|
322
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
323
|
+
/* @__PURE__ */ jsx("span", { className: "mb-1.5 block text-sm font-medium", children: "Task" }),
|
|
324
|
+
/* @__PURE__ */ jsx(
|
|
325
|
+
"textarea",
|
|
326
|
+
{
|
|
327
|
+
value: values.task,
|
|
328
|
+
onChange: (e) => set("task", e.target.value),
|
|
329
|
+
rows: 3,
|
|
330
|
+
placeholder: "Summarise yesterday's harness runs and post the digest\u2026",
|
|
331
|
+
className: auiInputClass("resize-y py-2"),
|
|
332
|
+
required: true
|
|
333
|
+
}
|
|
334
|
+
)
|
|
335
|
+
] }),
|
|
336
|
+
/* @__PURE__ */ jsxs("fieldset", { children: [
|
|
337
|
+
/* @__PURE__ */ jsx("legend", { className: "mb-1.5 text-sm font-medium", children: "Recurrence" }),
|
|
338
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-1 rounded-lg border border-border p-1", children: RECURRENCE_OPTIONS.map((opt) => {
|
|
339
|
+
const selected = values.recurrence === opt.value;
|
|
340
|
+
return /* @__PURE__ */ jsx(
|
|
341
|
+
"button",
|
|
342
|
+
{
|
|
343
|
+
type: "button",
|
|
344
|
+
className: cn(
|
|
345
|
+
"rounded-md px-2 py-1.5 text-xs font-medium transition-colors",
|
|
346
|
+
selected ? "border border-primary-button-bg/40 bg-primary-button-bg/10 text-primary-button-bg" : "text-text-secondary hover:bg-ghost-button-hover border border-transparent"
|
|
347
|
+
),
|
|
348
|
+
"aria-pressed": selected,
|
|
349
|
+
onClick: () => set("recurrence", opt.value),
|
|
350
|
+
children: opt.label
|
|
351
|
+
},
|
|
352
|
+
opt.value
|
|
353
|
+
);
|
|
354
|
+
}) })
|
|
355
|
+
] }),
|
|
356
|
+
values.recurrence === "hourly" ? /* @__PURE__ */ jsxs("div", { className: "block max-w-[8rem]", children: [
|
|
357
|
+
/* @__PURE__ */ jsx("span", { className: "mb-1.5 block text-sm font-medium", children: "Minute" }),
|
|
358
|
+
/* @__PURE__ */ jsx(
|
|
359
|
+
PopoverSelect,
|
|
360
|
+
{
|
|
361
|
+
"aria-label": "Minute",
|
|
362
|
+
value: String(values.minute),
|
|
363
|
+
options: MINUTE_OPTIONS,
|
|
364
|
+
onValueChange: (value) => set("minute", Number(value))
|
|
365
|
+
}
|
|
366
|
+
)
|
|
367
|
+
] }) : null,
|
|
368
|
+
values.recurrence === "daily" || values.recurrence === "weekly" ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-3", children: [
|
|
369
|
+
/* @__PURE__ */ jsxs("div", { className: "block min-w-[7rem] flex-1", children: [
|
|
370
|
+
/* @__PURE__ */ jsx("span", { className: "mb-1.5 block text-sm font-medium", children: "Hour" }),
|
|
371
|
+
/* @__PURE__ */ jsx(
|
|
372
|
+
PopoverSelect,
|
|
373
|
+
{
|
|
374
|
+
"aria-label": "Hour",
|
|
375
|
+
value: String(values.hour),
|
|
376
|
+
options: HOUR_OPTIONS,
|
|
377
|
+
onValueChange: (value) => set("hour", Number(value))
|
|
378
|
+
}
|
|
379
|
+
)
|
|
380
|
+
] }),
|
|
381
|
+
/* @__PURE__ */ jsxs("div", { className: "block min-w-[7rem] flex-1", children: [
|
|
382
|
+
/* @__PURE__ */ jsx("span", { className: "mb-1.5 block text-sm font-medium", children: "Minute" }),
|
|
383
|
+
/* @__PURE__ */ jsx(
|
|
384
|
+
PopoverSelect,
|
|
385
|
+
{
|
|
386
|
+
"aria-label": "Minute",
|
|
387
|
+
value: String(values.minute),
|
|
388
|
+
options: MINUTE_OPTIONS,
|
|
389
|
+
onValueChange: (value) => set("minute", Number(value))
|
|
390
|
+
}
|
|
391
|
+
)
|
|
392
|
+
] })
|
|
393
|
+
] }) : null,
|
|
394
|
+
values.recurrence === "weekly" ? /* @__PURE__ */ jsxs("fieldset", { children: [
|
|
395
|
+
/* @__PURE__ */ jsx("legend", { className: "mb-1.5 text-sm font-medium", children: "Days" }),
|
|
396
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: WEEKDAY_OPTIONS.map((day) => {
|
|
397
|
+
const selected = values.weekdays.includes(day.value);
|
|
398
|
+
return /* @__PURE__ */ jsx(
|
|
399
|
+
"button",
|
|
400
|
+
{
|
|
401
|
+
type: "button",
|
|
402
|
+
className: cn(
|
|
403
|
+
"rounded-md border px-2.5 py-1 text-xs font-medium",
|
|
404
|
+
selected ? "border-primary-button-bg/40 bg-primary-button-bg/10 text-primary-button-bg" : "border-border text-text-secondary hover:bg-ghost-button-hover"
|
|
405
|
+
),
|
|
406
|
+
"aria-pressed": selected,
|
|
407
|
+
onClick: () => toggleWeekday(day.value),
|
|
408
|
+
children: day.label
|
|
409
|
+
},
|
|
410
|
+
day.value
|
|
411
|
+
);
|
|
412
|
+
}) })
|
|
413
|
+
] }) : null,
|
|
414
|
+
values.recurrence === "custom" ? /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border bg-secondary-bg/40 px-3 py-2", children: [
|
|
415
|
+
/* @__PURE__ */ jsx("span", { className: "text-text-secondary block text-xs", children: "Existing custom schedule" }),
|
|
416
|
+
/* @__PURE__ */ jsx("code", { className: "text-text-primary mt-1 block font-mono text-sm", children: values.customCron }),
|
|
417
|
+
/* @__PURE__ */ jsx("span", { className: "text-text-secondary mt-1 block text-xs", children: "Select Hourly, Daily, or Weekly to replace this schedule." })
|
|
418
|
+
] }) : null,
|
|
419
|
+
/* @__PURE__ */ jsxs("div", { className: "block", children: [
|
|
420
|
+
/* @__PURE__ */ jsx("span", { className: "mb-1.5 block text-sm font-medium", children: "Timezone" }),
|
|
421
|
+
/* @__PURE__ */ jsx(
|
|
422
|
+
PopoverSelect,
|
|
423
|
+
{
|
|
424
|
+
"aria-label": "Timezone",
|
|
425
|
+
value: values.timezone,
|
|
426
|
+
options: timezoneOptions,
|
|
427
|
+
onValueChange: (value) => set("timezone", value),
|
|
428
|
+
menuPlacement: "top"
|
|
429
|
+
}
|
|
430
|
+
)
|
|
431
|
+
] }),
|
|
432
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border bg-secondary-bg/40 px-3 py-3", children: [
|
|
433
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
434
|
+
/* @__PURE__ */ jsx("span", { className: "text-text-secondary text-[10px] font-semibold tracking-wide uppercase", children: "Frequency" }),
|
|
435
|
+
/* @__PURE__ */ jsx("code", { className: "text-text-secondary font-mono text-xs", children: cron || "\u2014" })
|
|
436
|
+
] }),
|
|
437
|
+
/* @__PURE__ */ jsx("p", { className: "text-text-primary mt-1 text-sm font-semibold", children: cadence || "\u2014" })
|
|
438
|
+
] })
|
|
439
|
+
] });
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/atoms/schedules/TestScheduleScreen.tsx
|
|
443
|
+
import { useEffect, useState } from "react";
|
|
444
|
+
|
|
445
|
+
// src/atoms/schedules/scheduleRuns.ts
|
|
446
|
+
function isManualRun(name) {
|
|
447
|
+
return name.startsWith("manual-");
|
|
448
|
+
}
|
|
449
|
+
function runChipKind(status) {
|
|
450
|
+
if (status === "failed") return "failed";
|
|
451
|
+
return "success";
|
|
452
|
+
}
|
|
453
|
+
function runStatusLabel(status) {
|
|
454
|
+
if (status === "failed") return "Failed";
|
|
455
|
+
if (status === "triggered") return "Triggered";
|
|
456
|
+
return "Scheduled";
|
|
457
|
+
}
|
|
458
|
+
function runTypeLabel(name) {
|
|
459
|
+
return isManualRun(name) ? "Manual test" : "Cron";
|
|
460
|
+
}
|
|
461
|
+
function lastHistoricalRuns(runs, limit = 5) {
|
|
462
|
+
const historical = runs.filter((run) => run.status !== "scheduled");
|
|
463
|
+
return historical.slice(0, limit).reverse();
|
|
464
|
+
}
|
|
465
|
+
function formatScheduleRunInstant(iso) {
|
|
466
|
+
if (iso == null) return "\u2014";
|
|
467
|
+
const date = new Date(iso);
|
|
468
|
+
if (Number.isNaN(date.getTime())) return "\u2014";
|
|
469
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
470
|
+
month: "short",
|
|
471
|
+
day: "numeric",
|
|
472
|
+
year: "numeric",
|
|
473
|
+
hour: "numeric",
|
|
474
|
+
minute: "2-digit",
|
|
475
|
+
hour12: true
|
|
476
|
+
}).format(date);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/atoms/schedules/ScheduleRunTooltip.tsx
|
|
480
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
481
|
+
function ScheduleRunTooltip({ run }) {
|
|
482
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-1 text-left text-xs", children: [
|
|
483
|
+
/* @__PURE__ */ jsx2("p", { className: "text-text-primary font-medium", children: runStatusLabel(run.status) }),
|
|
484
|
+
/* @__PURE__ */ jsx2("p", { className: "text-text-secondary", children: runTypeLabel(run.name) }),
|
|
485
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-text-secondary", children: [
|
|
486
|
+
"Scheduled for: ",
|
|
487
|
+
formatScheduleRunInstant(run.scheduledFor)
|
|
488
|
+
] }),
|
|
489
|
+
run.triggeredAt != null ? /* @__PURE__ */ jsxs2("p", { className: "text-text-secondary", children: [
|
|
490
|
+
"Triggered at: ",
|
|
491
|
+
formatScheduleRunInstant(run.triggeredAt)
|
|
492
|
+
] }) : null,
|
|
493
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-text-secondary", children: [
|
|
494
|
+
"Triggered by: ",
|
|
495
|
+
run.triggeredBy
|
|
496
|
+
] })
|
|
497
|
+
] });
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/atoms/schedules/ScheduleRunChip.tsx
|
|
501
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
502
|
+
var CHIP_STYLES = {
|
|
503
|
+
success: "border-emerald-600/30 bg-emerald-500/10 text-emerald-700 dark:border-emerald-400/35 dark:bg-emerald-500/15 dark:text-emerald-300",
|
|
504
|
+
failed: "border-red-600/30 bg-red-500/10 text-red-700 dark:border-red-400/35 dark:bg-red-500/15 dark:text-red-300"
|
|
505
|
+
};
|
|
506
|
+
function ScheduleRunChip({ run }) {
|
|
507
|
+
const kind = runChipKind(run.status);
|
|
508
|
+
const when = formatScheduleRunInstant(run.triggeredAt ?? run.scheduledFor);
|
|
509
|
+
return /* @__PURE__ */ jsx3(Tooltip, { content: /* @__PURE__ */ jsx3(ScheduleRunTooltip, { run }), side: "top", children: /* @__PURE__ */ jsx3(
|
|
510
|
+
"span",
|
|
511
|
+
{
|
|
512
|
+
className: cn("inline-flex size-6 shrink-0 items-center justify-center rounded-md border", CHIP_STYLES[kind]),
|
|
513
|
+
"aria-label": `${runStatusLabel(run.status)} run at ${when}`,
|
|
514
|
+
children: /* @__PURE__ */ jsx3(Icon, { name: kind === "failed" ? "triangle-exclamation" : "check", className: "size-3.5" })
|
|
515
|
+
}
|
|
516
|
+
) });
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// src/atoms/schedules/ScheduleStatusBadge.tsx
|
|
520
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
521
|
+
function ScheduleStatusBadge({ status }) {
|
|
522
|
+
const active = status === "active";
|
|
523
|
+
return /* @__PURE__ */ jsxs3(
|
|
524
|
+
"span",
|
|
525
|
+
{
|
|
526
|
+
className: cn(
|
|
527
|
+
"inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium",
|
|
528
|
+
active ? "border-emerald-600/30 bg-emerald-500/10 text-emerald-700 dark:border-emerald-400/35 dark:bg-emerald-500/15 dark:text-emerald-300" : "border-border bg-secondary-bg text-text-secondary"
|
|
529
|
+
),
|
|
530
|
+
children: [
|
|
531
|
+
/* @__PURE__ */ jsx4(
|
|
532
|
+
"span",
|
|
533
|
+
{
|
|
534
|
+
className: cn(
|
|
535
|
+
"size-1.5 shrink-0 rounded-full",
|
|
536
|
+
active ? "bg-emerald-600 dark:bg-emerald-400" : "bg-text-secondary"
|
|
537
|
+
),
|
|
538
|
+
"aria-hidden": true
|
|
539
|
+
}
|
|
540
|
+
),
|
|
541
|
+
active ? "Active" : "Paused"
|
|
542
|
+
]
|
|
543
|
+
}
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// src/atoms/schedules/TestScheduleScreen.tsx
|
|
548
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
549
|
+
function mountConnector(connectors, name) {
|
|
550
|
+
return connectors.find((connector) => connector.name === name || connector.id === name) ?? null;
|
|
551
|
+
}
|
|
552
|
+
function ScheduleMcpConnect({
|
|
553
|
+
connector,
|
|
554
|
+
onConnected
|
|
555
|
+
}) {
|
|
556
|
+
const catalog = useOptionalCatalogServer();
|
|
557
|
+
if (catalog == null) return null;
|
|
558
|
+
return /* @__PURE__ */ jsx5(ConnectorConnectButton, { connector, onConnected });
|
|
559
|
+
}
|
|
560
|
+
function DetailRow({ label, children }) {
|
|
561
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex items-start justify-between gap-3 border-b border-border px-3 py-2.5 last:border-b-0", children: [
|
|
562
|
+
/* @__PURE__ */ jsx5("span", { className: "text-text-secondary shrink-0 text-[10px] font-semibold tracking-wide uppercase", children: label }),
|
|
563
|
+
/* @__PURE__ */ jsx5("div", { className: "min-w-0 text-right text-sm text-text-primary", children })
|
|
564
|
+
] });
|
|
565
|
+
}
|
|
566
|
+
function TestScheduleScreen({ schedule, agentName, mcpMounts, onEditConfiguration }) {
|
|
567
|
+
const scheduleServer = useScheduleServer();
|
|
568
|
+
const toaster = useToasterOptional();
|
|
569
|
+
const { connectors, ensureLoaded, refreshConnectors, loading, error } = useDraftCatalog();
|
|
570
|
+
const [running, setRunning] = useState(false);
|
|
571
|
+
const [lastTestRun, setLastTestRun] = useState(null);
|
|
572
|
+
const cadence = formatCadenceSummary({ cron: schedule.cron, timezone: schedule.timezone });
|
|
573
|
+
const handleRunTest = async () => {
|
|
574
|
+
setRunning(true);
|
|
575
|
+
try {
|
|
576
|
+
const run = await scheduleServer.createScheduleRun({ scheduleId: schedule.id });
|
|
577
|
+
setLastTestRun(run);
|
|
578
|
+
toaster?.showSuccess({ title: "Test run started" });
|
|
579
|
+
} catch (caught) {
|
|
580
|
+
toaster?.showError(caught);
|
|
581
|
+
} finally {
|
|
582
|
+
setRunning(false);
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
useEffect(() => {
|
|
586
|
+
ensureLoaded();
|
|
587
|
+
}, [ensureLoaded]);
|
|
588
|
+
useEffect(() => {
|
|
589
|
+
if (error != null) toaster?.showError(error);
|
|
590
|
+
}, [error, toaster]);
|
|
591
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-4 px-5 py-4", children: [
|
|
592
|
+
/* @__PURE__ */ jsxs4("section", { className: "overflow-hidden rounded-lg border border-border bg-card-bg", children: [
|
|
593
|
+
/* @__PURE__ */ jsx5(DetailRow, { label: "Agent", children: agentName }),
|
|
594
|
+
/* @__PURE__ */ jsx5(DetailRow, { label: "Name", children: schedule.name }),
|
|
595
|
+
/* @__PURE__ */ jsx5(DetailRow, { label: "Task", children: /* @__PURE__ */ jsx5("span", { className: "whitespace-pre-wrap break-words", children: schedule.task }) }),
|
|
596
|
+
/* @__PURE__ */ jsx5(DetailRow, { label: "Cadence", children: /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center justify-end gap-1.5", children: [
|
|
597
|
+
/* @__PURE__ */ jsx5("span", { children: cadence }),
|
|
598
|
+
/* @__PURE__ */ jsx5("code", { className: "text-text-secondary font-mono text-xs", children: schedule.cron })
|
|
599
|
+
] }) }),
|
|
600
|
+
/* @__PURE__ */ jsx5(DetailRow, { label: "Status", children: /* @__PURE__ */ jsx5(ScheduleStatusBadge, { status: schedule.status }) }),
|
|
601
|
+
/* @__PURE__ */ jsx5("div", { className: "flex justify-end px-3 py-2.5", children: /* @__PURE__ */ jsxs4(Button.Secondary, { type: "button", size: "small", onClick: onEditConfiguration, children: [
|
|
602
|
+
/* @__PURE__ */ jsx5(Icon, { name: "pencil", className: "size-3.5" }),
|
|
603
|
+
"Edit Configuration"
|
|
604
|
+
] }) })
|
|
605
|
+
] }),
|
|
606
|
+
/* @__PURE__ */ jsxs4("section", { className: "overflow-hidden rounded-lg border border-border bg-card-bg", children: [
|
|
607
|
+
/* @__PURE__ */ jsx5("div", { className: "border-b border-border px-3 py-2", children: /* @__PURE__ */ jsx5("h3", { className: "text-text-secondary text-[10px] font-semibold tracking-wide uppercase", children: "Tool Authorization" }) }),
|
|
608
|
+
mcpMounts.length === 0 ? /* @__PURE__ */ jsx5("p", { className: "text-text-secondary px-3 py-3 text-sm", children: "No MCP connectors configured for this agent." }) : /* @__PURE__ */ jsx5("ul", { className: "divide-y divide-border", children: mcpMounts.map((mount) => {
|
|
609
|
+
const connector = mountConnector(connectors, mount.name);
|
|
610
|
+
const authenticated = connector?.authenticated === true;
|
|
611
|
+
const unavailable = connector == null && !loading;
|
|
612
|
+
return /* @__PURE__ */ jsxs4("li", { className: "flex items-center gap-2 px-3 py-2.5", children: [
|
|
613
|
+
/* @__PURE__ */ jsx5(
|
|
614
|
+
"span",
|
|
615
|
+
{
|
|
616
|
+
className: cn(
|
|
617
|
+
"inline-flex size-4 shrink-0 items-center justify-center rounded-full border",
|
|
618
|
+
authenticated ? "border-emerald-600/40 bg-emerald-500/15 text-emerald-700 dark:text-emerald-300" : "border-border text-transparent"
|
|
619
|
+
),
|
|
620
|
+
"aria-hidden": true,
|
|
621
|
+
children: authenticated ? /* @__PURE__ */ jsx5(Icon, { name: "check", className: "size-3" }) : null
|
|
622
|
+
}
|
|
623
|
+
),
|
|
624
|
+
/* @__PURE__ */ jsx5("span", { className: "text-text-primary min-w-0 flex-1 truncate text-sm", children: mount.name }),
|
|
625
|
+
authenticated ? /* @__PURE__ */ jsx5("span", { className: "text-success-bg text-xs font-medium", children: "Connected" }) : unavailable ? /* @__PURE__ */ jsx5("span", { className: "text-text-secondary text-xs", children: "Unavailable" }) : connector != null && connector.authenticated === false ? /* @__PURE__ */ jsx5(ScheduleMcpConnect, { connector, onConnected: refreshConnectors }) : loading ? /* @__PURE__ */ jsx5("span", { className: "text-text-secondary text-xs", children: "Loading\u2026" }) : null
|
|
626
|
+
] }, mount.name);
|
|
627
|
+
}) })
|
|
628
|
+
] }),
|
|
629
|
+
/* @__PURE__ */ jsxs4("section", { className: "overflow-hidden rounded-lg border border-border bg-card-bg", children: [
|
|
630
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between gap-2 border-b border-border px-3 py-2", children: [
|
|
631
|
+
/* @__PURE__ */ jsx5("h3", { className: "text-text-secondary text-[10px] font-semibold tracking-wide uppercase", children: "Test Run" }),
|
|
632
|
+
/* @__PURE__ */ jsxs4(Button.Primary, { type: "button", disabled: running, size: "small", onClick: () => void handleRunTest(), children: [
|
|
633
|
+
/* @__PURE__ */ jsx5(Icon, { name: running ? "loader" : "play", className: cn("size-3.5", running && "animate-spin") }),
|
|
634
|
+
"Run Test"
|
|
635
|
+
] })
|
|
636
|
+
] }),
|
|
637
|
+
lastTestRun == null ? /* @__PURE__ */ jsx5("p", { className: "text-text-secondary px-3 py-3 text-sm", children: "Run a one-off test using the schedule task. This does not change the cron pending run." }) : /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3 px-3 py-3", children: [
|
|
638
|
+
/* @__PURE__ */ jsx5(ScheduleRunChip, { run: lastTestRun }),
|
|
639
|
+
/* @__PURE__ */ jsxs4("div", { className: "min-w-0 text-sm", children: [
|
|
640
|
+
/* @__PURE__ */ jsx5("p", { className: "text-text-primary font-medium", children: runTypeLabel(lastTestRun.name) }),
|
|
641
|
+
/* @__PURE__ */ jsx5("p", { className: "text-text-secondary", children: formatRelativeTime(lastTestRun.triggeredAt ?? lastTestRun.scheduledFor) })
|
|
642
|
+
] })
|
|
643
|
+
] })
|
|
644
|
+
] })
|
|
645
|
+
] });
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/atoms/schedules/ScheduleFormDrawer.tsx
|
|
649
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
650
|
+
function ScheduleFormDrawerBody({
|
|
651
|
+
open,
|
|
652
|
+
onOpenChange,
|
|
653
|
+
mode,
|
|
654
|
+
schedule,
|
|
655
|
+
initialAgentId = "",
|
|
656
|
+
onSaved
|
|
657
|
+
}) {
|
|
658
|
+
const scheduleServer = useScheduleServer();
|
|
659
|
+
const server = useServer();
|
|
660
|
+
const toaster = useToasterOptional();
|
|
661
|
+
const [form, setForm] = useState2(defaultScheduleFormValues);
|
|
662
|
+
const [agentId, setAgentId] = useState2(initialAgentId);
|
|
663
|
+
const [agents, setAgents] = useState2([]);
|
|
664
|
+
const [view, setView] = useState2({ kind: "form" });
|
|
665
|
+
const [saving, setSaving] = useState2(false);
|
|
666
|
+
const [activating, setActivating] = useState2(false);
|
|
667
|
+
const [error, setError] = useState2(null);
|
|
668
|
+
const savedFromCreate = view.kind === "form" ? view.saved : view.schedule;
|
|
669
|
+
const isExternalEdit = mode === "edit" && view.kind === "form" && view.saved == null && schedule != null;
|
|
670
|
+
const isCreatedEdit = view.kind === "form" && view.saved != null;
|
|
671
|
+
useEffect2(() => {
|
|
672
|
+
if (!open) return;
|
|
673
|
+
let cancelled = false;
|
|
674
|
+
void searchAllAgents(server).then((rows) => {
|
|
675
|
+
if (cancelled) return;
|
|
676
|
+
setAgents(rows);
|
|
677
|
+
}).catch(() => void 0);
|
|
678
|
+
return () => {
|
|
679
|
+
cancelled = true;
|
|
680
|
+
};
|
|
681
|
+
}, [open, server]);
|
|
682
|
+
useEffect2(() => {
|
|
683
|
+
if (!open) {
|
|
684
|
+
setForm(defaultScheduleFormValues());
|
|
685
|
+
setAgentId(initialAgentId);
|
|
686
|
+
setError(null);
|
|
687
|
+
setView({ kind: "form" });
|
|
688
|
+
setActivating(false);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (mode === "edit" && schedule != null) {
|
|
692
|
+
setForm(
|
|
693
|
+
cronToFormValues({
|
|
694
|
+
name: schedule.name,
|
|
695
|
+
task: schedule.task,
|
|
696
|
+
cron: schedule.cron,
|
|
697
|
+
timezone: schedule.timezone
|
|
698
|
+
})
|
|
699
|
+
);
|
|
700
|
+
setAgentId(schedule.agentId);
|
|
701
|
+
setView({ kind: "form" });
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
setForm(defaultScheduleFormValues());
|
|
705
|
+
setAgentId(initialAgentId);
|
|
706
|
+
setView({ kind: "form" });
|
|
707
|
+
}, [open, mode, schedule, initialAgentId]);
|
|
708
|
+
const agentOptions = useMemo(
|
|
709
|
+
() => agents.map((agent) => ({ agentId: libraryAgentId(agent), name: agent.name })),
|
|
710
|
+
[agents]
|
|
711
|
+
);
|
|
712
|
+
const selectedAgent = useMemo(
|
|
713
|
+
() => agents.find((agent) => libraryAgentId(agent) === agentId) ?? null,
|
|
714
|
+
[agents, agentId]
|
|
715
|
+
);
|
|
716
|
+
const agentLabel = selectedAgent?.name ?? savedFromCreate?.agentName ?? schedule?.agentName ?? agentOptions.find((option) => option.agentId === agentId)?.name ?? agentId;
|
|
717
|
+
const mcpMounts = useMemo(() => {
|
|
718
|
+
const mounts = selectedAgent?.agentSpec?.mcpServers ?? [];
|
|
719
|
+
return mounts.map(mountName).filter((name) => name != null).map((name) => ({ name }));
|
|
720
|
+
}, [selectedAgent]);
|
|
721
|
+
const canSubmit = useMemo(() => {
|
|
722
|
+
const cron = valuesToCron(form);
|
|
723
|
+
return form.name.trim().length > 0 && form.task.trim().length > 0 && cron.length > 0 && agentId.length > 0;
|
|
724
|
+
}, [form, agentId]);
|
|
725
|
+
const enterTestView = (saved) => {
|
|
726
|
+
setView({ kind: "test", schedule: saved });
|
|
727
|
+
setForm(
|
|
728
|
+
cronToFormValues({
|
|
729
|
+
name: saved.name,
|
|
730
|
+
task: saved.task,
|
|
731
|
+
cron: saved.cron,
|
|
732
|
+
timezone: saved.timezone
|
|
733
|
+
})
|
|
734
|
+
);
|
|
735
|
+
setAgentId(saved.agentId);
|
|
736
|
+
};
|
|
737
|
+
const handleSave = async (event) => {
|
|
738
|
+
event.preventDefault();
|
|
739
|
+
if (!canSubmit) return;
|
|
740
|
+
const cron = valuesToCron(form);
|
|
741
|
+
setSaving(true);
|
|
742
|
+
setError(null);
|
|
743
|
+
try {
|
|
744
|
+
if (isExternalEdit && schedule != null) {
|
|
745
|
+
await scheduleServer.updateSchedule({
|
|
746
|
+
id: schedule.id,
|
|
747
|
+
name: form.name.trim(),
|
|
748
|
+
task: form.task.trim(),
|
|
749
|
+
cron,
|
|
750
|
+
timezone: form.timezone,
|
|
751
|
+
status: schedule.status
|
|
752
|
+
});
|
|
753
|
+
onSaved?.();
|
|
754
|
+
onOpenChange(false);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (isCreatedEdit && view.kind === "form" && view.saved != null) {
|
|
758
|
+
const saved2 = await scheduleServer.updateSchedule({
|
|
759
|
+
id: view.saved.id,
|
|
760
|
+
name: form.name.trim(),
|
|
761
|
+
task: form.task.trim(),
|
|
762
|
+
cron,
|
|
763
|
+
timezone: form.timezone,
|
|
764
|
+
status: "paused"
|
|
765
|
+
});
|
|
766
|
+
onSaved?.();
|
|
767
|
+
enterTestView(saved2);
|
|
768
|
+
toaster?.showSuccess({
|
|
769
|
+
title: "Schedule saved as paused",
|
|
770
|
+
description: formatCadenceSummary({ cron: saved2.cron, timezone: saved2.timezone })
|
|
771
|
+
});
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const saved = await scheduleServer.createSchedule({
|
|
775
|
+
agentId,
|
|
776
|
+
name: form.name.trim(),
|
|
777
|
+
task: form.task.trim(),
|
|
778
|
+
cron,
|
|
779
|
+
timezone: form.timezone,
|
|
780
|
+
status: "paused"
|
|
781
|
+
});
|
|
782
|
+
onSaved?.();
|
|
783
|
+
enterTestView(saved);
|
|
784
|
+
toaster?.showSuccess({
|
|
785
|
+
title: "Schedule saved as paused",
|
|
786
|
+
description: formatCadenceSummary({ cron: saved.cron, timezone: saved.timezone })
|
|
787
|
+
});
|
|
788
|
+
} catch (caught) {
|
|
789
|
+
const message = caught instanceof Error ? caught.message : "Failed to save schedule";
|
|
790
|
+
setError(message);
|
|
791
|
+
} finally {
|
|
792
|
+
setSaving(false);
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
const handleActivate = async () => {
|
|
796
|
+
if (view.kind !== "test") return;
|
|
797
|
+
setActivating(true);
|
|
798
|
+
setError(null);
|
|
799
|
+
try {
|
|
800
|
+
await scheduleServer.updateSchedule({
|
|
801
|
+
id: view.schedule.id,
|
|
802
|
+
name: view.schedule.name,
|
|
803
|
+
task: view.schedule.task,
|
|
804
|
+
cron: view.schedule.cron,
|
|
805
|
+
timezone: view.schedule.timezone,
|
|
806
|
+
status: "active"
|
|
807
|
+
});
|
|
808
|
+
onSaved?.();
|
|
809
|
+
toaster?.showSuccess({ title: "Schedule activated" });
|
|
810
|
+
onOpenChange(false);
|
|
811
|
+
} catch (caught) {
|
|
812
|
+
const message = caught instanceof Error ? caught.message : "Failed to activate schedule";
|
|
813
|
+
setError(message);
|
|
814
|
+
toaster?.showError(caught);
|
|
815
|
+
} finally {
|
|
816
|
+
setActivating(false);
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
const title = view.kind === "test" ? "Test Schedule" : isExternalEdit || isCreatedEdit ? "Edit Schedule" : "New Schedule";
|
|
820
|
+
const description = view.kind === "test" ? `Recurring unattended runs of ${agentLabel}.` : isExternalEdit || isCreatedEdit ? "Update cadence and task for this schedule." : "Create a recurring unattended run for an agent.";
|
|
821
|
+
let footer;
|
|
822
|
+
if (view.kind === "test") {
|
|
823
|
+
footer = /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2", children: [
|
|
824
|
+
error != null ? /* @__PURE__ */ jsx6("p", { className: "text-failure-bg text-sm", children: error }) : null,
|
|
825
|
+
/* @__PURE__ */ jsx6(Button.Secondary, { type: "button", className: "w-full", disabled: activating, onClick: () => void handleActivate(), children: "Activate Anyway" })
|
|
826
|
+
] });
|
|
827
|
+
} else {
|
|
828
|
+
footer = /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2", children: [
|
|
829
|
+
error != null ? /* @__PURE__ */ jsx6("p", { className: "text-failure-bg text-sm", children: error }) : null,
|
|
830
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex justify-end gap-2", children: [
|
|
831
|
+
/* @__PURE__ */ jsx6(Button.Secondary, { type: "button", onClick: () => onOpenChange(false), disabled: saving, children: "Cancel" }),
|
|
832
|
+
/* @__PURE__ */ jsx6(Button.Primary, { type: "submit", form: "schedule-form", disabled: !canSubmit || saving, children: "Save" })
|
|
833
|
+
] })
|
|
834
|
+
] });
|
|
835
|
+
}
|
|
836
|
+
return /* @__PURE__ */ jsx6(
|
|
837
|
+
SideDrawer,
|
|
838
|
+
{
|
|
839
|
+
open,
|
|
840
|
+
onOpenChange,
|
|
841
|
+
title,
|
|
842
|
+
description,
|
|
843
|
+
anchor: "right",
|
|
844
|
+
size: "lg",
|
|
845
|
+
headerIcon: /* @__PURE__ */ jsx6("span", { className: "text-primary-button-bg inline-flex size-8 items-center justify-center", children: /* @__PURE__ */ jsx6(
|
|
846
|
+
Icon,
|
|
847
|
+
{
|
|
848
|
+
name: view.kind === "test" || isExternalEdit || isCreatedEdit ? "calendar" : "calendar-plus",
|
|
849
|
+
className: "size-5"
|
|
850
|
+
}
|
|
851
|
+
) }),
|
|
852
|
+
footer,
|
|
853
|
+
children: view.kind === "test" ? /* @__PURE__ */ jsx6(
|
|
854
|
+
TestScheduleScreen,
|
|
855
|
+
{
|
|
856
|
+
schedule: view.schedule,
|
|
857
|
+
agentName: agentLabel,
|
|
858
|
+
mcpMounts,
|
|
859
|
+
onEditConfiguration: () => {
|
|
860
|
+
setError(null);
|
|
861
|
+
setView({ kind: "form", saved: view.schedule });
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
) : /* @__PURE__ */ jsx6("form", { id: "schedule-form", onSubmit: (event) => void handleSave(event), children: /* @__PURE__ */ jsx6(
|
|
865
|
+
ScheduleFormFields,
|
|
866
|
+
{
|
|
867
|
+
values: form,
|
|
868
|
+
onChange: setForm,
|
|
869
|
+
agentId,
|
|
870
|
+
onAgentIdChange: isExternalEdit || isCreatedEdit ? void 0 : setAgentId,
|
|
871
|
+
agentOptions,
|
|
872
|
+
agentPickerDisabled: isExternalEdit || isCreatedEdit
|
|
873
|
+
}
|
|
874
|
+
) })
|
|
875
|
+
}
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
function ScheduleFormDrawer(props) {
|
|
879
|
+
return /* @__PURE__ */ jsx6(DraftCatalogProvider, { children: /* @__PURE__ */ jsx6(ScheduleFormDrawerBody, { ...props }) });
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// src/atoms/schedules/ScheduleLastRunsCell.tsx
|
|
883
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
884
|
+
function ScheduleLastRunsCell({ runs }) {
|
|
885
|
+
const history = lastHistoricalRuns(runs);
|
|
886
|
+
if (history.length === 0) {
|
|
887
|
+
return /* @__PURE__ */ jsx7("span", { className: "text-text-secondary text-sm", children: "\u2014" });
|
|
888
|
+
}
|
|
889
|
+
return /* @__PURE__ */ jsx7("div", { className: "flex items-center gap-1", children: history.map((run) => /* @__PURE__ */ jsx7(ScheduleRunChip, { run }, run.id)) });
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// src/atoms/schedules/SchedulesPage.tsx
|
|
893
|
+
import { Fragment, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
894
|
+
var STATUS_FILTER_OPTIONS = [
|
|
895
|
+
{ value: "all", label: "All statuses" },
|
|
896
|
+
{ value: "active", label: "Active" },
|
|
897
|
+
{ value: "paused", label: "Paused" }
|
|
898
|
+
];
|
|
899
|
+
var SCHEDULES_PAGE_SIZE_OPTIONS = [10, 25];
|
|
900
|
+
function clampPageSize(size) {
|
|
901
|
+
return Math.min(Math.max(size, 1), 25);
|
|
902
|
+
}
|
|
903
|
+
function filtersFromSearch(search) {
|
|
904
|
+
const share = readScheduleShareSearch(search);
|
|
905
|
+
return {
|
|
906
|
+
nameQuery: share.q ?? "",
|
|
907
|
+
statusFilter: share.status ?? "all",
|
|
908
|
+
agentFilter: share.agent ?? "all"
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
function ScheduleRowActions({
|
|
912
|
+
schedule,
|
|
913
|
+
running,
|
|
914
|
+
onRunNow,
|
|
915
|
+
onEdit,
|
|
916
|
+
onTogglePause,
|
|
917
|
+
onDelete
|
|
918
|
+
}) {
|
|
919
|
+
return /* @__PURE__ */ jsxs6("div", { className: "inline-flex items-center justify-end gap-1.5", children: [
|
|
920
|
+
/* @__PURE__ */ jsxs6(
|
|
921
|
+
Button.Secondary,
|
|
922
|
+
{
|
|
923
|
+
type: "button",
|
|
924
|
+
disabled: running,
|
|
925
|
+
"aria-label": `Run now ${schedule.name}`,
|
|
926
|
+
size: "large",
|
|
927
|
+
onClick: onRunNow,
|
|
928
|
+
children: [
|
|
929
|
+
/* @__PURE__ */ jsx8(Icon, { name: running ? "loader" : "play", className: cn("size-3.5", running && "animate-spin") }),
|
|
930
|
+
"Run now"
|
|
931
|
+
]
|
|
932
|
+
}
|
|
933
|
+
),
|
|
934
|
+
/* @__PURE__ */ jsxs6(
|
|
935
|
+
DropdownMenu,
|
|
936
|
+
{
|
|
937
|
+
align: "end",
|
|
938
|
+
trigger: /* @__PURE__ */ jsx8(
|
|
939
|
+
"button",
|
|
940
|
+
{
|
|
941
|
+
type: "button",
|
|
942
|
+
className: auiButtonClass({ variant: "ghost", size: "icon" }),
|
|
943
|
+
"aria-label": `Actions for ${schedule.name}`,
|
|
944
|
+
children: /* @__PURE__ */ jsx8(Icon, { name: "ellipsis" })
|
|
945
|
+
}
|
|
946
|
+
),
|
|
947
|
+
children: [
|
|
948
|
+
/* @__PURE__ */ jsxs6(DropdownMenuItem, { onClick: onEdit, children: [
|
|
949
|
+
/* @__PURE__ */ jsx8(Icon, { name: "pencil", className: "size-3.5" }),
|
|
950
|
+
"Edit"
|
|
951
|
+
] }),
|
|
952
|
+
/* @__PURE__ */ jsxs6(DropdownMenuItem, { onClick: onTogglePause, children: [
|
|
953
|
+
/* @__PURE__ */ jsx8(Icon, { name: schedule.status === "active" ? "pause" : "play", className: "size-3.5" }),
|
|
954
|
+
schedule.status === "active" ? "Pause" : "Resume"
|
|
955
|
+
] }),
|
|
956
|
+
/* @__PURE__ */ jsxs6(DropdownMenuItem, { className: "text-failure-bg focus:text-failure-bg", onClick: onDelete, children: [
|
|
957
|
+
/* @__PURE__ */ jsx8(Icon, { name: "trash", className: "size-3.5" }),
|
|
958
|
+
"Delete"
|
|
959
|
+
] })
|
|
960
|
+
]
|
|
961
|
+
}
|
|
962
|
+
)
|
|
963
|
+
] });
|
|
964
|
+
}
|
|
965
|
+
function SchedulesPage() {
|
|
966
|
+
const scheduleServer = useScheduleServer();
|
|
967
|
+
const server = useServer();
|
|
968
|
+
const toaster = useToasterOptional();
|
|
969
|
+
const [schedules, setSchedules] = useState3([]);
|
|
970
|
+
const [runsByScheduleId, setRunsByScheduleId] = useState3({});
|
|
971
|
+
const [runsLoading, setRunsLoading] = useState3(false);
|
|
972
|
+
const [runningScheduleIds, setRunningScheduleIds] = useState3(() => /* @__PURE__ */ new Set());
|
|
973
|
+
const [agentOptions, setAgentOptions] = useState3([]);
|
|
974
|
+
const [loading, setLoading] = useState3(true);
|
|
975
|
+
const [error, setError] = useState3(null);
|
|
976
|
+
const [nameQuery, setNameQuery] = useState3(() => filtersFromSearch(window.location.search).nameQuery);
|
|
977
|
+
const [statusFilter, setStatusFilter] = useState3(
|
|
978
|
+
() => filtersFromSearch(window.location.search).statusFilter
|
|
979
|
+
);
|
|
980
|
+
const [agentFilter, setAgentFilter] = useState3(() => filtersFromSearch(window.location.search).agentFilter);
|
|
981
|
+
const [drawer, setDrawer] = useState3({ kind: "closed" });
|
|
982
|
+
const [pendingDelete, setPendingDelete] = useState3(null);
|
|
983
|
+
const [pageSize, setPageSize] = useState3(() => clampPageSize(DEFAULT_TABLE_PAGE_SIZE));
|
|
984
|
+
const [pageToken, setPageToken] = useState3(void 0);
|
|
985
|
+
const [nextPageToken, setNextPageToken] = useState3(void 0);
|
|
986
|
+
const [prevTokenStack, setPrevTokenStack] = useState3([]);
|
|
987
|
+
const loadGenRef = useRef(0);
|
|
988
|
+
const didConsumeIsNewRef = useRef(false);
|
|
989
|
+
const loadRunsForSchedules = useCallback(
|
|
990
|
+
async ({ rows, gen }) => {
|
|
991
|
+
if (rows.length === 0) {
|
|
992
|
+
if (gen === loadGenRef.current) setRunsByScheduleId({});
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
setRunsLoading(true);
|
|
996
|
+
try {
|
|
997
|
+
const results = await Promise.allSettled(
|
|
998
|
+
rows.map((schedule) => scheduleServer.listScheduleRuns({ scheduleId: schedule.id }))
|
|
999
|
+
);
|
|
1000
|
+
if (gen !== loadGenRef.current) return;
|
|
1001
|
+
setRunsByScheduleId(
|
|
1002
|
+
Object.fromEntries(
|
|
1003
|
+
rows.map((schedule, index) => {
|
|
1004
|
+
const result = results[index];
|
|
1005
|
+
const runs = result?.status === "fulfilled" ? result.value : [];
|
|
1006
|
+
return [schedule.id, runs];
|
|
1007
|
+
})
|
|
1008
|
+
)
|
|
1009
|
+
);
|
|
1010
|
+
} finally {
|
|
1011
|
+
if (gen === loadGenRef.current) setRunsLoading(false);
|
|
1012
|
+
}
|
|
1013
|
+
},
|
|
1014
|
+
[scheduleServer]
|
|
1015
|
+
);
|
|
1016
|
+
const loadSchedules = useCallback(
|
|
1017
|
+
async ({ token, size, agentId }) => {
|
|
1018
|
+
const gen = ++loadGenRef.current;
|
|
1019
|
+
setLoading(true);
|
|
1020
|
+
setError(null);
|
|
1021
|
+
try {
|
|
1022
|
+
const page = await scheduleServer.listSchedules({
|
|
1023
|
+
limit: clampPageSize(size),
|
|
1024
|
+
...token === void 0 || token === "" ? {} : { pageToken: token },
|
|
1025
|
+
...agentId === "all" ? {} : { agentIds: [agentId] }
|
|
1026
|
+
});
|
|
1027
|
+
if (gen !== loadGenRef.current) return;
|
|
1028
|
+
setSchedules(page.data);
|
|
1029
|
+
setNextPageToken(page.nextPageToken);
|
|
1030
|
+
void loadRunsForSchedules({ rows: page.data, gen });
|
|
1031
|
+
} catch (caught) {
|
|
1032
|
+
if (gen !== loadGenRef.current) return;
|
|
1033
|
+
const message = caught instanceof Error ? caught.message : "Failed to load schedules";
|
|
1034
|
+
setError(message);
|
|
1035
|
+
setSchedules([]);
|
|
1036
|
+
setRunsByScheduleId({});
|
|
1037
|
+
setNextPageToken(void 0);
|
|
1038
|
+
} finally {
|
|
1039
|
+
if (gen === loadGenRef.current) setLoading(false);
|
|
1040
|
+
}
|
|
1041
|
+
},
|
|
1042
|
+
[scheduleServer, loadRunsForSchedules]
|
|
1043
|
+
);
|
|
1044
|
+
const resetToFirstPage = useCallback(
|
|
1045
|
+
(next) => {
|
|
1046
|
+
const size = next?.size ?? pageSize;
|
|
1047
|
+
const agentId = next?.agentId ?? agentFilter;
|
|
1048
|
+
setPageToken(void 0);
|
|
1049
|
+
setPrevTokenStack([]);
|
|
1050
|
+
void loadSchedules({ token: void 0, size, agentId });
|
|
1051
|
+
},
|
|
1052
|
+
[agentFilter, loadSchedules, pageSize]
|
|
1053
|
+
);
|
|
1054
|
+
useEffect3(() => {
|
|
1055
|
+
replaceScheduleShareSearch({
|
|
1056
|
+
agent: agentFilter === "all" ? null : agentFilter,
|
|
1057
|
+
status: statusFilter === "all" ? null : statusFilter,
|
|
1058
|
+
q: nameQuery.trim().length === 0 ? null : nameQuery
|
|
1059
|
+
});
|
|
1060
|
+
}, [agentFilter, statusFilter, nameQuery]);
|
|
1061
|
+
useEffect3(() => {
|
|
1062
|
+
if (didConsumeIsNewRef.current) return;
|
|
1063
|
+
const share = readScheduleShareSearch(window.location.search);
|
|
1064
|
+
if (!share.isNew) return;
|
|
1065
|
+
didConsumeIsNewRef.current = true;
|
|
1066
|
+
setDrawer({
|
|
1067
|
+
kind: "create",
|
|
1068
|
+
...share.agent != null ? { agentId: share.agent } : {}
|
|
1069
|
+
});
|
|
1070
|
+
replaceScheduleShareSearch({ isNew: null });
|
|
1071
|
+
}, []);
|
|
1072
|
+
useEffect3(() => {
|
|
1073
|
+
const syncFromUrl = () => {
|
|
1074
|
+
const next = filtersFromSearch(window.location.search);
|
|
1075
|
+
setNameQuery(next.nameQuery);
|
|
1076
|
+
setStatusFilter(next.statusFilter);
|
|
1077
|
+
setAgentFilter((current) => {
|
|
1078
|
+
if (current === next.agentFilter) return current;
|
|
1079
|
+
setPageToken(void 0);
|
|
1080
|
+
setPrevTokenStack([]);
|
|
1081
|
+
return next.agentFilter;
|
|
1082
|
+
});
|
|
1083
|
+
};
|
|
1084
|
+
window.addEventListener("popstate", syncFromUrl);
|
|
1085
|
+
return () => {
|
|
1086
|
+
window.removeEventListener("popstate", syncFromUrl);
|
|
1087
|
+
};
|
|
1088
|
+
}, []);
|
|
1089
|
+
useEffect3(() => {
|
|
1090
|
+
void loadSchedules({ token: pageToken, size: pageSize, agentId: agentFilter });
|
|
1091
|
+
}, [agentFilter, pageSize, pageToken, loadSchedules]);
|
|
1092
|
+
useEffect3(() => {
|
|
1093
|
+
let cancelled = false;
|
|
1094
|
+
void searchAllAgents(server).then((rows) => {
|
|
1095
|
+
if (cancelled) return;
|
|
1096
|
+
setAgentOptions(rows.map((agent) => ({ agentId: libraryAgentId(agent), name: agent.name })));
|
|
1097
|
+
}).catch(() => void 0);
|
|
1098
|
+
return () => {
|
|
1099
|
+
cancelled = true;
|
|
1100
|
+
};
|
|
1101
|
+
}, [server]);
|
|
1102
|
+
const agentNameById = useMemo2(() => {
|
|
1103
|
+
const map = /* @__PURE__ */ new Map();
|
|
1104
|
+
for (const agent of agentOptions) {
|
|
1105
|
+
map.set(agent.agentId, agent.name);
|
|
1106
|
+
}
|
|
1107
|
+
return map;
|
|
1108
|
+
}, [agentOptions]);
|
|
1109
|
+
const filtered = useMemo2(() => {
|
|
1110
|
+
const q = nameQuery.trim().toLowerCase();
|
|
1111
|
+
return schedules.filter((schedule) => {
|
|
1112
|
+
if (statusFilter !== "all" && schedule.status !== statusFilter) return false;
|
|
1113
|
+
if (q.length > 0 && !schedule.name.toLowerCase().includes(q)) return false;
|
|
1114
|
+
return true;
|
|
1115
|
+
});
|
|
1116
|
+
}, [schedules, nameQuery, statusFilter]);
|
|
1117
|
+
const hasPageNav = prevTokenStack.length > 0 || nextPageToken != null;
|
|
1118
|
+
const handleTogglePause = async (schedule) => {
|
|
1119
|
+
const nextStatus = schedule.status === "active" ? "paused" : "active";
|
|
1120
|
+
try {
|
|
1121
|
+
await scheduleServer.updateSchedule({ ...schedule, status: nextStatus });
|
|
1122
|
+
await loadSchedules({ token: pageToken, size: pageSize, agentId: agentFilter });
|
|
1123
|
+
} catch (caught) {
|
|
1124
|
+
toaster?.showError(caught);
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
const handleRunNow = async (schedule) => {
|
|
1128
|
+
setRunningScheduleIds((prev) => new Set(prev).add(schedule.id));
|
|
1129
|
+
try {
|
|
1130
|
+
await scheduleServer.createScheduleRun({ scheduleId: schedule.id });
|
|
1131
|
+
toaster?.showSuccess({ title: "Run started" });
|
|
1132
|
+
const runs = await scheduleServer.listScheduleRuns({ scheduleId: schedule.id });
|
|
1133
|
+
setRunsByScheduleId((prev) => ({ ...prev, [schedule.id]: runs }));
|
|
1134
|
+
} catch (caught) {
|
|
1135
|
+
toaster?.showError(caught);
|
|
1136
|
+
} finally {
|
|
1137
|
+
setRunningScheduleIds((prev) => {
|
|
1138
|
+
const next = new Set(prev);
|
|
1139
|
+
next.delete(schedule.id);
|
|
1140
|
+
return next;
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
const handleDelete = async (schedule) => {
|
|
1145
|
+
setPendingDelete(null);
|
|
1146
|
+
try {
|
|
1147
|
+
await scheduleServer.deleteSchedule({ id: schedule.id });
|
|
1148
|
+
resetToFirstPage();
|
|
1149
|
+
} catch (caught) {
|
|
1150
|
+
toaster?.showError(caught);
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
const goNext = () => {
|
|
1154
|
+
if (nextPageToken == null) return;
|
|
1155
|
+
setPrevTokenStack((stack) => [...stack, pageToken ?? ""]);
|
|
1156
|
+
setPageToken(nextPageToken);
|
|
1157
|
+
};
|
|
1158
|
+
const goPrev = () => {
|
|
1159
|
+
if (prevTokenStack.length === 0) return;
|
|
1160
|
+
const stack = [...prevTokenStack];
|
|
1161
|
+
const prev = stack.pop();
|
|
1162
|
+
setPrevTokenStack(stack);
|
|
1163
|
+
setPageToken(prev === "" ? void 0 : prev);
|
|
1164
|
+
};
|
|
1165
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex h-full min-h-0 flex-col bg-primary-bg", children: [
|
|
1166
|
+
/* @__PURE__ */ jsx8(
|
|
1167
|
+
PageHeader,
|
|
1168
|
+
{
|
|
1169
|
+
title: "Scheduled Agents",
|
|
1170
|
+
end: /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
1171
|
+
/* @__PURE__ */ jsx8("div", { className: "w-full sm:w-56", children: /* @__PURE__ */ jsx8(SearchInput_default, { query: nameQuery, setQuery: setNameQuery, placeholder: "Search schedules by name" }) }),
|
|
1172
|
+
/* @__PURE__ */ jsx8(
|
|
1173
|
+
PopoverSelect,
|
|
1174
|
+
{
|
|
1175
|
+
value: statusFilter,
|
|
1176
|
+
onValueChange: setStatusFilter,
|
|
1177
|
+
options: STATUS_FILTER_OPTIONS,
|
|
1178
|
+
className: "sm:w-40",
|
|
1179
|
+
"aria-label": "Filter by status"
|
|
1180
|
+
}
|
|
1181
|
+
),
|
|
1182
|
+
/* @__PURE__ */ jsx8(
|
|
1183
|
+
PopoverSelect,
|
|
1184
|
+
{
|
|
1185
|
+
value: agentFilter,
|
|
1186
|
+
onValueChange: (value) => {
|
|
1187
|
+
setAgentFilter(value);
|
|
1188
|
+
setPageToken(void 0);
|
|
1189
|
+
setPrevTokenStack([]);
|
|
1190
|
+
},
|
|
1191
|
+
options: [
|
|
1192
|
+
{ value: "all", label: "All agents" },
|
|
1193
|
+
...agentOptions.map((agent) => ({ value: agent.agentId, label: agent.name }))
|
|
1194
|
+
],
|
|
1195
|
+
className: "sm:w-40",
|
|
1196
|
+
"aria-label": "Filter by agent"
|
|
1197
|
+
}
|
|
1198
|
+
),
|
|
1199
|
+
/* @__PURE__ */ jsxs6(
|
|
1200
|
+
Button.Primary,
|
|
1201
|
+
{
|
|
1202
|
+
type: "button",
|
|
1203
|
+
onClick: () => setDrawer({
|
|
1204
|
+
kind: "create",
|
|
1205
|
+
agentId: agentFilter !== "all" ? agentFilter : void 0
|
|
1206
|
+
}),
|
|
1207
|
+
children: [
|
|
1208
|
+
/* @__PURE__ */ jsx8(Icon, { name: "plus", className: "size-3.5" }),
|
|
1209
|
+
"Create Schedule"
|
|
1210
|
+
]
|
|
1211
|
+
}
|
|
1212
|
+
)
|
|
1213
|
+
] })
|
|
1214
|
+
}
|
|
1215
|
+
),
|
|
1216
|
+
/* @__PURE__ */ jsx8("div", { className: "min-h-0 flex-1 overflow-auto px-4 py-4 md:px-6", children: loading ? /* @__PURE__ */ jsx8("div", { className: "flex flex-col gap-2", role: "status", "aria-label": "Loading schedules", children: Array.from({ length: 5 }, (_, i) => /* @__PURE__ */ jsx8(Skeleton, { className: "h-12 w-full rounded-md" }, i)) }) : error != null ? /* @__PURE__ */ jsx8("p", { className: "text-failure-bg px-3 py-8 text-center text-sm", children: error }) : schedules.length === 0 ? /* @__PURE__ */ jsx8(EmptyScreen, { title: "No Schedules Found", description: "Create one to get started.", className: "min-h-full" }) : filtered.length === 0 ? /* @__PURE__ */ jsxs6("div", { className: "flex min-h-full flex-col", children: [
|
|
1217
|
+
/* @__PURE__ */ jsx8(EmptyScreen, { title: "No Schedules Found", description: "No schedules match your filters.", className: "flex-1" }),
|
|
1218
|
+
hasPageNav ? /* @__PURE__ */ jsx8(
|
|
1219
|
+
TableTokenPagination,
|
|
1220
|
+
{
|
|
1221
|
+
pageSize,
|
|
1222
|
+
rowCount: 0,
|
|
1223
|
+
canPrev: prevTokenStack.length > 0,
|
|
1224
|
+
canNext: nextPageToken != null,
|
|
1225
|
+
onPrev: goPrev,
|
|
1226
|
+
onNext: goNext,
|
|
1227
|
+
pageSizeOptions: SCHEDULES_PAGE_SIZE_OPTIONS,
|
|
1228
|
+
onPageSizeChange: (size) => {
|
|
1229
|
+
const next = clampPageSize(size);
|
|
1230
|
+
setPageSize(next);
|
|
1231
|
+
setPageToken(void 0);
|
|
1232
|
+
setPrevTokenStack([]);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
) : null
|
|
1236
|
+
] }) : /* @__PURE__ */ jsxs6("div", { className: "overflow-hidden rounded-lg border border-border", children: [
|
|
1237
|
+
/* @__PURE__ */ jsxs6(Table, { className: "min-w-[48rem]", children: [
|
|
1238
|
+
/* @__PURE__ */ jsx8(TableHeader, { children: /* @__PURE__ */ jsxs6(TableRow, { className: "hover:bg-transparent", children: [
|
|
1239
|
+
/* @__PURE__ */ jsx8(TableHead, { children: "Name" }),
|
|
1240
|
+
/* @__PURE__ */ jsx8(TableHead, { children: "Agent" }),
|
|
1241
|
+
/* @__PURE__ */ jsx8(TableHead, { children: "Cadence" }),
|
|
1242
|
+
/* @__PURE__ */ jsx8(TableHead, { children: "Status" }),
|
|
1243
|
+
/* @__PURE__ */ jsx8(TableHead, { children: "Last 5 runs" }),
|
|
1244
|
+
/* @__PURE__ */ jsx8(TableHead, { children: /* @__PURE__ */ jsx8("span", { className: "sr-only", children: "Actions" }) })
|
|
1245
|
+
] }) }),
|
|
1246
|
+
/* @__PURE__ */ jsx8(TableBody, { children: filtered.map((schedule) => {
|
|
1247
|
+
const cadence = formatCadenceSummary({ cron: schedule.cron, timezone: schedule.timezone });
|
|
1248
|
+
const agentLabel = schedule.agentName ?? agentNameById.get(schedule.agentId) ?? schedule.agentId;
|
|
1249
|
+
return /* @__PURE__ */ jsxs6(TableRow, { children: [
|
|
1250
|
+
/* @__PURE__ */ jsx8(TableCell, { className: "text-text-primary font-medium", children: /* @__PURE__ */ jsx8(
|
|
1251
|
+
"button",
|
|
1252
|
+
{
|
|
1253
|
+
type: "button",
|
|
1254
|
+
className: "cursor-pointer text-left",
|
|
1255
|
+
onClick: () => setDrawer({ kind: "edit", schedule }),
|
|
1256
|
+
children: schedule.name
|
|
1257
|
+
}
|
|
1258
|
+
) }),
|
|
1259
|
+
/* @__PURE__ */ jsx8(TableCell, { children: agentLabel }),
|
|
1260
|
+
/* @__PURE__ */ jsx8(TableCell, { children: cadence }),
|
|
1261
|
+
/* @__PURE__ */ jsx8(TableCell, { children: /* @__PURE__ */ jsx8(ScheduleStatusBadge, { status: schedule.status }) }),
|
|
1262
|
+
/* @__PURE__ */ jsx8(TableCell, { children: runsLoading ? /* @__PURE__ */ jsx8("span", { className: "text-text-secondary text-sm", children: "\u2026" }) : /* @__PURE__ */ jsx8(ScheduleLastRunsCell, { runs: runsByScheduleId[schedule.id] ?? [] }) }),
|
|
1263
|
+
/* @__PURE__ */ jsx8(TableCell, { className: "text-right", children: /* @__PURE__ */ jsx8(
|
|
1264
|
+
ScheduleRowActions,
|
|
1265
|
+
{
|
|
1266
|
+
schedule,
|
|
1267
|
+
running: runningScheduleIds.has(schedule.id),
|
|
1268
|
+
onRunNow: () => void handleRunNow(schedule),
|
|
1269
|
+
onEdit: () => setDrawer({ kind: "edit", schedule }),
|
|
1270
|
+
onTogglePause: () => void handleTogglePause(schedule),
|
|
1271
|
+
onDelete: () => setPendingDelete(schedule)
|
|
1272
|
+
}
|
|
1273
|
+
) })
|
|
1274
|
+
] }, schedule.id);
|
|
1275
|
+
}) })
|
|
1276
|
+
] }),
|
|
1277
|
+
(filtered.length > 0 || hasPageNav) && /* @__PURE__ */ jsx8(
|
|
1278
|
+
TableTokenPagination,
|
|
1279
|
+
{
|
|
1280
|
+
pageSize,
|
|
1281
|
+
rowCount: filtered.length,
|
|
1282
|
+
canPrev: prevTokenStack.length > 0,
|
|
1283
|
+
canNext: nextPageToken != null,
|
|
1284
|
+
onPrev: goPrev,
|
|
1285
|
+
onNext: goNext,
|
|
1286
|
+
pageSizeOptions: SCHEDULES_PAGE_SIZE_OPTIONS,
|
|
1287
|
+
onPageSizeChange: (size) => {
|
|
1288
|
+
const next = clampPageSize(size);
|
|
1289
|
+
setPageSize(next);
|
|
1290
|
+
setPageToken(void 0);
|
|
1291
|
+
setPrevTokenStack([]);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
)
|
|
1295
|
+
] }) }),
|
|
1296
|
+
drawer.kind === "create" ? /* @__PURE__ */ jsx8(
|
|
1297
|
+
ScheduleFormDrawer,
|
|
1298
|
+
{
|
|
1299
|
+
open: true,
|
|
1300
|
+
mode: "create",
|
|
1301
|
+
initialAgentId: drawer.agentId ?? "",
|
|
1302
|
+
onOpenChange: (open) => {
|
|
1303
|
+
if (!open) setDrawer({ kind: "closed" });
|
|
1304
|
+
},
|
|
1305
|
+
onSaved: () => resetToFirstPage()
|
|
1306
|
+
}
|
|
1307
|
+
) : null,
|
|
1308
|
+
drawer.kind === "edit" ? /* @__PURE__ */ jsx8(
|
|
1309
|
+
ScheduleFormDrawer,
|
|
1310
|
+
{
|
|
1311
|
+
open: true,
|
|
1312
|
+
mode: "edit",
|
|
1313
|
+
schedule: drawer.schedule,
|
|
1314
|
+
onOpenChange: (open) => {
|
|
1315
|
+
if (!open) setDrawer({ kind: "closed" });
|
|
1316
|
+
},
|
|
1317
|
+
onSaved: () => void loadSchedules({ token: pageToken, size: pageSize, agentId: agentFilter })
|
|
1318
|
+
}
|
|
1319
|
+
) : null,
|
|
1320
|
+
pendingDelete != null ? /* @__PURE__ */ jsxs6(
|
|
1321
|
+
Dialog,
|
|
1322
|
+
{
|
|
1323
|
+
open: true,
|
|
1324
|
+
onOpenChange: (open) => {
|
|
1325
|
+
if (!open) setPendingDelete(null);
|
|
1326
|
+
},
|
|
1327
|
+
"aria-label": "Delete schedule",
|
|
1328
|
+
className: "max-w-md",
|
|
1329
|
+
children: [
|
|
1330
|
+
/* @__PURE__ */ jsx8(DialogContent, { children: /* @__PURE__ */ jsxs6(DialogHeader, { children: [
|
|
1331
|
+
/* @__PURE__ */ jsx8(DialogTitle, { children: "Delete schedule" }),
|
|
1332
|
+
/* @__PURE__ */ jsxs6("p", { className: "text-text-secondary text-sm", children: [
|
|
1333
|
+
"\u201C",
|
|
1334
|
+
pendingDelete.name,
|
|
1335
|
+
"\u201D will stop running. This cannot be undone."
|
|
1336
|
+
] })
|
|
1337
|
+
] }) }),
|
|
1338
|
+
/* @__PURE__ */ jsxs6(DialogFooter, { children: [
|
|
1339
|
+
/* @__PURE__ */ jsx8(Button.Secondary, { type: "button", onClick: () => setPendingDelete(null), children: "Cancel" }),
|
|
1340
|
+
/* @__PURE__ */ jsx8(Button.Destructive, { type: "button", onClick: () => void handleDelete(pendingDelete), children: "Delete" })
|
|
1341
|
+
] })
|
|
1342
|
+
]
|
|
1343
|
+
}
|
|
1344
|
+
) : null
|
|
1345
|
+
] });
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
export {
|
|
1349
|
+
ScheduleFormFields,
|
|
1350
|
+
ScheduleRunChip,
|
|
1351
|
+
ScheduleStatusBadge,
|
|
1352
|
+
TestScheduleScreen,
|
|
1353
|
+
ScheduleFormDrawer,
|
|
1354
|
+
ScheduleLastRunsCell,
|
|
1355
|
+
SchedulesPage
|
|
1356
|
+
};
|
|
1357
|
+
//# sourceMappingURL=chunk-GXYAOAK5.js.map
|