@cartanova/qgrid-cli 2.6.10 → 2.6.11

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 (56) 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/index-CFvFRDk_.js +78 -0
  26. package/bundle/web-dist/client/assets/index-CJ9iTI_V.css +1 -0
  27. package/bundle/web-dist/client/assets/{logs-BI5K8oe4.js → logs-DeAK5Uyu.js} +1 -1
  28. package/bundle/web-dist/client/assets/{monit-4Y-Razet.js → monit-Rj94Sz3_.js} +1 -1
  29. package/bundle/web-dist/client/assets/{routes-CkBXB-V4.js → routes-DVseTo6T.js} +1 -1
  30. package/bundle/web-dist/client/assets/{sd.generated-Bwa7rses.js → sd.generated-W6XqgWK6.js} +8 -8
  31. package/bundle/web-dist/client/assets/services.generated-Dz5pA5Fb.js +1 -0
  32. package/bundle/web-dist/client/assets/settings-DvuRDFfS.js +1 -0
  33. package/bundle/web-dist/client/assets/{show-CGRchJj7.js → show-C7etBj89.js} +1 -1
  34. package/bundle/web-dist/client/assets/{tokens-Bx5mQnoJ.js → tokens-BroPe7Tn.js} +1 -1
  35. package/bundle/web-dist/client/assets/{use-oauth-login-flow-Bz87RV5v.js → use-oauth-login-flow-CuLvs_CW.js} +1 -1
  36. package/bundle/web-dist/client/index.html +3 -3
  37. package/bundle/web-dist/server/assets/{lazyRouteComponent-Ws7I6fL-.js → lazyRouteComponent-C-N6sd5N.js} +2 -2
  38. package/bundle/web-dist/server/assets/{logs-C1rxLh8H.js → logs-CVkeSr1S.js} +3 -3
  39. package/bundle/web-dist/server/assets/{monit-C8gx9Sar.js → monit-Bb6QfT9V.js} +3 -3
  40. package/bundle/web-dist/server/assets/{plus-BISXNqRU.js → plus-B8JRuCb9.js} +1 -1
  41. package/bundle/web-dist/server/assets/{react-dom-D7duWOwf.js → react-dom-ByF9tqTB.js} +1 -1
  42. package/bundle/web-dist/server/assets/{routes-ZW_Ih5Em.js → routes-Dnm-Eti6.js} +4 -4
  43. package/bundle/web-dist/server/assets/{sd.generated-LO-h1_16.js → sd.generated-CuyajYtc.js} +126 -116
  44. package/bundle/web-dist/server/assets/{services.generated-CZIChgHV.js → services.generated-BAGLi2r8.js} +9 -1
  45. package/bundle/web-dist/server/assets/{settings--aoatERH.js → settings-4T3nBfY8.js} +271 -39
  46. package/bundle/web-dist/server/assets/{show-BY27w9oi.js → show-CQw03iTP.js} +3 -3
  47. package/bundle/web-dist/server/assets/{tokens-DIObtTuZ.js → tokens-Di_SGPYj.js} +4 -4
  48. package/bundle/web-dist/server/assets/{use-oauth-login-flow-V3k8PSqQ.js → use-oauth-login-flow-DiYCMNAQ.js} +2 -2
  49. package/bundle/web-dist/server/entry-server.generated.js +10 -10
  50. package/dist/cli.js +93 -38
  51. package/package.json +1 -1
  52. package/skills/qgrid/references/cli-env-and-server-boot.md +34 -3
  53. package/bundle/web-dist/client/assets/index-BRRNJRgu.js +0 -78
  54. package/bundle/web-dist/client/assets/index-rdbxJinT.css +0 -1
  55. package/bundle/web-dist/client/assets/services.generated-BzwTuZRP.js +0 -1
  56. package/bundle/web-dist/client/assets/settings-B62_r1i4.js +0 -1
@@ -1,4 +1,4 @@
1
- import { B as shouldThrowError, C as fetchState, D as environmentManager, E as pendingThenable, G as Subscribable, H as timeUntilStale, I as replaceData, L as resolveEnabled, P as noop, R as resolveStaleTime, T as notifyManager, U as timeoutManager, W as focusManager, Y as __toESM, a as fetch, b as hasPreviousPage, f as require_lib, g as require_react, h as require_jsx_runtime, i as dedupeAndFlatten, j as isValidTimeout, k as hashKey, m as useQueryClient, o as useRefreshable, s as useSSEStream, v as getDefaultState, x as infiniteQueryBehavior, y as hasNextPage, z as shallowEqualObjects } from "./sd.generated-LO-h1_16.js";
1
+ import { B as resolveStaleTime, C as infiniteQueryBehavior, D as notifyManager, G as timeoutManager, H as shouldThrowError, I as noop, K as focusManager, N as isValidTimeout, O as pendingThenable, R as replaceData, S as hasPreviousPage, T as fetchState, V as shallowEqualObjects, W as timeUntilStale, Z as __toESM, _ as require_jsx_runtime, a as fetch, b as getDefaultState, c as useSSEStream, g as useQueryClient, i as dedupeAndFlatten, j as hashKey, k as environmentManager, p as require_lib, q as Subscribable, s as useRefreshable, v as require_react, x as hasNextPage, z as resolveEnabled } from "./sd.generated-CuyajYtc.js";
2
2
  //#region ../../node_modules/.pnpm/@tanstack+query-core@5.95.2/node_modules/@tanstack/query-core/build/modern/queryObserver.js
