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:
@@ -168,7 +168,16 @@ async def get_network_traffic():
|
||||
return result
|
||||
|
||||
|
||||
# ============== DISK I/O ==============
|
||||
# ============== DISK USAGE + I/O ==============
|
||||
|
||||
@router.get("/disk-usage")
|
||||
async def get_disk_usage():
|
||||
"""Get filesystem disk usage from df (public)"""
|
||||
result = system_info.get_disk_usage()
|
||||
if "error" in result:
|
||||
raise HTTPException(status_code=500, detail=result["error"])
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/diskio")
|
||||
async def get_diskio():
|
||||
|
||||
@@ -429,6 +429,42 @@ def get_all_units() -> Dict[str, Any]:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
def get_disk_usage() -> Dict[str, Any]:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["/usr/bin/df", "-P", "-x", "tmpfs", "-x", "devtmpfs", "-x", "squashfs", "-x", "overlay"],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return {"error": result.stderr}
|
||||
|
||||
filesystems = []
|
||||
for line in result.stdout.strip().split("\n")[1:]:
|
||||
parts = line.split()
|
||||
if len(parts) < 6:
|
||||
continue
|
||||
try:
|
||||
total = int(parts[1]) * 1024
|
||||
used = int(parts[2]) * 1024
|
||||
available = int(parts[3]) * 1024
|
||||
capacity = int(parts[4].rstrip("%"))
|
||||
filesystems.append({
|
||||
"filesystem": parts[0],
|
||||
"mountpoint": parts[5],
|
||||
"total": total,
|
||||
"used": used,
|
||||
"available": available,
|
||||
"capacity": capacity,
|
||||
})
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
|
||||
return {"filesystems": filesystems}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting disk usage: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
def get_journal_logs(limit: int = 20) -> Dict[str, Any]:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
|
||||
+50
-1
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user