diff --git a/AppImage/README.md b/AppImage/README.md deleted file mode 100644 index b9a3f52..0000000 --- a/AppImage/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# ProxMenux Monitor - -A modern, responsive dashboard for monitoring Proxmox VE systems built with Next.js and React. - -## Features - -- **System Overview**: Real-time monitoring of CPU, memory, temperature, and active VMs/LXC containers -- **Storage Management**: Visual representation of storage distribution and disk performance metrics -- **Network Monitoring**: Network interface statistics and performance graphs -- **Virtual Machines**: Comprehensive view of VMs and LXC containers with resource usage -- **System Logs**: Real-time system log monitoring and filtering -- **Dark/Light Theme**: Toggle between themes with Proxmox-inspired design -- **Responsive Design**: Works seamlessly on desktop and mobile devices - -## Technology Stack - -- **Frontend**: Next.js 15, React 19, TypeScript -- **Styling**: Tailwind CSS with custom Proxmox-inspired theme -- **Charts**: Recharts for data visualization -- **UI Components**: Radix UI primitives with shadcn/ui -- **Backend**: Flask server for system data collection -- **Packaging**: AppImage for easy distribution - -## Development - -\`\`\`bash -# Install dependencies -npm install - -# Start development server -npm run dev - -# Build for production -npm run build -\`\`\` - -## Building AppImage - -\`\`\`bash -# Make build script executable -chmod +x scripts/build_appimage.sh - -# Build AppImage -./scripts/build_appimage.sh -\`\`\` - -## Translation Support - -The project includes a translation system for multi-language support: - -\`\`\`bash -# Build translation AppImage -chmod +x scripts/build_translate_appimage.sh -./scripts/build_translate_appimage.sh -\`\`\` - -## License - -MIT License - see LICENSE file for details. diff --git a/AppImage/app/api/flask/route.ts b/AppImage/app/api/flask/route.ts deleted file mode 100644 index a5264ec..0000000 --- a/AppImage/app/api/flask/route.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { type NextRequest, NextResponse } from "next/server" - -// This will be the bridge between Next.js and the Flask server -// For now, we'll return mock data that simulates what the Flask server would provide - -export async function GET(request: NextRequest) { - const { searchParams } = new URL(request.url) - const endpoint = searchParams.get("endpoint") - - // Mock data that would come from the Flask server running on port 8008 - const mockData = { - system: { - cpu_usage: 67.3, - memory_usage: 49.4, - temperature: 52, - uptime: "15d 7h 23m", - load_average: [1.23, 1.45, 1.67], - }, - storage: { - total: 2000, - used: 1250, - available: 750, - disks: [ - { name: "/dev/sda", type: "HDD", size: 1000, used: 650, health: "healthy", temp: 42 }, - { name: "/dev/sdb", type: "HDD", size: 1000, used: 480, health: "healthy", temp: 38 }, - { name: "/dev/sdc", type: "SSD", size: 500, used: 120, health: "healthy", temp: 35 }, - { name: "/dev/nvme0n1", type: "NVMe", size: 1000, used: 340, health: "warning", temp: 55 }, - ], - }, - network: { - interfaces: [ - { name: "vmbr0", type: "Bridge", status: "up", ip: "192.168.1.100/24", speed: "1000 Mbps" }, - { name: "enp1s0", type: "Physical", status: "up", ip: "192.168.1.101/24", speed: "1000 Mbps" }, - ], - traffic: { - incoming: 89, - outgoing: 67, - }, - }, - vms: [ - { - id: 100, - name: "web-server-01", - status: "running", - os: "Ubuntu 22.04", - cpu: 4, - memory: 8192, - disk: 50, - uptime: "15d 7h 23m", - cpu_usage: 45, - memory_usage: 62, - disk_usage: 78, - }, - ], - } - - try { - // In the real implementation, this would make a request to the Flask server - // const response = await fetch(`http://localhost:8008/api/${endpoint}`) - // const data = await response.json() - - // For now, return mock data based on the endpoint - switch (endpoint) { - case "system": - return NextResponse.json(mockData.system) - case "storage": - return NextResponse.json(mockData.storage) - case "network": - return NextResponse.json(mockData.network) - case "vms": - return NextResponse.json(mockData.vms) - default: - return NextResponse.json(mockData) - } - } catch (error) { - return NextResponse.json({ error: "Failed to fetch data from Flask server" }, { status: 500 }) - } -} diff --git a/AppImage/app/globals.css b/AppImage/app/globals.css index 2cfb3a7..f08e712 100644 --- a/AppImage/app/globals.css +++ b/AppImage/app/globals.css @@ -4,7 +4,6 @@ @custom-variant dark (&:is(.dark *)); :root { - /* Proxmox light theme colors */ --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); --card: oklch(1 0 0); @@ -32,10 +31,11 @@ --radius: 0.625rem; --sidebar: oklch(0.985 0 0); --sidebar-foreground: oklch(0.145 0 0); + --header-bg: oklch(1 0 0); + --header-foreground: oklch(0.145 0 0); } .dark { - /* Proxmox dark theme with proper gray background (#2b2f36) */ --background: oklch(0.205 0.005 240); /* Proxmox dark gray #2b2f36 */ --foreground: oklch(0.985 0 0); --card: oklch(0.235 0.005 240); /* Slightly lighter gray for cards #363c45 */ @@ -55,12 +55,15 @@ --border: oklch(0.335 0.005 240); /* More visible borders */ --input: oklch(0.285 0.005 240); --ring: oklch(0.439 0 0); - /* Updated chart colors to be more vibrant and visible in dark mode */ --chart-1: oklch(0.65 0.2 220); /* Bright Blue */ --chart-2: oklch(0.65 0.2 140); /* Bright Green */ --chart-3: oklch(0.7 0.2 50); /* Bright Yellow */ --chart-4: oklch(0.65 0.2 300); /* Bright Purple */ --chart-5: oklch(0.65 0.2 20); /* Bright Orange */ + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); --sidebar: oklch(0.205 0 0); --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.488 0.243 264.376); @@ -69,12 +72,13 @@ --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(0.285 0.005 240); --sidebar-ring: oklch(0.439 0 0); - /* Header is black only in dark mode */ --header-bg: oklch(0 0 0); --header-foreground: oklch(1 0 0); } @theme inline { + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); @@ -111,9 +115,6 @@ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); - /* Custom header colors */ - --color-header-bg: var(--header-bg); - --color-header-foreground: var(--header-foreground); } @layer base { @@ -123,80 +124,78 @@ body { @apply bg-background text-foreground; } -} + .header-bg { + background-color: var(--header-bg); + color: var(--header-foreground); + backdrop-filter: none; /* Remove any blur effects */ + } + /* Custom scrollbar with better contrast */ + ::-webkit-scrollbar { + width: 6px; + } -/* Header styling that adapts to theme */ -.header-bg { - background-color: var(--header-bg); - color: var(--header-foreground); -} + ::-webkit-scrollbar-track { + background: var(--background); + } -/* Custom scrollbar with better contrast */ -::-webkit-scrollbar { - width: 6px; -} + ::-webkit-scrollbar-thumb { + background: var(--muted); + border-radius: 3px; + } -::-webkit-scrollbar-track { - background: var(--background); -} + ::-webkit-scrollbar-thumb:hover { + background: var(--muted-foreground); + } -::-webkit-scrollbar-thumb { - background: var(--muted); - border-radius: 3px; -} + /* Better contrast for dark mode content */ + .dark .metric-card { + background: var(--card); + border: 1px solid var(--border); + } -::-webkit-scrollbar-thumb:hover { - background: var(--muted-foreground); -} + .dark .metric-value { + color: var(--foreground); + font-weight: 600; + } -/* Better contrast for dark mode content */ -.dark .metric-card { - background: var(--card); - border: 1px solid var(--border); -} + .dark .metric-label { + color: var(--muted-foreground); + } -.dark .metric-value { - color: var(--foreground); - font-weight: 600; -} + /* Fix chart axis visibility in dark mode */ + .dark .recharts-cartesian-axis-tick-value { + fill: var(--muted-foreground) !important; + } -.dark .metric-label { - color: var(--muted-foreground); -} + .dark .recharts-text { + fill: var(--muted-foreground) !important; + } -/* Fix chart axis visibility in dark mode */ -.dark .recharts-cartesian-axis-tick-value { - fill: var(--muted-foreground) !important; -} + /* Improve server info layout in header - clean design without transparency */ + .server-info { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.25rem 0.75rem; + border-radius: 0.375rem; + border: 1px solid rgba(255, 255, 255, 0.2); + } -.dark .recharts-text { - fill: var(--muted-foreground) !important; -} + .dark .server-info { + border: 1px solid rgba(255, 255, 255, 0.1); + } -/* Improve server info layout in header - clean design without transparency */ -.server-info { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.25rem 0.75rem; - border-radius: 0.375rem; - border: 1px solid rgba(255, 255, 255, 0.2); -} + /* Better spacing for VM/LXC badges */ + .vm-badges { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + align-items: center; + } -.dark .server-info { - border: 1px solid rgba(255, 255, 255, 0.1); -} - -/* Better spacing for VM/LXC badges */ -.vm-badges { - display: flex; - flex-wrap: wrap; - gap: 0.25rem; - align-items: center; -} - -.vm-badge { - font-size: 0.75rem; - padding: 0.125rem 0.5rem; - white-space: nowrap; + .vm-badge { + font-size: 0.75rem; + padding: 0.125rem 0.5rem; + white-space: nowrap; + } } diff --git a/AppImage/app/layout.tsx b/AppImage/app/layout.tsx index 27a5ad7..f4009cc 100644 --- a/AppImage/app/layout.tsx +++ b/AppImage/app/layout.tsx @@ -9,21 +9,8 @@ import "./globals.css" export const metadata: Metadata = { title: "ProxMenux Monitor", - description: "Proxmox System Dashboard and Monitor", + description: "Proxmox System Dashboard", generator: "v0.app", - manifest: "/manifest.json", - icons: { - icon: [ - { url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" }, - { url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" }, - ], - apple: [{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" }], - }, - viewport: "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no", - themeColor: [ - { media: "(prefers-color-scheme: light)", color: "#ffffff" }, - { media: "(prefers-color-scheme: dark)", color: "#1a1a1a" }, - ], } export default function RootLayout({ @@ -33,13 +20,13 @@ export default function RootLayout({ }>) { return ( - - Loading...}> + + {children} - + ) diff --git a/AppImage/app/page.tsx b/AppImage/app/page.tsx index 47629db..c6d59e1 100644 --- a/AppImage/app/page.tsx +++ b/AppImage/app/page.tsx @@ -1,6 +1,6 @@ -import { ProxmoxDashboard } from "../components/proxmox-dashboard" +import { ProxmoxDashboard } from "../AppImage/components/proxmox-dashboard" -export default function Home() { +export default function Page() { return (
diff --git a/AppImage/components.json b/AppImage/components.json new file mode 100644 index 0000000..4ee62ee --- /dev/null +++ b/AppImage/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/AppImage/components/network-metrics.tsx b/AppImage/components/network-metrics.tsx deleted file mode 100644 index 8d3a714..0000000 --- a/AppImage/components/network-metrics.tsx +++ /dev/null @@ -1,267 +0,0 @@ -"use client" - -import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" -import { Badge } from "./ui/badge" -import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area } from "recharts" -import { Wifi, Globe, Shield, Activity, Network, Router } from "lucide-react" - -const networkTraffic = [ - { time: "00:00", incoming: 45, outgoing: 32 }, - { time: "04:00", incoming: 52, outgoing: 28 }, - { time: "08:00", incoming: 78, outgoing: 65 }, - { time: "12:00", incoming: 65, outgoing: 45 }, - { time: "16:00", incoming: 82, outgoing: 58 }, - { time: "20:00", incoming: 58, outgoing: 42 }, - { time: "24:00", incoming: 43, outgoing: 35 }, -] - -const connectionData = [ - { time: "00:00", connections: 1250 }, - { time: "04:00", connections: 980 }, - { time: "08:00", connections: 1850 }, - { time: "12:00", connections: 1650 }, - { time: "16:00", connections: 2100 }, - { time: "20:00", connections: 1580 }, - { time: "24:00", connections: 1320 }, -] - -export function NetworkMetrics() { - return ( -
- {/* Network Overview Cards */} -
- - - Network Traffic - - - -
156 MB/s
-
- ↓ 89 MB/s - ↑ 67 MB/s -
-

