@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.
Files changed (68) hide show
  1. package/dist/{ApiExplorer-pYMZflL3.js → ApiExplorer-Bzt7Ye49.js} +18 -29
  2. package/dist/ApiExplorer-Bzt7Ye49.js.map +1 -0
  3. package/dist/ApiKeysView-Bqf6XYR1.js +1739 -0
  4. package/dist/ApiKeysView-Bqf6XYR1.js.map +1 -0
  5. package/dist/BackupsView-BhOHB6iP.js +354 -0
  6. package/dist/BackupsView-BhOHB6iP.js.map +1 -0
  7. package/dist/{BranchesView-D6Sz1a5f.js → BranchesView-BHfwGTYc.js} +16 -10
  8. package/dist/BranchesView-BHfwGTYc.js.map +1 -0
  9. package/dist/{CronJobsView-7hjgSYId.js → CronJobsView-CMNK7eWO.js} +175 -116
  10. package/dist/CronJobsView-CMNK7eWO.js.map +1 -0
  11. package/dist/{JSEditor-qiuwmcvk.js → JSEditor-DIO2Mw8q.js} +230 -165
  12. package/dist/JSEditor-DIO2Mw8q.js.map +1 -0
  13. package/dist/{LogsExplorer-Xq8K4oo6.js → LogsExplorer-C-bwDFIl.js} +66 -44
  14. package/dist/LogsExplorer-C-bwDFIl.js.map +1 -0
  15. package/dist/{RLSEditor-Djh43lBE.js → RLSEditor-jFD_d06f.js} +246 -84
  16. package/dist/RLSEditor-jFD_d06f.js.map +1 -0
  17. package/dist/{SQLEditor-TBw_K6CW.js → SQLEditor-D5Ci4pyW.js} +502 -600
  18. package/dist/SQLEditor-D5Ci4pyW.js.map +1 -0
  19. package/dist/{SchemaVisualizer-DtDzIUNy.js → SchemaVisualizer-D-rAqwul.js} +496 -227
  20. package/dist/SchemaVisualizer-D-rAqwul.js.map +1 -0
  21. package/dist/{StorageView-CMG1DOVX.js → StorageView-CPkDKbsX.js} +838 -451
  22. package/dist/StorageView-CPkDKbsX.js.map +1 -0
  23. package/dist/assets/dagre-layout.worker-BFnhAzuR.js.map +1 -0
  24. package/dist/components/ApiExplorer/TryItPanel.d.ts +6 -1
  25. package/dist/components/ApiKeys/CreateApiKeyDialog.d.ts +21 -2
  26. package/dist/components/ApiKeys/scope-words.d.ts +37 -0
  27. package/dist/components/ApiKeys/scopes.d.ts +113 -0
  28. package/dist/components/AuthSimulationSelector.d.ts +5 -1
  29. package/dist/components/LogsExplorer/useLogTail.d.ts +9 -0
  30. package/dist/components/RLSEditor/policyListing.d.ts +44 -0
  31. package/dist/components/SQLEditor/SQLEditor.d.ts +11 -0
  32. package/dist/components/SchemaVisualizer/dagre-layout.d.ts +32 -0
  33. package/dist/components/SchemaVisualizer/dagre-layout.worker.d.ts +13 -0
  34. package/dist/components/SchemaVisualizer/layout-runner.d.ts +23 -0
  35. package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +21 -11
  36. package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +44 -1
  37. package/dist/components/StorageView/useVisibleFileMetadata.d.ts +46 -0
  38. package/dist/components/load-failure.d.ts +1 -1
  39. package/dist/components/useRunAsUsers.d.ts +25 -0
  40. package/dist/dagre-layout.worker-j9NbH9Gb.js +27 -0
  41. package/dist/dagre-layout.worker-j9NbH9Gb.js.map +1 -0
  42. package/dist/index.es.js +16 -14
  43. package/dist/index.es.js.map +1 -1
  44. package/dist/{load-failure-C6OMJSGC.js → load-failure-Bd8lFIO7.js} +2 -2
  45. package/dist/load-failure-Bd8lFIO7.js.map +1 -0
  46. package/dist/sql_utils-BZD31xE3.js +478 -0
  47. package/dist/sql_utils-BZD31xE3.js.map +1 -0
  48. package/dist/useRunAsUsers-CLaAtXt6.js +122 -0
  49. package/dist/useRunAsUsers-CLaAtXt6.js.map +1 -0
  50. package/dist/utils/sql_utils.d.ts +150 -49
  51. package/package.json +9 -9
  52. package/dist/ApiExplorer-pYMZflL3.js.map +0 -1
  53. package/dist/ApiKeysView-qLSs47tw.js +0 -1239
  54. package/dist/ApiKeysView-qLSs47tw.js.map +0 -1
  55. package/dist/AuthSimulationSelector-BewmhMBw.js +0 -56
  56. package/dist/AuthSimulationSelector-BewmhMBw.js.map +0 -1
  57. package/dist/BackupsView-DYTAD6aW.js +0 -253
  58. package/dist/BackupsView-DYTAD6aW.js.map +0 -1
  59. package/dist/BranchesView-D6Sz1a5f.js.map +0 -1
  60. package/dist/CronJobsView-7hjgSYId.js.map +0 -1
  61. package/dist/JSEditor-qiuwmcvk.js.map +0 -1
  62. package/dist/LogsExplorer-Xq8K4oo6.js.map +0 -1
  63. package/dist/RLSEditor-Djh43lBE.js.map +0 -1
  64. package/dist/SQLEditor-TBw_K6CW.js.map +0 -1
  65. package/dist/SchemaVisualizer-DtDzIUNy.js.map +0 -1
  66. package/dist/StorageView-CMG1DOVX.js.map +0 -1
  67. package/dist/components/ApiKeys/permissions.d.ts +0 -66
  68. package/dist/load-failure-C6OMJSGC.js.map +0 -1
