@rebasepro/studio 0.24.0 → 0.24.1-canary.gcb918fc

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 (48) hide show
  1. package/dist/{ApiExplorer-CCtczj3F.js → ApiExplorer-Bzt7Ye49.js} +5 -3
  2. package/dist/ApiExplorer-Bzt7Ye49.js.map +1 -0
  3. package/dist/{ApiKeysView-B66TdzhH.js → ApiKeysView-Bqf6XYR1.js} +2 -1
  4. package/dist/{ApiKeysView-B66TdzhH.js.map → ApiKeysView-Bqf6XYR1.js.map} +1 -1
  5. package/dist/{BackupsView-DmSMzAbT.js → BackupsView-BhOHB6iP.js} +6 -20
  6. package/dist/BackupsView-BhOHB6iP.js.map +1 -0
  7. package/dist/{BranchesView-DP1a87E-.js → BranchesView-BHfwGTYc.js} +5 -3
  8. package/dist/BranchesView-BHfwGTYc.js.map +1 -0
  9. package/dist/{CronJobsView-TDErHTuK.js → CronJobsView-CMNK7eWO.js} +52 -59
  10. package/dist/CronJobsView-CMNK7eWO.js.map +1 -0
  11. package/dist/{JSEditor-B59pwg4c.js → JSEditor-DIO2Mw8q.js} +115 -96
  12. package/dist/JSEditor-DIO2Mw8q.js.map +1 -0
  13. package/dist/{LogsExplorer-BKM4yfP8.js → LogsExplorer-C-bwDFIl.js} +19 -23
  14. package/dist/LogsExplorer-C-bwDFIl.js.map +1 -0
  15. package/dist/{RLSEditor-BlLLV6xs.js → RLSEditor-jFD_d06f.js} +10 -8
  16. package/dist/RLSEditor-jFD_d06f.js.map +1 -0
  17. package/dist/{SQLEditor-1An7w0j6.js → SQLEditor-D5Ci4pyW.js} +372 -545
  18. package/dist/SQLEditor-D5Ci4pyW.js.map +1 -0
  19. package/dist/{SchemaVisualizer-DCLVfA5t.js → SchemaVisualizer-D-rAqwul.js} +479 -206
  20. package/dist/SchemaVisualizer-D-rAqwul.js.map +1 -0
  21. package/dist/{StorageView-CbsHuCBf.js → StorageView-CPkDKbsX.js} +498 -241
  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/SQLEditor/SQLEditor.d.ts +11 -0
  25. package/dist/components/SchemaVisualizer/dagre-layout.d.ts +32 -0
  26. package/dist/components/SchemaVisualizer/dagre-layout.worker.d.ts +13 -0
  27. package/dist/components/SchemaVisualizer/layout-runner.d.ts +23 -0
  28. package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +21 -10
  29. package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +38 -1
  30. package/dist/components/StorageView/useVisibleFileMetadata.d.ts +46 -0
  31. package/dist/dagre-layout.worker-j9NbH9Gb.js +27 -0
  32. package/dist/dagre-layout.worker-j9NbH9Gb.js.map +1 -0
  33. package/dist/index.es.js +16 -14
  34. package/dist/index.es.js.map +1 -1
  35. package/dist/sql_utils-BZD31xE3.js +478 -0
  36. package/dist/sql_utils-BZD31xE3.js.map +1 -0
  37. package/dist/utils/sql_utils.d.ts +60 -0
  38. package/package.json +9 -9
  39. package/dist/ApiExplorer-CCtczj3F.js.map +0 -1
  40. package/dist/BackupsView-DmSMzAbT.js.map +0 -1
  41. package/dist/BranchesView-DP1a87E-.js.map +0 -1
  42. package/dist/CronJobsView-TDErHTuK.js.map +0 -1
  43. package/dist/JSEditor-B59pwg4c.js.map +0 -1
  44. package/dist/LogsExplorer-BKM4yfP8.js.map +0 -1
  45. package/dist/RLSEditor-BlLLV6xs.js.map +0 -1
  46. package/dist/SQLEditor-1An7w0j6.js.map +0 -1
  47. package/dist/SchemaVisualizer-DCLVfA5t.js.map +0 -1
  48. package/dist/StorageView-CbsHuCBf.js.map +0 -1
@@ -19,13 +19,6 @@ function formatDate(iso) {
19
19
  return d.toLocaleString();
20
20
  }
21
21
  /**
22
- * The name the roles sidecar is saved under: the dump's, with the suffix
23
- * `rebase db restore` looks for beside it.
24
- */
25
- function rolesFileName(dumpName) {
26
- return dumpName.replace(/\.dump$/, "") + ".globals.sql";
27
- }
28
- /**
29
22
  * How the scheduled backup is doing, above the list — and, apart from it, a run
30
23
  * someone started by hand since, which is not the schedule's.
31
24
  */
