diff --git a/src/components/home/SystemStatValue.tsx b/src/components/home/SystemStatValue.tsx index 51f0a4b..caa84dc 100644 --- a/src/components/home/SystemStatValue.tsx +++ b/src/components/home/SystemStatValue.tsx @@ -1,9 +1,12 @@ +import { IconArrowDown, IconArrowUp } from '@tabler/icons-react' import { type FieldPath, Render } from 'juststore' -import { formatDuration } from '@/lib/format' +import { formatBytes, formatDuration } from '@/lib/format' import { cn } from '@/lib/utils' import { Progress } from '../ui/progress' import { type Store, store } from './store' +export type SystemStatValueType = 'text' | 'progress' | 'duration' | 'upload' | 'download' + export default function SystemStatValue({ valueKey, descriptionKey, @@ -11,7 +14,7 @@ export default function SystemStatValue({ }: { valueKey: FieldPath descriptionKey?: FieldPath - type: 'text' | 'progress' | 'duration' + type: SystemStatValueType }) { const state = store.systemInfo[valueKey] return ( @@ -35,7 +38,7 @@ function DisplayValue({ type, }: { valueKey: FieldPath - type: 'text' | 'progress' | 'duration' + type: SystemStatValueType }) { const displayValue = store.systemInfo[valueKey].useCompute(value => { if (type === 'duration') { @@ -44,10 +47,37 @@ function DisplayValue({ if (type === 'progress') { return `${value}%` } + if (type === 'upload') { + type = 'text' + return ( + <> + {' '} + {formatBytes(Number(value), { precision: 0, unit: '/s' })} + + ) + } + if (type === 'download') { + type = 'text' + return ( + <> + {' '} + {formatBytes(Number(value), { precision: 0, unit: '/s' })} + + ) + } return String(value) }) + const isTextLike = type === 'text' || type === 'upload' || type === 'download' return ( -
+
{displayValue}
) @@ -58,7 +88,7 @@ function Description({ type, }: { descriptionKey?: FieldPath - type: 'text' | 'progress' | 'duration' + type: SystemStatValueType }) { const value = store.systemInfo[descriptionKey ?? 'uptime'].use() return ( diff --git a/src/components/home/SystemStats.tsx b/src/components/home/SystemStats.tsx index 23ffe0d..b3fa800 100644 --- a/src/components/home/SystemStats.tsx +++ b/src/components/home/SystemStats.tsx @@ -1,11 +1,11 @@ import type { FieldPath } from 'juststore' -import { Clock, Cpu, HardDrive, type LucideIcon, MemoryStick } from 'lucide-react' +import { Clock, Cpu, HardDrive, type LucideIcon, MemoryStick, Wifi } from 'lucide-react' import { Suspense } from 'react' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { useIsMobile } from '@/hooks/use-mobile' import { formatDuration } from '@/lib/format' +import { cn } from '@/lib/utils' import SystemStatsProvider from './SystemStatsProvider' -import SystemStatValue from './SystemStatValue' +import SystemStatValue, { type SystemStatValueType } from './SystemStatValue' import { type Store, store } from './store' export default function SystemStats() { @@ -21,10 +21,13 @@ export default function SystemStats() { } function SystemStatsMobile() { + const stats = statsProps.filter(stat => { + return !('hideOnMobile' in stat && stat.hideOnMobile) + }) return ( - {statsProps.map(stat => ( + {stats.map(stat => ( ))} @@ -33,10 +36,16 @@ function SystemStatsMobile() { } function SystemStatsDesktop() { + const hasSecondaryDisk = Boolean(store.systemInfo.secondaryPartitionUsageDesc.use()) + return ( -
+
{statsProps.map(stat => ( - +
@@ -46,11 +55,35 @@ function SystemStatsDesktop() {
- + {stat.key === 'rootPartitionUsage' && hasSecondaryDisk ? ( +
+
+ +
+
+ +
+
+ ) : stat.key === 'networkSpeedUpload' ? ( +
+ + +
+ ) : ( + + )}
))} @@ -59,14 +92,17 @@ function SystemStatsDesktop() { } type StatProp = { + hideOnMobile?: boolean label: string mobileLabel: string icon: LucideIcon - mobileValueMode?: 'percent' | 'description' | 'duration' - type: 'text' | 'progress' | 'duration' + mobileValueMode?: 'percent' | 'description' | 'duration' | 'text' | 'networkSpeed' + type: SystemStatValueType color: string key: FieldPath descriptionKey?: FieldPath + secondaryKey?: FieldPath + secondaryDescriptionKey?: FieldPath format?: (value: number) => string } @@ -77,6 +113,12 @@ function MobileStatRow({ stat }: { stat: StatProp }) { if (stat.mobileValueMode === 'duration') { return formatDuration(Number(value), { unit: 's' }) } + if (stat.mobileValueMode === 'text') { + return String(value) + } + if (stat.mobileValueMode === 'networkSpeed') { + return null + } return `${value}%` })() @@ -91,7 +133,7 @@ function MobileStatRow({ stat }: { stat: StatProp }) { ) } -const statsProps: StatProp[] = [ +const statsProps = [ { label: 'Uptime', mobileLabel: 'Up', @@ -100,6 +142,7 @@ const statsProps: StatProp[] = [ type: 'duration', color: 'text-primary', key: 'uptime', + descriptionKey: undefined, }, { label: 'CPU Usage', @@ -109,6 +152,7 @@ const statsProps: StatProp[] = [ type: 'progress', color: 'bg-chart-1', key: 'cpuAverage', + descriptionKey: undefined, }, { label: 'Memory', @@ -129,5 +173,19 @@ const statsProps: StatProp[] = [ color: 'bg-chart-3', key: 'rootPartitionUsage', descriptionKey: 'rootPartitionUsageDesc', + secondaryKey: 'secondaryPartitionUsage', + secondaryDescriptionKey: 'secondaryPartitionUsageDesc', + }, + { + hideOnMobile: true, + label: 'Network', + mobileLabel: 'Net', + icon: Wifi, + mobileValueMode: 'networkSpeed', + type: 'text', + color: 'bg-chart-4', + key: 'networkSpeedUpload', + secondaryKey: 'networkSpeedDownload', + descriptionKey: undefined, }, -] as const +] as const satisfies StatProp[] diff --git a/src/components/home/SystemStatsProvider.tsx b/src/components/home/SystemStatsProvider.tsx index 9aacc58..368ec2b 100644 --- a/src/components/home/SystemStatsProvider.tsx +++ b/src/components/home/SystemStatsProvider.tsx @@ -1,7 +1,7 @@ import { useWebSocketApi } from '@/hooks/websocket' import type { DiskUsageStat, MemVirtualMemoryStat, StatsResponse, SystemInfo } from '@/lib/api' -import { store } from './store' import { formatBytes } from '@/lib/format' +import { store } from './store' export default function SystemStatsProvider() { useWebSocketApi({ @@ -15,8 +15,12 @@ export default function SystemStatsProvider() { cpuAverage: Math.round(data.cpu_average * 100) / 100, rootPartitionUsage: Math.round(getDiskUsage(data.disks, '/') ?? 0 * 100), rootPartitionUsageDesc: getDiskUsageDesc(data.disks, '/'), + secondaryPartitionUsage: Math.round(getSecondaryDiskUsage(data.disks, '/') ?? 0 * 100), + secondaryPartitionUsageDesc: getSecondaryDiskUsageDesc(data.disks, '/'), memoryUsage: Math.round(data.memory.used_percent * 100) / 100, memoryUsageDesc: getMemoryUsageDesc(data.memory), + networkSpeedUpload: data.network?.upload_speed ?? 0, + networkSpeedDownload: data.network?.download_speed ?? 0, }), }) @@ -44,6 +48,42 @@ function getDiskUsageDesc(disks: Record, path: string) { return `${formatBytes(disk.used, { precision: 1 })} / ${formatBytes(disk.total, { precision: 1 })}` } +function getSecondaryDisk(disks: Record, path: string) { + const allDisks = Object.values(disks) + const primaryDisk = getDisk(disks, path) + if (!primaryDisk) { + return undefined + } + return allDisks.find(d => !isSameDisk(d, primaryDisk)) +} + +function isSameDisk(disk: DiskUsageStat, otherDisk: DiskUsageStat) { + if (disk.path === otherDisk.path) { + return true + } + if ( + disk.fstype === otherDisk.fstype && + disk.total === otherDisk.total && + disk.used === otherDisk.used + ) { + return true + } + return false +} + +function getSecondaryDiskUsage(disks: Record, path: string) { + return getSecondaryDisk(disks, path)?.used_percent +} + +function getSecondaryDiskUsageDesc(disks: Record, path: string) { + const disk = getSecondaryDisk(disks, path) + if (!disk) { + return '' + } + const key = Object.entries(disks).find(([_, d]) => Object.is(d, disk))?.[0] + return `${key}: ${formatBytes(disk.used, { precision: 1 })} / ${formatBytes(disk.total, { precision: 1 })}` +} + function getMemoryUsageDesc(memory: MemVirtualMemoryStat) { return `${formatBytes(memory.used, { precision: 1 })} / ${formatBytes(memory.total, { precision: 1 })}` } diff --git a/src/components/home/store.ts b/src/components/home/store.ts index 738127c..4454721 100644 --- a/src/components/home/store.ts +++ b/src/components/home/store.ts @@ -40,8 +40,12 @@ type SystemInfoSimple = { cpuAverage: number rootPartitionUsage: number rootPartitionUsageDesc: string + secondaryPartitionUsage: number + secondaryPartitionUsageDesc: string memoryUsage: number memoryUsageDesc: string + networkSpeedUpload: number + networkSpeedDownload: number } export const store = createStore('homepage', { @@ -50,8 +54,12 @@ export const store = createStore('homepage', { cpuAverage: 0, rootPartitionUsage: 0, rootPartitionUsageDesc: '', + secondaryPartitionUsage: 0, + secondaryPartitionUsageDesc: '', memoryUsage: 0, memoryUsageDesc: '', + networkSpeedUpload: 0, + networkSpeedDownload: 0, }, homepageCategories: [], searchQuery: '',