✨ feat(node): Add NodeStatus
This commit is contained in:
parent
d0be6854e6
commit
c712624930
94
apps/admin/app/dashboard/server/node-status.tsx
Normal file
94
apps/admin/app/dashboard/server/node-status.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { formatDate } from '@repo/ui/utils';
|
||||||
|
import { Badge } from '@shadcn/ui/badge';
|
||||||
|
import { Progress } from '@shadcn/ui/progress';
|
||||||
|
import { ScrollArea } from '@shadcn/ui/scroll-area';
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@shadcn/ui/tooltip';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
export function formatPercentage(value: number): string {
|
||||||
|
return `${(value * 100).toFixed(1)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NodeStatusCell({ status }: { status: API.NodeStatus }) {
|
||||||
|
const t = useTranslations('server.node');
|
||||||
|
|
||||||
|
const {
|
||||||
|
last_at,
|
||||||
|
online_users,
|
||||||
|
status: serverStatus,
|
||||||
|
} = status || {
|
||||||
|
online_users: [],
|
||||||
|
status: {
|
||||||
|
cpu: 0,
|
||||||
|
mem: 0,
|
||||||
|
disk: 0,
|
||||||
|
updated_at: 0,
|
||||||
|
},
|
||||||
|
last_at: 0,
|
||||||
|
};
|
||||||
|
const isOnline = last_at > 0;
|
||||||
|
const badgeVariant = isOnline ? 'default' : 'destructive';
|
||||||
|
const badgeText = isOnline ? t('normal') : t('abnormal');
|
||||||
|
const onlineCount = Array.isArray(online_users) ? online_users.length : 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div className='flex items-center space-x-2 rounded-md'>
|
||||||
|
<Badge variant={badgeVariant}>{badgeText}</Badge>
|
||||||
|
<span className='text-sm font-medium'>
|
||||||
|
{t('onlineCount')}: {onlineCount}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent className='bg-muted text-foreground w-80'>
|
||||||
|
<div className='space-y-4'>
|
||||||
|
<>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<div className='space-y-1'>
|
||||||
|
<div className='flex justify-between text-xs'>
|
||||||
|
<span className='font-medium'>CPU</span>
|
||||||
|
<span>{formatPercentage(serverStatus?.cpu ?? 0)}</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={(serverStatus?.cpu ?? 0) * 100} className='h-2' />
|
||||||
|
</div>
|
||||||
|
<div className='space-y-1'>
|
||||||
|
<div className='flex justify-between text-xs'>
|
||||||
|
<span className='font-medium'>{t('memory')}</span>
|
||||||
|
<span>{formatPercentage(serverStatus?.mem ?? 0)}</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={(serverStatus?.mem ?? 0) * 100} className='h-2' />
|
||||||
|
</div>
|
||||||
|
<div className='space-y-1'>
|
||||||
|
<div className='flex justify-between text-xs'>
|
||||||
|
<span className='font-medium'>{t('disk')}</span>
|
||||||
|
<span>{formatPercentage(serverStatus?.disk ?? 0)}</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={(serverStatus?.disk ?? 0) * 100} className='h-2' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='text-xs'>
|
||||||
|
{t('lastUpdated')}: {formatDate(serverStatus?.updated_at ?? 0)}
|
||||||
|
</div>
|
||||||
|
{isOnline && online_users.length > 0 && (
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<h4 className='text-sm font-semibold'>{t('onlineUsers')}</h4>
|
||||||
|
<ScrollArea className='h-[400px] rounded-md border p-2'>
|
||||||
|
{online_users.map((user, index) => (
|
||||||
|
<div key={user.uid} className='py-1 text-xs'>
|
||||||
|
{user.ip} (UID: {user.uid})
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -21,6 +21,7 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import NodeForm from './node-form';
|
import NodeForm from './node-form';
|
||||||
|
import { NodeStatusCell } from './node-status';
|
||||||
|
|
||||||
export default function NodeTable() {
|
export default function NodeTable() {
|
||||||
const t = useTranslations('server.node');
|
const t = useTranslations('server.node');
|
||||||
@ -109,23 +110,11 @@ export default function NodeTable() {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
accessorKey: 'last',
|
accessorKey: 'status',
|
||||||
header: t('status'),
|
header: t('status'),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const { last } = row.original;
|
return <NodeStatusCell status={row.original?.status} />;
|
||||||
|
|
||||||
return last ? (
|
|
||||||
<>
|
|
||||||
<Badge>{t('normal')}</Badge>
|
|
||||||
<span className='ml-2'>
|
|
||||||
{t('onlineCount')}: {last.count}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Badge variant='destructive'>{t('abnormal')}</Badge>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -40,6 +40,10 @@
|
|||||||
"edit": "编辑",
|
"edit": "编辑",
|
||||||
"editNode": "编辑节点",
|
"editNode": "编辑节点",
|
||||||
"enable": "启用",
|
"enable": "启用",
|
||||||
|
"memory": "内存",
|
||||||
|
"disk": "磁盘",
|
||||||
|
"lastUpdated": "最后更新",
|
||||||
|
"onlineUsers": "在线用户",
|
||||||
"form": {
|
"form": {
|
||||||
"allowInsecure": "允许不安全",
|
"allowInsecure": "允许不安全",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
|
|||||||
27
apps/admin/services/admin/typings.d.ts
vendored
27
apps/admin/services/admin/typings.d.ts
vendored
@ -487,6 +487,17 @@ declare namespace API {
|
|||||||
node_push_interval: number;
|
node_push_interval: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type NodeStatus = {
|
||||||
|
online_users: OnlineUser[];
|
||||||
|
status: ServerStatus;
|
||||||
|
last_at: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OnlineUser = {
|
||||||
|
uid: number;
|
||||||
|
ip: string;
|
||||||
|
};
|
||||||
|
|
||||||
type Order = {
|
type Order = {
|
||||||
id: number;
|
id: number;
|
||||||
user_id: number;
|
user_id: number;
|
||||||
@ -541,11 +552,6 @@ declare namespace API {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Push = {
|
|
||||||
push_at: number;
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RegisterConfig = {
|
type RegisterConfig = {
|
||||||
stop_register: boolean;
|
stop_register: boolean;
|
||||||
enable_trial: boolean;
|
enable_trial: boolean;
|
||||||
@ -590,7 +596,7 @@ declare namespace API {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
updated_at: number;
|
updated_at: number;
|
||||||
last: Push;
|
status: NodeStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ServerGroup = {
|
type ServerGroup = {
|
||||||
@ -601,6 +607,13 @@ declare namespace API {
|
|||||||
updated_at: number;
|
updated_at: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ServerStatus = {
|
||||||
|
cpu: number;
|
||||||
|
mem: number;
|
||||||
|
disk: number;
|
||||||
|
updated_at: number;
|
||||||
|
};
|
||||||
|
|
||||||
type Shadowsocks = {
|
type Shadowsocks = {
|
||||||
method: string;
|
method: string;
|
||||||
port: number;
|
port: number;
|
||||||
@ -687,7 +700,7 @@ declare namespace API {
|
|||||||
type TransportConfig = {
|
type TransportConfig = {
|
||||||
path: string;
|
path: string;
|
||||||
host: string;
|
host: string;
|
||||||
server_name: string;
|
service_name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Trojan = {
|
type Trojan = {
|
||||||
|
|||||||
27
apps/admin/services/common/typings.d.ts
vendored
27
apps/admin/services/common/typings.d.ts
vendored
@ -139,6 +139,17 @@ declare namespace API {
|
|||||||
node_push_interval: number;
|
node_push_interval: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type NodeStatus = {
|
||||||
|
online_users: OnlineUser[];
|
||||||
|
status: ServerStatus;
|
||||||
|
last_at: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OnlineUser = {
|
||||||
|
uid: number;
|
||||||
|
ip: string;
|
||||||
|
};
|
||||||
|
|
||||||
type Order = {
|
type Order = {
|
||||||
id: number;
|
id: number;
|
||||||
user_id: number;
|
user_id: number;
|
||||||
@ -193,11 +204,6 @@ declare namespace API {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Push = {
|
|
||||||
push_at: number;
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RegisterConfig = {
|
type RegisterConfig = {
|
||||||
stop_register: boolean;
|
stop_register: boolean;
|
||||||
enable_trial: boolean;
|
enable_trial: boolean;
|
||||||
@ -258,7 +264,7 @@ declare namespace API {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
updated_at: number;
|
updated_at: number;
|
||||||
last: Push;
|
status: NodeStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ServerGroup = {
|
type ServerGroup = {
|
||||||
@ -269,6 +275,13 @@ declare namespace API {
|
|||||||
updated_at: number;
|
updated_at: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ServerStatus = {
|
||||||
|
cpu: number;
|
||||||
|
mem: number;
|
||||||
|
disk: number;
|
||||||
|
updated_at: number;
|
||||||
|
};
|
||||||
|
|
||||||
type Shadowsocks = {
|
type Shadowsocks = {
|
||||||
method: string;
|
method: string;
|
||||||
port: number;
|
port: number;
|
||||||
@ -351,7 +364,7 @@ declare namespace API {
|
|||||||
type TransportConfig = {
|
type TransportConfig = {
|
||||||
path: string;
|
path: string;
|
||||||
host: string;
|
host: string;
|
||||||
server_name: string;
|
service_name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Trojan = {
|
type Trojan = {
|
||||||
|
|||||||
27
apps/user/services/common/typings.d.ts
vendored
27
apps/user/services/common/typings.d.ts
vendored
@ -139,6 +139,17 @@ declare namespace API {
|
|||||||
node_push_interval: number;
|
node_push_interval: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type NodeStatus = {
|
||||||
|
online_users: OnlineUser[];
|
||||||
|
status: ServerStatus;
|
||||||
|
last_at: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OnlineUser = {
|
||||||
|
uid: number;
|
||||||
|
ip: string;
|
||||||
|
};
|
||||||
|
|
||||||
type Order = {
|
type Order = {
|
||||||
id: number;
|
id: number;
|
||||||
user_id: number;
|
user_id: number;
|
||||||
@ -193,11 +204,6 @@ declare namespace API {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Push = {
|
|
||||||
push_at: number;
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RegisterConfig = {
|
type RegisterConfig = {
|
||||||
stop_register: boolean;
|
stop_register: boolean;
|
||||||
enable_trial: boolean;
|
enable_trial: boolean;
|
||||||
@ -258,7 +264,7 @@ declare namespace API {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
updated_at: number;
|
updated_at: number;
|
||||||
last: Push;
|
status: NodeStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ServerGroup = {
|
type ServerGroup = {
|
||||||
@ -269,6 +275,13 @@ declare namespace API {
|
|||||||
updated_at: number;
|
updated_at: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ServerStatus = {
|
||||||
|
cpu: number;
|
||||||
|
mem: number;
|
||||||
|
disk: number;
|
||||||
|
updated_at: number;
|
||||||
|
};
|
||||||
|
|
||||||
type Shadowsocks = {
|
type Shadowsocks = {
|
||||||
method: string;
|
method: string;
|
||||||
port: number;
|
port: number;
|
||||||
@ -351,7 +364,7 @@ declare namespace API {
|
|||||||
type TransportConfig = {
|
type TransportConfig = {
|
||||||
path: string;
|
path: string;
|
||||||
host: string;
|
host: string;
|
||||||
server_name: string;
|
service_name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Trojan = {
|
type Trojan = {
|
||||||
|
|||||||
27
apps/user/services/user/typings.d.ts
vendored
27
apps/user/services/user/typings.d.ts
vendored
@ -156,6 +156,17 @@ declare namespace API {
|
|||||||
node_push_interval: number;
|
node_push_interval: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type NodeStatus = {
|
||||||
|
online_users: OnlineUser[];
|
||||||
|
status: ServerStatus;
|
||||||
|
last_at: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OnlineUser = {
|
||||||
|
uid: number;
|
||||||
|
ip: string;
|
||||||
|
};
|
||||||
|
|
||||||
type Order = {
|
type Order = {
|
||||||
id: number;
|
id: number;
|
||||||
user_id: number;
|
user_id: number;
|
||||||
@ -234,11 +245,6 @@ declare namespace API {
|
|||||||
order_no: string;
|
order_no: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Push = {
|
|
||||||
push_at: number;
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type QueryAnnouncementParams = {
|
type QueryAnnouncementParams = {
|
||||||
page: number;
|
page: number;
|
||||||
size: number;
|
size: number;
|
||||||
@ -391,7 +397,7 @@ declare namespace API {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
updated_at: number;
|
updated_at: number;
|
||||||
last: Push;
|
status: NodeStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ServerGroup = {
|
type ServerGroup = {
|
||||||
@ -402,6 +408,13 @@ declare namespace API {
|
|||||||
updated_at: number;
|
updated_at: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ServerStatus = {
|
||||||
|
cpu: number;
|
||||||
|
mem: number;
|
||||||
|
disk: number;
|
||||||
|
updated_at: number;
|
||||||
|
};
|
||||||
|
|
||||||
type Shadowsocks = {
|
type Shadowsocks = {
|
||||||
method: string;
|
method: string;
|
||||||
port: number;
|
port: number;
|
||||||
@ -490,7 +503,7 @@ declare namespace API {
|
|||||||
type TransportConfig = {
|
type TransportConfig = {
|
||||||
path: string;
|
path: string;
|
||||||
host: string;
|
host: string;
|
||||||
server_name: string;
|
service_name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Trojan = {
|
type Trojan = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user