Peak: 245 MB/s at 16:30

-
-
- - - - Active Connections - - - -
1,847
-
- - Normal - -
-

- ↑ 12% from last hour -

-
-
- - - - Firewall Status - - - -
Active
-
- - Protected - -
-

247 blocked attempts today

-
-
- - - - - - Latency - - - -
12ms
-
- - Excellent - -
-

Avg response time

-
-
-
- - {/* Network Charts */} -
- - - - - Network Traffic (24h) - - - - - - - - - [`${value} MB/s`, name === "incoming" ? "Incoming" : "Outgoing"]} - /> - - - - - - - - - - - - Active Connections (24h) - - - - - - - - - [`${value}`, "Connections"]} - /> - - - - - -
- - {/* Network Interfaces */} - - - - - Network Interfaces - - - -
- {[ - { - name: "vmbr0", - type: "Bridge", - status: "up", - ip: "192.168.1.100/24", - speed: "1000 Mbps", - rx: "2.3 GB", - tx: "1.8 GB", - }, - { - name: "enp1s0", - type: "Physical", - status: "up", - ip: "192.168.1.101/24", - speed: "1000 Mbps", - rx: "1.2 GB", - tx: "890 MB", - }, - { - name: "vmbr1", - type: "Bridge", - status: "up", - ip: "10.0.0.1/24", - speed: "1000 Mbps", - rx: "456 MB", - tx: "234 MB", - }, - { - name: "tap101i0", - type: "TAP", - status: "up", - ip: "10.0.0.101/24", - speed: "1000 Mbps", - rx: "123 MB", - tx: "89 MB", - }, - ].map((interface_, index) => ( -
-
- -
-
{interface_.name}
-
- {interface_.type} • {interface_.speed} -
-
-
- -
-
-
IP Address
-
{interface_.ip}
-
- -
-
RX / TX
-
- {interface_.rx} / {interface_.tx} -
-
- - - {interface_.status.toUpperCase()} - -
-
- ))} -
-
-
-
- ) -} diff --git a/AppImage/components/proxmox-dashboard.tsx b/AppImage/components/proxmox-dashboard.tsx deleted file mode 100644 index 626a28a..0000000 --- a/AppImage/components/proxmox-dashboard.tsx +++ /dev/null @@ -1,246 +0,0 @@ -"use client" - -import { useState, useEffect } from "react" -import { Badge } from "./ui/badge" -import { Button } from "./ui/button" -import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs" -import { SystemOverview } from "./system-overview" -import { StorageMetrics } from "./storage-metrics" -import { NetworkMetrics } from "./network-metrics" -import { VirtualMachines } from "./virtual-machines" -import { SystemLogs } from "./system-logs" -import { RefreshCw, AlertTriangle, CheckCircle, XCircle, Languages, Server } from "lucide-react" -import Image from "next/image" -import { ThemeToggle } from "./theme-toggle" - -interface SystemStatus { - status: "healthy" | "warning" | "critical" - uptime: string - lastUpdate: string - serverName: string - nodeId: string -} - -export function ProxmoxDashboard() { - const [systemStatus, setSystemStatus] = useState({ - status: "healthy", - uptime: "15d 7h 23m", - lastUpdate: new Date().toLocaleTimeString(), - serverName: "proxmox-01", - nodeId: "pve-node-01", - }) - const [isRefreshing, setIsRefreshing] = useState(false) - const [isTranslating, setIsTranslating] = useState(false) - - useEffect(() => { - const fetchServerInfo = async () => { - try { - const response = await fetch("/api/flask/system-info") - if (response.ok) { - const data = await response.json() - setSystemStatus((prev) => ({ - ...prev, - serverName: data.hostname || "proxmox-01", - nodeId: data.node_id || "pve-node-01", - })) - } - } catch (error) { - console.log("[v0] Using default server name due to API error:", error) - } - } - - fetchServerInfo() - }, []) - - const refreshData = async () => { - setIsRefreshing(true) - await new Promise((resolve) => setTimeout(resolve, 1000)) - setSystemStatus((prev) => ({ - ...prev, - lastUpdate: new Date().toLocaleTimeString(), - })) - setIsRefreshing(false) - } - - const translatePage = async () => { - setIsTranslating(true) - try { - if ("translate" in document.documentElement.dataset) { - const currentLang = document.documentElement.dataset.translate - document.documentElement.dataset.translate = currentLang === "yes" ? "no" : "yes" - } else { - const googleTranslateScript = document.createElement("script") - googleTranslateScript.src = "https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit" - document.head.appendChild(googleTranslateScript) - - window.googleTranslateElementInit = () => { - new window.google.translate.TranslateElement( - { - pageLanguage: "en", - includedLanguages: "es,en,fr,de,it,pt,ru,zh,ja,ko", - layout: window.google.translate.TranslateElement.InlineLayout.SIMPLE, - }, - "google_translate_element", - ) - } - } - } catch (error) { - console.error("Translation error:", error) - } - setIsTranslating(false) - } - - const getStatusIcon = () => { - switch (systemStatus.status) { - case "healthy": - return - case "warning": - return - case "critical": - return - } - } - - const getStatusColor = () => { - switch (systemStatus.status) { - case "healthy": - return "bg-green-500/10 text-green-500 border-green-500/20" - case "warning": - return "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" - case "critical": - return "bg-red-500/10 text-red-500 border-red-500/20" - } - } - - return ( -
-
-
-
-
-
-
- ProxMenux Logo -
-
-

