diff --git a/apps/admin/app/dashboard/tool/page.tsx b/apps/admin/app/dashboard/tool/page.tsx
deleted file mode 100644
index 8ec24b7..0000000
--- a/apps/admin/app/dashboard/tool/page.tsx
+++ /dev/null
@@ -1,351 +0,0 @@
-'use client';
-
-import { getSystemLog, getVersion, restartSystem } from '@/services/admin/tool';
-import { formatDate } from '@/utils/common';
-import { useQuery } from '@tanstack/react-query';
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from '@workspace/ui/components/accordion';
-import {
- AlertDialog,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
- AlertDialogTrigger,
-} from '@workspace/ui/components/alert-dialog';
-import { Badge } from '@workspace/ui/components/badge';
-import { Button } from '@workspace/ui/components/button';
-import {
- Card,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
-} from '@workspace/ui/components/card';
-import { ScrollArea } from '@workspace/ui/components/scroll-area';
-import { Icon } from '@workspace/ui/custom-components/icon';
-import { useTranslations } from 'next-intl';
-import { useState } from 'react';
-import packageJson from '../../../../../package.json';
-
-const getLogLevelColor = (level: string) => {
- const colorMap: { [key: string]: string } = {
- INFO: 'bg-blue-100 text-blue-800 hover:bg-blue-200',
- WARN: 'bg-yellow-100 text-yellow-800 hover:bg-yellow-200',
- ERROR: 'bg-red-100 text-red-800 hover:bg-red-200',
- };
- return colorMap[level] || 'bg-gray-100 text-gray-800 hover:bg-gray-200';
-};
-
-export default function Page() {
- const t = useTranslations('tool');
- const {
- data: logs,
- refetch,
- isLoading,
- } = useQuery({
- queryKey: ['getSystemLog'],
- queryFn: async () => {
- const { data } = await getSystemLog();
- return data.data?.list || [];
- },
- });
-
- const [openRestart, setOpenRestart] = useState(false);
- const [isRestarting, setIsRestarting] = useState(false);
-
- const { data: latestReleases } = useQuery({
- queryKey: ['getLatestReleases'],
- queryFn: async () => {
- try {
- const [webResponse, serverResponse] = await Promise.all([
- fetch('https://api.github.com/repos/perfect-panel/ppanel-web/releases/latest'),
- fetch('https://api.github.com/repos/perfect-panel/server/releases/latest'),
- ]);
-
- const webData = webResponse.ok ? await webResponse.json() : null;
- const serverData = serverResponse.ok ? await serverResponse.json() : null;
-
- return {
- web: webData
- ? {
- version: webData.tag_name,
- url: webData.html_url,
- publishedAt: webData.published_at,
- }
- : null,
- server: serverData
- ? {
- version: serverData.tag_name,
- url: serverData.html_url,
- publishedAt: serverData.published_at,
- }
- : null,
- };
- } catch (error) {
- console.error('Failed to fetch latest releases:', error);
- return { web: null, server: null };
- }
- },
- staleTime: 60 * 60 * 1000,
- retry: 1,
- retryDelay: 10000,
- });
-
- const hasNewVersion =
- latestReleases?.web && packageJson.version !== latestReleases.web.version.replace(/^v/, '');
-
- const { data: systemInfo } = useQuery({
- queryKey: ['getVersion'],
- queryFn: async () => {
- const { data } = await getVersion();
-
- const versionString = data.data?.version || '';
- const releaseVersionRegex = /^[Vv]?\d+\.\d+\.\d+(-[a-zA-Z]+(\.\d+)?)?$/;
- const timeMatch = versionString.match(/\(([^)]+)\)/);
- const timeInBrackets = timeMatch ? timeMatch[1] : '';
-
- const versionWithoutTime = versionString.replace(/\([^)]*\)/, '').trim();
-
- const isDevelopment =
- versionWithoutTime.includes('-dev') ||
- versionWithoutTime.includes('-debug') ||
- versionWithoutTime.includes('-nightly') ||
- versionWithoutTime.includes('dev') ||
- !releaseVersionRegex.test(versionWithoutTime);
-
- let baseVersion = versionWithoutTime;
- let lastUpdated = '';
-
- if (isDevelopment && versionWithoutTime.includes('-')) {
- const parts = versionWithoutTime.split('-');
- baseVersion = parts[0] || versionWithoutTime;
- }
-
- lastUpdated = formatDate(new Date(timeInBrackets || Date.now())) || '';
-
- const displayVersion =
- baseVersion.startsWith('V') || baseVersion.startsWith('v')
- ? baseVersion
- : `V${baseVersion}`;
-
- return {
- isRelease: !isDevelopment,
- version: displayVersion,
- lastUpdated,
- };
- },
- });
-
- const hasServerNewVersion =
- latestReleases?.server &&
- systemInfo &&
- systemInfo.version.replace(/^V/, '') !== latestReleases.server.version.replace(/^v/, '');
-
- return (
-
-
-
- {t('systemServices')}
- {t('viewLogsAndManage')}
-
-
- {/*
-
-
-
-
-
- {t('confirmSystemUpgrade')}
- {t('upgradeDescription')}
-
-
- {t('cancel')}
- {t('confirmUpgrade')}
-
-
- */}
-
-
-
-
-
-
- {t('confirmSystemReboot')}
- {t('rebootDescription')}
-
-
- {t('cancel')}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {t('webVersion')}
-
- V{packageJson.version}
-
- {hasNewVersion && (
-
- {t('newVersionAvailable')}
-
- )}
-
-
- {hasNewVersion && (
-
- )}
-
-
-
-
-
-
- {t('serverVersion')}
-
- {systemInfo?.version || 'V1.0.0'}
- {!systemInfo?.isRelease && (
- {t('developmentVersion')}
- )}
-
- {hasServerNewVersion && (
-
- {t('newVersionAvailable')}
-
- )}
-
-
-
- {hasServerNewVersion && (
-
- )}
-
-
{systemInfo?.lastUpdated || '--'}
-
-
-
-
-
-
-
- {t('systemLogs')}
-
-
-
-
-
- {isLoading ? (
-
-
-
- ) : (
-
- {logs?.map((log: any, index: number) => (
-
-
-
- {log.timestamp}
-
-
-
- {Object.entries(log).map(([key, value]) => (
-
- {key}:
- {value as string}
-
- ))}
- {/*
-
{t('ip')}:
-
{log.ip}
-
{t('request')}:
-
{log.request}
-
{t('status')}:
-
{log.status}
-
{t('caller')}:
-
{log.caller}
-
{t('errors')}:
-
{log.errors || t('none')}
-
{t('query')}:
-
{log.query || t('none')}
-
{t('userAgent')}:
-
{log['user-agent']}
-
*/}
-
-
- ))}
-
- )}
-
-
-
-
-
-
- );
-}
diff --git a/apps/admin/components/dashboard/statistics.tsx b/apps/admin/components/dashboard/statistics.tsx
index 7d266a8..e7a6038 100644
--- a/apps/admin/components/dashboard/statistics.tsx
+++ b/apps/admin/components/dashboard/statistics.tsx
@@ -22,6 +22,7 @@ import { useState } from 'react';
import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis } from 'recharts';
import { Empty } from '../empty';
import { RevenueStatisticsCard } from './revenue-statistics-card';
+import SystemVersionCard from './system-version-card';
import { UserStatisticsCard } from './user-statistics-card';
export default function Statistics() {
@@ -78,26 +79,18 @@ export default function Statistics() {
return (
<>
-
+
{[
{
title: t('onlineUsersCount'),
value: ServerTotal?.online_users || 0,
subtitle: t('currentlyOnline'),
icon: 'uil:users-alt',
- href: '/dashboard/servers',
+ href: '/dashboard/users',
color: 'text-blue-600 dark:text-blue-400',
iconBg: 'bg-blue-100 dark:bg-blue-900/30',
},
- {
- title: t('totalServers'),
- value: (ServerTotal?.online_servers || 0) + (ServerTotal?.offline_servers || 0),
- subtitle: `${t('online')} ${ServerTotal?.online_servers || 0} ${t('offline')} ${ServerTotal?.offline_servers || 0}`,
- icon: 'uil:server-network',
- href: '/dashboard/servers',
- color: 'text-green-600 dark:text-green-400',
- iconBg: 'bg-green-100 dark:bg-green-900/30',
- },
+
{
title: t('todayTraffic'),
value: formatBytes(
@@ -118,6 +111,15 @@ export default function Statistics() {
color: 'text-orange-600 dark:text-orange-400',
iconBg: 'bg-orange-100 dark:bg-orange-900/30',
},
+ {
+ title: t('totalServers'),
+ value: (ServerTotal?.online_servers || 0) + (ServerTotal?.offline_servers || 0),
+ subtitle: `${t('online')} ${ServerTotal?.online_servers || 0} ${t('offline')} ${ServerTotal?.offline_servers || 0}`,
+ icon: 'uil:server-network',
+ href: '/dashboard/servers',
+ color: 'text-green-600 dark:text-green-400',
+ iconBg: 'bg-green-100 dark:bg-green-900/30',
+ },
{
title: t('pendingTickets'),
value: TicketTotal || 0,
@@ -151,6 +153,7 @@ export default function Statistics() {
))}
+
diff --git a/apps/admin/components/dashboard/system-logs-dialog.tsx b/apps/admin/components/dashboard/system-logs-dialog.tsx
new file mode 100644
index 0000000..f893733
--- /dev/null
+++ b/apps/admin/components/dashboard/system-logs-dialog.tsx
@@ -0,0 +1,118 @@
+'use client';
+
+import { getSystemLog } from '@/services/admin/tool';
+import { useQuery } from '@tanstack/react-query';
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from '@workspace/ui/components/accordion';
+import { Button } from '@workspace/ui/components/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@workspace/ui/components/dialog';
+import { ScrollArea } from '@workspace/ui/components/scroll-area';
+import { Icon } from '@workspace/ui/custom-components/icon';
+import { useTranslations } from 'next-intl';
+import { useState } from 'react';
+
+const getLogLevelColor = (level: string) => {
+ const colorMap: { [key: string]: string } = {
+ INFO: 'bg-blue-100 text-blue-800 hover:bg-blue-200',
+ WARN: 'bg-yellow-100 text-yellow-800 hover:bg-yellow-200',
+ ERROR: 'bg-red-100 text-red-800 hover:bg-red-200',
+ };
+ return colorMap[level] || 'bg-gray-100 text-gray-800 hover:bg-gray-200';
+};
+
+interface SystemLogsDialogProps {
+ trigger?: React.ReactNode;
+ variant?: 'default' | 'outline' | 'ghost' | 'secondary';
+ size?: 'sm' | 'default' | 'lg';
+}
+
+export default function SystemLogsDialog({
+ trigger,
+ variant = 'outline',
+ size = 'sm',
+}: SystemLogsDialogProps) {
+ const t = useTranslations('tool');
+ const [open, setOpen] = useState(false);
+
+ const {
+ data: logs,
+ refetch,
+ isLoading,
+ } = useQuery({
+ queryKey: ['getSystemLog'],
+ queryFn: async () => {
+ const { data } = await getSystemLog();
+ return data.data?.list || [];
+ },
+ enabled: open,
+ });
+
+ const defaultTrigger = (
+
+ );
+
+ return (
+
+ );
+}
diff --git a/apps/admin/components/dashboard/system-version-card.tsx b/apps/admin/components/dashboard/system-version-card.tsx
new file mode 100644
index 0000000..4f73c86
--- /dev/null
+++ b/apps/admin/components/dashboard/system-version-card.tsx
@@ -0,0 +1,212 @@
+'use client';
+
+import { getVersion, restartSystem } from '@/services/admin/tool';
+import { formatDate } from '@/utils/common';
+import { useQuery } from '@tanstack/react-query';
+import {
+ AlertDialog,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from '@workspace/ui/components/alert-dialog';
+import { Badge } from '@workspace/ui/components/badge';
+import { Button } from '@workspace/ui/components/button';
+import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card';
+import { Icon } from '@workspace/ui/custom-components/icon';
+import { useTranslations } from 'next-intl';
+import Link from 'next/link';
+import { useState } from 'react';
+import packageJson from '../../../../package.json';
+import SystemLogsDialog from './system-logs-dialog';
+
+export default function SystemVersionCard() {
+ const t = useTranslations('tool');
+ const [openRestart, setOpenRestart] = useState(false);
+ const [isRestarting, setIsRestarting] = useState(false);
+
+ const { data: latestReleases } = useQuery({
+ queryKey: ['getLatestReleases'],
+ queryFn: async () => {
+ try {
+ const [webResponse, serverResponse] = await Promise.all([
+ fetch('https://api.github.com/repos/perfect-panel/ppanel-web/releases/latest'),
+ fetch('https://api.github.com/repos/perfect-panel/server/releases/latest'),
+ ]);
+
+ const webData = webResponse.ok ? await webResponse.json() : null;
+ const serverData = serverResponse.ok ? await serverResponse.json() : null;
+
+ return {
+ web: webData
+ ? {
+ version: webData.tag_name,
+ url: webData.html_url,
+ publishedAt: webData.published_at,
+ }
+ : null,
+ server: serverData
+ ? {
+ version: serverData.tag_name,
+ url: serverData.html_url,
+ publishedAt: serverData.published_at,
+ }
+ : null,
+ };
+ } catch (error) {
+ console.error('Failed to fetch latest releases:', error);
+ return { web: null, server: null };
+ }
+ },
+ staleTime: 60 * 60 * 1000,
+ retry: 1,
+ retryDelay: 10000,
+ });
+
+ const hasNewVersion =
+ latestReleases?.web && packageJson.version !== latestReleases.web.version.replace(/^v/, '');
+
+ const { data: systemInfo } = useQuery({
+ queryKey: ['getVersion'],
+ queryFn: async () => {
+ const { data } = await getVersion();
+
+ const versionString = (data.data?.version || '').replace(' Develop', '').trim();
+ const releaseVersionRegex = /^[Vv]?\d+\.\d+\.\d+(-[a-zA-Z]+(\.\d+)?)?$/;
+ const timeMatch = versionString.match(/\(([^)]+)\)/);
+ const timeInBrackets = timeMatch ? timeMatch[1] : '';
+
+ const versionWithoutTime = versionString.replace(/\([^)]*\)/, '').trim();
+
+ const isDevelopment =
+ versionWithoutTime.includes('-dev') ||
+ versionWithoutTime.includes('-debug') ||
+ versionWithoutTime.includes('-nightly') ||
+ versionWithoutTime.includes('dev') ||
+ !releaseVersionRegex.test(versionWithoutTime);
+
+ let baseVersion = versionWithoutTime;
+ let lastUpdated = '';
+
+ if (isDevelopment && versionWithoutTime.includes('-')) {
+ const parts = versionWithoutTime.split('-');
+ baseVersion = parts[0] || versionWithoutTime;
+ }
+
+ lastUpdated = formatDate(new Date(timeInBrackets || Date.now())) || '';
+
+ const displayVersion =
+ baseVersion.startsWith('V') || baseVersion.startsWith('v')
+ ? baseVersion
+ : `V${baseVersion}`;
+
+ return {
+ isRelease: !isDevelopment,
+ version: displayVersion,
+ lastUpdated,
+ };
+ },
+ });
+
+ const hasServerNewVersion =
+ latestReleases?.server &&
+ systemInfo &&
+ systemInfo.version.replace(/^V/, '') !== latestReleases.server.version.replace(/^v/, '');
+
+ return (
+
+
+
+ {t('systemServices')}
+
+
+
+
+
+
+
+
+ {t('confirmSystemReboot')}
+ {t('rebootDescription')}
+
+
+ {t('cancel')}
+
+
+
+
+
+
+
+
+
+
+
+ {t('webVersion')}
+
+
+ V{packageJson.version}
+ {hasNewVersion && (
+
+
+ {t('newVersionAvailable')}
+
+
+
+ )}
+
+
+
+
+
+ {t('serverVersion')}
+
+
+
+ {systemInfo?.version || 'V1.0.0'}
+
+ {hasServerNewVersion && (
+
+
+ {t('newVersionAvailable')}
+
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/apps/admin/config/navs.ts b/apps/admin/config/navs.ts
index a3fe597..aaf318e 100644
--- a/apps/admin/config/navs.ts
+++ b/apps/admin/config/navs.ts
@@ -77,7 +77,6 @@ export const navs = [
icon: 'flat-color-icons:currency-exchange',
},
{ title: 'ADS Config', url: '/dashboard/ads', icon: 'flat-color-icons:electrical-sensor' },
- { title: 'System Tool', url: '/dashboard/tool', icon: 'flat-color-icons:info' },
],
},