@cartanova/qgrid-cli 2.11.0 → 2.11.1

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 (51) hide show
  1. package/bundle/web-dist/client/assets/index-C96h3feE.js +32 -0
  2. package/bundle/web-dist/client/assets/logs-DBM6U0QD.js +1 -0
  3. package/bundle/web-dist/client/assets/monit-BBjXa7vB.js +1 -0
  4. package/bundle/web-dist/client/assets/plus-BsQdY7PS.js +1 -0
  5. package/bundle/web-dist/client/assets/refresh-cw-D9IUmT23.js +1 -0
  6. package/bundle/web-dist/client/assets/request-model-Bpp_rkK8.js +1 -0
  7. package/bundle/web-dist/client/assets/routes-Crz_PQzi.js +5 -0
  8. package/bundle/web-dist/client/assets/{services.generated-CefiDXEv.js → services.generated-B5lc3aIC.js} +1 -1
  9. package/bundle/web-dist/client/assets/settings-Cdwm3OBB.js +1 -0
  10. package/bundle/web-dist/client/assets/show-DBUhALT1.js +2 -0
  11. package/bundle/web-dist/client/assets/tokens-CVQAVJTo.js +1 -0
  12. package/bundle/web-dist/client/assets/use-oauth-login-flow-BfxYI4Oh.js +1 -0
  13. package/bundle/web-dist/client/assets/use-update-token-mutation-qKBC2sn7.js +1 -0
  14. package/bundle/web-dist/client/assets/vendor-react-DH4kUfK2.js +9 -0
  15. package/bundle/web-dist/client/assets/vendor-tanstack-DRPs09ai.js +1 -0
  16. package/bundle/web-dist/client/index.html +4 -4
  17. package/bundle/web-dist/server/assets/{lazyRouteComponent-D8iTIHyn.js → lazyRouteComponent-AKiHfZWp.js} +2 -2
  18. package/bundle/web-dist/server/assets/logs-B7QmGJq3.js +513 -0
  19. package/bundle/web-dist/server/assets/{monit-B2hqorPe.js → monit-DFt7x3bh.js} +117 -375
  20. package/bundle/web-dist/server/assets/plus-DaYQRpBt.js +19 -0
  21. package/bundle/web-dist/server/assets/{react-dom-BIYQzBqG.js → react-dom-Cv23mm1K.js} +1 -1
  22. package/bundle/web-dist/server/assets/refresh-cw-BpXNsAy-.js +23 -0
  23. package/bundle/web-dist/server/assets/{request-model-I6OdODSH.js → request-model-eHE6YbaF.js} +6 -15
  24. package/bundle/web-dist/server/assets/{routes-B95YlUv2.js → routes-_Y-PJfgO.js} +203 -679
  25. package/bundle/web-dist/server/assets/{services.generated-DQATJ6qf.js → services.generated-CMJ8AHbO.js} +3796 -4012
  26. package/bundle/web-dist/server/assets/settings-JwJKxn2e.js +461 -0
  27. package/bundle/web-dist/server/assets/{show-Bxaj3rhH.js → show-BEv6z1v_.js} +368 -1270
  28. package/bundle/web-dist/server/assets/tokens-CNT4mI-f.js +560 -0
  29. package/bundle/web-dist/server/assets/{use-oauth-login-flow-CKDHgc3U.js → use-oauth-login-flow-jkAkT3X6.js} +16 -50
  30. package/bundle/web-dist/server/assets/{use-update-token-mutation-BEbgOzaa.js → use-update-token-mutation-Sm6kh02m.js} +1 -1
  31. package/bundle/web-dist/server/entry-server.generated.js +1130 -2626
  32. package/package.json +1 -1
  33. package/bundle/web-dist/client/assets/index-BA7uQ-6c.js +0 -31
  34. package/bundle/web-dist/client/assets/logs-C47OJGEI.js +0 -1
  35. package/bundle/web-dist/client/assets/monit-BS9no0dU.js +0 -4
  36. package/bundle/web-dist/client/assets/plus-sdACTfH0.js +0 -1
  37. package/bundle/web-dist/client/assets/refresh-cw-CY8o_1cT.js +0 -1
  38. package/bundle/web-dist/client/assets/request-model-BRtFuZvj.js +0 -1
  39. package/bundle/web-dist/client/assets/routes-kqgkUxE8.js +0 -5
  40. package/bundle/web-dist/client/assets/settings-DNDvFGSw.js +0 -1
  41. package/bundle/web-dist/client/assets/show-u8Np69q3.js +0 -2
  42. package/bundle/web-dist/client/assets/tokens-QvP410NW.js +0 -1
  43. package/bundle/web-dist/client/assets/use-oauth-login-flow-Bzdh28Ws.js +0 -1
  44. package/bundle/web-dist/client/assets/use-update-token-mutation-CfJKaxmR.js +0 -1
  45. package/bundle/web-dist/client/assets/vendor-react-CUFFrVN9.js +0 -244
  46. package/bundle/web-dist/client/assets/vendor-tanstack-Bf_jag9G.js +0 -1
  47. package/bundle/web-dist/server/assets/logs-B3OTd9hy.js +0 -825
  48. package/bundle/web-dist/server/assets/plus-DwG7h9kq.js +0 -28
  49. package/bundle/web-dist/server/assets/refresh-cw-mMBkwOq0.js +0 -44
  50. package/bundle/web-dist/server/assets/settings-C3gbGk0m.js +0 -830
  51. package/bundle/web-dist/server/assets/tokens-CZjH3cB7.js +0 -1038