ProxMenux Monitor

-

Proxmox System Dashboard

-
-
-
-
- -
-
{systemStatus.nodeId}
-
-
-
-
- -
- - {getStatusIcon()} - {systemStatus.status} - - -
Uptime: {systemStatus.uptime}
- - - - - - -
-
-
-
-
- -
- - - - Overview - - - Storage - - - Network - - - Virtual Machines - - - System Logs - - - - - - - - - - - - - - - - - - - - - - - - -
-

Last updated: {systemStatus.lastUpdate} • ProxMenux Monitor v1.0.0

-
-
-
- ) -} diff --git a/AppImage/components/storage-metrics.tsx b/AppImage/components/storage-metrics.tsx deleted file mode 100644 index b22a03f..0000000 --- a/AppImage/components/storage-metrics.tsx +++ /dev/null @@ -1,243 +0,0 @@ -"use client" - -import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" -import { Progress } from "./ui/progress" -import { Badge } from "./ui/badge" -import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip } from "recharts" -import { HardDrive, Database, Archive, AlertTriangle, CheckCircle, Activity } from "lucide-react" - -const storageData = [ - { name: "Used", value: 1250, color: "#3b82f6" }, // Blue - { name: "Available", value: 750, color: "#10b981" }, // Green -] - -const diskPerformance = [ - { disk: "sda", read: 45, write: 32, iops: 1250 }, - { disk: "sdb", read: 67, write: 28, iops: 980 }, - { disk: "sdc", read: 23, write: 45, iops: 1100 }, - { disk: "nvme0n1", read: 156, write: 89, iops: 3400 }, -] - -export function StorageMetrics() { - return ( -
- {/* Storage Overview Cards */} -
- - - Total Storage - - - -
2.0 TB
- -

