mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-10-11 04:16:17 +00:00
Update AppImage
This commit is contained in:
@@ -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>
|
||||||
|
@@ -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',
|
||||||
|
Reference in New Issue
Block a user