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 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() { export function StorageOverview() {
const [storageData, setStorageData] = useState<StorageData | null>(null) const [storageData, setStorageData] = useState<StorageData | null>(null)
const [proxmoxStorage, setProxmoxStorage] = useState<ProxmoxStorageData | null>(null)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [selectedDisk, setSelectedDisk] = useState<DiskInfo | null>(null) const [selectedDisk, setSelectedDisk] = useState<DiskInfo | null>(null)
const [detailsOpen, setDetailsOpen] = useState(false) const [detailsOpen, setDetailsOpen] = useState(false)
@@ -58,10 +74,20 @@ export function StorageOverview() {
try { try {
const baseUrl = const baseUrl =
typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : "" 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] Storage data received:", data)
console.log("[v0] Proxmox storage data received:", proxmoxData)
setStorageData(data) setStorageData(data)
setProxmoxStorage(proxmoxData)
} catch (error) { } catch (error) {
console.error("Error fetching storage data:", error) console.error("Error fetching storage data:", error)
} finally { } finally {
@@ -131,6 +157,18 @@ export function StorageOverview() {
setDetailsOpen(true) 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) { if (loading) {
return ( return (
<div className="flex items-center justify-center h-64"> <div className="flex items-center justify-center h-64">
@@ -215,63 +253,78 @@ export function StorageOverview() {
</Card> </Card>
</div> </div>
{storageData.disks.some((disk) => disk.mountpoint) && ( {proxmoxStorage && proxmoxStorage.storage && proxmoxStorage.storage.length > 0 && (
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" /> <Database className="h-5 w-5" />
Mounted Partitions Proxmox Storage
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="space-y-4"> <div className="space-y-4">
{storageData.disks {proxmoxStorage.storage.map((storage) => (
.filter((disk) => disk.mountpoint) <div key={storage.name} className="border rounded-lg p-4">
.map((disk) => ( <div className="flex items-center justify-between mb-3">
<div key={disk.name} className="border rounded-lg p-4"> <div className="flex items-center gap-3">
<div className="flex items-center justify-between mb-2"> <Database className="h-5 w-5 text-muted-foreground" />
<div> <div>
<h3 className="font-semibold">{disk.mountpoint}</h3> <h3 className="font-semibold text-lg">{storage.name}</h3>
<p className="text-sm text-muted-foreground"> <Badge className={getStorageTypeBadge(storage.type)}>{storage.type}</Badge>
/dev/{disk.name} ({disk.fstype}) </div>
</div>
<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={storage.percent}
className={`h-2 ${
storage.percent > 90
? "[&>div]:bg-red-500"
: storage.percent > 75
? "[&>div]:bg-yellow-500"
: "[&>div]:bg-blue-500"
}`}
/>
<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"
: storage.percent > 75
? "text-yellow-400"
: "text-blue-400"
}`}
>
{storage.used.toLocaleString()} GB
</p> </p>
</div> </div>
{disk.usage_percent !== undefined && ( <div>
<span className="text-sm font-medium">{disk.usage_percent}%</span> <p className="text-muted-foreground">Available</p>
)} <p className="font-medium text-green-400">{storage.available.toLocaleString()} GB</p>
</div>
{disk.usage_percent !== undefined && (
<div className="space-y-1">
<Progress
value={disk.usage_percent}
className={`h-2 ${
disk.usage_percent > 90
? "[&>div]:bg-red-500"
: disk.usage_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
? "text-red-400"
: disk.usage_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>
</div>
</div> </div>
)} </div>
</div> </div>
))} </div>
))}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@@ -384,32 +437,6 @@ export function StorageOverview() {
</div> </div>
)} )}
</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>
))} ))}
</div> </div>
@@ -494,46 +521,6 @@ export function StorageOverview() {
</div> </div>
</div> </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> </div>
)} )}
</DialogContent> </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', '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', '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', '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', f'/dev/{disk_name}'], # Text output
['smartctl', '-a', '-d', 'ata', f'/dev/{disk_name}'], # Text with ATA device type ['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', 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', 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): 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} =====") print(f"[v0] ===== Final SMART data for /dev/{disk_name}: {smart_data} =====")
return 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(): def get_network_info():
"""Get network interface information""" """Get network interface information"""
try: try:
@@ -833,6 +908,11 @@ def api_storage():
"""Get storage information""" """Get storage information"""
return jsonify(get_storage_info()) 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']) @app.route('/api/network', methods=['GET'])
def api_network(): def api_network():
"""Get network information""" """Get network information"""
@@ -949,6 +1029,7 @@ def api_info():
'/api/system', '/api/system',
'/api/system-info', '/api/system-info',
'/api/storage', '/api/storage',
'/api/proxmox-storage', # Added new endpoint
'/api/network', '/api/network',
'/api/vms', '/api/vms',
'/api/logs', '/api/logs',