3
3
  var QueryObserver = class extends Subscribable {
4
4
  constructor(client, options) {
@@ -858,6 +858,14 @@ var SettingService;
858
858
  }
859
859
  _SettingService.triggerExpiryReminder = triggerExpiryReminder;
860
860
  _SettingService.useTriggerExpiryReminderMutation = () => useMutation({ mutationFn: (params) => triggerExpiryReminder() });
861
+ async function restartServer() {
862
+ return fetch({
863
+ method: "POST",
864
+ url: `/api/setting/restartServer`
865
+ });
866
+ }
867
+ _SettingService.restartServer = restartServer;
868
+ _SettingService.useRestartServerMutation = () => useMutation({ mutationFn: (params) => restartServer() });
861
869
  })(SettingService || (SettingService = {}));
862
870
  var RequestLogStepService;
863
871
  (function(_RequestLogStepService) {
@@ -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 { Z as __toESM, _ as require_jsx_runtime, g as useQueryClient, m as isAxiosError, o as isSonamuError, v as require_react } from "./sd.generated-CuyajYtc.js";
2
+ import { a as SettingService, c as useQuery, n as QgridService } from "./services.generated-BAGLi2r8.js";
3
+ import { t as lucidePlus } from "./plus-B8JRuCb9.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();
@@ -77,6 +77,7 @@ var GROUP_LABELS = {
77
77
  slack: "Slack 알림",
78
78
  slackConnection: "Slack 연결"
79
79
  };
80
+ var RESTART_READY_TIMEOUT_MS = 180 * 1e3;
80
81
  function maskValue(value) {
81
82
  if (!value) return "";
82
83
  if (value.length <= 12) return "•".repeat(value.length);
@@ -164,7 +165,7 @@ function SettingRow({ item }) {
164
165
  const pending = updateMutation.isPending || resetMutation.isPending;
165
166
  const value = draft ?? item.value;
166
167
  const dirty = draft !== null && draft !== item.value;
167
- const invalidate = () => queryClient.invalidateQueries({ queryKey: ["Setting", "getSettingList"] });
168
+ const invalidate = () => queryClient.invalidateQueries({ queryKey: SettingService.getSettingListQueryOptions().queryKey });
168
169
  const commit = async (next) => {
169
170
  setError(null);
170
171
  try {
@@ -301,8 +302,215 @@ function TriggerReminderButton() {
301
302
  })]
302
303
  });
303
304
  }
305
+ /**
306
+ * 재시작은 스스로 종료하고 감독자(pm2)가 다시 띄우는 것에 맡긴다. 되돌릴 수 없고 1~2분간
307
+ * 요청이 실패하므로 확인 단계를 둔다 — 무엇이 일어나는지 모달이 먼저 설명한다.
308
+ */
309
+ function RestartPanel({ supervisor }) {
310
+ const [confirming, setConfirming] = (0, import_react.useState)(false);
311
+ const [restartRequested, setRestartRequested] = (0, import_react.useState)(false);
312
+ const [restartStartedAt, setRestartStartedAt] = (0, import_react.useState)(null);
313
+ const [sawUnavailable, setSawUnavailable] = (0, import_react.useState)(false);
314
+ const [completing, setCompleting] = (0, import_react.useState)(false);
315
+ const [error, setError] = (0, import_react.useState)(null);
316
+ const completingRef = (0, import_react.useRef)(false);
317
+ const restartAttemptRef = (0, import_react.useRef)(0);
318
+ const queryClient = useQueryClient();
319
+ const mutation = SettingService.useRestartServerMutation();
320
+ const resetMutation = mutation.reset;
321
+ const healthQuery = useQuery({
322
+ ...QgridService.healthQueryOptions(),
323
+ enabled: restartRequested && !completing,
324
+ refetchInterval: restartRequested && !completing ? 1e3 : false,
325
+ refetchIntervalInBackground: true,
326
+ retry: false
327
+ });
328
+ (0, import_react.useEffect)(() => {
329
+ if (!restartRequested || restartStartedAt === null || completing) return;
330
+ const remaining = Math.max(0, RESTART_READY_TIMEOUT_MS - (Date.now() - restartStartedAt));
331
+ const timeout = window.setTimeout(() => {
332
+ restartAttemptRef.current += 1;
333
+ resetMutation();
334
+ setConfirming(false);
335
+ setRestartRequested(false);
336
+ setRestartStartedAt(null);
337
+ setSawUnavailable(false);
338
+ setError("재시작 후 3분 안에 서버 준비를 확인하지 못했습니다. 상태를 확인한 뒤 다시 시도하세요.");
339
+ }, remaining);
340
+ return () => window.clearTimeout(timeout);
341
+ }, [
342
+ completing,
343
+ resetMutation,
344
+ restartRequested,
345
+ restartStartedAt
346
+ ]);
347
+ (0, import_react.useEffect)(() => {
348
+ if (!restartRequested || restartStartedAt === null) return;
349
+ const currentAttemptError = healthQuery.isError && healthQuery.errorUpdatedAt >= restartStartedAt;
350
+ const currentAttemptData = healthQuery.isSuccess && healthQuery.dataUpdatedAt >= restartStartedAt;
351
+ if (currentAttemptError || currentAttemptData && healthQuery.data.ready === false) {
352
+ setSawUnavailable(true);
353
+ return;
354
+ }
355
+ if (!sawUnavailable || !currentAttemptData || healthQuery.data.ready !== true || completingRef.current) return;
356
+ completingRef.current = true;
357
+ setCompleting(true);
358
+ queryClient.invalidateQueries({ queryKey: SettingService.getSettingListQueryOptions().queryKey }).then(() => {
359
+ restartAttemptRef.current += 1;
360
+ resetMutation();
361
+ setConfirming(false);
362
+ setRestartRequested(false);
363
+ setRestartStartedAt(null);
364
+ setSawUnavailable(false);
365
+ }).catch((cause) => {
366
+ restartAttemptRef.current += 1;
367
+ resetMutation();
368
+ setConfirming(false);
369
+ setRestartRequested(false);
370
+ setRestartStartedAt(null);
371
+ setSawUnavailable(false);
372
+ setError(`서버는 준비됐지만 설정을 새로고침하지 못했습니다: ${cause.message}`);
373
+ }).finally(() => {
374
+ completingRef.current = false;
375
+ setCompleting(false);
376
+ });
377
+ }, [
378
+ healthQuery.dataUpdatedAt,
379
+ healthQuery.data?.ready,
380
+ healthQuery.errorUpdatedAt,
381
+ healthQuery.isError,
382
+ healthQuery.isSuccess,
383
+ queryClient,
384
+ resetMutation,
385
+ restartStartedAt,
386
+ restartRequested,
387
+ sawUnavailable
388
+ ]);
389
+ const restart = async () => {
390
+ const attempt = restartAttemptRef.current + 1;
391
+ restartAttemptRef.current = attempt;
392
+ setError(null);
393
+ setSawUnavailable(false);
394
+ setCompleting(false);
395
+ completingRef.current = false;
396
+ setRestartRequested(true);
397
+ setRestartStartedAt(Date.now());
398
+ try {
399
+ await mutation.mutateAsync();
400
+ } catch (e) {
401
+ if (restartAttemptRef.current !== attempt) return;
402
+ if (isSonamuError(e) ? e.code >= 400 && e.code < 500 : isAxiosError(e) && e.response !== void 0 && e.response.status >= 400 && e.response.status < 500) {
403
+ restartAttemptRef.current += 1;
404
+ resetMutation();
405
+ setConfirming(false);
406
+ setRestartRequested(false);
407
+ setRestartStartedAt(null);
408
+ setError(e.message || "재시작 요청이 거부되었습니다.");
409
+ }
410
+ }
411
+ };
412
+ const closeConfirmation = () => {
413
+ if (restartRequested) return;
414
+ setConfirming(false);
415
+ setError(null);
416
+ };
417
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
418
+ className: "panel overflow-hidden",
419
+ children: [
420
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
421
+ className: "panel-header px-4 py-2.5 sm:px-5",
422
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
423
+ className: "text-[13px] font-medium text-sand-700",
424
+ children: "서버 제어"
425
+ })
426
+ }),
427
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
428
+ className: "px-4 py-3 sm:px-5 flex items-start justify-between gap-4",
429
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
430
+ className: "min-w-0",
431
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
432
+ className: "text-[13px] text-sand-800",
433
+ children: "qgrid 재시작"
434
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
435
+ className: "mt-0.5 text-[11px] text-sand-500",
436
+ children: supervisor ? "재시작 필요 설정을 반영하고 qgrid·Codex·Claude CLI를 확인합니다" : "로컬 실행 환경에서는 재시작할 수 없습니다"
437
+ })]
438
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
439
+ type: "button",
440
+ disabled: !supervisor || restartRequested || mutation.isPending,
441
+ onClick: () => setConfirming(true),
442
+ 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",
443
+ children: "재시작"
444
+ })]
445
+ }),
446
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
447
+ className: "px-4 pb-3 sm:px-5 text-[11px] text-red-500",
448
+ children: error
449
+ }),
450
+ confirming && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
451
+ className: "fixed inset-0 z-50 flex items-start justify-center pt-[15vh]",
452
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
453
+ className: "absolute inset-0 bg-sand-900/8 backdrop-blur-sm",
454
+ onClick: closeConfirmation,
455
+ onKeyDown: () => {}
456
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
457
+ className: "relative panel shadow-xl w-full max-w-sm mx-4",
458
+ children: [
459
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
460
+ className: "px-5 py-4 border-b border-sand-100/60",
461
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
462
+ className: "text-base font-medium text-sand-900",
463
+ children: restartRequested ? mutation.isPending ? "재시작 요청 중" : "서버 재연결 중" : "qgrid 재시작"
464
+ })
465
+ }),
466
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
467
+ className: "px-5 py-4 space-y-2",
468
+ children: [
469
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
470
+ className: "text-[13px] text-sand-700",
471
+ children: [
472
+ "워커가 다시 뜰 때까지 ",
473
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", {
474
+ className: "text-sand-900",
475
+ children: "1~2분간 요청이 실패"
476
+ }),
477
+ "합니다."
478
+ ]
479
+ }),
480
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
481
+ className: "text-[12px] text-sand-500",
482
+ children: "서버를 종료하면 모니터링 도구가 다시 띄웁니다. 이때 재시작 필요 설정을 반영하고, qgrid와 Codex CLI·Claude Code의 최신 버전을 확인합니다."
483
+ }),
484
+ restartRequested && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
485
+ className: "text-[12px] font-medium text-sienna-600",
486
+ children: mutation.isPending ? "종료 요청을 처리하고 있습니다…" : sawUnavailable ? "새 서버가 준비될 때까지 연결을 확인하고 있습니다…" : "서버 종료를 기다리고 있습니다…"
487
+ })
488
+ ]
489
+ }),
490
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
491
+ className: "px-5 py-3 border-t border-sand-100/60 flex justify-end gap-2",
492
+ children: [!restartRequested && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
493
+ type: "button",
494
+ onClick: closeConfirmation,
495
+ className: "px-3 py-1.5 text-[12px] rounded-lg text-sand-600 hover:bg-sand-100 transition-colors duration-150",
496
+ children: "취소"
497
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
498
+ type: "button",
499
+ disabled: restartRequested || mutation.isPending,
500
+ onClick: () => void restart(),
501
+ 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",
502
+ children: restartRequested ? mutation.isPending ? "요청 중" : "재연결 중" : "재시작"
503
+ })]
504
+ })
505
+ ]
506
+ })]
507
+ })
508
+ ]
509
+ });
510
+ }
304
511
  function SettingsPanel() {
305
- const { data, isLoading } = SettingService.useSettingList();
512
+ const settingsQuery = SettingService.useSettingList();
513
+ const { data, isLoading } = settingsQuery;
306
514
  if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
307
515
  className: "space-y-4",
308
516
  children: [0, 1].map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -323,51 +531,75 @@ function SettingsPanel() {
323
531
  })]