@@ -1,8 +1,8 @@
1
- import { t as classifyLoadFailure } from "./load-failure-C6OMJSGC.js";
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
- function formatRelative(iso) {
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 now = Date.now();
17
- const diff = d.getTime() - now;
17
+ const diff = d.getTime() - Date.now();
18
18
  const abs = Math.abs(diff);
19
- if (abs < 6e4) return diff > 0 ? "in <1m" : "<1m ago";
20
- if (abs < 36e5) {
21
- const m = Math.round(abs / 6e4);
22
- return diff > 0 ? `in ${m}m` : `${m}m ago`;
23
- }
24
- if (abs < 864e5) {
25
- const h = Math.round(abs / 36e5);
26
- return diff > 0 ? `in ${h}h` : `${h}h ago`;
27
- }
28
- return d.toLocaleString();
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
- snackbarRef.current.open({
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: "Job triggered"
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 ? "Job enabled" : "Job paused"
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(Typography, {
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: [/* @__PURE__ */ jsxs("div", {
231
- className: cls("flex items-center justify-between px-4 py-2.5 border-b bg-surface-sheet min-h-[48px]", defaultBorderMixin),
232
- children: [/* @__PURE__ */ jsxs("div", {
233
- className: "flex items-center gap-2",
234
- children: [
235
- /* @__PURE__ */ jsx(CalendarIcon, {
236
- size: iconSize.smallest,
237
- className: "text-primary"
238
- }),
239
- /* @__PURE__ */ jsx(Typography, {
240
- variant: "subtitle2",
241
- className: "font-semibold",
242
- children: t("studio_tool_cron")
243
- }),
244
- /* @__PURE__ */ jsx(Chip, {
245
- size: "smallest",
246
- className: "bg-surface-raised text-surface-600 dark:text-surface-300",
247
- children: jobs.length
248
- })
249
- ]
250
- }), /* @__PURE__ */ jsx(IconButton, {
251
- size: "small",
252
- onClick: refreshJobs,
253
- title: "Refresh",
254
- children: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest })
255
- })]
256
- }), /* @__PURE__ */ jsx("div", {
257
- className: "flex-1 overflow-y-auto p-2 space-y-1",
258
- children: jobs.map((job) => /* @__PURE__ */ jsxs("div", {
259
- onClick: () => setSelectedId(job.id),
260
- 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"),
261
- children: [
262
- /* @__PURE__ */ jsx("div", { className: cls("w-2 h-2 rounded-full shrink-0", stateColors[job.state] || "bg-surface-400") }),
263
- /* @__PURE__ */ jsxs("div", {
264
- className: "flex-1 min-w-0",
265
- children: [/* @__PURE__ */ jsx(Typography, {
266
- variant: "body2",
267
- className: "truncate font-medium text-[13px]",
268
- children: job.name
269
- }), /* @__PURE__ */ jsx(Typography, {
270
- variant: "caption",
271
- color: "secondary",
272
- className: "truncate text-[11px] font-mono",
273
- children: job.schedule
274
- })]
275
- }),
276
- job.state === "running" && /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" })
277
- ]
278
- }, job.id))
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: "Select a cron job to view details"
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
- title: selectedJob.enabled ? "Pause job" : "Enable job",
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: "Run Now"
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: "Schedule",
396
+ label: t("studio_cron_schedule"),
332
397
  value: selectedJob.schedule,
333
398
  mono: true
334
399
  }),
335
400
  /* @__PURE__ */ jsx(StatCard, {
336
- label: "Last Run",
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: "Next Run",
341
- value: selectedJob.enabled ? formatRelative(selectedJob.nextRunAt) : "Paused"
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: "Duration",
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: "Status",
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: "Total Runs",
423
+ label: t("studio_cron_total_runs"),
359
424
  value: String(selectedJob.totalRuns)
360
425
  }),
361
426
  /* @__PURE__ */ jsx(StatCard, {
362
- label: "Failures",
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("div", {
369
- className: "mt-3 p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800/50",
370
- children: [/* @__PURE__ */ jsxs("div", {
371
- className: "flex items-center gap-2 mb-1",
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-mono text-red-600 dark:text-red-300 text-[11px] break-all",
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: "Execution History"
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: "Refresh logs",
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: "No executions yet"
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
- className: "bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300",
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("svg", {
486
- className: cls("w-3 h-3 transition-transform text-surface-400", expanded && "rotate-180"),
487
- fill: "currentColor",
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("div", {
498
- className: "p-2 rounded bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800/50",
555
+ log.error && /* @__PURE__ */ jsx(Alert, {
556
+ color: "error",
557
+ size: "small",
499
558
  children: /* @__PURE__ */ jsx(Typography, {
500
- variant: "caption",
501
- className: "font-mono text-[11px] text-red-600 dark:text-red-300 break-all",
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-7hjgSYId.js.map
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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"}