mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 22:32:12 +03:00
feat(quota): show OAuth token expiry on provider cards (small, blue, informative) (#3178)
Show OAuth token expiry on provider cards (small, blue, informative). Integrated into release/v3.8.10.
This commit is contained in:
committed by
GitHub
parent
42df1703c6
commit
3ac40829c0
@@ -4,7 +4,8 @@ import { useTranslations } from "next-intl";
|
||||
import Badge from "@/shared/components/Badge";
|
||||
import ProviderIcon from "@/shared/components/ProviderIcon";
|
||||
import { pickDisplayValue } from "@/shared/utils/maskEmail";
|
||||
import { STATUS_EMOJI, type CardStatus } from "../utils";
|
||||
import { STATUS_EMOJI, formatCountdown, type CardStatus } from "../utils";
|
||||
import { translateUsageOrFallback } from "../i18nFallback";
|
||||
|
||||
interface Props {
|
||||
connection: any;
|
||||
@@ -41,6 +42,27 @@ export default function QuotaCardHeader({
|
||||
connection.provider
|
||||
);
|
||||
|
||||
// OAuth token expiry — informative only. Shown small/blue for connections that
|
||||
// expose a concrete token expiry (e.g. Codex), so an operator can see at a
|
||||
// glance when the access token rotates. Hidden for API-key / no-expiry connections.
|
||||
const tokenExpiryIso =
|
||||
connection.authType === "oauth"
|
||||
? connection.tokenExpiresAt || connection.expiresAt || null
|
||||
: null;
|
||||
const tokenExpiryMs = tokenExpiryIso ? new Date(tokenExpiryIso).getTime() : NaN;
|
||||
const hasTokenExpiry = Number.isFinite(tokenExpiryMs);
|
||||
const tokenCountdown = hasTokenExpiry ? formatCountdown(tokenExpiryIso) : null;
|
||||
const tokenExpiryLabel = !hasTokenExpiry
|
||||
? null
|
||||
: tokenCountdown
|
||||
? translateUsageOrFallback(t, "tokenExpiresIn", `Token expires in ${tokenCountdown}`, {
|
||||
time: tokenCountdown,
|
||||
})
|
||||
: translateUsageOrFallback(t, "tokenExpired", "Token expired");
|
||||
const tokenExpiryTitle = hasTokenExpiry
|
||||
? new Date(tokenExpiryMs).toLocaleString()
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div className="flex items-start justify-between gap-2 px-3 pt-2.5 pb-1.5">
|
||||
<div className="flex items-start gap-2 min-w-0 flex-1">
|
||||
@@ -85,6 +107,14 @@ export default function QuotaCardHeader({
|
||||
<span className="text-[11px] text-text-muted truncate" title={accountName ?? ""}>
|
||||
{accountName}
|
||||
</span>
|
||||
{tokenExpiryLabel && (
|
||||
<span
|
||||
className={`text-[10px] truncate ${tokenCountdown ? "text-sky-500" : "text-rose-500"}`}
|
||||
title={tokenExpiryTitle}
|
||||
>
|
||||
{tokenExpiryLabel}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5 shrink-0">
|
||||
|
||||
@@ -6289,6 +6289,8 @@
|
||||
"notApplicable": "N/A",
|
||||
"rawPlanWithValue": "Raw plan: {plan}",
|
||||
"noPlanFromProvider": "No plan from provider",
|
||||
"tokenExpiresIn": "Token expires in {time}",
|
||||
"tokenExpired": "Token expired",
|
||||
"noQuotaData": "No quota data",
|
||||
"cardExpand": "Expand",
|
||||
"cardCollapse": "Collapse",
|
||||
|
||||
@@ -7611,6 +7611,8 @@
|
||||
"notApplicable": "N/D",
|
||||
"rawPlanWithValue": "Plano bruto: {plan}",
|
||||
"noPlanFromProvider": "Sem plano do provedor",
|
||||
"tokenExpiresIn": "Token expira em {time}",
|
||||
"tokenExpired": "Token expirado",
|
||||
"noQuotaData": "Sem dados de cota",
|
||||
"cardExpand": "Expandir",
|
||||
"cardCollapse": "Recolher",
|
||||
|
||||
49
tests/unit/quota-token-expiry-display.test.ts
Normal file
49
tests/unit/quota-token-expiry-display.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* The quota card shows an informative (small, blue) OAuth token-expiry line for
|
||||
* connections that expose a concrete token expiry (e.g. Codex), and an expired
|
||||
* marker otherwise. API-key / no-expiry connections show nothing.
|
||||
*/
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const headerPath = path.join(
|
||||
repoRoot,
|
||||
"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardHeader.tsx"
|
||||
);
|
||||
const source = fs.readFileSync(headerPath, "utf8");
|
||||
|
||||
test("QuotaCardHeader derives token expiry only for OAuth connections with a known expiry", () => {
|
||||
assert.match(source, /connection\.authType === "oauth"/, "must gate on oauth auth type");
|
||||
assert.match(
|
||||
source,
|
||||
/connection\.tokenExpiresAt \|\| connection\.expiresAt/,
|
||||
"must read tokenExpiresAt with expiresAt fallback"
|
||||
);
|
||||
assert.match(source, /formatCountdown\(/, "must format the countdown");
|
||||
});
|
||||
|
||||
test("QuotaCardHeader renders the expiry as a small blue (sky) informative line", () => {
|
||||
assert.match(source, /text-\[10px\]/, "expiry line must be small (10px)");
|
||||
assert.match(source, /text-sky-500/, "active expiry must be blue (sky-500)");
|
||||
assert.match(source, /tokenExpiresIn/, "must use the tokenExpiresIn i18n key");
|
||||
assert.match(source, /tokenExpired/, "must use the tokenExpired i18n key");
|
||||
});
|
||||
|
||||
test("token expiry i18n keys exist in en and pt-BR", () => {
|
||||
for (const locale of ["en", "pt-BR"]) {
|
||||
const msgs = JSON.parse(
|
||||
fs.readFileSync(path.join(repoRoot, `src/i18n/messages/${locale}.json`), "utf8")
|
||||
);
|
||||
assert.ok(msgs.usage?.tokenExpiresIn, `${locale}: usage.tokenExpiresIn must exist`);
|
||||
assert.ok(msgs.usage?.tokenExpired, `${locale}: usage.tokenExpired must exist`);
|
||||
assert.match(
|
||||
msgs.usage.tokenExpiresIn,
|
||||
/\{time\}/,
|
||||
`${locale}: tokenExpiresIn must interpolate {time}`
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user