@cartanova/qgrid-cli 2.6.0 → 2.6.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 (29) hide show
  1. package/bundle/dist/application/qgrid/oauth.js +3 -2
  2. package/bundle/dist/application/qgrid/qgrid.frame.js +81 -24
  3. package/bundle/dist/application/qgrid/qgrid.types.js +5 -2
  4. package/bundle/src/application/qgrid/oauth.ts +3 -0
  5. package/bundle/src/application/qgrid/qgrid-oauth-redirect.test.ts +118 -27
  6. package/bundle/src/application/qgrid/qgrid.frame.ts +95 -40
  7. package/bundle/src/application/qgrid/qgrid.types.ts +3 -0
  8. package/bundle/src/application/sonamu.generated.http +9 -0
  9. package/bundle/web-dist/client/assets/{index-YowQRE2y.js → index-BGGBFmzE.js} +3 -3
  10. package/bundle/web-dist/client/assets/{logs-o8mm530D.js → logs-Dh19u6Tk.js} +1 -1
  11. package/bundle/web-dist/client/assets/{monit-B51yXcqk.js → monit-CyqMmy1G.js} +1 -1
  12. package/bundle/web-dist/client/assets/{routes-BqaUiBc0.js → routes-Cy0gCq8c.js} +1 -1
  13. package/bundle/web-dist/client/assets/{services.generated-D3MH3EfY.js → services.generated-BuT6Gp8V.js} +1 -1
  14. package/bundle/web-dist/client/assets/{show-LAFY13v6.js → show-C5WxfoAt.js} +1 -1
  15. package/bundle/web-dist/client/assets/tokens-Ciolqw4l.js +1 -0
  16. package/bundle/web-dist/client/assets/{use-update-token-mutation-CLhv6SUL.js → use-update-token-mutation-DBkxL29Y.js} +1 -1
  17. package/bundle/web-dist/client/index.html +1 -1
  18. package/bundle/web-dist/server/assets/{logs-Dbk2Ig2z.js → logs-D0z1Ghj1.js} +1 -1
  19. package/bundle/web-dist/server/assets/{monit-Hm9dIPhx.js → monit-dBXxnAyI.js} +1 -1
  20. package/bundle/web-dist/server/assets/{routes-DztxeUjk.js → routes-B3p7fuwk.js} +2 -2
  21. package/bundle/web-dist/server/assets/{services.generated-1Kcul41h.js → services.generated-BNuxWs7K.js} +9 -0
  22. package/bundle/web-dist/server/assets/{show-BHUSw7qv.js → show-0iNioJ2I.js} +1 -1
  23. package/bundle/web-dist/server/assets/{tokens-DHU9lUT2.js → tokens-CvytQ9Xy.js} +71 -6
  24. package/bundle/web-dist/server/assets/{use-update-token-mutation-axqnye4L.js → use-update-token-mutation-BBCC2GKM.js} +1 -1
  25. package/bundle/web-dist/server/entry-server.generated.js +6 -6
  26. package/package.json +1 -1
  27. package/skills/qgrid/references/cli-env-and-server-boot.md +1 -1
  28. package/skills/qgrid/references/token-auth-quota-lifecycle.md +5 -1
  29. package/bundle/web-dist/client/assets/tokens-B9Ig7-9Z.js +0 -1
@@ -1,7 +1,7 @@
1
1
  import { Z as __toESM, _ as require_jsx_runtime, g as useQueryClient, v as require_react } from "./sd.generated-ek-tz-7H.js";
