@rebasepro/studio 0.22.0 → 0.24.0

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 (62) hide show
  1. package/dist/{ApiExplorer-D-jLWAaY.js → ApiExplorer-CCtczj3F.js} +16 -32
  2. package/dist/ApiExplorer-CCtczj3F.js.map +1 -0
  3. package/dist/ApiKeysView-B66TdzhH.js +1738 -0
  4. package/dist/ApiKeysView-B66TdzhH.js.map +1 -0
  5. package/dist/BackupsView-DmSMzAbT.js +368 -0
  6. package/dist/BackupsView-DmSMzAbT.js.map +1 -0
  7. package/dist/{BranchesView-D6Sz1a5f.js → BranchesView-DP1a87E-.js} +12 -8
  8. package/dist/BranchesView-DP1a87E-.js.map +1 -0
  9. package/dist/{CronJobsView-JwRjQCde.js → CronJobsView-TDErHTuK.js} +143 -68
  10. package/dist/CronJobsView-TDErHTuK.js.map +1 -0
  11. package/dist/{JSEditor-qiuwmcvk.js → JSEditor-B59pwg4c.js} +120 -74
  12. package/dist/JSEditor-B59pwg4c.js.map +1 -0
  13. package/dist/{LogsExplorer-Xq8K4oo6.js → LogsExplorer-BKM4yfP8.js} +48 -22
  14. package/dist/LogsExplorer-BKM4yfP8.js.map +1 -0
  15. package/dist/{MonacoEditor-D0_QljcX.js → MonacoEditor-CUxy5_k5.js} +11 -9
  16. package/dist/MonacoEditor-CUxy5_k5.js.map +1 -0
  17. package/dist/{RLSEditor-B-qrgdHy.js → RLSEditor-BlLLV6xs.js} +350 -98
  18. package/dist/RLSEditor-BlLLV6xs.js.map +1 -0
  19. package/dist/{SQLEditor-sdtoZv5F.js → SQLEditor-1An7w0j6.js} +496 -224
  20. package/dist/SQLEditor-1An7w0j6.js.map +1 -0
  21. package/dist/{SchemaVisualizer-DtDzIUNy.js → SchemaVisualizer-DCLVfA5t.js} +20 -24
  22. package/dist/SchemaVisualizer-DCLVfA5t.js.map +1 -0
  23. package/dist/{StorageView-C52dSZNf.js → StorageView-CbsHuCBf.js} +382 -228
  24. package/dist/StorageView-CbsHuCBf.js.map +1 -0
  25. package/dist/components/ApiExplorer/TryItPanel.d.ts +6 -1
  26. package/dist/components/ApiKeys/CreateApiKeyDialog.d.ts +21 -2
  27. package/dist/components/ApiKeys/scope-words.d.ts +37 -0
  28. package/dist/components/ApiKeys/scopes.d.ts +113 -0
  29. package/dist/components/AuthSimulationSelector.d.ts +5 -1
  30. package/dist/components/LogsExplorer/useLogTail.d.ts +9 -0
  31. package/dist/components/RLSEditor/policyListing.d.ts +44 -0
  32. package/dist/components/RLSEditor/policyRules.d.ts +49 -0
  33. package/dist/components/SQLEditor/MonacoEditor.d.ts +13 -1
  34. package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +0 -1
  35. package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +6 -0
  36. package/dist/components/load-failure.d.ts +1 -1
  37. package/dist/components/useRunAsUsers.d.ts +25 -0
  38. package/dist/index.es.js +11 -11
  39. package/dist/{load-failure-C6OMJSGC.js → load-failure-Bd8lFIO7.js} +2 -2
  40. package/dist/load-failure-Bd8lFIO7.js.map +1 -0
  41. package/dist/useRunAsUsers-CLaAtXt6.js +122 -0
  42. package/dist/useRunAsUsers-CLaAtXt6.js.map +1 -0
  43. package/dist/utils/sql_utils.d.ts +126 -36
  44. package/package.json +9 -9
  45. package/dist/ApiExplorer-D-jLWAaY.js.map +0 -1
  46. package/dist/ApiKeysView-OfJLH2x7.js +0 -1239
  47. package/dist/ApiKeysView-OfJLH2x7.js.map +0 -1
  48. package/dist/AuthSimulationSelector-BewmhMBw.js +0 -56
  49. package/dist/AuthSimulationSelector-BewmhMBw.js.map +0 -1
  50. package/dist/BackupsView-DYTAD6aW.js +0 -253
  51. package/dist/BackupsView-DYTAD6aW.js.map +0 -1
  52. package/dist/BranchesView-D6Sz1a5f.js.map +0 -1
  53. package/dist/CronJobsView-JwRjQCde.js.map +0 -1
  54. package/dist/JSEditor-qiuwmcvk.js.map +0 -1
  55. package/dist/LogsExplorer-Xq8K4oo6.js.map +0 -1
  56. package/dist/MonacoEditor-D0_QljcX.js.map +0 -1
  57. package/dist/RLSEditor-B-qrgdHy.js.map +0 -1
  58. package/dist/SQLEditor-sdtoZv5F.js.map +0 -1
  59. package/dist/SchemaVisualizer-DtDzIUNy.js.map +0 -1
  60. package/dist/StorageView-C52dSZNf.js.map +0 -1
  61. package/dist/components/ApiKeys/permissions.d.ts +0 -66
  62. 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, 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) {
@@ -27,6 +27,49 @@ function formatRelative(iso) {
27
27
  }
28
28
  return d.toLocaleString();
29
29
  }