1.25 TB used • 750 GB available

-
-
- - - - VM & LXC Storage - - - -
890 GB
- -

71.2% of allocated space

-
-
- - - - - - Backups - - - -
245 GB
-
- - 12 Backups - -
-

Last backup: 2h ago

-
-
- - - - - - IOPS - - - -
6.7K
-
- Read: 4.2K - Write: 2.5K -
-

Average operations/sec

-
-
-
- - {/* Storage Distribution and Performance */} -
- - - - - Storage Distribution - - - -
-
-
- Used Storage - 1.25 TB (62.5%) -
-
-
-
-
- -
-
- Available Storage - 750 GB (37.5%) -
-
-
-
-
- -
-
-
-
-
- Used -
-
1.25 TB
-
-
-
-
- Available -
-
750 GB
-
-
-
-
-
-
- - - - - - Disk Performance - - - - - - - - - - - - - - - -
- - {/* Disk Details */} - - - - - Storage Devices - - - -
- {[ - { name: "/dev/sda", type: "HDD", size: "1TB", used: "650GB", health: "healthy", temp: "42°C" }, - { name: "/dev/sdb", type: "HDD", size: "1TB", used: "480GB", health: "healthy", temp: "38°C" }, - { name: "/dev/sdc", type: "SSD", size: "500GB", used: "120GB", health: "healthy", temp: "35°C" }, - { name: "/dev/nvme0n1", type: "NVMe", size: "1TB", used: "340GB", health: "warning", temp: "55°C" }, - ].map((disk, index) => ( -
-
- -
-
{disk.name}
-
- {disk.type} • {disk.size} -
-
-
- -
-
-
- {disk.used} / {disk.size} -
- -
- -
-
Temp
-
{disk.temp}
-
- - - {disk.health === "healthy" ? ( - - ) : ( - - )} - {disk.health} - -
-
- ))} -
-
-
-
- ) -} diff --git a/AppImage/components/system-logs.tsx b/AppImage/components/system-logs.tsx deleted file mode 100644 index 5ef052b..0000000 --- a/AppImage/components/system-logs.tsx +++ /dev/null @@ -1,275 +0,0 @@ -"use client" - -import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" -import { Badge } from "./ui/badge" -import { Button } from "./ui/button" -import { Input } from "./ui/input" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" -import { ScrollArea } from "./ui/scroll-area" -import { FileText, Search, Download, AlertTriangle, Info, CheckCircle, XCircle } from "lucide-react" -import { useState } from "react" - -const systemLogs = [ - { - timestamp: "2024-01-15 14:32:15", - level: "info", - service: "pveproxy", - message: "User root@pam authenticated successfully", - source: "auth.log", - }, - { - timestamp: "2024-01-15 14:31:45", - level: "warning", - service: "pvedaemon", - message: "VM 101 high memory usage detected (85%)", - source: "syslog", - }, - { - timestamp: "2024-01-15 14:30:22", - level: "error", - service: "pve-cluster", - message: "Failed to connect to cluster node pve-02", - source: "cluster.log", - }, - { - timestamp: "2024-01-15 14:29:18", - level: "info", - service: "pvestatd", - message: "Storage local: 1.25TB used, 750GB available", - source: "syslog", - }, - { - timestamp: "2024-01-15 14:28:33", - level: "info", - service: "pve-firewall", - message: "Blocked connection attempt from 192.168.1.50", - source: "firewall.log", - }, - { - timestamp: "2024-01-15 14:27:45", - level: "warning", - service: "smartd", - message: "SMART warning: /dev/nvme0n1 temperature high (55°C)", - source: "smart.log", - }, - { - timestamp: "2024-01-15 14:26:12", - level: "info", - service: "pveproxy", - message: "Started backup job for VM 100", - source: "backup.log", - }, - { - timestamp: "2024-01-15 14:25:38", - level: "error", - service: "qemu-server", - message: "VM 102 failed to start: insufficient memory", - source: "qemu.log", - }, - { - timestamp: "2024-01-15 14:24:55", - level: "info", - service: "pvedaemon", - message: "VM 103 migrated successfully to node pve-01", - source: "migration.log", - }, - { - timestamp: "2024-01-15 14:23:17", - level: "warning", - service: "pve-ha-lrm", - message: "Resource VM:104 state changed to error", - source: "ha.log", - }, -] - -export function SystemLogs() { - const [searchTerm, setSearchTerm] = useState("") - const [levelFilter, setLevelFilter] = useState("all") - const [serviceFilter, setServiceFilter] = useState("all") - - const filteredLogs = systemLogs.filter((log) => { - const matchesSearch = - log.message.toLowerCase().includes(searchTerm.toLowerCase()) || - log.service.toLowerCase().includes(searchTerm.toLowerCase()) - const matchesLevel = levelFilter === "all" || log.level === levelFilter - const matchesService = serviceFilter === "all" || log.service === serviceFilter - - return matchesSearch && matchesLevel && matchesService - }) - - const getLevelColor = (level: string) => { - switch (level) { - case "error": - return "bg-red-500/10 text-red-500 border-red-500/20" - case "warning": - return "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" - case "info": - return "bg-blue-500/10 text-blue-500 border-blue-500/20" - default: - return "bg-gray-500/10 text-gray-500 border-gray-500/20" - } - } - - const getLevelIcon = (level: string) => { - switch (level) { - case "error": - return - case "warning": - return - case "info": - return - default: - return - } - } - - const logCounts = { - total: systemLogs.length, - error: systemLogs.filter((log) => log.level === "error").length, - warning: systemLogs.filter((log) => log.level === "warning").length, - info: systemLogs.filter((log) => log.level === "info").length, - } - - const uniqueServices = [...new Set(systemLogs.map((log) => log.service))] - - return ( -
- {/* Log Statistics */} -
- - - Total Logs - - - -
{logCounts.total}
-

Last 24 hours

-
-
- - - - Errors - - - -
{logCounts.error}
-

Requires attention

-
-
- - - - Warnings - - - -
{logCounts.warning}
-

Monitor closely

-
-
- - - - Info - - - -
{logCounts.info}
-

Normal operations

-
-
-
- - {/* Log Filters and Search */} - - - - - System Logs - - - -
-
-
- - setSearchTerm(e.target.value)} - className="pl-10 bg-background border-border" - /> -
-
- - - - - - -
- - -
- {filteredLogs.map((log, index) => ( -
-
- - {getLevelIcon(log.level)} - {log.level.toUpperCase()} - -
- -
-
-
{log.service}
-
{log.timestamp}
-
-
{log.message}
-
Source: {log.source}
-
-
- ))} - - {filteredLogs.length === 0 && ( -
- -

No logs found matching your criteria

-
- )} -
-
-
-
-
- ) -} diff --git a/AppImage/components/system-overview.tsx b/AppImage/components/system-overview.tsx deleted file mode 100644 index 5c9b319..0000000 --- a/AppImage/components/system-overview.tsx +++ /dev/null @@ -1,249 +0,0 @@ -"use client" - -import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" -import { Progress } from "./ui/progress" -import { Badge } from "./ui/badge" -import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area } from "recharts" -import { Cpu, MemoryStick, Thermometer, Users, Activity, Server, Zap } from "lucide-react" - -const cpuData = [ - { time: "00:00", value: 45 }, - { time: "04:00", value: 52 }, - { time: "08:00", value: 78 }, - { time: "12:00", value: 65 }, - { time: "16:00", value: 82 }, - { time: "20:00", value: 58 }, - { time: "24:00", value: 43 }, -] - -const memoryData = [ - { time: "00:00", used: 12.5, available: 19.5 }, - { time: "04:00", used: 14.2, available: 17.8 }, - { time: "08:00", used: 18.7, available: 13.3 }, - { time: "12:00", used: 16.3, available: 15.7 }, - { time: "16:00", used: 21.1, available: 10.9 }, - { time: "20:00", used: 15.8, available: 16.2 }, - { time: "24:00", used: 13.2, available: 18.8 }, -] - -export function SystemOverview() { - return ( -
- {/* Key Metrics Cards */} -
- - - CPU Usage - - - -
67.3%
- -