324
532
  }, i))
325
533
  });
534
+ if (settingsQuery.isError && !data) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
535
+ className: "panel px-5 py-6 text-center",
536
+ children: [
537
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
538
+ className: "text-[13px] font-medium text-sand-800",
539
+ children: "설정을 불러오지 못했습니다"
540
+ }),
541
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
542
+ className: "mt-1 text-[11px] text-red-500",
543
+ children: settingsQuery.error.message
544
+ }),
545
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
546
+ type: "button",
547
+ onClick: () => void settingsQuery.refetch(),
548
+ disabled: settingsQuery.isFetching,
549
+ 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",
550
+ children: settingsQuery.isFetching ? "다시 불러오는 중" : "다시 시도"
551
+ })
552
+ ]
553
+ });
326
554
  const settings = data?.settings ?? [];
327
555
  const runtime = data?.runtime ?? [];
328
556
  const groups = [...new Set(settings.map((s) => s.group))];
329
557
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
330
558
  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", {
559
+ children: [
560
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
358
561
  className: "panel overflow-hidden",
359
562
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
360
563
  className: "panel-header flex items-center justify-between px-4 py-2.5 sm:px-5",
361
564
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
362
565
  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))
566
+ children: "환경"
567
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
568
+ className: "text-[11px] text-sand-400",
569
+ children: "기동 시점 값"
570
+ })]
571
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dl", {
572
+ className: "px-4 py-3 sm:px-5 grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-1.5",
573
+ children: runtime.map((info) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
574
+ className: "flex items-baseline justify-between gap-3 min-w-0",
575
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
576
+ className: "text-[11px] text-sand-500 shrink-0",
577
+ children: info.label
578
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
579
+ className: "text-[12px] font-mono text-sand-700 truncate",
580
+ children: info.value
581
+ })]
582
+ }, info.label))
368
583
  })]
