Update AppImage

This commit is contained in:
MacRimi
2025-10-04 17:48:10 +02:00
parent 54ff50ce68
commit 2d89d06bcb
2 changed files with 180 additions and 112 deletions

View File

@@ -48,8 +48,24 @@ interface StorageData {
error?: string
}
interface ProxmoxStorage {
name: string
type: string
status: string
total: number
used: number
available: number
percent: number
}
interface ProxmoxStorageData {
storage: ProxmoxStorage[]
error?: string
}
export function StorageOverview() {
const [storageData, setStorageData] = useState<StorageData | null>(null)
const [proxmoxStorage, setProxmoxStorage] = useState<ProxmoxStorageData | null>(null)
const [loading, setLoading] = useState(true)
const [selectedDisk, setSelectedDisk] = useState<DiskInfo | null>(null)
const [detailsOpen, setDetailsOpen] = useState(false)
@@ -58,10 +74,20 @@ export function StorageOverview() {
try {
const baseUrl =
typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : ""
const response = await fetch(`${baseUrl}/api/storage`)
const data = await response.json()
const [storageResponse, proxmoxResponse] = await Promise.all([
fetch(`${baseUrl}/api/storage`),
fetch(`${baseUrl}/api/proxmox-storage`),
])
const data = await storageResponse.json()
const proxmoxData = await proxmoxResponse.json()
console.log("[v0] Storage data received:", data)
console.log("[v0] Proxmox storage data received:", proxmoxData)
setStorageData(data)
setProxmoxStorage(proxmoxData)
} catch (error) {
console.error("Error fetching storage data:", error)
} finally {
@@ -131,6 +157,18 @@ export function StorageOverview() {
setDetailsOpen(true)
}
const getStorageTypeBadge = (type: string) => {
const typeColors: Record<string, string> = {
pbs: "bg-purple-500/10 text-purple-500 border-purple-500/20",
dir: "bg-blue-500/10 text-blue-500 border-blue-500/20",
lvmthin: "bg-cyan-500/10 text-cyan-500 border-cyan-500/20",
zfspool: "bg-green-500/10 text-green-500 border-green-500/20",
nfs: "bg-orange-500/10 text-orange-500 border-orange-500/20",
cifs: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20",
}
return typeColors[type.toLowerCase()] || "bg-gray-500/10 text-gray-500 border-gray-500/20"
}
if (loading) {
return (
<div className="flex items-center justify-center h-64">
@@ -215,61 +253,76 @@ export function StorageOverview() {
</Card>
</div>
{storageData.disks.some((disk) => disk.mountpoint) && (
{proxmoxStorage && proxmoxStorage.storage && proxmoxStorage.storage.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" />
Mounted Partitions
Proxmox Storage
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{storageData.disks
.filter((disk) => disk.mountpoint)
.map((disk) => (
<div key={disk.name} className="border rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
{proxmoxStorage.storage.map((storage) => (
<div key={storage.name} className="border rounded-lg p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-3">
<Database className="h-5 w-5 text-muted-foreground" />
<div>
<h3 className="font-semibold">{disk.mountpoint}</h3>
<p className="text-sm text-muted-foreground">
/dev/{disk.name} ({disk.fstype})
</p>
<h3 className="font-semibold text-lg">{storage.name}</h3>
<Badge className={getStorageTypeBadge(storage.type)}>{storage.type}</Badge>
</div>
{disk.usage_percent !== undefined && (
<span className="text-sm font-medium">{disk.usage_percent}%</span>
)}
</div>
{disk.usage_percent !== undefined && (
<div className="space-y-1">
<div className="flex items-center gap-2">
<Badge
className={
storage.status === "active"
? "bg-green-500/10 text-green-500 border-green-500/20"
: "bg-gray-500/10 text-gray-500 border-gray-500/20"
}
>
{storage.status}
</Badge>
<span className="text-sm font-medium">{storage.percent}%</span>
</div>
</div>
<div className="space-y-2">
<Progress
value={disk.usage_percent}
value={storage.percent}
className={`h-2 ${
disk.usage_percent > 90
storage.percent > 90
? "[&>div]:bg-red-500"
: disk.usage_percent > 75
: storage.percent > 75
? "[&>div]:bg-yellow-500"
: "[&>div]:bg-blue-500"
}`}
/>
<div className="flex justify-between text-xs text-muted-foreground">
<span
className={
disk.usage_percent > 90
<div className="grid grid-cols-3 gap-4 text-sm">
<div>
<p className="text-muted-foreground">Total</p>
<p className="font-medium">{storage.total.toLocaleString()} GB</p>
</div>
<div>
<p className="text-muted-foreground">Used</p>
<p
className={`font-medium ${
storage.percent > 90
? "text-red-400"
: disk.usage_percent > 75
: storage.percent > 75
? "text-yellow-400"
: "text-blue-400"
}
}`}
>
{disk.used} GB used
</span>
<span className="text-green-400">
{disk.available} GB free of {disk.total} GB
</span>
{storage.used.toLocaleString()} GB
</p>
</div>
<div>
<p className="text-muted-foreground">Available</p>
<p className="font-medium text-green-400">{storage.available.toLocaleString()} GB</p>
</div>
</div>
</div>
)}
</div>
))}
</div>
@@ -384,32 +437,6 @@ export function StorageOverview() {
</div>
)}
</div>
{disk.mountpoint && (
<div className="mt-3 pt-3 border-t">
<div className="flex items-center justify-between mb-2">
<div className="text-sm">
<span className="text-muted-foreground">Mounted at: </span>
<span className="font-medium">{disk.mountpoint}</span>
{disk.fstype && <span className="text-muted-foreground ml-2">({disk.fstype})</span>}
</div>
{disk.usage_percent !== undefined && (
<span className="text-sm font-medium">{disk.usage_percent}%</span>
)}
</div>
{disk.usage_percent !== undefined && (
<div className="space-y-1">
<Progress value={disk.usage_percent} className="h-2" />
<div className="flex justify-between text-xs text-muted-foreground">
<span className="text-blue-400">{disk.used} GB used</span>
<span className="text-green-400">
{disk.available} GB free of {disk.total} GB
</span>
</div>
</div>
)}
</div>
)}
</div>
))}
</div>
@@ -494,46 +521,6 @@ export function StorageOverview() {
</div>
</div>
</div>
{selectedDisk.mountpoint && (
<div className="border-t pt-4">
<h4 className="font-semibold mb-3">Mount Information</h4>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Mount Point:</span>
<span className="font-medium">{selectedDisk.mountpoint}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Filesystem:</span>
<span className="font-medium">{selectedDisk.fstype}</span>
</div>
{selectedDisk.total && (
<>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Total:</span>
<span className="font-medium">{selectedDisk.total} GB</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Used:</span>
<span className="font-medium text-blue-400">{selectedDisk.used} GB</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Available:</span>
<span className="font-medium text-green-400">{selectedDisk.available} GB</span>
</div>
{selectedDisk.usage_percent !== undefined && (
<div className="mt-2">
<Progress value={selectedDisk.usage_percent} className="h-2" />
<p className="text-xs text-muted-foreground text-center mt-1">
{selectedDisk.usage_percent}% used
</p>
</div>
)}
</>
)}
</div>
</div>
)}
</div>
)}
</DialogContent>

View File

@@ -517,10 +517,16 @@ def get_smart_data(disk_name):
['smartctl', '-a', '-j', '-d', 'ata', f'/dev/{disk_name}'], # JSON with ATA device type
['smartctl', '-a', '-j', '-d', 'sat', f'/dev/{disk_name}'], # JSON with SAT device type
['smartctl', '-a', '-j', '-d', 'scsi', f'/dev/{disk_name}'], # JSON with SCSI device type
['smartctl', '-a', '-j', '-d', 'sat,12', f'/dev/{disk_name}'], # SAT with 12-byte commands
['smartctl', '-a', '-j', '-d', 'sat,16', f'/dev/{disk_name}'], # SAT with 16-byte commands
['smartctl', '-a', f'/dev/{disk_name}'], # Text output
['smartctl', '-a', '-d', 'ata', f'/dev/{disk_name}'], # Text with ATA device type
['smartctl', '-a', '-d', 'sat', f'/dev/{disk_name}'], # Text with SAT device type
['smartctl', '-a', '-d', 'sat,12', f'/dev/{disk_name}'], # Text SAT with 12-byte commands
['smartctl', '-a', '-d', 'sat,16', f'/dev/{disk_name}'], # Text SAT with 16-byte commands
['smartctl', '-i', '-H', f'/dev/{disk_name}'], # Basic info + health only
['smartctl', '-i', '-H', '-d', 'ata', f'/dev/{disk_name}'], # Basic with ATA
['smartctl', '-i', '-H', '-d', 'sat', f'/dev/{disk_name}'], # Basic with SAT
]
for cmd_index, cmd in enumerate(commands_to_try):
@@ -746,6 +752,75 @@ def get_smart_data(disk_name):
print(f"[v0] ===== Final SMART data for /dev/{disk_name}: {smart_data} =====")
return smart_data
def get_proxmox_storage():
"""Get Proxmox storage information using pvesm status"""
try:
print("[v0] Getting Proxmox storage with pvesm status...")
result = subprocess.run(['pvesm', 'status'], capture_output=True, text=True, timeout=10)
if result.returncode != 0:
print(f"[v0] pvesm status failed with return code {result.returncode}")
print(f"[v0] stderr: {result.stderr}")
return {
'error': 'pvesm command not available or failed',
'storage': []
}
storage_list = []
lines = result.stdout.strip().split('\n')
# Skip header line
if len(lines) < 2:
print("[v0] No storage found in pvesm output")
return {'storage': []}
# Parse each storage line
for line in lines[1:]: # Skip header
parts = line.split()
if len(parts) >= 6:
name = parts[0]
storage_type = parts[1]
status = parts[2]
total = int(parts[3])
used = int(parts[4])
available = int(parts[5])
percent = float(parts[6].rstrip('%')) if len(parts) > 6 else 0.0
# Convert bytes to GB
total_gb = round(total / (1024**2), 2)
used_gb = round(used / (1024**2), 2)
available_gb = round(available / (1024**2), 2)
storage_info = {
'name': name,
'type': storage_type,
'status': status,
'total': total_gb,
'used': used_gb,
'available': available_gb,
'percent': round(percent, 2)
}
print(f"[v0] Found storage: {name} ({storage_type}) - {used_gb}/{total_gb} GB ({percent}%)")
storage_list.append(storage_info)
return {'storage': storage_list}
except FileNotFoundError:
print("[v0] pvesm command not found - Proxmox not installed or not in PATH")
return {
'error': 'pvesm command not found - Proxmox not installed',
'storage': []
}
except Exception as e:
print(f"[v0] Error getting Proxmox storage: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()
return {
'error': f'Unable to get Proxmox storage: {str(e)}',
'storage': []
}
def get_network_info():
"""Get network interface information"""
try:
@@ -833,6 +908,11 @@ def api_storage():
"""Get storage information"""
return jsonify(get_storage_info())
@app.route('/api/proxmox-storage', methods=['GET'])
def api_proxmox_storage():
"""Get Proxmox storage information"""
return jsonify(get_proxmox_storage())
@app.route('/api/network', methods=['GET'])
def api_network():
"""Get network information"""
@@ -949,6 +1029,7 @@ def api_info():
'/api/system',
'/api/system-info',
'/api/storage',
'/api/proxmox-storage', # Added new endpoint
'/api/network',
'/api/vms',
'/api/logs',