Skip to content

Commit 9820198

Browse files
committed
fix: update user metrics display and improve loading skeletons
1 parent 0e99d2e commit 9820198

File tree

2 files changed

+81
-79
lines changed

2 files changed

+81
-79
lines changed

dashboard/src/components/dashboard/dashboard-statistics.tsx

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import useDirDetection from '@/hooks/use-dir-detection'
33
import { cn } from '@/lib/utils'
44
import { SystemStats } from '@/service/api'
55
import { formatBytes } from '@/utils/formatByte'
6-
import { Cpu, Database, Download, HardDrive, MemoryStick, Upload, Users } from 'lucide-react'
6+
import { Cpu, Database, Download, HardDrive, MemoryStick, Upload, UserCheck, Users, Wifi } from 'lucide-react'
77
import { useTranslation } from 'react-i18next'
88
import { CircularProgress } from '../ui/circular-progress'
99
import { Card, CardContent } from '../ui/card'
@@ -15,20 +15,31 @@ const DashboardStatistics = ({ systemData }: { systemData: SystemStats | undefin
1515
// Show skeleton loader while data is being fetched
1616
if (!systemData) {
1717
return (
18-
<div className={cn('grid h-full w-full gap-3 sm:gap-4 lg:gap-6', 'grid-cols-1 sm:grid-cols-2', 'auto-rows-fr', dir === 'rtl' && 'lg:grid-flow-col-reverse')}>
18+
<div className={cn('grid h-full w-full gap-3 sm:gap-4 lg:gap-6', 'grid-cols-1 sm:grid-cols-2', dir === 'rtl' && 'lg:grid-flow-col-reverse')}>
1919
{[...Array(5)].map((_, i) => (
20-
<Card key={i} className="h-full overflow-hidden border">
20+
<Card key={i} className={cn('h-full overflow-hidden border', i === 4 && 'sm:col-span-2')}>
2121
<CardContent className="flex h-full flex-col justify-between p-4 sm:p-5 lg:p-6">
2222
<div className="mb-2 flex items-start justify-between sm:mb-3">
2323
<div className="flex items-center gap-2 sm:gap-3">
2424
<Skeleton className="h-7 w-7 rounded-lg sm:h-9 sm:w-9" />
2525
<Skeleton className="h-4 w-24 sm:h-5" />
2626
</div>
2727
</div>
28-
<div className="flex items-end justify-between gap-2">
29-
<Skeleton className="h-8 w-20 sm:h-10 sm:w-32 lg:h-12 lg:w-40" />
30-
<Skeleton className="h-6 w-16 sm:h-7 sm:w-20" />
31-
</div>
28+
{i === 4 ? (
29+
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3 sm:gap-3">
30+
{[...Array(3)].map((_, metricIndex) => (
31+
<div key={metricIndex} className="rounded-lg border bg-background/60 p-3 sm:p-4">
32+
<Skeleton className="mb-2 h-4 w-24 sm:h-5 sm:w-28" />
33+
<Skeleton className="h-8 w-20 sm:h-10 sm:w-24" />
34+
</div>
35+
))}
36+
</div>
37+
) : (
38+
<div className="flex items-end justify-between gap-2">
39+
<Skeleton className="h-8 w-20 sm:h-10 sm:w-32 lg:h-12 lg:w-40" />
40+
<Skeleton className="h-6 w-16 sm:h-7 sm:w-20" />
41+
</div>
42+
)}
3243
</CardContent>
3344
</Card>
3445
))}
@@ -91,15 +102,18 @@ const DashboardStatistics = ({ systemData }: { systemData: SystemStats | undefin
91102
const cpu = getCpuInfo()
92103
const memoryPercent = Math.min(Math.max(memory.percentage, 0), 100)
93104
const diskPercent = Math.min(Math.max(disk.percentage, 0), 100)
105+
const totalUsers = Number(systemData.total_user) || 0
106+
const activeUsers = Number(systemData.active_users) || 0
107+
const onlineUsers = Number(systemData.online_users) || 0
108+
const activeUsersPercent = totalUsers > 0 ? Math.min(Math.max((activeUsers / totalUsers) * 100, 0), 100) : 0
109+
const onlineUsersPercent = totalUsers > 0 ? Math.min(Math.max((onlineUsers / totalUsers) * 100, 0), 100) : 0
94110

95111
return (
96112
<div
97113
className={cn(
98114
'grid h-full w-full gap-3 sm:gap-4 lg:gap-6',
99115
// Responsive grid: 1 column on mobile, 2 on tablet, 4 on desktop
100116
'grid-cols-1 sm:grid-cols-2',
101-
// Ensure equal height for all cards
102-
'auto-rows-fr',
103117
dir === 'rtl' && 'lg:grid-flow-col-reverse',
104118
)}
105119
>
@@ -135,7 +149,7 @@ const DashboardStatistics = ({ systemData }: { systemData: SystemStats | undefin
135149

136150
{cpu.cores > 0 && (
137151
<div className="flex shrink-0 items-center gap-1 rounded-md bg-muted/50 px-1.5 py-1 text-xs text-muted-foreground sm:px-2 sm:text-sm">
138-
<Cpu className="h-3 w-3" />
152+
<Cpu className="h-3 w-3 text-primary" />
139153
<span className="whitespace-nowrap font-medium">
140154
{cpu.cores} {t('statistics.cores')}
141155
</span>
@@ -250,13 +264,13 @@ const DashboardStatistics = ({ systemData }: { systemData: SystemStats | undefin
250264
{/* Incoming/Outgoing Details */}
251265
<div className="flex shrink-0 items-center gap-2 text-xs">
252266
<div className="flex items-center gap-1 rounded-md bg-muted/50 px-1.5 py-1 text-green-600 dark:text-green-400">
253-
<Download className="h-3 w-3" />
267+
<Download className="h-3 w-3 text-primary" />
254268
<span dir="ltr" className="font-medium">
255269
{formatBytes(getIncomingBandwidth() || 0, 1)}
256270
</span>
257271
</div>
258272
<div className="flex items-center gap-1 rounded-md bg-muted/50 px-1.5 py-1 text-blue-600 dark:text-blue-400">
259-
<Upload className="h-3 w-3" />
273+
<Upload className="h-3 w-3 text-primary" />
260274
<span dir="ltr" className="font-medium">
261275
{formatBytes(getOutgoingBandwidth() || 0, 1)}
262276
</span>
@@ -267,8 +281,8 @@ const DashboardStatistics = ({ systemData }: { systemData: SystemStats | undefin
267281
</Card>
268282
</div>
269283

270-
{/* Online Users */}
271-
<div className="h-full w-full animate-fade-in" style={{ animationDuration: '600ms', animationDelay: '450ms' }}>
284+
{/* Users Overview */}
285+
<div className={cn('h-full w-full animate-fade-in', 'sm:col-span-2')} style={{ animationDuration: '600ms', animationDelay: '450ms' }}>
272286
<Card dir={dir} className="group relative h-full w-full overflow-hidden rounded-lg border transition-all duration-300 hover:shadow-lg">
273287
<div
274288
className={cn(
@@ -284,15 +298,60 @@ const DashboardStatistics = ({ systemData }: { systemData: SystemStats | undefin
284298
<Users className="h-4 w-4 text-primary sm:h-5 sm:w-5" />
285299
</div>
286300
<div className="min-w-0 flex-1">
287-
<p className="truncate text-xs font-medium text-muted-foreground sm:text-sm">{t('statistics.onlineUsers')}</p>
301+
<p className="truncate text-xs font-medium text-muted-foreground sm:text-sm">{t('statistics.users')}</p>
288302
</div>
289303
</div>
304+
{totalUsers > 0 && (
305+
<div dir="ltr" className="shrink-0 rounded-md bg-muted/60 px-2 py-1 text-xs font-medium text-muted-foreground sm:text-sm">
306+
{activeUsersPercent.toFixed(1)}%
307+
</div>
308+
)}
290309
</div>
291310

292-
<div className="flex items-center gap-1 sm:gap-2">
293-
<span dir="ltr" className="text-xl font-bold transition-all duration-300 sm:text-2xl lg:text-3xl">
294-
{systemData?.online_users || 0}
295-
</span>
311+
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3 sm:gap-3">
312+
<div className="rounded-lg border bg-background/60 p-3 sm:p-4">
313+
<div className="mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground sm:gap-2 sm:text-sm">
314+
<Users className="h-3.5 w-3.5 text-primary sm:h-4 sm:w-4" />
315+
<span>{t('statistics.users')}</span>
316+
</div>
317+
<span dir="ltr" className="text-xl font-bold transition-all duration-300 sm:text-2xl lg:text-3xl">
318+
{totalUsers}
319+
</span>
320+
</div>
321+
322+
<div className="rounded-lg border bg-background/60 p-3 sm:p-4">
323+
<div className="mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground sm:gap-2 sm:text-sm">
324+
<UserCheck className="h-3.5 w-3.5 text-primary sm:h-4 sm:w-4" />
325+
<span>{t('statistics.activeUsers')}</span>
326+
</div>
327+
<div className="flex items-end justify-between gap-2">
328+
<span dir="ltr" className="text-xl font-bold transition-all duration-300 sm:text-2xl lg:text-3xl">
329+
{activeUsers}
330+
</span>
331+
{totalUsers > 0 && (
332+
<span dir="ltr" className="whitespace-nowrap rounded-md bg-muted/60 px-1.5 py-1 text-xs font-medium text-muted-foreground sm:px-2">
333+
{activeUsersPercent.toFixed(1)}%
334+
</span>
335+
)}
336+
</div>
337+
</div>
338+
339+
<div className="rounded-lg border bg-background/60 p-3 sm:p-4">
340+
<div className="mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground sm:gap-2 sm:text-sm">
341+
<Wifi className="h-3.5 w-3.5 text-primary sm:h-4 sm:w-4" />
342+
<span>{t('statistics.onlineUsers')}</span>
343+
</div>
344+
<div className="flex items-end justify-between gap-2">
345+
<span dir="ltr" className="text-xl font-bold transition-all duration-300 sm:text-2xl lg:text-3xl">
346+
{onlineUsers}
347+
</span>
348+
{totalUsers > 0 && (
349+
<span dir="ltr" className="whitespace-nowrap rounded-md bg-muted/60 px-1.5 py-1 text-xs font-medium text-muted-foreground sm:px-2">
350+
{onlineUsersPercent.toFixed(1)}%
351+
</span>
352+
)}
353+
</div>
354+
</div>
296355
</div>
297356
</CardContent>
298357
</Card>

dashboard/src/components/statistics/system-statistics-section.tsx

Lines changed: 3 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Card, CardContent } from '@/components/ui/card'
22
import { SystemStats, NodeRealtimeStats } from '@/service/api'
33
import { useTranslation } from 'react-i18next'
4-
import { Cpu, MemoryStick, Database, Upload, Download, Activity, HardDrive } from 'lucide-react'
4+
import { Cpu, MemoryStick, Database, Upload, Download, Activity } from 'lucide-react'
55
import { cn } from '@/lib/utils'
66
import useDirDetection from '@/hooks/use-dir-detection'
77
import { formatBytes } from '@/utils/formatByte'
@@ -95,18 +95,6 @@ export default function SystemStatisticsSection({ currentStats }: SystemStatisti
9595
return { used: memUsed, total: memTotal, percentage }
9696
}
9797

98-
const getDiskUsage = () => {
99-
if (!currentStats || isNodeStats(currentStats)) {
100-
return { used: 0, total: 0, percentage: 0 }
101-
}
102-
103-
const diskUsed = Number(currentStats.disk_used) || 0
104-
const diskTotal = Number(currentStats.disk_total) || 0
105-
const percentage = diskTotal > 0 ? (diskUsed / diskTotal) * 100 : 0
106-
107-
return { used: diskUsed, total: diskTotal, percentage }
108-
}
109-
11098
const getCpuInfo = () => {
11199
if (!currentStats) return { usage: 0, cores: 0 }
112100

@@ -121,11 +109,8 @@ export default function SystemStatisticsSection({ currentStats }: SystemStatisti
121109
}
122110

123111
const memory = getMemoryUsage()
124-
const disk = getDiskUsage()
125112
const cpu = getCpuInfo()
126-
const shouldShowDisk = Boolean(currentStats && !isNodeStats(currentStats))
127113
const memoryPercent = Math.min(Math.max(memory.percentage, 0), 100)
128-
const diskPercent = Math.min(Math.max(disk.percentage, 0), 100)
129114
const totalSpeed = formatMbpsPair(getTotalTrafficValue() || 0)
130115
const incomingSpeed = formatMbpsPair(getIncomingBandwidth() || 0)
131116
const outgoingSpeed = formatMbpsPair(getOutgoingBandwidth() || 0)
@@ -135,8 +120,7 @@ export default function SystemStatisticsSection({ currentStats }: SystemStatisti
135120
className={cn(
136121
'grid h-full w-full gap-3 sm:gap-4 lg:gap-6',
137122
// Responsive grid: 1 column on mobile, 2 on small tablet, 3 on large tablet and desktop
138-
'grid-cols-1 sm:grid-cols-2',
139-
shouldShowDisk ? 'xl:grid-cols-4' : 'xl:grid-cols-3',
123+
'grid-cols-1 sm:grid-cols-2 xl:grid-cols-3',
140124
// Ensure equal height for all cards
141125
'auto-rows-fr',
142126
)}
@@ -223,49 +207,8 @@ export default function SystemStatisticsSection({ currentStats }: SystemStatisti
223207
</Card>
224208
</div>
225209

226-
{shouldShowDisk && (
227-
<div className="h-full w-full animate-fade-in" style={{ animationDuration: '600ms', animationDelay: '250ms' }}>
228-
<Card dir={dir} className="group relative h-full w-full overflow-hidden rounded-lg border transition-all duration-300 hover:shadow-lg">
229-
<div
230-
className={cn(
231-
'absolute inset-0 bg-gradient-to-r from-primary/10 to-transparent opacity-0 transition-opacity duration-500',
232-
'dark:from-primary/5 dark:to-transparent',
233-
'group-hover:opacity-100',
234-
)}
235-
/>
236-
<CardContent className="relative z-10 flex h-full flex-col justify-between p-4 sm:p-5 lg:p-6">
237-
<div className="mb-2 flex items-start justify-between sm:mb-3">
238-
<div className="flex items-center gap-2 sm:gap-3">
239-
<div className="rounded-lg bg-primary/10 p-1.5 sm:p-2">
240-
<HardDrive className="h-4 w-4 text-primary sm:h-5 sm:w-5" />
241-
</div>
242-
<div className="min-w-0 flex-1">
243-
<p className="text-xs font-medium leading-tight text-muted-foreground sm:truncate sm:text-sm">
244-
{t('statistics.diskUsage', { defaultValue: 'Disk Usage' })}
245-
</p>
246-
</div>
247-
</div>
248-
<CircularProgress value={diskPercent} size={38} strokeWidth={4} showValue={false} className="shrink-0 opacity-90" />
249-
</div>
250-
251-
<div className="flex items-center gap-1 sm:gap-2">
252-
<span dir="ltr" className="text-base font-bold leading-tight transition-all duration-300 sm:text-xl lg:text-2xl">
253-
<span className="whitespace-normal sm:whitespace-nowrap">
254-
{formatBytes(disk.used, 1, false, false, 'GB')}/{formatBytes(disk.total, 1, true, false, 'GB')}
255-
<span className="ml-1 text-sm font-medium text-muted-foreground">({diskPercent.toFixed(1)}%)</span>
256-
</span>
257-
</span>
258-
</div>
259-
</CardContent>
260-
</Card>
261-
</div>
262-
)}
263-
264210
{/* Total Traffic / Network Speed (depends on whether it's master or node stats) */}
265-
<div
266-
className={cn('h-full w-full animate-fade-in', !shouldShowDisk && 'sm:col-span-2 lg:col-span-1')}
267-
style={{ animationDuration: '600ms', animationDelay: shouldShowDisk ? '350ms' : '250ms' }}
268-
>
211+
<div className="h-full w-full animate-fade-in sm:col-span-2 lg:col-span-1" style={{ animationDuration: '600ms', animationDelay: '250ms' }}>
269212
<Card dir={dir} className="group relative h-full w-full overflow-hidden rounded-lg border transition-all duration-300 hover:shadow-lg">
270213
<div
271214
className={cn(

0 commit comments

Comments
 (0)