@cartanova/qgrid-cli 2.6.1 → 2.6.4
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/README.ko.md +2 -0
- package/README.md +2 -0
- package/bundle/dist/application/qgrid/oauth.js +21 -3
- package/bundle/dist/application/qgrid/qgrid.dispatcher.js +40 -6
- package/bundle/dist/application/qgrid/qgrid.frame.js +47 -19
- package/bundle/dist/application/qgrid/qgrid.types.js +13 -3
- package/bundle/dist/application/qgrid/token-death.js +77 -0
- package/bundle/dist/application/queries.generated.js +3 -2
- package/bundle/dist/application/request-log/request-log.model.js +105 -31
- package/bundle/dist/application/request-log-step/request-log-step.model.js +1 -1
- package/bundle/dist/application/sonamu.generated.js +60 -5
- package/bundle/dist/application/sonamu.generated.sso.js +18 -2
- package/bundle/dist/application/token/token.model.js +53 -2
- package/bundle/dist/i18n/en.js +3 -1
- package/bundle/dist/i18n/ko.js +3 -1
- package/bundle/dist/i18n/sd.generated.js +18 -1
- package/bundle/dist/migrations/20260807133553_manual__backfill_request_log_cost.js +49 -0
- package/bundle/dist/migrations/20260807145144_alter_request_logs_alter1.js +13 -0
- package/bundle/dist/migrations/20260807164057_alter_request_logs_alter1.js +13 -0
- package/bundle/dist/server-error-handler.js +5 -3
- package/bundle/dist/sonamu.config.js +5 -1
- package/bundle/dist/utils/providers/openai/openai-refresh.js +12 -1
- package/bundle/dist/utils/slack-notify.js +65 -0
- package/bundle/src/application/qgrid/oauth.ts +20 -1
- package/bundle/src/application/qgrid/qgrid-oauth-redirect.test.ts +15 -3
- package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +30 -2
- package/bundle/src/application/qgrid/qgrid.dispatcher.ts +44 -6
- package/bundle/src/application/qgrid/qgrid.frame.ts +67 -47
- package/bundle/src/application/qgrid/qgrid.types.ts +10 -0
- package/bundle/src/application/qgrid/token-death.test.ts +150 -0
- package/bundle/src/application/qgrid/token-death.ts +96 -0
- package/bundle/src/application/queries.generated.ts +5 -2
- package/bundle/src/application/request-log/request-log.entity.json +46 -7
- package/bundle/src/application/request-log/request-log.model.test.ts +153 -42
- package/bundle/src/application/request-log/request-log.model.ts +136 -50
- package/bundle/src/application/sonamu.generated.http +19 -1
- package/bundle/src/application/sonamu.generated.sso.ts +16 -0
- package/bundle/src/application/sonamu.generated.ts +58 -3
- package/bundle/src/application/token/token.model.test.ts +49 -0
- package/bundle/src/application/token/token.model.ts +75 -0
- package/bundle/src/i18n/en.ts +3 -0
- package/bundle/src/i18n/ko.ts +3 -0
- package/bundle/src/i18n/sd.generated.ts +17 -0
- package/bundle/src/migrations/20260807133553_manual__backfill_request_log_cost.ts +110 -0
- package/bundle/src/migrations/20260807145144_alter_request_logs_alter1.ts +13 -0
- package/bundle/src/migrations/20260807164057_alter_request_logs_alter1.ts +13 -0
- package/bundle/src/server-error-handler.test.ts +58 -0
- package/bundle/src/server-error-handler.ts +16 -5
- package/bundle/src/sonamu.config.ts +6 -0
- package/bundle/src/utils/providers/openai/openai-refresh.ts +17 -0
- package/bundle/src/utils/slack-notify.test.ts +97 -0
- package/bundle/src/utils/slack-notify.ts +74 -0
- package/bundle/web-dist/client/assets/index-BGQSGMcK.css +1 -0
- package/bundle/web-dist/client/assets/index-CnNT0mdq.js +78 -0
- package/bundle/web-dist/client/assets/logs-DCY81UWb.js +1 -0
- package/bundle/web-dist/client/assets/{monit-CyqMmy1G.js → monit-D5OoAday.js} +1 -1
- package/bundle/web-dist/client/assets/routes-B4D9C86u.js +5 -0
- package/bundle/web-dist/client/assets/{sd.generated-CjW3rfJ8.js → sd.generated-DYdwSB2I.js} +1 -1
- package/bundle/web-dist/client/assets/{services.generated-BuT6Gp8V.js → services.generated-BsAvotjG.js} +1 -1
- package/bundle/web-dist/client/assets/{show-C5WxfoAt.js → show-BSX8ii7Z.js} +3 -3
- package/bundle/web-dist/client/assets/tokens-Gapk41cz.js +1 -0
- package/bundle/web-dist/client/assets/{use-update-token-mutation-DBkxL29Y.js → use-update-token-mutation-DqEpK7It.js} +1 -1
- package/bundle/web-dist/client/index.html +3 -3
- package/bundle/web-dist/server/assets/{lazyRouteComponent-PRxXWW3t.js → lazyRouteComponent-BaZaveNc.js} +1 -1
- package/bundle/web-dist/server/assets/{logs-D0z1Ghj1.js → logs-DMOw3SjD.js} +91 -42
- package/bundle/web-dist/server/assets/{monit-dBXxnAyI.js → monit-UmcqHroX.js} +2 -2
- package/bundle/web-dist/server/assets/{routes-B3p7fuwk.js → routes-Sv_4FjtH.js} +52 -46
- package/bundle/web-dist/server/assets/{sd.generated-ek-tz-7H.js → sd.generated-B0xqnHEo.js} +21 -0
- package/bundle/web-dist/server/assets/{services.generated-BNuxWs7K.js → services.generated-DqAKskAS.js} +23 -8
- package/bundle/web-dist/server/assets/{show-0iNioJ2I.js → show-C-oSdzHX.js} +31 -25
- package/bundle/web-dist/server/assets/{tokens-CvytQ9Xy.js → tokens-h-2A3wfD.js} +292 -191
- package/bundle/web-dist/server/assets/{use-update-token-mutation-BBCC2GKM.js → use-update-token-mutation-DHCaa7Ku.js} +1 -1
- package/bundle/web-dist/server/entry-server.generated.js +70 -29
- package/package.json +1 -1
- package/skills/qgrid/references/cli-env-and-server-boot.md +8 -0
- package/skills/qgrid/references/token-auth-quota-lifecycle.md +11 -0
- package/bundle/web-dist/client/assets/index-BGGBFmzE.js +0 -78
- package/bundle/web-dist/client/assets/index-GA9b-zy4.css +0 -1
- package/bundle/web-dist/client/assets/logs-Dh19u6Tk.js +0 -1
- package/bundle/web-dist/client/assets/routes-Cy0gCq8c.js +0 -5
- package/bundle/web-dist/client/assets/tokens-Ciolqw4l.js +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Z as __toESM, _ as require_jsx_runtime, g as useQueryClient, v as require_react } from "./sd.generated-
|
|
2
|
-
import { a as TokenService, n as QgridService } from "./services.generated-
|
|
1
|
+
import { Z as __toESM, _ as require_jsx_runtime, g as useQueryClient, v as require_react } from "./sd.generated-B0xqnHEo.js";
|
|
2
|
+
import { a as TokenService, n as QgridService } from "./services.generated-DqAKskAS.js";
|
|
3
3
|
import { i as Q, o as lucideKeyRound } from "../entry-server.generated.js";
|
|
4
|
-
import { t as useUpdateTokenMutation } from "./use-update-token-mutation-
|
|
4
|
+
import { t as useUpdateTokenMutation } from "./use-update-token-mutation-DHCaa7Ku.js";
|
|
5
5
|
//#region ~icons/lucide/plus.jsx
|
|
6
6
|
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
7
7
|
var import_jsx_runtime = require_jsx_runtime();
|
|
@@ -20,77 +20,168 @@ var lucidePlus = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
|
|
|
20
20
|
})
|
|
21
21
|
});
|
|
22
22
|
//#endregion
|
|
23
|
-
//#region src/components/qgrid/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
//#region src/components/qgrid/OAuthCodeEntry.tsx
|
|
24
|
+
/**
|
|
25
|
+
* 원격 접속(code 모드) Claude 로그인의 코드 입력 단계.
|
|
26
|
+
* Add Token 모달과 재로그인이 같은 문구·에러·재시작 동선을 쓰도록 한 곳에 둔다.
|
|
27
|
+
*/
|
|
28
|
+
function OAuthCodeEntry({ isPending, isError, onSubmit, onRestart }) {
|
|
29
29
|
const [pastedCode, setPastedCode] = (0, import_react.useState)("");
|
|
30
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
31
|
+
className: "space-y-2",
|
|
32
|
+
children: [
|
|
33
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
34
|
+
className: "text-[12px] text-sand-600 leading-relaxed",
|
|
35
|
+
children: "새 탭에서 Claude 로그인을 완료하면 코드가 표시됩니다. 코드 전체를 아래에 붙여넣으세요."
|
|
36
|
+
}),
|
|
37
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Q, {
|
|
38
|
+
value: pastedCode,
|
|
39
|
+
onChange: (e) => setPastedCode(e.target.value),
|
|
40
|
+
placeholder: "code#state",
|
|
41
|
+
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"
|
|
42
|
+
}),
|
|
43
|
+
isError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
44
|
+
className: "text-[11px] text-red-500",
|
|
45
|
+
children: "코드 확인에 실패했습니다 — 표시된 코드 전체를 다시 붙여넣거나 로그인을 다시 시작하세요."
|
|
46
|
+
}),
|
|
47
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
48
|
+
type: "button",
|
|
49
|
+
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",
|
|
50
|
+
disabled: !pastedCode.trim() || isPending,
|
|
51
|
+
onClick: () => onSubmit(pastedCode),
|
|
52
|
+
children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
53
|
+
className: "flex items-center gap-1.5",
|
|
54
|
+
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..."]
|
|
55
|
+
}) : "Complete login"
|
|
56
|
+
}),
|
|
57
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
58
|
+
type: "button",
|
|
59
|
+
className: "w-full py-1.5 text-[12px] text-sand-500 hover:text-sand-700 transition-colors duration-150",
|
|
60
|
+
onClick: () => {
|
|
61
|
+
setPastedCode("");
|
|
62
|
+
onRestart();
|
|
63
|
+
},
|
|
64
|
+
children: "처음부터 다시 시작"
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/components/qgrid/use-oauth-login-flow.ts
|
|
71
|
+
var OPENAI_POLL_INTERVAL_MS = 3e3;
|
|
72
|
+
var OPENAI_POLL_TIMEOUT_MS = 3e5;
|
|
73
|
+
/** 캐시에 적재된 토큰 행 수. 폴링 조기 종료 판정에만 쓰는 근사치다. */
|
|
74
|
+
function countTokens(queryClient) {
|
|
75
|
+
let total = 0;
|
|
76
|
+
for (const query of queryClient.getQueryCache().findAll({ queryKey: ["Token"] })) {
|
|
77
|
+
const rows = query.state.data?.rows;
|
|
78
|
+
if (Array.isArray(rows)) total = Math.max(total, rows.length);
|
|
79
|
+
}
|
|
80
|
+
return total;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* provider별 OAuth 로그인 시작과 코드 제출을 한 곳에 모은다.
|
|
84
|
+
* Add Token 모달과 tokens 목록의 재로그인 버튼이 같은 플로우를 쓴다 — 두 진입점이
|
|
85
|
+
* redirect/code 분기나 폴링 처리를 각자 구현하면 원격 접속 경험이 갈라진다.
|
|
86
|
+
*/
|
|
87
|
+
function useOAuthLoginFlow() {
|
|
88
|
+
const [loadingProvider, setLoadingProvider] = (0, import_react.useState)(null);
|
|
89
|
+
const [codeEntry, setCodeEntry] = (0, import_react.useState)(false);
|
|
30
90
|
const queryClient = useQueryClient();
|
|
31
91
|
const oauthStartMutation = QgridService.useOauthStartMutation();
|
|
32
92
|
const oauthStartOpenAIMutation = QgridService.useOauthStartOpenAIMutation();
|
|
33
93
|
const oauthCompleteMutation = QgridService.useOauthCompleteMutation();
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
94
|
+
const pollTimers = (0, import_react.useRef)({});
|
|
95
|
+
const stopPolling = () => {
|
|
96
|
+
if (pollTimers.current.interval !== void 0) clearInterval(pollTimers.current.interval);
|
|
97
|
+
if (pollTimers.current.timeout !== void 0) clearTimeout(pollTimers.current.timeout);
|
|
98
|
+
pollTimers.current = {};
|
|
99
|
+
};
|
|
100
|
+
(0, import_react.useEffect)(() => stopPolling, []);
|
|
101
|
+
const invalidateTokens = () => Promise.all([queryClient.invalidateQueries({ queryKey: ["Token"] }), queryClient.invalidateQueries({ queryKey: ["Qgrid"] })]);
|
|
102
|
+
const start = async (provider, name) => {
|
|
103
|
+
const trimmed = name.trim();
|
|
104
|
+
if (!trimmed) return;
|
|
105
|
+
const popup = window.open("about:blank", "_blank");
|
|
106
|
+
setLoadingProvider(provider);
|
|
107
|
+
try {
|
|
108
|
+
if (provider === "openai") {
|
|
109
|
+
const { authUrl } = await oauthStartOpenAIMutation.mutateAsync({ name: trimmed });
|
|
41
110
|
if (popup) popup.location.href = authUrl;
|
|
42
111
|
else window.open(authUrl, "_blank");
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
112
|
+
const before = countTokens(queryClient);
|
|
113
|
+
stopPolling();
|
|
114
|
+
pollTimers.current.interval = window.setInterval(() => {
|
|
115
|
+
invalidateTokens().then(() => {
|
|
116
|
+
if (countTokens(queryClient) > before) {
|
|
117
|
+
stopPolling();
|
|
118
|
+
setLoadingProvider(null);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}, OPENAI_POLL_INTERVAL_MS);
|
|
122
|
+
pollTimers.current.timeout = window.setTimeout(() => {
|
|
123
|
+
stopPolling();
|
|
124
|
+
setLoadingProvider(null);
|
|
125
|
+
}, OPENAI_POLL_TIMEOUT_MS);
|
|
126
|
+
return;
|
|
54
127
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
else window.open(authUrl, "_blank");
|
|
63
|
-
setCodeEntry(true);
|
|
64
|
-
setOauthLoading(null);
|
|
65
|
-
} else {
|
|
66
|
-
popup?.close();
|
|
67
|
-
window.location.href = authUrl;
|
|
68
|
-
}
|
|
69
|
-
} catch (e) {
|
|
128
|
+
const { authUrl, mode } = await oauthStartMutation.mutateAsync({ name: trimmed });
|
|
129
|
+
if (mode === "code") {
|
|
130
|
+
if (popup) popup.location.href = authUrl;
|
|
131
|
+
else window.open(authUrl, "_blank");
|
|
132
|
+
setCodeEntry(true);
|
|
133
|
+
setLoadingProvider(null);
|
|
134
|
+
} else {
|
|
70
135
|
popup?.close();
|
|
71
|
-
|
|
72
|
-
setOauthLoading(null);
|
|
136
|
+
window.location.href = authUrl;
|
|
73
137
|
}
|
|
138
|
+
} catch (e) {
|
|
139
|
+
popup?.close();
|
|
140
|
+
console.error("OAuth start failed:", e);
|
|
141
|
+
setLoadingProvider(null);
|
|
74
142
|
}
|
|
75
143
|
};
|
|
76
|
-
|
|
77
|
-
|
|
144
|
+
/** @returns 교환 성공 여부. 실패 시 `completeMutation.isError` 로 안내 문구를 띄운다. */
|
|
145
|
+
const submitCode = async (pastedCode) => {
|
|
146
|
+
const trimmed = pastedCode.trim();
|
|
147
|
+
if (!trimmed) return false;
|
|
78
148
|
try {
|
|
79
|
-
await oauthCompleteMutation.mutateAsync({ pastedCode:
|
|
149
|
+
await oauthCompleteMutation.mutateAsync({ pastedCode: trimmed });
|
|
80
150
|
} catch (e) {
|
|
81
151
|
console.error("OAuth complete failed:", e);
|
|
82
|
-
return;
|
|
152
|
+
return false;
|
|
83
153
|
}
|
|
84
|
-
await
|
|
85
|
-
|
|
154
|
+
await invalidateTokens();
|
|
155
|
+
return true;
|
|
156
|
+
};
|
|
157
|
+
const reset = () => {
|
|
158
|
+
stopPolling();
|
|
159
|
+
setLoadingProvider(null);
|
|
160
|
+
setCodeEntry(false);
|
|
161
|
+
oauthCompleteMutation.reset();
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
loadingProvider,
|
|
165
|
+
codeEntry,
|
|
166
|
+
start,
|
|
167
|
+
submitCode,
|
|
168
|
+
reset,
|
|
169
|
+
completeMutation: oauthCompleteMutation
|
|
86
170
|
};
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/components/qgrid/AddTokenModal.tsx
|
|
174
|
+
function AddTokenModal() {
|
|
175
|
+
const [open, setOpen] = (0, import_react.useState)(false);
|
|
176
|
+
const [name, setName] = (0, import_react.useState)("");
|
|
177
|
+
const oauth = useOAuthLoginFlow();
|
|
87
178
|
const close = () => {
|
|
88
179
|
setOpen(false);
|
|
89
180
|
setName("");
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
181
|
+
oauth.reset();
|
|
182
|
+
};
|
|
183
|
+
const submitPastedCode = async (pastedCode) => {
|
|
184
|
+
if (await oauth.submitCode(pastedCode)) close();
|
|
94
185
|
};
|
|
95
186
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
96
187
|
type: "button",
|
|
@@ -124,53 +215,20 @@ function AddTokenModal() {
|
|
|
124
215
|
onChange: (e) => setName(e.target.value),
|
|
125
216
|
placeholder: "e.g. your-token-name",
|
|
126
217
|
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"
|
|
127
|
-
})] }), codeEntry ? /* @__PURE__ */ (0, import_jsx_runtime.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
children: "새 탭에서 Claude 로그인을 완료하면 코드가 표시됩니다. 코드 전체를 아래에 붙여넣으세요."
|
|
133
|
-
}),
|
|
134
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Q, {
|
|
135
|
-
value: pastedCode,
|
|
136
|
-
onChange: (e) => setPastedCode(e.target.value),
|
|
137
|
-
placeholder: "code#state",
|
|
138
|
-
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"
|
|
139
|
-
}),
|
|
140
|
-
oauthCompleteMutation.isError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
141
|
-
className: "text-[11px] text-red-500",
|
|
142
|
-
children: "코드 확인에 실패했습니다 — 표시된 코드 전체를 다시 붙여넣거나 로그인을 다시 시작하세요."
|
|
143
|
-
}),
|
|
144
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
145
|
-
type: "button",
|
|
146
|
-
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",
|
|
147
|
-
disabled: !pastedCode.trim() || oauthCompleteMutation.isPending,
|
|
148
|
-
onClick: submitPastedCode,
|
|
149
|
-
children: oauthCompleteMutation.isPending ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
150
|
-
className: "flex items-center gap-1.5",
|
|
151
|
-
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..."]
|
|
152
|
-
}) : "Complete login"
|
|
153
|
-
}),
|
|
154
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
155
|
-
type: "button",
|
|
156
|
-
className: "w-full py-1.5 text-[12px] text-sand-500 hover:text-sand-700 transition-colors duration-150",
|
|
157
|
-
onClick: () => {
|
|
158
|
-
setCodeEntry(false);
|
|
159
|
-
setPastedCode("");
|
|
160
|
-
oauthCompleteMutation.reset();
|
|
161
|
-
},
|
|
162
|
-
children: "처음부터 다시 시작"
|
|
163
|
-
})
|
|
164
|
-
]
|
|
218
|
+
})] }), oauth.codeEntry ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OAuthCodeEntry, {
|
|
219
|
+
isPending: oauth.completeMutation.isPending,
|
|
220
|
+
isError: oauth.completeMutation.isError,
|
|
221
|
+
onSubmit: (code) => void submitPastedCode(code),
|
|
222
|
+
onRestart: oauth.reset
|
|
165
223
|
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
166
224
|
className: "space-y-2",
|
|
167
225
|
children: [
|
|
168
226
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
169
227
|
type: "button",
|
|
170
228
|
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",
|
|
171
|
-
disabled: !name.trim() ||
|
|
172
|
-
onClick: () =>
|
|
173
|
-
children:
|
|
229
|
+
disabled: !name.trim() || oauth.loadingProvider !== null,
|
|
230
|
+
onClick: () => void oauth.start("openai", name),
|
|
231
|
+
children: oauth.loadingProvider === "openai" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
174
232
|
className: "flex items-center gap-1.5",
|
|
175
233
|
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" }), "Waiting for login..."]
|
|
176
234
|
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideKeyRound, { className: "size-4" }), "Login with OpenAI"] })
|
|
@@ -189,9 +247,9 @@ function AddTokenModal() {
|
|
|
189
247
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
190
248
|
type: "button",
|
|
191
249
|
className: "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",
|
|
192
|
-
disabled: !name.trim() ||
|
|
193
|
-
onClick: () =>
|
|
194
|
-
children:
|
|
250
|
+
disabled: !name.trim() || oauth.loadingProvider !== null,
|
|
251
|
+
onClick: () => void oauth.start("anthropic", name),
|
|
252
|
+
children: oauth.loadingProvider === "anthropic" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
195
253
|
className: "flex items-center gap-1.5",
|
|
196
254
|
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" }), "Waiting for login..."]
|
|
197
255
|
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideKeyRound, { className: "size-4" }), "Login with Claude"] })
|
|
@@ -287,6 +345,7 @@ function TokenTable({ data, isLoading }) {
|
|
|
287
345
|
const removeMutation = QgridService.useRemoveTokenMutation();
|
|
288
346
|
const updateMutation = useUpdateTokenMutation();
|
|
289
347
|
const toggleMutation = QgridService.useToggleTokenMutation();
|
|
348
|
+
const oauth = useOAuthLoginFlow();
|
|
290
349
|
const invalidate = () => Promise.all([queryClient.invalidateQueries({ queryKey: ["Token"] }), queryClient.invalidateQueries({ queryKey: ["Qgrid"] })]);
|
|
291
350
|
const handleToggle = async (token) => {
|
|
292
351
|
await toggleMutation.mutateAsync({ id: token.id });
|
|
@@ -298,6 +357,9 @@ function TokenTable({ data, isLoading }) {
|
|
|
298
357
|
await invalidate();
|
|
299
358
|
setDeleteTarget(null);
|
|
300
359
|
};
|
|
360
|
+
const submitReloginCode = async (pastedCode) => {
|
|
361
|
+
if (await oauth.submitCode(pastedCode)) oauth.reset();
|
|
362
|
+
};
|
|
301
363
|
const openEdit = (token) => {
|
|
302
364
|
setEditTarget(token);
|
|
303
365
|
setEditName(token.name ?? "");
|
|
@@ -330,103 +392,142 @@ function TokenTable({ data, isLoading }) {
|
|
|
330
392
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
331
393
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
332
394
|
className: "panel overflow-hidden",
|
|
333
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.
|
|
334
|
-
className: "
|
|
335
|
-
children:
|
|
336
|
-
className: "
|
|
337
|
-
children: [
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
className: "
|
|
371
|
-
children:
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
className: "
|
|
378
|
-
children: token.name
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
className: "
|
|
388
|
-
children:
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
onClick: () => handleToggle(token),
|
|
397
|
-
disabled: toggleMutation.isPending,
|
|
398
|
-
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"}` })
|
|
399
|
-
})
|
|
400
|
-
}),
|
|
401
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
402
|
-
className: "px-5 py-3",
|
|
403
|
-
children: expiry.text && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
404
|
-
className: `text-xs ${expiry.color}`,
|
|
405
|
-
children: expiry.text
|
|
406
|
-
})
|
|
407
|
-
}),
|
|
408
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
409
|
-
className: "px-3 py-3",
|
|
410
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
411
|
-
className: "flex items-center gap-1",
|
|
412
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
413
|
-
type: "button",
|
|
414
|
-
className: "p-1 rounded text-sand-400 hover:text-sienna-500 transition-colors duration-150",
|
|
415
|
-
onClick: () => openEdit(token),
|
|
416
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucidePencil, { className: "size-4" })
|
|
417
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
395
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
396
|
+
className: "overflow-x-auto",
|
|
397
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", {
|
|
398
|
+
className: "w-full min-w-[640px] text-[13px]",
|
|
399
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", {
|
|
400
|
+
className: "panel-header",
|
|
401
|
+
children: [
|
|
402
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
403
|
+
className: "text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium",
|
|
404
|
+
children: "Provider"
|
|
405
|
+
}),
|
|
406
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
407
|
+
className: "text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium",
|
|
408
|
+
children: "Name"
|
|
409
|
+
}),
|
|
410
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
411
|
+
className: "text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium",
|
|
412
|
+
children: "Token"
|
|
413
|
+
}),
|
|
414
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
415
|
+
className: "text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium",
|
|
416
|
+
children: "Status"
|
|
417
|
+
}),
|
|
418
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
419
|
+
className: "text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium",
|
|
420
|
+
children: "Expires"
|
|
421
|
+
}),
|
|
422
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: "w-20 px-3 py-2.5" })
|
|
423
|
+
]
|
|
424
|
+
}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", {
|
|
425
|
+
className: "divide-y divide-sand-100/80",
|
|
426
|
+
children: tokens.map((token) => {
|
|
427
|
+
const expiry = formatExpiry(getExpiresAt(token));
|
|
428
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", {
|
|
429
|
+
className: `transition-colors duration-150 hover:bg-sand-100/60 ${token.active ? "" : "opacity-50"}`,
|
|
430
|
+
children: [
|
|
431
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
432
|
+
className: "px-5 py-3",
|
|
433
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
434
|
+
className: "text-xs font-medium text-sand-500 uppercase",
|
|
435
|
+
children: token.provider
|
|
436
|
+
})
|
|
437
|
+
}),
|
|
438
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
439
|
+
className: "px-5 py-3",
|
|
440
|
+
children: token.name ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
441
|
+
className: "text-sm text-sand-700",
|
|
442
|
+
children: token.name
|
|
443
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
444
|
+
className: "text-[11px] text-sand-400",
|
|
445
|
+
children: "—"
|
|
446
|
+
})
|
|
447
|
+
}),
|
|
448
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
449
|
+
className: "px-5 py-3",
|
|
450
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
|
|
451
|
+
className: "text-[13px] font-mono text-sand-800",
|
|
452
|
+
children: maskAccessToken(token)
|
|
453
|
+
})
|
|
454
|
+
}),
|
|
455
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
456
|
+
className: "px-5 py-3",
|
|
457
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
418
458
|
type: "button",
|
|
419
|
-
className:
|
|
420
|
-
onClick: () =>
|
|
421
|
-
|
|
422
|
-
|
|
459
|
+
className: `relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-200 ${token.active ? "bg-sage-400" : "bg-sand-300"}`,
|
|
460
|
+
onClick: () => handleToggle(token),
|
|
461
|
+
disabled: toggleMutation.isPending,
|
|
462
|
+
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"}` })
|
|
463
|
+
})
|
|
464
|
+
}),
|
|
465
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
466
|
+
className: "px-5 py-3",
|
|
467
|
+
children: expiry.text && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
468
|
+
className: `text-xs ${expiry.color}`,
|
|
469
|
+
children: expiry.text
|
|
470
|
+
})
|
|
471
|
+
}),
|
|
472
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
473
|
+
className: "px-3 py-3",
|
|
474
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
475
|
+
className: "flex items-center gap-1",
|
|
476
|
+
children: [
|
|
477
|
+
!token.active && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
478
|
+
type: "button",
|
|
479
|
+
title: "기존 이름으로 다시 로그인",
|
|
480
|
+
className: "px-2 py-0.5 mr-1 text-[11px] font-medium rounded-md border border-sand-300 text-sand-600 hover:bg-sand-100 disabled:opacity-50 transition-colors duration-150 inline-flex items-center gap-1",
|
|
481
|
+
disabled: oauth.loadingProvider !== null,
|
|
482
|
+
onClick: () => void oauth.start(token.provider, token.name ?? ""),
|
|
483
|
+
children: oauth.loadingProvider === token.provider ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "w-3 h-3 border-2 border-sand-400 border-t-transparent rounded-full animate-spin" }), "로그인 대기"] }) : "재로그인"
|
|
484
|
+
}),
|
|
485
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
486
|
+
type: "button",
|
|
487
|
+
className: "p-1 rounded text-sand-400 hover:text-sienna-500 transition-colors duration-150",
|
|
488
|
+
onClick: () => openEdit(token),
|
|
489
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucidePencil, { className: "size-4" })
|
|
490
|
+
}),
|
|
491
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
492
|
+
type: "button",
|
|
493
|
+
className: "p-1 rounded text-sand-400 hover:text-red-500 transition-colors duration-150",
|
|
494
|
+
onClick: () => setDeleteTarget(token),
|
|
495
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideTrash2, { className: "size-4" })
|
|
496
|
+
})
|
|
497
|
+
]
|
|
498
|
+
})
|
|
423
499
|
})
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
500
|
+
]
|
|
501
|
+
}, token.id);
|
|
502
|
+
})
|
|
503
|
+
})]
|
|
504
|
+
})
|
|
505
|
+
})
|
|
506
|
+
}),
|
|
507
|
+
oauth.codeEntry && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
508
|
+
className: "fixed inset-0 z-50 flex items-start justify-center pt-[15vh]",
|
|
509
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
510
|
+
className: "absolute inset-0 bg-sand-900/8 backdrop-blur-sm",
|
|
511
|
+
onClick: oauth.reset,
|
|
512
|
+
onKeyDown: () => {}
|
|
513
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
514
|
+
className: "relative panel shadow-xl w-full max-w-md mx-4",
|
|
515
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
516
|
+
className: "px-5 py-4 border-b border-sand-100/60",
|
|
517
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
|
|
518
|
+
className: "text-base font-medium text-sand-900",
|
|
519
|
+
children: "재로그인"
|
|
520
|
+
})
|
|
521
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
522
|
+
className: "px-5 py-4",
|
|
523
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OAuthCodeEntry, {
|
|
524
|
+
isPending: oauth.completeMutation.isPending,
|
|
525
|
+
isError: oauth.completeMutation.isError,
|
|
526
|
+
onSubmit: (code) => void submitReloginCode(code),
|
|
527
|
+
onRestart: oauth.reset
|
|
427
528
|
})
|
|
428
529
|
})]
|
|
429
|
-
})
|
|
530
|
+
})]
|
|
430
531
|
}),
|
|
431
532
|
editTarget && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
432
533
|
className: "fixed inset-0 z-50 flex items-start justify-center pt-[15vh]",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as QgridService, o as useMutation } from "./services.generated-
|
|
1
|
+
import { n as QgridService, o as useMutation } from "./services.generated-DqAKskAS.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 }) => QgridService.updateToken(id, name, quotaThreshold, weight) });
|