feat(provider-credentials): add endpoint to rescan local credentials and update tokens
feat(usage): enhance reset time formatting and display full reset date in UI
This commit is contained in:
parent
25abfd3e15
commit
99965330b5
|
|
@ -14,6 +14,8 @@ from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|||
from sqlmodel import select
|
||||
|
||||
from app.api.deps import require_org_admin, require_org_member
|
||||
from app.core.auth_mode import AuthMode
|
||||
from app.core.config import settings
|
||||
from app.core.time import utcnow
|
||||
from app.db import crud
|
||||
from app.db.session import get_session
|
||||
|
|
@ -318,6 +320,29 @@ async def get_provider_usage_live(
|
|||
)
|
||||
|
||||
|
||||
@router.post("/rescan-local", status_code=status.HTTP_200_OK)
|
||||
async def rescan_local_credentials(
|
||||
ctx: OrganizationContext = ORG_MEMBER_DEP,
|
||||
) -> dict:
|
||||
"""Re-read local credential files and upsert updated tokens into the database.
|
||||
|
||||
Only has effect in LOCAL auth mode; returns {"changed": 0} otherwise.
|
||||
Reads ~/.claude/.credentials.json (Claude) and ~/.codex/auth.json (Codex/GPT)
|
||||
and updates any rows whose session_key has changed.
|
||||
"""
|
||||
if settings.auth_mode != AuthMode.LOCAL:
|
||||
return {"changed": 0}
|
||||
try:
|
||||
from scripts.seed_provider_credentials import seed
|
||||
changed = await seed(verbose=False)
|
||||
return {"changed": changed}
|
||||
except Exception as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Local credential rescan failed: {exc}",
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{credential_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_provider_credential(
|
||||
credential_id: UUID,
|
||||
|
|
|
|||
|
|
@ -515,12 +515,26 @@ function fmtTokens(n: number | null | undefined): string {
|
|||
|
||||
function fmtResetMs(ms: number | null | undefined): string {
|
||||
if (ms == null || ms <= 0) return "< 1m";
|
||||
const s = Math.floor(ms / 1000);
|
||||
if (s < 60) return `${s}s`;
|
||||
const m = Math.floor(s / 60);
|
||||
if (m < 60) return `${m}m ${s % 60}s`;
|
||||
const h = Math.floor(m / 60);
|
||||
return `${h}h ${m % 60}m`;
|
||||
const totalMinutes = Math.floor(ms / 60000);
|
||||
const d = Math.floor(totalMinutes / (60 * 24));
|
||||
const h = Math.floor((totalMinutes % (60 * 24)) / 60);
|
||||
const m = totalMinutes % 60;
|
||||
const parts: string[] = [];
|
||||
if (d > 0) parts.push(`${d}d`);
|
||||
if (h > 0) parts.push(`${h}hrs`);
|
||||
if (m > 0 || parts.length === 0) parts.push(`${m}m`);
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
function fmtResetDate(ms: number | null | undefined): string | null {
|
||||
if (ms == null || ms <= 0) return null;
|
||||
const resetAt = new Date(Date.now() + ms);
|
||||
return resetAt.toLocaleString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
function fmtLatencyMs(ms: number | null | undefined): string {
|
||||
|
|
@ -548,6 +562,7 @@ interface UsageWindowBarProps {
|
|||
|
||||
function UsageWindowBar({ label, pct, resetInMs, badge }: UsageWindowBarProps) {
|
||||
const clamped = Math.max(0, Math.min(100, pct));
|
||||
const resetDate = fmtResetDate(resetInMs);
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between text-[11px]">
|
||||
|
|
@ -567,7 +582,12 @@ function UsageWindowBar({ label, pct, resetInMs, badge }: UsageWindowBarProps) {
|
|||
style={{ width: `${clamped}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted">Resets in {fmtResetMs(resetInMs)}</p>
|
||||
<p className="text-[11px] text-muted">
|
||||
Resets in {fmtResetMs(resetInMs)}
|
||||
{resetDate && (
|
||||
<span className="ml-1 text-[color:var(--text-quiet)]">· Full reset at {resetDate}</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -583,6 +603,12 @@ function UsageStrip({ credentialId, provider }: { credentialId: string; provider
|
|||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
if (refresh && provider !== "ollama") {
|
||||
await customFetch<unknown>(
|
||||
"/api/v1/provider-credentials/rescan-local",
|
||||
{ method: "POST" },
|
||||
);
|
||||
}
|
||||
const res = await getProviderUsageLiveApiV1ProviderCredentialsCredentialIdUsageGet(
|
||||
credentialId,
|
||||
refresh ? { refresh: true } : undefined,
|
||||
|
|
@ -597,7 +623,7 @@ function UsageStrip({ credentialId, provider }: { credentialId: string; provider
|
|||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[credentialId],
|
||||
[credentialId, provider],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -44,12 +44,26 @@ function clampPct(pct: number): number {
|
|||
|
||||
function fmtResetMs(ms: number | null | undefined): string {
|
||||
if (ms == null || ms <= 0) return "< 1m";
|
||||
const s = Math.floor(ms / 1000);
|
||||
if (s < 60) return `${s}s`;
|
||||
const m = Math.floor(s / 60);
|
||||
if (m < 60) return `${m}m ${s % 60}s`;
|
||||
const h = Math.floor(m / 60);
|
||||
return `${h}h ${m % 60}m`;
|
||||
const totalMinutes = Math.floor(ms / 60000);
|
||||
const d = Math.floor(totalMinutes / (60 * 24));
|
||||
const h = Math.floor((totalMinutes % (60 * 24)) / 60);
|
||||
const m = totalMinutes % 60;
|
||||
const parts: string[] = [];
|
||||
if (d > 0) parts.push(`${d}d`);
|
||||
if (h > 0) parts.push(`${h}hrs`);
|
||||
if (m > 0 || parts.length === 0) parts.push(`${m}m`);
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
function fmtResetDate(ms: number | null | undefined): string | null {
|
||||
if (ms == null || ms <= 0) return null;
|
||||
const resetAt = new Date(Date.now() + ms);
|
||||
return resetAt.toLocaleString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
function remainingPct(window: SubscriptionWindowRead | null): number | null {
|
||||
|
|
@ -102,7 +116,9 @@ function resetLabel(
|
|||
modelsWindow: SubscriptionWindowRead | null,
|
||||
): string {
|
||||
const resetInMs = sessionWindow?.reset_in_ms ?? modelsWindow?.reset_in_ms;
|
||||
return resetInMs == null ? "-" : fmtResetMs(resetInMs);
|
||||
if (resetInMs == null) return "-";
|
||||
const date = fmtResetDate(resetInMs);
|
||||
return date ? `${fmtResetMs(resetInMs)} · Full reset at ${date}` : fmtResetMs(resetInMs);
|
||||
}
|
||||
|
||||
function formatRemaining(pct: number | null): string {
|
||||
|
|
|
|||
Loading…
Reference in New Issue