2
- import { a as TokenService, n as QgridService } from "./services.generated-1Kcul41h.js";
2
+ import { a as TokenService, n as QgridService } from "./services.generated-BNuxWs7K.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-axqnye4L.js";
4
+ import { t as useUpdateTokenMutation } from "./use-update-token-mutation-BBCC2GKM.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();
@@ -25,9 +25,12 @@ function AddTokenModal() {
25
25
  const [open, setOpen] = (0, import_react.useState)(false);
26
26
  const [oauthLoading, setOauthLoading] = (0, import_react.useState)(null);
27
27
  const [name, setName] = (0, import_react.useState)("");
28
+ const [codeEntry, setCodeEntry] = (0, import_react.useState)(false);
29
+ const [pastedCode, setPastedCode] = (0, import_react.useState)("");
28
30
  const queryClient = useQueryClient();
29
31
  const oauthStartMutation = QgridService.useOauthStartMutation();
30
32
  const oauthStartOpenAIMutation = QgridService.useOauthStartOpenAIMutation();
33
+ const oauthCompleteMutation = QgridService.useOauthCompleteMutation();
31
34
  const handleOAuthLogin = async (provider) => {
32
35
  if (!name.trim()) return;
33
36
  if (provider === "openai") {
@@ -50,20 +53,44 @@ function AddTokenModal() {
50
53
  setOauthLoading(null);
51
54
  }
52
55
  } else {
56
+ const popup = window.open("about:blank", "_blank");
53
57
  setOauthLoading("anthropic");
54
58
  try {
55
- const { authUrl } = await oauthStartMutation.mutateAsync({ name: name.trim() });
56
- window.location.href = authUrl;
59
+ const { authUrl, mode } = await oauthStartMutation.mutateAsync({ name: name.trim() });
60
+ if (mode === "code") {
61
+ if (popup) popup.location.href = authUrl;
62
+ else window.open(authUrl, "_blank");
63
+ setCodeEntry(true);
64
+ setOauthLoading(null);
65
+ } else {
66
+ popup?.close();
67
+ window.location.href = authUrl;
68
+ }
57
69
  } catch (e) {
70
+ popup?.close();
58
71
  console.error("OAuth start failed:", e);
59
72
  setOauthLoading(null);
60
73
  }
61
74
  }
62
75
  };
76
+ const submitPastedCode = async () => {
77
+ if (!pastedCode.trim()) return;
78
+ try {
79
+ await oauthCompleteMutation.mutateAsync({ pastedCode: pastedCode.trim() });
80
+ } catch (e) {
81
+ console.error("OAuth complete failed:", e);
82
+ return;
83
+ }
84
+ await Promise.all([queryClient.invalidateQueries({ queryKey: ["Token"] }), queryClient.invalidateQueries({ queryKey: ["Qgrid"] })]);
85
+ close();
86
+ };
63
87
  const close = () => {
64
88
  setOpen(false);
65
89
  setName("");
66
90
  setOauthLoading(null);
91
+ setCodeEntry(false);
92
+ setPastedCode("");
93
+ oauthCompleteMutation.reset();
67
94
  };
68
95
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
69
96
  type: "button",
@@ -97,7 +124,45 @@ function AddTokenModal() {
97
124
  onChange: (e) => setName(e.target.value),
98
125
  placeholder: "e.g. your-token-name",
99
126
  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"
100
- })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
127
+ })] }), codeEntry ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
128
+ className: "space-y-2",
129
+ children: [
130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
131
+ className: "text-[12px] text-sand-600 leading-relaxed",
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
+ ]
165
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
101
166
  className: "space-y-2",
102
167
  children: [
103
168
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
@@ -128,7 +193,7 @@ function AddTokenModal() {
128
193
  onClick: () => handleOAuthLogin("anthropic"),
129
194
  children: oauthLoading === "anthropic" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
130
195
  className: "flex items-center gap-1.5",
131
- 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" }), "Redirecting..."]
196
+ 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..."]
132
197
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(lucideKeyRound, { className: "size-4" }), "Login with Claude"] })
133
198
  })
134
199
  ]
@@ -1,4 +1,4 @@
1
- import { n as QgridService, o as useMutation } from "./services.generated-1Kcul41h.js";
1
+ import { n as QgridService, o as useMutation } from "./services.generated-BNuxWs7K.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) });
@@ -22703,7 +22703,7 @@ var sections = [{
22703
22703
  }
22704
22704
  ]
22705
22705
  }];