@@ -155,20 +148,12 @@ function BackupsView() {
155
148
  useEffect(() => {
156
149
  load();
157
150
  }, [load]);
158
- const handleDownload = async (key, fileName) => {
151
+ const handleDownload = async (key) => {
159
152
  const c = clientRef.current;
160
153
  if (!c?.backups) return;
161
154
  setDownloading(key);
162
155
  try {
163
- const blob = await c.backups.download(key);
164
- const url = URL.createObjectURL(blob);
165
- const a = document.createElement("a");
166
- a.href = url;
167
- a.download = fileName;
168
- document.body.appendChild(a);
169
- a.click();
170
- a.remove();
171
- URL.revokeObjectURL(url);
156
+ await c.backups.download(key);
172
157
  } catch (e) {
173
158
  snackbarRef.current.open({
174
159
  type: "error",
@@ -230,6 +215,7 @@ function BackupsView() {
230
215
  })
231
216
  ]
232
217
  }), /* @__PURE__ */ jsx(IconButton, {
218
+ "aria-label": t("refresh_data"),
233
219
  size: "small",
234
220
  onClick: load,
235
221
  title: t("refresh_data"),
@@ -332,7 +318,7 @@ function BackupsView() {
332
318
  children: /* @__PURE__ */ jsx(Button, {
333
319
  size: "small",
334
320
  variant: "text",
335
- onClick: () => handleDownload(globalsKey, rolesFileName(backup.name)),
321
+ onClick: () => handleDownload(globalsKey),
336
322
  disabled: downloading === globalsKey,
337
323
  startIcon: downloading === globalsKey ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : /* @__PURE__ */ jsx(DownloadIcon, { size: iconSize.smallest }),
338
324
  children: downloading === globalsKey ? t("studio_backups_downloading") : t("studio_backups_roles_file")
@@ -348,7 +334,7 @@ function BackupsView() {
348
334
  /* @__PURE__ */ jsx(Button, {
349
335
  size: "small",
350
336
  variant: "outlined",
351
- onClick: () => handleDownload(backup.key, backup.name),
337
+ onClick: () => handleDownload(backup.key),
352
338
  disabled: downloading === backup.key,
353
339
  startIcon: downloading === backup.key ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : /* @__PURE__ */ jsx(DownloadIcon, { size: iconSize.smallest }),
354
340
  children: downloading === backup.key ? t("studio_backups_downloading") : t("download")
@@ -365,4 +351,4 @@ function BackupsView() {
365
351
  //#endregion
366
352
  export { BackupsView };
367
353
 
368
- //# sourceMappingURL=BackupsView-DmSMzAbT.js.map
354
+ //# sourceMappingURL=BackupsView-BhOHB6iP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackupsView-BhOHB6iP.js","names":[],"sources":["../src/components/Backups/BackupsView.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n cls,\n DatabaseIcon,\n defaultBorderMixin,\n DownloadIcon,\n iconSize,\n IconButton,\n RefreshCwIcon,\n Tooltip,\n Typography\n} from \"@rebasepro/ui\";\nimport { useRebaseClient, useSnackbarController, useTranslation } from \"@rebasepro/app\";\nimport type { BackupInfo, BackupScheduleStatus, RebaseClient } from \"@rebasepro/types\";\n\nimport { classifyLoadFailure, type LoadFailure } from \"../load-failure\";\nimport { LoadFailureView } from \"../load-failure-view\";\n\nfunction formatSize(bytes: number | undefined): string {\n if (bytes === undefined || bytes === null) return \"—\";\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n\nfunction formatDate(iso: string | undefined): string {\n if (!iso) return \"—\";\n const d = new Date(iso);\n if (isNaN(d.getTime())) return \"—\";\n return d.toLocaleString();\n}\n\n/**\n * How the scheduled backup is doing, above the list — and, apart from it, a run\n * someone started by hand since, which is not the schedule's.\n */\nfunction BackupScheduleSummary({ schedule }: { schedule: BackupScheduleStatus }) {\n const { t } = useTranslation();\n const manual = schedule.lastManualRun;\n return (\n <div className=\"flex flex-col gap-2\">\n <ScheduledBackupStatus schedule={schedule}/>\n {manual && (manual.success ? (\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-[12px]\">\n {t(\"studio_backups_manual_run_ok\", { when: formatDate(manual.startedAt) })}\n </Typography>\n ) : (\n <Alert color=\"warning\">\n <Typography variant=\"body2\" className=\"text-[13px] font-semibold\">\n {t(\"studio_backups_manual_run_failed\", { when: formatDate(manual.startedAt) })}\n </Typography>\n {manual.error && (\n <Typography variant=\"caption\" className=\"font-mono text-[12px] whitespace-pre-wrap break-words\">\n {manual.error}\n </Typography>\n )}\n </Alert>\n ))}\n </div>\n );\n}\n\n/**\n * The scheduled backup's own state.\n *\n * A failed last run is an error, not a footnote: a backup that fails every\n * night used to leave this panel empty under \"wait for the next scheduled run\",\n * with the reason only in the cron history.\n */\nfunction ScheduledBackupStatus({ schedule }: { schedule: BackupScheduleStatus }) {\n const { t } = useTranslation();\n const last = schedule.lastRun;\n\n // Refused at boot: it never runs, and resuming it in Cron Jobs is not\n // possible — it is not a job there. Only its cron file can fix it.\n if (schedule.refused) {\n return (\n <Alert color=\"error\">\n <Typography variant=\"body2\" className=\"text-[13px] font-semibold\">\n {t(\"studio_backups_job_refused\", { job: schedule.jobId })}\n </Typography>\n <Typography variant=\"caption\" className=\"font-mono text-[12px] whitespace-pre-wrap break-words\">\n {schedule.schedule} — {schedule.refused}\n </Typography>\n </Alert>\n );\n }\n\n if (last && !last.success) {\n return (\n <Alert color=\"error\">\n <Typography variant=\"body2\" className=\"text-[13px] font-semibold\">\n {t(\"studio_backups_last_run_failed\", { when: formatDate(last.startedAt) })}\n </Typography>\n {last.error && (\n <Typography variant=\"caption\" className=\"font-mono text-[12px] whitespace-pre-wrap break-words\">\n {last.error}\n </Typography>\n )}\n </Alert>\n );\n }\n\n if (!schedule.enabled) {\n // Declared off is not paused: \"resume it in Cron Jobs\" would turn on\n // the placeholder the documented cron file exports while\n // BACKUP_SCHEDULE is unset.\n return (\n <Alert color=\"warning\">\n <Typography variant=\"body2\" className=\"text-[13px]\">\n {schedule.disabledInCode\n ? t(\"studio_backups_schedule_off_in_code\", { job: schedule.jobId })\n : t(\"studio_backups_schedule_paused\", { job: schedule.jobId })}\n </Typography>\n </Alert>\n );\n }\n\n // Unknown is not \"has not run yet\": that line would be read off an empty\n // history the server could not fetch.\n if (schedule.historyError) {\n return (\n <Alert color=\"warning\">\n <Typography variant=\"body2\" className=\"text-[13px]\">\n {t(\"studio_backups_history_unreadable\")}\n </Typography>\n </Alert>\n );\n }\n\n const parts = [\n last ? t(\"studio_backups_last_run_ok\", { when: formatDate(last.startedAt) }) : t(\"studio_backups_never_ran\"),\n schedule.nextRunAt ? t(\"studio_backups_next_run\", { when: formatDate(schedule.nextRunAt) }) : null\n ].filter((part): part is string => Boolean(part));\n\n return (\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-[12px]\">\n {parts.join(\" · \")}\n </Typography>\n );\n}\n\nexport function BackupsView() {\n const client = useRebaseClient<RebaseClient>();\n const snackbar = useSnackbarController();\n const { t } = useTranslation();\n\n const [backups, setBackups] = useState<BackupInfo[]>([]);\n const [destinationKind, setDestinationKind] = useState<string>(\"local\");\n const [configured, setConfigured] = useState(true);\n const [localDiskOfThisProcess, setLocalDiskOfThisProcess] = useState(false);\n const [schedule, setSchedule] = useState<BackupScheduleStatus | null>(null);\n const [loading, setLoading] = useState(true);\n const [downloading, setDownloading] = useState<string | null>(null);\n /** Why the listing failed, classified — see `load-failure.ts`. */\n const [failure, setFailure] = useState<LoadFailure | null>(null);\n\n const clientRef = useRef(client);\n clientRef.current = client;\n const snackbarRef = useRef(snackbar);\n snackbarRef.current = snackbar;\n\n const load = useCallback(async () => {\n const c = clientRef.current;\n if (!c?.backups) {\n setLoading(false);\n setConfigured(false);\n return;\n }\n setFailure(null);\n try {\n const res = await c.backups.list();\n setBackups(res.backups);\n setDestinationKind(res.destinationKind);\n setConfigured(res.configured);\n setLocalDiskOfThisProcess(res.localDiskOfThisProcess === true);\n // Absent from a server that predates the field.\n setSchedule(res.schedule ?? null);\n } catch (e: unknown) {\n // The snackbar is gone in seconds, and the list underneath it is\n // empty — which is how a refused listing came to read \"No backups\n // found yet\" about a database that has backups. Keep the reason on\n // screen instead.\n setFailure(classifyLoadFailure(e));\n } finally {\n setLoading(false);\n }\n }, []);\n\n useEffect(() => {\n load();\n }, [load]);\n\n const handleDownload = async (key: string) => {\n const c = clientRef.current;\n if (!c?.backups) return;\n setDownloading(key);\n try {\n // The browser is sent to a 60-second link and streams the file to\n // disk under its own name — the `.globals.sql` beside its dump is\n // the name `rebase db restore` looks for. Fetched into a Blob here,\n // the whole database sat in the tab's memory.\n await c.backups.download(key);\n } catch (e: unknown) {\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n } finally {\n setDownloading(null);\n }\n };\n\n // Backups API not available on this client (e.g. non-Postgres backend).\n if (!client?.backups) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-4 text-center p-8\">\n <DatabaseIcon size={iconSize.large} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"h6\" color=\"secondary\">{t(\"studio_backups_unavailable_title\")}</Typography>\n <Typography variant=\"body2\" color=\"disabled\" className=\"max-w-md\">\n {t(\"studio_backups_unavailable_body\")}\n </Typography>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <CircularProgress/>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full w-full overflow-hidden bg-surface-card\">\n {/* Header */}\n <div className={cls(\"flex items-center justify-between px-5 py-2.5 border-b bg-surface-sheet min-h-[48px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-2\">\n <DatabaseIcon size={iconSize.small} className=\"text-primary\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold\">{t(\"studio_tool_backups\")}</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-raised text-surface-600 dark:text-surface-300\">{backups.length}</Chip>\n <Chip size=\"smallest\" className=\"bg-surface-raised text-surface-500 dark:text-surface-400 uppercase font-mono text-[10px]\">{destinationKind}</Chip>\n </div>\n <IconButton aria-label={t(\"refresh_data\")} size=\"small\" onClick={load} title={t(\"refresh_data\")}>\n <RefreshCwIcon size={iconSize.smallest}/>\n </IconButton>\n </div>\n\n <div className=\"flex-1 overflow-y-auto p-5\">\n {failure ? (\n <LoadFailureView\n failure={failure}\n title={t(\"studio_backups_read_failed\")}\n deniedTitle={t(\"studio_backups_denied_title\")}\n deniedHint={t(\"studio_backups_denied_hint\")}\n onRetry={load}\n />\n ) : (\n <div className=\"flex flex-col gap-4 min-h-full\">\n {schedule && (\n <div className=\"max-w-3xl\">\n <BackupScheduleSummary schedule={schedule}/>\n </div>\n )}\n {/* The api process of a split deployment lists its own disk; the worker writes elsewhere. */}\n {configured && localDiskOfThisProcess && (\n <div className=\"max-w-3xl\">\n <Alert color=\"info\">\n <Typography variant=\"body2\" className=\"text-[13px]\">\n {t(\"studio_backups_local_disk_of_this_process\")}\n </Typography>\n </Alert>\n </div>\n )}\n {!configured ? (\n <Alert color=\"info\">\n <Typography variant=\"body2\" className=\"text-[13px]\">\n <strong>{t(\"studio_backups_not_configured_title\")}</strong>{\" \"}\n {t(\"studio_backups_not_configured_body\")}{\" \"}\n <a\n href=\"https://rebase.pro/docs/backend/jobs\"\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-primary underline\"\n >\n {t(\"studio_read_the_docs\")}\n </a>\n </Typography>\n </Alert>\n ) : backups.length === 0 ? (\n <div className=\"flex flex-1 flex-col items-center justify-center gap-3 text-center\">\n <DatabaseIcon size={iconSize.large} className=\"text-surface-200 dark:text-surface-700\"/>\n <Typography variant=\"body2\" color=\"disabled\">\n {t(\"studio_backups_empty\")}\n </Typography>\n {/* With a schedule, the summary above already says when the next one runs, or why the last one did not. */}\n {!schedule && (\n <Typography variant=\"caption\" color=\"disabled\" className=\"max-w-md\">\n {t(\"studio_backups_empty_hint\")}\n </Typography>\n )}\n </div>\n ) : (\n <div className=\"space-y-2 max-w-3xl\">\n {backups.map(backup => {\n // The roles sidecar travels with the dump: a restore\n // into a new Postgres needs both files.\n const globalsKey = backup.globalsKey;\n return (\n <div\n key={backup.key}\n className={cls(\"flex items-center gap-3 px-4 py-3 rounded-lg border bg-surface-card\", defaultBorderMixin)}\n >\n <DatabaseIcon size={iconSize.small} className=\"text-surface-400 shrink-0\"/>\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"body2\" className=\"truncate font-medium font-mono text-[12px]\">{backup.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-[11px]\">\n {formatDate(backup.createdAt)} · {formatSize(backup.sizeBytes)}\n </Typography>\n </div>\n {globalsKey ? (\n <Tooltip title={t(\"studio_backups_roles_file_hint\")}>\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={() => handleDownload(globalsKey)}\n disabled={downloading === globalsKey}\n startIcon={downloading === globalsKey\n ? <CircularProgress size=\"smallest\"/>\n : <DownloadIcon size={iconSize.smallest}/>}\n >\n {downloading === globalsKey ? t(\"studio_backups_downloading\") : t(\"studio_backups_roles_file\")}\n </Button>\n </Tooltip>\n ) : (\n <Tooltip title={t(\"studio_backups_no_roles_file_hint\")}>\n <Chip size=\"smallest\" colorScheme=\"orangeDarker\">{t(\"studio_backups_no_roles_file\")}</Chip>\n </Tooltip>\n )}\n <Button\n size=\"small\"\n variant=\"outlined\"\n onClick={() => handleDownload(backup.key)}\n disabled={downloading === backup.key}\n startIcon={downloading === backup.key\n ? <CircularProgress size=\"smallest\"/>\n : <DownloadIcon size={iconSize.smallest}/>}\n >\n {downloading === backup.key ? t(\"studio_backups_downloading\") : t(\"download\")}\n </Button>\n </div>\n );\n })}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -186,14 +186,16 @@ function BranchesView() {
186
186
  }), /* @__PURE__ */ jsxs("div", {
187
187
  className: "flex items-center gap-1",
188
188
  children: [/* @__PURE__ */ jsx(IconButton, {
189
+ "aria-label": t("refresh"),
189
190
  size: "small",
190
191
  onClick: loadBranches,
191
- title: "Refresh",
192
+ title: t("refresh"),
192
193
  children: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest })
193
194
  }), /* @__PURE__ */ jsx(IconButton, {
195
+ "aria-label": t("create_branch"),
194
196
  size: "small",
195
197
  onClick: () => setCreateOpen(true),
196
- title: "Create branch",
198
+ title: t("create_branch"),
197
199
  className: "text-primary",
198
200
  children: /* @__PURE__ */ jsx(PlusIcon, { size: iconSize.smallest })
199
201
  })]
@@ -519,4 +521,4 @@ function StatCard({ label, value, mono }) {
519
521
  //#endregion
520
522
  export { BranchesView };
521
523
 
522
- //# sourceMappingURL=BranchesView-DP1a87E-.js.map
524
+ //# sourceMappingURL=BranchesView-BHfwGTYc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BranchesView-BHfwGTYc.js","names":[],"sources":["../src/components/Branches/BranchesView.tsx"],"sourcesContent":["\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n cls,\n CopyIcon,\n defaultBorderMixin,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n GitBranchIcon,\n IconButton,\n iconSize,\n Paper,\n PlusIcon,\n RefreshCwIcon,\n Select,\n SelectItem,\n TextField,\n Trash2Icon,\n Typography\n} from \"@rebasepro/ui\";\nimport { useRebaseContext, useSnackbarController, ConfirmationDialog, useTranslation } from \"@rebasepro/app\";\nimport { isBranchAdmin } from \"@rebasepro/types\";\nimport type { BranchInfo } from \"@rebasepro/types\";\nimport { formatRelativeTime } from \"@rebasepro/utils\";\n\nimport { classifyLoadFailure, type LoadFailure } from \"../load-failure\";\n\n/**\n * The prefix the driver puts on every branch database.\n *\n * Stated here rather than derived from the row, because `BranchInfo` carries\n * the *name* a person typed and not the database it became — and the detail\n * pane used to print the name under \"Database:\", which is a connection string\n * to nothing. Kept in step with `BranchService.BRANCH_DB_PREFIX` and the CLI's\n * `branch-pointer.ts`, which say the same thing for the same reason.\n */\nconst BRANCH_DB_PREFIX = \"rb_\";\nimport { LoadFailureView } from \"../load-failure-view\";\n\nfunction formatSize(bytes: number | undefined): string {\n if (bytes === undefined || bytes === null) return \"—\";\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n\nfunction formatRelative(date: Date | string | undefined): string {\n if (!date) return \"—\";\n const d = date instanceof Date ? date : new Date(date);\n if (isNaN(d.getTime())) return \"—\";\n return formatRelativeTime(d) ?? d.toLocaleDateString();\n}\n\nexport function BranchesView() {\n const { databaseAdmin } = useRebaseContext();\n const snackbar = useSnackbarController();\n const { t } = useTranslation();\n\n const [branches, setBranches] = useState<BranchInfo[]>([]);\n const [loading, setLoading] = useState(true);\n const [selectedBranch, setSelectedBranch] = useState<string | null>(null);\n\n // Create dialog\n const [createOpen, setCreateOpen] = useState(false);\n const [newBranchName, setNewBranchName] = useState(\"\");\n /**\n * The database to copy, when it is not the main one: a branch's own\n * database, which is not its name.\n */\n const [sourceDatabase, setSourceDatabase] = useState<string | undefined>(undefined);\n const [creating, setCreating] = useState(false);\n /**\n * Why the last create attempt was refused, kept on screen.\n *\n * The server refuses branching outright on the managed development\n * database — PGlite serves one database, so the \"branch\" would be the\n * parent. That refusal is the whole answer to what the reader just tried to\n * do, and a snackbar is gone in four seconds; the same argument\n * `load-failure.ts` makes for listings.\n */\n const [createError, setCreateError] = useState<string | null>(null);\n\n // Delete confirm\n const [deleteTarget, setDeleteTarget] = useState<string | null>(null);\n const [deleting, setDeleting] = useState(false);\n\n /** Why the branch listing failed, classified — see `load-failure.ts`. */\n const [failure, setFailure] = useState<LoadFailure | null>(null);\n\n // Refs\n const snackbarRef = useRef(snackbar);\n snackbarRef.current = snackbar;\n\n const branchAdmin = isBranchAdmin(databaseAdmin) ? databaseAdmin : undefined;\n\n const loadBranches = useCallback(async () => {\n if (!branchAdmin) {\n setLoading(false);\n return;\n }\n try {\n const result = await branchAdmin.listBranches();\n setBranches(result);\n setFailure(null);\n } catch (e: unknown) {\n // \"No branches yet. Create one…\" over a refused listing invites the\n // reader to create a branch they are not allowed to see, and then\n // hides the one they already have.\n setFailure(classifyLoadFailure(e));\n } finally {\n setLoading(false);\n }\n }, [branchAdmin]);\n\n useEffect(() => {\n loadBranches();\n }, [loadBranches]);\n\n const handleCreate = async () => {\n if (!branchAdmin || !newBranchName.trim()) return;\n setCreating(true);\n setCreateError(null);\n try {\n await branchAdmin.createBranch(newBranchName.trim(), sourceDatabase ? { source: sourceDatabase } : undefined);\n snackbarRef.current.open({\n type: \"success\",\n message: `Branch \"${newBranchName.trim()}\" created successfully`\n });\n setCreateOpen(false);\n setNewBranchName(\"\");\n setSourceDatabase(undefined);\n await loadBranches();\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : String(e);\n setCreateError(message);\n snackbarRef.current.open({ type: \"error\",\n message });\n } finally {\n setCreating(false);\n }\n };\n\n const handleDelete = async () => {\n if (!branchAdmin || !deleteTarget) return;\n setDeleting(true);\n try {\n await branchAdmin.deleteBranch(deleteTarget);\n snackbarRef.current.open({\n type: \"success\",\n message: `Branch \"${deleteTarget}\" deleted`\n });\n if (selectedBranch === deleteTarget) setSelectedBranch(null);\n setDeleteTarget(null);\n await loadBranches();\n } catch (e: unknown) {\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n } finally {\n setDeleting(false);\n }\n };\n\n // Not supported\n if (!branchAdmin) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-4 text-center p-8\">\n <GitBranchIcon size={iconSize.large} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"h6\" color=\"secondary\">{t(\"studio_branches_unavailable_title\")}</Typography>\n <Typography variant=\"body2\" color=\"disabled\" className=\"max-w-md\">\n {t(\"studio_branches_unavailable_body\")}\n </Typography>\n <a\n href=\"https://rebase.pro/docs/backend/branching\"\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\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <CircularProgress/>\n </div>\n );\n }\n\n const selected = branches.find(b => b.name === selectedBranch);\n\n return (\n <div className=\"flex h-full w-full overflow-hidden bg-surface-card\">\n {/* ── Branch 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 <GitBranchIcon size={iconSize.small} className=\"text-primary\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold\">{t(\"studio_tool_branches\")}</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-raised text-surface-600 dark:text-surface-300\">{branches.length}</Chip>\n </div>\n <div className=\"flex items-center gap-1\">\n <IconButton aria-label={t(\"refresh\")} size=\"small\" onClick={loadBranches} title={t(\"refresh\")}>\n <RefreshCwIcon size={iconSize.smallest}/>\n </IconButton>\n <IconButton aria-label={t(\"create_branch\")} size=\"small\" onClick={() => setCreateOpen(true)} title={t(\"create_branch\")} className=\"text-primary\">\n <PlusIcon size={iconSize.smallest}/>\n </IconButton>\n </div>\n </div>\n <div className=\"flex-1 overflow-y-auto p-2 space-y-1\">\n {failure ? (\n <LoadFailureView\n failure={failure}\n title={t(\"studio_branches_read_failed\")}\n deniedTitle={t(\"studio_branches_denied_title\")}\n deniedHint={t(\"studio_branches_denied_hint\")}\n onRetry={loadBranches}\n />\n ) : branches.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-center px-4\">\n <CopyIcon size={iconSize.small} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"body2\" color=\"disabled\" className=\"text-[13px]\">\n No branches yet. Create one to start working with an isolated database copy.\n </Typography>\n <Button\n size=\"small\"\n variant=\"outlined\"\n onClick={() => setCreateOpen(true)}\n startIcon={<PlusIcon size={iconSize.smallest}/>}\n >\n Create Branch\n </Button>\n </div>\n ) : (\n branches.map(branch => (\n <div\n key={branch.name}\n onClick={() => setSelectedBranch(branch.name)}\n className={cls(\n \"flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all\",\n selectedBranch === branch.name\n ? \"bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30\"\n : \"hover:bg-surface-hover\"\n )}\n >\n <div className=\"w-2 h-2 rounded-full shrink-0 bg-emerald-500\"/>\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"body2\" className=\"truncate font-medium text-[13px]\">{branch.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate text-[11px]\">\n from {branch.parentDatabase} · {formatRelative(branch.createdAt)}\n </Typography>\n </div>\n {branch.sizeBytes !== undefined && (\n <Typography variant=\"caption\" color=\"disabled\" className=\"font-mono text-[10px] shrink-0\">\n {formatSize(branch.sizeBytes)}\n </Typography>\n )}\n </div>\n ))\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 {!selected ? (\n <div className=\"flex flex-col items-center justify-center h-full gap-3\">\n <GitBranchIcon size={iconSize.large} className=\"text-surface-200 dark:text-surface-700\"/>\n <Typography variant=\"body2\" color=\"disabled\">\n {branches.length === 0 ? \"Create a branch to get started\" : \"Select a branch to view details\"}\n </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=\"w-2.5 h-2.5 rounded-full bg-emerald-500\"/>\n <div className=\"min-w-0\">\n <Typography variant=\"subtitle1\" className=\"font-semibold truncate\">{selected.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate\">\n Created from <span className=\"font-mono text-[11px]\">{selected.parentDatabase}</span>\n </Typography>\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <Button\n size=\"small\"\n color=\"error\"\n variant=\"outlined\"\n onClick={() => setDeleteTarget(selected.name)}\n startIcon={<Trash2Icon size={iconSize.smallest}/>}\n >\n Delete\n </Button>\n </div>\n </div>\n\n {/* Info 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=\"Branch Name\" value={selected.name} mono/>\n <StatCard label=\"Source Database\" value={selected.parentDatabase} mono/>\n <StatCard label=\"Created\" value={formatRelative(selected.createdAt)}/>\n <StatCard label=\"Size\" value={formatSize(selected.sizeBytes)}/>\n </div>\n </div>\n\n {/* Usage Info */}\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n <Alert color=\"info\">\n <Typography variant=\"body2\" className=\"text-[13px]\">\n <strong>How to use this branch:</strong> point this checkout at it with\n <code className=\"mx-1 px-1.5 py-0.5 rounded bg-surface-well font-mono text-[12px]\">rebase db branch switch {selected.name}</code>\n — every later command uses it, and\n <code className=\"mx-1 px-1.5 py-0.5 rounded bg-surface-well font-mono text-[12px]\">rebase db branch switch --off</code>\n goes back to the main database. Changes made here don&apos;t affect it.\n </Typography>\n </Alert>\n <div className=\"mt-4 p-4 rounded-lg border bg-surface-sheet border-hairline-strong\">\n <Typography variant=\"caption\" className=\"text-[10px] uppercase tracking-wider text-surface-400 mb-2 block font-medium\">Connection Details</Typography>\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n {/* The *database*, not the branch name. They are not the\n same string — the driver prefixes every branch database\n with `rb_` — and a connection string built from the name\n on this pane pointed at a database that does not\n exist. */}\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Database:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px]\">{`${BRANCH_DB_PREFIX}${selected.name}`}</Typography>\n </div>\n <div className=\"flex items-start gap-2\">\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Connect with:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px] break-all\">\n {`DATABASE_URL=…/${BRANCH_DB_PREFIX}${selected.name}`}\n </Typography>\n </div>\n <div className=\"flex items-center gap-2\">\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Branched from:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px]\">{selected.parentDatabase}</Typography>\n </div>\n <div className=\"flex items-center gap-2\">\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Created at:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px]\">\n {selected.createdAt instanceof Date\n ? selected.createdAt.toLocaleString()\n : new Date(selected.createdAt).toLocaleString()}\n </Typography>\n </div>\n </div>\n </div>\n </div>\n </>\n )}\n </div>\n\n {/* ── Create Dialog ── */}\n <Dialog open={createOpen} onOpenChange={(open) => {\n setCreateOpen(open);\n if (!open) setCreateError(null);\n }}>\n <DialogTitle>Create New Branch</DialogTitle>\n <DialogContent className=\"space-y-4 min-w-[400px]\">\n <Typography variant=\"body2\" color=\"secondary\" className=\"text-[13px]\">\n Create an isolated database copy. The branch will be a full clone of the source database at this point in time.\n </Typography>\n {createError && (\n <Alert color=\"error\">\n <Typography variant=\"body2\" className=\"text-[13px]\">{createError}</Typography>\n </Alert>\n )}\n <div>\n <TextField\n label=\"Branch Name\"\n value={newBranchName}\n onChange={(e) => setNewBranchName(e.target.value)}\n placeholder=\"e.g. feature-auth, staging, preview-pr-42\"\n size=\"small\"\n autoFocus\n />\n </div>\n {branches.length > 0 && (\n <div>\n <Select\n label=\"Source Database\"\n value={sourceDatabase ?? \"__main__\"}\n onValueChange={(v) => setSourceDatabase(v === \"__main__\" ? undefined : v)}\n placeholder=\"Default (main database)\"\n size=\"small\"\n >\n <SelectItem value=\"__main__\">Default (main database)</SelectItem>\n {branches.map(b => (\n <SelectItem key={b.name} value={b.database}>{b.name}</SelectItem>\n ))}\n </Select>\n </div>\n )}\n </DialogContent>\n <DialogActions>\n <Button variant=\"text\" onClick={() => setCreateOpen(false)} disabled={creating}>\n Cancel\n </Button>\n <Button\n color=\"primary\"\n onClick={handleCreate}\n disabled={!newBranchName.trim() || creating}\n startIcon={creating ? <CircularProgress size=\"smallest\"/> : <PlusIcon size={iconSize.smallest}/>}\n >\n {creating ? \"Creating...\" : \"Create Branch\"}\n </Button>\n </DialogActions>\n </Dialog>\n\n {/* ── Delete Confirm ── */}\n <ConfirmationDialog\n open={!!deleteTarget}\n onAccept={handleDelete}\n onCancel={() => setDeleteTarget(null)}\n title=\"Delete Branch\"\n body={\n <Typography variant=\"body2\">\n Are you sure you want to permanently delete the branch <strong>{deleteTarget}</strong>?\n This action cannot be undone and all data in this branch will be lost.\n </Typography>\n }\n loading={deleting}\n />\n </div>\n );\n}\n\nfunction StatCard({ label, value, mono }: {\n label: string; value: string; mono?: 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] truncate\",\n mono && \"font-mono\"\n )}>{value}</Typography>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ 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 { Alert, 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,18 @@ 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");
29
25
  }
30
26
  function problemsOf(listing) {
31
27
  const rejected = listing.rejected ?? [];
@@ -80,7 +76,8 @@ var stateColors = {
80
76
  function CronJobsView() {
81
77
  const client = useRebaseClient();
82
78
  const snackbar = useSnackbarController();
83
- const { t } = useTranslation();
79
+ const { t, i18n } = useTranslation();
80
+ const language = i18n?.language ?? "en";
84
81
  const [jobs, setJobs] = useState([]);
85
82
  const [problems, setProblems] = useState({
86
83
  rejected: [],
@@ -230,7 +227,7 @@ function CronJobsView() {
230
227
  await c.cron.toggleJob(id, enabled);
231
228
  snackbarRef.current.open({
232
229
  type: "success",
233
- message: enabled ? "Job enabled" : "Job paused"
230
+ message: enabled ? t("studio_cron_job_enabled") : t("studio_cron_job_paused")
234
231
  });
235
232
  await refreshJobs();
236
233
  } catch (e) {
@@ -308,9 +305,10 @@ function CronJobsView() {
308
305
  })
309
306
  ]
310
307
  }), /* @__PURE__ */ jsx(IconButton, {
308
+ "aria-label": t("studio_cron_refresh"),
311
309
  size: "small",
312
310
  onClick: refreshJobs,
313
- title: "Refresh",
311
+ title: t("studio_cron_refresh"),
314
312
  children: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest })
315
313
  })]
316
314
  }),
@@ -350,7 +348,7 @@ function CronJobsView() {
350
348
  children: /* @__PURE__ */ jsx(Typography, {
351
349
  variant: "body2",
352
350
  color: "disabled",
353
- children: "Select a cron job to view details"
351
+ children: t("studio_cron_select_job")
354
352
  })
355
353
  }) : /* @__PURE__ */ jsxs(Fragment, { children: [
356
354
  /* @__PURE__ */ jsxs("div", {
@@ -373,7 +371,8 @@ function CronJobsView() {
373
371
  }), /* @__PURE__ */ jsxs("div", {
374
372
  className: "flex items-center gap-2 shrink-0",
375
373
  children: [/* @__PURE__ */ jsx(IconButton, {
376
- 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"),
377
376
  size: "small",
378
377
  onClick: () => handleToggle(selectedJob.id, !selectedJob.enabled),
379
378
  children: selectedJob.enabled ? /* @__PURE__ */ jsx(PauseIcon, { size: iconSize.small }) : /* @__PURE__ */ jsx(PlayIcon, { size: iconSize.smallest })
@@ -383,7 +382,7 @@ function CronJobsView() {
383
382
  onClick: () => handleTrigger(selectedJob.id),
384
383
  disabled: triggering === selectedJob.id,
385
384
  startIcon: triggering === selectedJob.id ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : /* @__PURE__ */ jsx(PlayIcon, { size: iconSize.smallest }),
386
- children: "Run Now"
385
+ children: t("studio_cron_run_now")
387
386
  })]
388
387
  })]
389
388
  }),
@@ -394,20 +393,20 @@ function CronJobsView() {
394
393
  className: "grid grid-cols-2 md:grid-cols-4 gap-3",
395
394
  children: [
396
395
  /* @__PURE__ */ jsx(StatCard, {
397
- label: "Schedule",
396
+ label: t("studio_cron_schedule"),
398
397
  value: selectedJob.schedule,
399
398
  mono: true
400
399
  }),
401
400
  /* @__PURE__ */ jsx(StatCard, {
402
- label: "Last Run",
403
- value: formatRelative(selectedJob.lastRunAt)
401
+ label: t("studio_cron_last_run"),
402
+ value: formatRelative(selectedJob.lastRunAt, language)
404
403
  }),
405
404
  /* @__PURE__ */ jsx(StatCard, {
406
- label: "Next Run",
407
- 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")
408
407
  }),
409
408
  /* @__PURE__ */ jsx(StatCard, {
410
- label: "Duration",
409
+ label: t("studio_cron_duration"),
411
410
  value: selectedJob.lastDurationMs !== void 0 ? formatDuration(selectedJob.lastDurationMs) : "—"
412
411
  })
413
412
  ]
@@ -416,36 +415,31 @@ function CronJobsView() {
416
415
  className: "grid grid-cols-3 gap-3 mt-3",
417
416
  children: [
418
417
  /* @__PURE__ */ jsx(StatCard, {
419
- label: "Status",
418
+ label: t("studio_cron_status"),
420
419
  value: selectedJob.state.toUpperCase(),
421
420
  chipColor: selectedJob.state === "error" ? "red" : selectedJob.state === "disabled" ? "gray" : "green"
422
421
  }),
423
422
  /* @__PURE__ */ jsx(StatCard, {
424
- label: "Total Runs",
423
+ label: t("studio_cron_total_runs"),
425
424
  value: String(selectedJob.totalRuns)
426
425
  }),
427
426
  /* @__PURE__ */ jsx(StatCard, {
428
- label: "Failures",
427
+ label: t("studio_cron_failures"),
429
428
  value: String(selectedJob.totalFailures),
430
429
  highlight: selectedJob.totalFailures > 0
431
430
  })
432
431
  ]
433
432
  }),
434
- selectedJob.lastError && /* @__PURE__ */ jsxs("div", {
435
- className: "mt-3 p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800/50",
436
- children: [/* @__PURE__ */ jsxs("div", {
437
- className: "flex items-center gap-2 mb-1",
438
- children: [/* @__PURE__ */ jsx(AlertCircleIcon, {
439
- size: iconSize.smallest,
440
- className: "text-red-500"
441
- }), /* @__PURE__ */ jsx(Typography, {
442
- variant: "caption",
443
- className: "font-semibold text-red-700 dark:text-red-400",
444
- children: "Last Error"
445
- })]
446
- }), /* @__PURE__ */ jsx(Typography, {
433
+ selectedJob.lastError && /* @__PURE__ */ jsxs(Alert, {
434
+ color: "error",
435
+ outerClassName: "mt-3",
436
+ children: [/* @__PURE__ */ jsx(Typography, {
447
437
  variant: "caption",
448
- 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",
449
443
  children: selectedJob.lastError
450
444
  })]
451
445
  })
@@ -461,12 +455,13 @@ function CronJobsView() {
461
455
  }), /* @__PURE__ */ jsx(Typography, {
462
456
  variant: "subtitle2",
463
457
  className: "font-semibold text-[13px]",
464
- children: "Execution History"
458
+ children: t("studio_cron_execution_history")
465
459
  })]
466
460
  }), /* @__PURE__ */ jsx(IconButton, {
461
+ "aria-label": t("studio_cron_refresh_logs"),
467
462
  size: "small",
468
463
  onClick: () => refreshLogs(selectedJob.id),
469
- title: "Refresh logs",
464
+ title: t("studio_cron_refresh_logs"),
470
465
  children: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest })
471
466
  })]
472
467
  }),
@@ -486,7 +481,7 @@ function CronJobsView() {
486
481
  children: /* @__PURE__ */ jsx(Typography, {
487
482
  variant: "body2",
488
483
  color: "disabled",
489
- children: "No executions yet"
484
+ children: t("studio_cron_no_executions")
490
485
  })
491
486
  }) : /* @__PURE__ */ jsx("div", {
492
487
  className: "divide-y divide-surface-100 dark:divide-surface-950",
@@ -503,7 +498,6 @@ function StatCard({ label, value, mono, chipColor, highlight }) {
503
498
  children: [/* @__PURE__ */ jsx(Typography, {
504
499
  variant: "caption",
505
500
  color: "secondary",
506
- className: "text-[10px] uppercase tracking-wider font-medium",
507
501
  children: label
508
502
  }), /* @__PURE__ */ jsx(Typography, {
509
503
  variant: "body2",
@@ -513,6 +507,7 @@ function StatCard({ label, value, mono, chipColor, highlight }) {
513
507
  });
514
508
  }
515
509
  function LogRow({ log }) {
510
+ const { t } = useTranslation();
516
511
  const [expanded, setExpanded] = useState(false);
517
512
  return /* @__PURE__ */ jsxs("div", {
518
513
  className: "px-5 py-2.5 hover:bg-surface-hover transition-colors",
@@ -540,19 +535,16 @@ function LogRow({ log }) {
540
535
  children: [
541
536
  log.manual && /* @__PURE__ */ jsx(Chip, {
542
537
  size: "smallest",
543
- className: "bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300",
544
- children: "manual"
538
+ children: t("studio_cron_manual_run")
545
539
  }),
546
540
  /* @__PURE__ */ jsx(Typography, {
547
541
  variant: "caption",
548
542
  className: "font-mono text-[11px]",
549
543
  children: formatDuration(log.durationMs)
550
544
  }),
551
- /* @__PURE__ */ jsx("svg", {
552
- className: cls("w-3 h-3 transition-transform text-surface-400", expanded && "rotate-180"),
553
- fill: "currentColor",
554
- viewBox: "0 0 20 20",
555
- 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")
556
548
  })
557
549
  ]
558
550
  })
@@ -560,11 +552,12 @@ function LogRow({ log }) {
560
552
  }), expanded && /* @__PURE__ */ jsxs("div", {
561
553
  className: "mt-2 ml-6 space-y-2",
562
554
  children: [
563
- log.error && /* @__PURE__ */ jsx("div", {
564
- 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",
565
558
  children: /* @__PURE__ */ jsx(Typography, {
566
- variant: "caption",
567
- className: "font-mono text-[11px] text-red-600 dark:text-red-300 break-all",
559
+ variant: "mono",
560
+ className: "text-[11px] break-all",
568
561
  children: log.error
569
562
  })
570
563
  }),
@@ -593,4 +586,4 @@ function LogRow({ log }) {
593
586
  //#endregion
594
587
  export { CronJobsView };
595
588
 
596
- //# sourceMappingURL=CronJobsView-TDErHTuK.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":";;;;;;;AA8BA,SAAS,eAAe,IAAoB;CACxC,IAAI,KAAK,KAAM,OAAO,GAAG,GAAG;CAC5B,IAAI,KAAK,KAAO,OAAO,IAAI,KAAK,IAAA,CAAM,QAAQ,CAAC,EAAE;CACjD,OAAO,IAAI,KAAK,IAAA,CAAO,QAAQ,CAAC,EAAE;AACtC;;AAGA,SAAS,eAAe,KAAyB,UAA0B;CACvE,IAAI,CAAC,KAAK,OAAO;CACjB,MAAM,IAAI,IAAI,KAAK,GAAG;CACtB,MAAM,OAAO,EAAE,QAAQ,IAAI,KAAK,IAAI;CACpC,MAAM,MAAM,KAAK,IAAI,IAAI;CACzB,IAAI,OAAO,OAAU,OAAO,EAAE,eAAe,QAAQ;CACrD,MAAM,WAAW,IAAI,KAAK,mBAAmB,UAAU;EAAE,OAAO;EAAS,SAAS;CAAO,CAAC;CAC1F,OAAO,MAAM,OACP,SAAS,OAAO,KAAK,MAAM,OAAO,GAAK,GAAG,QAAQ,IAClD,SAAS,OAAO,KAAK,MAAM,OAAO,IAAO,GAAG,MAAM;AAC5D;AASA,SAAS,WAAW,SAAuC;CACvD,MAAM,WAAW,QAAQ,YAAY,CAAC;CACtC,OAAO;EAAE;EAAU,aAAa,KAAK,IAAI,IAAI,QAAQ,WAAW,KAAK,SAAS,MAAM;CAAE;AAC1F;;;;;;;AAQA,SAAS,iBAAiB,EAAE,YAAwC;CAChE,MAAM,EAAE,MAAM,eAAe;CAC7B,IAAI,SAAS,SAAS,WAAW,KAAK,SAAS,gBAAgB,GAAG,OAAO;CACzE,OACI,oBAAC,OAAD;EAAO,OAAM;YACT,qBAAC,OAAD;GAAK,WAAU;aAAf,CACK,SAAS,SAAS,KAAI,QACnB,qBAAC,OAAD;IAAkB,WAAU;cAA5B,CACI,oBAAC,YAAD;KAAY,SAAQ;KAAQ,WAAU;eACjC,EAAE,2BAA2B,EAAE,KAAK,IAAI,KAAK,CAAC;IACvC,CAAA,GACZ,qBAAC,YAAD;KAAY,SAAQ;KAAU,WAAU;eAAxC;MACK,IAAI;MAAS;MAAI,IAAI;KACd;MACX;MAPK,IAAI,EAOT,CACR,GACA,SAAS,cAAc,KACpB,oBAAC,YAAD;IAAY,SAAQ;IAAQ,WAAU;cACjC,EAAE,4BAA4B,EAAE,OAAO,SAAS,YAAY,CAAC;GACtD,CAAA,CAEf;;CACF,CAAA;AAEf;AAEA,IAAM,cAAsC;CACxC,MAAM;CACV,SAAS;CACT,SAAS;CACL,OAAO;CACX,UAAU;AACV;AAEA,SAAgB,eAAe;CAC3B,MAAM,SAAS,gBAA8B;CAC7C,MAAM,WAAW,sBAAsB;CACvC,MAAM,EAAE,GAAG,SAAS,eAAe;CACnC,MAAM,WAAW,MAAM,YAAY;CACnC,MAAM,CAAC,MAAM,WAAW,SAA0B,CAAC,CAAC;CACpD,MAAM,CAAC,UAAU,eAAe,SAAuB;EAAE,UAAU,CAAC;EAAG,aAAa;CAAE,CAAC;CACvF,MAAM,CAAC,SAAS,cAAc,SAAS,IAAI;CAC3C,MAAM,CAAC,YAAY,iBAAiB,SAAwB,IAAI;CAChE,MAAM,CAAC,MAAM,WAAW,SAA4B,CAAC,CAAC;CACtD,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;;;;;;CAMpD,MAAM,CAAC,aAAa,kBAAkB,SAA6B,IAAI;CACvE,MAAM,CAAC,YAAY,iBAAiB,SAAwB,IAAI;;CAEhE,MAAM,CAAC,SAAS,cAAc,SAA6B,IAAI;CAG/D,MAAM,YAAY,OAAO,MAAM;CAC/B,UAAU,UAAU;CACpB,MAAM,cAAc,OAAO,QAAQ;CACnC,YAAY,UAAU;CAGtB,gBAAgB;EACZ,IAAI,YAAY;EAEhB,eAAe,OAAO;GAClB,MAAM,IAAI,UAAU;GACpB,IAAI,CAAC,GAAG,MAAM;IACV,WAAW,KAAK;IAChB;GACJ;GACA,IAAI;IACA,MAAM,MAAM,MAAM,EAAE,KAAK,SAAS;IAClC,IAAI,CAAC,WAAW;KACZ,QAAQ,IAAI,IAAI;KAChB,YAAY,WAAW,GAAG,CAAC;KAC3B,WAAW,IAAI;IACnB;GACJ,SAAS,GAAY;IAKjB,IAAI,CAAC,WAAW,WAAW,oBAAoB,CAAC,CAAC;GACrD,UAAU;IACN,IAAI,CAAC,WAAW,WAAW,KAAK;GACpC;EACJ;EAEA,KAAK;EAEL,IAAI,YAAkD;EAEtD,MAAM,qBAAqB;GACvB,IAAI,WAAW;GACf,YAAY,WAAW,YAAY;IAC/B,IAAI,SAAS,oBAAoB,WAC7B,MAAM,KAAK;IAEf,aAAa;GACjB,GAAG,IAAM;EACb;EAEA,aAAa;EAEb,MAAM,yBAAyB;GAC3B,IAAI,SAAS,oBAAoB,WAC7B,KAAK;EAEb;EACA,SAAS,iBAAiB,oBAAoB,gBAAgB;EAE9D,aAAa;GACT,YAAY;GACZ,IAAI,WAAW,aAAa,SAAS;GACrC,SAAS,oBAAoB,oBAAoB,gBAAgB;EACrE;CACJ,GAAG,CAAC,CAAC;CAGL,gBAAgB;EACZ,IAAI,CAAC,YAAY;GACb,QAAQ,CAAC,CAAC;GACV,eAAe,IAAI;GACnB;EACJ;EACA,IAAI,YAAY;EAChB,MAAM,IAAI,UAAU;EACpB,IAAI,CAAC,GAAG,MAAM;EAEd,eAAe,IAAI;EACnB,eAAe,IAAI;EACnB,EAAE,KAAK,WAAW,YAAY,EAAE,OAAO,GAAG,CAAC,CAAC,CACvC,MAAK,QAAO;GAAE,IAAI,CAAC,WAAW,QAAQ,IAAI,IAAI;EAAG,CAAC,CAAC,CACnD,OAAO,MAAe;GACnB,IAAI,WAAW;GAEf,QAAQ,CAAC,CAAC;GACV,eAAe,oBAAoB,CAAC,CAAC;EACzC,CAAC,CAAC,CACD,cAAc;GAAE,IAAI,CAAC,WAAW,eAAe,KAAK;EAAG,CAAC;EAE7D,aAAa;GAAE,YAAY;EAAM;CACrC,GAAG,CAAC,UAAU,CAAC;CAGf,eAAe,cAAc;EACzB,MAAM,IAAI,UAAU;EACpB,IAAI,CAAC,GAAG,MAAM;EACd,IAAI;GACA,MAAM,MAAM,MAAM,EAAE,KAAK,SAAS;GAClC,QAAQ,IAAI,IAAI;GAChB,YAAY,WAAW,GAAG,CAAC;GAC3B,WAAW,IAAI;EACnB,SAAS,GAAY;GAKjB,WAAW,oBAAoB,CAAC,CAAC;EACrC;CACJ;CAEA,eAAe,YAAY,IAAY;EACnC,MAAM,IAAI,UAAU;EACpB,IAAI,CAAC,GAAG,MAAM;EACd,eAAe,IAAI;EACnB,eAAe,IAAI;EACnB,IAAI;GACA,MAAM,MAAM,MAAM,EAAE,KAAK,WAAW,IAAI,EAAE,OAAO,GAAG,CAAC;GACrD,QAAQ,IAAI,IAAI;EACpB,SAAS,GAAY;GAEjB,QAAQ,CAAC,CAAC;GACV,eAAe,oBAAoB,CAAC,CAAC;EACzC,UACQ;GAAE,eAAe,KAAK;EAAG;CACrC;CAEA,MAAM,gBAAgB,OAAO,OAAe;EACxC,MAAM,IAAI,UAAU;EACpB,IAAI,CAAC,GAAG,MAAM;EACd,cAAc,EAAE;EAChB,IAAI;GAGA,MAAM,EAAE,QAAQ,MAAM,EAAE,KAAK,WAAW,EAAE;GAC1C,MAAM,MAAM,KAAK,MAAK,MAAK,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ;GACjD,YAAY,QAAQ,KAAK,IAAI,UACvB;IAAE,MAAM;IAAW,SAAS,EAAE,6BAA6B,EAAE,IAAI,CAAC;GAAE,IACpE;IAAE,MAAM;IAAS,SAAS,EAAE,0BAA0B;KAAE;KAAK,OAAO,IAAI,SAAS;IAAI,CAAC;GAAE,CAAC;GAC/F,MAAM,YAAY;GAClB,IAAI,eAAe,IAAI,YAAY,EAAE;EACzC,SAAS,GAAY;GACjB,YAAY,QAAQ,KAAK;IAAE,MAAM;IAC7C,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;GAAE,CAAC;EAC7C,UAAU;GAAE,cAAc,IAAI;EAAG;CACrC;CAEA,MAAM,eAAe,OAAO,IAAY,YAAqB;EACzD,MAAM,IAAI,UAAU;EACpB,IAAI,CAAC,GAAG,MAAM;EACd,IAAI;GACA,MAAM,EAAE,KAAK,UAAU,IAAI,OAAO;GAClC,YAAY,QAAQ,KAAK;IAAE,MAAM;IAC7C,SAAS,UAAU,EAAE,yBAAyB,IAAI,EAAE,wBAAwB;GAAE,CAAC;GACnE,MAAM,YAAY;EACtB,SAAS,GAAY;GACjB,YAAY,QAAQ,KAAK;IAAE,MAAM;IAC7C,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;GAAE,CAAC;EAC7C;CACJ;CAEA,MAAM,cAAc,KAAK,MAAK,MAAK,EAAE,OAAO,UAAU;CAEtD,IAAI,SAAS,OAAO,oBAAC,OAAD;EAAK,WAAU;YAA0C,oBAAC,kBAAD,CAAkB,CAAA;CAAM,CAAA;CAErG,IAAI,SAAS,OACT,oBAAC,iBAAD;EACa;EACT,OAAO,EAAE,yBAAyB;EAClC,aAAa,EAAE,0BAA0B;EACzC,YAAY,EAAE,yBAAyB;EACvC,SAAS;CACZ,CAAA;CAGL,MAAM,cAAc,SAAS,SAAS,SAAS,KAAK,SAAS,cAAc;CAE3E,IAAI,KAAK,WAAW,GAAG,OACnB,qBAAC,OAAD;EAAK,WAAU;YAAf;GACI,oBAAC,cAAD;IAAc,MAAM,SAAS;IAAQ,WAAU;GAAyC,CAAA;GACvF,cACG,oBAAC,OAAD;IAAK,WAAU;cACX,oBAAC,kBAAD,EAA4B,SAAU,CAAA;GACrC,CAAA,IAEL,qBAAA,UAAA,EAAA,UAAA,CACI,oBAAC,YAAD;IAAY,SAAQ;IAAK,OAAM;cAAa,EAAE,yBAAyB;GAAc,CAAA,GACrF,oBAAC,YAAD;IAAY,SAAQ;IAAQ,OAAM;IAAW,WAAU;cAClD,EAAE,wBAAwB;GACnB,CAAA,CACd,EAAA,CAAA;GAEN,oBAAC,KAAD;IACI,MAAK;IACL,QAAO;IACP,KAAI;IACJ,WAAU;cAET,EAAE,sBAAsB;GAC1B,CAAA;EACF;;CAGT,OACI,qBAAC,OAAD;EAAK,WAAU;YAAf,CAEI,qBAAC,OAAD;GAAK,WAAW,IAAI,yDAAyD,kBAAkB;aAA/F;IACI,qBAAC,OAAD;KAAK,WAAW,IAAI,wFAAwF,kBAAkB;eAA9H,CACI,qBAAC,OAAD;MAAK,WAAU;gBAAf;OACI,oBAAC,cAAD;QAAc,MAAM,SAAS;QAAU,WAAU;OAAe,CAAA;OAChE,oBAAC,YAAD;QAAY,SAAQ;QAAY,WAAU;kBAAiB,EAAE,kBAAkB;OAAc,CAAA;OAC7F,oBAAC,MAAD;QAAM,MAAK;QAAW,WAAU;kBAA4D,KAAK;OAAa,CAAA;MAC7G;SACL,oBAAC,YAAD;MAAY,cAAY,EAAE,qBAAqB;MAAG,MAAK;MAAQ,SAAS;MAAa,OAAO,EAAE,qBAAqB;gBAAG,oBAAC,eAAD,EAAe,MAAM,SAAS,SAAU,CAAA;KAAa,CAAA,CAC1K;;IACJ,eACG,oBAAC,OAAD;KAAK,WAAU;eACX,oBAAC,kBAAD,EAA4B,SAAU,CAAA;IACrC,CAAA;IAET,oBAAC,OAAD;KAAK,WAAU;eACV,KAAK,KAAI,QACN,qBAAC,OAAD;MAEI,eAAe,cAAc,IAAI,EAAE;MACnC,WAAW,IACP,gFACA,eAAe,IAAI,KACb,4DACA,wBACV;gBARJ;OAUI,oBAAC,OAAD,EAAK,WAAW,IAAI,iCAAiC,YAAY,IAAI,UAAU,gBAAgB,EAAG,CAAA;OAClG,qBAAC,OAAD;QAAK,WAAU;kBAAf,CACI,oBAAC,YAAD;SAAY,SAAQ;SAAQ,WAAU;mBAAoC,IAAI;QAAiB,CAAA,GAC/F,oBAAC,YAAD;SAAY,SAAQ;SAAU,OAAM;SAAY,WAAU;mBAAkC,IAAI;QAAqB,CAAA,CACpH;;OACJ,IAAI,UAAU,aAAa,oBAAC,kBAAD,EAAkB,MAAK,WAAW,CAAA;MAC7D;QAfI,IAAI,EAeR,CACR;IACA,CAAA;GACJ;MAGL,oBAAC,OAAD;GAAK,WAAU;aACV,CAAC,cACE,oBAAC,OAAD;IAAK,WAAU;cACX,oBAAC,YAAD;KAAY,SAAQ;KAAQ,OAAM;eAAY,EAAE,wBAAwB;IAAc,CAAA;GACrF,CAAA,IAEL,qBAAA,UAAA,EAAA,UAAA;IAEI,qBAAC,OAAD;KAAK,WAAW,IAAI,qFAAqF,kBAAkB;eAA3H,CACI,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACI,oBAAC,OAAD,EAAK,WAAW,IAAI,4BAA4B,YAAY,YAAY,MAAM,EAAG,CAAA,GACjF,qBAAC,OAAD;OAAK,WAAU;iBAAf,CACI,oBAAC,YAAD;QAAY,SAAQ;QAAY,WAAU;kBAA0B,YAAY;OAAiB,CAAA,GAChG,YAAY,eAAe,oBAAC,YAAD;QAAY,SAAQ;QAAU,OAAM;QAAY,WAAU;kBAAY,YAAY;OAAwB,CAAA,CACrI;QACJ;SACL,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACI,oBAAC,YAAD;OAAY,cAAY,YAAY,UAAU,EAAE,uBAAuB,IAAI,EAAE,wBAAwB;OAAG,OAAO,YAAY,UAAU,EAAE,uBAAuB,IAAI,EAAE,wBAAwB;OAAG,MAAK;OAAQ,eAAe,aAAa,YAAY,IAAI,CAAC,YAAY,OAAO;iBACvQ,YAAY,UAAU,oBAAC,WAAD,EAAW,MAAM,SAAS,MAAO,CAAA,IAAI,oBAAC,UAAD,EAAU,MAAM,SAAS,SAAU,CAAA;MACvF,CAAA,GACZ,oBAAC,QAAD;OACI,MAAK;OACL,OAAM;OACN,eAAe,cAAc,YAAY,EAAE;OAC3C,UAAU,eAAe,YAAY;OACrC,WAAW,eAAe,YAAY,KAAK,oBAAC,kBAAD,EAAkB,MAAK,WAAW,CAAA,IAAI,oBAAC,UAAD,EAAU,MAAM,SAAS,SAAU,CAAA;iBAEnH,EAAE,qBAAqB;MACpB,CAAA,CACP;OACJ;;IAGL,qBAAC,OAAD;KAAK,WAAU;eAAf;MACI,qBAAC,OAAD;OAAK,WAAU;iBAAf;QACI,oBAAC,UAAD;SAAU,OAAO,EAAE,sBAAsB;SAAG,OAAO,YAAY;SAAU,MAAA;QAAK,CAAA;QAC9E,oBAAC,UAAD;SAAU,OAAO,EAAE,sBAAsB;SAAG,OAAO,eAAe,YAAY,WAAW,QAAQ;QAAG,CAAA;QACpG,oBAAC,UAAD;SAAU,OAAO,EAAE,sBAAsB;SAAG,OAAO,YAAY,UAAU,eAAe,YAAY,WAAW,QAAQ,IAAI,EAAE,oBAAoB;QAAG,CAAA;QACpJ,oBAAC,UAAD;SAAU,OAAO,EAAE,sBAAsB;SAAG,OAAO,YAAY,mBAAmB,KAAA,IAAY,eAAe,YAAY,cAAc,IAAI;QAAK,CAAA;OAC/I;;MACL,qBAAC,OAAD;OAAK,WAAU;iBAAf;QACI,oBAAC,UAAD;SAAU,OAAO,EAAE,oBAAoB;SAAG,OAAO,YAAY,MAAM,YAAY;SAAG,WAAW,YAAY,UAAU,UAAU,QAAQ,YAAY,UAAU,aAAa,SAAS;QAAS,CAAA;QAC1L,oBAAC,UAAD;SAAU,OAAO,EAAE,wBAAwB;SAAG,OAAO,OAAO,YAAY,SAAS;QAAG,CAAA;QACpF,oBAAC,UAAD;SAAU,OAAO,EAAE,sBAAsB;SAAG,OAAO,OAAO,YAAY,aAAa;SAAG,WAAW,YAAY,gBAAgB;QAAG,CAAA;OAC/H;;MACJ,YAAY,aACT,qBAAC,OAAD;OAAO,OAAM;OAAQ,gBAAe;iBAApC,CACI,oBAAC,YAAD;QAAY,SAAQ;QAAU,WAAU;kBAAqB,EAAE,wBAAwB;OAAc,CAAA,GACrG,oBAAC,YAAD;QAAY,SAAQ;QAAO,WAAU;kBAAyB,YAAY;OAAsB,CAAA,CAC7F;;KAEV;;IAGL,qBAAC,OAAD;KAAK,WAAW,IAAI,wEAAwE,kBAAkB;eAA9G,CACI,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACI,oBAAC,aAAD;OAAa,MAAM,SAAS;OAAU,WAAU;MAAmB,CAAA,GACnE,oBAAC,YAAD;OAAY,SAAQ;OAAY,WAAU;iBAA6B,EAAE,+BAA+B;MAAc,CAAA,CACrH;SACL,oBAAC,YAAD;MAAY,cAAY,EAAE,0BAA0B;MAAG,MAAK;MAAQ,eAAe,YAAY,YAAY,EAAE;MAAG,OAAO,EAAE,0BAA0B;gBAAG,oBAAC,eAAD,EAAe,MAAM,SAAS,SAAU,CAAA;KAAa,CAAA,CAC1M;;IACL,oBAAC,OAAD;KAAK,WAAU;eACV,cACG,oBAAC,OAAD;MAAK,WAAU;gBAA0B,oBAAC,kBAAD,EAAkB,MAAK,QAAQ,CAAA;KAAM,CAAA,IAC9E,cACA,oBAAC,iBAAD;MACI,SAAS;MACT,OAAO,EAAE,8BAA8B;MACvC,aAAa,EAAE,+BAA+B;MAC9C,YAAY,EAAE,yBAAyB;MACvC,eAAe,YAAY,YAAY,EAAE;KAC5C,CAAA,IACD,KAAK,WAAW,IAChB,oBAAC,OAAD;MAAK,WAAU;gBACX,oBAAC,YAAD;OAAY,SAAQ;OAAQ,OAAM;iBAAY,EAAE,2BAA2B;MAAc,CAAA;KACxF,CAAA,IAEL,oBAAC,OAAD;MAAK,WAAU;gBACV,KAAK,KAAK,KAAK,QACZ,oBAAC,QAAD,EAAuB,IAAK,GAAf,GAAe,CAC/B;KACA,CAAA;IAER,CAAA;GACP,EAAA,CAAA;EAEL,CAAA,CACJ;;AAEb;AAEA,SAAS,SAAS,EAAE,OAAO,OAAO,MAAM,WAAW,aAEhD;CACC,OACI,qBAAC,OAAD;EAAK,WAAW,IAAI,+CAA+C,kBAAkB;YAArF,CACI,oBAAC,YAAD;GAAY,SAAQ;GAAU,OAAM;aAAa;EAAkB,CAAA,GACnE,oBAAC,YAAD;GAAY,SAAQ;GAAQ,WAAW,IACnC,oCACA,QAAQ,aACR,aAAa,kCACb,cAAc,SAAS,gBACvB,cAAc,WAAW,oBACzB,cAAc,UAAU,kBAC5B;aAAI;EAAkB,CAAA,CACrB;;AAEb;AAEA,SAAS,OAAO,EAAE,OAAiC;CAC/C,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,OACI,qBAAC,OAAD;EAAK,WAAU;YAAf,CACI,qBAAC,OAAD;GAAK,WAAU;GAAyC,eAAe,YAAY,CAAC,QAAQ;aAA5F;IACK,IAAI,UACC,oBAAC,iBAAD;KAAiB,MAAM,SAAS;KAAU,WAAU;IAA4B,CAAA,IAChF,oBAAC,iBAAD;KAAiB,MAAM,SAAS;KAAU,WAAU;IAAwB,CAAA;IAClF,oBAAC,OAAD;KAAK,WAAU;eACX,oBAAC,YAAD;MAAY,SAAQ;MAAU,WAAU;gBAA0C,IAAI,KAAK,IAAI,SAAS,CAAC,CAAC,eAAe;KAAc,CAAA;IACtI,CAAA;IACL,qBAAC,OAAD;KAAK,WAAU;eAAf;MACK,IAAI,UAAU,oBAAC,MAAD;OAAM,MAAK;iBAAY,EAAE,wBAAwB;MAAQ,CAAA;MACxE,oBAAC,YAAD;OAAY,SAAQ;OAAU,WAAU;iBAAyB,eAAe,IAAI,UAAU;MAAc,CAAA;MAC5G,oBAAC,iBAAD;OAAiB,MAAM,SAAS;OAAU,WAAW,IAAI,0EAA0E,YAAY,YAAY;MAAG,CAAA;KAC7J;;GACJ;MACJ,YACG,qBAAC,OAAD;GAAK,WAAU;aAAf;IACK,IAAI,SACD,oBAAC,OAAD;KAAO,OAAM;KAAQ,MAAK;eACtB,oBAAC,YAAD;MAAY,SAAQ;MAAO,WAAU;gBAAyB,IAAI;KAAkB,CAAA;IACjF,CAAA;IAEV,IAAI,KAAK,SAAS,KACf,oBAAC,OAAD;KAAK,WAAU;eACV,IAAI,KAAK,KAAK,MAAM,MACjB,oBAAC,OAAD;MAAa,WAAU;gBAAgF;KAAU,GAAvG,CAAuG,CACpH;IACA,CAAA;IAER,IAAI,WAAW,KAAA,KACZ,qBAAC,OAAD;KAAK,WAAU;eAAf,CACI,oBAAC,YAAD;MAAY,SAAQ;MAAU,WAAU;gBAAmE;KAAkB,CAAA,GAC7H,oBAAC,OAAD;MAAK,WAAU;gBAA8F,KAAK,UAAU,IAAI,QAAQ,MAAM,CAAC;KAAO,CAAA,CACrJ;;GAER;IAER;;AAEb"}