@chrysb/alphaclaw 0.6.2-beta.5 → 0.6.2-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/public/css/cron.css +91 -39
- package/lib/public/js/components/cron-tab/cron-calendar.js +287 -164
- package/lib/public/js/components/cron-tab/cron-insights-panel.js +325 -0
- package/lib/public/js/components/cron-tab/cron-job-detail.js +38 -363
- package/lib/public/js/components/cron-tab/cron-job-settings-card.js +233 -0
- package/lib/public/js/components/cron-tab/cron-overview.js +40 -19
- package/lib/public/js/components/cron-tab/cron-prompt-editor.js +173 -0
- package/lib/public/js/components/cron-tab/cron-run-history-panel.js +69 -56
- package/lib/public/js/components/cron-tab/cron-runs-trend-card.js +20 -2
- package/lib/public/js/components/cron-tab/index.js +170 -78
- package/lib/public/js/components/file-viewer/editor-surface.js +5 -1
- package/lib/public/js/components/file-viewer/use-editor-line-number-sync.js +36 -0
- package/lib/public/js/components/file-viewer/use-file-viewer.js +7 -23
- package/lib/public/js/components/file-viewer/utils.js +1 -5
- package/lib/public/js/components/onboarding/welcome-pairing-step.js +88 -59
- package/package.json +1 -1
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { h } from "https://esm.sh/preact";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useState,
|
|
7
|
+
} from "https://esm.sh/preact/hooks";
|
|
3
8
|
import htm from "https://esm.sh/htm";
|
|
4
|
-
import {
|
|
9
|
+
import { SegmentedControl } from "../segmented-control.js";
|
|
5
10
|
import { Tooltip } from "../tooltip.js";
|
|
6
11
|
import { formatCost, formatTokenCount } from "./cron-helpers.js";
|
|
7
12
|
import { formatCronScheduleLabel } from "./cron-helpers.js";
|
|
@@ -24,7 +29,8 @@ const formatHourLabel = (hourOfDay) => {
|
|
|
24
29
|
});
|
|
25
30
|
};
|
|
26
31
|
|
|
27
|
-
const buildCellKey = (dayKey, hourOfDay) =>
|
|
32
|
+
const buildCellKey = (dayKey, hourOfDay) =>
|
|
33
|
+
`${String(dayKey || "")}:${hourOfDay}`;
|
|
28
34
|
const toLocalDayKey = (valueMs) => {
|
|
29
35
|
const dateValue = new Date(valueMs);
|
|
30
36
|
const year = dateValue.getFullYear();
|
|
@@ -49,31 +55,53 @@ const slotStateClassName = ({
|
|
|
49
55
|
const tierClassName = tierClassNameByKey[tokenTier] || tierClassNameByKey.low;
|
|
50
56
|
if (!isPast) return `${tierClassName} cron-calendar-slot-upcoming`;
|
|
51
57
|
if (mappedStatus === "ok") return `${tierClassName} cron-calendar-slot-ok`;
|
|
52
|
-
if (mappedStatus === "error")
|
|
53
|
-
|
|
58
|
+
if (mappedStatus === "error")
|
|
59
|
+
return `${tierClassName} cron-calendar-slot-error`;
|
|
60
|
+
if (mappedStatus === "skipped")
|
|
61
|
+
return `${tierClassName} cron-calendar-slot-skipped`;
|
|
54
62
|
return `${tierClassName} cron-calendar-slot-past`;
|
|
55
63
|
};
|
|
56
64
|
|
|
57
65
|
const renderLegend = () => html`
|
|
58
66
|
<div class="cron-calendar-legend">
|
|
59
67
|
<span class="cron-calendar-legend-label">Token intensity</span>
|
|
60
|
-
<span class="cron-calendar-legend-pill cron-calendar-slot-tier-unknown"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<span class="cron-calendar-legend-pill cron-calendar-slot-tier-
|
|
64
|
-
|
|
68
|
+
<span class="cron-calendar-legend-pill cron-calendar-slot-tier-unknown"
|
|
69
|
+
>No usage</span
|
|
70
|
+
>
|
|
71
|
+
<span class="cron-calendar-legend-pill cron-calendar-slot-tier-low"
|
|
72
|
+
>Low</span
|
|
73
|
+
>
|
|
74
|
+
<span class="cron-calendar-legend-pill cron-calendar-slot-tier-medium"
|
|
75
|
+
>Medium</span
|
|
76
|
+
>
|
|
77
|
+
<span class="cron-calendar-legend-pill cron-calendar-slot-tier-high"
|
|
78
|
+
>High</span
|
|
79
|
+
>
|
|
80
|
+
<span class="cron-calendar-legend-pill cron-calendar-slot-tier-very-high"
|
|
81
|
+
>Very high</span
|
|
82
|
+
>
|
|
65
83
|
</div>
|
|
66
84
|
`;
|
|
67
85
|
|
|
68
86
|
const kNowRefreshMs = 60 * 1000;
|
|
69
87
|
const kCalendarExpandedUiSettingKey = "cronCalendarExpanded";
|
|
88
|
+
const kCalendarViewUiSettingKey = "cronCalendarView";
|
|
89
|
+
const kCalendarViewUpcoming = "upcoming";
|
|
90
|
+
const kCalendarViewCalendar = "calendar";
|
|
91
|
+
const kCalendarViewOptions = [
|
|
92
|
+
{ label: "Up next", value: kCalendarViewUpcoming },
|
|
93
|
+
{ label: "Calendar", value: kCalendarViewCalendar },
|
|
94
|
+
];
|
|
70
95
|
const kRunWindow7dMs = 7 * 24 * 60 * 60 * 1000;
|
|
71
96
|
const kSlotRunToleranceMs = 45 * 60 * 1000;
|
|
72
97
|
const kUnknownTier = "unknown";
|
|
73
98
|
|
|
74
99
|
const formatUpcomingTime = (timestampMs) => {
|
|
75
100
|
const dateValue = new Date(timestampMs);
|
|
76
|
-
return dateValue.toLocaleTimeString([], {
|
|
101
|
+
return dateValue.toLocaleTimeString([], {
|
|
102
|
+
hour: "numeric",
|
|
103
|
+
minute: "2-digit",
|
|
104
|
+
});
|
|
77
105
|
};
|
|
78
106
|
|
|
79
107
|
const getRunTotalTokens = (entry = {}) => {
|
|
@@ -110,40 +138,58 @@ const getRunEstimatedCost = (entry = {}) => {
|
|
|
110
138
|
return null;
|
|
111
139
|
};
|
|
112
140
|
|
|
113
|
-
const buildRunSummaryByJobId = ({
|
|
141
|
+
const buildRunSummaryByJobId = ({
|
|
142
|
+
runsByJobId = {},
|
|
143
|
+
nowMs = Date.now(),
|
|
144
|
+
} = {}) => {
|
|
114
145
|
const cutoffMs = Number(nowMs || Date.now()) - kRunWindow7dMs;
|
|
115
|
-
return Object.entries(runsByJobId || {}).reduce(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
totalCost,
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
return Object.entries(runsByJobId || {}).reduce(
|
|
147
|
+
(accumulator, [jobId, runResult]) => {
|
|
148
|
+
const entries = Array.isArray(runResult?.entries)
|
|
149
|
+
? runResult.entries
|
|
150
|
+
: [];
|
|
151
|
+
const recentEntries = entries.filter((entry) => {
|
|
152
|
+
const timestampMs = Number(entry?.ts || 0);
|
|
153
|
+
return (
|
|
154
|
+
Number.isFinite(timestampMs) &&
|
|
155
|
+
timestampMs >= cutoffMs &&
|
|
156
|
+
timestampMs <= nowMs
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
const runCount = recentEntries.length;
|
|
160
|
+
const totalTokens = recentEntries.reduce(
|
|
161
|
+
(sum, entry) => sum + Number(getRunTotalTokens(entry) || 0),
|
|
162
|
+
0,
|
|
163
|
+
);
|
|
164
|
+
const totalCost = recentEntries.reduce((sum, entry) => {
|
|
165
|
+
const cost = getRunEstimatedCost(entry);
|
|
166
|
+
return sum + Number(cost == null ? 0 : cost);
|
|
167
|
+
}, 0);
|
|
168
|
+
accumulator[String(jobId || "")] = {
|
|
169
|
+
runCount,
|
|
170
|
+
totalTokens,
|
|
171
|
+
totalCost,
|
|
172
|
+
avgTokensPerRun: runCount > 0 ? Math.round(totalTokens / runCount) : 0,
|
|
173
|
+
avgCostPerRun: runCount > 0 ? totalCost / runCount : 0,
|
|
174
|
+
};
|
|
175
|
+
return accumulator;
|
|
176
|
+
},
|
|
177
|
+
{},
|
|
178
|
+
);
|
|
139
179
|
};
|
|
140
180
|
|
|
141
|
-
const mapRunsToSlots = ({
|
|
181
|
+
const mapRunsToSlots = ({
|
|
182
|
+
slots = [],
|
|
183
|
+
runsByJobId = {},
|
|
184
|
+
nowMs = Date.now(),
|
|
185
|
+
} = {}) => {
|
|
142
186
|
const runsBySlotKey = {};
|
|
143
187
|
const consumedRunTimestampsByJobId = {};
|
|
144
188
|
const runEntriesByJobId = Object.entries(runsByJobId || {}).reduce(
|
|
145
189
|
(accumulator, [jobId, runResult]) => {
|
|
146
|
-
const entries = Array.isArray(runResult?.entries)
|
|
190
|
+
const entries = Array.isArray(runResult?.entries)
|
|
191
|
+
? runResult.entries
|
|
192
|
+
: [];
|
|
147
193
|
const normalizedEntries = entries
|
|
148
194
|
.map((entry) => ({ ...entry, ts: Number(entry?.ts || 0) }))
|
|
149
195
|
.filter((entry) => Number.isFinite(entry.ts) && entry.ts > 0)
|
|
@@ -205,14 +251,14 @@ const classifyTokenTier = ({
|
|
|
205
251
|
} = {}) => {
|
|
206
252
|
if (!enabled) return "disabled";
|
|
207
253
|
const safeValue = Number(tokenValue || 0);
|
|
208
|
-
if (!Number.isFinite(safeValue) || safeValue <= 0 || !thresholds)
|
|
254
|
+
if (!Number.isFinite(safeValue) || safeValue <= 0 || !thresholds)
|
|
255
|
+
return kUnknownTier;
|
|
209
256
|
if (safeValue <= thresholds.q1) return "low";
|
|
210
257
|
if (safeValue <= thresholds.q2) return "medium";
|
|
211
258
|
if (safeValue <= thresholds.p90) return "high";
|
|
212
259
|
return "very-high";
|
|
213
260
|
};
|
|
214
261
|
|
|
215
|
-
|
|
216
262
|
const buildJobTooltipText = ({
|
|
217
263
|
jobName = "",
|
|
218
264
|
job = null,
|
|
@@ -223,18 +269,25 @@ const buildJobTooltipText = ({
|
|
|
223
269
|
scheduledStatus = "",
|
|
224
270
|
nowMs = Date.now(),
|
|
225
271
|
} = {}) => {
|
|
226
|
-
const isPastSlot =
|
|
272
|
+
const isPastSlot =
|
|
273
|
+
Number(scheduledAtMs || 0) > 0 && Number(scheduledAtMs || 0) <= nowMs;
|
|
227
274
|
const runCount7d = Number(runSummary7d?.runCount || 0);
|
|
228
275
|
const avgTokensPerRun7d = Number(runSummary7d?.avgTokensPerRun || 0);
|
|
229
276
|
const avgCostPerRun7d = Number(runSummary7d?.avgCostPerRun || 0);
|
|
230
277
|
const slotRunTokens = getRunTotalTokens(slotRun || {});
|
|
231
278
|
const slotRunCost = getRunEstimatedCost(slotRun || {});
|
|
232
|
-
const slotRunStatus = String(slotRun?.status || "")
|
|
279
|
+
const slotRunStatus = String(slotRun?.status || "")
|
|
280
|
+
.trim()
|
|
281
|
+
.toLowerCase();
|
|
233
282
|
|
|
234
283
|
const lines = [String(jobName || "Job")];
|
|
235
284
|
if (isPastSlot) {
|
|
236
|
-
lines.push(
|
|
237
|
-
|
|
285
|
+
lines.push(
|
|
286
|
+
`Run tokens: ${slotRun ? formatTokenCount(slotRunTokens) : "—"}`,
|
|
287
|
+
);
|
|
288
|
+
lines.push(
|
|
289
|
+
`Run cost: ${slotRunCost == null ? "—" : formatCost(slotRunCost)}`,
|
|
290
|
+
);
|
|
238
291
|
lines.push(`Run status: ${slotRunStatus || scheduledStatus || "unknown"}`);
|
|
239
292
|
if (slotRun?.ts) {
|
|
240
293
|
lines.push(
|
|
@@ -248,7 +301,9 @@ const buildJobTooltipText = ({
|
|
|
248
301
|
lines.push(
|
|
249
302
|
`Avg cost/run (last 7d): ${runCount7d > 0 ? formatCost(avgCostPerRun7d) : "—"}`,
|
|
250
303
|
);
|
|
251
|
-
lines.push(
|
|
304
|
+
lines.push(
|
|
305
|
+
`Runs (last 7d): ${runCount7d > 0 ? formatTokenCount(runCount7d) : "none"}`,
|
|
306
|
+
);
|
|
252
307
|
}
|
|
253
308
|
|
|
254
309
|
if (!isPastSlot && latestRun?.status) {
|
|
@@ -259,12 +314,15 @@ const buildJobTooltipText = ({
|
|
|
259
314
|
lines.push("Latest run: none");
|
|
260
315
|
}
|
|
261
316
|
if (Number(job?.state?.runningAtMs || 0) > 0) {
|
|
262
|
-
lines.push(
|
|
317
|
+
lines.push(
|
|
318
|
+
`Current run: active (${new Date(Number(job.state.runningAtMs)).toLocaleString()})`,
|
|
319
|
+
);
|
|
263
320
|
}
|
|
264
321
|
|
|
265
322
|
if (scheduledAtMs > 0) {
|
|
266
323
|
const slotLabel = new Date(scheduledAtMs).toLocaleString();
|
|
267
|
-
const slotState =
|
|
324
|
+
const slotState =
|
|
325
|
+
scheduledStatus || (scheduledAtMs <= Date.now() ? "past" : "upcoming");
|
|
268
326
|
lines.push(`Slot: ${slotState} (${slotLabel})`);
|
|
269
327
|
}
|
|
270
328
|
return lines.join("\n");
|
|
@@ -275,16 +333,29 @@ export const CronCalendar = ({
|
|
|
275
333
|
runsByJobId = {},
|
|
276
334
|
onSelectJob = () => {},
|
|
277
335
|
}) => {
|
|
278
|
-
const [
|
|
336
|
+
const [calendarView, setCalendarView] = useState(() => {
|
|
279
337
|
const settings = readUiSettings();
|
|
280
|
-
|
|
338
|
+
const savedView = String(
|
|
339
|
+
settings?.[kCalendarViewUiSettingKey] || "",
|
|
340
|
+
).trim();
|
|
341
|
+
if (savedView === kCalendarViewCalendar) return kCalendarViewCalendar;
|
|
342
|
+
if (savedView === kCalendarViewUpcoming) return kCalendarViewUpcoming;
|
|
343
|
+
return settings[kCalendarExpandedUiSettingKey] === true
|
|
344
|
+
? kCalendarViewCalendar
|
|
345
|
+
: kCalendarViewUpcoming;
|
|
281
346
|
});
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
347
|
+
const isCalendarView = calendarView === kCalendarViewCalendar;
|
|
348
|
+
const onChangeCalendarView = useCallback((nextValue) => {
|
|
349
|
+
const nextView =
|
|
350
|
+
nextValue === kCalendarViewCalendar
|
|
351
|
+
? kCalendarViewCalendar
|
|
352
|
+
: kCalendarViewUpcoming;
|
|
353
|
+
setCalendarView(nextView);
|
|
354
|
+
updateUiSettings((settings) => ({
|
|
355
|
+
...settings,
|
|
356
|
+
[kCalendarViewUiSettingKey]: nextView,
|
|
357
|
+
[kCalendarExpandedUiSettingKey]: nextView === kCalendarViewCalendar,
|
|
358
|
+
}));
|
|
288
359
|
}, []);
|
|
289
360
|
|
|
290
361
|
const [nowMs, setNowMs] = useState(() => Date.now());
|
|
@@ -306,7 +377,12 @@ export const CronCalendar = ({
|
|
|
306
377
|
[scheduledJobs, nowMs],
|
|
307
378
|
);
|
|
308
379
|
const statusBySlotKey = useMemo(
|
|
309
|
-
() =>
|
|
380
|
+
() =>
|
|
381
|
+
mapRunStatusesToSlots({
|
|
382
|
+
slots: timeline.slots,
|
|
383
|
+
bulkRunsByJobId: runsByJobId,
|
|
384
|
+
nowMs,
|
|
385
|
+
}),
|
|
310
386
|
[timeline.slots, runsByJobId, nowMs],
|
|
311
387
|
);
|
|
312
388
|
const jobById = useMemo(
|
|
@@ -320,14 +396,21 @@ export const CronCalendar = ({
|
|
|
320
396
|
);
|
|
321
397
|
const latestRunByJobId = useMemo(
|
|
322
398
|
() =>
|
|
323
|
-
Object.entries(runsByJobId || {}).reduce(
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
399
|
+
Object.entries(runsByJobId || {}).reduce(
|
|
400
|
+
(accumulator, [jobId, runResult]) => {
|
|
401
|
+
const entries = Array.isArray(runResult?.entries)
|
|
402
|
+
? runResult.entries
|
|
403
|
+
: [];
|
|
404
|
+
const latest = entries
|
|
405
|
+
.filter((entry) => Number(entry?.ts || 0) > 0)
|
|
406
|
+
.sort(
|
|
407
|
+
(left, right) => Number(right?.ts || 0) - Number(left?.ts || 0),
|
|
408
|
+
)[0];
|
|
409
|
+
accumulator[jobId] = latest || null;
|
|
410
|
+
return accumulator;
|
|
411
|
+
},
|
|
412
|
+
{},
|
|
413
|
+
),
|
|
331
414
|
[runsByJobId],
|
|
332
415
|
);
|
|
333
416
|
const runSummary7dByJobId = useMemo(
|
|
@@ -349,47 +432,68 @@ export const CronCalendar = ({
|
|
|
349
432
|
if (slotRunTokens > 0) values.push(slotRunTokens);
|
|
350
433
|
return;
|
|
351
434
|
}
|
|
352
|
-
const projectedAvgTokens = Number(
|
|
435
|
+
const projectedAvgTokens = Number(
|
|
436
|
+
runSummary7dByJobId[slot.jobId]?.avgTokensPerRun || 0,
|
|
437
|
+
);
|
|
353
438
|
if (projectedAvgTokens > 0) values.push(projectedAvgTokens);
|
|
354
439
|
});
|
|
355
440
|
repeatingJobs.forEach((job) => {
|
|
356
441
|
const jobId = String(job?.id || "");
|
|
357
|
-
const projectedAvgTokens = Number(
|
|
442
|
+
const projectedAvgTokens = Number(
|
|
443
|
+
runSummary7dByJobId[jobId]?.avgTokensPerRun || 0,
|
|
444
|
+
);
|
|
358
445
|
if (projectedAvgTokens > 0) values.push(projectedAvgTokens);
|
|
359
446
|
});
|
|
360
447
|
return buildTierThresholds(values);
|
|
361
|
-
}, [
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
448
|
+
}, [
|
|
449
|
+
jobById,
|
|
450
|
+
nowMs,
|
|
451
|
+
repeatingJobs,
|
|
452
|
+
runBySlotKey,
|
|
453
|
+
runSummary7dByJobId,
|
|
454
|
+
timeline.slots,
|
|
455
|
+
]);
|
|
456
|
+
const getSlotTokenTier = useCallback(
|
|
457
|
+
(slot = null) => {
|
|
458
|
+
const jobId = String(slot?.jobId || "");
|
|
459
|
+
const job = jobById[jobId] || null;
|
|
460
|
+
const enabled = job?.enabled !== false;
|
|
461
|
+
const isPastSlot = Number(slot?.scheduledAtMs || 0) <= nowMs;
|
|
462
|
+
if (isPastSlot) {
|
|
463
|
+
const slotRunTokens = getRunTotalTokens(
|
|
464
|
+
runBySlotKey[String(slot?.key || "")] || {},
|
|
465
|
+
);
|
|
466
|
+
return classifyTokenTier({
|
|
467
|
+
enabled,
|
|
468
|
+
tokenValue: slotRunTokens,
|
|
469
|
+
thresholds: slotTierThresholds,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
const projectedAvgTokens = Number(
|
|
473
|
+
runSummary7dByJobId[jobId]?.avgTokensPerRun || 0,
|
|
474
|
+
);
|
|
369
475
|
return classifyTokenTier({
|
|
370
476
|
enabled,
|
|
371
|
-
tokenValue:
|
|
477
|
+
tokenValue: projectedAvgTokens,
|
|
372
478
|
thresholds: slotTierThresholds,
|
|
373
479
|
});
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
});
|
|
389
|
-
}, [jobById, runSummary7dByJobId, slotTierThresholds]);
|
|
480
|
+
},
|
|
481
|
+
[jobById, nowMs, runBySlotKey, runSummary7dByJobId, slotTierThresholds],
|
|
482
|
+
);
|
|
483
|
+
const getJobProjectedTier = useCallback(
|
|
484
|
+
(jobId = "") => {
|
|
485
|
+
const job = jobById[jobId] || null;
|
|
486
|
+
return classifyTokenTier({
|
|
487
|
+
enabled: job?.enabled !== false,
|
|
488
|
+
tokenValue: Number(runSummary7dByJobId[jobId]?.avgTokensPerRun || 0),
|
|
489
|
+
thresholds: slotTierThresholds,
|
|
490
|
+
});
|
|
491
|
+
},
|
|
492
|
+
[jobById, runSummary7dByJobId, slotTierThresholds],
|
|
493
|
+
);
|
|
390
494
|
|
|
391
495
|
const upcomingSlots = useMemo(
|
|
392
|
-
() => getUpcomingSlots({ slots: timeline.slots, nowMs }),
|
|
496
|
+
() => getUpcomingSlots({ slots: timeline.slots, nowMs, limit: 3 }),
|
|
393
497
|
[timeline.slots, nowMs],
|
|
394
498
|
);
|
|
395
499
|
|
|
@@ -418,7 +522,9 @@ export const CronCalendar = ({
|
|
|
418
522
|
const renderCompactStrip = () => {
|
|
419
523
|
return html`
|
|
420
524
|
${upcomingSlots.length === 0
|
|
421
|
-
? html`<div class="text-xs text-gray-500 py-1">
|
|
525
|
+
? html`<div class="text-xs text-gray-500 py-1">
|
|
526
|
+
No upcoming jobs in the next 24 hours.
|
|
527
|
+
</div>`
|
|
422
528
|
: html`
|
|
423
529
|
<div class="cron-calendar-compact-strip">
|
|
424
530
|
${upcomingSlots.map((slot) => {
|
|
@@ -460,8 +566,12 @@ export const CronCalendar = ({
|
|
|
460
566
|
? html`
|
|
461
567
|
<button
|
|
462
568
|
class="text-[11px] text-gray-500 hover:text-gray-300 self-center transition-colors"
|
|
463
|
-
onClick=${
|
|
464
|
-
|
|
569
|
+
onClick=${() =>
|
|
570
|
+
onChangeCalendarView(kCalendarViewCalendar)}
|
|
571
|
+
>
|
|
572
|
+
+${Math.max(0, totalUpcoming - upcomingSlots.length)} more
|
|
573
|
+
this week
|
|
574
|
+
</button>
|
|
465
575
|
`
|
|
466
576
|
: null}
|
|
467
577
|
</div>
|
|
@@ -476,7 +586,9 @@ export const CronCalendar = ({
|
|
|
476
586
|
</div>
|
|
477
587
|
|
|
478
588
|
${hourRows.length === 0
|
|
479
|
-
? html`<div class="text-sm text-gray-500">
|
|
589
|
+
? html`<div class="text-sm text-gray-500">
|
|
590
|
+
No scheduled jobs in this rolling window.
|
|
591
|
+
</div>`
|
|
480
592
|
: html`
|
|
481
593
|
<div class="cron-calendar-grid-wrap">
|
|
482
594
|
<div class="cron-calendar-grid-header">
|
|
@@ -493,34 +605,41 @@ export const CronCalendar = ({
|
|
|
493
605
|
)}
|
|
494
606
|
</div>
|
|
495
607
|
<div class="cron-calendar-grid-body">
|
|
496
|
-
${hourRows.map(
|
|
497
|
-
|
|
498
|
-
<div class="cron-calendar-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
608
|
+
${hourRows.map(
|
|
609
|
+
(hourOfDay) => html`
|
|
610
|
+
<div key=${hourOfDay} class="cron-calendar-grid-row">
|
|
611
|
+
<div class="cron-calendar-hour-cell">
|
|
612
|
+
${formatHourLabel(hourOfDay)}
|
|
613
|
+
</div>
|
|
614
|
+
${timeline.days.map((day) => {
|
|
615
|
+
const cellKey = buildCellKey(day.dayKey, hourOfDay);
|
|
616
|
+
const cellSlots = slotsByCellKey[cellKey] || [];
|
|
617
|
+
const visibleSlots = cellSlots.slice(0, 3);
|
|
618
|
+
const overflowCount = Math.max(
|
|
619
|
+
0,
|
|
620
|
+
cellSlots.length - visibleSlots.length,
|
|
621
|
+
);
|
|
622
|
+
return html`
|
|
623
|
+
<div
|
|
624
|
+
key=${cellKey}
|
|
625
|
+
class=${`cron-calendar-grid-cell ${day.dayKey === todayDayKey ? "is-today" : ""}`}
|
|
626
|
+
>
|
|
627
|
+
${visibleSlots.map((slot) => {
|
|
628
|
+
const status = statusBySlotKey[slot.key] || "";
|
|
629
|
+
const isPast = slot.scheduledAtMs <= nowMs;
|
|
630
|
+
const tokenTier = getSlotTokenTier(slot);
|
|
631
|
+
const tooltipText = buildJobTooltipText({
|
|
632
|
+
jobName: slot.jobName,
|
|
633
|
+
job: jobById[slot.jobId] || null,
|
|
634
|
+
runSummary7d:
|
|
635
|
+
runSummary7dByJobId[slot.jobId] || {},
|
|
636
|
+
slotRun: runBySlotKey[slot.key] || null,
|
|
637
|
+
latestRun: latestRunByJobId[slot.jobId],
|
|
638
|
+
scheduledAtMs: slot.scheduledAtMs,
|
|
639
|
+
scheduledStatus: status,
|
|
640
|
+
nowMs,
|
|
641
|
+
});
|
|
642
|
+
return html`
|
|
524
643
|
<${Tooltip}
|
|
525
644
|
text=${tooltipText}
|
|
526
645
|
widthClass="w-72"
|
|
@@ -529,16 +648,22 @@ export const CronCalendar = ({
|
|
|
529
648
|
>
|
|
530
649
|
<div
|
|
531
650
|
key=${slot.key}
|
|
532
|
-
class=${`cron-calendar-slot-chip ${slotStateClassName(
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
651
|
+
class=${`cron-calendar-slot-chip ${slotStateClassName(
|
|
652
|
+
{
|
|
653
|
+
isPast,
|
|
654
|
+
mappedStatus: status,
|
|
655
|
+
tokenTier,
|
|
656
|
+
},
|
|
657
|
+
)}`}
|
|
537
658
|
role="button"
|
|
538
659
|
tabindex="0"
|
|
539
660
|
onClick=${() => onSelectJob(slot.jobId)}
|
|
540
661
|
onKeyDown=${(event) => {
|
|
541
|
-
if (
|
|
662
|
+
if (
|
|
663
|
+
event.key !== "Enter" &&
|
|
664
|
+
event.key !== " "
|
|
665
|
+
)
|
|
666
|
+
return;
|
|
542
667
|
event.preventDefault();
|
|
543
668
|
onSelectJob(slot.jobId);
|
|
544
669
|
}}
|
|
@@ -547,19 +672,21 @@ export const CronCalendar = ({
|
|
|
547
672
|
</div>
|
|
548
673
|
</${Tooltip}>
|
|
549
674
|
`;
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
675
|
+
})}
|
|
676
|
+
${overflowCount > 0
|
|
677
|
+
? html`<div class="cron-calendar-slot-overflow">
|
|
678
|
+
+${overflowCount} more
|
|
679
|
+
</div>`
|
|
680
|
+
: null}
|
|
681
|
+
</div>
|
|
682
|
+
`;
|
|
683
|
+
})}
|
|
684
|
+
</div>
|
|
685
|
+
`,
|
|
686
|
+
)}
|
|
559
687
|
</div>
|
|
560
688
|
</div>
|
|
561
689
|
`}
|
|
562
|
-
|
|
563
690
|
${repeatingJobs.length > 0
|
|
564
691
|
? html`
|
|
565
692
|
<div class="cron-calendar-repeating-strip">
|
|
@@ -586,16 +713,19 @@ export const CronCalendar = ({
|
|
|
586
713
|
triggerClassName="inline-flex max-w-full"
|
|
587
714
|
>
|
|
588
715
|
<div
|
|
589
|
-
class=${`cron-calendar-repeating-pill ${slotStateClassName(
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
716
|
+
class=${`cron-calendar-repeating-pill ${slotStateClassName(
|
|
717
|
+
{
|
|
718
|
+
isPast: false,
|
|
719
|
+
mappedStatus: "",
|
|
720
|
+
tokenTier: getJobProjectedTier(jobId),
|
|
721
|
+
},
|
|
722
|
+
)}`}
|
|
594
723
|
role="button"
|
|
595
724
|
tabindex="0"
|
|
596
725
|
onClick=${() => onSelectJob(jobId)}
|
|
597
726
|
onKeyDown=${(event) => {
|
|
598
|
-
if (event.key !== "Enter" && event.key !== " ")
|
|
727
|
+
if (event.key !== "Enter" && event.key !== " ")
|
|
728
|
+
return;
|
|
599
729
|
event.preventDefault();
|
|
600
730
|
onSelectJob(jobId);
|
|
601
731
|
}}
|
|
@@ -605,9 +735,11 @@ export const CronCalendar = ({
|
|
|
605
735
|
${formatCronScheduleLabel(job.schedule, {
|
|
606
736
|
includeTimeZoneWhenDifferent: true,
|
|
607
737
|
})}
|
|
608
|
-
${
|
|
609
|
-
|
|
610
|
-
|
|
738
|
+
${
|
|
739
|
+
avgTokensPerRun > 0
|
|
740
|
+
? ` | avg ${formatTokenCount(avgTokensPerRun)} tk`
|
|
741
|
+
: ""
|
|
742
|
+
}
|
|
611
743
|
</span>
|
|
612
744
|
</div>
|
|
613
745
|
</${Tooltip}>
|
|
@@ -622,24 +754,15 @@ export const CronCalendar = ({
|
|
|
622
754
|
|
|
623
755
|
return html`
|
|
624
756
|
<section class="bg-surface border border-border rounded-xl p-4 space-y-3">
|
|
625
|
-
<div class="flex items-center
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
${!expanded && repeatingJobs.length > 0
|
|
631
|
-
? html`<span class="text-[11px] text-gray-500">+ ${repeatingJobs.length} repeating</span>`
|
|
632
|
-
: null}
|
|
633
|
-
</div>
|
|
634
|
-
<${ActionButton}
|
|
635
|
-
onClick=${toggleExpanded}
|
|
636
|
-
tone="neutral"
|
|
637
|
-
size="sm"
|
|
638
|
-
idleLabel=${expanded ? "Collapse" : "Show full week"}
|
|
757
|
+
<div class="flex items-center gap-2">
|
|
758
|
+
<${SegmentedControl}
|
|
759
|
+
options=${kCalendarViewOptions}
|
|
760
|
+
value=${calendarView}
|
|
761
|
+
onChange=${onChangeCalendarView}
|
|
639
762
|
/>
|
|
640
763
|
</div>
|
|
641
764
|
|
|
642
|
-
${
|
|
765
|
+
${isCalendarView ? renderFullGrid() : renderCompactStrip()}
|
|
643
766
|
</section>
|
|
644
767
|
`;
|
|
645
768
|
};
|