Feature: Disk Usage via df im Dashboard (LXC-kompatibel)

- get_disk_usage() in system_info.py via /usr/bin/df -P
- GET /api/system/disk-usage Endpoint
- getDiskUsage() im API-Client
- Dashboard zeigt Disk Usage Karten mit Balken + Total/Used/Free
  (sichtbar auf LXC wo /proc/diskstats keine Blockgeräte liefert)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 15:34:18 +02:00
parent 3bc57ef36b
commit c4454a675a
4 changed files with 101 additions and 2 deletions
+50 -1
View File
@@ -24,6 +24,7 @@ export default function Dashboard() {
const [networkInfo, setNetworkInfo] = useState<any>(null)
const [networkTraffic, setNetworkTraffic] = useState<any>(null)
const [diskIO, setDiskIO] = useState<any>(null)
const [diskUsage, setDiskUsage] = useState<any>(null)
// History buffers for sparklines (rolling window of 30 points, ~2.5 minutes at 5s intervals)
const cpuHistoryRef = useRef<number[]>([])
@@ -100,7 +101,7 @@ export default function Dashboard() {
const loadSystemStats = async () => {
try {
const [sysInfo, memInfo, cpuData, uptime, network, traffic, diskio] = await Promise.all([
const [sysInfo, memInfo, cpuData, uptime, network, traffic, diskio, diskusage] = await Promise.all([
api.getSystemInfo().catch(() => null),
api.getMemory().catch(() => null),
api.getCpuInfo().catch(() => null),
@@ -108,6 +109,7 @@ export default function Dashboard() {
api.getNetwork().catch(() => null),
api.getNetworkTraffic().catch(() => null),
api.getDiskIO().catch(() => null),
api.getDiskUsage().catch(() => null),
])
setSystemInfo(sysInfo)
setMemoryInfo(memInfo)
@@ -116,6 +118,7 @@ export default function Dashboard() {
setNetworkInfo(network)
setNetworkTraffic(traffic)
setDiskIO(diskio)
setDiskUsage(diskusage)
// Add to history
if (cpuData?.percent !== undefined) {
@@ -524,6 +527,52 @@ export default function Dashboard() {
</div>
)}
{/* Disk Usage (df-based, immer sichtbar wenn Daten vorhanden) */}
{diskUsage?.filesystems && diskUsage.filesystems.length > 0 && (
<div className="mb-8">
<h2 className="text-xl font-semibold mb-4">Disk Usage</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{diskUsage.filesystems.map((fs: any) => {
const pct = fs.capacity
const barColor = pct > 85 ? "bg-red-500" : pct > 70 ? "bg-yellow-500" : "bg-blue-500"
return (
<Card key={fs.mountpoint}>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground flex items-center gap-2">
<HardDrive className="w-4 h-4" />
{fs.mountpoint}
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex items-center gap-2">
<div className="flex-1 h-2 bg-muted rounded-full overflow-hidden">
<div className={`h-full ${barColor} rounded-full`} style={{ width: `${pct}%` }} />
</div>
<span className="text-xs text-muted-foreground w-9 text-right">{pct}%</span>
</div>
<div className="grid grid-cols-3 gap-2 text-sm">
<div>
<p className="text-xs text-muted-foreground">Total</p>
<p className="font-medium">{formatBytes(fs.total)}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Used</p>
<p className="font-medium">{formatBytes(fs.used)}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Free</p>
<p className="font-medium">{formatBytes(fs.available)}</p>
</div>
</div>
<p className="text-xs text-muted-foreground truncate">{fs.filesystem}</p>
</CardContent>
</Card>
)
})}
</div>
</div>
)}
{/* Disk I/O */}
{diskIO?.disks && diskIO.disks.length > 0 && (
<div className="mb-8">
+5
View File
@@ -426,6 +426,11 @@ export class ZFSManagerAPI {
return response.data
}
async getDiskUsage(): Promise<{ filesystems: { filesystem: string; mountpoint: string; total: number; used: number; available: number; capacity: number }[] }> {
const response = await this.client.get("/api/system/disk-usage")
return response.data
}
async getServices(): Promise<{ services: any[] }> {
const response = await this.client.get("/api/system/services")
return response.data