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 sqlmodel import select
|
||||||
|
|
||||||
from app.api.deps import require_org_admin, require_org_member
|
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.core.time import utcnow
|
||||||
from app.db import crud
|
from app.db import crud
|
||||||
from app.db.session import get_session
|
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)
|
@router.delete("/{credential_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_provider_credential(
|
async def delete_provider_credential(
|
||||||
credential_id: UUID,
|
credential_id: UUID,
|
||||||
|
|
|
||||||
|
|
@ -515,12 +515,26 @@ function fmtTokens(n: number | null | undefined): string {
|
||||||
|
|
||||||
function fmtResetMs(ms: number | null | undefined): string {
|
function fmtResetMs(ms: number | null | undefined): string {
|
||||||
if (ms == null || ms <= 0) return "< 1m";
|
if (ms == null || ms <= 0) return "< 1m";
|
||||||
const s = Math.floor(ms / 1000);
|
const totalMinutes = Math.floor(ms / 60000);
|
||||||
if (s < 60) return `${s}s`;
|
const d = Math.floor(totalMinutes / (60 * 24));
|
||||||
const m = Math.floor(s / 60);
|
const h = Math.floor((totalMinutes % (60 * 24)) / 60);
|
||||||
if (m < 60) return `${m}m ${s % 60}s`;
|
const m = totalMinutes % 60;
|
||||||
const h = Math.floor(m / 60);
|
const parts: string[] = [];
|
||||||
return `${h}h ${m % 60}m`;
|
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 {
|
function fmtLatencyMs(ms: number | null | undefined): string {
|
||||||
|
|
@ -548,6 +562,7 @@ interface UsageWindowBarProps {
|
||||||
|
|
||||||
function UsageWindowBar({ label, pct, resetInMs, badge }: UsageWindowBarProps) {
|
function UsageWindowBar({ label, pct, resetInMs, badge }: UsageWindowBarProps) {
|
||||||
const clamped = Math.max(0, Math.min(100, pct));
|
const clamped = Math.max(0, Math.min(100, pct));
|
||||||
|
const resetDate = fmtResetDate(resetInMs);
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center justify-between text-[11px]">
|
<div className="flex items-center justify-between text-[11px]">
|
||||||
|
|
@ -567,7 +582,12 @@ function UsageWindowBar({ label, pct, resetInMs, badge }: UsageWindowBarProps) {
|
||||||
style={{ width: `${clamped}%` }}
|
style={{ width: `${clamped}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -583,6 +603,12 @@ function UsageStrip({ credentialId, provider }: { credentialId: string; provider
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
|
if (refresh && provider !== "ollama") {
|
||||||
|
await customFetch<unknown>(
|
||||||
|
"/api/v1/provider-credentials/rescan-local",
|
||||||
|
{ method: "POST" },
|
||||||
|
);
|
||||||
|
}
|
||||||
const res = await getProviderUsageLiveApiV1ProviderCredentialsCredentialIdUsageGet(
|
const res = await getProviderUsageLiveApiV1ProviderCredentialsCredentialIdUsageGet(
|
||||||
credentialId,
|
credentialId,
|
||||||
refresh ? { refresh: true } : undefined,
|
refresh ? { refresh: true } : undefined,
|
||||||
|
|
@ -597,7 +623,7 @@ function UsageStrip({ credentialId, provider }: { credentialId: string; provider
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[credentialId],
|
[credentialId, provider],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,26 @@ function clampPct(pct: number): number {
|
||||||
|
|
||||||
function fmtResetMs(ms: number | null | undefined): string {
|
function fmtResetMs(ms: number | null | undefined): string {
|
||||||
if (ms == null || ms <= 0) return "< 1m";
|
if (ms == null || ms <= 0) return "< 1m";
|
||||||
const s = Math.floor(ms / 1000);
|
const totalMinutes = Math.floor(ms / 60000);
|
||||||
if (s < 60) return `${s}s`;
|
const d = Math.floor(totalMinutes / (60 * 24));
|
||||||
const m = Math.floor(s / 60);
|
const h = Math.floor((totalMinutes % (60 * 24)) / 60);
|
||||||
if (m < 60) return `${m}m ${s % 60}s`;
|
const m = totalMinutes % 60;
|
||||||
const h = Math.floor(m / 60);
|
const parts: string[] = [];
|
||||||
return `${h}h ${m % 60}m`;
|
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 {
|
function remainingPct(window: SubscriptionWindowRead | null): number | null {
|
||||||
|
|
@ -102,7 +116,9 @@ function resetLabel(
|
||||||
modelsWindow: SubscriptionWindowRead | null,
|
modelsWindow: SubscriptionWindowRead | null,
|
||||||
): string {
|
): string {
|
||||||
const resetInMs = sessionWindow?.reset_in_ms ?? modelsWindow?.reset_in_ms;
|
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 {
|
function formatRemaining(pct: number | null): string {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue