@cartanova/qgrid-cli 2.6.6 → 2.6.8
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.
- package/bundle/dist/application/monit/monit.frame.js +2 -2
- package/bundle/dist/application/qgrid/expired-token-reminder.js +37 -16
- package/bundle/dist/application/qgrid/oauth.js +1 -1
- package/bundle/dist/application/qgrid/qgrid.frame.js +3 -3
- package/bundle/dist/application/qgrid/slack-user-map.js +5 -14
- package/bundle/dist/application/qgrid/token-death.js +3 -2
- package/bundle/dist/application/qgrid/tool-emulation.js +1 -1
- package/bundle/dist/application/queries.generated.js +8 -2
- package/bundle/dist/application/request-log/request-log.model.js +2 -2
- package/bundle/dist/application/request-log-step/request-log-step.model.js +1 -1
- package/bundle/dist/application/setting/setting.model.js +236 -0
- package/bundle/dist/application/setting/setting.schema.js +157 -0
- package/bundle/dist/application/setting/setting.store.js +66 -0
- package/bundle/dist/application/setting/setting.types.js +48 -0
- package/bundle/dist/application/sonamu.generated.js +32 -2
- package/bundle/dist/application/sonamu.generated.sso.js +12 -2
- package/bundle/dist/application/token/token.model.js +1 -1
- package/bundle/dist/i18n/en.js +3 -1
- package/bundle/dist/i18n/ko.js +3 -1
- package/bundle/dist/i18n/sd.generated.js +9 -1
- package/bundle/dist/migrations/20260808194708_create__settings.js +24 -0
- package/bundle/dist/sonamu.config.js +8 -2
- package/bundle/dist/utils/providers/openai/openai-worker-pool-config.js +21 -6
- package/bundle/dist/utils/quiet-hours.js +43 -0
- package/bundle/dist/utils/slack-notify.js +12 -5
- package/bundle/src/application/qgrid/expired-token-reminder.test.ts +135 -13
- package/bundle/src/application/qgrid/expired-token-reminder.ts +60 -21
- package/bundle/src/application/qgrid/slack-user-map.test.ts +23 -0
- package/bundle/src/application/qgrid/slack-user-map.ts +4 -14
- package/bundle/src/application/qgrid/token-death.ts +2 -0
- package/bundle/src/application/queries.generated.ts +21 -1
- package/bundle/src/application/setting/setting.entity.json +59 -0
- package/bundle/src/application/setting/setting.model.ts +263 -0
- package/bundle/src/application/setting/setting.schema.test.ts +80 -0
- package/bundle/src/application/setting/setting.schema.ts +174 -0
- package/bundle/src/application/setting/setting.store.test.ts +87 -0
- package/bundle/src/application/setting/setting.store.ts +85 -0
- package/bundle/src/application/setting/setting.types.ts +51 -0
- package/bundle/src/application/sonamu.generated.http +47 -0
- package/bundle/src/application/sonamu.generated.sso.ts +21 -0
- package/bundle/src/application/sonamu.generated.ts +51 -0
- package/bundle/src/i18n/en.ts +2 -0
- package/bundle/src/i18n/ko.ts +2 -0
- package/bundle/src/i18n/sd.generated.ts +8 -0
- package/bundle/src/migrations/20260808194708_create__settings.ts +19 -0
- package/bundle/src/sonamu.config.ts +10 -0
- package/bundle/src/utils/providers/openai/openai-dispatcher.test.ts +12 -0
- package/bundle/src/utils/providers/openai/openai-worker-pool-config.ts +43 -5
- package/bundle/src/utils/quiet-hours.ts +40 -0
- package/bundle/src/utils/slack-notify.test.ts +88 -20
- package/bundle/src/utils/slack-notify.ts +24 -4
- package/bundle/web-dist/client/assets/index-7SZyiqAx.css +1 -0
- package/bundle/web-dist/client/assets/index-DrsXE13r.js +78 -0
- package/bundle/web-dist/client/assets/{logs-CvmI4KuC.js → logs-e4-6aYnO.js} +1 -1
- package/bundle/web-dist/client/assets/{monit-CUueNzoL.js → monit-eOAlYTK9.js} +1 -1
- package/bundle/web-dist/client/assets/plus-p4vtg6LN.js +1 -0
- package/bundle/web-dist/client/assets/{routes-CAfjRsMY.js → routes-Qie4ogtJ.js} +2 -2
- package/bundle/web-dist/client/assets/{sd.generated-DYdwSB2I.js → sd.generated-Bwa7rses.js} +1 -1
- package/bundle/web-dist/client/assets/services.generated-T4ctVcia.js +1 -0
- package/bundle/web-dist/client/assets/settings-CTVZpAAg.js +1 -0
- package/bundle/web-dist/client/assets/{show-C5vKpp5z.js → show-60iOOM-e.js} +1 -1
- package/bundle/web-dist/client/assets/tokens-DdDccGlX.js +1 -0
- package/bundle/web-dist/client/assets/{use-oauth-login-flow-B3o4CXDW.js → use-oauth-login-flow-0ZGq74bZ.js} +1 -1
- package/bundle/web-dist/client/index.html +3 -3
- package/bundle/web-dist/server/assets/{lazyRouteComponent-BaZaveNc.js → lazyRouteComponent-Ws7I6fL-.js} +2 -1
- package/bundle/web-dist/server/assets/{logs-DMOw3SjD.js → logs-DmkzGxGU.js} +4 -4
- package/bundle/web-dist/server/assets/{monit-UmcqHroX.js → monit-DyKLJX4s.js} +3 -2
- package/bundle/web-dist/server/assets/plus-BISXNqRU.js +19 -0
- package/bundle/web-dist/server/assets/react-dom-D7duWOwf.js +462 -0
- package/bundle/web-dist/server/assets/{routes-BfoQmYud.js → routes-g62E9xmC.js} +5 -4
- package/bundle/web-dist/server/assets/{sd.generated-B0xqnHEo.js → sd.generated-LO-h1_16.js} +13 -461
- package/bundle/web-dist/server/assets/{services.generated-DqAKskAS.js → services.generated-DADr9OGH.js} +111 -2
- package/bundle/web-dist/server/assets/settings-CMJKayff.js +332 -0
- package/bundle/web-dist/server/assets/{show-C-oSdzHX.js → show-BGAiCPGq.js} +4 -4
- package/bundle/web-dist/server/assets/{tokens-Dkd_uXEk.js → tokens-DmhIfVct.js} +6 -21
- package/bundle/web-dist/server/assets/{use-oauth-login-flow-YM1nsd-H.js → use-oauth-login-flow-B6MY65IN.js} +2 -2
- package/bundle/web-dist/server/entry-server.generated.js +54 -18
- package/package.json +1 -1
- package/skills/qgrid/references/cli-env-and-server-boot.md +14 -2
- package/skills/qgrid/references/openai-codex-runtime.md +1 -1
- package/skills/qgrid/references/sonamu-api-web-flow.md +2 -0
- package/skills/qgrid/references/token-auth-quota-lifecycle.md +2 -1
- package/bundle/web-dist/client/assets/index-B4GlN1IX.css +0 -1
- package/bundle/web-dist/client/assets/index-qs8K8bOv.js +0 -78
- package/bundle/web-dist/client/assets/services.generated-BsAvotjG.js +0 -1
- package/bundle/web-dist/client/assets/tokens-C9KKP2Yq.js +0 -1
- /package/bundle/web-dist/server/assets/{cost-CSH_t17I.js → cost-DzjfVtUr.js} +0 -0
|
@@ -0,0 +1,332 @@
|
|
|
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-DADr9OGH.js";
|
|
3
|
+
import { t as lucidePlus } from "./plus-BISXNqRU.js";
|
|
4
|
+
//#region ~icons/lucide/eye.jsx
|
|
5
|
+
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
6
|
+
var import_jsx_runtime = require_jsx_runtime();
|
|
7
|
+
var lucideEye = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
|
|
8
|
+
viewBox: "0 0 24 24",
|
|
9
|
+
width: "1.2em",
|
|
10
|
+
height: "1.2em",
|
|
11
|
+
...props,
|
|
12
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", {
|
|
13
|
+
fill: "none",
|
|
14
|
+
stroke: "currentColor",
|
|
15
|
+
strokeLinecap: "round",
|
|
16
|
+
strokeLinejoin: "round",
|
|
17
|
+
strokeWidth: 2,
|
|
18
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2.062 12.348a1 1 0 0 1 0-.696a10.75 10.75 0 0 1 19.876 0a1 1 0 0 1 0 .696a10.75 10.75 0 0 1-19.876 0" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
|
|
19
|
+
cx: 12,
|
|
20
|
+
cy: 12,
|
|
21
|
+
r: 3
|
|
22
|
+
})]
|
|
23
|
+
})
|
|
24
|
+
});
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region ~icons/lucide/eye-off.jsx
|
|
27
|
+
var lucideEyeOff = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
|
|
28
|
+
viewBox: "0 0 24 24",
|
|
29
|
+
width: "1.2em",
|
|
30
|
+
height: "1.2em",
|
|
31
|
+
...props,
|
|
32
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", {
|
|
33
|
+
fill: "none",
|
|
34
|
+
stroke: "currentColor",
|
|
35
|
+
strokeLinecap: "round",
|
|
36
|
+
strokeLinejoin: "round",
|
|
37
|
+
strokeWidth: 2,
|
|
38
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575a1 1 0 0 1 0 .696a10.8 10.8 0 0 1-1.444 2.49m-6.41-.679a3 3 0 0 1-4.242-4.242" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151a1 1 0 0 1 0-.696a10.75 10.75 0 0 1 4.446-5.143M2 2l20 20" })]
|
|
39
|
+
})
|
|
40
|
+
});
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region ~icons/lucide/minus.jsx
|
|
43
|
+
var lucideMinus = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
|
|
44
|
+
viewBox: "0 0 24 24",
|
|
45
|
+
width: "1.2em",
|
|
46
|
+
height: "1.2em",
|
|
47
|
+
...props,
|
|
48
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
|
|
49
|
+
fill: "none",
|
|
50
|
+
stroke: "currentColor",
|
|
51
|
+
strokeLinecap: "round",
|
|
52
|
+
strokeLinejoin: "round",
|
|
53
|
+
strokeWidth: 2,
|
|
54
|
+
d: "M5 12h14"
|
|
55
|
+
})
|
|
56
|
+
});
|
|
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
|
+
//#region src/components/qgrid/SettingsPanel.tsx
|
|
75
|
+
var GROUP_LABELS = {
|
|
76
|
+
openai: "OpenAI 워커",
|
|
77
|
+
slack: "Slack 알림"
|
|
78
|
+
};
|
|
79
|
+
function maskValue(value) {
|
|
80
|
+
if (!value) return "";
|
|
81
|
+
if (value.length <= 12) return "•".repeat(value.length);
|
|
82
|
+
return `${value.slice(0, 8)}${"•".repeat(12)}${value.slice(-4)}`;
|
|
83
|
+
}
|
|
84
|
+
/** 정수·실수는 타이핑보다 증감이 빠르다. 범위도 버튼이 알아서 지킨다. */
|
|
85
|
+
function Stepper({ value, min, max, step, disabled, onChange }) {
|
|
86
|
+
const current = Number(value);
|
|
87
|
+
const valid = Number.isFinite(current);
|
|
88
|
+
const lower = min ?? Number.NEGATIVE_INFINITY;
|
|
89
|
+
const upper = max ?? Number.POSITIVE_INFINITY;
|
|
90
|
+
const adjust = (delta) => {
|
|
91
|
+
const next = Math.min(Math.max((valid ? current : min ?? 0) + delta, lower), upper);
|
|
92
|
+
onChange(String(Number(next.toFixed(2))));
|
|
93
|
+
};
|
|
94
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
95
|
+
className: "inline-flex items-center rounded-lg border border-sand-200/80 bg-white overflow-hidden",
|
|
96
|
+
children: [
|
|
97
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
98
|
+
type: "button",
|
|
99
|
+
"aria-label": "감소",
|
|
100
|
+
onClick: () => adjust(-step),
|
|
101
|
+
disabled: disabled || valid && current <= lower,
|
|
102
|
+
className: "size-7 grid place-items-center text-sand-500 hover:bg-sand-100 disabled:opacity-30 disabled:hover:bg-transparent transition-colors duration-150",
|
|
103
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideMinus, { className: "size-3" })
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
106
|
+
value,
|
|
107
|
+
onChange: (e) => onChange(e.target.value),
|
|
108
|
+
disabled,
|
|
109
|
+
inputMode: "decimal",
|
|
110
|
+
className: "w-12 text-center text-[12px] tabular-nums text-sand-800 bg-transparent border-x border-sand-200/80 py-1 focus:outline-none focus:bg-sand-50"
|
|
111
|
+
}),
|
|
112
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
113
|
+
type: "button",
|
|
114
|
+
"aria-label": "증가",
|
|
115
|
+
onClick: () => adjust(step),
|
|
116
|
+
disabled: disabled || valid && current >= upper,
|
|
117
|
+
className: "size-7 grid place-items-center text-sand-500 hover:bg-sand-100 disabled:opacity-30 disabled:hover:bg-transparent transition-colors duration-150",
|
|
118
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucidePlus, { className: "size-3" })
|
|
119
|
+
})
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function Toggle({ on, disabled, onChange }) {
|
|
124
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
125
|
+
type: "button",
|
|
126
|
+
role: "switch",
|
|
127
|
+
"aria-checked": on,
|
|
128
|
+
disabled,
|
|
129
|
+
onClick: () => onChange(!on),
|
|
130
|
+
className: `relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors duration-200 disabled:opacity-50 ${on ? "bg-sage-400" : "bg-sand-300"}`,
|
|
131
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform duration-200 ${on ? "translate-x-4.5" : "translate-x-0.5"}` })
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function SettingRow({ item }) {
|
|
135
|
+
const [draft, setDraft] = (0, import_react.useState)(null);
|
|
136
|
+
const [revealed, setRevealed] = (0, import_react.useState)(false);
|
|
137
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
138
|
+
const queryClient = useQueryClient();
|
|
139
|
+
const updateMutation = SettingService.useUpdateSettingMutation();
|
|
140
|
+
const resetMutation = SettingService.useResetSettingMutation();
|
|
141
|
+
const pending = updateMutation.isPending || resetMutation.isPending;
|
|
142
|
+
const value = draft ?? item.value;
|
|
143
|
+
const dirty = draft !== null && draft !== item.value;
|
|
144
|
+
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["Setting", "getSettingList"] });
|
|
145
|
+
const commit = async (next) => {
|
|
146
|
+
setError(null);
|
|
147
|
+
try {
|
|
148
|
+
await updateMutation.mutateAsync({
|
|
149
|
+
key: item.key,
|
|
150
|
+
value: next
|
|
151
|
+
});
|
|
152
|
+
await invalidate();
|
|
153
|
+
setDraft(null);
|
|
154
|
+
} catch (e) {
|
|
155
|
+
setError(e.message);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const reset = async () => {
|
|
159
|
+
setError(null);
|
|
160
|
+
try {
|
|
161
|
+
await resetMutation.mutateAsync({ key: item.key });
|
|
162
|
+
await invalidate();
|
|
163
|
+
setDraft(null);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
setError(e.message);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const isToggle = item.kind === "boolean";
|
|
169
|
+
const isNumeric = item.kind === "integer" || item.kind === "number";
|
|
170
|
+
const isSecret = item.kind === "secret";
|
|
171
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
172
|
+
className: "px-4 py-3 sm:px-5",
|
|
173
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
174
|
+
className: "flex items-start gap-4",
|
|
175
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
176
|
+
className: "min-w-0 flex-1",
|
|
177
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
178
|
+
className: "flex items-center gap-2",
|
|
179
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
180
|
+
className: "text-[13px] text-sand-800",
|
|
181
|
+
children: item.label
|
|
182
|
+
}), item.applies === "restart" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
183
|
+
className: "text-[10px] text-caution-500",
|
|
184
|
+
title: "저장해도 다음 재시작부터 반영됩니다",
|
|
185
|
+
children: "재시작 필요"
|
|
186
|
+
})]
|
|
187
|
+
}), item.help && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
188
|
+
className: "mt-0.5 text-[11px] text-sand-500",
|
|
189
|
+
children: item.help
|
|
190
|
+
})]
|
|
191
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
192
|
+
className: "flex items-center gap-1.5 shrink-0",
|
|
193
|
+
children: [
|
|
194
|
+
isToggle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Toggle, {
|
|
195
|
+
on: value === "true",
|
|
196
|
+
disabled: pending,
|
|
197
|
+
onChange: (next) => void commit(String(next))
|
|
198
|
+
}),
|
|
199
|
+
isNumeric && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Stepper, {
|
|
200
|
+
value,
|
|
201
|
+
min: item.min,
|
|
202
|
+
max: item.max,
|
|
203
|
+
step: item.kind === "integer" ? 1 : .5,
|
|
204
|
+
disabled: pending,
|
|
205
|
+
onChange: setDraft
|
|
206
|
+
}),
|
|
207
|
+
(item.kind === "string" || isSecret) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
208
|
+
className: "relative",
|
|
209
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
210
|
+
value: isSecret && !revealed ? maskValue(value) : value,
|
|
211
|
+
onChange: (e) => setDraft(e.target.value),
|
|
212
|
+
onFocus: () => isSecret && setRevealed(true),
|
|
213
|
+
disabled: pending,
|
|
214
|
+
className: `w-48 sm:w-64 rounded-lg border border-sand-200/80 bg-white py-1.5 pl-2.5 text-[12px] text-sand-800 focus:outline-none focus:border-sienna-300 ${isSecret ? "pr-8 font-mono" : "pr-2.5"}`
|
|
215
|
+
}), isSecret && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
216
|
+
type: "button",
|
|
217
|
+
"aria-label": revealed ? "값 숨기기" : "값 보기",
|
|
218
|
+
onClick: () => setRevealed((r) => !r),
|
|
219
|
+
className: "absolute right-1.5 top-1/2 -translate-y-1/2 size-6 grid place-items-center text-sand-400 hover:text-sand-600 transition-colors duration-150",
|
|
220
|
+
children: revealed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideEyeOff, { className: "size-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideEye, { className: "size-3.5" })
|
|
221
|
+
})]
|
|
222
|
+
}),
|
|
223
|
+
dirty && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
224
|
+
type: "button",
|
|
225
|
+
onClick: () => void commit(value),
|
|
226
|
+
disabled: pending,
|
|
227
|
+
className: "px-2.5 py-1.5 text-[11px] font-medium rounded-lg bg-sienna-400 text-white hover:bg-sienna-500 disabled:opacity-50 transition-colors duration-150",
|
|
228
|
+
children: updateMutation.isPending ? "저장 중" : "저장"
|
|
229
|
+
}),
|
|
230
|
+
!dirty && item.source === "db" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
231
|
+
type: "button",
|
|
232
|
+
title: "저장값을 지우고 env 또는 기본값으로 되돌립니다",
|
|
233
|
+
onClick: () => void reset(),
|
|
234
|
+
disabled: pending,
|
|
235
|
+
className: "size-7 grid place-items-center rounded-lg text-sand-400 hover:text-sand-600 hover:bg-sand-100 disabled:opacity-50 transition-colors duration-150",
|
|
236
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideRotateCcw, { className: "size-3.5" })
|
|
237
|
+
})
|
|
238
|
+
]
|
|
239
|
+
})]
|
|
240
|
+
}), error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
241
|
+
className: "mt-1.5 text-[11px] text-red-500",
|
|
242
|
+
children: error
|
|
243
|
+
})]
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
function SourceLegend({ items }) {
|
|
247
|
+
const stored = items.filter((i) => i.source === "db").length;
|
|
248
|
+
if (stored === 0) return null;
|
|
249
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
250
|
+
className: "text-[11px] text-sand-400",
|
|
251
|
+
children: [stored, "개 항목이 저장값을 사용 중"]
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function SettingsPanel() {
|
|
255
|
+
const { data, isLoading } = SettingService.useSettingList();
|
|
256
|
+
if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
257
|
+
className: "space-y-4",
|
|
258
|
+
children: [0, 1].map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
259
|
+
className: "panel overflow-hidden",
|
|
260
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
261
|
+
className: "panel-header px-4 py-2.5 sm:px-5",
|
|
262
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-3 w-24 rounded bg-sand-200 animate-pulse" })
|
|
263
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
264
|
+
className: "divide-y divide-sand-100/80",
|
|
265
|
+
children: [
|
|
266
|
+
0,
|
|
267
|
+
1,
|
|
268
|
+
2
|
|
269
|
+
].map((j) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
270
|
+
className: "px-4 py-3 sm:px-5 flex items-center justify-between gap-4",
|
|
271
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-3 w-40 rounded bg-sand-100 animate-pulse" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-7 w-24 rounded-lg bg-sand-100 animate-pulse" })]
|
|
272
|
+
}, j))
|
|
273
|
+
})]
|
|
274
|
+
}, i))
|
|
275
|
+
});
|
|
276
|
+
const settings = data?.settings ?? [];
|
|
277
|
+
const runtime = data?.runtime ?? [];
|
|
278
|
+
const groups = [...new Set(settings.map((s) => s.group))];
|
|
279
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
280
|
+
className: "space-y-4",
|
|
281
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
282
|
+
className: "panel overflow-hidden",
|
|
283
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
284
|
+
className: "panel-header flex items-center justify-between px-4 py-2.5 sm:px-5",
|
|
285
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
286
|
+
className: "text-[13px] font-medium text-sand-700",
|
|
287
|
+
children: "환경"
|
|
288
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
289
|
+
className: "text-[11px] text-sand-400",
|
|
290
|
+
children: "기동 시점 값"
|
|
291
|
+
})]
|
|
292
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dl", {
|
|
293
|
+
className: "px-4 py-3 sm:px-5 grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-1.5",
|
|
294
|
+
children: runtime.map((info) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
295
|
+
className: "flex items-baseline justify-between gap-3 min-w-0",
|
|
296
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
|
|
297
|
+
className: "text-[11px] text-sand-500 shrink-0",
|
|
298
|
+
children: info.label
|
|
299
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
|
|
300
|
+
className: "text-[12px] font-mono text-sand-700 truncate",
|
|
301
|
+
children: info.value
|
|
302
|
+
})]
|
|
303
|
+
}, info.label))
|
|
304
|
+
})]
|
|
305
|
+
}), groups.map((group) => {
|
|
306
|
+
const items = settings.filter((s) => s.group === group);
|
|
307
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
308
|
+
className: "panel overflow-hidden",
|
|
309
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
310
|
+
className: "panel-header flex items-center justify-between px-4 py-2.5 sm:px-5",
|
|
311
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
312
|
+
className: "text-[13px] font-medium text-sand-700",
|
|
313
|
+
children: GROUP_LABELS[group] ?? group
|
|
314
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SourceLegend, { items })]
|
|
315
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
316
|
+
className: "divide-y divide-sand-100/80",
|
|
317
|
+
children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingRow, { item }, item.key))
|
|
318
|
+
})]
|
|
319
|
+
}, group);
|
|
320
|
+
})]
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region src/routes/settings.tsx?tsr-split=component
|
|
325
|
+
function SettingsPage() {
|
|
326
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
327
|
+
className: "max-w-4xl mx-auto",
|
|
328
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingsPanel, {})
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
//#endregion
|
|
332
|
+
export { SettingsPage as component };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as RequestLogStepService, r as RequestLogService } from "./services.generated-
|
|
3
|
-
import { a as Link } from "./lazyRouteComponent-
|
|
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-DADr9OGH.js";
|
|
3
|
+
import { a as Link } from "./lazyRouteComponent-Ws7I6fL-.js";
|
|
4
4
|
import { t as Route } from "../entry-server.generated.js";
|
|
5
|
-
import { n as formatMicroUsd, t as cacheHitRate } from "./cost-
|
|
5
|
+
import { n as formatMicroUsd, t as cacheHitRate } from "./cost-DzjfVtUr.js";
|
|
6
6
|
import minpath from "node:path";
|
|
7
7
|
import minproc from "node:process";
|
|
8
8
|
import { fileURLToPath as urlToPath } from "node:url";
|
|
@@ -1,26 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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-DADr9OGH.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-
|
|
5
|
-
|
|
6
|
-
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
7
|
-
var import_jsx_runtime = require_jsx_runtime();
|
|
8
|
-
var lucidePlus = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
|
|
9
|
-
viewBox: "0 0 24 24",
|
|
10
|
-
width: "1.2em",
|
|
11
|
-
height: "1.2em",
|
|
12
|
-
...props,
|
|
13
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
|
|
14
|
-
fill: "none",
|
|
15
|
-
stroke: "currentColor",
|
|
16
|
-
strokeLinecap: "round",
|
|
17
|
-
strokeLinejoin: "round",
|
|
18
|
-
strokeWidth: 2,
|
|
19
|
-
d: "M5 12h14m-7-7v14"
|
|
20
|
-
})
|
|
21
|
-
});
|
|
22
|
-
//#endregion
|
|
4
|
+
import { n as OAuthCodeEntry, r as useUpdateTokenMutation, t as useOAuthLoginFlow } from "./use-oauth-login-flow-B6MY65IN.js";
|
|
5
|
+
import { t as lucidePlus } from "./plus-BISXNqRU.js";
|
|
23
6
|
//#region src/components/qgrid/AddTokenModal.tsx
|
|
7
|
+
var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
|
|
8
|
+
var import_jsx_runtime = require_jsx_runtime();
|
|
24
9
|
function AddTokenModal() {
|
|
25
10
|
const [open, setOpen] = (0, import_react.useState)(false);
|
|
26
11
|
const [name, setName] = (0, import_react.useState)("");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as QgridService,
|
|
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-DADr9OGH.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,5 +1,6 @@
|
|
|
1
|
-
import { A as
|
|
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-
|
|
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";
|
|
3
4
|
//#region ../../node_modules/.pnpm/@tanstack+query-core@5.95.2/node_modules/@tanstack/query-core/build/modern/hydration.js
|
|
4
5
|
function defaultTransformerFn(data) {
|
|
5
6
|
return data;
|
|
@@ -22659,6 +22660,26 @@ var lucideKeyRound = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg
|
|
|
22659
22660
|
})
|
|
22660
22661
|
});
|
|
22661
22662
|
//#endregion
|
|
22663
|
+
//#region ~icons/lucide/settings.jsx
|
|
22664
|
+
var lucideSettings = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
|
|
22665
|
+
viewBox: "0 0 24 24",
|
|
22666
|
+
width: "1.2em",
|
|
22667
|
+
height: "1.2em",
|
|
22668
|
+
...props,
|
|
22669
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", {
|
|
22670
|
+
fill: "none",
|
|
22671
|
+
stroke: "currentColor",
|
|
22672
|
+
strokeLinecap: "round",
|
|
22673
|
+
strokeLinejoin: "round",
|
|
22674
|
+
strokeWidth: 2,
|
|
22675
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
|
|
22676
|
+
cx: 12,
|
|
22677
|
+
cy: 12,
|
|
22678
|
+
r: 3
|
|
22679
|
+
})]
|
|
22680
|
+
})
|
|
22681
|
+
});
|
|
22682
|
+
//#endregion
|
|
22662
22683
|
//#region ~icons/lucide/terminal.jsx
|
|
22663
22684
|
var lucideTerminal = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
|
|
22664
22685
|
viewBox: "0 0 24 24",
|
|
@@ -22703,6 +22724,11 @@ var NAV_SECTIONS = [{
|
|
|
22703
22724
|
shortTitle: "Server",
|
|
22704
22725
|
path: "/monit",
|
|
22705
22726
|
icon: lucideTerminal
|
|
22727
|
+
},
|
|
22728
|
+
{
|
|
22729
|
+
title: "Settings",
|
|
22730
|
+
path: "/settings",
|
|
22731
|
+
icon: lucideSettings
|
|
22706
22732
|
}
|
|
22707
22733
|
]
|
|
22708
22734
|
}];
|
|
@@ -22742,7 +22768,7 @@ function BottomNav() {
|
|
|
22742
22768
|
}
|
|
22743
22769
|
//#endregion
|
|
22744
22770
|
//#region src/components/Sidebar.tsx
|
|
22745
|
-
var qgridVersion = "2.6.
|
|
22771
|
+
var qgridVersion = "2.6.8";
|
|
22746
22772
|
function Sidebar({ className }) {
|
|
22747
22773
|
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
|
22748
22774
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
|
|
@@ -44577,7 +44603,7 @@ function SonamuProvider({ children }) {
|
|
|
44577
44603
|
}
|
|
44578
44604
|
//#endregion
|
|
44579
44605
|
//#region src/routes/__root.tsx
|
|
44580
|
-
var Route$
|
|
44606
|
+
var Route$6 = createRootRouteWithContext()({
|
|
44581
44607
|
head: () => ({ meta: [
|
|
44582
44608
|
{ charSet: "utf-8" },
|
|
44583
44609
|
{
|
|
@@ -44589,7 +44615,7 @@ var Route$5 = createRootRouteWithContext()({
|
|
|
44589
44615
|
component: RootComponent
|
|
44590
44616
|
});
|
|
44591
44617
|
function RootComponent() {
|
|
44592
|
-
const { queryClient } = Route$
|
|
44618
|
+
const { queryClient } = Route$6.useRouteContext();
|
|
44593
44619
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("html", {
|
|
44594
44620
|
lang: "en",
|
|
44595
44621
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("head", { children: [
|
|
@@ -44623,11 +44649,15 @@ function RootComponent() {
|
|
|
44623
44649
|
}
|
|
44624
44650
|
//#endregion
|
|
44625
44651
|
//#region src/routes/tokens.tsx
|
|
44626
|
-
var $$splitComponentImporter$
|
|
44627
|
-
var Route$
|
|
44652
|
+
var $$splitComponentImporter$5 = () => import("./assets/tokens-DmhIfVct.js");
|
|
44653
|
+
var Route$5 = createFileRoute("/tokens")({ component: lazyRouteComponent($$splitComponentImporter$5, "component") });
|
|
44654
|
+
//#endregion
|
|
44655
|
+
//#region src/routes/settings.tsx
|
|
44656
|
+
var $$splitComponentImporter$4 = () => import("./assets/settings-CMJKayff.js");
|
|
44657
|
+
var Route$4 = createFileRoute("/settings")({ component: lazyRouteComponent($$splitComponentImporter$4, "component") });
|
|
44628
44658
|
//#endregion
|
|
44629
44659
|
//#region src/routes/monit.tsx
|
|
44630
|
-
var $$splitComponentImporter$3 = () => import("./assets/monit-
|
|
44660
|
+
var $$splitComponentImporter$3 = () => import("./assets/monit-DyKLJX4s.js");
|
|
44631
44661
|
var monitSearchSchema = object({ level: _enum([
|
|
44632
44662
|
"all",
|
|
44633
44663
|
"scaling",
|
|
@@ -44640,7 +44670,7 @@ var Route$3 = createFileRoute("/monit")({
|
|
|
44640
44670
|
});
|
|
44641
44671
|
//#endregion
|
|
44642
44672
|
//#region src/routes/logs.tsx
|
|
44643
|
-
var $$splitComponentImporter$2 = () => import("./assets/logs-
|
|
44673
|
+
var $$splitComponentImporter$2 = () => import("./assets/logs-DmkzGxGU.js");
|
|
44644
44674
|
var logsSearchSchema = object({
|
|
44645
44675
|
token: string().optional(),
|
|
44646
44676
|
project: string().optional(),
|
|
@@ -44654,11 +44684,11 @@ var Route$2 = createFileRoute("/logs")({
|
|
|
44654
44684
|
});
|
|
44655
44685
|
//#endregion
|
|
44656
44686
|
//#region src/routes/index.tsx
|
|
44657
|
-
var $$splitComponentImporter$1 = () => import("./assets/routes-
|
|
44687
|
+
var $$splitComponentImporter$1 = () => import("./assets/routes-g62E9xmC.js");
|
|
44658
44688
|
var Route$1 = createFileRoute("/")({ component: lazyRouteComponent($$splitComponentImporter$1, "component") });
|
|
44659
44689
|
//#endregion
|
|
44660
44690
|
//#region src/routes/requests/show.tsx
|
|
44661
|
-
var $$splitComponentImporter = () => import("./assets/show-
|
|
44691
|
+
var $$splitComponentImporter = () => import("./assets/show-BGAiCPGq.js");
|
|
44662
44692
|
var showSearchSchema = object({ id: number() });
|
|
44663
44693
|
var Route = createFileRoute("/requests/show")({
|
|
44664
44694
|
validateSearch: showSearchSchema,
|
|
@@ -44666,37 +44696,43 @@ var Route = createFileRoute("/requests/show")({
|
|
|
44666
44696
|
});
|
|
44667
44697
|
//#endregion
|
|
44668
44698
|
//#region src/routeTree.gen.ts
|
|
44669
|
-
var TokensRoute = Route$
|
|
44699
|
+
var TokensRoute = Route$5.update({
|
|
44670
44700
|
id: "/tokens",
|
|
44671
44701
|
path: "/tokens",
|
|
44672
|
-
getParentRoute: () => Route$
|
|
44702
|
+
getParentRoute: () => Route$6
|
|
44703
|
+
});
|
|
44704
|
+
var SettingsRoute = Route$4.update({
|
|
44705
|
+
id: "/settings",
|
|
44706
|
+
path: "/settings",
|
|
44707
|
+
getParentRoute: () => Route$6
|
|
44673
44708
|
});
|
|
44674
44709
|
var MonitRoute = Route$3.update({
|
|
44675
44710
|
id: "/monit",
|
|
44676
44711
|
path: "/monit",
|
|
44677
|
-
getParentRoute: () => Route$
|
|
44712
|
+
getParentRoute: () => Route$6
|
|
44678
44713
|
});
|
|
44679
44714
|
var LogsRoute = Route$2.update({
|
|
44680
44715
|
id: "/logs",
|
|
44681
44716
|
path: "/logs",
|
|
44682
|
-
getParentRoute: () => Route$
|
|
44717
|
+
getParentRoute: () => Route$6
|
|
44683
44718
|
});
|
|
44684
44719
|
var rootRouteChildren = {
|
|
44685
44720
|
IndexRoute: Route$1.update({
|
|
44686
44721
|
id: "/",
|
|
44687
44722
|
path: "/",
|
|
44688
|
-
getParentRoute: () => Route$
|
|
44723
|
+
getParentRoute: () => Route$6
|
|
44689
44724
|
}),
|
|
44690
44725
|
LogsRoute,
|
|
44691
44726
|
MonitRoute,
|
|
44727
|
+
SettingsRoute,
|
|
44692
44728
|
TokensRoute,
|
|
44693
44729
|
RequestsShowRoute: Route.update({
|
|
44694
44730
|
id: "/requests/show",
|
|
44695
44731
|
path: "/requests/show",
|
|
44696
|
-
getParentRoute: () => Route$
|
|
44732
|
+
getParentRoute: () => Route$6
|
|
44697
44733
|
})
|
|
44698
44734
|
};
|
|
44699
|
-
var routeTree = Route$
|
|
44735
|
+
var routeTree = Route$6._addFileChildren(rootRouteChildren)._addFileTypes();
|
|
44700
44736
|
//#endregion
|
|
44701
44737
|
//#region src/entry-server.generated.tsx
|
|
44702
44738
|
/**
|
package/package.json
CHANGED
|
@@ -69,8 +69,8 @@ When helping a user set up qgrid locally, check for `QGRID_PROJECT_NAME` or conf
|
|
|
69
69
|
| `QGRID_OPENAI_MAX_WORKERS_PER_TOKEN` | `3` when autoscaling | Maximum workers per active OpenAI token. It cannot be lower than the resolved minimum and is hard-capped at 20. When autoscaling is disabled, the maximum equals the minimum. |
|
|
70
70
|
| `QGRID_OPENAI_SCALE_INTERVAL_MS` | `5000` | OpenAI pool health, idle-thread cleanup, and demand autoscaling evaluation interval. Clamped to 250..300000 ms. |
|
|
71
71
|
| `QGRID_OPENAI_SCALE_DOWN_IDLE_MS` | `600000` | Idle time before an excess worker becomes eligible for scale-down. Clamped to 1000 ms..24 hours. |
|
|
72
|
-
| `QGRID_OPENAI_MAX_ESTIMATED_RSS_GIB` | `16` | Refuse scale-up when estimated qgrid worker RSS would exceed this value. Estimate: `0.71 + 0.157 * totalWorkerCount` GiB. |
|
|
73
|
-
| `QGRID_OPENAI_MIN_HOST_AVAILABLE_GIB` | `20` | Refuse scale-up when current host available memory is below this value. |
|
|
72
|
+
| `QGRID_OPENAI_MAX_ESTIMATED_RSS_GIB` | `16` | Refuse scale-up when estimated qgrid worker RSS would exceed this value. Estimate: `0.71 + 0.157 * totalWorkerCount` GiB. Clamped to 1..32 GiB; the upper bound is an operator-typo guard, not an OOM-safety guarantee. |
|
|
73
|
+
| `QGRID_OPENAI_MIN_HOST_AVAILABLE_GIB` | `20` | Refuse scale-up when current host available memory is below this value. Clamped to 0..64 GiB. |
|
|
74
74
|
| `QGRID_OPENAI_THREAD_REUSE` | enabled | Set to `"false"` to disable OpenAI thread reuse and force cold thread behavior. |
|
|
75
75
|
| `MAX_STRUCTURED_OUTPUT_RETRIES` | `1` for structured Anthropic streaming calls | Claude Code structured-output retry count for streaming only, clamped to at least 1 by qgrid. Non-streaming `generate` leaves the variable unset and uses Claude Code's default retry budget. |
|
|
76
76
|
|
|
@@ -88,6 +88,18 @@ When helping a user set up qgrid locally, check for `QGRID_PROJECT_NAME` or conf
|
|
|
88
88
|
|
|
89
89
|
On shutdown, it stops provider dispatchers and the token subscriber.
|
|
90
90
|
|
|
91
|
+
### Runtime settings
|
|
92
|
+
|
|
93
|
+
Some env values are editable at runtime through the dashboard's Settings page, backed by the `settings` table (key-value). Resolution order is DB → env → code default: env stays as a fallback so a deploy that has not written any setting yet keeps working exactly as before. `loadSettings()` runs in `onStart` before the dispatchers are constructed, since the OpenAI pool reads its worker counts in its constructor.
|
|
94
|
+
|
|
95
|
+
- Editable: OpenAI autoscale and min/max workers per token, the worker memory guards, the Slack expiry-reminder interval, and the Slack channel/user-map/bot-token. `SETTING_DEFS` in `setting.schema.ts` is the single definition — key, env name, type, bounds, and whether the change applies immediately or needs a restart.
|
|
96
|
+
- Not editable: anything needed before the server can read its own database (`QGRID_DB_*`, `HOST`, `PORT`, `NODE_ENV`). These are exposed read-only on the same page so an operator can confirm which environment they are looking at; the DB password is masked.
|
|
97
|
+
- Worker settings are marked `restart` because `resolveOpenAIWorkerPoolConfig` is called once in the dispatcher constructor. Changing them writes to the DB but does not resize a running pool.
|
|
98
|
+
- Slack channel, bot-token, and user-map changes are read on the next notification. Changing the expiry-reminder interval replaces the current timer immediately without sending a notification merely because the setting changed; process boot still performs the intended immediate reminder run.
|
|
99
|
+
- `immediate` means the API process that handled the update. The in-memory settings cache is not propagated to sibling processes; multi-instance deployments need a separate settings-change transport before treating the label as cluster-wide.
|
|
100
|
+
- The public settings API exposes the curated list/set/reset methods only. Keep generic Sonamu `save`/`del` methods internal so callers cannot bypass key validation, cache updates, or runtime change hooks.
|
|
101
|
+
- The settings response returns the Slack bot token in full so the authenticated dashboard can support its reveal toggle. Treat that endpoint as an operator-only surface at the deployment boundary; dev0 relies on Caddy authentication for the dashboard. The database password remains masked and is never returned in full.
|
|
102
|
+
|
|
91
103
|
### Readiness during startup
|
|
92
104
|
|
|
93
105
|
HTTP listening opens before the dispatchers finish starting, so requests can arrive while a provider is still unavailable. On dev0 that window is 1–2 minutes: 25 OpenAI workers spawn at `SPAWN_INTERVAL_MS` apart, plus codex process init.
|
|
@@ -34,7 +34,7 @@ Autoscaling is enabled unless `QGRID_OPENAI_AUTOSCALE` is `"false"` or `"0"`. It
|
|
|
34
34
|
- estimated qgrid worker RSS: `0.71 + 0.157 * totalWorkerCount` GiB, limited to 16 GiB by default;
|
|
35
35
|
- current host available memory at scale-up evaluation time, required to be at least 20 GiB by default.
|
|
36
36
|
|
|
37
|
-
All sizing and memory limits can be
|
|
37
|
+
All sizing and memory limits can be configured within the bounds listed in `cli-env-and-server-boot.md`.
|
|
38
38
|
|
|
39
39
|
Pool health maintenance is separate from demand autoscaling:
|
|
40
40
|
|
|
@@ -20,6 +20,8 @@ qgrid dashboard work is usually a Sonamu API/model/generated-client/web change,
|
|
|
20
20
|
- i18n generated files may change when entity labels or static data change.
|
|
21
21
|
- Web components under `packages/web/src/components/qgrid` consume generated API shapes.
|
|
22
22
|
- The main dashboard sidebar version should stay synced to `packages/cli/package.json`. `packages/web/vite.config.ts` reads that package version and exposes it as `__QGRID_CLI_VERSION__`; `Sidebar.tsx` displays it.
|
|
23
|
+
- Runtime settings use the curated `listSettings`, `setSetting`, and `resetSetting` API methods. Do not expose the model's generic `save` or `del` methods: they bypass the settings schema validation, process-local cache update, and runtime change handler.
|
|
24
|
+
- The Slack bot token is returned to the authenticated settings dashboard for its reveal toggle. Preserve the deployment authentication boundary when changing this response; do not describe the value as masked unless the API contract is changed to match.
|
|
23
25
|
|
|
24
26
|
## Migration workflow
|
|
25
27
|
|
|
@@ -210,8 +210,9 @@ When a refresh fails in a way that only re-login can fix, qgrid removes the toke
|
|
|
210
210
|
- Deactivation is one conditional `active=true → false` update. The process whose update affects a row is the only one that sends the Slack death notification, so a shared database produces exactly one alert per event.
|
|
211
211
|
- The provider's last active token is never auto-deactivated. A systemic failure (client_id revocation, OAuth contract change) would otherwise empty the pool; qgrid logs an error, keeps the token, and sends its own Slack alert. That path leaves no state change behind, so every following request would re-alert — a per-provider 30-minute cooldown holds it to one. The cooldown is in-process, so N instances can each emit once per window.
|
|
212
212
|
- The other notification is registration: every completed login sends one alert, whether the account is new or replacing an existing row through the account dedup. Manually toggling a token back to active is not a registration and sends nothing.
|
|
213
|
+
- Quiet hours suppress non-urgent Slack notifications: weekends, plus 20:00–08:00 on weekdays, in `Asia/Seoul`. The hour is computed through `Intl` with an explicit timezone rather than `getHours()` — dev0 runs in UTC, where "20:00" would land at 05:00 Korean time. Expiry alerts repeat, so one dropped in quiet hours returns on the first cycle of the next working window. The last-active-token death alert sets `urgent: true` and ignores quiet hours: it means the provider pool is empty, which cannot wait for the next working day.
|
|
213
214
|
- Notifications are fail-open: unset `SLACK_BOT_TOKEN`/`SLACK_CHANNEL_ID` is a silent no-op, and send failures (including HTTP 200 with `ok:false`) only warn. Payloads carry an internal reason code such as `anthropic:400`, never the raw provider response body.
|
|
214
|
-
- `expired-token-reminder.ts` repeats the session-expiry alert for tokens that are still inactive, because the one-shot alert is lost on anyone not watching the channel at that moment and re-login only happens when a person does it. It reuses the same `세션 만료` title rather than inventing a second name — there is one event, so a differently-named alert would read as a separate problem. It runs every `SLACK_EXPIRY_REMINDER_INTERVAL_MINUTES` (unset or 0 disables), batches every inactive token into one message, and sends nothing when none are inactive — a periodic "all clear" trains people to ignore the channel.
|
|
215
|
+
- `expired-token-reminder.ts` repeats the session-expiry alert for tokens that are still inactive, because the one-shot alert is lost on anyone not watching the channel at that moment and re-login only happens when a person does it. It reuses the same `세션 만료` title rather than inventing a second name — there is one event, so a differently-named alert would read as a separate problem. It runs once at startup and then every `SLACK_EXPIRY_REMINDER_INTERVAL_MINUTES` (unset or 0 disables), batches every inactive token into one message, and sends nothing when none are inactive. The startup run matters because a restart is exactly when someone is watching, and waiting a full interval to learn a token is already dead is backwards; the cost is one extra alert per restart — a periodic "all clear" trains people to ignore the channel.
|
|
215
216
|
- Owners are mentioned through `SLACK_USER_MAP` (`tokenName:SlackUserId`) on every token notification, not just the reminder. The map is explicit rather than resolved through `users.list`: Slack handles diverge from token names often enough (`haze`→`haze.lee`, `byeongjun`→`potados`) that auto-matching drops the mention silently, and an alert nobody is tagged in has no one to act on it. Shared accounts are left out of the map on purpose and appear by name only. Each instance sends its own reminder — unlike deactivation there is no state change to gate on, and a few duplicates a day cost less than a locking scheme.
|
|
216
217
|
|
|
217
218
|
## Quota Threshold Semantics
|