refactor: improve widget styling and UX consistency

Enhance all server stats widgets with improved styling and user experience:

Widget improvements:
- Fix hardcoded titles, now use i18n translations for all widgets
- Improve data formatting with consistent translation keys
- Enhance empty state displays with better visual hierarchy
- Add smooth hover transitions and visual feedback
- Standardize spacing and layout patterns across widgets

Specific optimizations:
- CPU: Use translated load average display
- Memory: Translate "Free" label
- Disk: Translate "Available" label
- System: Improve icon colors and spacing consistency
- Network: Better empty state, enhanced card styling
- Processes: Improved card borders and spacing

Visual polish:
- Unified icon sizing and opacity for empty states
- Consistent border radius (rounded-lg)
- Better hover states with subtle transitions
- Enhanced font weights for improved readability
This commit is contained in:
ZacharyZcR
2025-10-09 13:32:19 +08:00
parent d740abd0e8
commit 1decac481e
6 changed files with 48 additions and 36 deletions

View File

@@ -34,7 +34,9 @@ export function CpuWidget({ metrics, metricsHistory }: CpuWidgetProps) {
<div className="h-full w-full p-4 rounded-lg bg-dark-bg/50 border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200 flex flex-col overflow-hidden">
<div className="flex items-center gap-2 flex-shrink-0 mb-3">
<Cpu className="h-5 w-5 text-blue-400" />
<h3 className="font-semibold text-lg text-white">CPU Usage</h3>
<h3 className="font-semibold text-lg text-white">
{t("serverStats.cpuUsage")}
</h3>
</div>
<div className="flex flex-col flex-1 min-h-0 gap-2">
@@ -52,8 +54,12 @@ export function CpuWidget({ metrics, metricsHistory }: CpuWidgetProps) {
</div>
<div className="text-xs text-gray-500 flex-shrink-0">
{metrics?.cpu?.load
? `Load: ${metrics.cpu.load[0].toFixed(2)} / ${metrics.cpu.load[1].toFixed(2)} / ${metrics.cpu.load[2].toFixed(2)}`
: "Load: N/A"}
? t("serverStats.loadAverage", {
avg1: metrics.cpu.load[0].toFixed(2),
avg5: metrics.cpu.load[1].toFixed(2),
avg15: metrics.cpu.load[2].toFixed(2),
})
: t("serverStats.loadAverageNA")}
</div>
<div className="flex-1 min-h-0">
<ResponsiveContainer width="100%" height="100%">

View File

@@ -31,7 +31,9 @@ export function DiskWidget({ metrics, metricsHistory }: DiskWidgetProps) {
<div className="h-full w-full p-4 rounded-lg bg-dark-bg/50 border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200 flex flex-col overflow-hidden">
<div className="flex items-center gap-2 flex-shrink-0 mb-3">
<HardDrive className="h-5 w-5 text-orange-400" />
<h3 className="font-semibold text-lg text-white">Disk Usage</h3>
<h3 className="font-semibold text-lg text-white">
{t("serverStats.diskUsage")}
</h3>
</div>
<div className="flex flex-col flex-1 min-h-0">
@@ -86,7 +88,9 @@ export function DiskWidget({ metrics, metricsHistory }: DiskWidgetProps) {
<div className="text-xs text-gray-500">
{(() => {
const available = metrics?.disk?.availableHuman;
return available ? `Available: ${available}` : "Available: N/A";
return available
? `${t("serverStats.available")}: ${available}`
: `${t("serverStats.available")}: N/A`;
})()}
</div>
</div>

View File

@@ -34,7 +34,9 @@ export function MemoryWidget({ metrics, metricsHistory }: MemoryWidgetProps) {
<div className="h-full w-full p-4 rounded-lg bg-dark-bg/50 border border-dark-border/50 hover:bg-dark-bg/70 transition-colors duration-200 flex flex-col overflow-hidden">
<div className="flex items-center gap-2 flex-shrink-0 mb-3">
<MemoryStick className="h-5 w-5 text-green-400" />
<h3 className="font-semibold text-lg text-white">Memory Usage</h3>
<h3 className="font-semibold text-lg text-white">
{t("serverStats.memoryUsage")}
</h3>
</div>
<div className="flex flex-col flex-1 min-h-0 gap-2">
@@ -63,7 +65,7 @@ export function MemoryWidget({ metrics, metricsHistory }: MemoryWidgetProps) {
typeof used === "number" && typeof total === "number"
? (total - used).toFixed(1)
: "N/A";
return `Free: ${free} GiB`;
return `${t("serverStats.free")}: ${free} GiB`;
})()}
</div>
<div className="flex-1 min-h-0">

View File

@@ -23,38 +23,40 @@ export function NetworkWidget({ metrics }: NetworkWidgetProps) {
</h3>
</div>
<div className="space-y-2 overflow-auto flex-1">
<div className="space-y-2.5 overflow-auto flex-1">
{interfaces.length === 0 ? (
<div className="flex flex-col items-center justify-center py-8 text-gray-400">
<WifiOff className="h-8 w-8 mb-2" />
<div className="flex flex-col items-center justify-center py-12 text-gray-400">
<WifiOff className="h-10 w-10 mb-3 opacity-50" />
<p className="text-sm">{t("serverStats.noInterfacesFound")}</p>
</div>
) : (
interfaces.map((iface: any, index: number) => (
<div
key={index}
className="p-3 rounded-md bg-dark-bg/50 border border-dark-border/30"
className="p-3 rounded-lg bg-dark-bg/50 border border-dark-border/30 hover:bg-dark-bg/60 transition-colors"
>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<Wifi
className={`h-4 w-4 ${iface.state === "UP" ? "text-green-400" : "text-gray-400"}`}
className={`h-4 w-4 ${iface.state === "UP" ? "text-green-400" : "text-gray-500"}`}
/>
<span className="text-sm font-semibold text-white font-mono">
{iface.name}
</span>
</div>
<span
className={`text-xs px-2 py-0.5 rounded-full ${
className={`text-xs px-2.5 py-0.5 rounded-full font-medium ${
iface.state === "UP"
? "bg-green-500/20 text-green-400"
: "bg-gray-500/20 text-gray-400"
: "bg-gray-500/20 text-gray-500"
}`}
>
{iface.state}
</span>
</div>
<div className="text-xs text-gray-400 font-mono">{iface.ip}</div>
<div className="text-xs text-gray-400 font-mono font-medium">
{iface.ip}
</div>
</div>
))
)}

View File

@@ -40,32 +40,30 @@ export function ProcessesWidget({ metrics }: ProcessesWidgetProps) {
<div className="overflow-auto flex-1">
{topProcesses.length === 0 ? (
<div className="flex flex-col items-center justify-center py-8 text-gray-400">
<Activity className="h-8 w-8 mb-2" />
<div className="flex flex-col items-center justify-center py-12 text-gray-400">
<Activity className="h-10 w-10 mb-3 opacity-50" />
<p className="text-sm">{t("serverStats.noProcessesFound")}</p>
</div>
) : (
<div className="space-y-1">
<div className="space-y-2">
{topProcesses.map((proc: any, index: number) => (
<div
key={index}
className="p-2 rounded-md bg-dark-bg/30 hover:bg-dark-bg/50 transition-colors"
className="p-2.5 rounded-lg bg-dark-bg/30 hover:bg-dark-bg/50 transition-colors border border-dark-border/20"
>
<div className="flex items-center justify-between mb-1">
<span className="text-xs font-mono text-gray-400">
<div className="flex items-center justify-between mb-1.5">
<span className="text-xs font-mono text-gray-400 font-medium">
PID: {proc.pid}
</span>
<div className="flex gap-3 text-xs">
<div className="flex gap-3 text-xs font-medium">
<span className="text-blue-400">CPU: {proc.cpu}%</span>
<span className="text-green-400">MEM: {proc.mem}%</span>
</div>
</div>
<div className="text-xs text-white font-mono truncate">
<div className="text-xs text-white font-mono truncate mb-1">
{proc.command}
</div>
<div className="text-xs text-gray-500 mt-1">
User: {proc.user}
</div>
<div className="text-xs text-gray-500">User: {proc.user}</div>
</div>
))}
</div>

View File

@@ -22,38 +22,38 @@ export function SystemWidget({ metrics }: SystemWidgetProps) {
</h3>
</div>
<div className="space-y-3">
<div className="space-y-4">
<div className="flex items-start gap-3">
<Info className="h-4 w-4 text-gray-400 mt-0.5 flex-shrink-0" />
<Info className="h-4 w-4 text-purple-400 mt-0.5 flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-gray-400 mb-1">
<p className="text-xs text-gray-400 mb-1.5">
{t("serverStats.hostname")}
</p>
<p className="text-sm text-white font-mono truncate">
<p className="text-sm text-white font-mono truncate font-medium">
{system?.hostname || "N/A"}
</p>
</div>
</div>
<div className="flex items-start gap-3">
<Info className="h-4 w-4 text-gray-400 mt-0.5 flex-shrink-0" />
<Info className="h-4 w-4 text-purple-400 mt-0.5 flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-gray-400 mb-1">
<p className="text-xs text-gray-400 mb-1.5">
{t("serverStats.operatingSystem")}
</p>
<p className="text-sm text-white font-mono truncate">
<p className="text-sm text-white font-mono truncate font-medium">
{system?.os || "N/A"}
</p>
</div>
</div>
<div className="flex items-start gap-3">
<Info className="h-4 w-4 text-gray-400 mt-0.5 flex-shrink-0" />
<Info className="h-4 w-4 text-purple-400 mt-0.5 flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-gray-400 mb-1">
<p className="text-xs text-gray-400 mb-1.5">
{t("serverStats.kernel")}
</p>
<p className="text-sm text-white font-mono truncate">
<p className="text-sm text-white font-mono truncate font-medium">
{system?.kernel || "N/A"}
</p>
</div>