@cartanova/qgrid-cli 2.6.10 → 2.7.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 (64) hide show
  1. package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +137 -60
  2. package/bundle/dist/application/qgrid/qgrid.frame.js +5 -2
  3. package/bundle/dist/application/setting/setting.model.js +65 -3
  4. package/bundle/dist/application/setting/setting.types.js +13 -3
  5. package/bundle/dist/i18n/en.js +3 -1
  6. package/bundle/dist/i18n/ko.js +3 -1
  7. package/bundle/dist/utils/process-supervisor.js +22 -0
  8. package/bundle/dist/utils/server-restart.js +61 -0
  9. package/bundle/src/application/qgrid/qgrid-oauth-redirect.test.ts +7 -0
  10. package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +113 -0
  11. package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +172 -75
  12. package/bundle/src/application/qgrid/qgrid.frame.test.ts +45 -0
  13. package/bundle/src/application/qgrid/qgrid.frame.ts +4 -0
  14. package/bundle/src/application/setting/setting.model.test.ts +104 -0
  15. package/bundle/src/application/setting/setting.model.ts +75 -3
  16. package/bundle/src/application/setting/setting.types.ts +12 -0
  17. package/bundle/src/application/sonamu.generated.http +7 -0
  18. package/bundle/src/i18n/en.ts +3 -0
  19. package/bundle/src/i18n/ko.ts +3 -0
  20. package/bundle/src/server-error-handler.test.ts +15 -0
  21. package/bundle/src/utils/process-supervisor.test.ts +24 -0
  22. package/bundle/src/utils/process-supervisor.ts +17 -0
  23. package/bundle/src/utils/server-restart.test.ts +76 -0
  24. package/bundle/src/utils/server-restart.ts +68 -0
  25. package/bundle/web-dist/client/assets/chat-icon-JYfue7fB.png +0 -0
  26. package/bundle/web-dist/client/assets/index-C24D5PiN.js +106 -0
  27. package/bundle/web-dist/client/assets/index-CcMEpCgt.css +1 -0
  28. package/bundle/web-dist/client/assets/logs-CIlZG6A3.js +1 -0
  29. package/bundle/web-dist/client/assets/monit-DZFBMxKl.js +1 -0
  30. package/bundle/web-dist/client/assets/{routes-CkBXB-V4.js → routes-C3wlSNBL.js} +2 -2
  31. package/bundle/web-dist/client/assets/services.generated-jGypfumC.js +51 -0
  32. package/bundle/web-dist/client/assets/settings-ClRDWO-8.js +1 -0
  33. package/bundle/web-dist/client/assets/show-BP7U8yyJ.js +2 -0
  34. package/bundle/web-dist/client/assets/tokens-DfQeULnJ.js +1 -0
  35. package/bundle/web-dist/client/assets/{use-oauth-login-flow-Bz87RV5v.js → use-oauth-login-flow-CY-ASCfL.js} +1 -1
  36. package/bundle/web-dist/client/index.html +3 -3
  37. package/bundle/web-dist/server/assets/{lazyRouteComponent-Ws7I6fL-.js → lazyRouteComponent-DOc8TV-P.js} +2 -2
  38. package/bundle/web-dist/server/assets/{logs-C1rxLh8H.js → logs-CfF3nmLM.js} +3 -4
  39. package/bundle/web-dist/server/assets/{monit-C8gx9Sar.js → monit-BSaKhh5D.js} +2 -3
  40. package/bundle/web-dist/server/assets/{plus-BISXNqRU.js → plus-DLFUHsfx.js} +1 -1
  41. package/bundle/web-dist/server/assets/{react-dom-D7duWOwf.js → react-dom-BotjLnTK.js} +1 -1
  42. package/bundle/web-dist/server/assets/{routes-ZW_Ih5Em.js → routes-CHMxrTT0.js} +4 -5
  43. package/bundle/web-dist/server/assets/{sd.generated-LO-h1_16.js → services.generated-D3Q3FYot.js} +16697 -15377
  44. package/bundle/web-dist/server/assets/{settings--aoatERH.js → settings-DOQjV28c.js} +271 -55
  45. package/bundle/web-dist/server/assets/show-Cn_C6Qvl.js +2776 -0
  46. package/bundle/web-dist/server/assets/{tokens-DIObtTuZ.js → tokens-c0EN3iCx.js} +4 -5
  47. package/bundle/web-dist/server/assets/{use-oauth-login-flow-V3k8PSqQ.js → use-oauth-login-flow-B68AbMDE.js} +1 -2
  48. package/bundle/web-dist/server/entry-server.generated.js +23856 -40
  49. package/dist/cli.js +93 -38
  50. package/package.json +1 -1
  51. package/skills/qgrid/references/cli-env-and-server-boot.md +34 -3
  52. package/bundle/web-dist/client/assets/index-BRRNJRgu.js +0 -78
  53. package/bundle/web-dist/client/assets/index-rdbxJinT.css +0 -1
  54. package/bundle/web-dist/client/assets/logs-BI5K8oe4.js +0 -1
  55. package/bundle/web-dist/client/assets/monit-4Y-Razet.js +0 -1
  56. package/bundle/web-dist/client/assets/sd.generated-Bwa7rses.js +0 -51
  57. package/bundle/web-dist/client/assets/services.generated-BzwTuZRP.js +0 -1
  58. package/bundle/web-dist/client/assets/settings-B62_r1i4.js +0 -1
  59. package/bundle/web-dist/client/assets/show-CGRchJj7.js +0 -30
  60. package/bundle/web-dist/client/assets/tokens-Bx5mQnoJ.js +0 -1
  61. package/bundle/web-dist/server/assets/services.generated-CZIChgHV.js +0 -1305
  62. package/bundle/web-dist/server/assets/show-BY27w9oi.js +0 -25968
  63. /package/bundle/web-dist/client/assets/{plus-p4vtg6LN.js → plus-DqBd6gHk.js} +0 -0
  64. /package/bundle/web-dist/server/assets/{cost-DzjfVtUr.js → cost-CSH_t17I.js} +0 -0
