179 lines
6.4 KiB
TypeScript
179 lines
6.4 KiB
TypeScript
import { useTranslation } from 'react-i18next';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Trash2,
|
|
Pencil,
|
|
Key,
|
|
CheckCircle2,
|
|
XCircle,
|
|
Loader2,
|
|
MapPin,
|
|
Wallet,
|
|
CloudDownload,
|
|
} from 'lucide-react';
|
|
import { CloudBrandIcon, getProviderColor } from '@/components/icons/CloudBrandIcon';
|
|
import type { JsonData } from '@/types/common';
|
|
import type { ActionLoading } from './types';
|
|
|
|
export interface AccountCardProps {
|
|
account: JsonData;
|
|
providerLabels: Record<string, string>;
|
|
actionLoading: ActionLoading;
|
|
onEdit: (account: JsonData) => void;
|
|
onDelete: (id: string) => void;
|
|
onDetail: (account: JsonData) => void;
|
|
onSyncRegions: (account: JsonData) => void;
|
|
onSyncBilling: (account: JsonData) => void;
|
|
onImport: (account: JsonData) => void;
|
|
}
|
|
|
|
export function AccountCard({
|
|
account,
|
|
providerLabels,
|
|
actionLoading,
|
|
onEdit,
|
|
onDelete,
|
|
onDetail,
|
|
onSyncRegions,
|
|
onSyncBilling,
|
|
onImport,
|
|
}: AccountCardProps) {
|
|
const { t } = useTranslation();
|
|
const accountIdStr = String(account.id);
|
|
const accessKey = account.access_key || account.accessKey || '';
|
|
const serverCount = account.server_count ?? account.serverCount ?? 0;
|
|
const lastSync = account.last_sync || account.lastSync || account.created_at || '-';
|
|
const monthlyCost = account.monthly_cost ?? account.monthlyCost ?? 0;
|
|
|
|
return (
|
|
<Card key={account.id} className="dg-card dg-card-hover">
|
|
<CardContent className="p-4">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-start gap-3 min-w-0">
|
|
<div
|
|
className="w-10 h-10 rounded-lg bg-[var(--dg-bg-tertiary)] flex items-center justify-center shrink-0"
|
|
style={{ borderBottom: `2px solid ${getProviderColor(account.provider)}` }}
|
|
>
|
|
<CloudBrandIcon provider={account.provider} className="h-6 w-6" />
|
|
</div>
|
|
<div className="min-w-0">
|
|
<button
|
|
type="button"
|
|
className="font-medium text-[var(--dg-text-primary)] hover:text-[var(--dg-blue)] text-left truncate block max-w-full"
|
|
onClick={() => onDetail(account)}
|
|
>
|
|
{account.name}
|
|
</button>
|
|
<p className="text-sm dg-text-secondary mt-0.5">
|
|
{providerLabels[account.provider] || account.provider}
|
|
</p>
|
|
<div className="flex items-center gap-3 mt-2">
|
|
{accessKey && (
|
|
<Badge variant="secondary" className="dg-badge-muted">
|
|
<Key className="h-3 w-3 mr-1" />
|
|
{accessKey.slice(0, 8)}...
|
|
</Badge>
|
|
)}
|
|
{account.status === 'active' && (
|
|
<Badge className="dg-status-running border-0">
|
|
<CheckCircle2 className="h-3 w-3 mr-1" />
|
|
{t('cloud.normal')}
|
|
</Badge>
|
|
)}
|
|
{account.status === 'inactive' && (
|
|
<Badge className="dg-status-stopped border-0">
|
|
<XCircle className="h-3 w-3 mr-1" />
|
|
{t('cloud.stopped')}
|
|
</Badge>
|
|
)}
|
|
{account.status === 'error' && (
|
|
<Badge className="dg-status-error border-0">
|
|
<Key className="h-3 w-3 mr-1" />
|
|
{t('cloud.error')}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-8 w-8 dg-text-muted hover:text-[var(--dg-blue)]"
|
|
onClick={() => onEdit(account)}
|
|
>
|
|
<Pencil className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-8 w-8 dg-text-muted hover:text-[var(--dg-red)]"
|
|
onClick={() => onDelete(account.id)}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-3 gap-4 mt-4 pt-4 border-t border-[var(--dg-border)]">
|
|
<div>
|
|
<p className="text-xs dg-text-secondary">{t('cloud.monthlyFee')}</p>
|
|
<p className="text-lg font-medium text-[var(--dg-green)]">
|
|
¥{Number(monthlyCost).toLocaleString()}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs dg-text-secondary">{t('cloud.serverCount')}</p>
|
|
<p className="text-lg font-medium text-[var(--dg-text-primary)]">{serverCount}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs dg-text-secondary">{t('cloud.syncTime')}</p>
|
|
<p className="text-sm text-[var(--dg-text-secondary)]">
|
|
{lastSync !== '-' ? new Date(lastSync).toLocaleString() : '-'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-wrap gap-2 mt-3 pt-3 border-t border-[var(--dg-border)]">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="h-7 text-xs dg-btn-secondary"
|
|
disabled={actionLoading[accountIdStr] === 'regions'}
|
|
onClick={() => onSyncRegions(account)}
|
|
>
|
|
{actionLoading[accountIdStr] === 'regions' ? (
|
|
<Loader2 className="h-3 w-3 mr-1 animate-spin" />
|
|
) : (
|
|
<MapPin className="h-3 w-3 mr-1" />
|
|
)}
|
|
{t('cloud.syncRegions')}
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="h-7 text-xs dg-btn-secondary"
|
|
disabled={actionLoading[accountIdStr] === 'billing'}
|
|
onClick={() => onSyncBilling(account)}
|
|
>
|
|
{actionLoading[accountIdStr] === 'billing' ? (
|
|
<Loader2 className="h-3 w-3 mr-1 animate-spin" />
|
|
) : (
|
|
<Wallet className="h-3 w-3 mr-1" />
|
|
)}
|
|
{t('cloud.syncBilling')}
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
className="h-7 text-xs dg-btn-primary"
|
|
onClick={() => onImport(account)}
|
|
>
|
|
<CloudDownload className="h-3 w-3 mr-1" />
|
|
{t('cloud.importHosts')}
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|