From 45646551f14a304c62988d6f0fee2c111fd0bdf2 Mon Sep 17 00:00:00 2001 From: Naterfute Date: Thu, 15 Jan 2026 04:39:24 -0800 Subject: [PATCH] feat: added uptime and improved buttons on console --- .../components/elements/ActionButton.tsx | 3 +- .../components/server/UptimeDuration.ts | 19 +++ .../components/server/UptimeDuration.tsx | 24 --- .../server/console/PowerButtons.tsx | 19 +-- .../server/console/ServerConsoleContainer.tsx | 1 + .../server/console/ServerDetailsBlock.tsx | 151 ------------------ .../components/server/console/StatGraphs.tsx | 15 +- 7 files changed, 46 insertions(+), 186 deletions(-) create mode 100644 resources/scripts/components/server/UptimeDuration.ts delete mode 100644 resources/scripts/components/server/UptimeDuration.tsx delete mode 100644 resources/scripts/components/server/console/ServerDetailsBlock.tsx diff --git a/resources/scripts/components/elements/ActionButton.tsx b/resources/scripts/components/elements/ActionButton.tsx index 1e492f68f..e2fb69e80 100644 --- a/resources/scripts/components/elements/ActionButton.tsx +++ b/resources/scripts/components/elements/ActionButton.tsx @@ -2,7 +2,7 @@ import { forwardRef } from 'react'; interface ActionButtonProps extends React.ButtonHTMLAttributes { variant?: 'primary' | 'secondary' | 'danger'; - size?: 'sm' | 'md' | 'lg'; + size?: 'start' | 'sm' | 'md' | 'lg'; children: React.ReactNode; } @@ -19,6 +19,7 @@ const ActionButton = forwardRef( }; const sizeClasses = { + start: 'h-8 px-2 py-1.5 text-xs', sm: 'h-8 px-3 py-1.5 text-xs', md: 'h-10 px-4 py-2 text-sm', lg: 'h-12 px-6 py-3 text-base', diff --git a/resources/scripts/components/server/UptimeDuration.ts b/resources/scripts/components/server/UptimeDuration.ts new file mode 100644 index 000000000..4268f9aa0 --- /dev/null +++ b/resources/scripts/components/server/UptimeDuration.ts @@ -0,0 +1,19 @@ +export function formatUptime(uptime: number): string { + if (uptime <= 0) { + return 'Offline'; + } + + const secondsTotal = Math.floor(uptime / 1000); + const days = Math.floor(secondsTotal / 86400); + const hours = Math.floor((secondsTotal % 86400) / 3600); + const minutes = Math.floor((secondsTotal % 3600) / 60); + const seconds = secondsTotal % 60; + + if (days > 0) { + return `${days}d ${hours}h ${minutes}m`; + } + + return `${hours}h ${minutes}m ${seconds}s`; +} + +export default formatUptime; diff --git a/resources/scripts/components/server/UptimeDuration.tsx b/resources/scripts/components/server/UptimeDuration.tsx deleted file mode 100644 index 1bc8cf17b..000000000 --- a/resources/scripts/components/server/UptimeDuration.tsx +++ /dev/null @@ -1,24 +0,0 @@ -const UptimeDuration = ({ uptime }: { uptime: number }) => { - const uptimeDiv = uptime / 1000; - const days = Math.floor(uptimeDiv / (24 * 60 * 60)); - const hours = Math.floor((Math.floor(uptimeDiv) / 60 / 60) % 24); - const remainder = Math.floor(uptimeDiv - hours * 60 * 60); - const minutes = Math.floor((remainder / 60) % 60); - const seconds = remainder % 60; - - if (days > 0) { - return ( - <> - {days}d {hours}h {minutes}m - - ); - } - - return ( - <> - {hours}h {minutes}m {seconds}s - - ); -}; - -export default UptimeDuration; diff --git a/resources/scripts/components/server/console/PowerButtons.tsx b/resources/scripts/components/server/console/PowerButtons.tsx index 83ff7cbb8..a8e836e63 100644 --- a/resources/scripts/components/server/console/PowerButtons.tsx +++ b/resources/scripts/components/server/console/PowerButtons.tsx @@ -3,6 +3,7 @@ import { HugeiconsIcon } from '@hugeicons/react'; import { useEffect, useState } from 'react'; import { toast } from 'sonner'; +import ActionButton from '@/components/elements/ActionButton'; import Can from '@/components/elements/Can'; import { Dialog } from '@/components/elements/dialog'; import { PowerAction } from '@/components/server/console/ServerConsoleContainer'; @@ -71,9 +72,9 @@ const PowerButtons = ({ className }: PowerButtonProps) => { Forcibly stopping a server can lead to data corruption. - + - + - + ); diff --git a/resources/scripts/components/server/console/ServerConsoleContainer.tsx b/resources/scripts/components/server/console/ServerConsoleContainer.tsx index 233b3457c..3996984f8 100644 --- a/resources/scripts/components/server/console/ServerConsoleContainer.tsx +++ b/resources/scripts/components/server/console/ServerConsoleContainer.tsx @@ -36,6 +36,7 @@ const ServerConsoleContainer = () => { {name} +
diff --git a/resources/scripts/components/server/console/ServerDetailsBlock.tsx b/resources/scripts/components/server/console/ServerDetailsBlock.tsx deleted file mode 100644 index 297696fd4..000000000 --- a/resources/scripts/components/server/console/ServerDetailsBlock.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { useEffect, useMemo, useState } from 'react'; - -import StatBlock from '@/components/server/console/StatBlock'; -import { SocketEvent, SocketRequest } from '@/components/server/events'; - -import { bytesToString, ip, mbToBytes } from '@/lib/formatters'; -import { cn } from '@/lib/utils'; - -import { ServerContext } from '@/state/server'; - -import useWebsocketEvent from '@/plugins/useWebsocketEvent'; - -type Stats = Record<'memory' | 'cpu' | 'disk' | 'uptime' | 'rx' | 'tx', number>; - -// const getBackgroundColor = (value: number, max: number | null): string | undefined => { -// const delta = !max ? 0 : value / max; - -// if (delta > 0.8) { -// if (delta > 0.9) { -// return 'bg-red-500'; -// } -// return 'bg-yellow-500'; -// } - -// return undefined; -// }; - -// @ts-expect-error - Unused parameter in component definition -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const Limit = ({ limit, children }: { limit: string | null; children: React.ReactNode }) => <>{children}; - -const ServerDetailsBlock = ({ className }: { className?: string }) => { - const [stats, setStats] = useState({ memory: 0, cpu: 0, disk: 0, uptime: 0, tx: 0, rx: 0 }); - - const status = ServerContext.useStoreState((state) => state.status.value); - const connected = ServerContext.useStoreState((state) => state.socket.connected); - const instance = ServerContext.useStoreState((state) => state.socket.instance); - const limits = ServerContext.useStoreState((state) => state.server.data!.limits); - - const textLimits = useMemo( - () => ({ - cpu: limits?.cpu ? `${limits.cpu}%` : null, - memory: limits?.memory ? bytesToString(mbToBytes(limits.memory)) : null, - disk: limits?.disk ? bytesToString(mbToBytes(limits.disk)) : null, - }), - [limits], - ); - - const allocation = ServerContext.useStoreState((state) => { - const match = state.server.data!.allocations.find((allocation) => allocation.isDefault); - - return !match ? 'n/a' : `${match.alias || ip(match.ip)}:${match.port}`; - }); - - useEffect(() => { - if (!connected || !instance) { - return; - } - - instance.send(SocketRequest.SEND_STATS); - }, [instance, connected]); - - useWebsocketEvent(SocketEvent.STATS, (data) => { - let stats: any = {}; - try { - stats = JSON.parse(data); - } catch (e) { - return; - } - - setStats({ - memory: stats.memory_bytes, - cpu: stats.cpu_absolute, - disk: stats.disk_bytes, - tx: stats.network.tx_bytes, - rx: stats.network.rx_bytes, - uptime: stats.uptime || 0, - }); - }); - - return ( -
-
- - {allocation} - -
-
- - {status === 'offline' ? ( - Offline - ) : ( - {stats.cpu.toFixed(2)}% - )} - -
-
- - {status === 'offline' ? ( - Offline - ) : ( - {bytesToString(stats.memory)} - )} - -
-
- - {bytesToString(stats.disk)} - -
-
- ); -}; - -export default ServerDetailsBlock; diff --git a/resources/scripts/components/server/console/StatGraphs.tsx b/resources/scripts/components/server/console/StatGraphs.tsx index d748c7aa5..1f763e408 100644 --- a/resources/scripts/components/server/console/StatGraphs.tsx +++ b/resources/scripts/components/server/console/StatGraphs.tsx @@ -1,5 +1,5 @@ import { CloudDownload, CloudUpload } from '@carbon/icons-react'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Line } from 'react-chartjs-2'; import ChartBlock from '@/components/server/console/ChartBlock'; @@ -14,9 +14,12 @@ import { ServerContext } from '@/state/server'; import useWebsocketEvent from '@/plugins/useWebsocketEvent'; +import formatUptime from '../UptimeDuration'; + interface StatsData { cpu_absolute: number; memory_bytes: number; + uptime: number; network: { tx_bytes: number; rx_bytes: number; @@ -29,6 +32,7 @@ const StatGraphs = () => { const previous = useRef>({ tx: -1, rx: -1 }); const cpu = useChartTickLabel('CPU', limits.cpu, '%', 2); + const [uptime, setUptime] = useState(0); const memory = useChartTickLabel('Memory', limits.memory, 'MiB'); const network = useChart('Network', { sets: 2, @@ -58,6 +62,7 @@ const StatGraphs = () => { cpu.clear(); memory.clear(); network.clear(); + setUptime(0); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [status]); @@ -69,8 +74,10 @@ const StatGraphs = () => { } catch { return; } + setUptime(values.uptime); cpu.push(values.cpu_absolute); memory.push(Math.floor(values.memory_bytes / 1024 / 1024)); + network.push([ previous.current.tx < 0 ? 0 : Math.max(0, values.network.tx_bytes - previous.current.tx), previous.current.rx < 0 ? 0 : Math.max(0, values.network.rx_bytes - previous.current.rx), @@ -96,6 +103,12 @@ const StatGraphs = () => {
{allocation}
+
+
+

Uptime

+
{formatUptime(uptime)}
+
+

Description