30
+ function problemsOf(listing) {
31
+ const rejected = listing.rejected ?? [];
32
+ return {
33
+ rejected,
34
+ filesFailed: Math.max(0, (listing.skipped ?? 0) - rejected.length)
35
+ };
36
+ }
37
+ /**
38
+ * The cron files that failed to load and the jobs the scheduler refused. None
39
+ * of them is scheduled, and none is in the list: without this a refused job
40
+ * was simply absent, and a project whose only job was refused read "No cron
41
+ * jobs registered".
42
+ */
43
+ function CronLoadProblems({ problems }) {
44
+ const { t } = useTranslation();
45
+ if (problems.rejected.length === 0 && problems.filesFailed === 0) return null;
46
+ return /* @__PURE__ */ jsx(Alert, {
47
+ color: "error",
48
+ children: /* @__PURE__ */ jsxs("div", {
49
+ className: "flex flex-col gap-2",
50
+ children: [problems.rejected.map((job) => /* @__PURE__ */ jsxs("div", {
51
+ className: "flex flex-col",
52
+ children: [/* @__PURE__ */ jsx(Typography, {
53
+ variant: "body2",
54
+ className: "text-[13px] font-semibold",
55
+ children: t("studio_cron_job_refused", { job: job.name })
56
+ }), /* @__PURE__ */ jsxs(Typography, {
57
+ variant: "caption",
58
+ className: "font-mono text-[12px] break-words",
59
+ children: [
60
+ job.schedule,
61
+ " — ",
62
+ job.reason
63
+ ]
64
+ })]
65
+ }, job.id)), problems.filesFailed > 0 && /* @__PURE__ */ jsx(Typography, {
66
+ variant: "body2",
67
+ className: "text-[13px]",
68
+ children: t("studio_cron_files_failed", { count: problems.filesFailed })
69
+ })]
70
+ })
71
+ });
72
+ }
30
73
  var stateColors = {
31
74
  idle: "bg-emerald-500",
32
75
  running: "bg-blue-500",
@@ -39,10 +82,20 @@ function CronJobsView() {
39
82
  const snackbar = useSnackbarController();
40
83
  const { t } = useTranslation();
41
84
  const [jobs, setJobs] = useState([]);
85
+ const [problems, setProblems] = useState({
86
+ rejected: [],
87
+ filesFailed: 0
88
+ });
42
89
  const [loading, setLoading] = useState(true);
43
90
  const [selectedId, setSelectedId] = useState(null);
44
91
  const [logs, setLogs] = useState([]);
45
92
  const [logsLoading, setLogsLoading] = useState(false);
93
+ /**
94
+ * Why the selected job's executions could not be read. Shown where the
95
+ * list would be: an empty list reads as "this job has never run", and the
96
+ * snackbar that used to carry the reason was gone in four seconds.
97
+ */
98
+ const [logsFailure, setLogsFailure] = useState(null);
46
99
  const [triggering, setTriggering] = useState(null);
47
100
  /** Why the job listing failed, classified — see `load-failure.ts`. */
48
101
  const [failure, setFailure] = useState(null);
@@ -62,6 +115,7 @@ function CronJobsView() {
62
115
  const res = await c.cron.listJobs();
63
116
  if (!cancelled) {
64
117
  setJobs(res.jobs);
118
+ setProblems(problemsOf(res));
65
119
  setFailure(null);
66
120
  }
67
121
  } catch (e) {
@@ -93,21 +147,20 @@ function CronJobsView() {
93
147
  useEffect(() => {
94
148
  if (!selectedId) {
95
149
  setLogs([]);
150
+ setLogsFailure(null);
96
151
  return;
97
152
  }
98
153
  let cancelled = false;
99
154
  const c = clientRef.current;
100
155
  if (!c?.cron) return;
101
156
  setLogsLoading(true);
157
+ setLogsFailure(null);
102
158
  c.cron.getJobLogs(selectedId, { limit: 25 }).then((res) => {
103
159
  if (!cancelled) setLogs(res.logs);
104
160
  }).catch((e) => {
105
161
  if (cancelled) return;
106
162
  setLogs([]);
107
- snackbarRef.current.open({
108
- type: "error",
109
- message: e instanceof Error ? e.message : String(e)
110
- });
163
+ setLogsFailure(classifyLoadFailure(e));
111
164
  }).finally(() => {
112
165
  if (!cancelled) setLogsLoading(false);
113
166
  });
@@ -121,6 +174,7 @@ function CronJobsView() {
121
174
  try {
122
175
  const res = await c.cron.listJobs();
123
176
  setJobs(res.jobs);
177
+ setProblems(problemsOf(res));
124
178
  setFailure(null);
125
179
  } catch (e) {
126
180
  setFailure(classifyLoadFailure(e));
@@ -130,15 +184,13 @@ function CronJobsView() {
130
184
  const c = clientRef.current;
131
185
  if (!c?.cron) return;
132
186
  setLogsLoading(true);
187
+ setLogsFailure(null);
133
188
  try {
134
189
  const res = await c.cron.getJobLogs(id, { limit: 25 });
135
190
  setLogs(res.logs);
136
191
  } catch (e) {
137
192
  setLogs([]);
138
- snackbarRef.current.open({
139
- type: "error",
140
- message: e instanceof Error ? e.message : String(e)
141
- });
193
+ setLogsFailure(classifyLoadFailure(e));
142
194
  } finally {
143
195
  setLogsLoading(false);
144
196
  }
@@ -148,10 +200,17 @@ function CronJobsView() {
148
200
  if (!c?.cron) return;
149
201
  setTriggering(id);
150
202
  try {
151
- await c.cron.triggerJob(id);
152
- snackbarRef.current.open({
203
+ const { log } = await c.cron.triggerJob(id);
204
+ const job = jobs.find((j) => j.id === id)?.name ?? id;
205
+ snackbarRef.current.open(log.success ? {
153
206
  type: "success",
154
- message: "Job triggered"
207
+ message: t("studio_cron_run_succeeded", { job })
208
+ } : {
209
+ type: "error",
210
+ message: t("studio_cron_run_failed", {
211
+ job,
212
+ error: log.error ?? "—"
213
+ })
155
214
  });
156
215
  await refreshJobs();
157
216
  if (selectedId === id) refreshLogs(id);
@@ -193,6 +252,7 @@ function CronJobsView() {
193
252
  deniedHint: t("studio_cron_denied_hint"),
194
253
  onRetry: refreshJobs
195
254
  });
255
+ const hasProblems = problems.rejected.length > 0 || problems.filesFailed > 0;
196
256
  if (jobs.length === 0) return /* @__PURE__ */ jsxs("div", {
197
257
  className: "flex flex-col items-center justify-center h-full gap-4 text-center p-8",
198
258
  children: [
@@ -200,17 +260,19 @@ function CronJobsView() {
200
260
  size: iconSize.medium,
201
261
  className: "text-surface-300 dark:text-surface-600"
202
262
  }),
203
- /* @__PURE__ */ jsx(Typography, {
263
+ hasProblems ? /* @__PURE__ */ jsx("div", {
264
+ className: "max-w-lg text-left",
265
+ children: /* @__PURE__ */ jsx(CronLoadProblems, { problems })
266
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Typography, {
204
267
  variant: "h6",
205
268
  color: "secondary",
206
269
  children: t("studio_cron_empty_title")
207
- }),
208
- /* @__PURE__ */ jsx(Typography, {
270
+ }), /* @__PURE__ */ jsx(Typography, {
209
271
  variant: "body2",
210
272
  color: "disabled",
211
273
  className: "max-w-md",
212
274
  children: t("studio_cron_empty_body")
213
- }),
275
+ })] }),
214
276
  /* @__PURE__ */ jsx("a", {
215
277
  href: "https://rebase.pro/docs/backend/cron-jobs",
216
278
  target: "_blank",
@@ -224,56 +286,63 @@ function CronJobsView() {
224
286
  className: "flex h-full w-full overflow-hidden bg-surface-card",
225
287
  children: [/* @__PURE__ */ jsxs("div", {
226
288
  className: cls("flex flex-col w-[340px] min-w-[280px] border-r h-full", defaultBorderMixin),
227
- children: [/* @__PURE__ */ jsxs("div", {
228
- className: cls("flex items-center justify-between px-4 py-2.5 border-b bg-surface-sheet min-h-[48px]", defaultBorderMixin),
229
- children: [/* @__PURE__ */ jsxs("div", {
230
- className: "flex items-center gap-2",
231
- children: [
232
- /* @__PURE__ */ jsx(CalendarIcon, {
233
- size: iconSize.smallest,
234
- className: "text-primary"
235
- }),
236
- /* @__PURE__ */ jsx(Typography, {
237
- variant: "subtitle2",
238
- className: "font-semibold",
239
- children: t("studio_tool_cron")
240
- }),
241
- /* @__PURE__ */ jsx(Chip, {
242
- size: "smallest",
243
- className: "bg-surface-raised text-surface-600 dark:text-surface-300",
244
- children: jobs.length
245
- })
246
- ]
247
- }), /* @__PURE__ */ jsx(IconButton, {
248
- size: "small",
249
- onClick: refreshJobs,
250
- title: "Refresh",
251
- children: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest })
252
- })]
253
- }), /* @__PURE__ */ jsx("div", {
254
- className: "flex-1 overflow-y-auto p-2 space-y-1",
255
- children: jobs.map((job) => /* @__PURE__ */ jsxs("div", {
256
- onClick: () => setSelectedId(job.id),
257
- 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"),
258
- children: [
259
- /* @__PURE__ */ jsx("div", { className: cls("w-2 h-2 rounded-full shrink-0", stateColors[job.state] || "bg-surface-400") }),
260
- /* @__PURE__ */ jsxs("div", {
261
- className: "flex-1 min-w-0",
262
- children: [/* @__PURE__ */ jsx(Typography, {
263
- variant: "body2",
264
- className: "truncate font-medium text-[13px]",
265
- children: job.name
266
- }), /* @__PURE__ */ jsx(Typography, {
267
- variant: "caption",
268
- color: "secondary",
269
- className: "truncate text-[11px] font-mono",
270
- children: job.schedule
271
- })]
272
- }),
273
- job.state === "running" && /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" })
274
- ]
275
- }, job.id))
276
- })]
289
+ children: [
290
+ /* @__PURE__ */ jsxs("div", {
291
+ className: cls("flex items-center justify-between px-4 py-2.5 border-b bg-surface-sheet min-h-[48px]", defaultBorderMixin),
292
+ children: [/* @__PURE__ */ jsxs("div", {
293
+ className: "flex items-center gap-2",
294
+ children: [
295
+ /* @__PURE__ */ jsx(CalendarIcon, {
296
+ size: iconSize.smallest,
297
+ className: "text-primary"
298
+ }),
299
+ /* @__PURE__ */ jsx(Typography, {
300
+ variant: "subtitle2",
301
+ className: "font-semibold",
302
+ children: t("studio_tool_cron")
303
+ }),
304
+ /* @__PURE__ */ jsx(Chip, {
305
+ size: "smallest",
306
+ className: "bg-surface-raised text-surface-600 dark:text-surface-300",
307
+ children: jobs.length
308
+ })
309
+ ]
310
+ }), /* @__PURE__ */ jsx(IconButton, {
311
+ size: "small",
312
+ onClick: refreshJobs,
313
+ title: "Refresh",
314
+ children: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest })
315
+ })]
316
+ }),
317
+ hasProblems && /* @__PURE__ */ jsx("div", {
318
+ className: "p-2",
319
+ children: /* @__PURE__ */ jsx(CronLoadProblems, { problems })
320
+ }),
321
+ /* @__PURE__ */ jsx("div", {
322
+ className: "flex-1 overflow-y-auto p-2 space-y-1",
323
+ children: jobs.map((job) => /* @__PURE__ */ jsxs("div", {
324
+ onClick: () => setSelectedId(job.id),
325
+ 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"),
326
+ children: [
327
+ /* @__PURE__ */ jsx("div", { className: cls("w-2 h-2 rounded-full shrink-0", stateColors[job.state] || "bg-surface-400") }),
328
+ /* @__PURE__ */ jsxs("div", {
329
+ className: "flex-1 min-w-0",
330
+ children: [/* @__PURE__ */ jsx(Typography, {
331
+ variant: "body2",
332
+ className: "truncate font-medium text-[13px]",
333
+ children: job.name
334
+ }), /* @__PURE__ */ jsx(Typography, {
335
+ variant: "caption",
336
+ color: "secondary",
337
+ className: "truncate text-[11px] font-mono",
338
+ children: job.schedule
339
+ })]
340
+ }),
341
+ job.state === "running" && /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" })
342
+ ]
343
+ }, job.id))
344
+ })
345
+ ]
277
346
  }), /* @__PURE__ */ jsx("div", {
278
347
  className: "flex-1 flex flex-col min-w-0 h-full overflow-hidden",
279
348
  children: !selectedJob ? /* @__PURE__ */ jsx("div", {
@@ -406,6 +475,12 @@ function CronJobsView() {
406
475
  children: logsLoading ? /* @__PURE__ */ jsx("div", {
407
476
  className: "flex justify-center p-8",
408
477
  children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
478
+ }) : logsFailure ? /* @__PURE__ */ jsx(LoadFailureView, {
479
+ failure: logsFailure,
480
+ title: t("studio_cron_logs_read_failed"),
481
+ deniedTitle: t("studio_cron_logs_denied_title"),
482
+ deniedHint: t("studio_cron_denied_hint"),
483
+ onRetry: () => refreshLogs(selectedJob.id)
409
484
  }) : logs.length === 0 ? /* @__PURE__ */ jsx("div", {
410
485
  className: "flex items-center justify-center h-32",
411
486
  children: /* @__PURE__ */ jsx(Typography, {
@@ -518,4 +593,4 @@ function LogRow({ log }) {
518
593
  //#endregion
519
594
  export { CronJobsView };
520
595
 
521
- //# sourceMappingURL=CronJobsView-JwRjQCde.js.map
596
+ //# sourceMappingURL=CronJobsView-TDErHTuK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CronJobsView-TDErHTuK.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 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\nfunction formatRelative(iso: string | undefined): string {\n if (!iso) return \"—\";\n const d = new Date(iso);\n const now = Date.now();\n const diff = d.getTime() - now;\n const abs = Math.abs(diff);\n if (abs < 60000) return diff > 0 ? \"in <1m\" : \"<1m ago\";\n if (abs < 3600000) { const m = Math.round(abs / 60000); return diff > 0 ? `in ${m}m` : `${m}m ago`; }\n if (abs < 86400000) { const h = Math.round(abs / 3600000); return diff > 0 ? `in ${h}h` : `${h}h ago`; }\n return d.toLocaleString();\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 } = useTranslation();\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 ? \"Job enabled\" : \"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 size=\"small\" onClick={refreshJobs} title=\"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\">Select a cron job to view details</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 title={selectedJob.enabled ? \"Pause job\" : \"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 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=\"Schedule\" value={selectedJob.schedule} mono/>\n <StatCard label=\"Last Run\" value={formatRelative(selectedJob.lastRunAt)}/>\n <StatCard label=\"Next Run\" value={selectedJob.enabled ? formatRelative(selectedJob.nextRunAt) : \"Paused\"}/>\n <StatCard label=\"Duration\" value={selectedJob.lastDurationMs !== undefined ? formatDuration(selectedJob.lastDurationMs) : \"—\"}/>\n </div>\n <div className=\"grid grid-cols-3 gap-3 mt-3\">\n <StatCard label=\"Status\" value={selectedJob.state.toUpperCase()} chipColor={selectedJob.state === \"error\" ? \"red\" : selectedJob.state === \"disabled\" ? \"gray\" : \"green\"}/>\n <StatCard label=\"Total Runs\" value={String(selectedJob.totalRuns)}/>\n <StatCard label=\"Failures\" value={String(selectedJob.totalFailures)} highlight={selectedJob.totalFailures > 0}/>\n </div>\n {selectedJob.lastError && (\n <div className=\"mt-3 p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800/50\">\n <div className=\"flex items-center gap-2 mb-1\">\n <AlertCircleIcon size={iconSize.smallest} className=\"text-red-500\"/>\n <Typography variant=\"caption\" className=\"font-semibold text-red-700 dark:text-red-400\">Last Error</Typography>\n </div>\n <Typography variant=\"caption\" className=\"font-mono text-red-600 dark:text-red-300 text-[11px] break-all\">{selectedJob.lastError}</Typography>\n </div>\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]\">Execution History</Typography>\n </div>\n <IconButton size=\"small\" onClick={() => refreshLogs(selectedJob.id)} title=\"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\">No executions yet</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\" className=\"text-[10px] uppercase tracking-wider font-medium\">{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 [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\" className=\"bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300\">manual</Chip>}\n <Typography variant=\"caption\" className=\"font-mono text-[11px]\">{formatDuration(log.durationMs)}</Typography>\n <svg className={cls(\"w-3 h-3 transition-transform text-surface-400\", expanded && \"rotate-180\")} fill=\"currentColor\" viewBox=\"0 0 20 20\"><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\"/></svg>\n </div>\n </div>\n {expanded && (\n <div className=\"mt-2 ml-6 space-y-2\">\n {log.error && (\n <div className=\"p-2 rounded bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800/50\">\n <Typography variant=\"caption\" className=\"font-mono text-[11px] text-red-600 dark:text-red-300 break-all\">{log.error}</Typography>\n </div>\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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"}