369
- }, group);
370
- })]
584
+ }),
585
+ groups.map((group) => {
586
+ const items = settings.filter((s) => s.group === group);
587
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
588
+ className: "panel overflow-hidden",
589
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
590
+ className: "panel-header flex items-center justify-between px-4 py-2.5 sm:px-5",
591
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
592
+ className: "text-[13px] font-medium text-sand-700",
593
+ children: GROUP_LABELS[group] ?? group
594
+ }), group === "slack" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriggerReminderButton, {})]
595
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
596
+ className: "divide-y divide-sand-100/80",
597
+ children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingRow, { item }, item.key))
598
+ })]
599
+ }, group);
600
+ }),
601
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RestartPanel, { supervisor: data?.supervisor ?? null })
602
+ ]
371
603
  });
372
604
  }
373
605
  //#endregion
@@ -1,6 +1,6 @@
1
- import { K as __commonJSMin, Y as __toESM, g as require_react, h as require_jsx_runtime, q as __exportAll } from "./sd.generated-LO-h1_16.js";
2
- import { i as RequestLogStepService, r as RequestLogService } from "./services.generated-CZIChgHV.js";
3
- import { a as Link } from "./lazyRouteComponent-Ws7I6fL-.js";
1
+ import { J as __commonJSMin, Y as __exportAll, Z as __toESM, _ as require_jsx_runtime, v as require_react } from "./sd.generated-CuyajYtc.js";
2
+ import { i as RequestLogStepService, r as RequestLogService } from "./services.generated-BAGLi2r8.js";
3
+ import { a as Link } from "./lazyRouteComponent-C-N6sd5N.js";
4
4
  import { t as Route } from "../entry-server.generated.js";