22706
- var qgridVersion = "2.6.0";
22706
+ var qgridVersion = "2.6.1";
22707
22707
  function Sidebar({ className }) {
22708
22708
  const pathname = useRouterState({ select: (s) => s.location.pathname });
22709
22709
  const isActive = (path) => {
@@ -44584,11 +44584,11 @@ function RootComponent() {
44584
44584
  }
44585
44585
  //#endregion
44586
44586
  //#region src/routes/tokens.tsx
44587
- var $$splitComponentImporter$4 = () => import("./assets/tokens-DHU9lUT2.js");
44587
+ var $$splitComponentImporter$4 = () => import("./assets/tokens-CvytQ9Xy.js");
44588
44588
  var Route$4 = createFileRoute("/tokens")({ component: lazyRouteComponent($$splitComponentImporter$4, "component") });
44589
44589
  //#endregion
44590
44590
  //#region src/routes/monit.tsx
44591
- var $$splitComponentImporter$3 = () => import("./assets/monit-Hm9dIPhx.js");
44591
+ var $$splitComponentImporter$3 = () => import("./assets/monit-dBXxnAyI.js");
44592
44592
  var monitSearchSchema = object({ level: _enum([
44593
44593
  "all",
44594
44594
  "scaling",
@@ -44601,7 +44601,7 @@ var Route$3 = createFileRoute("/monit")({
44601
44601
  });
44602
44602
  //#endregion
44603
44603
  //#region src/routes/logs.tsx
44604
- var $$splitComponentImporter$2 = () => import("./assets/logs-Dbk2Ig2z.js");
44604
+ var $$splitComponentImporter$2 = () => import("./assets/logs-D0z1Ghj1.js");
44605
44605
  var logsSearchSchema = object({
44606
44606
  token: string().optional(),
44607
44607
  project: string().optional(),
@@ -44613,11 +44613,11 @@ var Route$2 = createFileRoute("/logs")({
44613
44613
  });
44614
44614
  //#endregion
44615
44615
  //#region src/routes/index.tsx
44616
- var $$splitComponentImporter$1 = () => import("./assets/routes-DztxeUjk.js");
44616
+ var $$splitComponentImporter$1 = () => import("./assets/routes-B3p7fuwk.js");
44617
44617
  var Route$1 = createFileRoute("/")({ component: lazyRouteComponent($$splitComponentImporter$1, "component") });
44618
44618
  //#endregion
44619
44619
  //#region src/routes/requests/show.tsx
44620
- var $$splitComponentImporter = () => import("./assets/show-BHUSw7qv.js");
44620
+ var $$splitComponentImporter = () => import("./assets/show-0iNioJ2I.js");
44621
44621
  var showSearchSchema = object({ id: number() });
44622
44622
  var Route = createFileRoute("/requests/show")({
44623
44623
  validateSearch: showSearchSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cartanova/qgrid-cli",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cartanova-ai/qgrid"
@@ -90,5 +90,5 @@ On shutdown, it stops provider dispatchers and the token subscriber.
90
90
 
91
91
  ## OAuth callbacks
92
92
 
93
- - Anthropic OAuth uses `/callback` on the qgrid server. The callback base URL is derived per request from the browser-sent `Origin` header (falling back to `X-Forwarded-Host`/`Host`), so no env configuration is needed; direct non-HTTP calls fall back to localhost and `PORT`.
93
+ - Anthropic OAuth: loopback dashboards use `/callback` on the qgrid server (base derived per request from `Origin`, falling back to `X-Forwarded-Host`/`Host`). Remote dashboards cannot use a public callback (client redirect-URI allowlist), so `oauthStart` returns `mode: "code"` with the console callback and the user pastes the shown `code#state` into the dashboard (`oauthComplete`). No env configuration either way; direct non-HTTP calls fall back to localhost and `PORT`.
94
94
  - OpenAI OAuth is handled by Codex app-server's own callback flow and completed through qgrid's OpenAI OAuth APIs.
@@ -94,7 +94,11 @@ Anthropic OAuth is implemented directly in qgrid.
94
94
  6. Existing Anthropic tokens with the same `accountUuid` are deleted.
95
95
  7. qgrid saves a new `provider: "anthropic"` token.
96
96
 
97
- The callback base is derived per request from the `Origin` header (falling back to `X-Forwarded-Host`/`Host`) — the address the dashboard is reached at is where the callback returns. Without an HTTP context it falls back to `http://localhost:${PORT}/callback`.
97
+ The Anthropic OAuth client enforces a redirect-URI allowlist: loopback (`http://localhost:*/callback`) and the console callback (`https://console.anthropic.com/oauth/code/callback`) only — arbitrary public URLs cannot be registered. qgrid therefore picks the flow per request from the `Origin` header (falling back to `X-Forwarded-Host`/`Host`):
98
+
99
+ - Loopback origin: redirect flow — the callback returns to the request-derived base URL (`mode: "redirect"`).
100
+ - Remote origin: code flow — `redirect_uri` is the console callback; after login the console page shows `code#state`, which the user pastes into the dashboard and `oauthComplete` exchanges (`mode: "code"`, same flow as Claude Code CLI).
101
+ - No HTTP context (direct calls/tests): falls back to `http://localhost:${PORT}/callback`.
98
102
 
99
103
  Refresh:
100
104
 
@@ -1 +0,0 @@
1
- import{r as e}from"./rolldown-runtime-Dw2cE7zH.js";import{y as t}from"./vendor-tanstack-DJRQAbrY.js";import{i as n,r}from"./vendor-react-DH4kUfK2.js";import{i,o as a}from"./index-YowQRE2y.js";import{a as o,n as s}from"./services.generated-D3MH3EfY.js";import{t as c}from"./use-update-token-mutation-CLhv6SUL.js";var l=e(n()),u=r(),d=e=>(0,u.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`1.2em`,height:`1.2em`,...e,children:(0,u.jsx)(`path`,{fill:`none`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:2,d:`M5 12h14m-7-7v14`})});function f(){let[e,n]=(0,l.useState)(!1),[r,o]=(0,l.useState)(null),[c,f]=(0,l.useState)(``),p=t(),m=s.useOauthStartMutation(),h=s.useOauthStartOpenAIMutation(),g=async e=>{if(c.trim())if(e===`openai`){let e=window.open(`about:blank`,`_blank`);o(`openai`);try{let{authUrl:t}=await h.mutateAsync({name:c.trim()});e?e.location.href=t:window.open(t,`_blank`);let n=setInterval(async()=>{await p.invalidateQueries({queryKey:[`Token`]}),await p.invalidateQueries({queryKey:[`Qgrid`]})},3e3);setTimeout(()=>{clearInterval(n),o(null)},3e5)}catch(e){console.error(`OAuth start failed:`,e),o(null)}}else{o(`anthropic`);try{let{authUrl:e}=await m.mutateAsync({name:c.trim()});window.location.href=e}catch(e){console.error(`OAuth start failed:`,e),o(null)}}},_=()=>{n(!1),f(``),o(null)};return(0,u.jsxs)(u.Fragment,{children:[(0,u.jsxs)(`button`,{type:`button`,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`,onClick:()=>n(!0),children:[(0,u.jsx)(d,{className:`size-3.5`}),`Add Token`]}),e&&(0,u.jsxs)(`div`,{className:`fixed inset-0 z-50 flex items-start justify-center pt-[15vh]`,children:[(0,u.jsx)(`div`,{className:`absolute inset-0 bg-sand-900/8 backdrop-blur-sm`,onClick:_,onKeyDown:()=>{}}),(0,u.jsxs)(`div`,{className:`relative panel shadow-xl w-full max-w-md mx-4`,children:[(0,u.jsx)(`div`,{className:`px-5 py-4 border-b border-sand-100/50`,children:(0,u.jsx)(`h2`,{className:`text-base font-medium text-sand-900`,children:`Add Token`})}),(0,u.jsxs)(`div`,{className:`px-5 py-4 space-y-4`,children:[(0,u.jsxs)(`div`,{children:[(0,u.jsx)(`label`,{htmlFor:`token-name`,className:`text-[10px] uppercase tracking-wider text-sand-500 font-medium`,children:`Name *`}),(0,u.jsx)(i,{value:c,onChange:e=>f(e.target.value),placeholder:`e.g. your-token-name`,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`})]}),(0,u.jsxs)(`div`,{className:`space-y-2`,children:[(0,u.jsx)(`button`,{type:`button`,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`,disabled:!c.trim()||r!==null,onClick:()=>g(`openai`),children:r===`openai`?(0,u.jsxs)(`span`,{className:`flex items-center gap-1.5`,children:[(0,u.jsx)(`span`,{className:`w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin`}),`Waiting for login...`]}):(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(a,{className:`size-4`}),`Login with OpenAI`]})}),(0,u.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,u.jsx)(`div`,{className:`flex-1 h-px bg-sand-200`}),(0,u.jsx)(`span`,{className:`text-[10px] text-sand-400 uppercase`,children:`or`}),(0,u.jsx)(`div`,{className:`flex-1 h-px bg-sand-200`})]}),(0,u.jsx)(`button`,{type:`button`,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`,disabled:!c.trim()||r!==null,onClick:()=>g(`anthropic`),children:r===`anthropic`?(0,u.jsxs)(`span`,{className:`flex items-center gap-1.5`,children:[(0,u.jsx)(`span`,{className:`w-3.5 h-3.5 border-2 border-sand-400 border-t-transparent rounded-full animate-spin`}),`Redirecting...`]}):(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(a,{className:`size-4`}),`Login with Claude`]})})]})]}),(0,u.jsx)(`div`,{className:`px-5 py-3 border-t border-sand-100/50 flex items-center justify-end gap-2`,children:(0,u.jsx)(`button`,{type:`button`,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`,onClick:_,children:`Cancel`})})]})]})]})}var p=e=>(0,u.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`1.2em`,height:`1.2em`,...e,children:(0,u.jsx)(`path`,{fill:`none`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:2,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`})}),m=e=>(0,u.jsx)(`svg`,{viewBox:`0 0 24 24`,width:`1.2em`,height:`1.2em`,...e,children:(0,u.jsx)(`path`,{fill:`none`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:2,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`})});function h(e){let t=e.credentials;return`expiresAt`in t&&typeof t.expiresAt==`number`?t.expiresAt:`accessTokenExpiresAt`in t&&typeof t.accessTokenExpiresAt==`number`?t.accessTokenExpiresAt:null}function g(e){if(!e)return{text:``,color:``};let t=e-Date.now();if(t<=0)return{text:`expired`,color:`text-red-500`};let n=Math.floor(t/36e5),r=Math.floor(t%36e5/6e4),i=n>0?`${n}h ${r}m`:`${r}m`;return t<36e5?{text:i,color:`text-amber-500`}:{text:i,color:`text-sand-400`}}function _(e){let t=e.credentials.accessToken;return t.length<=12?t:`${t.slice(0,8)}...${t.slice(-4)}`}function v({data:e,isLoading:n}){let[r,a]=(0,l.useState)(null),[o,d]=(0,l.useState)(null),[f,v]=(0,l.useState)(``),y=t(),b=s.useRemoveTokenMutation(),x=c(),S=s.useToggleTokenMutation(),C=()=>Promise.all([y.invalidateQueries({queryKey:[`Token`]}),y.invalidateQueries({queryKey:[`Qgrid`]})]),w=async e=>{await S.mutateAsync({id:e.id}),await C()},T=async()=>{r&&(await b.mutateAsync({id:r.id}),await C(),a(null))},E=e=>{d(e),v(e.name??``)},D=async()=>{o&&(await x.mutateAsync({id:o.id,name:f.trim()}),await C(),d(null))};if(n)return(0,u.jsx)(`div`,{className:`space-y-2`,children:Array.from({length:3}).map((e,t)=>(0,u.jsx)(`div`,{className:`h-12 bg-sand-100 rounded-md animate-pulse`},`skeleton-${t}`))});let O=e??[];return O.length===0?(0,u.jsxs)(`div`,{className:`text-sand-400 text-center py-16 text-sm`,children:[`No tokens registered. Click `,(0,u.jsx)(`strong`,{className:`text-sand-600`,children:`Add Token`}),` to get started.`]}):(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(`div`,{className:`panel overflow-hidden`,children:(0,u.jsxs)(`table`,{className:`w-full text-[13px]`,children:[(0,u.jsx)(`thead`,{children:(0,u.jsxs)(`tr`,{className:`panel-header`,children:[(0,u.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Provider`}),(0,u.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Name`}),(0,u.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Token`}),(0,u.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Status`}),(0,u.jsx)(`th`,{className:`text-left px-5 py-2.5 text-[10px] uppercase tracking-wider text-sand-400 font-medium`,children:`Expires`}),(0,u.jsx)(`th`,{className:`w-20 px-3 py-2.5`})]})}),(0,u.jsx)(`tbody`,{className:`divide-y divide-sand-100/80`,children:O.map(e=>{let t=g(h(e));return(0,u.jsxs)(`tr`,{className:`transition-colors duration-150 hover:bg-sand-100/60 ${e.active?``:`opacity-50`}`,children:[(0,u.jsx)(`td`,{className:`px-5 py-3`,children:(0,u.jsx)(`span`,{className:`text-xs font-medium text-sand-500 uppercase`,children:e.provider})}),(0,u.jsx)(`td`,{className:`px-5 py-3`,children:e.name?(0,u.jsx)(`span`,{className:`text-sm text-sand-700`,children:e.name}):(0,u.jsx)(`span`,{className:`text-[11px] text-sand-400`,children:`—`})}),(0,u.jsx)(`td`,{className:`px-5 py-3`,children:(0,u.jsx)(`code`,{className:`text-[13px] font-mono text-sand-800`,children:_(e)})}),(0,u.jsx)(`td`,{className:`px-5 py-3`,children:(0,u.jsx)(`button`,{type:`button`,className:`relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-200 ${e.active?`bg-sage-400`:`bg-sand-300`}`,onClick:()=>w(e),disabled:S.isPending,children:(0,u.jsx)(`span`,{className:`inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform duration-200 ${e.active?`translate-x-4.5`:`translate-x-0.5`}`})})}),(0,u.jsx)(`td`,{className:`px-5 py-3`,children:t.text&&(0,u.jsx)(`span`,{className:`text-xs ${t.color}`,children:t.text})}),(0,u.jsx)(`td`,{className:`px-3 py-3`,children:(0,u.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,u.jsx)(`button`,{type:`button`,className:`p-1 rounded text-sand-400 hover:text-sienna-500 transition-colors duration-150`,onClick:()=>E(e),children:(0,u.jsx)(p,{className:`size-4`})}),(0,u.jsx)(`button`,{type:`button`,className:`p-1 rounded text-sand-400 hover:text-red-500 transition-colors duration-150`,onClick:()=>a(e),children:(0,u.jsx)(m,{className:`size-4`})})]})})]},e.id)})})]})}),o&&(0,u.jsxs)(`div`,{className:`fixed inset-0 z-50 flex items-start justify-center pt-[15vh]`,children:[(0,u.jsx)(`div`,{className:`absolute inset-0 bg-sand-900/8 backdrop-blur-sm`,onClick:()=>d(null),onKeyDown:()=>{}}),(0,u.jsxs)(`div`,{className:`relative panel shadow-xl w-full max-w-sm mx-4`,children:[(0,u.jsx)(`div`,{className:`px-5 py-4 border-b border-sand-100/60`,children:(0,u.jsx)(`h3`,{className:`text-base font-medium text-sand-900`,children:`Edit Token`})}),(0,u.jsx)(`div`,{className:`px-5 py-4 space-y-3`,children:(0,u.jsxs)(`div`,{children:[(0,u.jsx)(`label`,{htmlFor:`token-name`,className:`text-[10px] uppercase tracking-wider text-sand-500 font-medium`,children:`Name`}),(0,u.jsx)(i,{value:f,onChange:e=>v(e.target.value),placeholder:`e.g. prod, dev, team-a`,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`})]})}),(0,u.jsxs)(`div`,{className:`px-5 py-3 border-t border-sand-100/60 flex items-center justify-end gap-2`,children:[(0,u.jsx)(`button`,{type:`button`,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`,onClick:()=>d(null),children:`Cancel`}),(0,u.jsx)(`button`,{type:`button`,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`,disabled:x.isPending,onClick:D,children:x.isPending?`Saving...`:`Save`})]})]})]}),r&&(0,u.jsxs)(`div`,{className:`fixed inset-0 z-50 flex items-start justify-center pt-[15vh]`,children:[(0,u.jsx)(`div`,{className:`absolute inset-0 bg-sand-900/8 backdrop-blur-sm`,onClick:()=>a(null),onKeyDown:()=>{}}),(0,u.jsxs)(`div`,{className:`relative panel shadow-xl w-full max-w-sm mx-4`,children:[(0,u.jsxs)(`div`,{className:`px-5 py-4`,children:[(0,u.jsx)(`h3`,{className:`text-base font-medium text-sand-900`,children:`Remove Token`}),(0,u.jsxs)(`p`,{className:`text-sm text-sand-700 mt-2`,children:[`Are you sure you want to remove`,` `,(0,u.jsx)(`code`,{className:`text-[13px] font-mono text-sand-800`,children:_(r)}),r.name&&(0,u.jsxs)(`span`,{className:`text-sand-500`,children:[` (`,r.name,`)`]}),`?`]})]}),(0,u.jsxs)(`div`,{className:`px-5 py-3 border-t border-sand-100/60 flex items-center justify-end gap-2`,children:[(0,u.jsx)(`button`,{type:`button`,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`,onClick:()=>a(null),children:`Cancel`}),(0,u.jsx)(`button`,{type:`button`,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`,disabled:b.isPending,onClick:T,children:b.isPending?`Removing...`:`Remove`})]})]})]})]})}function y(){let{data:e,isLoading:t}=o.useTokens(`A`);return(0,u.jsxs)(`div`,{className:`max-w-6xl mx-auto space-y-4`,children:[(0,u.jsx)(`div`,{className:`flex items-center justify-end`,children:(0,u.jsx)(f,{})}),(0,u.jsx)(v,{data:e?.rows,isLoading:t})]})}export{y as component};