@cartanova/qgrid-cli 2.6.2 → 2.6.5
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/expired-token-digest.js +78 -0
- package/bundle/dist/application/qgrid/qgrid.dispatcher.js +40 -6
- package/bundle/dist/application/qgrid/qgrid.frame.js +22 -7
- package/bundle/dist/application/qgrid/qgrid.types.js +13 -3
- package/bundle/dist/application/qgrid/token-death.js +29 -4
- package/bundle/dist/application/queries.generated.js +2 -1
- package/bundle/dist/application/request-log/request-log.model.js +37 -5
- package/bundle/dist/application/request-log-step/request-log-step.model.js +1 -1
- package/bundle/dist/application/sonamu.generated.js +41 -3
- package/bundle/dist/application/token/token.model.js +44 -14
- 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/20260807164057_alter_request_logs_alter1.js +13 -0
- package/bundle/dist/server-error-handler.js +5 -3
- package/bundle/dist/sonamu.config.js +8 -1
- package/bundle/dist/utils/slack-notify.js +2 -2
- package/bundle/src/application/qgrid/expired-token-digest.test.ts +49 -0
- package/bundle/src/application/qgrid/expired-token-digest.ts +93 -0
- package/bundle/src/application/qgrid/qgrid-oauth-redirect.test.ts +0 -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 +13 -4
- package/bundle/src/application/qgrid/qgrid.types.ts +10 -0
- package/bundle/src/application/qgrid/token-death.test.ts +33 -3
- package/bundle/src/application/qgrid/token-death.ts +32 -3
- package/bundle/src/application/queries.generated.ts +3 -0
- package/bundle/src/application/request-log/request-log.entity.json +26 -1
- package/bundle/src/application/request-log/request-log.model.ts +50 -10
- package/bundle/src/application/sonamu.generated.http +6 -0
- package/bundle/src/application/sonamu.generated.ts +42 -2
- package/bundle/src/application/token/token.model.test.ts +13 -3
- package/bundle/src/application/token/token.model.ts +34 -17
- 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/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 +13 -0
- package/bundle/src/utils/slack-notify.test.ts +6 -4
- package/bundle/src/utils/slack-notify.ts +10 -3
- package/bundle/web-dist/client/assets/index-BGQSGMcK.css +1 -0
- package/bundle/web-dist/client/assets/index-DTy-s79t.js +78 -0
- package/bundle/web-dist/client/assets/logs-DYqUSqg-.js +1 -0
- package/bundle/web-dist/client/assets/{monit-BSa5lSZp.js → monit-GnFS0_Xa.js} +1 -1
- package/bundle/web-dist/client/assets/routes-CBwpn4d-.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-B-dMUtEu.js → services.generated-BsAvotjG.js} +1 -1
- package/bundle/web-dist/client/assets/{show-C1jEPhuS.js → show-DDgukGuY.js} +3 -3
- package/bundle/web-dist/client/assets/{tokens-1e66MAC0.js → tokens-Co1wPrXX.js} +1 -1
- package/bundle/web-dist/client/assets/{use-update-token-mutation-DueEppqt.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-CwoOsveV.js → logs-DMOw3SjD.js} +82 -40
- package/bundle/web-dist/server/assets/{monit-DIsEsDJN.js → monit-UmcqHroX.js} +2 -2
- package/bundle/web-dist/server/assets/{routes-Bts-KJ9W.js → routes-Sv_4FjtH.js} +47 -45
- 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-Bh7Vs8Ds.js → services.generated-DqAKskAS.js} +16 -1
- package/bundle/web-dist/server/assets/{show-DJfaCNUw.js → show-C-oSdzHX.js} +31 -25
- package/bundle/web-dist/server/assets/{tokens-BA4vwDat.js → tokens-h-2A3wfD.js} +112 -109
- package/bundle/web-dist/server/assets/{use-update-token-mutation-BQRzEPy2.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 +3 -2
- package/bundle/web-dist/client/assets/index-2cAgaLOm.css +0 -1
- package/bundle/web-dist/client/assets/index-BgWzzjvj.js +0 -78
- package/bundle/web-dist/client/assets/logs-Lyv8OfHE.js +0 -1
- package/bundle/web-dist/client/assets/routes-Cp0re-Nn.js +0 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { J as __commonJSMin, Y as __exportAll, Z as __toESM, _ as require_jsx_runtime, v as require_react } from "./sd.generated-
|
|
2
|
-
import { i as RequestLogStepService, r as RequestLogService } from "./services.generated-
|
|
3
|
-
import { a as Link } from "./lazyRouteComponent-
|
|
1
|
+
import { J as __commonJSMin, Y as __exportAll, Z as __toESM, _ as require_jsx_runtime, v as require_react } from "./sd.generated-B0xqnHEo.js";
|
|
2
|
+
import { i as RequestLogStepService, r as RequestLogService } from "./services.generated-DqAKskAS.js";
|
|
3
|
+
import { a as Link } from "./lazyRouteComponent-BaZaveNc.js";
|
|
4
4
|
import { t as Route } from "../entry-server.generated.js";
|
|
5
5
|
import { n as formatMicroUsd, t as cacheHitRate } from "./cost-CSH_t17I.js";
|
|
6
6
|
import minpath from "node:path";
|
|
@@ -25269,13 +25269,16 @@ function Section({ title, defaultOpen = true, children }) {
|
|
|
25269
25269
|
});
|
|
25270
25270
|
}
|
|
25271
25271
|
function Metric({ label, value }) {
|
|
25272
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
25273
|
-
className: "
|
|
25274
|
-
children:
|
|
25275
|
-
|
|
25276
|
-
|
|
25277
|
-
|
|
25278
|
-
|
|
25272
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
25273
|
+
className: "min-w-0",
|
|
25274
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", {
|
|
25275
|
+
className: "text-[10px] uppercase tracking-wider text-sand-400 font-medium truncate",
|
|
25276
|
+
children: label
|
|
25277
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", {
|
|
25278
|
+
className: "text-[15px] font-semibold text-sand-800 tabular-nums mt-0.5 truncate",
|
|
25279
|
+
children: value
|
|
25280
|
+
})]
|
|
25281
|
+
});
|
|
25279
25282
|
}
|
|
25280
25283
|
var STATUS_STYLE = {
|
|
25281
25284
|
running: "bg-blue-100 text-blue-600",
|
|
@@ -25306,10 +25309,10 @@ function HeaderBar({ data }) {
|
|
|
25306
25309
|
const hasFallback = !isRunning && data.requested_model_name !== null && data.model_name !== null && data.requested_model_name !== data.model_name;
|
|
25307
25310
|
const hasRequestedOnly = !isRunning && data.requested_model_name !== null && data.model_name === null;
|
|
25308
25311
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
25309
|
-
className: "panel overflow-hidden px-
|
|
25312
|
+
className: "panel overflow-hidden px-4 py-3 flex flex-wrap items-center gap-x-2 gap-y-1.5 sm:px-5",
|
|
25310
25313
|
children: [
|
|
25311
25314
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
25312
|
-
className: "text-[15px] font-semibold text-sand-900",
|
|
25315
|
+
className: "text-[15px] font-semibold text-sand-900 break-all",
|
|
25313
25316
|
children: isRunning ? "실행 중" : hasFallback ? `${data.requested_model_name} → ${data.model_name}` : data.model_name ?? data.requested_model_name ?? "Unknown model"
|
|
25314
25317
|
}),
|
|
25315
25318
|
hasRequestedOnly && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
@@ -25441,13 +25444,16 @@ function formatTokensPerSec(outputTokens, durationMs, ttftMs, enabled) {
|
|
|
25441
25444
|
return `${Math.round(outputTokens / (generationMs / 1e3)).toLocaleString()} tok/s`;
|
|
25442
25445
|
}
|
|
25443
25446
|
function CompactMetric({ label, value }) {
|
|
25444
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
25445
|
-
className: "
|
|
25446
|
-
children:
|
|
25447
|
-
|
|
25448
|
-
|
|
25449
|
-
|
|
25450
|
-
|
|
25447
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
25448
|
+
className: "min-w-0",
|
|
25449
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
25450
|
+
className: "text-[10px] uppercase tracking-wider text-sand-400 font-medium truncate",
|
|
25451
|
+
children: label
|
|
25452
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
25453
|
+
className: "mt-0.5 text-[13px] font-semibold text-sand-800 tabular-nums truncate",
|
|
25454
|
+
children: value
|
|
25455
|
+
})]
|
|
25456
|
+
});
|
|
25451
25457
|
}
|
|
25452
25458
|
function readToolInputImages(args) {
|
|
25453
25459
|
if (!args || typeof args !== "object" || Array.isArray(args)) return [];
|
|
@@ -25662,7 +25668,7 @@ function StepTreeItem({ entry, tokensPerSecEnabled }) {
|
|
|
25662
25668
|
children: [generate && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
25663
25669
|
className: "rounded-md border border-sand-100 bg-sand-50/70 p-3",
|
|
25664
25670
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
25665
|
-
className: "grid grid-cols-2 md:grid-cols-4 xl:grid-cols-8 gap-3",
|
|
25671
|
+
className: "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-8 gap-x-4 gap-y-3",
|
|
25666
25672
|
children: [
|
|
25667
25673
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CompactMetric, {
|
|
25668
25674
|
label: "Duration",
|
|
@@ -25928,16 +25934,16 @@ function RequestDetail({ id }) {
|
|
|
25928
25934
|
hasHistory && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HistorySection, { history }),
|
|
25929
25935
|
hasTools && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolsSection, { tools }),
|
|
25930
25936
|
hasSteps ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
25931
|
-
className: "flex gap-4 items-start",
|
|
25937
|
+
className: "flex flex-col lg:flex-row gap-4 items-stretch lg:items-start",
|
|
25932
25938
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
25933
|
-
className: "flex-1 min-w-0",
|
|
25934
|
-
children: promptSections
|
|
25935
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
25936
|
-
className: "flex-1 min-w-0 space-y-4",
|
|
25939
|
+
className: "order-1 lg:order-2 flex-1 min-w-0 space-y-4",
|
|
25937
25940
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StepTreeSection, {
|
|
25938
25941
|
steps: stepTree,
|
|
25939
25942
|
tokensPerSecEnabled
|
|
25940
25943
|
})
|
|
25944
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
25945
|
+
className: "order-2 lg:order-1 flex-1 min-w-0",
|
|
25946
|
+
children: promptSections
|
|
25941
25947
|
})]
|
|
25942
25948
|
}) : promptSections,
|
|
25943
25949
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, {
|
|
@@ -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();
|
|
@@ -392,113 +392,116 @@ function TokenTable({ data, isLoading }) {
|
|
|
392
392
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
393
393
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
394
394
|
className: "panel overflow-hidden",
|
|
395
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.
|
|
396
|
-
className: "
|
|
397
|
-
children:
|
|
398
|
-
className: "
|
|
399
|
-
children: [
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
className: "
|
|
433
|
-
children:
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
className: "
|
|
440
|
-
children: token.name
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
className: "
|
|
450
|
-
children:
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
className:
|
|
467
|
-
children: expiry.text
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
className: "
|
|
474
|
-
children:
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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", {
|
|
458
|
+
type: "button",
|
|
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
|
+
})
|
|
496
499
|
})
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
500
|
-
})
|
|
501
|
-
})
|
|
500
|
+
]
|
|
501
|
+
}, token.id);
|
|
502
|
+
})
|
|
503
|
+
})]
|
|
504
|
+
})
|
|
502
505
|
})
|
|
503
506
|
}),
|
|
504
507
|
oauth.codeEntry && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
@@ -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) });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as functionalUpdate, B as resolveStaleTime, C as infiniteQueryBehavior, D as notifyManager, E as onlineManager, F as matchQuery, I as noop$1, J as __commonJSMin, K as focusManager, L as partialMatchKey, M as hashQueryKeyByOptions, P as matchMutation, U as skipToken, X as __require, Z as __toESM, _ as require_jsx_runtime, c as _enum, d as string, h as QueryClientProvider, j as hashKey, l as number, m as require_shim, n as SUPPORTED_LOCALES, p as require_react_dom, q as Subscribable, r as setLocale, t as SD, u as object, v as require_react, w as Query, y as Mutation } from "./assets/sd.generated-
|
|
2
|
-
import { A as decodePath, C as findRouteMatch, D as createLRUCache, E as processRouteTree, F as last, I as replaceEqualDeep, M as findLast, N as functionalUpdate$1, O as invariant, P as isPromise, S as findFlatMatch, T as processRouteMasks, _ as interpolatePath, a as Link$1, b as trimPath, c as usePrevious$1, d as useRouter, f as getRouterContext, g as cleanPath, h as isNotFound, i as createRootRouteWithContext, j as deepEqual$2, k as createControlledPromise, l as matchContext, m as rootRouteId, n as createFileRoute, p as warning, r as createLazyFileRoute, s as useLayoutEffect$3, t as lazyRouteComponent, u as useRouterState, v as joinPaths, w as findSingleMatch, x as trimPathRight, y as resolvePath } from "./assets/lazyRouteComponent-
|
|
1
|
+
import { A as functionalUpdate, B as resolveStaleTime, C as infiniteQueryBehavior, D as notifyManager, E as onlineManager, F as matchQuery, I as noop$1, J as __commonJSMin, K as focusManager, L as partialMatchKey, M as hashQueryKeyByOptions, P as matchMutation, U as skipToken, X as __require, Z as __toESM, _ as require_jsx_runtime, c as _enum, d as string, h as QueryClientProvider, j as hashKey, l as number, m as require_shim, n as SUPPORTED_LOCALES, p as require_react_dom, q as Subscribable, r as setLocale, t as SD, u as object, v as require_react, w as Query, y as Mutation } from "./assets/sd.generated-B0xqnHEo.js";
|
|
2
|
+
import { A as decodePath, C as findRouteMatch, D as createLRUCache, E as processRouteTree, F as last, I as replaceEqualDeep, M as findLast, N as functionalUpdate$1, O as invariant, P as isPromise, S as findFlatMatch, T as processRouteMasks, _ as interpolatePath, a as Link$1, b as trimPath, c as usePrevious$1, d as useRouter, f as getRouterContext, g as cleanPath, h as isNotFound, i as createRootRouteWithContext, j as deepEqual$2, k as createControlledPromise, l as matchContext, m as rootRouteId, n as createFileRoute, p as warning, r as createLazyFileRoute, s as useLayoutEffect$3, t as lazyRouteComponent, u as useRouterState, v as joinPaths, w as findSingleMatch, x as trimPathRight, y as resolvePath } from "./assets/lazyRouteComponent-BaZaveNc.js";
|
|
3
3
|
//#region ../../node_modules/.pnpm/@tanstack+query-core@5.95.2/node_modules/@tanstack/query-core/build/modern/hydration.js
|
|
4
4
|
function defaultTransformerFn(data) {
|
|
5
5
|
return data;
|
|
@@ -22675,8 +22675,9 @@ var lucideTerminal = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg
|
|
|
22675
22675
|
})
|
|
22676
22676
|
});
|
|
22677
22677
|
//#endregion
|
|
22678
|
-
//#region src/components/
|
|
22679
|
-
|
|
22678
|
+
//#region src/components/nav-items.ts
|
|
22679
|
+
/** 사이드바(데스크톱)와 하단 탭 바(모바일)가 공유하는 단일 정의. */
|
|
22680
|
+
var NAV_SECTIONS = [{
|
|
22680
22681
|
label: "Overview",
|
|
22681
22682
|
items: [{
|
|
22682
22683
|
title: "Dashboard",
|
|
@@ -22693,23 +22694,57 @@ var sections = [{
|
|
|
22693
22694
|
},
|
|
22694
22695
|
{
|
|
22695
22696
|
title: "Request Logs",
|
|
22697
|
+
shortTitle: "Logs",
|
|
22696
22698
|
path: "/logs",
|
|
22697
22699
|
icon: lucideFileText
|
|
22698
22700
|
},
|
|
22699
22701
|
{
|
|
22700
22702
|
title: "Server Logs",
|
|
22703
|
+
shortTitle: "Server",
|
|
22701
22704
|
path: "/monit",
|
|
22702
22705
|
icon: lucideTerminal
|
|
22703
22706
|
}
|
|
22704
22707
|
]
|
|
22705
22708
|
}];
|
|
22706
|
-
var
|
|
22709
|
+
var NAV_ITEMS = NAV_SECTIONS.flatMap((section) => section.items);
|
|
22710
|
+
function isNavItemActive(path, pathname) {
|
|
22711
|
+
if (path === "/") return pathname === "/" || pathname === "";
|
|
22712
|
+
return pathname.startsWith(path);
|
|
22713
|
+
}
|
|
22714
|
+
//#endregion
|
|
22715
|
+
//#region src/components/BottomNav.tsx
|
|
22716
|
+
/**
|
|
22717
|
+
* 좁은 화면 전용 하단 탭 바.
|
|
22718
|
+
*
|
|
22719
|
+
* 사이드바는 `md` 미만에서 숨겨지는데, 그러면 URL 을 직접 치지 않는 한 페이지 이동이
|
|
22720
|
+
* 불가능해진다. 항목이 4개뿐이라 햄버거 메뉴보다 탭 바가 맞다 — 한 번의 탭으로 이동하고
|
|
22721
|
+
* 현재 위치도 항상 보인다.
|
|
22722
|
+
*/
|
|
22723
|
+
function BottomNav() {
|
|
22724
|
+
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
|
22725
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", {
|
|
22726
|
+
className: "md:hidden shrink-0 border-t border-sand-200/60 bg-sand-50/95 backdrop-blur-sm pb-[env(safe-area-inset-bottom)]",
|
|
22727
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
22728
|
+
className: "flex",
|
|
22729
|
+
children: NAV_ITEMS.map((item) => {
|
|
22730
|
+
const active = isNavItemActive(item.path, pathname);
|
|
22731
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link$1, {
|
|
22732
|
+
to: item.path,
|
|
22733
|
+
className: `flex-1 flex flex-col items-center gap-0.5 py-2 no-underline transition-colors duration-150 ${active ? "text-sienna-600" : "text-sand-500"}`,
|
|
22734
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(item.icon, { className: `size-5 ${active ? "text-sienna-500" : "text-sand-400"}` }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
22735
|
+
className: `text-[10px] ${active ? "font-medium" : ""}`,
|
|
22736
|
+
children: item.shortTitle ?? item.title
|
|
22737
|
+
})]
|
|
22738
|
+
}, item.path);
|
|
22739
|
+
})
|
|
22740
|
+
})
|
|
22741
|
+
});
|
|
22742
|
+
}
|
|
22743
|
+
//#endregion
|
|
22744
|
+
//#region src/components/Sidebar.tsx
|
|
22745
|
+
var qgridVersion = "2.6.5";
|
|
22707
22746
|
function Sidebar({ className }) {
|
|
22708
22747
|
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
|
22709
|
-
const isActive = (path) => {
|
|
22710
|
-
if (path === "/") return pathname === "/" || pathname === "";
|
|
22711
|
-
return pathname.startsWith(path);
|
|
22712
|
-
};
|
|
22713
22748
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
|
|
22714
22749
|
className: `hidden md:flex w-[200px] bg-sand-50 flex-col shrink-0 border-r border-sand-200/40 ${className ?? ""}`,
|
|
22715
22750
|
children: [
|
|
@@ -22722,7 +22757,7 @@ function Sidebar({ className }) {
|
|
|
22722
22757
|
}),
|
|
22723
22758
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", {
|
|
22724
22759
|
className: "flex-1 px-2.5 py-3 space-y-4 overflow-y-auto",
|
|
22725
|
-
children:
|
|
22760
|
+
children: NAV_SECTIONS.map((section) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
22726
22761
|
className: "px-2 pb-1.5",
|
|
22727
22762
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
22728
22763
|
className: "text-[10px] uppercase tracking-[0.08em] text-sand-400 font-medium",
|
|
@@ -22731,7 +22766,7 @@ function Sidebar({ className }) {
|
|
|
22731
22766
|
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
22732
22767
|
className: "space-y-0.5",
|
|
22733
22768
|
children: section.items.map((item) => {
|
|
22734
|
-
const active =
|
|
22769
|
+
const active = isNavItemActive(item.path, pathname);
|
|
22735
22770
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link$1, {
|
|
22736
22771
|
to: item.path,
|
|
22737
22772
|
className: `flex items-center gap-2 px-2 py-[6px] rounded-[7px] text-[13px] no-underline transition-all duration-150 ${active ? "bg-sand-200/60 text-sand-900 font-medium" : "text-sand-500 hover:bg-sand-100 hover:text-sand-700"}`,
|
|
@@ -22803,20 +22838,24 @@ function App({ children }) {
|
|
|
22803
22838
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
22804
22839
|
className: "flex h-screen w-full bg-sand-100 overflow-hidden font-sans",
|
|
22805
22840
|
children: [showSidebar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sidebar, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
22806
|
-
className: "flex-1 flex flex-col overflow-hidden",
|
|
22807
|
-
children: [
|
|
22808
|
-
|
|
22809
|
-
|
|
22810
|
-
className: "
|
|
22811
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
22812
|
-
|
|
22813
|
-
|
|
22814
|
-
|
|
22815
|
-
|
|
22816
|
-
|
|
22841
|
+
className: "flex-1 flex flex-col overflow-hidden min-w-0",
|
|
22842
|
+
children: [
|
|
22843
|
+
showSidebar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Toolbar, {}),
|
|
22844
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("main", {
|
|
22845
|
+
className: "flex-1 overflow-y-auto main-scroll content-inset",
|
|
22846
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
22847
|
+
className: "px-3 py-4 sm:px-6 sm:py-5 md:px-8",
|
|
22848
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Suspense, {
|
|
22849
|
+
fallback: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
22850
|
+
className: "text-sand-400 text-center py-8 text-sm",
|
|
22851
|
+
children: "로딩 중..."
|
|
22852
|
+
}),
|
|
22853
|
+
children
|
|
22854
|
+
})
|
|
22817
22855
|
})
|
|
22818
|
-
})
|
|
22819
|
-
|
|
22856
|
+
}),
|
|
22857
|
+
showSidebar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BottomNav, {})
|
|
22858
|
+
]
|
|
22820
22859
|
})]
|
|
22821
22860
|
});
|
|
22822
22861
|
}
|
|
@@ -44584,11 +44623,11 @@ function RootComponent() {
|
|
|
44584
44623
|
}
|
|
44585
44624
|
//#endregion
|
|
44586
44625
|
//#region src/routes/tokens.tsx
|
|
44587
|
-
var $$splitComponentImporter$4 = () => import("./assets/tokens-
|
|
44626
|
+
var $$splitComponentImporter$4 = () => import("./assets/tokens-h-2A3wfD.js");
|
|
44588
44627
|
var Route$4 = createFileRoute("/tokens")({ component: lazyRouteComponent($$splitComponentImporter$4, "component") });
|
|
44589
44628
|
//#endregion
|
|
44590
44629
|
//#region src/routes/monit.tsx
|
|
44591
|
-
var $$splitComponentImporter$3 = () => import("./assets/monit-
|
|
44630
|
+
var $$splitComponentImporter$3 = () => import("./assets/monit-UmcqHroX.js");
|
|
44592
44631
|
var monitSearchSchema = object({ level: _enum([
|
|
44593
44632
|
"all",
|
|
44594
44633
|
"scaling",
|
|
@@ -44601,10 +44640,12 @@ var Route$3 = createFileRoute("/monit")({
|
|
|
44601
44640
|
});
|
|
44602
44641
|
//#endregion
|
|
44603
44642
|
//#region src/routes/logs.tsx
|
|
44604
|
-
var $$splitComponentImporter$2 = () => import("./assets/logs-
|
|
44643
|
+
var $$splitComponentImporter$2 = () => import("./assets/logs-DMOw3SjD.js");
|
|
44605
44644
|
var logsSearchSchema = object({
|
|
44606
44645
|
token: string().optional(),
|
|
44607
44646
|
project: string().optional(),
|
|
44647
|
+
model: string().optional(),
|
|
44648
|
+
sort: string().optional(),
|
|
44608
44649
|
page: number().optional().default(1)
|
|
44609
44650
|
});
|
|
44610
44651
|
var Route$2 = createFileRoute("/logs")({
|
|
@@ -44613,11 +44654,11 @@ var Route$2 = createFileRoute("/logs")({
|
|
|
44613
44654
|
});
|
|
44614
44655
|
//#endregion
|
|
44615
44656
|
//#region src/routes/index.tsx
|
|
44616
|
-
var $$splitComponentImporter$1 = () => import("./assets/routes-
|
|
44657
|
+
var $$splitComponentImporter$1 = () => import("./assets/routes-Sv_4FjtH.js");
|
|
44617
44658
|
var Route$1 = createFileRoute("/")({ component: lazyRouteComponent($$splitComponentImporter$1, "component") });
|
|
44618
44659
|
//#endregion
|
|
44619
44660
|
//#region src/routes/requests/show.tsx
|
|
44620
|
-
var $$splitComponentImporter = () => import("./assets/show-
|
|
44661
|
+
var $$splitComponentImporter = () => import("./assets/show-C-oSdzHX.js");
|
|
44621
44662
|
var showSearchSchema = object({ id: number() });
|
|
44622
44663
|
var Route = createFileRoute("/requests/show")({
|
|
44623
44664
|
validateSearch: showSearchSchema,
|
package/package.json
CHANGED
|
@@ -88,6 +88,14 @@ When helping a user set up qgrid locally, check for `QGRID_PROJECT_NAME` or conf
|
|
|
88
88
|
|
|
89
89
|
On shutdown, it stops provider dispatchers and the token subscriber.
|
|
90
90
|
|
|
91
|
+
### Readiness during startup
|
|
92
|
+
|
|
93
|
+
HTTP listening opens before the dispatchers finish starting, so requests can arrive while a provider is still unavailable. On dev0 that window is 1–2 minutes: 25 OpenAI workers spawn at `SPAWN_INTERVAL_MS` apart, plus codex process init.
|
|
94
|
+
|
|
95
|
+
- `QgridDispatcher.startupState` tracks each provider as `starting` → `ready` or `failed`. A request for an unready provider throws `ServiceUnavailableException` (503, with a `Retry-After` header) while starting, and `InternalServerErrorException` (500) once initialization has failed — retrying helps in the first case and never in the second. Both used to be `QuotaError`, which told callers their tokens were exhausted and gave them nothing to act on.
|
|
96
|
+
- `GET /api/qgrid/health` reports `ready` plus per-provider state. A 200 alone does not mean requests will succeed; read `providers` for that.
|
|
97
|
+
- `handleServerError` trusts `statusCode` only on `instanceof SoException`. Sonamu's own `isSoException` is a `statusCode !== undefined` duck-type check, which would pass upstream fetch errors straight through and echo an upstream 401 as if the caller were unauthorized.
|
|
98
|
+
|
|
91
99
|
## OAuth callbacks
|
|
92
100
|
|
|
93
101
|
- 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`.
|
|
@@ -208,9 +208,10 @@ When a refresh fails in a way that only re-login can fix, qgrid removes the toke
|
|
|
208
208
|
- Detection lives at the existing refresh failure points, not a background scanner. OpenAI classifies on the error code (`refresh_token_expired`, `refresh_token_reused`, `refresh_token_invalidated`) regardless of HTTP status; Anthropic treats refresh 400/401 as auth-dead.
|
|
209
209
|
- Before deactivating, qgrid re-reads the token and confirms the refresh token used by the failed attempt still matches the stored one. Anthropic refresh tokens rotate, so a late retry carrying an already-rotated token is a normal race, not a death — those are logged and skipped. `QgridFrame.refreshToken` also dedups concurrent refreshes per token.
|
|
210
210
|
- Deactivation is one conditional `active=true → false` update. The process whose update affects a row is the only one that sends the Slack death notification, so a shared database produces exactly one alert per event.
|
|
211
|
-
- The provider's last active token is never auto-deactivated. A systemic failure (client_id revocation, OAuth contract change) would otherwise empty the pool; qgrid logs an error
|
|
212
|
-
- The other notification is registration:
|
|
211
|
+
- The provider's last active token is never auto-deactivated. A systemic failure (client_id revocation, OAuth contract change) would otherwise empty the pool; qgrid logs an error, keeps the token, and sends its own Slack alert. That path leaves no state change behind, so every following request would re-alert — a per-provider 30-minute cooldown holds it to one. The cooldown is in-process, so N instances can each emit once per window.
|
|
212
|
+
- The other notification is registration: every completed login sends one alert, whether the account is new or replacing an existing row through the account dedup. Manually toggling a token back to active is not a registration and sends nothing.
|
|
213
213
|
- Notifications are fail-open: unset `SLACK_BOT_TOKEN`/`SLACK_CHANNEL_ID` is a silent no-op, and send failures (including HTTP 200 with `ok:false`) only warn. Payloads carry an internal reason code such as `anthropic:400`, never the raw provider response body.
|
|
214
|
+
- A periodic digest (`expired-token-digest.ts`) re-announces tokens that are still inactive, because the one-shot death alert is lost on anyone not watching the channel at that moment and re-login only happens when a person does it. It runs every `SLACK_EXPIRY_DIGEST_INTERVAL_MINUTES` (unset or 0 disables) and sends nothing when no token is inactive — a periodic "all clear" trains people to ignore the channel. Owners come from `SLACK_USER_MAP` (`tokenName:SlackUserId`), which is explicit rather than resolved through `users.list`: Slack handles diverge from token names often enough (`haze`→`haze.lee`, `byeongjun`→`potados`) that auto-matching drops the mention silently, and a digest without the mention has no one to act on it. Shared accounts are left out of the map on purpose and appear by name only. Each instance sends its own digest — unlike deactivation there is no state change to gate on, and a few duplicates a day cost less than a locking scheme.
|
|
214
215
|
|
|
215
216
|
## Quota Threshold Semantics
|
|
216
217
|
|