mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 22:14:22 +00:00
fix(ui): replace misleading 'Expires' with 'Last used' in credential health
- credential-health-list.tsx showed 'Expires: Expired' based on stale file state (CLIProxyAPIPlus intentionally doesn't persist refreshed token expiry to disk) - Now shows runtime-based 'Last used: Xm/h/d ago' from useCliproxyStats - Consistent with account-item.tsx changes from previous commit
This commit is contained in:
1 parent
6ccf6c5e13
commit
4233415095
1 file changed
+38
-24
@@ -6,8 +6,9 @@
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { CheckCircle2, AlertCircle, XCircle, MinusCircle, RefreshCw } from 'lucide-react';
|
import { CheckCircle2, AlertCircle, XCircle, MinusCircle, RefreshCw, Clock } from 'lucide-react';
|
||||||
import { useCliproxyAuth } from '@/hooks/use-cliproxy';
|
import { useCliproxyAuth } from '@/hooks/use-cliproxy';
|
||||||
|
import { useCliproxyStats } from '@/hooks/use-cliproxy-stats';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
|
import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ interface CredentialRowProps {
|
|||||||
status: CredentialStatus;
|
status: CredentialStatus;
|
||||||
statusMessage: string;
|
statusMessage: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
expiresAt?: string;
|
lastUsedAt?: string;
|
||||||
onRefresh?: () => void;
|
onRefresh?: () => void;
|
||||||
privacyMode?: boolean;
|
privacyMode?: boolean;
|
||||||
}
|
}
|
||||||
@@ -30,7 +31,7 @@ function CredentialRow({
|
|||||||
status,
|
status,
|
||||||
statusMessage,
|
statusMessage,
|
||||||
email,
|
email,
|
||||||
expiresAt,
|
lastUsedAt,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
privacyMode,
|
privacyMode,
|
||||||
}: CredentialRowProps) {
|
}: CredentialRowProps) {
|
||||||
@@ -60,16 +61,23 @@ function CredentialRow({
|
|||||||
const config = statusConfig[status];
|
const config = statusConfig[status];
|
||||||
const Icon = config.icon;
|
const Icon = config.icon;
|
||||||
|
|
||||||
const formatExpiry = (date?: string) => {
|
const formatLastUsed = (date?: string) => {
|
||||||
if (!date) return 'Never';
|
if (!date) return 'Never used';
|
||||||
const expiry = new Date(date);
|
try {
|
||||||
const now = new Date();
|
const lastUsed = new Date(date);
|
||||||
const diff = expiry.getTime() - now.getTime();
|
const now = new Date();
|
||||||
if (diff < 0) return 'Expired';
|
const diff = now.getTime() - lastUsed.getTime();
|
||||||
const hours = Math.floor(diff / 3600000);
|
if (diff < 0) return 'Just now';
|
||||||
if (hours < 1) return 'Soon';
|
const minutes = Math.floor(diff / 60000);
|
||||||
if (hours < 24) return `${hours}h`;
|
const hours = Math.floor(diff / 3600000);
|
||||||
return `${Math.floor(hours / 24)}d`;
|
const days = Math.floor(diff / 86400000);
|
||||||
|
if (days > 0) return `${days}d ago`;
|
||||||
|
if (hours > 0) return `${hours}h ago`;
|
||||||
|
if (minutes > 0) return `${minutes}m ago`;
|
||||||
|
return 'Just now';
|
||||||
|
} catch {
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -96,8 +104,9 @@ function CredentialRow({
|
|||||||
>
|
>
|
||||||
{statusMessage}
|
{statusMessage}
|
||||||
</Badge>
|
</Badge>
|
||||||
<div className="text-xs text-muted-foreground mt-0.5">
|
<div className="flex items-center gap-1 text-xs text-muted-foreground mt-0.5 justify-end">
|
||||||
Expires: {formatExpiry(expiresAt)}
|
<Clock className="w-3 h-3" />
|
||||||
|
{formatLastUsed(lastUsedAt)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{status === 'warning' && onRefresh && (
|
{status === 'warning' && onRefresh && (
|
||||||
@@ -129,23 +138,28 @@ function CredentialHealthSkeleton() {
|
|||||||
|
|
||||||
export function CredentialHealthList() {
|
export function CredentialHealthList() {
|
||||||
const { data: authData, isLoading } = useCliproxyAuth();
|
const { data: authData, isLoading } = useCliproxyAuth();
|
||||||
|
const { data: stats } = useCliproxyStats(true);
|
||||||
const { privacyMode } = usePrivacy();
|
const { privacyMode } = usePrivacy();
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <CredentialHealthSkeleton />;
|
return <CredentialHealthSkeleton />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten accounts from all providers
|
// Flatten accounts from all providers with runtime lastUsedAt
|
||||||
const credentials =
|
const credentials =
|
||||||
authData?.authStatus.flatMap((status) =>
|
authData?.authStatus.flatMap((status) =>
|
||||||
(status.accounts ?? []).map((account) => ({
|
(status.accounts ?? []).map((account) => {
|
||||||
name: account.id,
|
const accountKey = account.email || account.id;
|
||||||
provider: status.provider,
|
const runtimeLastUsed = stats?.accountStats?.[accountKey]?.lastUsedAt;
|
||||||
status: (account as { status?: CredentialStatus }).status ?? 'ready',
|
return {
|
||||||
statusMessage: (account as { statusMessage?: string }).statusMessage ?? 'Ready',
|
name: account.id,
|
||||||
email: account.email,
|
provider: status.provider,
|
||||||
expiresAt: (account as { expiresAt?: string }).expiresAt,
|
status: (account as { status?: CredentialStatus }).status ?? 'ready',
|
||||||
}))
|
statusMessage: (account as { statusMessage?: string }).statusMessage ?? 'Ready',
|
||||||
|
email: account.email,
|
||||||
|
lastUsedAt: runtimeLastUsed || account.lastUsedAt,
|
||||||
|
};
|
||||||
|
})
|
||||||
) ?? [];
|
) ?? [];
|
||||||
|
|
||||||
if (credentials.length === 0) {
|
if (credentials.length === 0) {
|
||||||
|
|||||||
Reference in new issue
Block a user