5
5
  import { n as formatMicroUsd, t as cacheHitRate } from "./cost-DzjfVtUr.js";
6
6
  import minpath from "node:path";
@@ -1,8 +1,8 @@
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 { n as QgridService, o as TokenService } from "./services.generated-CZIChgHV.js";
1
+ import { Z as __toESM, _ as require_jsx_runtime, g as useQueryClient, v as require_react } from "./sd.generated-CuyajYtc.js";
2
+ import { n as QgridService, o as TokenService } from "./services.generated-BAGLi2r8.js";
3
3
  import { i as Q, o as lucideKeyRound } from "../entry-server.generated.js";
4
- import { n as OAuthCodeEntry, r as useUpdateTokenMutation, t as useOAuthLoginFlow } from "./use-oauth-login-flow-V3k8PSqQ.js";
5
- import { t as lucidePlus } from "./plus-BISXNqRU.js";
4
+ import { n as OAuthCodeEntry, r as useUpdateTokenMutation, t as useOAuthLoginFlow } from "./use-oauth-login-flow-DiYCMNAQ.js";
5
+ import { t as lucidePlus } from "./plus-B8JRuCb9.js";
6
6
  //#region src/components/qgrid/AddTokenModal.tsx
7
7
  var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
8
8
  var import_jsx_runtime = require_jsx_runtime();
@@ -1,5 +1,5 @@
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 { n as QgridService, s as useMutation } from "./services.generated-CZIChgHV.js";
1
+ import { Z as __toESM, _ as require_jsx_runtime, g as useQueryClient, v as require_react } from "./sd.generated-CuyajYtc.js";
2
+ import { n as QgridService, s as useMutation } from "./services.generated-BAGLi2r8.js";
3
3
  import { i as Q } from "../entry-server.generated.js";
4
4
  //#region src/services/token/use-update-token-mutation.ts
5
5
  function useUpdateTokenMutation() {
@@ -1,6 +1,6 @@
1
- import { A as hashQueryKeyByOptions, F as partialMatchKey, G as Subscribable, J as __require, K as __commonJSMin, M as matchMutation, N as matchQuery, O as functionalUpdate, P as noop$1, R as resolveStaleTime, S as Query, T as notifyManager, V as skipToken, W as focusManager, Y as __toESM, _ as Mutation, c as _enum, d as string, g as require_react, h as require_jsx_runtime, k as hashKey, l as number, n as SUPPORTED_LOCALES, p as QueryClientProvider, r as setLocale, t as SD, u as object, w as onlineManager, x as infiniteQueryBehavior } from "./assets/sd.generated-LO-h1_16.js";
2
- import { A as decodePath, C as findRouteMatch, D as createLRUCache, E as processRouteTree, F as last, I as replaceEqualDeep, M as findLast, N as functionalUpdate$1, O as invariant, P as isPromise, S as findFlatMatch, T as processRouteMasks, _ as interpolatePath, a as Link$1, b as trimPath, c as usePrevious$1, d as useRouter, f as getRouterContext, g as cleanPath, h as isNotFound, i as createRootRouteWithContext, j as deepEqual$2, k as createControlledPromise, l as matchContext, m as rootRouteId, n as createFileRoute, p as warning, r as createLazyFileRoute, s as useLayoutEffect$3, t as lazyRouteComponent, u as useRouterState, v as joinPaths, w as findSingleMatch, x as trimPathRight, y as resolvePath } from "./assets/lazyRouteComponent-Ws7I6fL-.js";
3
- import { n as require_shim, t as require_react_dom } from "./assets/react-dom-D7duWOwf.js";
1
+ import { A as functionalUpdate, B as resolveStaleTime, C as infiniteQueryBehavior, D as notifyManager, E as onlineManager, F as matchQuery, I as noop$1, J as __commonJSMin, K as focusManager, L as partialMatchKey, M as hashQueryKeyByOptions, P as matchMutation, U as skipToken, X as __require, Z as __toESM, _ as require_jsx_runtime, d as object, f as string, h as QueryClientProvider, j as hashKey, l as _enum, n as SUPPORTED_LOCALES, q as Subscribable, r as setLocale, t as SD, u as number, v as require_react, w as Query, y as Mutation } from "./assets/sd.generated-CuyajYtc.js";
2
+ import { A as decodePath, C as findRouteMatch, D as createLRUCache, E as processRouteTree, F as last, I as replaceEqualDeep, M as findLast, N as functionalUpdate$1, O as invariant, P as isPromise, S as findFlatMatch, T as processRouteMasks, _ as interpolatePath, a as Link$1, b as trimPath, c as usePrevious$1, d as useRouter, f as getRouterContext, g as cleanPath, h as isNotFound, i as createRootRouteWithContext, j as deepEqual$2, k as createControlledPromise, l as matchContext, m as rootRouteId, n as createFileRoute, p as warning, r as createLazyFileRoute, s as useLayoutEffect$3, t as lazyRouteComponent, u as useRouterState, v as joinPaths, w as findSingleMatch, x as trimPathRight, y as resolvePath } from "./assets/lazyRouteComponent-C-N6sd5N.js";
3
+ import { n as require_shim, t as require_react_dom } from "./assets/react-dom-ByF9tqTB.js";
4
4
  //#region ../../node_modules/.pnpm/@tanstack+query-core@5.95.2/node_modules/@tanstack/query-core/build/modern/hydration.js
5
5
  function defaultTransformerFn(data) {
6
6
  return data;
@@ -22768,7 +22768,7 @@ function BottomNav() {
22768
22768
  }
22769
22769
  //#endregion
22770
22770
  //#region src/components/Sidebar.tsx
22771
- var qgridVersion = "2.6.10";
22771
+ var qgridVersion = "2.6.11";
22772
22772
  function Sidebar({ className }) {
22773
22773
  const pathname = useRouterState({ select: (s) => s.location.pathname });
22774
22774
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
@@ -44649,15 +44649,15 @@ function RootComponent() {
44649
44649
  }
44650
44650
  //#endregion
44651
44651
  //#region src/routes/tokens.tsx
44652
- var $$splitComponentImporter$5 = () => import("./assets/tokens-DIObtTuZ.js");
44652
+ var $$splitComponentImporter$5 = () => import("./assets/tokens-Di_SGPYj.js");
44653
44653
  var Route$5 = createFileRoute("/tokens")({ component: lazyRouteComponent($$splitComponentImporter$5, "component") });
44654
44654
  //#endregion
44655
44655
  //#region src/routes/settings.tsx
44656
- var $$splitComponentImporter$4 = () => import("./assets/settings--aoatERH.js");
44656
+ var $$splitComponentImporter$4 = () => import("./assets/settings-4T3nBfY8.js");
44657
44657
  var Route$4 = createFileRoute("/settings")({ component: lazyRouteComponent($$splitComponentImporter$4, "component") });
44658
44658
  //#endregion
44659
44659
  //#region src/routes/monit.tsx
44660
- var $$splitComponentImporter$3 = () => import("./assets/monit-C8gx9Sar.js");
44660
+ var $$splitComponentImporter$3 = () => import("./assets/monit-Bb6QfT9V.js");
44661
44661
  var monitSearchSchema = object({ level: _enum([
44662
44662
  "all",
44663
44663
  "scaling",
@@ -44670,7 +44670,7 @@ var Route$3 = createFileRoute("/monit")({
44670
44670
  });
44671
44671
  //#endregion
44672
44672
  //#region src/routes/logs.tsx
44673
- var $$splitComponentImporter$2 = () => import("./assets/logs-C1rxLh8H.js");
44673
+ var $$splitComponentImporter$2 = () => import("./assets/logs-CVkeSr1S.js");
44674
44674
  var logsSearchSchema = object({
44675
44675
  token: string().optional(),
44676
44676
  project: string().optional(),
@@ -44684,11 +44684,11 @@ var Route$2 = createFileRoute("/logs")({
44684
44684
  });
44685
44685
  //#endregion
44686
44686
  //#region src/routes/index.tsx
44687
- var $$splitComponentImporter$1 = () => import("./assets/routes-ZW_Ih5Em.js");
44687
+ var $$splitComponentImporter$1 = () => import("./assets/routes-Dnm-Eti6.js");
44688
44688
  var Route$1 = createFileRoute("/")({ component: lazyRouteComponent($$splitComponentImporter$1, "component") });
44689
44689
  //#endregion
44690
44690
  //#region src/routes/requests/show.tsx
44691
- var $$splitComponentImporter = () => import("./assets/show-BY27w9oi.js");
44691
+ var $$splitComponentImporter = () => import("./assets/show-CQw03iTP.js");
44692
44692
  var showSearchSchema = object({ id: number() });
44693
44693
  var Route = createFileRoute("/requests/show")({
44694
44694
  validateSearch: showSearchSchema,
package/dist/cli.js CHANGED
@@ -27,6 +27,68 @@ function applyQgridDatabaseEnv(env) {
27
27
  }
28
28
  //#endregion
29
29
  //#region src/self-update.ts
30
+ const SELF_UPDATE_CHECK_TIMEOUT_MS = 15e3;
31
+ const SELF_UPDATE_INSTALL_TIMEOUT_MS = 12e4;
32
+ function parseVersion(output) {
33
+ return output?.match(/\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?/)?.[0] ?? null;
34
+ }
35
+ /** qgrid 자체 업데이트만 조율한다. 계속 기동할지, 새 바이너리 종료 코드를 전달할지 반환한다. */
36
+ function runSelfUpdate(params, dependencies) {
37
+ const { packageName, currentVersion, args, env, userAgent } = params;
38
+ const { latestVersion, commandPath, commandVersion, spawn, log, warn, error } = dependencies;
39
+ let latest;
40
+ try {
41
+ latest = latestVersion(packageName);
42
+ } catch (cause) {
43
+ warn(`Warning: failed to check for qgrid-cli updates: ${cause.message}`);
44
+ warn(`Continuing with qgrid-cli ${currentVersion}.`);
45
+ return { kind: "continue" };
46
+ }
47
+ if (latest === currentVersion) return { kind: "continue" };
48
+ const packageManager = detectSelfUpdatePackageManager(commandPath("qgrid"), userAgent);
49
+ const globalBinDir = packageManager === "pnpm" ? dependencies.pnpmGlobalBinDir() : null;
50
+ const updateEnv = withPrependedPath(env, globalBinDir);
51
+ const installCmd = selfUpdateInstallCommand(packageManager, packageName, latest);
52
+ log(`Updating qgrid-cli: ${currentVersion} → ${latest}`);
53
+ try {
54
+ if (spawn(installCmd.command, installCmd.args, {
55
+ env: updateEnv,
56
+ stdio: "inherit",
57
+ timeout: 12e4
58
+ }).status !== 0) {
59
+ warn(`Warning: failed to update qgrid-cli to ${latest}.`);
60
+ warn(`Continuing with qgrid-cli ${currentVersion}; fix the global ${packageManager} install.`);
61
+ return { kind: "continue" };
62
+ }
63
+ } catch (cause) {
64
+ warn(`Warning: failed to update qgrid-cli to ${latest}: ${cause.message}`);
65
+ warn(`Continuing with qgrid-cli ${currentVersion}; fix the global ${packageManager} install.`);
66
+ return { kind: "continue" };
67
+ }
68
+ const restartCommand = resolveQgridRestartCommand(packageManager, globalBinDir);
69
+ const updatedVersion = parseVersion(commandVersion(restartCommand, updateEnv));
70
+ if (updatedVersion !== latest) {
71
+ warn(`Warning: updated qgrid-cli verification failed. Expected ${latest}, found ${updatedVersion ?? "unknown"}.`);
72
+ warn(`Continuing with qgrid-cli ${currentVersion}; check PATH and global ${packageManager} bin configuration.`);
73
+ return { kind: "continue" };
74
+ }
75
+ log("Updated. Restarting...\n");
76
+ const restarted = spawn(restartCommand, args.concat("--skip-update"), {
77
+ env: updateEnv,
78
+ stdio: "inherit"
79
+ });
80
+ if (restarted.status === null) {
81
+ error("Failed to restart qgrid-cli after updating.");
82
+ return {
83
+ kind: "exit",
84
+ exitCode: 1
85
+ };
86
+ }
87
+ return {
88
+ kind: "exit",
89
+ exitCode: restarted.status
90
+ };
91
+ }
30
92
  function detectSelfUpdatePackageManager(qgridPath, userAgent = process.env.npm_config_user_agent) {
31
93
  return userAgent?.includes("pnpm") || qgridPath.includes("pnpm") ? "pnpm" : "npm";
32
94
  }
@@ -34,7 +96,10 @@ function parsePnpmGlobalBinDir(output) {
34
96
  return output.match(/configured global bin directory "([^"]+)"/)?.[1] ?? output.match(/global bin directory "([^"]+)"/)?.[1] ?? null;
35
97
  }
36
98
  function pnpmGlobalBinDir() {
37
- const result = spawnSync("pnpm", ["bin", "-g"], { encoding: "utf-8" });
99
+ const result = spawnSync("pnpm", ["bin", "-g"], {
100
+ encoding: "utf-8",
101
+ timeout: SELF_UPDATE_CHECK_TIMEOUT_MS
102
+ });
38
103
  if (result.status === 0) {
39
104
  const value = result.stdout.trim();
40
105
  return value.length > 0 ? value : null;
@@ -94,7 +159,8 @@ function commandVersion(command, env = process.env) {
94
159
  return execFileSync(command, ["--version"], {
95
160
  encoding: "utf-8",
96
161
  env,
97
- stdio: "pipe"
162
+ stdio: "pipe",
163
+ timeout: SELF_UPDATE_CHECK_TIMEOUT_MS
98
164
  }).trim();
99
165
  } catch {
100
166
  return null;
@@ -107,17 +173,16 @@ function packageLatestVersion(packageName) {
107
173
  "version"
108
174
  ], {
109
175
  encoding: "utf-8",
110
- stdio: "pipe"
176
+ stdio: "pipe",
177
+ timeout: SELF_UPDATE_CHECK_TIMEOUT_MS
111
178
  }).trim();
112
179
  }
113
- function parseVersion(output) {
114
- return output?.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/)?.[0] ?? null;
115
- }
116
180
  function commandPath(command) {
117
181
  try {
118
182
  return execFileSync("which", [command], {
119
183
  encoding: "utf-8",
120
- stdio: "pipe"
184
+ stdio: "pipe",
185
+ timeout: SELF_UPDATE_CHECK_TIMEOUT_MS
121
186
  }).trim();
122
187
  } catch {
123
188
  return "";
@@ -132,7 +197,10 @@ function installRuntimeCliDependency(dep) {
132
197
  "i",
133
198
  "-g",
134
199
  `${dep.packageName}@latest`
135
- ], { stdio: "inherit" });
200
+ ], {
201
+ stdio: "inherit",
202
+ timeout: SELF_UPDATE_INSTALL_TIMEOUT_MS
203
+ });
136
204
  }
137
205
  function ensureLatestRuntimeCliDependencies() {
138
206
  for (const dep of RUNTIME_CLI_DEPENDENCIES) {
@@ -177,36 +245,23 @@ program.name("qgrid").version(pkg.version).description("Qgrid — LLM subscripti
177
245
  }
178
246
  } catch {}
179
247
  if (!opts.skipUpdate) {
180
- const latest = packageLatestVersion("@cartanova/qgrid-cli");
181
- if (latest !== pkg.version) {
182
- const packageManager = detectSelfUpdatePackageManager(commandPath("qgrid"));
183
- const globalBinDir = packageManager === "pnpm" ? pnpmGlobalBinDir() : null;
184
- const updateEnv = withPrependedPath(process.env, globalBinDir);
185
- const installCmd = selfUpdateInstallCommand(packageManager, "@cartanova/qgrid-cli", latest);
186
- console.log(`Updating qgrid-cli: ${pkg.version} → ${latest}`);
187
- const updated = spawnSync(installCmd.command, installCmd.args, {
188
- env: updateEnv,
189
- stdio: "inherit"
190
- });
191
- if (updated.status !== 0) {
192
- console.error(`Failed to update qgrid-cli to ${latest}.`);
193
- console.error(`Fix the global ${packageManager} installation and run qgrid again.`);
194
- process.exit(updated.status ?? 1);
195
- }
196
- const restartCommand = resolveQgridRestartCommand(packageManager, globalBinDir);
197
- const updatedVersion = parseVersion(commandVersion(restartCommand, updateEnv));
198
- if (updatedVersion !== latest) {
199
- console.error(`Failed to run updated qgrid-cli. Expected ${latest}, found ${updatedVersion ?? "unknown"}.`);
200
- console.error(`Check PATH and global ${packageManager} bin configuration.`);
201
- process.exit(1);
202
- }
203
- console.log("Updated. Restarting...\n");
204
- const restarted = spawnSync(restartCommand, process.argv.slice(2).concat("--skip-update"), {
205
- env: updateEnv,
206
- stdio: "inherit"
207
- });
208
- process.exit(restarted.status ?? 0);
209
- }
248
+ const outcome = runSelfUpdate({
249
+ packageName: "@cartanova/qgrid-cli",
250
+ currentVersion: pkg.version,
251
+ args: process.argv.slice(2),
252
+ env: process.env,
253
+ userAgent: process.env.npm_config_user_agent
254
+ }, {
255
+ latestVersion: packageLatestVersion,
256
+ commandPath,
257
+ commandVersion,
258
+ pnpmGlobalBinDir,
259
+ spawn: (command, args, options) => spawnSync(command, args, options),
260
+ log: console.log,
261
+ warn: console.warn,
262
+ error: console.error
263
+ });
264
+ if (outcome.kind === "exit") process.exit(outcome.exitCode);
210
265
  }
211
266
  ensureLatestRuntimeCliDependencies();
212
267
  if (opts.db) {