@@ -1,6 +1,6 @@
1
- import { Y as __toESM, g as require_react, h as require_jsx_runtime, m as useQueryClient } from "./sd.generated-LO-h1_16.js";
2
- import { a as SettingService } from "./services.generated-CZIChgHV.js";
3
- import { t as lucidePlus } from "./plus-BISXNqRU.js";
1
+ import { V as __toESM, _ as useQuery, a as SettingService, b as require_jsx_runtime, h as isAxiosError, n as QgridService, s as isSonamuError, x as require_react, y as useQueryClient } from "./services.generated-D3Q3FYot.js";
2
+ import { s as lucideRotateCcw } from "../entry-server.generated.js";
3
+ import { t as lucidePlus } from "./plus-DLFUHsfx.js";
4
4
  //#region ~icons/lucide/eye.jsx
5
5
  var import_react = /* @__PURE__ */ __toESM(require_react());
6
6
  var import_jsx_runtime = require_jsx_runtime();
@@ -55,28 +55,13 @@ var lucideMinus = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg",
55
55
  })
56
56
  });
57
57
  //#endregion
58
- //#region ~icons/lucide/rotate-ccw.jsx
59
- var lucideRotateCcw = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
60
- viewBox: "0 0 24 24",
61
- width: "1.2em",
62
- height: "1.2em",
63
- ...props,
64
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", {
65
- fill: "none",
66
- stroke: "currentColor",
67
- strokeLinecap: "round",
68
- strokeLinejoin: "round",
69
- strokeWidth: 2,
70
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 12a9 9 0 1 0 9-9a9.75 9.75 0 0 0-6.74 2.74L3 8" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 3v5h5" })]
71
- })
72
- });
73
- //#endregion
74
58
  //#region src/components/qgrid/SettingsPanel.tsx