- ↓ 2.1% from last hour -

-
-
- - - - Memory Usage - - - -
15.8 GB
- -

- 49.4% of 32 GB • ↑ 1.2 GB -

-
-
- - - - Temperature - - - -
52°C
-
- - Normal - -
-

Max: 78°C • Avg: 48°C

-
-
- - - - Active VMs & LXC - - - -
15
-
- - 8 Running VMs - - - 3 Running LXC - - - 4 Stopped - -
-

Total: 12 VMs • 6 LXC configured

-
-
-
- - {/* Charts Section */} -
- - - - - CPU Usage (24h) - - - - - - - - - - - - - - - - - - - - Memory Usage (24h) - - - - - - - - - - - - - - - -
- - {/* System Information */} -
- - - - - System Information - - - -
- Hostname: - proxmox-01 -
-
- Version: - PVE 8.1.3 -
-
- Kernel: - 6.5.11-7-pve -
-
- Architecture: - x86_64 -
-
-
- - - - - - Active Sessions - - - -
- Web Console: - - 3 active - -
-
- SSH Sessions: - - 1 active - -
-
- API Calls: - 247/hour -
-
-
- - - - - - Power & Performance - - - -
- Power State: - - Running - -
-
- Load Average: - 1.23, 1.45, 1.67 -
-
- Boot Time: - 2.3s -
-
-
-
-
- ) -} diff --git a/AppImage/components/theme-toggle.tsx b/AppImage/components/theme-toggle.tsx deleted file mode 100644 index eb4c91c..0000000 --- a/AppImage/components/theme-toggle.tsx +++ /dev/null @@ -1,22 +0,0 @@ -"use client" -import { Moon, Sun } from "lucide-react" -import { useTheme } from "next-themes" - -import { Button } from "./ui/button" - -export function ThemeToggle() { - const { theme, setTheme } = useTheme() - - return ( - - ) -} diff --git a/AppImage/components/ui/accordion.tsx b/AppImage/components/ui/accordion.tsx new file mode 100644 index 0000000..e538a33 --- /dev/null +++ b/AppImage/components/ui/accordion.tsx @@ -0,0 +1,66 @@ +'use client' + +import * as React from 'react' +import * as AccordionPrimitive from '@radix-ui/react-accordion' +import { ChevronDownIcon } from 'lucide-react' + +import { cn } from '@/lib/utils' + +function Accordion({ + ...props +}: React.ComponentProps) { + return +} + +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + svg]:rotate-180', + className, + )} + {...props} + > + {children} + + + + ) +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
{children}
+
+ ) +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/AppImage/components/ui/alert-dialog.tsx b/AppImage/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..9704452 --- /dev/null +++ b/AppImage/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +'use client' + +import * as React from 'react' +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' + +import { cn } from '@/lib/utils' +import { buttonVariants } from '@/components/ui/button' + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/AppImage/components/ui/alert.tsx b/AppImage/components/ui/alert.tsx new file mode 100644 index 0000000..e6751ab --- /dev/null +++ b/AppImage/components/ui/alert.tsx @@ -0,0 +1,66 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const alertVariants = cva( + 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current', + { + variants: { + variant: { + default: 'bg-card text-card-foreground', + destructive: + 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<'div'> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription } diff --git a/AppImage/components/ui/aspect-ratio.tsx b/AppImage/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..40bb120 --- /dev/null +++ b/AppImage/components/ui/aspect-ratio.tsx @@ -0,0 +1,11 @@ +'use client' + +import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio' + +function AspectRatio({ + ...props +}: React.ComponentProps) { + return +} + +export { AspectRatio } diff --git a/AppImage/components/ui/avatar.tsx b/AppImage/components/ui/avatar.tsx new file mode 100644 index 0000000..aa98465 --- /dev/null +++ b/AppImage/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +'use client' + +import * as React from 'react' +import * as AvatarPrimitive from '@radix-ui/react-avatar' + +import { cn } from '@/lib/utils' + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/AppImage/components/ui/badge.tsx b/AppImage/components/ui/badge.tsx index 78b3863..fc4126b 100644 --- a/AppImage/components/ui/badge.tsx +++ b/AppImage/components/ui/badge.tsx @@ -1,28 +1,46 @@ -import type * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "@/lib/utils" +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' const badgeVariants = cva( - "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', { variants: { variant: { - default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", - secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", - destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", - outline: "text-foreground", + default: + 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: + 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', }, }, defaultVariants: { - variant: "default", + variant: 'default', }, }, ) -export interface BadgeProps extends React.HTMLAttributes, VariantProps {} +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<'span'> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : 'span' -function Badge({ className, variant, ...props }: BadgeProps) { - return
+ return ( + + ) } export { Badge, badgeVariants } diff --git a/AppImage/components/ui/breadcrumb.tsx b/AppImage/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..1750ff2 --- /dev/null +++ b/AppImage/components/ui/breadcrumb.tsx @@ -0,0 +1,109 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { ChevronRight, MoreHorizontal } from 'lucide-react' + +import { cn } from '@/lib/utils' + +function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) { + return