@rebasepro/studio 0.23.0 → 0.24.1-canary.g56af4a4
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/dist/{ApiExplorer-pYMZflL3.js → ApiExplorer-Bzt7Ye49.js} +18 -29
- package/dist/ApiExplorer-Bzt7Ye49.js.map +1 -0
- package/dist/ApiKeysView-Bqf6XYR1.js +1739 -0
- package/dist/ApiKeysView-Bqf6XYR1.js.map +1 -0
- package/dist/BackupsView-BhOHB6iP.js +354 -0
- package/dist/BackupsView-BhOHB6iP.js.map +1 -0
- package/dist/{BranchesView-D6Sz1a5f.js → BranchesView-BHfwGTYc.js} +16 -10
- package/dist/BranchesView-BHfwGTYc.js.map +1 -0
- package/dist/{CronJobsView-7hjgSYId.js → CronJobsView-CMNK7eWO.js} +175 -116
- package/dist/CronJobsView-CMNK7eWO.js.map +1 -0
- package/dist/{JSEditor-qiuwmcvk.js → JSEditor-DIO2Mw8q.js} +230 -165
- package/dist/JSEditor-DIO2Mw8q.js.map +1 -0
- package/dist/{LogsExplorer-Xq8K4oo6.js → LogsExplorer-C-bwDFIl.js} +66 -44
- package/dist/LogsExplorer-C-bwDFIl.js.map +1 -0
- package/dist/{RLSEditor-Djh43lBE.js → RLSEditor-jFD_d06f.js} +246 -84
- package/dist/RLSEditor-jFD_d06f.js.map +1 -0
- package/dist/{SQLEditor-TBw_K6CW.js → SQLEditor-D5Ci4pyW.js} +502 -600
- package/dist/SQLEditor-D5Ci4pyW.js.map +1 -0
- package/dist/{SchemaVisualizer-DtDzIUNy.js → SchemaVisualizer-D-rAqwul.js} +496 -227
- package/dist/SchemaVisualizer-D-rAqwul.js.map +1 -0
- package/dist/{StorageView-CMG1DOVX.js → StorageView-CPkDKbsX.js} +838 -451
- package/dist/StorageView-CPkDKbsX.js.map +1 -0
- package/dist/assets/dagre-layout.worker-BFnhAzuR.js.map +1 -0
- package/dist/components/ApiExplorer/TryItPanel.d.ts +6 -1
- package/dist/components/ApiKeys/CreateApiKeyDialog.d.ts +21 -2
- package/dist/components/ApiKeys/scope-words.d.ts +37 -0
- package/dist/components/ApiKeys/scopes.d.ts +113 -0
- package/dist/components/AuthSimulationSelector.d.ts +5 -1
- package/dist/components/LogsExplorer/useLogTail.d.ts +9 -0
- package/dist/components/RLSEditor/policyListing.d.ts +44 -0
- package/dist/components/SQLEditor/SQLEditor.d.ts +11 -0
- package/dist/components/SchemaVisualizer/dagre-layout.d.ts +32 -0
- package/dist/components/SchemaVisualizer/dagre-layout.worker.d.ts +13 -0
- package/dist/components/SchemaVisualizer/layout-runner.d.ts +23 -0
- package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +21 -11
- package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +44 -1
- package/dist/components/StorageView/useVisibleFileMetadata.d.ts +46 -0
- package/dist/components/load-failure.d.ts +1 -1
- package/dist/components/useRunAsUsers.d.ts +25 -0
- package/dist/dagre-layout.worker-j9NbH9Gb.js +27 -0
- package/dist/dagre-layout.worker-j9NbH9Gb.js.map +1 -0
- package/dist/index.es.js +16 -14
- package/dist/index.es.js.map +1 -1
- package/dist/{load-failure-C6OMJSGC.js → load-failure-Bd8lFIO7.js} +2 -2
- package/dist/load-failure-Bd8lFIO7.js.map +1 -0
- package/dist/sql_utils-BZD31xE3.js +478 -0
- package/dist/sql_utils-BZD31xE3.js.map +1 -0
- package/dist/useRunAsUsers-CLaAtXt6.js +122 -0
- package/dist/useRunAsUsers-CLaAtXt6.js.map +1 -0
- package/dist/utils/sql_utils.d.ts +150 -49
- package/package.json +9 -9
- package/dist/ApiExplorer-pYMZflL3.js.map +0 -1
- package/dist/ApiKeysView-qLSs47tw.js +0 -1239
- package/dist/ApiKeysView-qLSs47tw.js.map +0 -1
- package/dist/AuthSimulationSelector-BewmhMBw.js +0 -56
- package/dist/AuthSimulationSelector-BewmhMBw.js.map +0 -1
- package/dist/BackupsView-DYTAD6aW.js +0 -253
- package/dist/BackupsView-DYTAD6aW.js.map +0 -1
- package/dist/BranchesView-D6Sz1a5f.js.map +0 -1
- package/dist/CronJobsView-7hjgSYId.js.map +0 -1
- package/dist/JSEditor-qiuwmcvk.js.map +0 -1
- package/dist/LogsExplorer-Xq8K4oo6.js.map +0 -1
- package/dist/RLSEditor-Djh43lBE.js.map +0 -1
- package/dist/SQLEditor-TBw_K6CW.js.map +0 -1
- package/dist/SchemaVisualizer-DtDzIUNy.js.map +0 -1
- package/dist/StorageView-CMG1DOVX.js.map +0 -1
- package/dist/components/ApiKeys/permissions.d.ts +0 -66
- package/dist/load-failure-C6OMJSGC.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as classifyLoadFailure } from "./load-failure-
|
|
1
|
+
import { t as classifyLoadFailure } from "./load-failure-Bd8lFIO7.js";
|
|
2
2
|
import { t as LoadFailureView } from "./load-failure-view-CBTB_h-h.js";
|
|
3
3
|
import { useRebaseClient, useSnackbarController, useTranslation } from "@rebasepro/app";
|
|
4
4
|
import { useEffect, useRef, useState } from "react";
|
|
5
|
-
import { AlertCircleIcon, Button, CalendarIcon, CheckCircleIcon, Chip, CircularProgress, HistoryIcon, IconButton, PauseIcon, PlayIcon, RefreshCwIcon, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
5
|
+
import { Alert, AlertCircleIcon, Button, CalendarIcon, CheckCircleIcon, ChevronDownIcon, Chip, CircularProgress, HistoryIcon, IconButton, PauseIcon, PlayIcon, RefreshCwIcon, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
//#region src/components/CronJobs/CronJobsView.tsx
|
|
8
8
|
function formatDuration(ms) {
|
|
@@ -10,22 +10,61 @@ function formatDuration(ms) {
|
|
|
10
10
|
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
11
11
|
return `${(ms / 6e4).toFixed(1)}m`;
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
/** `iso` relative to now, in `language`: "in 5 min", "2 hr. ago"; a date past a day. */
|
|
14
|
+
function formatRelative(iso, language) {
|
|
14
15
|
if (!iso) return "—";
|
|
15
16
|
const d = new Date(iso);
|
|
16
|
-
const
|
|
17
|
-
const diff = d.getTime() - now;
|
|
17
|
+
const diff = d.getTime() - Date.now();
|
|
18
18
|
const abs = Math.abs(diff);
|
|
19
|
-
if (abs
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return
|
|
19
|
+
if (abs >= 864e5) return d.toLocaleString(language);
|
|
20
|
+
const relative = new Intl.RelativeTimeFormat(language, {
|
|
21
|
+
style: "short",
|
|
22
|
+
numeric: "auto"
|
|
23
|
+
});
|
|
24
|
+
return abs < 36e5 ? relative.format(Math.round(diff / 6e4), "minute") : relative.format(Math.round(diff / 36e5), "hour");
|
|
25
|
+
}
|
|
26
|
+
function problemsOf(listing) {
|
|
27
|
+
const rejected = listing.rejected ?? [];
|
|
28
|
+
return {
|
|
29
|
+
rejected,
|
|
30
|
+
filesFailed: Math.max(0, (listing.skipped ?? 0) - rejected.length)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The cron files that failed to load and the jobs the scheduler refused. None
|
|
35
|
+
* of them is scheduled, and none is in the list: without this a refused job
|
|
36
|
+
* was simply absent, and a project whose only job was refused read "No cron
|
|
37
|
+
* jobs registered".
|
|
38
|
+
*/
|
|
39
|
+
function CronLoadProblems({ problems }) {
|
|
40
|
+
const { t } = useTranslation();
|
|
41
|
+
if (problems.rejected.length === 0 && problems.filesFailed === 0) return null;
|
|
42
|
+
return /* @__PURE__ */ jsx(Alert, {
|
|
43
|
+
color: "error",
|
|
44
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
45
|
+
className: "flex flex-col gap-2",
|
|
46
|
+
children: [problems.rejected.map((job) => /* @__PURE__ */ jsxs("div", {
|
|
47
|
+
className: "flex flex-col",
|
|
48
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
49
|
+
variant: "body2",
|
|
50
|
+
className: "text-[13px] font-semibold",
|
|
51
|
+
children: t("studio_cron_job_refused", { job: job.name })
|
|
52
|
+
}), /* @__PURE__ */ jsxs(Typography, {
|
|
53
|
+
variant: "caption",
|
|
54
|
+
className: "font-mono text-[12px] break-words",
|
|
55
|
+
children: [
|
|
56
|
+
job.schedule,
|
|
57
|
+
" — ",
|
|
58
|
+
job.reason
|
|
59
|
+
]
|
|
60
|
+
})]
|
|
61
|
+
}, job.id)), problems.filesFailed > 0 && /* @__PURE__ */ jsx(Typography, {
|
|
62
|
+
variant: "body2",
|
|
63
|
+
className: "text-[13px]",
|
|
64
|
+
children: t("studio_cron_files_failed", { count: problems.filesFailed })
|
|
65
|
+
})]
|
|
66
|
+
})
|
|
67
|
+
});
|
|
29
68
|
}
|
|
30
69
|
var stateColors = {
|
|
31
70
|
idle: "bg-emerald-500",
|
|
@@ -37,8 +76,13 @@ var stateColors = {
|
|
|
37
76
|
function CronJobsView() {
|
|
38
77
|
const client = useRebaseClient();
|
|
39
78
|
const snackbar = useSnackbarController();
|
|
40
|
-
const { t } = useTranslation();
|
|
79
|
+
const { t, i18n } = useTranslation();
|
|
80
|
+
const language = i18n?.language ?? "en";
|
|
41
81
|
const [jobs, setJobs] = useState([]);
|
|
82
|
+
const [problems, setProblems] = useState({
|
|
83
|
+
rejected: [],
|
|
84
|
+
filesFailed: 0
|
|
85
|
+
});
|
|
42
86
|
const [loading, setLoading] = useState(true);
|
|
43
87
|
const [selectedId, setSelectedId] = useState(null);
|
|
44
88
|
const [logs, setLogs] = useState([]);
|
|
@@ -68,6 +112,7 @@ function CronJobsView() {
|
|
|
68
112
|
const res = await c.cron.listJobs();
|
|
69
113
|
if (!cancelled) {
|
|
70
114
|
setJobs(res.jobs);
|
|
115
|
+
setProblems(problemsOf(res));
|
|
71
116
|
setFailure(null);
|
|
72
117
|
}
|
|
73
118
|
} catch (e) {
|
|
@@ -126,6 +171,7 @@ function CronJobsView() {
|
|
|
126
171
|
try {
|
|
127
172
|
const res = await c.cron.listJobs();
|
|
128
173
|
setJobs(res.jobs);
|
|
174
|
+
setProblems(problemsOf(res));
|
|
129
175
|
setFailure(null);
|
|
130
176
|
} catch (e) {
|
|
131
177
|
setFailure(classifyLoadFailure(e));
|
|
@@ -151,10 +197,17 @@ function CronJobsView() {
|
|
|
151
197
|
if (!c?.cron) return;
|
|
152
198
|
setTriggering(id);
|
|
153
199
|
try {
|
|
154
|
-
await c.cron.triggerJob(id);
|
|
155
|
-
|
|
200
|
+
const { log } = await c.cron.triggerJob(id);
|
|
201
|
+
const job = jobs.find((j) => j.id === id)?.name ?? id;
|
|
202
|
+
snackbarRef.current.open(log.success ? {
|
|
156
203
|
type: "success",
|
|
157
|
-
message: "
|
|
204
|
+
message: t("studio_cron_run_succeeded", { job })
|
|
205
|
+
} : {
|
|
206
|
+
type: "error",
|
|
207
|
+
message: t("studio_cron_run_failed", {
|
|
208
|
+
job,
|
|
209
|
+
error: log.error ?? "—"
|
|
210
|
+
})
|
|
158
211
|
});
|
|
159
212
|
await refreshJobs();
|
|
160
213
|
if (selectedId === id) refreshLogs(id);
|
|
@@ -174,7 +227,7 @@ function CronJobsView() {
|
|
|
174
227
|
await c.cron.toggleJob(id, enabled);
|
|
175
228
|
snackbarRef.current.open({
|
|
176
229
|
type: "success",
|
|
177
|
-
message: enabled ? "
|
|
230
|
+
message: enabled ? t("studio_cron_job_enabled") : t("studio_cron_job_paused")
|
|
178
231
|
});
|
|
179
232
|
await refreshJobs();
|
|
180
233
|
} catch (e) {
|
|
@@ -196,6 +249,7 @@ function CronJobsView() {
|
|
|
196
249
|
deniedHint: t("studio_cron_denied_hint"),
|
|
197
250
|
onRetry: refreshJobs
|
|
198
251
|
});
|
|
252
|
+
const hasProblems = problems.rejected.length > 0 || problems.filesFailed > 0;
|
|
199
253
|
if (jobs.length === 0) return /* @__PURE__ */ jsxs("div", {
|
|
200
254
|
className: "flex flex-col items-center justify-center h-full gap-4 text-center p-8",
|
|
201
255
|
children: [
|
|
@@ -203,17 +257,19 @@ function CronJobsView() {
|
|
|
203
257
|
size: iconSize.medium,
|
|
204
258
|
className: "text-surface-300 dark:text-surface-600"
|
|
205
259
|
}),
|
|
206
|
-
/* @__PURE__ */ jsx(
|
|
260
|
+
hasProblems ? /* @__PURE__ */ jsx("div", {
|
|
261
|
+
className: "max-w-lg text-left",
|
|
262
|
+
children: /* @__PURE__ */ jsx(CronLoadProblems, { problems })
|
|
263
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Typography, {
|
|
207
264
|
variant: "h6",
|
|
208
265
|
color: "secondary",
|
|
209
266
|
children: t("studio_cron_empty_title")
|
|
210
|
-
}),
|
|
211
|
-
/* @__PURE__ */ jsx(Typography, {
|
|
267
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
212
268
|
variant: "body2",
|
|
213
269
|
color: "disabled",
|
|
214
270
|
className: "max-w-md",
|
|
215
271
|
children: t("studio_cron_empty_body")
|
|
216
|
-
}),
|
|
272
|
+
})] }),
|
|
217
273
|
/* @__PURE__ */ jsx("a", {
|
|
218
274
|
href: "https://rebase.pro/docs/backend/cron-jobs",
|
|
219
275
|
target: "_blank",
|
|
@@ -227,56 +283,64 @@ function CronJobsView() {
|
|
|
227
283
|
className: "flex h-full w-full overflow-hidden bg-surface-card",
|
|
228
284
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
229
285
|
className: cls("flex flex-col w-[340px] min-w-[280px] border-r h-full", defaultBorderMixin),
|
|
230
|
-
children: [
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
className:
|
|
261
|
-
children:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
286
|
+
children: [
|
|
287
|
+
/* @__PURE__ */ jsxs("div", {
|
|
288
|
+
className: cls("flex items-center justify-between px-4 py-2.5 border-b bg-surface-sheet min-h-[48px]", defaultBorderMixin),
|
|
289
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
290
|
+
className: "flex items-center gap-2",
|
|
291
|
+
children: [
|
|
292
|
+
/* @__PURE__ */ jsx(CalendarIcon, {
|
|
293
|
+
size: iconSize.smallest,
|
|
294
|
+
className: "text-primary"
|
|
295
|
+
}),
|
|
296
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
297
|
+
variant: "subtitle2",
|
|
298
|
+
className: "font-semibold",
|
|
299
|
+
children: t("studio_tool_cron")
|
|
300
|
+
}),
|
|
301
|
+
/* @__PURE__ */ jsx(Chip, {
|
|
302
|
+
size: "smallest",
|
|
303
|
+
className: "bg-surface-raised text-surface-600 dark:text-surface-300",
|
|
304
|
+
children: jobs.length
|
|
305
|
+
})
|
|
306
|
+
]
|
|
307
|
+
}), /* @__PURE__ */ jsx(IconButton, {
|
|
308
|
+
"aria-label": t("studio_cron_refresh"),
|
|
309
|
+
size: "small",
|
|
310
|
+
onClick: refreshJobs,
|
|
311
|
+
title: t("studio_cron_refresh"),
|
|
312
|
+
children: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest })
|
|
313
|
+
})]
|
|
314
|
+
}),
|
|
315
|
+
hasProblems && /* @__PURE__ */ jsx("div", {
|
|
316
|
+
className: "p-2",
|
|
317
|
+
children: /* @__PURE__ */ jsx(CronLoadProblems, { problems })
|
|
318
|
+
}),
|
|
319
|
+
/* @__PURE__ */ jsx("div", {
|
|
320
|
+
className: "flex-1 overflow-y-auto p-2 space-y-1",
|
|
321
|
+
children: jobs.map((job) => /* @__PURE__ */ jsxs("div", {
|
|
322
|
+
onClick: () => setSelectedId(job.id),
|
|
323
|
+
className: cls("flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all", selectedId === job.id ? "bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30" : "hover:bg-surface-hover"),
|
|
324
|
+
children: [
|
|
325
|
+
/* @__PURE__ */ jsx("div", { className: cls("w-2 h-2 rounded-full shrink-0", stateColors[job.state] || "bg-surface-400") }),
|
|
326
|
+
/* @__PURE__ */ jsxs("div", {
|
|
327
|
+
className: "flex-1 min-w-0",
|
|
328
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
329
|
+
variant: "body2",
|
|
330
|
+
className: "truncate font-medium text-[13px]",
|
|
331
|
+
children: job.name
|
|
332
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
333
|
+
variant: "caption",
|
|
334
|
+
color: "secondary",
|
|
335
|
+
className: "truncate text-[11px] font-mono",
|
|
336
|
+
children: job.schedule
|
|
337
|
+
})]
|
|
338
|
+
}),
|
|
339
|
+
job.state === "running" && /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" })
|
|
340
|
+
]
|
|
341
|
+
}, job.id))
|
|
342
|
+
})
|
|
343
|
+
]
|
|
280
344
|
}), /* @__PURE__ */ jsx("div", {
|
|
281
345
|
className: "flex-1 flex flex-col min-w-0 h-full overflow-hidden",
|
|
282
346
|
children: !selectedJob ? /* @__PURE__ */ jsx("div", {
|
|
@@ -284,7 +348,7 @@ function CronJobsView() {
|
|
|
284
348
|
children: /* @__PURE__ */ jsx(Typography, {
|
|
285
349
|
variant: "body2",
|
|
286
350
|
color: "disabled",
|
|
287
|
-
children: "
|
|
351
|
+
children: t("studio_cron_select_job")
|
|
288
352
|
})
|
|
289
353
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
290
354
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -307,7 +371,8 @@ function CronJobsView() {
|
|
|
307
371
|
}), /* @__PURE__ */ jsxs("div", {
|
|
308
372
|
className: "flex items-center gap-2 shrink-0",
|
|
309
373
|
children: [/* @__PURE__ */ jsx(IconButton, {
|
|
310
|
-
|
|
374
|
+
"aria-label": selectedJob.enabled ? t("studio_cron_pause_job") : t("studio_cron_enable_job"),
|
|
375
|
+
title: selectedJob.enabled ? t("studio_cron_pause_job") : t("studio_cron_enable_job"),
|
|
311
376
|
size: "small",
|
|
312
377
|
onClick: () => handleToggle(selectedJob.id, !selectedJob.enabled),
|
|
313
378
|
children: selectedJob.enabled ? /* @__PURE__ */ jsx(PauseIcon, { size: iconSize.small }) : /* @__PURE__ */ jsx(PlayIcon, { size: iconSize.smallest })
|
|
@@ -317,7 +382,7 @@ function CronJobsView() {
|
|
|
317
382
|
onClick: () => handleTrigger(selectedJob.id),
|
|
318
383
|
disabled: triggering === selectedJob.id,
|
|
319
384
|
startIcon: triggering === selectedJob.id ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : /* @__PURE__ */ jsx(PlayIcon, { size: iconSize.smallest }),
|
|
320
|
-
children: "
|
|
385
|
+
children: t("studio_cron_run_now")
|
|
321
386
|
})]
|
|
322
387
|
})]
|
|
323
388
|
}),
|
|
@@ -328,20 +393,20 @@ function CronJobsView() {
|
|
|
328
393
|
className: "grid grid-cols-2 md:grid-cols-4 gap-3",
|
|
329
394
|
children: [
|
|
330
395
|
/* @__PURE__ */ jsx(StatCard, {
|
|
331
|
-
label: "
|
|
396
|
+
label: t("studio_cron_schedule"),
|
|
332
397
|
value: selectedJob.schedule,
|
|
333
398
|
mono: true
|
|
334
399
|
}),
|
|
335
400
|
/* @__PURE__ */ jsx(StatCard, {
|
|
336
|
-
label: "
|
|
337
|
-
value: formatRelative(selectedJob.lastRunAt)
|
|
401
|
+
label: t("studio_cron_last_run"),
|
|
402
|
+
value: formatRelative(selectedJob.lastRunAt, language)
|
|
338
403
|
}),
|
|
339
404
|
/* @__PURE__ */ jsx(StatCard, {
|
|
340
|
-
label: "
|
|
341
|
-
value: selectedJob.enabled ? formatRelative(selectedJob.nextRunAt) : "
|
|
405
|
+
label: t("studio_cron_next_run"),
|
|
406
|
+
value: selectedJob.enabled ? formatRelative(selectedJob.nextRunAt, language) : t("studio_cron_paused")
|
|
342
407
|
}),
|
|
343
408
|
/* @__PURE__ */ jsx(StatCard, {
|
|
344
|
-
label: "
|
|
409
|
+
label: t("studio_cron_duration"),
|
|
345
410
|
value: selectedJob.lastDurationMs !== void 0 ? formatDuration(selectedJob.lastDurationMs) : "—"
|
|
346
411
|
})
|
|
347
412
|
]
|
|
@@ -350,36 +415,31 @@ function CronJobsView() {
|
|
|
350
415
|
className: "grid grid-cols-3 gap-3 mt-3",
|
|
351
416
|
children: [
|
|
352
417
|
/* @__PURE__ */ jsx(StatCard, {
|
|
353
|
-
label: "
|
|
418
|
+
label: t("studio_cron_status"),
|
|
354
419
|
value: selectedJob.state.toUpperCase(),
|
|
355
420
|
chipColor: selectedJob.state === "error" ? "red" : selectedJob.state === "disabled" ? "gray" : "green"
|
|
356
421
|
}),
|
|
357
422
|
/* @__PURE__ */ jsx(StatCard, {
|
|
358
|
-
label: "
|
|
423
|
+
label: t("studio_cron_total_runs"),
|
|
359
424
|
value: String(selectedJob.totalRuns)
|
|
360
425
|
}),
|
|
361
426
|
/* @__PURE__ */ jsx(StatCard, {
|
|
362
|
-
label: "
|
|
427
|
+
label: t("studio_cron_failures"),
|
|
363
428
|
value: String(selectedJob.totalFailures),
|
|
364
429
|
highlight: selectedJob.totalFailures > 0
|
|
365
430
|
})
|
|
366
431
|
]
|
|
367
432
|
}),
|
|
368
|
-
selectedJob.lastError && /* @__PURE__ */ jsxs(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
children: [/* @__PURE__ */ jsx(AlertCircleIcon, {
|
|
373
|
-
size: iconSize.smallest,
|
|
374
|
-
className: "text-red-500"
|
|
375
|
-
}), /* @__PURE__ */ jsx(Typography, {
|
|
376
|
-
variant: "caption",
|
|
377
|
-
className: "font-semibold text-red-700 dark:text-red-400",
|
|
378
|
-
children: "Last Error"
|
|
379
|
-
})]
|
|
380
|
-
}), /* @__PURE__ */ jsx(Typography, {
|
|
433
|
+
selectedJob.lastError && /* @__PURE__ */ jsxs(Alert, {
|
|
434
|
+
color: "error",
|
|
435
|
+
outerClassName: "mt-3",
|
|
436
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
381
437
|
variant: "caption",
|
|
382
|
-
className: "font-
|
|
438
|
+
className: "font-medium block",
|
|
439
|
+
children: t("studio_cron_last_error")
|
|
440
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
441
|
+
variant: "mono",
|
|
442
|
+
className: "text-[11px] break-all",
|
|
383
443
|
children: selectedJob.lastError
|
|
384
444
|
})]
|
|
385
445
|
})
|
|
@@ -395,12 +455,13 @@ function CronJobsView() {
|
|
|
395
455
|
}), /* @__PURE__ */ jsx(Typography, {
|
|
396
456
|
variant: "subtitle2",
|
|
397
457
|
className: "font-semibold text-[13px]",
|
|
398
|
-
children: "
|
|
458
|
+
children: t("studio_cron_execution_history")
|
|
399
459
|
})]
|
|
400
460
|
}), /* @__PURE__ */ jsx(IconButton, {
|
|
461
|
+
"aria-label": t("studio_cron_refresh_logs"),
|
|
401
462
|
size: "small",
|
|
402
463
|
onClick: () => refreshLogs(selectedJob.id),
|
|
403
|
-
title: "
|
|
464
|
+
title: t("studio_cron_refresh_logs"),
|
|
404
465
|
children: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest })
|
|
405
466
|
})]
|
|
406
467
|
}),
|
|
@@ -420,7 +481,7 @@ function CronJobsView() {
|
|
|
420
481
|
children: /* @__PURE__ */ jsx(Typography, {
|
|
421
482
|
variant: "body2",
|
|
422
483
|
color: "disabled",
|
|
423
|
-
children: "
|
|
484
|
+
children: t("studio_cron_no_executions")
|
|
424
485
|
})
|
|
425
486
|
}) : /* @__PURE__ */ jsx("div", {
|
|
426
487
|
className: "divide-y divide-surface-100 dark:divide-surface-950",
|
|
@@ -437,7 +498,6 @@ function StatCard({ label, value, mono, chipColor, highlight }) {
|
|
|
437
498
|
children: [/* @__PURE__ */ jsx(Typography, {
|
|
438
499
|
variant: "caption",
|
|
439
500
|
color: "secondary",
|
|
440
|
-
className: "text-[10px] uppercase tracking-wider font-medium",
|
|
441
501
|
children: label
|
|
442
502
|
}), /* @__PURE__ */ jsx(Typography, {
|
|
443
503
|
variant: "body2",
|
|
@@ -447,6 +507,7 @@ function StatCard({ label, value, mono, chipColor, highlight }) {
|
|
|
447
507
|
});
|
|
448
508
|
}
|
|
449
509
|
function LogRow({ log }) {
|
|
510
|
+
const { t } = useTranslation();
|
|
450
511
|
const [expanded, setExpanded] = useState(false);
|
|
451
512
|
return /* @__PURE__ */ jsxs("div", {
|
|
452
513
|
className: "px-5 py-2.5 hover:bg-surface-hover transition-colors",
|
|
@@ -474,19 +535,16 @@ function LogRow({ log }) {
|
|
|
474
535
|
children: [
|
|
475
536
|
log.manual && /* @__PURE__ */ jsx(Chip, {
|
|
476
537
|
size: "smallest",
|
|
477
|
-
|
|
478
|
-
children: "manual"
|
|
538
|
+
children: t("studio_cron_manual_run")
|
|
479
539
|
}),
|
|
480
540
|
/* @__PURE__ */ jsx(Typography, {
|
|
481
541
|
variant: "caption",
|
|
482
542
|
className: "font-mono text-[11px]",
|
|
483
543
|
children: formatDuration(log.durationMs)
|
|
484
544
|
}),
|
|
485
|
-
/* @__PURE__ */ jsx(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
viewBox: "0 0 20 20",
|
|
489
|
-
children: /* @__PURE__ */ jsx("path", { d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" })
|
|
545
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, {
|
|
546
|
+
size: iconSize.smallest,
|
|
547
|
+
className: cls("transition-transform text-text-secondary dark:text-text-secondary-dark", expanded && "rotate-180")
|
|
490
548
|
})
|
|
491
549
|
]
|
|
492
550
|
})
|
|
@@ -494,11 +552,12 @@ function LogRow({ log }) {
|
|
|
494
552
|
}), expanded && /* @__PURE__ */ jsxs("div", {
|
|
495
553
|
className: "mt-2 ml-6 space-y-2",
|
|
496
554
|
children: [
|
|
497
|
-
log.error && /* @__PURE__ */ jsx(
|
|
498
|
-
|
|
555
|
+
log.error && /* @__PURE__ */ jsx(Alert, {
|
|
556
|
+
color: "error",
|
|
557
|
+
size: "small",
|
|
499
558
|
children: /* @__PURE__ */ jsx(Typography, {
|
|
500
|
-
variant: "
|
|
501
|
-
className: "
|
|
559
|
+
variant: "mono",
|
|
560
|
+
className: "text-[11px] break-all",
|
|
502
561
|
children: log.error
|
|
503
562
|
})
|
|
504
563
|
}),
|
|
@@ -527,4 +586,4 @@ function LogRow({ log }) {
|
|
|
527
586
|
//#endregion
|
|
528
587
|
export { CronJobsView };
|
|
529
588
|
|
|
530
|
-
//# sourceMappingURL=CronJobsView-
|
|
589
|
+
//# sourceMappingURL=CronJobsView-CMNK7eWO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CronJobsView-CMNK7eWO.js","names":[],"sources":["../src/components/CronJobs/CronJobsView.tsx"],"sourcesContent":["\nimport React, { useState, useEffect, useRef } from \"react\";\nimport {\n Alert,\n AlertCircleIcon,\n Button,\n CalendarIcon,\n Card,\n CheckCircleIcon,\n ChevronDownIcon,\n Chip,\n CircularProgress,\n cls,\n defaultBorderMixin,\n HistoryIcon,\n IconButton,\n iconSize,\n Paper,\n PauseIcon,\n PlayIcon,\n RefreshCwIcon,\n Typography\n} from \"@rebasepro/ui\";\nimport { useRebaseClient, useSnackbarController, useTranslation } from \"@rebasepro/app\";\nimport type { CronJobListing, CronJobStatus, CronJobLogEntry, RejectedCronJob } from \"@rebasepro/types\";\nimport type { RebaseClient } from \"@rebasepro/types\";\n\nimport { classifyLoadFailure, type LoadFailure } from \"../load-failure\";\nimport { LoadFailureView } from \"../load-failure-view\";\n\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\n return `${(ms / 60000).toFixed(1)}m`;\n}\n\n/** `iso` relative to now, in `language`: \"in 5 min\", \"2 hr. ago\"; a date past a day. */\nfunction formatRelative(iso: string | undefined, language: string): string {\n if (!iso) return \"—\";\n const d = new Date(iso);\n const diff = d.getTime() - Date.now();\n const abs = Math.abs(diff);\n if (abs >= 86400000) return d.toLocaleString(language);\n const relative = new Intl.RelativeTimeFormat(language, { style: \"short\", numeric: \"auto\" });\n return abs < 3600000\n ? relative.format(Math.round(diff / 60000), \"minute\")\n : relative.format(Math.round(diff / 3600000), \"hour\");\n}\n\n/** What the listing says did not become a job — see {@link CronLoadProblems}. */\ninterface CronProblems {\n rejected: RejectedCronJob[];\n /** Cron files that failed to load: `skipped` counts these and the refused jobs together. */\n filesFailed: number;\n}\n\nfunction problemsOf(listing: CronJobListing): CronProblems {\n const rejected = listing.rejected ?? [];\n return { rejected, filesFailed: Math.max(0, (listing.skipped ?? 0) - rejected.length) };\n}\n\n/**\n * The cron files that failed to load and the jobs the scheduler refused. None\n * of them is scheduled, and none is in the list: without this a refused job\n * was simply absent, and a project whose only job was refused read \"No cron\n * jobs registered\".\n */\nfunction CronLoadProblems({ problems }: { problems: CronProblems }) {\n const { t } = useTranslation();\n if (problems.rejected.length === 0 && problems.filesFailed === 0) return null;\n return (\n <Alert color=\"error\">\n <div className=\"flex flex-col gap-2\">\n {problems.rejected.map(job => (\n <div key={job.id} className=\"flex flex-col\">\n <Typography variant=\"body2\" className=\"text-[13px] font-semibold\">\n {t(\"studio_cron_job_refused\", { job: job.name })}\n </Typography>\n <Typography variant=\"caption\" className=\"font-mono text-[12px] break-words\">\n {job.schedule} — {job.reason}\n </Typography>\n </div>\n ))}\n {problems.filesFailed > 0 && (\n <Typography variant=\"body2\" className=\"text-[13px]\">\n {t(\"studio_cron_files_failed\", { count: problems.filesFailed })}\n </Typography>\n )}\n </div>\n </Alert>\n );\n}\n\nconst stateColors: Record<string, string> = {\n idle: \"bg-emerald-500\",\nrunning: \"bg-blue-500\",\nsuccess: \"bg-emerald-500\",\n error: \"bg-red-500\",\ndisabled: \"bg-surface-400\"\n};\n\nexport function CronJobsView() {\n const client = useRebaseClient<RebaseClient>();\n const snackbar = useSnackbarController();\n const { t, i18n } = useTranslation();\n const language = i18n?.language ?? \"en\";\n const [jobs, setJobs] = useState<CronJobStatus[]>([]);\n const [problems, setProblems] = useState<CronProblems>({ rejected: [], filesFailed: 0 });\n const [loading, setLoading] = useState(true);\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const [logs, setLogs] = useState<CronJobLogEntry[]>([]);\n const [logsLoading, setLogsLoading] = useState(false);\n /**\n * Why the selected job's executions could not be read. Shown where the\n * list would be: an empty list reads as \"this job has never run\", and the\n * snackbar that used to carry the reason was gone in four seconds.\n */\n const [logsFailure, setLogsFailure] = useState<LoadFailure | null>(null);\n const [triggering, setTriggering] = useState<string | null>(null);\n /** Why the job listing failed, classified — see `load-failure.ts`. */\n const [failure, setFailure] = useState<LoadFailure | null>(null);\n\n // Refs so effects never re-fire due to identity changes\n const clientRef = useRef(client);\n clientRef.current = client;\n const snackbarRef = useRef(snackbar);\n snackbarRef.current = snackbar;\n\n // ── Fetch jobs on mount + poll every 15s ──\n useEffect(() => {\n let cancelled = false;\n\n async function load() {\n const c = clientRef.current;\n if (!c?.cron) {\n setLoading(false);\n return;\n }\n try {\n const res = await c.cron.listJobs();\n if (!cancelled) {\n setJobs(res.jobs);\n setProblems(problemsOf(res));\n setFailure(null);\n }\n } catch (e: unknown) {\n // A snackbar over an empty list said \"No Cron Jobs Registered\"\n // about a project whose crons the caller may not read. This\n // view polls every 15s, so the toast is also gone long before\n // anyone looks.\n if (!cancelled) setFailure(classifyLoadFailure(e));\n } finally {\n if (!cancelled) setLoading(false);\n }\n }\n\n load();\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const scheduleNext = () => {\n if (cancelled) return;\n timeoutId = setTimeout(async () => {\n if (document.visibilityState === \"visible\") {\n await load();\n }\n scheduleNext();\n }, 15_000);\n };\n\n scheduleNext();\n\n const handleVisibility = () => {\n if (document.visibilityState === \"visible\") {\n load();\n }\n };\n document.addEventListener(\"visibilitychange\", handleVisibility);\n\n return () => {\n cancelled = true;\n if (timeoutId) clearTimeout(timeoutId);\n document.removeEventListener(\"visibilitychange\", handleVisibility);\n };\n }, []); // runs once\n\n // ── Fetch logs when selection changes ──\n useEffect(() => {\n if (!selectedId) {\n setLogs([]);\n setLogsFailure(null);\n return;\n }\n let cancelled = false;\n const c = clientRef.current;\n if (!c?.cron) return;\n\n setLogsLoading(true);\n setLogsFailure(null);\n c.cron.getJobLogs(selectedId, { limit: 25 })\n .then(res => { if (!cancelled) setLogs(res.logs); })\n .catch((e: unknown) => {\n if (cancelled) return;\n // Same reasoning as `refreshLogs`: an empty log list is a claim.\n setLogs([]);\n setLogsFailure(classifyLoadFailure(e));\n })\n .finally(() => { if (!cancelled) setLogsLoading(false); });\n\n return () => { cancelled = true; };\n }, [selectedId]);\n\n // ── Imperative helpers (not in any dep array) ──\n async function refreshJobs() {\n const c = clientRef.current;\n if (!c?.cron) return;\n try {\n const res = await c.cron.listJobs();\n setJobs(res.jobs);\n setProblems(problemsOf(res));\n setFailure(null);\n } catch (e: unknown) {\n // Swallowed before, which left the list showing whatever it last\n // held — or nothing — after a failed refresh. \"No cron jobs\" and\n // \"could not read the cron jobs\" are not the same statement, so the\n // reason stays on screen rather than passing through a snackbar.\n setFailure(classifyLoadFailure(e));\n }\n }\n\n async function refreshLogs(id: string) {\n const c = clientRef.current;\n if (!c?.cron) return;\n setLogsLoading(true);\n setLogsFailure(null);\n try {\n const res = await c.cron.getJobLogs(id, { limit: 25 });\n setLogs(res.logs);\n } catch (e: unknown) {\n // Clearing the list silently reads as \"this job has never run\".\n setLogs([]);\n setLogsFailure(classifyLoadFailure(e));\n }\n finally { setLogsLoading(false); }\n }\n\n const handleTrigger = async (id: string) => {\n const c = clientRef.current;\n if (!c?.cron) return;\n setTriggering(id);\n try {\n // The request returns when the run has ended, with how it went: a\n // run that threw is still a 200, so the log decides the toast.\n const { log } = await c.cron.triggerJob(id);\n const job = jobs.find(j => j.id === id)?.name ?? id;\n snackbarRef.current.open(log.success\n ? { type: \"success\", message: t(\"studio_cron_run_succeeded\", { job }) }\n : { type: \"error\", message: t(\"studio_cron_run_failed\", { job, error: log.error ?? \"—\" }) });\n await refreshJobs();\n if (selectedId === id) refreshLogs(id);\n } catch (e: unknown) {\n snackbarRef.current.open({ type: \"error\",\nmessage: e instanceof Error ? e.message : String(e) });\n } finally { setTriggering(null); }\n };\n\n const handleToggle = async (id: string, enabled: boolean) => {\n const c = clientRef.current;\n if (!c?.cron) return;\n try {\n await c.cron.toggleJob(id, enabled);\n snackbarRef.current.open({ type: \"success\",\nmessage: enabled ? t(\"studio_cron_job_enabled\") : t(\"studio_cron_job_paused\") });\n await refreshJobs();\n } catch (e: unknown) {\n snackbarRef.current.open({ type: \"error\",\nmessage: e instanceof Error ? e.message : String(e) });\n }\n };\n\n const selectedJob = jobs.find(j => j.id === selectedId);\n\n if (loading) return <div className=\"flex items-center justify-center h-full\"><CircularProgress/></div>;\n\n if (failure) return (\n <LoadFailureView\n failure={failure}\n title={t(\"studio_cron_read_failed\")}\n deniedTitle={t(\"studio_cron_denied_title\")}\n deniedHint={t(\"studio_cron_denied_hint\")}\n onRetry={refreshJobs}\n />\n );\n\n const hasProblems = problems.rejected.length > 0 || problems.filesFailed > 0;\n\n if (jobs.length === 0) return (\n <div className=\"flex flex-col items-center justify-center h-full gap-4 text-center p-8\">\n <CalendarIcon size={iconSize.medium} className=\"text-surface-300 dark:text-surface-600\"/>\n {hasProblems ? (\n <div className=\"max-w-lg text-left\">\n <CronLoadProblems problems={problems}/>\n </div>\n ) : (\n <>\n <Typography variant=\"h6\" color=\"secondary\">{t(\"studio_cron_empty_title\")}</Typography>\n <Typography variant=\"body2\" color=\"disabled\" className=\"max-w-md\">\n {t(\"studio_cron_empty_body\")}\n </Typography>\n </>\n )}\n <a\n href=\"https://rebase.pro/docs/backend/cron-jobs\"\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-primary text-sm underline\"\n >\n {t(\"studio_read_the_docs\")}\n </a>\n </div>\n );\n\n return (\n <div className=\"flex h-full w-full overflow-hidden bg-surface-card\">\n {/* ── Job List ── */}\n <div className={cls(\"flex flex-col w-[340px] min-w-[280px] border-r h-full\", defaultBorderMixin)}>\n <div className={cls(\"flex items-center justify-between px-4 py-2.5 border-b bg-surface-sheet min-h-[48px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-2\">\n <CalendarIcon size={iconSize.smallest} className=\"text-primary\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold\">{t(\"studio_tool_cron\")}</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-raised text-surface-600 dark:text-surface-300\">{jobs.length}</Chip>\n </div>\n <IconButton aria-label={t(\"studio_cron_refresh\")} size=\"small\" onClick={refreshJobs} title={t(\"studio_cron_refresh\")}><RefreshCwIcon size={iconSize.smallest}/></IconButton>\n </div>\n {hasProblems && (\n <div className=\"p-2\">\n <CronLoadProblems problems={problems}/>\n </div>\n )}\n <div className=\"flex-1 overflow-y-auto p-2 space-y-1\">\n {jobs.map(job => (\n <div\n key={job.id}\n onClick={() => setSelectedId(job.id)}\n className={cls(\n \"flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all\",\n selectedId === job.id\n ? \"bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30\"\n : \"hover:bg-surface-hover\"\n )}\n >\n <div className={cls(\"w-2 h-2 rounded-full shrink-0\", stateColors[job.state] || \"bg-surface-400\")}/>\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"body2\" className=\"truncate font-medium text-[13px]\">{job.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate text-[11px] font-mono\">{job.schedule}</Typography>\n </div>\n {job.state === \"running\" && <CircularProgress size=\"smallest\"/>}\n </div>\n ))}\n </div>\n </div>\n\n {/* ── Detail Panel ── */}\n <div className=\"flex-1 flex flex-col min-w-0 h-full overflow-hidden\">\n {!selectedJob ? (\n <div className=\"flex items-center justify-center h-full\">\n <Typography variant=\"body2\" color=\"disabled\">{t(\"studio_cron_select_job\")}</Typography>\n </div>\n ) : (\n <>\n {/* Header */}\n <div className={cls(\"flex items-center justify-between px-5 py-3 border-b bg-surface-card min-h-[56px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className={cls(\"w-2.5 h-2.5 rounded-full\", stateColors[selectedJob.state])}/>\n <div className=\"min-w-0\">\n <Typography variant=\"subtitle1\" className=\"font-semibold truncate\">{selectedJob.name}</Typography>\n {selectedJob.description && <Typography variant=\"caption\" color=\"secondary\" className=\"truncate\">{selectedJob.description}</Typography>}\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <IconButton aria-label={selectedJob.enabled ? t(\"studio_cron_pause_job\") : t(\"studio_cron_enable_job\")} title={selectedJob.enabled ? t(\"studio_cron_pause_job\") : t(\"studio_cron_enable_job\")} size=\"small\" onClick={() => handleToggle(selectedJob.id, !selectedJob.enabled)}>\n {selectedJob.enabled ? <PauseIcon size={iconSize.small}/> : <PlayIcon size={iconSize.smallest}/>}\n </IconButton>\n <Button\n size=\"small\"\n color=\"primary\"\n onClick={() => handleTrigger(selectedJob.id)}\n disabled={triggering === selectedJob.id}\n startIcon={triggering === selectedJob.id ? <CircularProgress size=\"smallest\"/> : <PlayIcon size={iconSize.smallest}/>}\n >\n {t(\"studio_cron_run_now\")}\n </Button>\n </div>\n </div>\n\n {/* Stats Cards */}\n <div className=\"px-5 py-4 bg-surface-sheet\">\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-3\">\n <StatCard label={t(\"studio_cron_schedule\")} value={selectedJob.schedule} mono/>\n <StatCard label={t(\"studio_cron_last_run\")} value={formatRelative(selectedJob.lastRunAt, language)}/>\n <StatCard label={t(\"studio_cron_next_run\")} value={selectedJob.enabled ? formatRelative(selectedJob.nextRunAt, language) : t(\"studio_cron_paused\")}/>\n <StatCard label={t(\"studio_cron_duration\")} value={selectedJob.lastDurationMs !== undefined ? formatDuration(selectedJob.lastDurationMs) : \"—\"}/>\n </div>\n <div className=\"grid grid-cols-3 gap-3 mt-3\">\n <StatCard label={t(\"studio_cron_status\")} value={selectedJob.state.toUpperCase()} chipColor={selectedJob.state === \"error\" ? \"red\" : selectedJob.state === \"disabled\" ? \"gray\" : \"green\"}/>\n <StatCard label={t(\"studio_cron_total_runs\")} value={String(selectedJob.totalRuns)}/>\n <StatCard label={t(\"studio_cron_failures\")} value={String(selectedJob.totalFailures)} highlight={selectedJob.totalFailures > 0}/>\n </div>\n {selectedJob.lastError && (\n <Alert color=\"error\" outerClassName=\"mt-3\">\n <Typography variant=\"caption\" className=\"font-medium block\">{t(\"studio_cron_last_error\")}</Typography>\n <Typography variant=\"mono\" className=\"text-[11px] break-all\">{selectedJob.lastError}</Typography>\n </Alert>\n )}\n </div>\n\n {/* Logs Section */}\n <div className={cls(\"flex items-center justify-between px-5 py-2 border-y bg-surface-card\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-2\">\n <HistoryIcon size={iconSize.smallest} className=\"text-surface-400\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold text-[13px]\">{t(\"studio_cron_execution_history\")}</Typography>\n </div>\n <IconButton aria-label={t(\"studio_cron_refresh_logs\")} size=\"small\" onClick={() => refreshLogs(selectedJob.id)} title={t(\"studio_cron_refresh_logs\")}><RefreshCwIcon size={iconSize.smallest}/></IconButton>\n </div>\n <div className=\"flex-1 overflow-y-auto\">\n {logsLoading ? (\n <div className=\"flex justify-center p-8\"><CircularProgress size=\"small\"/></div>\n ) : logsFailure ? (\n <LoadFailureView\n failure={logsFailure}\n title={t(\"studio_cron_logs_read_failed\")}\n deniedTitle={t(\"studio_cron_logs_denied_title\")}\n deniedHint={t(\"studio_cron_denied_hint\")}\n onRetry={() => refreshLogs(selectedJob.id)}\n />\n ) : logs.length === 0 ? (\n <div className=\"flex items-center justify-center h-32\">\n <Typography variant=\"body2\" color=\"disabled\">{t(\"studio_cron_no_executions\")}</Typography>\n </div>\n ) : (\n <div className=\"divide-y divide-surface-100 dark:divide-surface-950\">\n {logs.map((log, idx) => (\n <LogRow key={idx} log={log}/>\n ))}\n </div>\n )}\n </div>\n </>\n )}\n </div>\n </div>\n );\n}\n\nfunction StatCard({ label, value, mono, chipColor, highlight }: {\n label: string; value: string; mono?: boolean; chipColor?: string; highlight?: boolean;\n}) {\n return (\n <div className={cls(\"px-3 py-2 rounded-lg border bg-surface-card\", defaultBorderMixin)}>\n <Typography variant=\"caption\" color=\"secondary\">{label}</Typography>\n <Typography variant=\"body2\" className={cls(\n \"mt-0.5 font-semibold text-[13px]\",\n mono && \"font-mono\",\n highlight && \"text-red-500 dark:text-red-400\",\n chipColor === \"red\" && \"text-red-500\",\n chipColor === \"green\" && \"text-emerald-500\",\n chipColor === \"gray\" && \"text-surface-400\"\n )}>{value}</Typography>\n </div>\n );\n}\n\nfunction LogRow({ log }: { log: CronJobLogEntry }) {\n const { t } = useTranslation();\n const [expanded, setExpanded] = useState(false);\n return (\n <div className=\"px-5 py-2.5 hover:bg-surface-hover transition-colors\">\n <div className=\"flex items-center gap-3 cursor-pointer\" onClick={() => setExpanded(!expanded)}>\n {log.success\n ? <CheckCircleIcon size={iconSize.smallest} className=\"text-emerald-500 shrink-0\"/>\n : <AlertCircleIcon size={iconSize.smallest} className=\"text-red-500 shrink-0\"/>}\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"caption\" className=\"font-mono text-[11px] text-surface-500\">{new Date(log.startedAt).toLocaleString()}</Typography>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n {log.manual && <Chip size=\"smallest\">{t(\"studio_cron_manual_run\")}</Chip>}\n <Typography variant=\"caption\" className=\"font-mono text-[11px]\">{formatDuration(log.durationMs)}</Typography>\n <ChevronDownIcon size={iconSize.smallest} className={cls(\"transition-transform text-text-secondary dark:text-text-secondary-dark\", expanded && \"rotate-180\")}/>\n </div>\n </div>\n {expanded && (\n <div className=\"mt-2 ml-6 space-y-2\">\n {log.error && (\n <Alert color=\"error\" size=\"small\">\n <Typography variant=\"mono\" className=\"text-[11px] break-all\">{log.error}</Typography>\n </Alert>\n )}\n {log.logs.length > 0 && (\n <div className=\"p-2 rounded bg-surface-sheet border border-hairline-strong max-h-40 overflow-auto\">\n {log.logs.map((line, i) => (\n <div key={i} className=\"font-mono text-[11px] text-surface-600 dark:text-surface-400 leading-relaxed\">{line}</div>\n ))}\n </div>\n )}\n {log.result !== undefined && (\n <div className=\"p-2 rounded bg-surface-sheet border border-hairline-strong\">\n <Typography variant=\"caption\" className=\"text-[10px] uppercase tracking-wider text-surface-400 mb-1 block\">Result</Typography>\n <pre className=\"font-mono text-[11px] text-surface-600 dark:text-surface-400 whitespace-pre-wrap break-all\">{JSON.stringify(log.result, null, 2)}</pre>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|