75
59
  var GROUP_LABELS = {
76
60
  openai: "OpenAI 워커",
77
61
  slack: "Slack 알림",
78
62
  slackConnection: "Slack 연결"
79
63
  };
64
+ var RESTART_READY_TIMEOUT_MS = 180 * 1e3;
80
65
  function maskValue(value) {
81
66
  if (!value) return "";
82
67
  if (value.length <= 12) return "•".repeat(value.length);
@@ -164,7 +149,7 @@ function SettingRow({ item }) {
164
149
  const pending = updateMutation.isPending || resetMutation.isPending;
165
150
  const value = draft ?? item.value;
166
151
  const dirty = draft !== null && draft !== item.value;
167
- const invalidate = () => queryClient.invalidateQueries({ queryKey: ["Setting", "getSettingList"] });
152
+ const invalidate = () => queryClient.invalidateQueries({ queryKey: SettingService.getSettingListQueryOptions().queryKey });
168
153
  const commit = async (next) => {
169
154
  setError(null);
170
155
  try {
@@ -301,8 +286,215 @@ function TriggerReminderButton() {
301
286
  })]
302
287
  });
303
288
  }
289
+ /**
290
+ * 재시작은 스스로 종료하고 감독자(pm2)가 다시 띄우는 것에 맡긴다. 되돌릴 수 없고 1~2분간
291
+ * 요청이 실패하므로 확인 단계를 둔다 — 무엇이 일어나는지 모달이 먼저 설명한다.
292
+ */
293
+ function RestartPanel({ supervisor }) {
294
+ const [confirming, setConfirming] = (0, import_react.useState)(false);
295
+ const [restartRequested, setRestartRequested] = (0, import_react.useState)(false);
296
+ const [restartStartedAt, setRestartStartedAt] = (0, import_react.useState)(null);
297
+ const [sawUnavailable, setSawUnavailable] = (0, import_react.useState)(false);
298
+ const [completing, setCompleting] = (0, import_react.useState)(false);
299
+ const [error, setError] = (0, import_react.useState)(null);
300
+ const completingRef = (0, import_react.useRef)(false);
301
+ const restartAttemptRef = (0, import_react.useRef)(0);
302
+ const queryClient = useQueryClient();
303
+ const mutation = SettingService.useRestartServerMutation();
304
+ const resetMutation = mutation.reset;
305
+ const healthQuery = useQuery({
306
+ ...QgridService.healthQueryOptions(),
307
+ enabled: restartRequested && !completing,
308
+ refetchInterval: restartRequested && !completing ? 1e3 : false,
309
+ refetchIntervalInBackground: true,
310
+ retry: false
311
+ });
312
+ (0, import_react.useEffect)(() => {
313
+ if (!restartRequested || restartStartedAt === null || completing) return;
314
+ const remaining = Math.max(0, RESTART_READY_TIMEOUT_MS - (Date.now() - restartStartedAt));
315
+ const timeout = window.setTimeout(() => {
316
+ restartAttemptRef.current += 1;
317
+ resetMutation();
318
+ setConfirming(false);
319
+ setRestartRequested(false);
320
+ setRestartStartedAt(null);
321
+ setSawUnavailable(false);
322
+ setError("재시작 후 3분 안에 서버 준비를 확인하지 못했습니다. 상태를 확인한 뒤 다시 시도하세요.");
323
+ }, remaining);
324
+ return () => window.clearTimeout(timeout);
325
+ }, [
326
+ completing,
327
+ resetMutation,
328
+ restartRequested,
329
+ restartStartedAt
330
+ ]);
331
+ (0, import_react.useEffect)(() => {
332
+ if (!restartRequested || restartStartedAt === null) return;
333
+ const currentAttemptError = healthQuery.isError && healthQuery.errorUpdatedAt >= restartStartedAt;
334
+ const currentAttemptData = healthQuery.isSuccess && healthQuery.dataUpdatedAt >= restartStartedAt;
335
+ if (currentAttemptError || currentAttemptData && !healthQuery.data.ready) {
336
+ setSawUnavailable(true);
337
+ return;
338
+ }
339
+ if (!sawUnavailable || !currentAttemptData || !healthQuery.data.ready || completingRef.current) return;
340
+ completingRef.current = true;
341
+ setCompleting(true);
342
+ queryClient.invalidateQueries({ queryKey: SettingService.getSettingListQueryOptions().queryKey }).then(() => {
343
+ restartAttemptRef.current += 1;
344
+ resetMutation();
345
+ setConfirming(false);
346
+ setRestartRequested(false);
347
+ setRestartStartedAt(null);
348
+ setSawUnavailable(false);
349
+ }).catch((cause) => {
350
+ restartAttemptRef.current += 1;
351
+ resetMutation();
352
+ setConfirming(false);
353
+ setRestartRequested(false);
354
+ setRestartStartedAt(null);
355
+ setSawUnavailable(false);
356
+ setError(`서버는 준비됐지만 설정을 새로고침하지 못했습니다: ${cause.message}`);
357
+ }).finally(() => {
358
+ completingRef.current = false;
359
+ setCompleting(false);
360
+ });
361
+ }, [
362
+ healthQuery.dataUpdatedAt,
363
+ healthQuery.data?.ready,
364
+ healthQuery.errorUpdatedAt,
365
+ healthQuery.isError,
366
+ healthQuery.isSuccess,
367
+ queryClient,
368
+ resetMutation,
369
+ restartStartedAt,
370
+ restartRequested,
371
+ sawUnavailable
372
+ ]);
373
+ const restart = async () => {
374
+ const attempt = restartAttemptRef.current + 1;
375
+ restartAttemptRef.current = attempt;
376
+ setError(null);
377
+ setSawUnavailable(false);
378
+ setCompleting(false);
379
+ completingRef.current = false;
380
+ setRestartRequested(true);
381
+ setRestartStartedAt(Date.now());
382
+ try {
383
+ await mutation.mutateAsync();
384
+ } catch (e) {
385
+ if (restartAttemptRef.current !== attempt) return;
386
+ if (isSonamuError(e) ? e.code >= 400 && e.code < 500 : isAxiosError(e) && e.response !== void 0 && e.response.status >= 400 && e.response.status < 500) {
387
+ restartAttemptRef.current += 1;
388
+ resetMutation();
389
+ setConfirming(false);
390
+ setRestartRequested(false);
391
+ setRestartStartedAt(null);
392
+ setError(e.message || "재시작 요청이 거부되었습니다.");
393
+ }
394
+ }
395
+ };
396
+ const closeConfirmation = () => {
397
+ if (restartRequested) return;
398
+ setConfirming(false);
399
+ setError(null);
400
+ };
401
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
402
+ className: "panel overflow-hidden",
403
+ children: [
404
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
405
+ className: "panel-header px-4 py-2.5 sm:px-5",
406
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
407
+ className: "text-[13px] font-medium text-sand-700",
408
+ children: "서버 제어"
409
+ })
410
+ }),
411
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
412
+ className: "px-4 py-3 sm:px-5 flex items-start justify-between gap-4",
413
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
414
+ className: "min-w-0",
415
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
416
+ className: "text-[13px] text-sand-800",
417
+ children: "qgrid 재시작"
418
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
419
+ className: "mt-0.5 text-[11px] text-sand-500",
420
+ children: supervisor ? "재시작 필요 설정을 반영하고 qgrid·Codex·Claude CLI를 확인합니다" : "로컬 실행 환경에서는 재시작할 수 없습니다"
421
+ })]
422
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
423
+ type: "button",
424
+ disabled: !supervisor || restartRequested || mutation.isPending,
425
+ onClick: () => setConfirming(true),
426
+ className: "shrink-0 px-2.5 py-1.5 text-[11px] font-medium rounded-lg border border-red-200 text-red-600 hover:bg-red-50 disabled:opacity-40 disabled:hover:bg-transparent transition-colors duration-150",
427
+ children: "재시작"
428
+ })]
429
+ }),
430
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
431
+ className: "px-4 pb-3 sm:px-5 text-[11px] text-red-500",
432
+ children: error
433
+ }),
434
+ confirming && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
435
+ className: "fixed inset-0 z-50 flex items-start justify-center pt-[15vh]",
436
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
437
+ className: "absolute inset-0 bg-sand-900/8 backdrop-blur-sm",
438
+ onClick: closeConfirmation,
439
+ onKeyDown: () => {}
440
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
441
+ className: "relative panel shadow-xl w-full max-w-sm mx-4",
442
+ children: [
443
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
444
+ className: "px-5 py-4 border-b border-sand-100/60",
445
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
446
+ className: "text-base font-medium text-sand-900",
447
+ children: restartRequested ? mutation.isPending ? "재시작 요청 중" : "서버 재연결 중" : "qgrid 재시작"
448
+ })
449
+ }),
450
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
451
+ className: "px-5 py-4 space-y-2",
452
+ children: [
453
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
454
+ className: "text-[13px] text-sand-700",
455
+ children: [
456
+ "워커가 다시 뜰 때까지 ",
457
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", {
458
+ className: "text-sand-900",
459
+ children: "1~2분간 요청이 실패"
460
+ }),
461
+ "합니다."
462
+ ]
463
+ }),
464
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
465
+ className: "text-[12px] text-sand-500",
466
+ children: "서버를 종료하면 모니터링 도구가 다시 띄웁니다. 이때 재시작 필요 설정을 반영하고, qgrid와 Codex CLI·Claude Code의 최신 버전을 확인합니다."
467
+ }),
468
+ restartRequested && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
469
+ className: "text-[12px] font-medium text-sienna-600",
470
+ children: mutation.isPending ? "종료 요청을 처리하고 있습니다…" : sawUnavailable ? "새 서버가 준비될 때까지 연결을 확인하고 있습니다…" : "서버 종료를 기다리고 있습니다…"
471
+ })
472
+ ]
473
+ }),
474
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
475
+ className: "px-5 py-3 border-t border-sand-100/60 flex justify-end gap-2",
476
+ children: [!restartRequested && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
477
+ type: "button",
478
+ onClick: closeConfirmation,
479
+ className: "px-3 py-1.5 text-[12px] rounded-lg text-sand-600 hover:bg-sand-100 transition-colors duration-150",
480
+ children: "취소"
481
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
482
+ type: "button",
483
+ disabled: restartRequested || mutation.isPending,
484
+ onClick: () => void restart(),
485
+ className: "px-3 py-1.5 text-[12px] font-medium rounded-lg bg-red-500 text-white hover:bg-red-600 disabled:opacity-50 transition-colors duration-150",
486
+ children: restartRequested ? mutation.isPending ? "요청 중" : "재연결 중" : "재시작"
487
+ })]
488
+ })
489
+ ]
490
+ })]
491
+ })
492
+ ]
493
+ });
494
+ }
304
495
  function SettingsPanel() {
305
- const { data, isLoading } = SettingService.useSettingList();
496
+ const settingsQuery = SettingService.useSettingList();
497
+ const { data, isLoading } = settingsQuery;
306
498
  if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
307
499
  className: "space-y-4",
308
500
  children: [0, 1].map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -323,51 +515,75 @@ function SettingsPanel() {
323
515
  })]
