Update AppImage

This commit is contained in:
MacRimi
2025-10-25 18:47:24 +02:00
parent 09744818dc
commit b0a7b6c7cd
3 changed files with 91 additions and 68 deletions

View File

@@ -153,7 +153,6 @@ export function NetworkMetrics() {
const [selectedInterface, setSelectedInterface] = useState<NetworkInterface | null>(null)
const [timeframe, setTimeframe] = useState<"hour" | "day" | "week" | "month" | "year">("day")
const [interfaceTimeframe, setInterfaceTimeframe] = useState<"hour" | "day" | "week" | "month" | "year">("day")
const [networkTotals, setNetworkTotals] = useState<{ received: number; sent: number }>({ received: 0, sent: 0 })
const { data: interfaceHistoricalData } = useSWR<any>(`/api/node/metrics?timeframe=${timeframe}`, fetcher, {
@@ -161,15 +160,6 @@ export function NetworkMetrics() {
revalidateOnFocus: false,
})
const { data: selectedInterfaceData, isLoading: interfaceDataLoading } = useSWR<any>(
selectedInterface ? `/api/network/${selectedInterface.name}/history?timeframe=${interfaceTimeframe}` : null,
fetcher,
{
refreshInterval: 30000,
revalidateOnFocus: false,
},
)
if (isLoading) {
return (
<div className="space-y-6">
@@ -652,22 +642,6 @@ export function NetworkMetrics() {
{selectedInterface && (
<div className="space-y-6">
{/* Timeframe Selector */}
<div className="flex justify-end">
<Select value={interfaceTimeframe} onValueChange={(value: any) => setInterfaceTimeframe(value)}>
<SelectTrigger className="w-[180px] bg-card border-border">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="hour">1 Hour</SelectItem>
<SelectItem value="day">24 Hours</SelectItem>
<SelectItem value="week">7 Days</SelectItem>
<SelectItem value="month">30 Days</SelectItem>
<SelectItem value="year">1 Year</SelectItem>
</SelectContent>
</Select>
</div>
{/* Basic Information */}
<div>
<h3 className="text-sm font-semibold text-muted-foreground mb-3">Basic Information</h3>
@@ -799,39 +773,6 @@ export function NetworkMetrics() {
</div>
)}
{/* Network Traffic Chart */}
{selectedInterfaceData && (
<div>
<h3 className="text-sm font-semibold text-muted-foreground mb-3">
Network Traffic -{" "}
{interfaceTimeframe === "hour"
? "1 Hour"
: interfaceTimeframe === "day"
? "24 Hours"
: interfaceTimeframe === "week"
? "7 Days"
: interfaceTimeframe === "month"
? "30 Days"
: "1 Year"}
</h3>
{interfaceDataLoading ? (
<div className="text-center py-8 text-muted-foreground">
<div className="text-sm">Loading historical data...</div>
</div>
) : (
<div className="rounded-lg border border-border/30 bg-background/50 p-4">
<NetworkTrafficChart
timeframe={interfaceTimeframe}
interfaceName={selectedInterface.name}
onTotalsCalculated={(totals) => {
// Optional: update totals if needed
}}
/>
</div>
)}
</div>
)}
{/* Traffic Statistics */}
<div>
<h3 className="text-sm font-semibold text-muted-foreground mb-3">Traffic since last boot</h3>

View File

@@ -13,7 +13,6 @@ interface NetworkMetricsData {
interface NetworkTrafficChartProps {
timeframe: string
interfaceName?: string // Added optional interfaceName prop for specific interface data
onTotalsCalculated?: (totals: { received: number; sent: number }) => void
}
@@ -37,7 +36,7 @@ const CustomNetworkTooltip = ({ active, payload, label }: any) => {
return null
}
export function NetworkTrafficChart({ timeframe, interfaceName, onTotalsCalculated }: NetworkTrafficChartProps) {
export function NetworkTrafficChart({ timeframe, onTotalsCalculated }: NetworkTrafficChartProps) {
const [data, setData] = useState<NetworkMetricsData[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
@@ -48,7 +47,7 @@ export function NetworkTrafficChart({ timeframe, interfaceName, onTotalsCalculat
useEffect(() => {
fetchMetrics()
}, [timeframe, interfaceName])
}, [timeframe])
const fetchMetrics = async () => {
setLoading(true)
@@ -57,15 +56,12 @@ export function NetworkTrafficChart({ timeframe, interfaceName, onTotalsCalculat
try {
const baseUrl =
typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : ""
const apiUrl = interfaceName
? `${baseUrl}/api/network/${interfaceName}/history?timeframe=${timeframe}`
: `${baseUrl}/api/node/metrics?timeframe=${timeframe}`
const apiUrl = `${baseUrl}/api/node/metrics?timeframe=${timeframe}`
const response = await fetch(apiUrl)
if (!response.ok) {
throw new Error(`Failed to fetch network metrics: ${response.status}`)
throw new Error(`Failed to fetch node metrics: ${response.status}`)
}
const result = await response.json()

View File

@@ -2653,7 +2653,7 @@ def get_detailed_gpu_info(gpu):
mem_clock = clocks['GFX_MCLK']
if 'value' in mem_clock:
detailed_info['clock_memory'] = f"{mem_clock['value']} MHz"
print(f"[v0] Memory Clock: {detailed_info['clock_memory']} MHz", flush=True)
print(f"[v0] Memory Clock: {detailed_info['clock_memory']}", flush=True)
data_retrieved = True
# Parse GPU activity (gpu_activity.GFX)
@@ -3711,6 +3711,92 @@ def api_network():
"""Get network information"""
return jsonify(get_network_info())
@app.route('/api/network/<interface_name>/metrics', methods=['GET'])
def api_network_interface_metrics(interface_name):
"""Get historical metrics (RRD data) for a specific network interface"""
try:
timeframe = request.args.get('timeframe', 'day') # hour, day, week, month, year
print(f"[v0] ===== NETWORK INTERFACE METRICS REQUEST =====")
print(f"[v0] Interface: {interface_name}")
print(f"[v0] Timeframe: {timeframe}")
# Validate timeframe
valid_timeframes = ['hour', 'day', 'week', 'month', 'year']
if timeframe not in valid_timeframes:
print(f"[v0] ERROR: Invalid timeframe: {timeframe}")
return jsonify({'error': f'Invalid timeframe. Must be one of: {", ".join(valid_timeframes)}'}), 400
# Get local node name
local_node = socket.gethostname()
print(f"[v0] Local node: {local_node}")
# Determine interface type and get appropriate RRD data
interface_type = get_interface_type(interface_name)
print(f"[v0] Interface type: {interface_type}")
rrd_data = []
if interface_type == 'vm_lxc':
# For VM/LXC interfaces, get data from the VM/LXC RRD
vmid, vm_type = extract_vmid_from_interface(interface_name)
if vmid:
print(f"[v0] Fetching RRD data for {vm_type} {vmid}...")
rrd_result = subprocess.run(['pvesh', 'get', f'/nodes/{local_node}/{vm_type}/{vmid}/rrddata',
'--timeframe', timeframe, '--output-format', 'json'],
capture_output=True, text=True, timeout=10)
if rrd_result.returncode == 0:
all_data = json.loads(rrd_result.stdout)
# Filter to only network-related fields
for point in all_data:
filtered_point = {'time': point.get('time')}
# Add network fields if they exist
for key in ['netin', 'netout', 'diskread', 'diskwrite']:
if key in point:
filtered_point[key] = point[key]
rrd_data.append(filtered_point)
print(f"[v0] RRD data points: {len(rrd_data)}")
else:
print(f"[v0] ERROR: Failed to get RRD data for VM/LXC")
print(f"[v0] stderr: {rrd_result.stderr}")
else:
# For physical/bridge interfaces, get data from node RRD
print(f"[v0] Fetching RRD data for node {local_node}...")
rrd_result = subprocess.run(['pvesh', 'get', f'/nodes/{local_node}/rrddata',
'--timeframe', timeframe, '--output-format', 'json'],
capture_output=True, text=True, timeout=10)
if rrd_result.returncode == 0:
all_data = json.loads(rrd_result.stdout)
# Filter to only network-related fields for this interface
for point in all_data:
filtered_point = {'time': point.get('time')}
# Add network fields if they exist
for key in ['netin', 'netout']:
if key in point:
filtered_point[key] = point[key]
rrd_data.append(filtered_point)
print(f"[v0] RRD data points: {len(rrd_data)}")
else:
print(f"[v0] ERROR: Failed to get RRD data for node")
print(f"[v0] stderr: {rrd_result.stderr}")
print(f"[v0] ===== NETWORK INTERFACE METRICS REQUEST SUCCESS =====")
return jsonify({
'interface': interface_name,
'type': interface_type,
'timeframe': timeframe,
'data': rrd_data
})
except Exception as e:
print(f"[v0] EXCEPTION in api_network_interface_metrics: {e}")
print(f"[v0] ===== NETWORK INTERFACE METRICS REQUEST EXCEPTION =====")
return jsonify({'error': str(e)}), 500
# ... existing code ...
@app.route('/api/vms', methods=['GET'])
def api_vms():
"""Get virtual machine information"""