diff --git a/AppImage/components/hardware.tsx b/AppImage/components/hardware.tsx index 8bb8e9d..4c92378 100644 --- a/AppImage/components/hardware.tsx +++ b/AppImage/components/hardware.tsx @@ -20,8 +20,14 @@ import { } from "lucide-react" import useSWR from "swr" import { useState, useEffect } from "react" -import { type HardwareData, type GPU, type PCIDevice, type StorageDevice, fetcher } from "../types/hardware" -import { API_PORT } from "@/lib/api-config" +import { + type HardwareData, + type GPU, + type PCIDevice, + type StorageDevice, + fetcher as swrFetcher, +} from "../types/hardware" +import { fetchApi } from "@/lib/api-config" const parseLsblkSize = (sizeStr: string | undefined): number => { if (!sizeStr) return 0 @@ -169,7 +175,7 @@ export default function Hardware() { data: staticHardwareData, error: staticError, isLoading: staticLoading, - } = useSWR("/api/hardware", fetcher, { + } = useSWR("/api/hardware", swrFetcher, { revalidateOnFocus: false, revalidateOnReconnect: false, refreshInterval: 0, // No auto-refresh for static data @@ -180,7 +186,7 @@ export default function Hardware() { data: dynamicHardwareData, error: dynamicError, isLoading: dynamicLoading, - } = useSWR("/api/hardware", fetcher, { + } = useSWR("/api/hardware", swrFetcher, { refreshInterval: 7000, }) @@ -231,6 +237,21 @@ export default function Hardware() { const [selectedNetwork, setSelectedNetwork] = useState(null) const [selectedUPS, setSelectedUPS] = useState(null) + const fetcher = async (url: string) => { + const data = await fetchApi(url) + return data + } + + const { + data: hardwareDataSWR, + error: swrError, + isLoading: swrLoading, + mutate, + } = useSWR("/api/hardware", fetcher, { + refreshInterval: 30000, + revalidateOnFocus: false, + }) + useEffect(() => { if (!selectedGPU) return @@ -243,30 +264,10 @@ export default function Hardware() { const fetchRealtimeData = async () => { try { - const { protocol, hostname, port } = window.location - const isStandardPort = port === "" || port === "80" || port === "443" - - const apiUrl = isStandardPort - ? `/api/gpu/${fullSlot}/realtime` - : `${protocol}//${hostname}:${API_PORT}/api/gpu/${fullSlot}/realtime` - - const response = await fetch(apiUrl, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - signal: abortController.signal, - }) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = await response.json() + const data = await fetchApi(`/api/gpu/${fullSlot}/realtime`) setRealtimeGPUData(data) setDetailsLoading(false) } catch (error) { - // Only log non-abort errors if (error instanceof Error && error.name !== "AbortError") { console.error("[v0] Error fetching GPU realtime data:", error) } @@ -275,10 +276,7 @@ export default function Hardware() { } } - // Initial fetch fetchRealtimeData() - - // Poll every 3 seconds const interval = setInterval(fetchRealtimeData, 3000) return () => { @@ -294,14 +292,14 @@ export default function Hardware() { } const findPCIDeviceForGPU = (gpu: GPU): PCIDevice | null => { - if (!hardwareData?.pci_devices || !gpu.slot) return null + if (!hardwareDataSWR?.pci_devices || !gpu.slot) return null // Try to find exact match first (e.g., "00:02.0") - let pciDevice = hardwareData.pci_devices.find((d) => d.slot === gpu.slot) + let pciDevice = hardwareDataSWR.pci_devices.find((d) => d.slot === gpu.slot) // If not found, try to match by partial slot (e.g., "00" matches "00:02.0") if (!pciDevice && gpu.slot.length <= 2) { - pciDevice = hardwareData.pci_devices.find( + pciDevice = hardwareDataSWR.pci_devices.find( (d) => d.slot.startsWith(gpu.slot + ":") && (d.type.toLowerCase().includes("vga") || @@ -320,7 +318,7 @@ export default function Hardware() { return realtimeGPUData.has_monitoring_tool === true } - if (isLoading) { + if (swrLoading) { return (
@@ -333,7 +331,7 @@ export default function Hardware() { return (
{/* System Information - CPU & Motherboard */} - {(hardwareData?.cpu || hardwareData?.motherboard) && ( + {(hardwareDataSWR?.cpu || hardwareDataSWR?.motherboard) && (
@@ -342,44 +340,44 @@ export default function Hardware() {
{/* CPU Info */} - {hardwareData?.cpu && Object.keys(hardwareData.cpu).length > 0 && ( + {hardwareDataSWR?.cpu && Object.keys(hardwareDataSWR.cpu).length > 0 && (

CPU

- {hardwareData.cpu.model && ( + {hardwareDataSWR.cpu.model && (
Model - {hardwareData.cpu.model} + {hardwareDataSWR.cpu.model}
)} - {hardwareData.cpu.cores_per_socket && hardwareData.cpu.sockets && ( + {hardwareDataSWR.cpu.cores_per_socket && hardwareDataSWR.cpu.sockets && (
Cores - {hardwareData.cpu.sockets} × {hardwareData.cpu.cores_per_socket} ={" "} - {hardwareData.cpu.sockets * hardwareData.cpu.cores_per_socket} cores + {hardwareDataSWR.cpu.sockets} × {hardwareDataSWR.cpu.cores_per_socket} ={" "} + {hardwareDataSWR.cpu.sockets * hardwareDataSWR.cpu.cores_per_socket} cores
)} - {hardwareData.cpu.total_threads && ( + {hardwareDataSWR.cpu.total_threads && (
Threads - {hardwareData.cpu.total_threads} + {hardwareDataSWR.cpu.total_threads}
)} - {hardwareData.cpu.l3_cache && ( + {hardwareDataSWR.cpu.l3_cache && (
L3 Cache - {hardwareData.cpu.l3_cache} + {hardwareDataSWR.cpu.l3_cache}
)} - {hardwareData.cpu.virtualization && ( + {hardwareDataSWR.cpu.virtualization && (
Virtualization - {hardwareData.cpu.virtualization} + {hardwareDataSWR.cpu.virtualization}
)}
@@ -387,41 +385,41 @@ export default function Hardware() { )} {/* Motherboard Info */} - {hardwareData?.motherboard && Object.keys(hardwareData.motherboard).length > 0 && ( + {hardwareDataSWR?.motherboard && Object.keys(hardwareDataSWR.motherboard).length > 0 && (

Motherboard

- {hardwareData.motherboard.manufacturer && ( + {hardwareDataSWR.motherboard.manufacturer && (
Manufacturer - {hardwareData.motherboard.manufacturer} + {hardwareDataSWR.motherboard.manufacturer}
)} - {hardwareData.motherboard.model && ( + {hardwareDataSWR.motherboard.model && (
Model - {hardwareData.motherboard.model} + {hardwareDataSWR.motherboard.model}
)} - {hardwareData.motherboard.bios?.vendor && ( + {hardwareDataSWR.motherboard.bios?.vendor && (
BIOS - {hardwareData.motherboard.bios.vendor} + {hardwareDataSWR.motherboard.bios.vendor}
)} - {hardwareData.motherboard.bios?.version && ( + {hardwareDataSWR.motherboard.bios?.version && (
Version - {hardwareData.motherboard.bios.version} + {hardwareDataSWR.motherboard.bios.version}
)} - {hardwareData.motherboard.bios?.date && ( + {hardwareDataSWR.motherboard.bios?.date && (
Date - {hardwareData.motherboard.bios.date} + {hardwareDataSWR.motherboard.bios.date}
)}
@@ -432,18 +430,18 @@ export default function Hardware() { )} {/* Memory Modules */} - {hardwareData?.memory_modules && hardwareData.memory_modules.length > 0 && ( + {hardwareDataSWR?.memory_modules && hardwareDataSWR.memory_modules.length > 0 && (

Memory Modules

- {hardwareData.memory_modules.length} installed + {hardwareDataSWR.memory_modules.length} installed
- {hardwareData.memory_modules.map((module, index) => ( + {hardwareDataSWR.memory_modules.map((module, index) => (
{module.slot}
@@ -479,29 +477,29 @@ export default function Hardware() { )} {/* Thermal Monitoring */} - {hardwareData?.temperatures && hardwareData.temperatures.length > 0 && ( + {hardwareDataSWR?.temperatures && hardwareDataSWR.temperatures.length > 0 && (

Thermal Monitoring

- {hardwareData.temperatures.length} sensors + {hardwareDataSWR.temperatures.length} sensors
{/* CPU Sensors */} - {groupAndSortTemperatures(hardwareData.temperatures).CPU.length > 0 && ( + {groupAndSortTemperatures(hardwareDataSWR.temperatures).CPU.length > 0 && (

CPU

- {groupAndSortTemperatures(hardwareData.temperatures).CPU.length} + {groupAndSortTemperatures(hardwareDataSWR.temperatures).CPU.length}
- {groupAndSortTemperatures(hardwareData.temperatures).CPU.map((temp, index) => { + {groupAndSortTemperatures(hardwareDataSWR.temperatures).CPU.map((temp, index) => { const percentage = temp.critical > 0 ? (temp.current / temp.critical) * 100 : (temp.current / 100) * 100 const isHot = temp.current > (temp.high || 80) @@ -532,21 +530,21 @@ export default function Hardware() { )} {/* GPU Sensors */} - {groupAndSortTemperatures(hardwareData.temperatures).GPU.length > 0 && ( + {groupAndSortTemperatures(hardwareDataSWR.temperatures).GPU.length > 0 && (
1 ? "md:col-span-2" : ""} + className={groupAndSortTemperatures(hardwareDataSWR.temperatures).GPU.length > 1 ? "md:col-span-2" : ""} >

GPU

- {groupAndSortTemperatures(hardwareData.temperatures).GPU.length} + {groupAndSortTemperatures(hardwareDataSWR.temperatures).GPU.length}
1 ? "md:grid-cols-2" : ""}`} + className={`grid gap-4 ${groupAndSortTemperatures(hardwareDataSWR.temperatures).GPU.length > 1 ? "md:grid-cols-2" : ""}`} > - {groupAndSortTemperatures(hardwareData.temperatures).GPU.map((temp, index) => { + {groupAndSortTemperatures(hardwareDataSWR.temperatures).GPU.map((temp, index) => { const percentage = temp.critical > 0 ? (temp.current / temp.critical) * 100 : (temp.current / 100) * 100 const isHot = temp.current > (temp.high || 80) @@ -577,21 +575,23 @@ export default function Hardware() { )} {/* NVME Sensors */} - {groupAndSortTemperatures(hardwareData.temperatures).NVME.length > 0 && ( + {groupAndSortTemperatures(hardwareDataSWR.temperatures).NVME.length > 0 && (
1 ? "md:col-span-2" : ""} + className={ + groupAndSortTemperatures(hardwareDataSWR.temperatures).NVME.length > 1 ? "md:col-span-2" : "" + } >

NVME

- {groupAndSortTemperatures(hardwareData.temperatures).NVME.length} + {groupAndSortTemperatures(hardwareDataSWR.temperatures).NVME.length}
1 ? "md:grid-cols-2" : ""}`} + className={`grid gap-4 ${groupAndSortTemperatures(hardwareDataSWR.temperatures).NVME.length > 1 ? "md:grid-cols-2" : ""}`} > - {groupAndSortTemperatures(hardwareData.temperatures).NVME.map((temp, index) => { + {groupAndSortTemperatures(hardwareDataSWR.temperatures).NVME.map((temp, index) => { const percentage = temp.critical > 0 ? (temp.current / temp.critical) * 100 : (temp.current / 100) * 100 const isHot = temp.current > (temp.high || 80) @@ -622,21 +622,21 @@ export default function Hardware() { )} {/* PCI Sensors */} - {groupAndSortTemperatures(hardwareData.temperatures).PCI.length > 0 && ( + {groupAndSortTemperatures(hardwareDataSWR.temperatures).PCI.length > 0 && (
1 ? "md:col-span-2" : ""} + className={groupAndSortTemperatures(hardwareDataSWR.temperatures).PCI.length > 1 ? "md:col-span-2" : ""} >

PCI

- {groupAndSortTemperatures(hardwareData.temperatures).PCI.length} + {groupAndSortTemperatures(hardwareDataSWR.temperatures).PCI.length}
1 ? "md:grid-cols-2" : ""}`} + className={`grid gap-4 ${groupAndSortTemperatures(hardwareDataSWR.temperatures).PCI.length > 1 ? "md:grid-cols-2" : ""}`} > - {groupAndSortTemperatures(hardwareData.temperatures).PCI.map((temp, index) => { + {groupAndSortTemperatures(hardwareDataSWR.temperatures).PCI.map((temp, index) => { const percentage = temp.critical > 0 ? (temp.current / temp.critical) * 100 : (temp.current / 100) * 100 const isHot = temp.current > (temp.high || 80) @@ -667,21 +667,23 @@ export default function Hardware() { )} {/* OTHER Sensors */} - {groupAndSortTemperatures(hardwareData.temperatures).OTHER.length > 0 && ( + {groupAndSortTemperatures(hardwareDataSWR.temperatures).OTHER.length > 0 && (
1 ? "md:col-span-2" : ""} + className={ + groupAndSortTemperatures(hardwareDataSWR.temperatures).OTHER.length > 1 ? "md:col-span-2" : "" + } >

OTHER

- {groupAndSortTemperatures(hardwareData.temperatures).OTHER.length} + {groupAndSortTemperatures(hardwareDataSWR.temperatures).OTHER.length}
1 ? "md:grid-cols-2" : ""}`} + className={`grid gap-4 ${groupAndSortTemperatures(hardwareDataSWR.temperatures).OTHER.length > 1 ? "md:grid-cols-2" : ""}`} > - {groupAndSortTemperatures(hardwareData.temperatures).OTHER.map((temp, index) => { + {groupAndSortTemperatures(hardwareDataSWR.temperatures).OTHER.map((temp, index) => { const percentage = temp.critical > 0 ? (temp.current / temp.critical) * 100 : (temp.current / 100) * 100 const isHot = temp.current > (temp.high || 80) @@ -715,18 +717,18 @@ export default function Hardware() { )} {/* GPU Information - Enhanced with on-demand data fetching */} - {hardwareData?.gpus && hardwareData.gpus.length > 0 && ( + {hardwareDataSWR?.gpus && hardwareDataSWR.gpus.length > 0 && (

Graphics Cards

- {hardwareData.gpus.length} GPU{hardwareData.gpus.length > 1 ? "s" : ""} + {hardwareDataSWR.gpus.length} GPU{hardwareDataSWR.gpus.length > 1 ? "s" : ""}
- {hardwareData.gpus.map((gpu, index) => { + {hardwareDataSWR.gpus.map((gpu, index) => { const pciDevice = findPCIDeviceForGPU(gpu) const fullSlot = pciDevice?.slot || gpu.slot @@ -1104,18 +1106,18 @@ export default function Hardware() { {/* PCI Devices - Changed to modal */} - {hardwareData?.pci_devices && hardwareData.pci_devices.length > 0 && ( + {hardwareDataSWR?.pci_devices && hardwareDataSWR.pci_devices.length > 0 && (

PCI Devices

- {hardwareData.pci_devices.length} devices + {hardwareDataSWR.pci_devices.length} devices
- {hardwareData.pci_devices.map((device, index) => ( + {hardwareDataSWR.pci_devices.map((device, index) => (
setSelectedPCIDevice(device)} @@ -1190,7 +1192,7 @@ export default function Hardware() { {/* Power Consumption */} - {hardwareData?.power_meter && ( + {hardwareDataSWR?.power_meter && (
@@ -1200,13 +1202,13 @@ export default function Hardware() {
-

{hardwareData.power_meter.name}

- {hardwareData.power_meter.adapter && ( -

{hardwareData.power_meter.adapter}

+

{hardwareDataSWR.power_meter.name}

+ {hardwareDataSWR.power_meter.adapter && ( +

{hardwareDataSWR.power_meter.adapter}

)}
-

{hardwareData.power_meter.watts.toFixed(1)} W

+

{hardwareDataSWR.power_meter.watts.toFixed(1)} W

Current Draw

@@ -1215,18 +1217,18 @@ export default function Hardware() { )} {/* Power Supplies */} - {hardwareData?.power_supplies && hardwareData.power_supplies.length > 0 && ( + {hardwareDataSWR?.power_supplies && hardwareDataSWR.power_supplies.length > 0 && (

Power Supplies

- {hardwareData.power_supplies.length} PSUs + {hardwareDataSWR.power_supplies.length} PSUs
- {hardwareData.power_supplies.map((psu, index) => ( + {hardwareDataSWR.power_supplies.map((psu, index) => (
{psu.name} @@ -1243,18 +1245,18 @@ export default function Hardware() { )} {/* Fans */} - {hardwareData?.fans && hardwareData.fans.length > 0 && ( + {hardwareDataSWR?.fans && hardwareDataSWR.fans.length > 0 && (

System Fans

- {hardwareData.fans.length} fans + {hardwareDataSWR.fans.length} fans
- {hardwareData.fans.map((fan, index) => { + {hardwareDataSWR.fans.map((fan, index) => { const isPercentage = fan.unit === "percent" || fan.unit === "%" const percentage = isPercentage ? fan.speed : Math.min((fan.speed / 5000) * 100, 100) @@ -1278,18 +1280,18 @@ export default function Hardware() { )} {/* UPS */} - {hardwareData?.ups && Array.isArray(hardwareData.ups) && hardwareData.ups.length > 0 && ( + {hardwareDataSWR?.ups && Array.isArray(hardwareDataSWR.ups) && hardwareDataSWR.ups.length > 0 && (

UPS Status

- {hardwareData.ups.length} UPS + {hardwareDataSWR.ups.length} UPS
- {hardwareData.ups.map((ups: any, index: number) => { + {hardwareDataSWR.ups.map((ups: any, index: number) => { const batteryCharge = ups.battery_charge_raw || Number.parseFloat(ups.battery_charge?.replace("%", "") || "0") const loadPercent = ups.load_percent_raw || Number.parseFloat(ups.load_percent?.replace("%", "") || "0") @@ -1560,19 +1562,19 @@ export default function Hardware() { {/* Network Summary - Clickable */} - {hardwareData?.pci_devices && - hardwareData.pci_devices.filter((d) => d.type.toLowerCase().includes("network")).length > 0 && ( + {hardwareDataSWR?.pci_devices && + hardwareDataSWR.pci_devices.filter((d) => d.type.toLowerCase().includes("network")).length > 0 && (

Network Summary

- {hardwareData.pci_devices.filter((d) => d.type.toLowerCase().includes("network")).length} interfaces + {hardwareDataSWR.pci_devices.filter((d) => d.type.toLowerCase().includes("network")).length} interfaces
- {hardwareData.pci_devices + {hardwareDataSWR.pci_devices .filter((d) => d.type.toLowerCase().includes("network")) .map((device, index) => (
{/* Storage Summary - Clickable */} - {hardwareData?.storage_devices && hardwareData.storage_devices.length > 0 && ( + {hardwareDataSWR?.storage_devices && hardwareDataSWR.storage_devices.length > 0 && (

Storage Summary

{ - hardwareData.storage_devices.filter( + hardwareDataSWR.storage_devices.filter( (device) => device.type === "disk" && !device.name.startsWith("zd") && !device.name.startsWith("loop"), ).length @@ -1669,7 +1671,7 @@ export default function Hardware() {
- {hardwareData.storage_devices + {hardwareDataSWR.storage_devices .filter( (device) => device.type === "disk" && !device.name.startsWith("zd") && !device.name.startsWith("loop"), ) diff --git a/AppImage/components/network-card.tsx b/AppImage/components/network-card.tsx index 3c00c28..06f8f95 100644 --- a/AppImage/components/network-card.tsx +++ b/AppImage/components/network-card.tsx @@ -4,6 +4,7 @@ import { Card, CardContent } from "./ui/card" import { Badge } from "./ui/badge" import { Wifi, Zap } from "lucide-react" import { useState, useEffect } from "react" +import { fetchApi } from "../lib/api-config" interface NetworkCardProps { interface_: { @@ -94,26 +95,12 @@ export function NetworkCard({ interface_, timeframe, onClick }: NetworkCardProps useEffect(() => { const fetchTrafficData = async () => { try { - const response = await fetch(`/api/network/${interface_.name}/metrics?timeframe=${timeframe}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - signal: AbortSignal.timeout(5000), - }) + const data = await fetchApi(`/api/network/${interface_.name}/metrics?timeframe=${timeframe}`) - if (!response.ok) { - throw new Error(`Failed to fetch traffic data: ${response.status}`) - } - - const data = await response.json() - - // Calculate totals from the data points if (data.data && data.data.length > 0) { const lastPoint = data.data[data.data.length - 1] const firstPoint = data.data[0] - // Calculate the difference between last and first data points const receivedGB = Math.max(0, (lastPoint.netin || 0) - (firstPoint.netin || 0)) const sentGB = Math.max(0, (lastPoint.netout || 0) - (firstPoint.netout || 0)) @@ -124,16 +111,13 @@ export function NetworkCard({ interface_, timeframe, onClick }: NetworkCardProps } } catch (error) { console.error("[v0] Failed to fetch traffic data for card:", error) - // Keep showing 0 values on error setTrafficData({ received: 0, sent: 0 }) } } - // Only fetch if interface is up and not a VM if (interface_.status.toLowerCase() === "up" && interface_.vm_type !== "vm") { fetchTrafficData() - // Refresh every 60 seconds const interval = setInterval(fetchTrafficData, 60000) return () => clearInterval(interval) } diff --git a/AppImage/components/network-metrics.tsx b/AppImage/components/network-metrics.tsx index 6b6de14..724bd80 100644 --- a/AppImage/components/network-metrics.tsx +++ b/AppImage/components/network-metrics.tsx @@ -8,6 +8,7 @@ import { Wifi, Activity, Network, Router, AlertCircle, Zap } from "lucide-react" import useSWR from "swr" import { NetworkTrafficChart } from "./network-traffic-chart" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" +import { fetchApi } from "../lib/api-config" interface NetworkData { interfaces: NetworkInterface[] @@ -128,19 +129,7 @@ const formatSpeed = (speed: number): string => { } const fetcher = async (url: string): Promise => { - const response = await fetch(url, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - signal: AbortSignal.timeout(5000), - }) - - if (!response.ok) { - throw new Error(`Flask server responded with status: ${response.status}`) - } - - return response.json() + return fetchApi(url) } export function NetworkMetrics() { diff --git a/AppImage/components/network-traffic-chart.tsx b/AppImage/components/network-traffic-chart.tsx index a093c41..7d9eac0 100644 --- a/AppImage/components/network-traffic-chart.tsx +++ b/AppImage/components/network-traffic-chart.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from "react" import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts" import { Loader2 } from "lucide-react" -import { API_PORT } from "@/lib/api-config" +import { fetchApi } from "@/lib/api-config" interface NetworkMetricsData { time: string @@ -76,24 +76,13 @@ export function NetworkTrafficChart({ setError(null) try { - const { protocol, hostname, port } = window.location - const isStandardPort = port === "" || port === "80" || port === "443" + const apiPath = interfaceName + ? `/api/network/${interfaceName}/metrics?timeframe=${timeframe}` + : `/api/node/metrics?timeframe=${timeframe}` - const baseUrl = isStandardPort ? "" : `${protocol}//${hostname}:${API_PORT}` + console.log("[v0] Fetching network metrics from:", apiPath) - const apiUrl = interfaceName - ? `${baseUrl}/api/network/${interfaceName}/metrics?timeframe=${timeframe}` - : `${baseUrl}/api/node/metrics?timeframe=${timeframe}` - - console.log("[v0] Fetching network metrics from:", apiUrl) - - const response = await fetch(apiUrl) - - if (!response.ok) { - throw new Error(`Failed to fetch network metrics: ${response.status}`) - } - - const result = await response.json() + const result = await fetchApi(apiPath) if (!result.data || !Array.isArray(result.data)) { throw new Error("Invalid data format received from server") diff --git a/AppImage/components/node-metrics-charts.tsx b/AppImage/components/node-metrics-charts.tsx index 40be442..575b973 100644 --- a/AppImage/components/node-metrics-charts.tsx +++ b/AppImage/components/node-metrics-charts.tsx @@ -6,7 +6,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ". import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts" import { Loader2, TrendingUp, MemoryStick } from "lucide-react" import { useIsMobile } from "../hooks/use-mobile" -import { API_PORT } from "@/lib/api-config" +import { fetchApi } from "@/lib/api-config" const TIMEFRAME_OPTIONS = [ { value: "hour", label: "1 Hour" }, @@ -89,27 +89,8 @@ export function NodeMetricsCharts() { setError(null) try { - const { protocol, hostname, port } = window.location - const isStandardPort = port === "" || port === "80" || port === "443" + const result = await fetchApi(`/api/node/metrics?timeframe=${timeframe}`) - const baseUrl = isStandardPort ? "" : `${protocol}//${hostname}:${API_PORT}` - - const apiUrl = `${baseUrl}/api/node/metrics?timeframe=${timeframe}` - - console.log("[v0] Fetching node metrics from:", apiUrl) - - const response = await fetch(apiUrl) - - console.log("[v0] Response status:", response.status) - console.log("[v0] Response ok:", response.ok) - - if (!response.ok) { - const errorText = await response.text() - console.log("[v0] Error response text:", errorText) - throw new Error(`Failed to fetch node metrics: ${response.status}`) - } - - const result = await response.json() console.log("[v0] Node metrics result:", result) console.log("[v0] Result keys:", Object.keys(result)) console.log("[v0] Data array length:", result.data?.length || 0) diff --git a/AppImage/components/system-logs.tsx b/AppImage/components/system-logs.tsx index 0ef6091..ecc4ce7 100644 --- a/AppImage/components/system-logs.tsx +++ b/AppImage/components/system-logs.tsx @@ -28,7 +28,7 @@ import { Terminal, } from "lucide-react" import { useState, useEffect, useMemo } from "react" -import { API_PORT } from "@/lib/api-config" +import { API_PORT, fetchApi } from "@/lib/api-config" interface Log { timestamp: string @@ -135,6 +135,10 @@ export function SystemLogs() { return `${protocol}//${hostname}:${API_PORT}${endpoint}` } } + // This part might not be strictly necessary if only running client-side, but good for SSR safety + // In a real SSR scenario, you'd need to handle API_PORT differently + const protocol = typeof window !== "undefined" ? window.location.protocol : "http:" // Defaulting to http for SSR safety + const hostname = typeof window !== "undefined" ? window.location.hostname : "localhost" // Defaulting to localhost for SSR safety return `${protocol}//${hostname}:${API_PORT}${endpoint}` } @@ -194,27 +198,15 @@ export function SystemLogs() { const [logsRes, backupsRes, eventsRes, notificationsRes] = await Promise.all([ fetchSystemLogs(), - fetch(getApiUrl("/api/backups")), - fetch(getApiUrl("/api/events?limit=50")), - fetch(getApiUrl("/api/notifications")), + fetchApi("/api/backups"), + fetchApi("/api/events?limit=50"), + fetchApi("/api/notifications"), ]) setLogs(logsRes) - - if (backupsRes.ok) { - const backupsData = await backupsRes.json() - setBackups(backupsData.backups || []) - } - - if (eventsRes.ok) { - const eventsData = await eventsRes.json() - setEvents(eventsData.events || []) - } - - if (notificationsRes.ok) { - const notificationsData = await notificationsRes.json() - setNotifications(notificationsData.notifications || []) - } + setBackups(backupsRes.backups || []) + setEvents(eventsRes.events || []) + setNotifications(notificationsRes.notifications || []) } catch (err) { console.error("[v0] Error fetching system logs data:", err) setError("Failed to connect to server") @@ -225,7 +217,7 @@ export function SystemLogs() { const fetchSystemLogs = async (): Promise => { try { - let apiUrl = getApiUrl("/api/logs") + let apiUrl = "/api/logs" const params = new URLSearchParams() // CHANGE: Always add since_days parameter (no more "now" option) @@ -258,22 +250,7 @@ export function SystemLogs() { } console.log("[v0] Making fetch request to:", apiUrl) - const response = await fetch(apiUrl, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - cache: "no-store", - signal: AbortSignal.timeout(30000), // 30 second timeout - }) - - console.log("[v0] Response status:", response.status, "OK:", response.ok) - - if (!response.ok) { - throw new Error(`Flask server responded with status: ${response.status}`) - } - - const data = await response.json() + const data = await fetchApi(apiUrl) console.log("[v0] Received logs data, count:", data.logs?.length || 0) const logsArray = Array.isArray(data) ? data : data.logs || [] @@ -364,37 +341,33 @@ export function SystemLogs() { if (upid) { // Try to fetch the complete task log from Proxmox try { - const response = await fetch(getApiUrl(`/api/task-log/${encodeURIComponent(upid)}`)) + const taskLog = await fetchApi(`/api/task-log/${encodeURIComponent(upid)}`, {}, "text") - if (response.ok) { - const taskLog = await response.text() + // Download the complete task log + const blob = new Blob( + [ + `Proxmox Task Log\n`, + `================\n\n`, + `UPID: ${upid}\n`, + `Timestamp: ${notification.timestamp}\n`, + `Service: ${notification.service}\n`, + `Source: ${notification.source}\n\n`, + `Complete Task Log:\n`, + `${"-".repeat(80)}\n`, + `${taskLog}\n`, + ], + { type: "text/plain" }, + ) - // Download the complete task log - const blob = new Blob( - [ - `Proxmox Task Log\n`, - `================\n\n`, - `UPID: ${upid}\n`, - `Timestamp: ${notification.timestamp}\n`, - `Service: ${notification.service}\n`, - `Source: ${notification.source}\n\n`, - `Complete Task Log:\n`, - `${"-".repeat(80)}\n`, - `${taskLog}\n`, - ], - { type: "text/plain" }, - ) - - const url = window.URL.createObjectURL(blob) - const a = document.createElement("a") - a.href = url - a.download = `task_log_${upid.replace(/:/g, "_")}_${notification.timestamp.replace(/[:\s]/g, "_")}.txt` - document.body.appendChild(a) - a.click() - window.URL.revokeObjectURL(url) - document.body.removeChild(a) - return - } + const url = window.URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = `task_log_${upid.replace(/:/g, "_")}_${notification.timestamp.replace(/[:\s]/g, "_")}.txt` + document.body.appendChild(a) + a.click() + window.URL.revokeObjectURL(url) + document.body.removeChild(a) + return } catch (error) { console.error("[v0] Failed to fetch task log from Proxmox:", error) // Fall through to download notification message diff --git a/AppImage/components/virtual-machines.tsx b/AppImage/components/virtual-machines.tsx index 9a0fd49..6687b2a 100644 --- a/AppImage/components/virtual-machines.tsx +++ b/AppImage/components/virtual-machines.tsx @@ -26,6 +26,7 @@ import { import useSWR from "swr" import { MetricsView } from "./metrics-dialog" import { formatStorage } from "@/lib/utils" // Import formatStorage utility +import { fetchApi } from "../lib/api-config" interface VMData { vmid: number @@ -133,20 +134,7 @@ interface VMDetails extends VMData { } const fetcher = async (url: string) => { - const response = await fetch(url, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - signal: AbortSignal.timeout(60000), - }) - - if (!response.ok) { - throw new Error(`Flask server responded with status: ${response.status}`) - } - - const data = await response.json() - return data + return fetchApi(url) } const formatBytes = (bytes: number | undefined): string => { @@ -310,19 +298,14 @@ export function VirtualMachines() { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 10000) - const response = await fetch(`/api/vms/${lxc.vmid}`, { - signal: controller.signal, - }) + const details = await fetchApi(`/api/vms/${lxc.vmid}`) clearTimeout(timeoutId) - if (response.ok) { - const details = await response.json() - if (details.lxc_ip_info?.primary_ip) { - configs[lxc.vmid] = details.lxc_ip_info.primary_ip - } else if (details.config) { - configs[lxc.vmid] = extractIPFromConfig(details.config, details.lxc_ip_info) - } + if (details.lxc_ip_info?.primary_ip) { + configs[lxc.vmid] = details.lxc_ip_info.primary_ip + } else if (details.config) { + configs[lxc.vmid] = extractIPFromConfig(details.config, details.lxc_ip_info) } } catch (error) { console.log(`[v0] Could not fetch IP for LXC ${lxc.vmid}`) @@ -350,11 +333,8 @@ export function VirtualMachines() { setEditedNotes("") setDetailsLoading(true) try { - const response = await fetch(`/api/vms/${vm.vmid}`) - if (response.ok) { - const details = await response.json() - setVMDetails(details) - } + const details = await fetchApi(`/api/vms/${vm.vmid}`) + setVMDetails(details) } catch (error) { console.error("Error fetching VM details:", error) } finally { @@ -373,23 +353,16 @@ export function VirtualMachines() { const handleVMControl = async (vmid: number, action: string) => { setControlLoading(true) try { - const response = await fetch(`/api/vms/${vmid}/control`, { + await fetchApi(`/api/vms/${vmid}/control`, { method: "POST", - headers: { - "Content-Type": "application/json", - }, body: JSON.stringify({ action }), }) - if (response.ok) { - mutate() - setSelectedVM(null) - setVMDetails(null) - } else { - console.error("Failed to control VM") - } + mutate() + setSelectedVM(null) + setVMDetails(null) } catch (error) { - console.error("Error controlling VM:", error) + console.error("Failed to control VM") } finally { setControlLoading(false) } @@ -397,36 +370,33 @@ export function VirtualMachines() { const handleDownloadLogs = async (vmid: number, vmName: string) => { try { - const response = await fetch(`/api/vms/${vmid}/logs`) - if (response.ok) { - const data = await response.json() + const data = await fetchApi(`/api/vms/${vmid}/logs`) - // Format logs as plain text - let logText = `=== Logs for ${vmName} (VMID: ${vmid}) ===\n` - logText += `Node: ${data.node}\n` - logText += `Type: ${data.type}\n` - logText += `Total lines: ${data.log_lines}\n` - logText += `Generated: ${new Date().toISOString()}\n` - logText += `\n${"=".repeat(80)}\n\n` + // Format logs as plain text + let logText = `=== Logs for ${vmName} (VMID: ${vmid}) ===\n` + logText += `Node: ${data.node}\n` + logText += `Type: ${data.type}\n` + logText += `Total lines: ${data.log_lines}\n` + logText += `Generated: ${new Date().toISOString()}\n` + logText += `\n${"=".repeat(80)}\n\n` - if (data.logs && Array.isArray(data.logs)) { - data.logs.forEach((log: any) => { - if (typeof log === "object" && log.t) { - logText += `${log.t}\n` - } else if (typeof log === "string") { - logText += `${log}\n` - } - }) - } - - const blob = new Blob([logText], { type: "text/plain" }) - const url = URL.createObjectURL(blob) - const a = document.createElement("a") - a.href = url - a.download = `${vmName}-${vmid}-logs.txt` - a.click() - URL.revokeObjectURL(url) + if (data.logs && Array.isArray(data.logs)) { + data.logs.forEach((log: any) => { + if (typeof log === "object" && log.t) { + logText += `${log.t}\n` + } else if (typeof log === "string") { + logText += `${log}\n` + } + }) } + + const blob = new Blob([logText], { type: "text/plain" }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = `${vmName}-${vmid}-logs.txt` + a.click() + URL.revokeObjectURL(url) } catch (error) { console.error("Error downloading logs:", error) } @@ -621,29 +591,21 @@ export function VirtualMachines() { setSavingNotes(true) try { - const response = await fetch(`/api/vms/${selectedVM.vmid}/config`, { + await fetchApi(`/api/vms/${selectedVM.vmid}/config`, { method: "PUT", - headers: { - "Content-Type": "application/json", - }, body: JSON.stringify({ description: editedNotes, // Send as-is, pvesh will handle encoding }), }) - if (response.ok) { - setVMDetails({ - ...vmDetails, - config: { - ...vmDetails.config, - description: editedNotes, // Store unencoded - }, - }) - setIsEditingNotes(false) - } else { - console.error("Failed to save notes") - alert("Failed to save notes. Please try again.") - } + setVMDetails({ + ...vmDetails, + config: { + ...vmDetails.config, + description: editedNotes, // Store unencoded + }, + }) + setIsEditingNotes(false) } catch (error) { console.error("Error saving notes:", error) alert("Error saving notes. Please try again.")