324
516
  }, i))
325
517
  });
518
+ if (settingsQuery.isError && !data) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
519
+ className: "panel px-5 py-6 text-center",
520
+ children: [
521
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
522
+ className: "text-[13px] font-medium text-sand-800",
523
+ children: "설정을 불러오지 못했습니다"
524
+ }),
525
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
526
+ className: "mt-1 text-[11px] text-red-500",
527
+ children: settingsQuery.error.message
528
+ }),
529
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
530
+ type: "button",
531
+ onClick: () => void settingsQuery.refetch(),
532
+ disabled: settingsQuery.isFetching,
533
+ className: "mt-3 px-3 py-1.5 text-[12px] rounded-lg border border-sand-200 text-sand-600 hover:bg-sand-100 disabled:opacity-50",
534
+ children: settingsQuery.isFetching ? "다시 불러오는 중" : "다시 시도"
535
+ })
536
+ ]
537
+ });
326
538
  const settings = data?.settings ?? [];
327
539
  const runtime = data?.runtime ?? [];
328
540
  const groups = [...new Set(settings.map((s) => s.group))];
329
541
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
330
542
  className: "space-y-4",
331
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
332
- className: "panel overflow-hidden",
333
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
334
- className: "panel-header flex items-center justify-between px-4 py-2.5 sm:px-5",
335
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
336
- className: "text-[13px] font-medium text-sand-700",
337
- children: "환경"
338
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
339
- className: "text-[11px] text-sand-400",
340
- children: "기동 시점 값"
341
- })]
342
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dl", {
343
- className: "px-4 py-3 sm:px-5 grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-1.5",
344
- children: runtime.map((info) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
345
- className: "flex items-baseline justify-between gap-3 min-w-0",
346
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
347
- className: "text-[11px] text-sand-500 shrink-0",
348
- children: info.label
349
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
350
- className: "text-[12px] font-mono text-sand-700 truncate",
351
- children: info.value
352
- })]
353
- }, info.label))
354
- })]
355
- }), groups.map((group) => {
356
- const items = settings.filter((s) => s.group === group);
357
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
543
+ children: [
544
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
358
545
  className: "panel overflow-hidden",
359
546
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
360
547
  className: "panel-header flex items-center justify-between px-4 py-2.5 sm:px-5",
361
548
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
362
549
  className: "text-[13px] font-medium text-sand-700",
363
- children: GROUP_LABELS[group] ?? group
364
- }), group === "slack" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriggerReminderButton, {})]
365
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
366
- className: "divide-y divide-sand-100/80",
367
- children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingRow, { item }, item.key))
550
+ children: "환경"
551
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
552
+ className: "text-[11px] text-sand-400",
553
+ children: "기동 시점 값"
554
+ })]
555
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dl", {
556
+ className: "px-4 py-3 sm:px-5 grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-1.5",
557
+ children: runtime.map((info) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
558
+ className: "flex items-baseline justify-between gap-3 min-w-0",
559
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
560
+ className: "text-[11px] text-sand-500 shrink-0",
561
+ children: info.label
562
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
563
+ className: "text-[12px] font-mono text-sand-700 truncate",
564
+ children: info.value
565
+ })]
566
+ }, info.label))
368
567
  })]
369
- }, group);
370
- })]
568
+ }),
569
+ groups.map((group) => {
570
+ const items = settings.filter((s) => s.group === group);
571
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
572
+ className: "panel overflow-hidden",
573
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
574
+ className: "panel-header flex items-center justify-between px-4 py-2.5 sm:px-5",
575
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
576
+ className: "text-[13px] font-medium text-sand-700",
577
+ children: GROUP_LABELS[group] ?? group
578
+ }), group === "slack" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriggerReminderButton, {})]
579
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
580
+ className: "divide-y divide-sand-100/80",
581
+ children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingRow, { item }, item.key))
582
+ })]
583
+ }, group);
584
+ }),
585
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RestartPanel, { supervisor: data?.supervisor ?? null })
586
+ ]
371
587
  });
372
588
  }
373
589
  //#endregion