113 lines
3.9 KiB
TypeScript
113 lines
3.9 KiB
TypeScript
'use client';
|
|
|
|
import {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
} from '@workspace/ui/components/accordion';
|
|
import { Badge } from '@workspace/ui/components/badge';
|
|
import { Progress } from '@workspace/ui/components/progress';
|
|
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from '@workspace/ui/components/tooltip';
|
|
import { formatDate } from '@workspace/ui/utils';
|
|
import { useTranslations } from 'next-intl';
|
|
import { useState } from 'react';
|
|
import { UserSubscribeDetail } from '../user/user-detail';
|
|
|
|
export function formatPercentage(value: number): string {
|
|
return `${value.toFixed(1)}%`;
|
|
}
|
|
|
|
export function NodeStatusCell({ status }: { status: API.NodeStatus }) {
|
|
const t = useTranslations('server.node');
|
|
const [openItem, setOpenItem] = useState<string | null>(null);
|
|
|
|
const { online, cpu, mem, disk, updated_at } = status || {
|
|
online: {},
|
|
cpu: 0,
|
|
mem: 0,
|
|
disk: 0,
|
|
updated_at: 0,
|
|
};
|
|
const isOnline = updated_at > 0;
|
|
const badgeVariant = isOnline ? 'default' : 'destructive';
|
|
const badgeText = isOnline ? t('normal') : t('abnormal');
|
|
const onlineCount = Object.keys(online).length || 0;
|
|
|
|
return (
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<div className='flex items-center gap-2 text-xs *:flex-1'>
|
|
<div className='flex items-center space-x-1'>
|
|
<Badge variant={badgeVariant}>{badgeText}</Badge>
|
|
<span className='font-medium'>
|
|
{t('onlineCount')}: {onlineCount}
|
|
</span>
|
|
</div>
|
|
<div className='flex flex-col space-y-1'>
|
|
<div className='flex justify-between'>
|
|
<span>CPU</span>
|
|
<span>{formatPercentage(cpu ?? 0)}</span>
|
|
</div>
|
|
<Progress value={cpu ?? 0} className='h-2' max={100} />
|
|
</div>
|
|
<div className='flex flex-col space-y-1'>
|
|
<div className='flex justify-between'>
|
|
<span>{t('memory')}</span>
|
|
<span>{formatPercentage(mem ?? 0)}</span>
|
|
</div>
|
|
<Progress value={mem ?? 0} className='h-2' max={100} />
|
|
</div>
|
|
<div className='flex flex-col space-y-1'>
|
|
<div className='flex justify-between'>
|
|
<span>{t('disk')}</span>
|
|
<span>{formatPercentage(disk ?? 0)}</span>
|
|
</div>
|
|
<Progress value={disk ?? 0} className='h-2' max={100} />
|
|
</div>
|
|
{isOnline && (
|
|
<div>
|
|
{t('lastUpdated')}: {formatDate(updated_at ?? 0)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TooltipTrigger>
|
|
{isOnline && onlineCount > 0 && (
|
|
<TooltipContent className='bg-card text-foreground w-96'>
|
|
<ScrollArea className='h-[540px] rounded-md border px-4 py-2'>
|
|
<h4 className='py-1 text-sm font-semibold'>{t('onlineUsers')}</h4>
|
|
<Accordion
|
|
type='single'
|
|
collapsible
|
|
className='w-full'
|
|
onValueChange={(value) => setOpenItem(value)}
|
|
>
|
|
{Object.entries(online).map(([uid, ips]) => (
|
|
<AccordionItem key={uid} value={uid}>
|
|
<AccordionTrigger>{`[UID: ${uid}] - ${ips[0]}`}</AccordionTrigger>
|
|
<AccordionContent>
|
|
<ul>
|
|
{ips.map((ip: string) => (
|
|
<li key={ip}>{ip}</li>
|
|
))}
|
|
</ul>
|
|
<UserSubscribeDetail id={Number(uid)} enabled={openItem === uid} />
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
))}
|
|
</Accordion>
|
|
</ScrollArea>
|
|
</TooltipContent>
|
|
)}
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
);
|
|
}
|