@@ -0,0 +1,560 @@
1
+ import { $ as __toESM, M as require_jsx_runtime, N as require_react, j as useQueryClient, n as QgridService, o as TokenService } from "./services.generated-CMJ8AHbO.js";
2
+ import { i as o, n as useOAuthLoginFlow, r as OAuthCodeEntry } from "./use-oauth-login-flow-jkAkT3X6.js";
3
+ import { u as lucideKeyRound } from "../entry-server.generated.js";
4
+ import { t as useUpdateTokenMutation } from "./use-update-token-mutation-Sm6kh02m.js";
5
+ import { t as lucidePlus } from "./plus-DaYQRpBt.js";
6
+ //#region src/components/qgrid/DeviceCodeEntry.tsx
7
+ var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
8
+ var import_jsx_runtime = require_jsx_runtime();
9
+ function formatRemaining(ms) {
10
+ const totalSec = Math.max(Math.ceil(ms / 1e3), 0);
11
+ const m = Math.floor(totalSec / 60);
12
+ const s = totalSec % 60;
13
+ return `${m}:${String(s).padStart(2, "0")}`;
14
+ }
15
+ /** 만료 시각까지 1초마다 갱신되는 남은 시간. */
16
+ function Countdown({ expiresAt }) {
17
+ const [now, setNow] = (0, import_react.useState)(() => Date.now());
18
+ (0, import_react.useEffect)(() => {
19
+ const id = window.setInterval(() => setNow(Date.now()), 1e3);
20
+ return () => clearInterval(id);
21
+ }, []);
22
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
23
+ className: "tabular-nums",
24
+ children: formatRemaining(expiresAt - now)
25
+ });
26
+ }
27
+ /**
28
+ * Grok device-code 로그인 단계. 사용자 코드와 인증 링크를 보여 주고, 폴링 상태를 표시한다.
29
+ * 폴링 자체는 `useOAuthLoginFlow().startDevice` 가 소유한다.
30
+ */
31
+ function DeviceCodeEntry({ flow, onRestart }) {
32
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
33
+ className: "space-y-2",
34
+ children: [
35
+ flow.status === "starting" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
36
+ className: "flex items-center gap-1.5 text-[12px] text-sand-600",
37
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "w-3.5 h-3.5 border-2 border-sand-400 border-t-transparent rounded-full animate-spin" }), "Requesting device code..."]
38
+ }),
39
+ flow.status === "waiting" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
40
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
41
+ className: "text-[12px] text-sand-600 leading-relaxed",
42
+ children: "아래 링크를 열어 xAI 계정으로 로그인한 뒤 이 코드를 입력하세요. 승인되면 토큰이 자동으로 추가됩니다."
43
+ }),
44
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
45
+ className: "rounded-md border border-sand-200 bg-sand-50 px-3 py-3 text-center",
46
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
47
+ className: "text-[10px] uppercase tracking-wider text-sand-500 font-medium",
48
+ children: "Device code"
49
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
50
+ className: "mt-1 font-mono text-lg tracking-widest text-sand-900 select-all",
51
+ children: flow.userCode
52
+ })]
53
+ }),
54
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
55
+ href: flow.verificationUriComplete ?? flow.verificationUri,
56
+ target: "_blank",
57
+ rel: "noopener noreferrer",
58
+ className: "block w-full py-2.5 text-sm font-medium rounded-md bg-sand-900 text-white text-center hover:bg-sand-800 transition-colors duration-150",
59
+ children: "Open xAI to approve"
60
+ }),
61
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
62
+ className: "flex items-center justify-between text-[11px] text-sand-500",
63
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
64
+ className: "flex items-center gap-1.5",
65
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "w-2.5 h-2.5 border-2 border-sand-400 border-t-transparent rounded-full animate-spin" }), flow.slowDown ? "Waiting for approval (polling slowed down)..." : "Waiting for approval..."]
66
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: ["expires in ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Countdown, { expiresAt: flow.expiresAt })] })]
67
+ })
68
+ ] }),
69
+ flow.status === "added" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
70
+ className: "text-[12px] text-sage-600",
71
+ children: [
72
+ "Token \"",
73
+ flow.name,
74
+ "\" added."
75
+ ]
76
+ }),
77
+ flow.status === "expired" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
78
+ className: "text-[11px] text-red-500",
79
+ children: "Device code가 만료되었습니다 — 로그인을 다시 시작하세요."
80
+ }),
81
+ flow.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
82
+ className: "text-[11px] text-red-500",
83
+ children: flow.message
84
+ }),
85
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
86
+ type: "button",
87
+ className: "w-full py-1.5 text-[12px] text-sand-500 hover:text-sand-700 transition-colors duration-150",
88
+ onClick: onRestart,
89
+ children: "처음부터 다시 시작"
90
+ })
91
+ ]
92
+ });
93
+ }
94
+ //#endregion
95
+ //#region src/components/qgrid/AddTokenModal.tsx
96
+ function Spinner({ light }) {
97
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
98
+ className: "flex items-center gap-1.5",
99
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `w-3.5 h-3.5 border-2 border-t-transparent rounded-full animate-spin ${light ? "border-white" : "border-sand-400"}` }), "Waiting for login..."]
100
+ });
101
+ }
102
+ function OrDivider() {
103
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
104
+ className: "flex items-center gap-3",
105
+ children: [
106
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex-1 h-px bg-sand-200" }),
107
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
108
+ className: "text-[10px] text-sand-400 uppercase",
109
+ children: "or"
110
+ }),
111
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex-1 h-px bg-sand-200" })
112
+ ]
113
+ });
114
+ }
115
+ /** Provider 선택 버튼들. 상태는 상위(`useOAuthLoginFlow`)가 소유한다. */
116
+ function AddTokenProviderOptions({ disabled, loadingProvider, onStart, onStartGrokDevice }) {
117
+ const secondary = "w-full py-2.5 text-sm font-medium rounded-md border border-sand-300 text-sand-700 hover:bg-sand-100 disabled:opacity-50 transition-colors duration-150 flex items-center justify-center gap-2";
118
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
119
+ className: "space-y-2",
120
+ children: [
121
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
122
+ type: "button",
123
+ className: "w-full py-2.5 text-sm font-medium rounded-md bg-sand-900 text-white hover:bg-sand-800 disabled:opacity-50 transition-colors duration-150 flex items-center justify-center gap-2",
124
+ disabled,
125
+ onClick: () => onStart("openai"),
126
+ children: loadingProvider === "openai" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { light: true }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideKeyRound, { className: "size-4" }), "Login with OpenAI"] })
127
+ }),
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OrDivider, {}),
129
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
130
+ type: "button",
131
+ className: secondary,
132
+ disabled,
133
+ onClick: () => onStart("anthropic"),
134
+ children: loadingProvider === "anthropic" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { light: false }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideKeyRound, { className: "size-4" }), "Login with Claude"] })
135
+ }),
136
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OrDivider, {}),
137
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
138
+ type: "button",
139
+ className: secondary,
140
+ disabled,
141
+ onClick: onStartGrokDevice,
142
+ children: loadingProvider === "grok" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { light: false }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideKeyRound, { className: "size-4" }), "Login with xAI"] })
143
+ })
144
+ ]
145
+ });
146
+ }
147
+ function AddTokenModal() {
148
+ const [open, setOpen] = (0, import_react.useState)(false);
149
+ const [name, setName] = (0, import_react.useState)("");
150
+ const oauth = useOAuthLoginFlow();
151
+ const close = () => {
152
+ setOpen(false);
153
+ setName("");
154
+ oauth.reset();
155
+ };
156
+ const submitPastedCode = async (pastedCode) => {
157
+ if (await oauth.submitCode(pastedCode)) close();
158
+ };
159
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
160
+ type: "button",
161
+ className: "px-3.5 py-1.5 text-[12px] font-medium rounded-lg bg-sienna-500 text-white hover:bg-sienna-600 disabled:opacity-50 transition-all duration-150 active:scale-[0.97] flex items-center gap-1.5 shadow-sm",
162
+ onClick: () => setOpen(true),
163
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucidePlus, { className: "size-3.5" }), "Add Token"]
164
+ }), open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
165
+ className: "fixed inset-0 z-50 flex items-start justify-center pt-[15vh]",
166
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
167
+ className: "absolute inset-0 bg-sand-900/8 backdrop-blur-sm",
168
+ onClick: close,
169
+ onKeyDown: () => {}
170
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
171
+ className: "relative panel shadow-xl w-full max-w-md mx-4",
172
+ children: [
173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
174
+ className: "px-5 py-4 border-b border-sand-100/50",
175
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
176
+ className: "text-base font-medium text-sand-900",
177
+ children: "Add Token"
178
+ })
179
+ }),
180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
181
+ className: "px-5 py-4 space-y-4",
182
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
183
+ htmlFor: "token-name",
184
+ className: "text-[10px] uppercase tracking-wider text-sand-500 font-medium",
185
+ children: "Name *"
186
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(o, {
187
+ value: name,
188
+ onChange: (e) => setName(e.target.value),
189
+ placeholder: "e.g. your-token-name",
190
+ className: "mt-1 w-full border border-sand-200 rounded-md px-3 py-2 text-sm text-sand-900 bg-white placeholder:text-sand-300 focus:outline-none focus:border-sienna-300"
191
+ })] }), oauth.codeEntryProvider ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OAuthCodeEntry, {
192
+ provider: oauth.codeEntryProvider,
193
+ isPending: oauth.completeMutation.isPending,
194
+ isError: oauth.completeMutation.isError,
195
+ onSubmit: (code) => void submitPastedCode(code),
196
+ onRestart: oauth.reset
197
+ }) : oauth.deviceFlow ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeviceCodeEntry, {
198
+ flow: oauth.deviceFlow,
199
+ onRestart: oauth.reset
200
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AddTokenProviderOptions, {
201
+ disabled: !name.trim() || oauth.loadingProvider !== null,
202
+ loadingProvider: oauth.loadingProvider,
203
+ onStart: (provider) => void oauth.start(provider, name),
204
+ onStartGrokDevice: () => void oauth.startDevice(name, close)
205
+ })]
206
+ }),
207
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
208
+ className: "px-5 py-3 border-t border-sand-100/50 flex items-center justify-end gap-2",
209
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
210
+ type: "button",
211
+ className: "px-3 py-1 text-xs font-medium rounded-md border border-sand-200 text-sand-600 hover:bg-sand-100 transition-colors duration-150",
212
+ onClick: close,
213
+ children: "Cancel"
214
+ })
215
+ })
216
+ ]
217
+ })]
218
+ })] });
219
+ }
220
+ //#endregion
221
+ //#region ~icons/lucide/pencil.jsx
222
+ var lucidePencil = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
223
+ viewBox: "0 0 24 24",
224
+ width: "1.2em",
225
+ height: "1.2em",
226
+ ...props,
227
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
228
+ fill: "none",
229
+ stroke: "currentColor",
230
+ strokeLinecap: "round",
231
+ strokeLinejoin: "round",
232
+ strokeWidth: 2,
233
+ d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497zM15 5l4 4"
234
+ })
235
+ });
236
+ //#endregion
237
+ //#region ~icons/lucide/trash-2.jsx
238
+ var lucideTrash2 = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
239
+ viewBox: "0 0 24 24",
240
+ width: "1.2em",
241
+ height: "1.2em",
242
+ ...props,
243
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
244
+ fill: "none",
245
+ stroke: "currentColor",
246
+ strokeLinecap: "round",
247
+ strokeLinejoin: "round",
248
+ strokeWidth: 2,
249
+ d: "M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
250
+ })
251
+ });
252
+ //#endregion
253
+ //#region src/lib/token-label.ts
254
+ function maskAccessToken(credentials) {
255
+ const token = credentials.accessToken;
256
+ if (!token) return "—";
257
+ return token.length <= 12 ? token : `${token.slice(0, 8)}...${token.slice(-4)}`;
258
+ }
259
+ //#endregion
260
+ //#region src/components/qgrid/TokenTable.tsx
261
+ function getExpiresAt(token) {
262
+ const c = token.credentials;
263
+ if ("accessTokenExpiresAt" in c) return c.accessTokenExpiresAt;
264
+ if ("authMode" in c) {
265
+ const ms = Date.parse(c.expiresAt);
266
+ return Number.isNaN(ms) ? null : ms;
267
+ }
268
+ return c.expiresAt;
269
+ }
270
+ function formatExpiry(expiresAt) {
271
+ if (!expiresAt) return {
272
+ text: "",
273
+ color: ""
274
+ };
275
+ const diff = expiresAt - Date.now();
276
+ if (diff <= 0) return {
277
+ text: "expired",
278
+ color: "text-red-500"
279
+ };
280
+ const h = Math.floor(diff / 36e5);
281
+ const m = Math.floor(diff % 36e5 / 6e4);
282
+ const text = h > 0 ? `${h}h ${m}m` : `${m}m`;
283
+ if (diff < 36e5) return {
284
+ text,
285
+ color: "text-amber-500"
286
+ };
287
+ return {
288
+ text,
289
+ color: "text-sand-400"
290
+ };
291
+ }
292
+ function TokenTable({ data, isLoading }) {
293
+ const [deleteTarget, setDeleteTarget] = (0, import_react.useState)(null);
294
+ const [editTarget, setEditTarget] = (0, import_react.useState)(null);
295
+ const [editName, setEditName] = (0, import_react.useState)("");
296
+ const queryClient = useQueryClient();
297
+ const removeMutation = QgridService.useRemoveTokenMutation();
298
+ const updateMutation = useUpdateTokenMutation();
299
+ const toggleMutation = QgridService.useToggleTokenMutation();
300
+ const invalidate = () => Promise.all([queryClient.invalidateQueries({ queryKey: ["Token"] }), queryClient.invalidateQueries({ queryKey: ["Qgrid"] })]);
301
+ const handleToggle = async (token) => {
302
+ await toggleMutation.mutateAsync({ id: token.id });
303
+ await invalidate();
304
+ };
305
+ const handleDelete = async () => {
306
+ if (!deleteTarget) return;
307
+ await removeMutation.mutateAsync({ id: deleteTarget.id });
308
+ await invalidate();
309
+ setDeleteTarget(null);
310
+ };
311
+ const openEdit = (token) => {
312
+ setEditTarget(token);
313
+ setEditName(token.name ?? "");
314
+ };
315
+ const handleUpdate = async () => {
316
+ if (!editTarget) return;
317
+ await updateMutation.mutateAsync({
318
+ id: editTarget.id,
319
+ name: editName.trim()
320
+ });
321
+ await invalidate();
322
+ setEditTarget(null);
323
+ };
324
+ if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
325
+ className: "space-y-2",
326
+ children: Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-12 bg-sand-100 rounded-md animate-pulse" }, `skeleton-${i}`))
327
+ });
328
+ const tokens = data ?? [];
329
+ if (tokens.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
330
+ className: "text-sand-400 text-center py-16 text-sm",
331
+ children: [
332
+ "No tokens registered. Click ",
333
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", {
334
+ className: "text-sand-600",
335
+ children: "Add Token"
336
+ }),
337
+ " to get started."
338
+ ]
339
+ });
340
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
342
+ className: "panel overflow-hidden",
343
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
344
+ className: "overflow-x-auto",
345
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", {
346
+ className: "w-full min-w-[640px] text-[13px]",
347
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", {
348
+ className: "panel-header",
349
+ children: [
350
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
351
+ className: "text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium",
352
+ children: "Provider"
353
+ }),
354
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
355
+ className: "text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium",
356
+ children: "Name"
357
+ }),
358
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
359
+ className: "text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium",
360
+ children: "Token"
361
+ }),
362
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
363
+ className: "text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium",
364
+ children: "Status"
365
+ }),
366
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
367
+ className: "text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium",
368
+ children: "Expires"
369
+ }),
370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: "w-20 px-3 py-2.5" })
371
+ ]
372
+ }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", {
373
+ className: "divide-y divide-sand-100/80",
374
+ children: tokens.map((token) => {
375
+ const expiry = formatExpiry(getExpiresAt(token));
376
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", {
377
+ className: `transition-colors duration-150 hover:bg-sand-100/60 ${token.active ? "" : "opacity-50"}`,
378
+ children: [
379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
380
+ className: "px-5 py-3",
381
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
382
+ className: "text-xs font-medium text-sand-500 uppercase",
383
+ children: token.provider
384
+ })
385
+ }),
386
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
387
+ className: "px-5 py-3",
388
+ children: token.name ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
389
+ className: "text-sm text-sand-700",
390
+ children: token.name
391
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
392
+ className: "text-[11px] text-sand-400",
393
+ children: "—"
394
+ })
395
+ }),
396
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
397
+ className: "px-5 py-3",
398
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
399
+ className: "text-[13px] font-mono text-sand-800",
400
+ children: maskAccessToken(token.credentials)
401
+ })
402
+ }),
403
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
404
+ className: "px-5 py-3",
405
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
406
+ type: "button",
407
+ className: `relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-200 ${token.active ? "bg-sage-400" : "bg-sand-300"}`,
408
+ onClick: () => handleToggle(token),
409
+ disabled: toggleMutation.isPending || !token.active && token.reauth_required,
410
+ title: !token.active && token.reauth_required ? "재로그인이 필요한 토큰입니다" : void 0,
411
+ 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 ${token.active ? "translate-x-4.5" : "translate-x-0.5"}` })
412
+ })
413
+ }),
414
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
415
+ className: "px-5 py-3",
416
+ children: expiry.text && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
417
+ className: `text-xs ${expiry.color}`,
418
+ children: expiry.text
419
+ })
420
+ }),
421
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
422
+ className: "px-3 py-3",
423
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
424
+ className: "flex items-center gap-1",
425
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
426
+ type: "button",
427
+ className: "p-1 rounded text-sand-400 hover:text-sienna-500 transition-colors duration-150",
428
+ onClick: () => openEdit(token),
429
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucidePencil, { className: "size-4" })
430
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
431
+ type: "button",
432
+ className: "p-1 rounded text-sand-400 hover:text-red-500 transition-colors duration-150",
433
+ onClick: () => setDeleteTarget(token),
434
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideTrash2, { className: "size-4" })
435
+ })]
436
+ })
437
+ })
438
+ ]
439
+ }, token.id);
440
+ })
441
+ })]
442
+ })
443
+ })
444
+ }),
445
+ editTarget && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
446
+ className: "fixed inset-0 z-50 flex items-start justify-center pt-[15vh]",
447
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
448
+ className: "absolute inset-0 bg-sand-900/8 backdrop-blur-sm",
449
+ onClick: () => setEditTarget(null),
450
+ onKeyDown: () => {}
451
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
452
+ className: "relative panel shadow-xl w-full max-w-sm mx-4",
453
+ children: [
454
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
455
+ className: "px-5 py-4 border-b border-sand-100/60",
456
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
457
+ className: "text-base font-medium text-sand-900",
458
+ children: "Edit Token"
459
+ })
460
+ }),
461
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
462
+ className: "px-5 py-4 space-y-3",
463
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
464
+ htmlFor: "token-name",
465
+ className: "text-[10px] uppercase tracking-wider text-sand-500 font-medium",
466
+ children: "Name"
467
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(o, {
468
+ value: editName,
469
+ onChange: (e) => setEditName(e.target.value),
470
+ placeholder: "e.g. prod, dev, team-a",
471
+ className: "mt-1 w-full border border-sand-200 rounded-md px-3 py-2 text-sm text-sand-900 bg-white placeholder:text-sand-300 focus:outline-none focus:border-sienna-300"
472
+ })] })
473
+ }),
474
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
475
+ className: "px-5 py-3 border-t border-sand-100/60 flex items-center justify-end gap-2",
476
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
477
+ type: "button",
478
+ className: "px-3 py-1 text-xs font-medium rounded-md border border-sand-200 text-sand-600 hover:bg-sand-100 transition-colors duration-150",
479
+ onClick: () => setEditTarget(null),
480
+ children: "Cancel"
481
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
482
+ type: "button",
483
+ className: "px-3 py-1 text-xs font-medium rounded-md bg-sienna-400 text-white hover:bg-sienna-500 disabled:opacity-50 transition-colors duration-150",
484
+ disabled: updateMutation.isPending,
485
+ onClick: handleUpdate,
486
+ children: updateMutation.isPending ? "Saving..." : "Save"
487
+ })]
488
+ })
489
+ ]
490
+ })]
491
+ }),
492
+ deleteTarget && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
493
+ className: "fixed inset-0 z-50 flex items-start justify-center pt-[15vh]",
494
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
495
+ className: "absolute inset-0 bg-sand-900/8 backdrop-blur-sm",
496
+ onClick: () => setDeleteTarget(null),
497
+ onKeyDown: () => {}
498
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
499
+ className: "relative panel shadow-xl w-full max-w-sm mx-4",
500
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
501
+ className: "px-5 py-4",
502
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
503
+ className: "text-base font-medium text-sand-900",
504
+ children: "Remove Token"
505
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
506
+ className: "text-sm text-sand-700 mt-2",
507
+ children: [
508
+ "Are you sure you want to remove",
509
+ " ",
510
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
511
+ className: "text-[13px] font-mono text-sand-800",
512
+ children: maskAccessToken(deleteTarget.credentials)
513
+ }),
514
+ deleteTarget.name && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
515
+ className: "text-sand-500",
516
+ children: [
517
+ " (",
518
+ deleteTarget.name,
519
+ ")"
520
+ ]
521
+ }),
522
+ "?"
523
+ ]
524
+ })]
525
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
526
+ className: "px-5 py-3 border-t border-sand-100/60 flex items-center justify-end gap-2",
527
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
528
+ type: "button",
529
+ className: "px-3 py-1 text-xs font-medium rounded-md border border-sand-200 text-sand-600 hover:bg-sand-100 transition-colors duration-150",
530
+ onClick: () => setDeleteTarget(null),
531
+ children: "Cancel"
532
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
533
+ type: "button",
534
+ className: "px-3 py-1 text-xs font-medium rounded-md bg-red-500 text-white hover:bg-red-600 disabled:opacity-50 transition-colors duration-150",
535
+ disabled: removeMutation.isPending,
536
+ onClick: handleDelete,
537
+ children: removeMutation.isPending ? "Removing..." : "Remove"
538
+ })]
539
+ })]
540
+ })]
541
+ })
542
+ ] });
543
+ }
544
+ //#endregion
545
+ //#region src/routes/tokens.tsx?tsr-split=component
546
+ function TokensPage() {
547
+ const { data, isLoading } = TokenService.useTokens("A");
548
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
549
+ className: "max-w-6xl mx-auto space-y-4",
550
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
551
+ className: "flex items-center justify-end",
552
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AddTokenModal, {})
553
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TokenTable, {
554
+ data: data?.rows,
555
+ isLoading
556
+ })]
557
+ });
558
+ }
559
+ //#endregion
560
+ export { TokensPage as component };
@@ -1,4 +1,4 @@
1
- import { F as require_react, N as useQueryClient, P as require_jsx_runtime, k as require_jsx_dev_runtime, n as QgridService, o as TokenService, tt as __toESM } from "./services.generated-DQATJ6qf.js";
1
+ import { $ as __toESM, M as require_jsx_runtime, N as require_react, j as useQueryClient, n as QgridService, o as TokenService } from "./services.generated-CMJ8AHbO.js";
2
2
  import { t as clsx } from "./clsx-D_N5PN_2.js";
3
3
  //#region ../../node_modules/.pnpm/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs
4
4
  /**
@@ -2023,8 +2023,6 @@ var o = import_react.forwardRef(({ className: o, type: s, onValueChange: c, onCh
2023
2023
  o.displayName = "Input";
2024
2024
  //#endregion
2025
2025
  //#region src/components/qgrid/OAuthCodeEntry.tsx
2026
- var import_jsx_dev_runtime = require_jsx_dev_runtime();
2027
- var _jsxFileName = "/home/runner/work/qgrid/qgrid/packages/web/src/components/qgrid/OAuthCodeEntry.tsx";
2028
2026
  var HELP_TEXT = {
2029
2027
  openai: "새 탭에서 OpenAI 로그인을 완료한 뒤 연결 실패 화면이 나타나면, 주소창의 전체 URL을 아래에 붙여넣으세요.",
2030
2028
  anthropic: "새 탭에서 Claude 로그인을 완료하면 코드가 표시됩니다. 코드 전체를 아래에 붙여넣으세요.",
@@ -2041,58 +2039,34 @@ var PLACEHOLDER = {
2041
2039
  */
2042
2040
  function OAuthCodeEntry({ provider, isPending, isError, onSubmit, onRestart }) {
2043
2041
  const [pastedCode, setPastedCode] = (0, import_react.useState)("");
2044
- return /* @__PURE__ */ (0, import_jsx_dev_runtime.jsxDEV)("div", {
2042
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2045
2043
  className: "space-y-2",
2046
2044
  children: [
2047
- /* @__PURE__ */ (0, import_jsx_dev_runtime.jsxDEV)("p", {
2045
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
2048
2046
  className: "text-[12px] text-sand-600 leading-relaxed",
2049
2047
  children: HELP_TEXT[provider]
2050
- }, void 0, false, {
2051
- fileName: _jsxFileName,
2052
- lineNumber: 41,
2053
- columnNumber: 7
2054
- }, this),
2055
- /* @__PURE__ */ (0, import_jsx_dev_runtime.jsxDEV)(o, {
2048
+ }),
2049
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(o, {
2056
2050
  value: pastedCode,
2057
2051
  onChange: (e) => setPastedCode(e.target.value),
2058
2052
  placeholder: PLACEHOLDER[provider],
2059
2053
  className: "w-full border border-sand-200 rounded-md px-3 py-2 text-sm font-mono text-sand-900 bg-white placeholder:text-sand-300 focus:outline-none focus:border-sienna-300"
2060
- }, void 0, false, {
2061
- fileName: _jsxFileName,
2062
- lineNumber: 42,
2063
- columnNumber: 7
2064
- }, this),
2065
- isError && /* @__PURE__ */ (void 0)("p", {
2054
+ }),
2055
+ isError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
2066
2056
  className: "text-[11px] text-red-500",
2067
2057
  children: "인증 결과 확인에 실패했습니다 — 전체 값을 다시 붙여넣거나 로그인을 다시 시작하세요."
2068
- }, void 0, false, {
2069
- fileName: _jsxFileName,
2070
- lineNumber: 49,
2071
- columnNumber: 9
2072
- }, this),
2073
- /* @__PURE__ */ (0, import_jsx_dev_runtime.jsxDEV)("button", {
2058
+ }),
2059
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
2074
2060
  type: "button",
2075
2061
  className: "w-full py-2.5 text-sm font-medium rounded-md bg-sand-900 text-white hover:bg-sand-800 disabled:opacity-50 transition-colors duration-150 flex items-center justify-center gap-2",
2076
2062
  disabled: !pastedCode.trim() || isPending,
2077
2063
  onClick: () => onSubmit(pastedCode),
2078
- children: isPending ? /* @__PURE__ */ (0, import_jsx_dev_runtime.jsxDEV)("span", {
2064
+ children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2079
2065
  className: "flex items-center gap-1.5",
2080
- children: [/* @__PURE__ */ (0, import_jsx_dev_runtime.jsxDEV)("span", { className: "w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" }, void 0, false, {
2081
- fileName: _jsxFileName,
2082
- lineNumber: 61,
2083
- columnNumber: 13
2084
- }, this), "Verifying..."]
2085
- }, void 0, true, {
2086
- fileName: _jsxFileName,
2087
- lineNumber: 60,
2088
- columnNumber: 11
2089
- }, this) : "Complete login"
2090
- }, void 0, false, {
2091
- fileName: _jsxFileName,
2092
- lineNumber: 53,
2093
- columnNumber: 7
2094
- }, this),
2095
- /* @__PURE__ */ (0, import_jsx_dev_runtime.jsxDEV)("button", {
2066
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" }), "Verifying..."]
2067
+ }) : "Complete login"
2068
+ }),
2069
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
2096
2070
  type: "button",
2097
2071
  className: "w-full py-1.5 text-[12px] text-sand-500 hover:text-sand-700 transition-colors duration-150",
2098
2072
  onClick: () => {
@@ -2100,17 +2074,9 @@ function OAuthCodeEntry({ provider, isPending, isError, onSubmit, onRestart }) {
2100
2074
  onRestart();
2101
2075
  },
2102
2076
  children: "처음부터 다시 시작"
2103
- }, void 0, false, {
2104
- fileName: _jsxFileName,
2105
- lineNumber: 68,
2106
- columnNumber: 7
2107
- }, this)
2077
+ })
2108
2078
  ]
2109
- }, void 0, true, {
2110
- fileName: _jsxFileName,
2111
- lineNumber: 40,
2112
- columnNumber: 5
2113
- }, this);
2079
+ });
2114
2080
  }
2115
2081
  //#endregion
2116
2082
  //#region src/components/qgrid/use-oauth-login-flow.ts
@@ -1,4 +1,4 @@
1
- import { A as useMutation, n as QgridService } from "./services.generated-DQATJ6qf.js";
1
+ import { O as useMutation, n as QgridService } from "./services.generated-CMJ8AHbO.js";
2
2
  //#region src/services/token/use-update-token-mutation.ts
3
3
  function useUpdateTokenMutation() {
4
4
  return useMutation({ mutationFn: ({ id, name, quotaThreshold, weight, keepaliveEnabled }) => QgridService.updateToken(id, name, quotaThreshold, weight, keepaliveEnabled) });