From fe699809a4e02e3bb9c9ae8c31ad7875f3aec5a2 Mon Sep 17 00:00:00 2001 From: web Date: Sat, 13 Sep 2025 04:07:31 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20Added=20system=20version?= =?UTF-8?q?=20card=20and=20system=20log=20dialog=20components;=20updated?= =?UTF-8?q?=20statistics=20page=20to=20include=20total=20server=20and=20us?= =?UTF-8?q?er=20statistics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/admin/app/dashboard/tool/page.tsx | 351 ------------------ .../admin/components/dashboard/statistics.tsx | 25 +- .../dashboard/system-logs-dialog.tsx | 118 ++++++ .../dashboard/system-version-card.tsx | 212 +++++++++++ apps/admin/config/navs.ts | 1 - 5 files changed, 344 insertions(+), 363 deletions(-) delete mode 100644 apps/admin/app/dashboard/tool/page.tsx create mode 100644 apps/admin/components/dashboard/system-logs-dialog.tsx create mode 100644 apps/admin/components/dashboard/system-version-card.tsx 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 ( + + {trigger || defaultTrigger} + + + {t('systemLogs')} + + + {isLoading ? ( +
+ +
+ ) : ( + + {logs?.map((log: any, index: number) => ( + + +
+ {log.timestamp} +
+
+ + {Object.entries(log).map(([key, value]) => ( +
+ {key}: + {value as string} +
+ ))} +
+
+ ))} +
+ )} +
+ + + +
+
+ ); +} 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' }, ], },