111 Commits
v1.1.6 ... main

Author SHA1 Message Date
ProxMenuxBot
013d1980a3 Update helpers_cache.json 2025-10-01 18:18:31 +00:00
MacRimi
df9f4a23b4 Update AppImage 2025-10-01 18:14:58 +02:00
MacRimi
c41da47a48 Update AppImage 2025-10-01 18:08:31 +02:00
MacRimi
e7214ad8df Update globals.css 2025-10-01 18:04:38 +02:00
MacRimi
d6671de842 Update AppImage 2025-10-01 18:01:54 +02:00
MacRimi
aad218db5d Update globals.css 2025-10-01 17:44:00 +02:00
MacRimi
724ba1e271 Update AppImge 2025-10-01 17:39:43 +02:00
MacRimi
97d554f638 update AppImage 2025-10-01 17:27:05 +02:00
MacRimi
c5a7655d26 Update AppImage 2025-10-01 17:10:37 +02:00
MacRimi
403e896e3e Update proxmox-dashboard.tsx 2025-10-01 17:03:56 +02:00
MacRimi
1a15f43cad Update proxmox-dashboard.tsx 2025-10-01 16:53:37 +02:00
MacRimi
399b460c53 Update globals.css 2025-09-30 00:11:24 +02:00
MacRimi
acc0362180 Update AppImage 2025-09-30 00:09:11 +02:00
MacRimi
00db93e03f Update AppImage 2025-09-29 23:56:33 +02:00
MacRimi
d1997794c8 Update globals.css 2025-09-29 23:40:45 +02:00
MacRimi
aa1ebe69f2 Update globals.css 2025-09-29 23:11:40 +02:00
MacRimi
4e7f5f56f1 Update AppImage 2025-09-29 22:59:10 +02:00
MacRimi
28cb7359ce Update system-overview.tsx 2025-09-29 22:38:25 +02:00
ProxMenuxBot
91c272d21c Update helpers_cache.json 2025-09-29 18:19:17 +00:00
MacRimi
3c00125e83 Update flask_server.py 2025-09-29 19:19:35 +02:00
MacRimi
f359848a2f Update AppRun 2025-09-29 19:12:56 +02:00
MacRimi
989769e5e8 Update AppRun 2025-09-29 19:07:35 +02:00
MacRimi
0f2f1b6211 Update system-overview.tsx 2025-09-29 19:02:59 +02:00
MacRimi
ffe8f4acc6 Update AppImage 2025-09-29 18:58:53 +02:00
MacRimi
edb09777de Update package.json 2025-09-29 18:47:18 +02:00
MacRimi
5262c7863e Update AppImage 2025-09-29 18:43:14 +02:00
MacRimi
54256826fe Update AppImage 2025-09-29 18:37:32 +02:00
MacRimi
3d3c224b3a Update flask_server.py 2025-09-29 18:31:09 +02:00
MacRimi
049eccb872 Update AppImage 2025-09-29 18:27:09 +02:00
MacRimi
269828c79e Update AppImage 2025-09-29 18:16:01 +02:00
MacRimi
b4e25ae66d Update AppImage 2025-09-29 18:07:30 +02:00
MacRimi
b20dd74d23 Update AppImage 2025-09-29 17:57:00 +02:00
MacRimi
bc3e2ec358 Update AppImage 2025-09-29 17:46:37 +02:00
MacRimi
6133a6d6d8 Update build_appimage.sh 2025-09-29 17:32:24 +02:00
MacRimi
46a16c04e6 Update AppImage 2025-09-29 17:21:59 +02:00
MacRimi
8469b3b26f Update AppImage 2025-09-29 17:05:42 +02:00
ProxMenuxBot
2ed04f57fe Update helpers_cache.json 2025-09-29 12:26:48 +00:00
MacRimi
b19bac679a Update flask_server.py 2025-09-29 00:00:01 +02:00
MacRimi
3c33d5982c Update AppImage 2025-09-28 23:51:06 +02:00
MacRimi
5b934eeb87 Update AppImage 2025-09-28 23:25:58 +02:00
MacRimi
795d96f8d5 Update AppImage 2025-09-28 23:09:31 +02:00
MacRimi
a8e7119b4a Update AppImage 2025-09-28 23:05:59 +02:00
MacRimi
38569ff7fc Update AppImage 2025-09-28 22:57:15 +02:00
MacRimi
e404557d62 Update AppImage 2025-09-28 22:53:42 +02:00
MacRimi
96cbc75a5e Update build_appimage.sh 2025-09-28 21:26:25 +02:00
MacRimi
c989af6cf0 Update build_appimage.sh 2025-09-28 21:18:52 +02:00
MacRimi
4eac9d03ea Update build_appimage.sh 2025-09-28 21:15:18 +02:00
MacRimi
6292009b0b Update AppImage 2025-09-28 21:08:58 +02:00
MacRimi
3272be967d Update build_appimage.sh 2025-09-28 21:01:48 +02:00
MacRimi
1c015da440 Update build_appimage.sh 2025-09-28 20:57:23 +02:00
MacRimi
0d047cc956 Update build_appimage.sh 2025-09-28 20:46:55 +02:00
MacRimi
e682070b85 update AppImage 2025-09-28 20:28:35 +02:00
MacRimi
9f08694d9b Update Appimage 2025-09-28 20:25:55 +02:00
MacRimi
70f0db73e5 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-28 20:20:46 +02:00
MacRimi
9dc8f44379 Create tsconfig.json 2025-09-28 20:20:31 +02:00
MacRimi
59f7ccd723 Update build-appimage.yml 2025-09-28 20:15:56 +02:00
MacRimi
0710e95a6d Update package.json 2025-09-28 20:15:33 +02:00
MacRimi
4d1b5e3919 Update build-appimage.yml 2025-09-28 20:12:21 +02:00
MacRimi
0cc2cb92dd Update build-appimage.yml 2025-09-28 20:10:08 +02:00
MacRimi
dba4d168f7 Update build-appimage.yml 2025-09-28 20:08:02 +02:00
MacRimi
d87ac7843c Update build-appimage.yml 2025-09-28 20:07:12 +02:00
MacRimi
040535b004 Update build-appimage.yml 2025-09-28 20:04:39 +02:00
MacRimi
c8acd2c0b1 Update build-appimage.yml 2025-09-28 19:56:03 +02:00
MacRimi
d67fecea6e Update build-appimage.yml 2025-09-28 19:46:03 +02:00
MacRimi
61f80f9ee6 Update build-appimage.yml 2025-09-28 19:44:51 +02:00
MacRimi
9da8f9a5d1 Update build-appimage.yml 2025-09-28 19:43:05 +02:00
MacRimi
f381468d5a Create build-appimage.yml 2025-09-28 19:41:13 +02:00
MacRimi
6ae97266e4 Create AppImage 2025-09-28 19:40:23 +02:00
MacRimi
66060f345c Create jd2_2.sh 2025-09-27 20:21:15 +02:00
MacRimi
c61f568170 Update nfs_lxc_server.sh 2025-09-27 18:50:50 +02:00
MacRimi
dcd108bda3 Update update-pve.sh 2025-09-27 18:28:56 +02:00
MacRimi
9d89f98987 Update customizable_post_install.sh 2025-09-27 18:26:23 +02:00
MacRimi
ca7b959fce Update auto_post_install.sh 2025-09-27 18:25:25 +02:00
MacRimi
4a30793595 Update uninstall-tools.sh 2025-09-27 18:24:02 +02:00
MacRimi
35e2d53f0f update remove subscription banner PVE 9 2025-09-27 18:16:12 +02:00
MacRimi
503efa4572 Create remove-banner-pve9_2.sh 2025-09-27 17:42:18 +02:00
ProxMenuxBot
b0c33d9dff Update helpers_cache.json 2025-09-27 00:56:57 +00:00
MacRimi
012b156b46 Update install_coral_pve9.sh 2025-09-26 00:17:32 +02:00
MacRimi
25d0d3bf59 Create install_coral_pve9.sh 2025-09-25 23:45:50 +02:00
ProxMenuxBot
0f1babc82b Update helpers_cache.json 2025-09-25 18:20:06 +00:00
ProxMenuxBot
e2b93ea785 Update helpers_cache.json 2025-09-24 18:19:23 +00:00
ProxMenuxBot
b1cedfa81e Update helpers_cache.json 2025-09-24 12:27:12 +00:00
ProxMenuxBot
701ee36f6a Update helpers_cache.json 2025-09-23 12:25:52 +00:00
ProxMenuxBot
4e5db86434 Update helpers_cache.json 2025-09-21 12:23:25 +00:00
ProxMenuxBot
f45e9e657c Update helpers_cache.json 2025-09-19 18:18:42 +00:00
ProxMenuxBot
4936fcdb1e Update helpers_cache.json 2025-09-18 18:18:59 +00:00
ProxMenuxBot
374e05c422 Update helpers_cache.json 2025-09-18 12:25:39 +00:00
ProxMenuxBot
9c00798373 Update helpers_cache.json 2025-09-17 18:18:09 +00:00
ProxMenuxBot
db82fce925 Update helpers_cache.json 2025-09-15 12:26:37 +00:00
ProxMenuxBot
acaa28e476 Update helpers_cache.json 2025-09-13 00:54:55 +00:00
ProxMenuxBot
f297ce5809 Update helpers_cache.json 2025-09-12 12:24:43 +00:00
ProxMenuxBot
3dc3fc5f67 Update helpers_cache.json 2025-09-12 00:57:45 +00:00
ProxMenuxBot
4884fc4418 Update helpers_cache.json 2025-09-11 18:15:27 +00:00
MacRimi
adc17842ec Update README.md 2025-09-11 18:18:42 +02:00
MacRimi
daa48b0b7c Update README.md 2025-09-11 18:17:16 +02:00
MacRimi
17c0362df3 Update cache.json 2025-09-10 20:29:19 +02:00
MacRimi
29b9a63fc9 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-10 20:28:00 +02:00
MacRimi
2a9fae160e Update cache.json 2025-09-10 20:27:58 +02:00
ProxMenuxBot
0c49a1e3bd Update helpers_cache.json 2025-09-10 18:18:53 +00:00
MacRimi
e896c41be1 Update main_menu.sh 2025-09-10 19:14:42 +02:00
MacRimi
187250fa24 update text ProxMenux 2025-09-10 19:06:04 +02:00
MacRimi
9035b18584 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-10 18:58:12 +02:00
MacRimi
4534d78978 update text ProxMenux 2025-09-10 18:57:49 +02:00
MacRimi
f4ab0e982c Update README.md 2025-09-10 18:53:41 +02:00
MacRimi
3e7c6629a6 update text ProxMenux 2025-09-10 18:47:55 +02:00
MacRimi
3ea17331fe Update install_proxmenux.sh 2025-09-10 18:24:14 +02:00
MacRimi
1057fcc271 Update install_proxmenux.sh 2025-09-10 18:19:13 +02:00
MacRimi
5a31c36097 update menu share 2025-09-10 18:05:06 +02:00
MacRimi
1677a69bba Update version.txt 2025-09-10 17:52:17 +02:00
MacRimi
315c49165d Update CHANGELOG.md 2025-09-10 17:50:53 +02:00
MacRimi
aae70e7ec0 Update CHANGELOG.md 2025-09-10 17:47:37 +02:00
87 changed files with 4850 additions and 158 deletions

56
.github/workflows/build-appimage.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
name: Build ProxMenux Monitor AppImage
on:
push:
branches: [ main ]
paths: [ 'AppImage/**' ]
pull_request:
branches: [ main ]
paths: [ 'AppImage/**' ]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
working-directory: AppImage
run: npm install --legacy-peer-deps
- name: Build Next.js app
working-directory: AppImage
run: npm run build
- name: Install Python dependencies
run: |
sudo apt-get update
sudo apt-get install -y python3 python3-pip python3-venv
- name: Make build script executable
working-directory: AppImage
run: chmod +x scripts/build_appimage.sh
- name: Build AppImage
working-directory: AppImage
run: ./scripts/build_appimage.sh
- name: Get version from package.json
id: version
working-directory: AppImage
run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
- name: Upload AppImage artifact
uses: actions/upload-artifact@v4
with:
name: ProxMenux-${{ steps.version.outputs.VERSION }}-AppImage
path: AppImage/dist/*.AppImage
retention-days: 30

22
AppImage/README.md Normal file
View File

@@ -0,0 +1,22 @@
# 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

View File

@@ -0,0 +1,9 @@
import { ProxmoxDashboard } from "../../components/proxmox-dashboard"
export default function DashboardPage() {
return (
<main className="min-h-screen bg-background">
<ProxmoxDashboard />
</main>
)
}

124
AppImage/app/globals.css Normal file
View File

@@ -0,0 +1,124 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
}
@theme inline {
/* optional: --font-sans, --font-serif, --font-mono if they are applied in the layout.tsx */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

46
AppImage/app/layout.tsx Normal file
View File

@@ -0,0 +1,46 @@
import type React from "react"
import type { Metadata } from "next"
import { GeistSans } from "geist/font/sans"
import { GeistMono } from "geist/font/mono"
import { Analytics } from "@vercel/analytics/next"
import { ThemeProvider } from "../components/theme-provider"
import { Suspense } from "react"
import "./globals.css"
export const metadata: Metadata = {
title: "ProxMenux Monitor",
description: "Proxmox System Dashboard and Monitor",
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: "#2b2f36" },
],
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className={`${GeistSans.variable} ${GeistMono.variable} antialiased bg-background text-foreground`}>
<Suspense fallback={<div>Loading...</div>}>
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange>
{children}
</ThemeProvider>
<Analytics />
</Suspense>
</body>
</html>
)
}

7
AppImage/app/page.tsx Normal file
View File

@@ -0,0 +1,7 @@
"use client"
import { ProxmoxDashboard } from "../components/proxmox-dashboard"
export default function Home() {
return <ProxmoxDashboard />
}

View File

@@ -0,0 +1,239 @@
"use client"
import { useState, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"
import { Badge } from "./ui/badge"
import { Wifi, Globe, Shield, Activity, Network, Router, AlertCircle } from "lucide-react"
interface NetworkData {
interfaces: NetworkInterface[]
traffic: {
bytes_sent: number
bytes_recv: number
packets_sent?: number
packets_recv?: number
}
}
interface NetworkInterface {
name: string
status: string
addresses: Array<{
ip: string
netmask: string
}>
}
const fetchNetworkData = async (): Promise<NetworkData | null> => {
try {
console.log("[v0] Fetching network data from Flask server...")
const response = await fetch("/api/network", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
signal: AbortSignal.timeout(5000),
})
if (!response.ok) {
throw new Error(`Flask server responded with status: ${response.status}`)
}
const data = await response.json()
console.log("[v0] Successfully fetched network data from Flask:", data)
return data
} catch (error) {
console.error("[v0] Failed to fetch network data from Flask server:", error)
return null
}
}
export function NetworkMetrics() {
const [networkData, setNetworkData] = useState<NetworkData | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const fetchData = async () => {
setLoading(true)
setError(null)
const result = await fetchNetworkData()
if (!result) {
setError("Flask server not available. Please ensure the server is running.")
} else {
setNetworkData(result)
}
setLoading(false)
}
fetchData()
const interval = setInterval(fetchData, 30000)
return () => clearInterval(interval)
}, [])
if (loading) {
return (
<div className="space-y-6">
<div className="text-center py-8">
<div className="text-lg font-medium text-foreground mb-2">Loading network data...</div>
</div>
</div>
)
}
if (error || !networkData) {
return (
<div className="space-y-6">
<Card className="bg-red-500/10 border-red-500/20">
<CardContent className="p-6">
<div className="flex items-center gap-3 text-red-600">
<AlertCircle className="h-6 w-6" />
<div>
<div className="font-semibold text-lg mb-1">Flask Server Not Available</div>
<div className="text-sm">
{error || "Unable to connect to the Flask server. Please ensure the server is running and try again."}
</div>
</div>
</div>
</CardContent>
</Card>
</div>
)
}
const trafficInMB = (networkData.traffic.bytes_recv / (1024 * 1024)).toFixed(1)
const trafficOutMB = (networkData.traffic.bytes_sent / (1024 * 1024)).toFixed(1)
return (
<div className="space-y-6">
{/* Network Overview Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Network Traffic</CardTitle>
<Activity className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">{trafficInMB} MB</div>
<div className="flex items-center space-x-2 mt-2">
<span className="text-xs text-green-500"> {trafficInMB} MB</span>
<span className="text-xs text-blue-500"> {trafficOutMB} MB</span>
</div>
<p className="text-xs text-muted-foreground mt-2">Total data transferred</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Active Interfaces</CardTitle>
<Network className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">
{networkData.interfaces.filter((i) => i.status === "up").length}
</div>
<div className="flex items-center mt-2">
<Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20">
Online
</Badge>
</div>
<p className="text-xs text-muted-foreground mt-2">{networkData.interfaces.length} total interfaces</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Firewall Status</CardTitle>
<Shield className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">Active</div>
<div className="flex items-center mt-2">
<Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20">
Protected
</Badge>
</div>
<p className="text-xs text-muted-foreground mt-2">System protected</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Globe className="h-5 w-5 mr-2" />
Packets
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">
{networkData.traffic.packets_recv ? (networkData.traffic.packets_recv / 1000).toFixed(0) : "N/A"}K
</div>
<div className="flex items-center mt-2">
<Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20">
Received
</Badge>
</div>
<p className="text-xs text-muted-foreground mt-2">Total packets received</p>
</CardContent>
</Card>
</div>
{/* Network Interfaces */}
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Router className="h-5 w-5 mr-2" />
Network Interfaces
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{networkData.interfaces.map((interface_, index) => (
<div
key={index}
className="flex items-center justify-between p-4 rounded-lg border border-border bg-card/50"
>
<div className="flex items-center space-x-4">
<Wifi className="h-5 w-5 text-muted-foreground" />
<div>
<div className="font-medium text-foreground">{interface_.name}</div>
<div className="text-sm text-muted-foreground">Network Interface</div>
</div>
</div>
<div className="flex items-center space-x-6">
<div className="text-center">
<div className="text-sm text-muted-foreground">IP Address</div>
<div className="text-sm font-medium text-foreground font-mono">
{interface_.addresses.length > 0 ? interface_.addresses[0].ip : "N/A"}
</div>
</div>
<div className="text-center">
<div className="text-sm text-muted-foreground">Netmask</div>
<div className="text-sm font-medium text-foreground">
{interface_.addresses.length > 0 ? interface_.addresses[0].netmask : "N/A"}
</div>
</div>
<Badge
variant="outline"
className={
interface_.status === "up"
? "bg-green-500/10 text-green-500 border-green-500/20"
: "bg-red-500/10 text-red-500 border-red-500/20"
}
>
{interface_.status.toUpperCase()}
</Badge>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,294 @@
"use client"
import { useState, useEffect, useMemo, useCallback } 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, 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
}
interface FlaskSystemData {
hostname: string
node_id: string
uptime: string
cpu_usage: number
memory_usage: number
temperature: number
load_average: number[]
}
export function ProxmoxDashboard() {
const [systemStatus, setSystemStatus] = useState<SystemStatus>({
status: "healthy",
uptime: "Loading...",
lastUpdate: new Date().toLocaleTimeString(),
serverName: "Loading...",
nodeId: "Loading...",
})
const [isRefreshing, setIsRefreshing] = useState(false)
const [isServerConnected, setIsServerConnected] = useState(true)
const [componentKey, setComponentKey] = useState(0)
const fetchSystemData = useCallback(async () => {
console.log("[v0] Fetching system data from Flask server...")
console.log("[v0] Current window location:", window.location.href)
const apiUrl = "/api/system"
console.log("[v0] API URL:", apiUrl)
try {
const response = await fetch(apiUrl, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
console.log("[v0] Response status:", response.status)
if (!response.ok) {
throw new Error(`Server responded with status: ${response.status}`)
}
const data: FlaskSystemData = await response.json()
console.log("[v0] System data received:", data)
let status: "healthy" | "warning" | "critical" = "healthy"
if (data.cpu_usage > 90 || data.memory_usage > 90) {
status = "critical"
} else if (data.cpu_usage > 75 || data.memory_usage > 75) {
status = "warning"
}
setSystemStatus({
status,
uptime: data.uptime,
lastUpdate: new Date().toLocaleTimeString(),
serverName: data.hostname,
nodeId: data.node_id,
})
setIsServerConnected(true)
} catch (error) {
console.error("[v0] Failed to fetch system data from Flask server:", error)
console.error("[v0] Error details:", {
message: error instanceof Error ? error.message : "Unknown error",
apiUrl,
windowLocation: window.location.href,
})
setIsServerConnected(false)
setSystemStatus((prev) => ({
...prev,
status: "critical",
serverName: "Server Offline",
nodeId: "Server Offline",
uptime: "N/A",
lastUpdate: new Date().toLocaleTimeString(),
}))
}
}, [])
useEffect(() => {
fetchSystemData()
const interval = setInterval(fetchSystemData, 5000)
return () => clearInterval(interval)
}, [fetchSystemData])
const refreshData = async () => {
setIsRefreshing(true)
await fetchSystemData()
setComponentKey((prev) => prev + 1)
await new Promise((resolve) => setTimeout(resolve, 500))
setIsRefreshing(false)
}
const statusIcon = useMemo(() => {
switch (systemStatus.status) {
case "healthy":
return <CheckCircle className="h-4 w-4 text-green-500" />
case "warning":
return <AlertTriangle className="h-4 w-4 text-yellow-500" />
case "critical":
return <XCircle className="h-4 w-4 text-red-500" />
}
}, [systemStatus.status])
const statusColor = useMemo(() => {
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"
}
}, [systemStatus.status])
return (
<div className="min-h-screen bg-background">
{!isServerConnected && (
<div className="bg-red-500/10 border-b border-red-500/20 px-6 py-3">
<div className="container mx-auto">
<div className="flex items-center space-x-2 text-red-500 mb-2">
<XCircle className="h-5 w-5" />
<span className="font-medium">Flask Server Connection Failed</span>
</div>
<div className="text-sm text-red-500/80 space-y-1 ml-7">
<p> Check that the AppImage is running correctly</p>
<p> The Flask server should start automatically on port 8008</p>
<p>
Try accessing:{" "}
<a
href={`http://${typeof window !== "undefined" ? window.location.host : "localhost:8008"}/api/health`}
target="_blank"
rel="noopener noreferrer"
className="underline"
>
http://{typeof window !== "undefined" ? window.location.host : "localhost:8008"}/api/health
</a>
</p>
</div>
</div>
</div>
)}
<header className="border-b border-border bg-card sticky top-0 z-50 shadow-sm">
<div className="container mx-auto px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 relative flex items-center justify-center bg-primary/10 overflow-hidden">
<Image
src="/images/proxmenux-logo.png"
alt="ProxMenux Logo"
width={40}
height={40}
className="object-contain"
priority
onError={(e) => {
console.log("[v0] Logo failed to load, using fallback icon")
const target = e.target as HTMLImageElement
target.style.display = "none"
const fallback = target.parentElement?.querySelector(".fallback-icon")
if (fallback) {
fallback.classList.remove("hidden")
}
}}
/>
<Server className="h-6 w-6 text-primary absolute fallback-icon hidden" />
</div>
<div>
<h1 className="text-xl font-semibold text-foreground">ProxMenux Monitor</h1>
<p className="text-sm text-muted-foreground">Proxmox System Dashboard</p>
</div>
</div>
<div className="flex items-center space-x-4">
<div className="hidden md:flex items-center space-x-2">
<Server className="h-4 w-4 text-muted-foreground" />
<div className="text-sm">
<div className="font-medium text-foreground">{systemStatus.serverName}</div>
<div className="text-xs text-muted-foreground">{systemStatus.nodeId}</div>
</div>
</div>
<Badge variant="outline" className={statusColor}>
{statusIcon}
<span className="ml-1 capitalize">{systemStatus.status}</span>
</Badge>
<div className="text-sm text-muted-foreground">Uptime: {systemStatus.uptime}</div>
<Button
variant="outline"
size="sm"
onClick={refreshData}
disabled={isRefreshing}
className="border-border/50 bg-transparent hover:bg-secondary"
>
<RefreshCw className={`h-4 w-4 mr-2 ${isRefreshing ? "animate-spin" : ""}`} />
Refresh
</Button>
<ThemeToggle />
</div>
</div>
</div>
</header>
<div className="container mx-auto px-6 py-6">
<Tabs defaultValue="overview" className="space-y-6">
<TabsList className="grid w-full grid-cols-5 bg-card border border-border">
<TabsTrigger
value="overview"
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground"
>
Overview
</TabsTrigger>
<TabsTrigger
value="storage"
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground"
>
Storage
</TabsTrigger>
<TabsTrigger
value="network"
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground"
>
Network
</TabsTrigger>
<TabsTrigger
value="vms"
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground"
>
Virtual Machines
</TabsTrigger>
<TabsTrigger
value="logs"
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground"
>
System Logs
</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="space-y-6">
<SystemOverview key={`overview-${componentKey}`} />
</TabsContent>
<TabsContent value="storage" className="space-y-6">
<StorageMetrics key={`storage-${componentKey}`} />
</TabsContent>
<TabsContent value="network" className="space-y-6">
<NetworkMetrics key={`network-${componentKey}`} />
</TabsContent>
<TabsContent value="vms" className="space-y-6">
<VirtualMachines key={`vms-${componentKey}`} />
</TabsContent>
<TabsContent value="logs" className="space-y-6">
<SystemLogs key={`logs-${componentKey}`} />
</TabsContent>
</Tabs>
<footer className="mt-12 pt-6 border-t border-border text-center text-sm text-muted-foreground">
<p>Last updated: {systemStatus.lastUpdate} ProxMenux Monitor v1.0.0</p>
</footer>
</div>
</div>
)
}

View File

@@ -0,0 +1,237 @@
"use client"
import { useState, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"
import { Progress } from "./ui/progress"
import { Badge } from "./ui/badge"
import { HardDrive, Database, Archive, AlertTriangle, CheckCircle, Activity, AlertCircle } from "lucide-react"
interface StorageData {
total: number
used: number
available: number
disks: DiskInfo[]
}
interface DiskInfo {
name: string
mountpoint: string
fstype: string
total: number
used: number
available: number
usage_percent: number
health: string
temperature: number
}
const fetchStorageData = async (): Promise<StorageData | null> => {
try {
console.log("[v0] Fetching storage data from Flask server...")
const response = await fetch("/api/storage", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
signal: AbortSignal.timeout(5000),
})
if (!response.ok) {
throw new Error(`Flask server responded with status: ${response.status}`)
}
const data = await response.json()
console.log("[v0] Successfully fetched storage data from Flask:", data)
return data
} catch (error) {
console.error("[v0] Failed to fetch storage data from Flask server:", error)
return null
}
}
export function StorageMetrics() {
const [storageData, setStorageData] = useState<StorageData | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const fetchData = async () => {
setLoading(true)
setError(null)
const result = await fetchStorageData()
if (!result) {
setError("Flask server not available. Please ensure the server is running.")
} else {
setStorageData(result)
}
setLoading(false)
}
fetchData()
const interval = setInterval(fetchData, 30000)
return () => clearInterval(interval)
}, [])
if (loading) {
return (
<div className="space-y-6">
<div className="text-center py-8">
<div className="text-lg font-medium text-foreground mb-2">Loading storage data...</div>
</div>
</div>
)
}
if (error || !storageData) {
return (
<div className="space-y-6">
<Card className="bg-red-500/10 border-red-500/20">
<CardContent className="p-6">
<div className="flex items-center gap-3 text-red-600">
<AlertCircle className="h-6 w-6" />
<div>
<div className="font-semibold text-lg mb-1">Flask Server Not Available</div>
<div className="text-sm">
{error || "Unable to connect to the Flask server. Please ensure the server is running and try again."}
</div>
</div>
</div>
</CardContent>
</Card>
</div>
)
}
const usagePercent = storageData.total > 0 ? (storageData.used / storageData.total) * 100 : 0
return (
<div className="space-y-6">
{/* Storage Overview Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Total Storage</CardTitle>
<HardDrive className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">{storageData.total.toFixed(1)} GB</div>
<Progress value={usagePercent} className="mt-2" />
<p className="text-xs text-muted-foreground mt-2">
{storageData.used.toFixed(1)} GB used {storageData.available.toFixed(1)} GB available
</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Used Storage</CardTitle>
<Database className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">{storageData.used.toFixed(1)} GB</div>
<Progress value={usagePercent} className="mt-2" />
<p className="text-xs text-muted-foreground mt-2">{usagePercent.toFixed(1)}% of total space</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Archive className="h-5 w-5 mr-2" />
Available
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">{storageData.available.toFixed(1)} GB</div>
<div className="flex items-center mt-2">
<Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20">
{((storageData.available / storageData.total) * 100).toFixed(1)}% Free
</Badge>
</div>
<p className="text-xs text-muted-foreground mt-2">Available space</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Activity className="h-5 w-5 mr-2" />
Disks
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">{storageData.disks.length}</div>
<div className="flex items-center space-x-2 mt-2">
<Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20">
{storageData.disks.filter((d) => d.health === "healthy").length} Healthy
</Badge>
</div>
<p className="text-xs text-muted-foreground mt-2">Storage devices</p>
</CardContent>
</Card>
</div>
{/* Disk Details */}
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Database className="h-5 w-5 mr-2" />
Storage Devices
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{storageData.disks.map((disk, index) => (
<div
key={index}
className="flex items-center justify-between p-4 rounded-lg border border-border bg-card/50"
>
<div className="flex items-center space-x-4">
<HardDrive className="h-5 w-5 text-muted-foreground" />
<div>
<div className="font-medium text-foreground">{disk.name}</div>
<div className="text-sm text-muted-foreground">
{disk.fstype} {disk.mountpoint}
</div>
</div>
</div>
<div className="flex items-center space-x-6">
<div className="text-right">
<div className="text-sm font-medium text-foreground">
{disk.used.toFixed(1)} GB / {disk.total.toFixed(1)} GB
</div>
<Progress value={disk.usage_percent} className="w-24 mt-1" />
</div>
<div className="text-center">
<div className="text-sm text-muted-foreground">Temp</div>
<div className="text-sm font-medium text-foreground">{disk.temperature}°C</div>
</div>
<Badge
variant="outline"
className={
disk.health === "healthy"
? "bg-green-500/10 text-green-500 border-green-500/20"
: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20"
}
>
{disk.health === "healthy" ? (
<CheckCircle className="h-3 w-3 mr-1" />
) : (
<AlertTriangle className="h-3 w-3 mr-1" />
)}
{disk.health}
</Badge>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,275 @@
"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 <XCircle className="h-3 w-3 mr-1" />
case "warning":
return <AlertTriangle className="h-3 w-3 mr-1" />
case "info":
return <Info className="h-3 w-3 mr-1" />
default:
return <CheckCircle className="h-3 w-3 mr-1" />
}
}
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 (
<div className="space-y-6">
{/* Log Statistics */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Total Logs</CardTitle>
<FileText className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">{logCounts.total}</div>
<p className="text-xs text-muted-foreground mt-2">Last 24 hours</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Errors</CardTitle>
<XCircle className="h-4 w-4 text-red-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-red-500">{logCounts.error}</div>
<p className="text-xs text-muted-foreground mt-2">Requires attention</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Warnings</CardTitle>
<AlertTriangle className="h-4 w-4 text-yellow-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-yellow-500">{logCounts.warning}</div>
<p className="text-xs text-muted-foreground mt-2">Monitor closely</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Info</CardTitle>
<Info className="h-4 w-4 text-blue-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-blue-500">{logCounts.info}</div>
<p className="text-xs text-muted-foreground mt-2">Normal operations</p>
</CardContent>
</Card>
</div>
{/* Log Filters and Search */}
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<FileText className="h-5 w-5 mr-2" />
System Logs
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-col sm:flex-row gap-4 mb-6">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search logs..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 bg-background border-border"
/>
</div>
</div>
<Select value={levelFilter} onValueChange={setLevelFilter}>
<SelectTrigger className="w-full sm:w-[180px] bg-background border-border">
<SelectValue placeholder="Filter by level" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Levels</SelectItem>
<SelectItem value="error">Error</SelectItem>
<SelectItem value="warning">Warning</SelectItem>
<SelectItem value="info">Info</SelectItem>
</SelectContent>
</Select>
<Select value={serviceFilter} onValueChange={setServiceFilter}>
<SelectTrigger className="w-full sm:w-[180px] bg-background border-border">
<SelectValue placeholder="Filter by service" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Services</SelectItem>
{uniqueServices.map((service) => (
<SelectItem key={service} value={service}>
{service}
</SelectItem>
))}
</SelectContent>
</Select>
<Button variant="outline" className="border-border bg-transparent">
<Download className="h-4 w-4 mr-2" />
Export
</Button>
</div>
<ScrollArea className="h-[600px] w-full rounded-md border border-border">
<div className="space-y-2 p-4">
{filteredLogs.map((log, index) => (
<div
key={index}
className="flex items-start space-x-4 p-3 rounded-lg bg-card/50 border border-border/50"
>
<div className="flex-shrink-0">
<Badge variant="outline" className={getLevelColor(log.level)}>
{getLevelIcon(log.level)}
{log.level.toUpperCase()}
</Badge>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between mb-1">
<div className="text-sm font-medium text-foreground">{log.service}</div>
<div className="text-xs text-muted-foreground font-mono">{log.timestamp}</div>
</div>
<div className="text-sm text-foreground mb-1">{log.message}</div>
<div className="text-xs text-muted-foreground">Source: {log.source}</div>
</div>
</div>
))}
{filteredLogs.length === 0 && (
<div className="text-center py-8 text-muted-foreground">
<FileText className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>No logs found matching your criteria</p>
</div>
)}
</div>
</ScrollArea>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,485 @@
"use client"
import { useState, useEffect } from "react"
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, Activity, Server, Zap, AlertCircle } from "lucide-react"
interface SystemData {
cpu_usage: number
memory_usage: number
memory_total: number
memory_used: number
temperature: number
uptime: string
load_average: number[]
hostname: string
node_id: string
timestamp: string
}
interface VMData {
vmid: number
name: string
status: string
cpu: number
mem: number
maxmem: number
disk: number
maxdisk: number
uptime: number
}
interface HistoricalData {
timestamp: string
cpu_usage: number
memory_used: number
memory_total: number
}
const historicalDataStore: HistoricalData[] = []
const MAX_HISTORICAL_POINTS = 24 // Store 24 data points for 24h view
const fetchSystemData = async (): Promise<SystemData | null> => {
try {
console.log("[v0] Fetching system data from Flask server...")
const baseUrl = typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : ""
const apiUrl = `${baseUrl}/api/system`
console.log("[v0] Fetching from URL:", apiUrl)
const response = await fetch(apiUrl, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
cache: "no-store",
})
console.log("[v0] Response status:", response.status)
console.log("[v0] Response ok:", response.ok)
console.log("[v0] Response headers:", Object.fromEntries(response.headers.entries()))
if (!response.ok) {
const errorText = await response.text()
console.error("[v0] Flask server error response:", errorText)
throw new Error(`Flask server responded with status: ${response.status}`)
}
const responseText = await response.text()
console.log("[v0] Raw response text:", responseText)
console.log("[v0] Response text length:", responseText.length)
console.log("[v0] First 100 chars:", responseText.substring(0, 100))
// Try to parse the JSON
let data
try {
data = JSON.parse(responseText)
console.log("[v0] Successfully parsed JSON:", data)
} catch (parseError) {
console.error("[v0] JSON parse error:", parseError)
console.error("[v0] Failed to parse response as JSON")
throw new Error("Invalid JSON response from server")
}
// Store historical data
historicalDataStore.push({
timestamp: data.timestamp,
cpu_usage: data.cpu_usage,
memory_used: data.memory_used,
memory_total: data.memory_total,
})
// Keep only last MAX_HISTORICAL_POINTS
if (historicalDataStore.length > MAX_HISTORICAL_POINTS) {
historicalDataStore.shift()
}
return data
} catch (error) {
console.error("[v0] Failed to fetch system data from Flask server:", error)
console.error("[v0] Error type:", error instanceof Error ? error.constructor.name : typeof error)
console.error("[v0] Error message:", error instanceof Error ? error.message : String(error))
console.error("[v0] Error stack:", error instanceof Error ? error.stack : "No stack trace")
return null
}
}
const fetchVMData = async (): Promise<VMData[]> => {
try {
console.log("[v0] Fetching VM data from Flask server...")
const baseUrl = typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : ""
const apiUrl = `${baseUrl}/api/vms`
console.log("[v0] Fetching from URL:", apiUrl)
const response = await fetch(apiUrl, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
cache: "no-store",
})
console.log("[v0] VM Response status:", response.status)
if (!response.ok) {
const errorText = await response.text()
console.error("[v0] Flask server error response:", errorText)
throw new Error(`Flask server responded with status: ${response.status}`)
}
const data = await response.json()
console.log("[v0] Successfully fetched VM data from Flask:", data)
return Array.isArray(data) ? data : data.vms || []
} catch (error) {
console.error("[v0] Failed to fetch VM data from Flask server:", error)
console.error("[v0] Error type:", error instanceof Error ? error.constructor.name : typeof error)
console.error("[v0] Error message:", error instanceof Error ? error.message : String(error))
return []
}
}
const generateChartData = () => {
if (historicalDataStore.length === 0) {
return { cpuData: [], memoryData: [] }
}
const cpuData = historicalDataStore.map((point) => ({
time: new Date(point.timestamp).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" }),
value: point.cpu_usage,
}))
const memoryData = historicalDataStore.map((point) => ({
time: new Date(point.timestamp).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" }),
used: point.memory_used,
available: point.memory_total - point.memory_used,
}))
return { cpuData, memoryData }
}
export function SystemOverview() {
const [systemData, setSystemData] = useState<SystemData | null>(null)
const [vmData, setVmData] = useState<VMData[]>([])
const [chartData, setChartData] = useState(generateChartData())
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true)
setError(null)
const [systemResult, vmResult] = await Promise.all([fetchSystemData(), fetchVMData()])
if (!systemResult) {
setError("Flask server not available. Please ensure the server is running.")
setLoading(false)
return
}
setSystemData(systemResult)
setVmData(vmResult)
setChartData(generateChartData())
} catch (err) {
console.error("[v0] Error fetching data:", err)
setError("Failed to connect to Flask server. Please check your connection.")
} finally {
setLoading(false)
}
}
fetchData()
const interval = setInterval(() => {
fetchData()
}, 30000) // Update every 30 seconds
return () => {
clearInterval(interval)
}
}, [])
if (loading) {
return (
<div className="space-y-6">
<div className="text-center py-8">
<div className="text-lg font-medium text-foreground mb-2">Connecting to ProxMenux Monitor...</div>
<div className="text-sm text-muted-foreground">Fetching real-time system data</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{[...Array(4)].map((_, i) => (
<Card key={i} className="bg-card border-border animate-pulse">
<CardContent className="p-6">
<div className="h-4 bg-muted rounded w-1/2 mb-4"></div>
<div className="h-8 bg-muted rounded w-3/4 mb-2"></div>
<div className="h-2 bg-muted rounded w-full mb-2"></div>
<div className="h-3 bg-muted rounded w-2/3"></div>
</CardContent>
</Card>
))}
</div>
</div>
)
}
if (error || !systemData) {
return (
<div className="space-y-6">
<Card className="bg-red-500/10 border-red-500/20">
<CardContent className="p-6">
<div className="flex items-center gap-3 text-red-600">
<AlertCircle className="h-6 w-6" />
<div>
<div className="font-semibold text-lg mb-1">Flask Server Not Available</div>
<div className="text-sm">
{error || "Unable to connect to the Flask server. Please ensure the server is running and try again."}
</div>
<div className="text-sm mt-2">
<strong>Troubleshooting:</strong>
<ul className="list-disc list-inside mt-1 space-y-1">
<li>Check if the Flask server is running on the correct port</li>
<li>Verify network connectivity</li>
<li>Check server logs for errors</li>
</ul>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
)
}
const vmStats = {
total: vmData.length,
running: vmData.filter((vm) => vm.status === "running").length,
stopped: vmData.filter((vm) => vm.status === "stopped").length,
lxc: 0,
}
const getTemperatureStatus = (temp: number) => {
if (temp === 0) return { status: "N/A", color: "bg-gray-500/10 text-gray-500 border-gray-500/20" }
if (temp < 60) return { status: "Normal", color: "bg-green-500/10 text-green-500 border-green-500/20" }
if (temp < 75) return { status: "Warm", color: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" }
return { status: "Hot", color: "bg-red-500/10 text-red-500 border-red-500/20" }
}
const tempStatus = getTemperatureStatus(systemData.temperature)
return (
<div className="space-y-6">
{/* Key Metrics Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">CPU Usage</CardTitle>
<Cpu className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">{systemData.cpu_usage}%</div>
<Progress value={systemData.cpu_usage} className="mt-2" />
<p className="text-xs text-muted-foreground mt-2">Real-time data from Flask server</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Memory Usage</CardTitle>
<MemoryStick className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">{systemData.memory_used.toFixed(1)} GB</div>
<Progress value={systemData.memory_usage} className="mt-2" />
<p className="text-xs text-muted-foreground mt-2">
{systemData.memory_usage.toFixed(1)}% of {systemData.memory_total} GB
</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Temperature</CardTitle>
<Thermometer className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">
{systemData.temperature === 0 ? "N/A" : `${systemData.temperature}°C`}
</div>
<div className="flex items-center mt-2">
<Badge variant="outline" className={tempStatus.color}>
{tempStatus.status}
</Badge>
</div>
<p className="text-xs text-muted-foreground mt-2">
{systemData.temperature === 0 ? "No sensor available" : "Live temperature reading"}
</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Active VMs</CardTitle>
<Server className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">{vmStats.running}</div>
<div className="mt-2 flex flex-wrap gap-1">
<Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20">
{vmStats.running} Running
</Badge>
{vmStats.stopped > 0 && (
<Badge variant="outline" className="bg-yellow-500/10 text-yellow-500 border-yellow-500/20">
{vmStats.stopped} Stopped
</Badge>
)}
</div>
<p className="text-xs text-muted-foreground mt-2">Total: {vmStats.total} VMs configured</p>
</CardContent>
</Card>
</div>
{/* Charts Section */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Activity className="h-5 w-5 mr-2" />
CPU Usage (Last {historicalDataStore.length} readings)
</CardTitle>
</CardHeader>
<CardContent>
{chartData.cpuData.length > 0 ? (
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={chartData.cpuData}>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
<XAxis dataKey="time" stroke="hsl(var(--muted-foreground))" fontSize={12} />
<YAxis stroke="hsl(var(--muted-foreground))" fontSize={12} />
<Tooltip
contentStyle={{
backgroundColor: "hsl(var(--card))",
border: "1px solid hsl(var(--border))",
borderRadius: "8px",
color: "hsl(var(--foreground))",
}}
/>
<Area type="monotone" dataKey="value" stroke="#3b82f6" fill="#3b82f6" fillOpacity={0.2} />
</AreaChart>
</ResponsiveContainer>
) : (
<div className="h-[300px] flex items-center justify-center text-muted-foreground">
Collecting data... Check back in a few minutes
</div>
)}
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<MemoryStick className="h-5 w-5 mr-2" />
Memory Usage (Last {historicalDataStore.length} readings)
</CardTitle>
</CardHeader>
<CardContent>
{chartData.memoryData.length > 0 ? (
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={chartData.memoryData}>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
<XAxis dataKey="time" stroke="hsl(var(--muted-foreground))" fontSize={12} />
<YAxis stroke="hsl(var(--muted-foreground))" fontSize={12} />
<Tooltip
contentStyle={{
backgroundColor: "hsl(var(--card))",
border: "1px solid hsl(var(--border))",
borderRadius: "8px",
color: "hsl(var(--foreground))",
}}
/>
<Area type="monotone" dataKey="used" stackId="1" stroke="#3b82f6" fill="#3b82f6" fillOpacity={0.6} />
<Area
type="monotone"
dataKey="available"
stackId="1"
stroke="#10b981"
fill="#10b981"
fillOpacity={0.6}
/>
</AreaChart>
</ResponsiveContainer>
) : (
<div className="h-[300px] flex items-center justify-center text-muted-foreground">
Collecting data... Check back in a few minutes
</div>
)}
</CardContent>
</Card>
</div>
{/* System Information */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Server className="h-5 w-5 mr-2" />
System Information
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex justify-between">
<span className="text-muted-foreground">Hostname:</span>
<span className="text-foreground font-mono">{systemData.hostname}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Uptime:</span>
<span className="text-foreground">{systemData.uptime}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Node ID:</span>
<span className="text-foreground font-mono">{systemData.node_id}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Last Update:</span>
<span className="text-foreground">{new Date(systemData.timestamp).toLocaleTimeString()}</span>
</div>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Zap className="h-5 w-5 mr-2" />
Performance Metrics
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex justify-between">
<span className="text-muted-foreground">Load Average:</span>
<span className="text-foreground font-mono">
{systemData.load_average.map((avg) => avg.toFixed(2)).join(", ")}
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Total Memory:</span>
<span className="text-foreground">{systemData.memory_total} GB</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Available Memory:</span>
<span className="text-foreground">
{(systemData.memory_total - systemData.memory_used).toFixed(1)} GB
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">CPU Cores:</span>
<span className="text-foreground">{navigator.hardwareConcurrency || "N/A"}</span>
</div>
</CardContent>
</Card>
</div>
</div>
)
}

View File

@@ -0,0 +1,7 @@
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import type { ThemeProviderProps } from "next-themes"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

View File

@@ -0,0 +1,39 @@
"use client"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { useEffect, useState } from "react"
import { Button } from "./ui/button"
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
const handleThemeToggle = () => {
console.log("[v0] Current theme:", theme)
const newTheme = theme === "light" ? "dark" : "light"
console.log("[v0] Switching to theme:", newTheme)
setTheme(newTheme)
}
if (!mounted) {
return (
<Button variant="outline" size="sm" className="border-border bg-transparent w-9 h-9">
<Sun className="h-4 w-4" />
<span className="sr-only">Toggle theme</span>
</Button>
)
}
return (
<Button variant="outline" size="sm" onClick={handleThemeToggle} className="border-border bg-transparent w-9 h-9">
<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
)
}

View File

@@ -0,0 +1,28 @@
import type * as React from "react"
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",
{
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",
},
},
defaultVariants: {
variant: "default",
},
},
)
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />
}
export { Badge, badgeVariants }

View File

@@ -0,0 +1,46 @@
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 buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
},
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -0,0 +1,42 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)} {...props} />
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
),
)
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3 ref={ref} className={cn("text-2xl font-semibold leading-none tracking-tight", className)} {...props} />
),
)
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
),
)
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />,
)
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
),
)
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@@ -0,0 +1,22 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}
{...props}
/>
)
})
Input.displayName = "Input"
export { Input }

View File

@@ -0,0 +1,25 @@
"use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/lib/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn("relative h-2 w-full overflow-hidden rounded-full bg-secondary", className)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }

View File

@@ -0,0 +1,40 @@
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root ref={ref} className={cn("relative overflow-hidden", className)} {...props}>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">{children}</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]",
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }

View File

@@ -0,0 +1,144 @@
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className,
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label ref={ref} className={cn("px-2 py-1.5 text-sm font-semibold", className)} {...props} />
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}

View File

@@ -0,0 +1,52 @@
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className,
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className,
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className,
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@@ -0,0 +1,267 @@
"use client"
import { useState, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"
import { Badge } from "./ui/badge"
import { Progress } from "./ui/progress"
import { Server, Play, Square, Monitor, Cpu, MemoryStick, AlertCircle } from "lucide-react"
interface VMData {
vmid: number
name: string
status: string
cpu: number
mem: number
maxmem: number
disk: number
maxdisk: number
uptime: number
}
const fetchVMData = async (): Promise<VMData[]> => {
try {
console.log("[v0] Fetching VM data from Flask server...")
const response = await fetch("/api/vms", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
signal: AbortSignal.timeout(5000),
})
if (!response.ok) {
throw new Error(`Flask server responded with status: ${response.status}`)
}
const data = await response.json()
console.log("[v0] Successfully fetched VM data from Flask:", data)
return Array.isArray(data) ? data : []
} catch (error) {
console.error("[v0] Failed to fetch VM data from Flask server:", error)
throw error
}
}
export function VirtualMachines() {
const [vmData, setVmData] = useState<VMData[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true)
setError(null)
const result = await fetchVMData()
setVmData(result)
} catch (err) {
setError("Flask server not available. Please ensure the server is running.")
} finally {
setLoading(false)
}
}
fetchData()
const interval = setInterval(fetchData, 30000)
return () => clearInterval(interval)
}, [])
if (loading) {
return (
<div className="space-y-6">
<div className="text-center py-8">
<div className="text-lg font-medium text-foreground mb-2">Loading VM data...</div>
</div>
</div>
)
}
if (error) {
return (
<div className="space-y-6">
<Card className="bg-red-500/10 border-red-500/20">
<CardContent className="p-6">
<div className="flex items-center gap-3 text-red-600">
<AlertCircle className="h-6 w-6" />
<div>
<div className="font-semibold text-lg mb-1">Flask Server Not Available</div>
<div className="text-sm">
{error || "Unable to connect to the Flask server. Please ensure the server is running and try again."}
</div>
</div>
</div>
</CardContent>
</Card>
</div>
)
}
const runningVMs = vmData.filter((vm) => vm.status === "running").length
const stoppedVMs = vmData.filter((vm) => vm.status === "stopped").length
const totalCPU = vmData.reduce((sum, vm) => sum + (vm.cpu || 0), 0)
const totalMemory = vmData.reduce((sum, vm) => sum + (vm.maxmem || 0), 0)
const getStatusColor = (status: string) => {
switch (status) {
case "running":
return "bg-green-500/10 text-green-500 border-green-500/20"
case "stopped":
return "bg-red-500/10 text-red-500 border-red-500/20"
default:
return "bg-yellow-500/10 text-yellow-500 border-yellow-500/20"
}
}
const getStatusIcon = (status: string) => {
switch (status) {
case "running":
return <Play className="h-3 w-3 mr-1" />
case "stopped":
return <Square className="h-3 w-3 mr-1" />
default:
return null
}
}
const formatUptime = (seconds: number) => {
const days = Math.floor(seconds / 86400)
const hours = Math.floor((seconds % 86400) / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
return `${days}d ${hours}h ${minutes}m`
}
return (
<div className="space-y-6">
{/* VM Overview Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Total VMs</CardTitle>
<Server className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">{vmData.length}</div>
<div className="vm-badges mt-2">
<Badge variant="outline" className="vm-badge bg-green-500/10 text-green-500 border-green-500/20">
{runningVMs} Running
</Badge>
<Badge variant="outline" className="vm-badge bg-red-500/10 text-red-500 border-red-500/20">
{stoppedVMs} Stopped
</Badge>
</div>
<p className="text-xs text-muted-foreground mt-2">Virtual machines configured</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Total CPU</CardTitle>
<Cpu className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">{(totalCPU * 100).toFixed(0)}%</div>
<p className="text-xs text-muted-foreground mt-2">Allocated CPU usage</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Total Memory</CardTitle>
<MemoryStick className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">{(totalMemory / 1024 ** 3).toFixed(1)} GB</div>
<p className="text-xs text-muted-foreground mt-2">Allocated RAM</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Average Load</CardTitle>
<Monitor className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">
{runningVMs > 0 ? ((totalCPU / runningVMs) * 100).toFixed(0) : 0}%
</div>
<p className="text-xs text-muted-foreground mt-2">Average resource utilization</p>
</CardContent>
</Card>
</div>
{/* Virtual Machines List */}
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Server className="h-5 w-5 mr-2" />
Virtual Machines
</CardTitle>
</CardHeader>
<CardContent>
{vmData.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">No virtual machines found</div>
) : (
<div className="space-y-4">
{vmData.map((vm) => {
const cpuPercent = (vm.cpu * 100).toFixed(1)
const memPercent = vm.maxmem > 0 ? ((vm.mem / vm.maxmem) * 100).toFixed(1) : "0"
const memGB = (vm.mem / 1024 ** 3).toFixed(1)
const maxMemGB = (vm.maxmem / 1024 ** 3).toFixed(1)
return (
<div key={vm.vmid} className="p-6 rounded-lg border border-border bg-card/50">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-4">
<Server className="h-6 w-6 text-muted-foreground" />
<div>
<div className="font-semibold text-foreground text-lg flex items-center">
{vm.name}
<Badge
variant="outline"
className="ml-2 text-xs bg-purple-500/10 text-purple-500 border-purple-500/20"
>
VM
</Badge>
</div>
<div className="text-sm text-muted-foreground">ID: {vm.vmid}</div>
</div>
</div>
<div className="flex items-center space-x-3">
<Badge variant="outline" className={getStatusColor(vm.status)}>
{getStatusIcon(vm.status)}
{vm.status.toUpperCase()}
</Badge>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div>
<div className="text-sm text-muted-foreground mb-2">CPU Usage</div>
<div className="text-lg font-semibold text-foreground mb-1">{cpuPercent}%</div>
<Progress value={Number.parseFloat(cpuPercent)} className="h-2" />
</div>
<div>
<div className="text-sm text-muted-foreground mb-2">Memory Usage</div>
<div className="text-lg font-semibold text-foreground mb-1">
{memGB} GB / {maxMemGB} GB
</div>
<Progress value={Number.parseFloat(memPercent)} className="h-2" />
</div>
<div>
<div className="text-sm text-muted-foreground mb-2">Uptime</div>
<div className="text-lg font-semibold text-foreground">{formatUptime(vm.uptime)}</div>
</div>
</div>
</div>
)
})}
</div>
)}
</CardContent>
</Card>
</div>
)
}

6
AppImage/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

30
AppImage/next.config.mjs Normal file
View File

@@ -0,0 +1,30 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
trailingSlash: true,
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
images: {
unoptimized: true,
},
experimental: {
esmExternals: 'loose',
},
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
net: false,
tls: false,
};
}
return config;
},
};
export default nextConfig;

74
AppImage/package.json Normal file
View File

@@ -0,0 +1,74 @@
{
"name": "proxmenux-monitor",
"version": "1.0.0",
"description": "Proxmox System Monitoring Dashboard",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"export": "next build"
},
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accordion": "1.2.2",
"@radix-ui/react-alert-dialog": "1.1.4",
"@radix-ui/react-aspect-ratio": "1.1.1",
"@radix-ui/react-avatar": "1.1.2",
"@radix-ui/react-checkbox": "1.1.3",
"@radix-ui/react-collapsible": "1.1.2",
"@radix-ui/react-context-menu": "2.2.4",
"@radix-ui/react-dialog": "1.1.4",
"@radix-ui/react-dropdown-menu": "2.1.4",
"@radix-ui/react-hover-card": "1.1.4",
"@radix-ui/react-label": "2.1.1",
"@radix-ui/react-menubar": "1.1.4",
"@radix-ui/react-navigation-menu": "1.2.3",
"@radix-ui/react-popover": "1.1.4",
"@radix-ui/react-progress": "1.1.1",
"@radix-ui/react-radio-group": "1.2.2",
"@radix-ui/react-scroll-area": "1.2.2",
"@radix-ui/react-select": "2.1.4",
"@radix-ui/react-separator": "1.1.1",
"@radix-ui/react-slider": "1.2.2",
"@radix-ui/react-slot": "1.1.1",
"@radix-ui/react-switch": "1.1.2",
"@radix-ui/react-tabs": "1.1.2",
"@radix-ui/react-toast": "1.2.4",
"@radix-ui/react-toggle": "1.1.1",
"@radix-ui/react-toggle-group": "1.1.1",
"@radix-ui/react-tooltip": "1.1.6",
"@vercel/analytics": "1.3.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
"date-fns": "4.1.0",
"embla-carousel-react": "8.5.1",
"geist": "^1.3.1",
"input-otp": "1.4.1",
"lucide-react": "^0.454.0",
"next": "15.1.6",
"next-themes": "^0.4.6",
"react": "^19",
"react-day-picker": "9.8.0",
"react-dom": "^19",
"react-hook-form": "^7.60.0",
"react-resizable-panels": "^2.1.7",
"recharts": "2.15.4",
"sonner": "^1.7.4",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.9",
"zod": "3.25.67"
},
"devDependencies": {
"@types/node": "^22",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.4.20",
"postcss": "^8.5",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}

View File

@@ -0,0 +1,9 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
export default config;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
AppImage/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,16 @@
{
"name": "ProxMenux Monitor",
"short_name": "ProxMenux",
"description": "Proxmox System Dashboard and Monitor",
"start_url": "/",
"display": "standalone",
"background_color": "#2b2f36",
"theme_color": "#2b2f36",
"icons": [
{
"src": "/images/proxmenux-logo.png",
"sizes": "256x256",
"type": "image/png"
}
]
}

45
AppImage/scripts/AppRun Normal file
View File

@@ -0,0 +1,45 @@
#!/bin/bash
# ProxMenux Monitor AppImage Entry Point
# This script is executed when the AppImage is run
# Get the directory where this AppImage is mounted
APPDIR="$(dirname "$(readlink -f "${0}")")"
export PATH="${APPDIR}/usr/bin:${PATH}"
export LD_LIBRARY_PATH="${APPDIR}/usr/lib:${LD_LIBRARY_PATH}"
export PYTHONPATH="${APPDIR}/usr/lib/python3/dist-packages:${APPDIR}/usr/lib/python3/site-packages:${PYTHONPATH}"
# Change to the AppImage directory
cd "${APPDIR}"
# Debug: Print directory structure for troubleshooting
echo "[v0] AppImage mounted at: ${APPDIR}"
echo "[v0] Contents of AppImage root:"
ls -la "${APPDIR}/" || echo "[v0] Cannot list AppImage root"
echo "[v0] Contents of web directory:"
ls -la "${APPDIR}/web/" || echo "[v0] Web directory not found"
echo "[v0] Looking for index.html:"
find "${APPDIR}" -name "index.html" -type f || echo "[v0] No index.html found"
echo "[v0] Python path: ${PYTHONPATH}"
echo "[v0] Checking Flask installation:"
python3 -c "import flask; print('Flask version:', flask.__version__)" 2>/dev/null || echo "[v0] Flask not found"
# Check for translation argument
if [[ "$1" == "--translate" ]]; then
echo "🌐 Starting ProxMenux Translation Service..."
exec python3 "${APPDIR}/usr/bin/translate_cli.py" "${@:2}"
else
echo "🚀 Starting ProxMenux Monitor Dashboard..."
echo "📊 Dashboard will be available at: http://localhost:8008"
echo "🔌 API endpoints at: http://localhost:8008/api/"
echo ""
echo "Press Ctrl+C to stop the server"
echo ""
# Start the Flask server
exec python3 "${APPDIR}/usr/bin/flask_server.py"
fi

View File

@@ -0,0 +1,338 @@
#!/bin/bash
# ProxMenux Monitor AppImage Builder
# This script creates a single AppImage with Flask server, Next.js dashboard, and translation support
set -e
WORK_DIR="/tmp/proxmenux_build"
APP_DIR="$WORK_DIR/ProxMenux.AppDir"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DIST_DIR="$SCRIPT_DIR/../dist"
APPIMAGE_ROOT="$SCRIPT_DIR/.."
VERSION=$(node -p "require('$APPIMAGE_ROOT/package.json').version")
APPIMAGE_NAME="ProxMenux-${VERSION}.AppImage"
echo "🚀 Building ProxMenux Monitor AppImage v${VERSION} with translation support..."
# Clean and create work directory
rm -rf "$WORK_DIR"
mkdir -p "$APP_DIR"
mkdir -p "$DIST_DIR"
# Download appimagetool if not exists
if [ ! -f "$WORK_DIR/appimagetool" ]; then
echo "📥 Downloading appimagetool..."
wget -q "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" -O "$WORK_DIR/appimagetool"
chmod +x "$WORK_DIR/appimagetool"
fi
# Create directory structure
mkdir -p "$APP_DIR/usr/bin"
mkdir -p "$APP_DIR/usr/lib/python3/dist-packages"
mkdir -p "$APP_DIR/usr/share/applications"
mkdir -p "$APP_DIR/usr/share/icons/hicolor/256x256/apps"
mkdir -p "$APP_DIR/web"
echo "🔨 Building Next.js application..."
cd "$APPIMAGE_ROOT"
if [ ! -f "package.json" ]; then
echo "❌ Error: package.json not found in AppImage directory"
exit 1
fi
# Install dependencies if node_modules doesn't exist
if [ ! -d "node_modules" ]; then
echo "📦 Installing dependencies..."
npm install
fi
echo "🏗️ Building Next.js static export..."
npm run export
echo "🔍 Checking export results..."
if [ -d "out" ]; then
echo "✅ Export directory found"
echo "📁 Contents of out directory:"
ls -la out/
if [ -f "out/index.html" ]; then
echo "✅ index.html found in out directory"
else
echo "❌ index.html NOT found in out directory"
echo "📁 Looking for HTML files:"
find out/ -name "*.html" -type f || echo "No HTML files found"
fi
else
echo "❌ Error: Next.js export failed - out directory not found"
echo "📁 Current directory contents:"
ls -la
echo "📁 Looking for any build outputs:"
find . -name "*.html" -type f 2>/dev/null || echo "No HTML files found anywhere"
exit 1
fi
# Return to script directory
cd "$SCRIPT_DIR"
# Copy Flask server
echo "📋 Copying Flask server..."
cp "$SCRIPT_DIR/flask_server.py" "$APP_DIR/usr/bin/"
echo "📋 Adding translation support..."
cat > "$APP_DIR/usr/bin/translate_cli.py" << 'PYEOF'
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ProxMenux translate CLI
stdin JSON -> {"text":"...", "dest_lang":"es", "context":"...", "cache_file":"/usr/local/share/proxmenux/cache.json"}
stdout JSON -> {"success":true,"text":"..."} or {"success":false,"error":"..."}
"""
import sys, json, re
from pathlib import Path
# Ensure embedded site-packages are discoverable
HERE = Path(__file__).resolve().parents[2] # .../AppDir
DIST = HERE / "usr" / "lib" / "python3" / "dist-packages"
SITE = HERE / "usr" / "lib" / "python3" / "site-packages"
for p in (str(DIST), str(SITE)):
if p not in sys.path:
sys.path.insert(0, p)
# Python 3.13 compat: inline 'cgi' shim
try:
import cgi
except Exception:
import types, html
def _parse_header(value: str):
value = str(value or "")
parts = [p.strip() for p in value.split(";")]
if not parts:
return "", {}
key = parts[0].lower()
params = {}
for item in parts[1:]:
if not item:
continue
if "=" in item:
k, v = item.split("=", 1)
k = k.strip().lower()
v = v.strip().strip('"').strip("'")
params[k] = v
else:
params[item.strip().lower()] = ""
return key, params
cgi = types.SimpleNamespace(parse_header=_parse_header, escape=html.escape)
try:
from googletrans import Translator
except Exception as e:
print(json.dumps({"success": False, "error": f"ImportError: {e}"}))
sys.exit(0)
def load_json_stdin():
try:
return json.load(sys.stdin)
except Exception as e:
print(json.dumps({"success": False, "error": f"Invalid JSON input: {e}"}))
sys.exit(0)
def ensure_cache(path: Path):
try:
path.parent.mkdir(parents=True, exist_ok=True)
if not path.exists():
path.write_text("{}", encoding="utf-8")
json.loads(path.read_text(encoding="utf-8") or "{}")
except Exception:
path.write_text("{}", encoding="utf-8")
def read_cache(path: Path):
try:
return json.loads(path.read_text(encoding="utf-8") or "{}")
except Exception:
return {}
def write_cache(path: Path, cache: dict):
tmp = path.with_suffix(".tmp")
tmp.write_text(json.dumps(cache, ensure_ascii=False), encoding="utf-8")
tmp.replace(path)
def clean_translated(s: str) -> str:
s = re.sub(r'^.*?(Translate:|Traducir:|Traduire:|Übersetzen:|Tradurre:|Traduzir:|翻译:|翻訳:)', '', s, flags=re.IGNORECASE | re.DOTALL).strip()
s = re.sub(r'^.*?(Context:|Contexto:|Contexte:|Kontext:|Contesto:|上下文:|コンテキスト:).*?:', '', s, flags=re.IGNORECASE | re.DOTALL).strip()
return s.strip()
def main():
req = load_json_stdin()
text = req.get("text", "")
dest = req.get("dest_lang", "en") or "en"
context = req.get("context", "")
cache_file = Path(req.get("cache_file", "")) if req.get("cache_file") else None
if dest == "en":
print(json.dumps({"success": True, "text": text}))
return
cache = {}
if cache_file:
ensure_cache(cache_file)
cache = read_cache(cache_file)
if text in cache and (dest in cache[text] or "notranslate" in cache[text]):
found = cache[text].get(dest) or cache[text].get("notranslate")
print(json.dumps({"success": True, "text": found}))
return
try:
full = (context + " " + text).strip() if context else text
tr = Translator()
result = tr.translate(full, dest=dest).text
result = clean_translated(result)
if cache_file:
cache.setdefault(text, {})
cache[text][dest] = result
write_cache(cache_file, cache)
print(json.dumps({"success": True, "text": result}))
except Exception as e:
print(json.dumps({"success": False, "error": str(e)}))
if __name__ == "__main__":
main()
PYEOF
chmod +x "$APP_DIR/usr/bin/translate_cli.py"
# Copy Next.js build
echo "📋 Copying web dashboard..."
if [ -d "$APPIMAGE_ROOT/out" ]; then
mkdir -p "$APP_DIR/web"
echo "📁 Copying from $APPIMAGE_ROOT/out to $APP_DIR/web"
cp -r "$APPIMAGE_ROOT/out"/* "$APP_DIR/web/"
if [ -f "$APP_DIR/web/index.html" ]; then
echo "✅ index.html copied successfully to $APP_DIR/web/"
else
echo "❌ index.html NOT found after copying"
echo "📁 Contents of $APP_DIR/web:"
ls -la "$APP_DIR/web/" || echo "Directory is empty or doesn't exist"
fi
if [ -d "$APPIMAGE_ROOT/public" ]; then
cp -r "$APPIMAGE_ROOT/public"/* "$APP_DIR/web/" 2>/dev/null || true
fi
cp "$APPIMAGE_ROOT/package.json" "$APP_DIR/web/"
echo "✅ Next.js static export copied successfully"
else
echo "❌ Error: Next.js export not found even after building"
exit 1
fi
# Copy AppRun script
echo "📋 Copying AppRun script..."
if [ -f "$SCRIPT_DIR/AppRun" ]; then
cp "$SCRIPT_DIR/AppRun" "$APP_DIR/AppRun"
chmod +x "$APP_DIR/AppRun"
echo "✅ AppRun script copied successfully"
else
echo "❌ Error: AppRun script not found at $SCRIPT_DIR/AppRun"
exit 1
fi
# Create desktop file
cat > "$APP_DIR/proxmenux-monitor.desktop" << EOF
[Desktop Entry]
Type=Application
Name=ProxMenux Monitor
Comment=Proxmox System Monitoring Dashboard with Translation Support
Exec=AppRun
Icon=proxmenux-monitor
Categories=System;Monitor;
Terminal=false
StartupNotify=true
EOF
# Copy desktop file to applications directory
cp "$APP_DIR/proxmenux-monitor.desktop" "$APP_DIR/usr/share/applications/"
# Download and set icon
echo "🎨 Setting up icon..."
if [ -f "$APPIMAGE_ROOT/public/images/proxmenux-logo.png" ]; then
cp "$APPIMAGE_ROOT/public/images/proxmenux-logo.png" "$APP_DIR/proxmenux-monitor.png"
else
wget -q "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/images/logo.png" -O "$APP_DIR/proxmenux-monitor.png" || {
echo "⚠️ Could not download logo, creating placeholder..."
convert -size 256x256 xc:blue -fill white -gravity center -pointsize 24 -annotate +0+0 "PM" "$APP_DIR/proxmenux-monitor.png" 2>/dev/null || {
echo "⚠️ ImageMagick not available, skipping icon creation"
}
}
fi
if [ -f "$APP_DIR/proxmenux-monitor.png" ]; then
cp "$APP_DIR/proxmenux-monitor.png" "$APP_DIR/usr/share/icons/hicolor/256x256/apps/"
fi
echo "📦 Installing Python dependencies..."
pip3 install --target "$APP_DIR/usr/lib/python3/dist-packages" \
flask \
flask-cors \
psutil \
requests \
googletrans==4.0.0-rc1 \
httpx==0.13.3 \
httpcore==0.9.1 \
beautifulsoup4
cat > "$APP_DIR/usr/lib/python3/dist-packages/cgi.py" << 'PYEOF'
from typing import Tuple, Dict
try:
from html import escape as _html_escape
except Exception:
def _html_escape(s, quote=True): return s
__all__ = ["parse_header", "escape"]
def escape(s, quote=True):
return _html_escape(s, quote=quote)
def parse_header(value: str) -> Tuple[str, Dict[str, str]]:
if not isinstance(value, str):
value = str(value or "")
parts = [p.strip() for p in value.split(";")]
if not parts:
return "", {}
key = parts[0].lower()
params: Dict[str, str] = {}
for item in parts[1:]:
if not item:
continue
if "=" in item:
k, v = item.split("=", 1)
k = k.strip().lower()
v = v.strip().strip('"').strip("'")
params[k] = v
else:
params[item.strip().lower()] = ""
return key, params
PYEOF
# Build AppImage
echo "🔨 Building unified AppImage v${VERSION}..."
cd "$WORK_DIR"
export NO_CLEANUP=1
export APPIMAGE_EXTRACT_AND_RUN=1
ARCH=x86_64 ./appimagetool --no-appstream --verbose "$APP_DIR" "$APPIMAGE_NAME"
# Move to dist directory
mv "$APPIMAGE_NAME" "$DIST_DIR/"
echo "✅ Unified AppImage created: $DIST_DIR/$APPIMAGE_NAME"
echo ""
echo "📋 Usage:"
echo " Dashboard: ./$APPIMAGE_NAME"
echo " Translation: ./$APPIMAGE_NAME --translate"
echo ""
echo "🚀 Installation:"
echo " sudo cp $DIST_DIR/$APPIMAGE_NAME /usr/local/bin/proxmenux-monitor"
echo " sudo chmod +x /usr/local/bin/proxmenux-monitor"

View File

@@ -0,0 +1,582 @@
#!/usr/bin/env python3
"""
ProxMenux Flask Server
Provides REST API endpoints for Proxmox monitoring data
Runs on port 8008 and serves system metrics, storage info, network stats, etc.
Also serves the Next.js dashboard as static files
"""
from flask import Flask, jsonify, request, send_from_directory, send_file
from flask_cors import CORS
import psutil
import subprocess
import json
import os
import time
import socket
from datetime import datetime, timedelta
app = Flask(__name__)
CORS(app) # Enable CORS for Next.js frontend
@app.route('/')
def serve_dashboard():
"""Serve the main dashboard page from Next.js build"""
try:
# Detectar si estamos ejecutándose desde AppImage
appimage_root = os.environ.get('APPDIR')
if not appimage_root:
# Fallback: intentar detectar desde la ubicación del script
base_dir = os.path.dirname(os.path.abspath(__file__))
appimage_root = os.path.dirname(base_dir) # Subir un nivel desde usr/bin/
index_paths = [
os.path.join(appimage_root, 'web', 'index.html'), # Ruta principal para AppImage
os.path.join(appimage_root, 'usr', 'web', 'index.html'), # Fallback con usr/
os.path.join(appimage_root, 'web', 'out', 'index.html'), # Fallback si está en subcarpeta
os.path.join(appimage_root, 'usr', 'web', 'out', 'index.html'), # Fallback con usr/out/
]
print(f"[v0] Flask server looking for index.html in:")
for path in index_paths:
abs_path = os.path.abspath(path)
exists = os.path.exists(abs_path)
print(f"[v0] {abs_path} - {'EXISTS' if exists else 'NOT FOUND'}")
if exists:
print(f"[v0] Found index.html, serving from: {abs_path}")
return send_file(abs_path)
# If no Next.js build found, return error message with actual paths checked
actual_paths = [os.path.abspath(path) for path in index_paths]
return f'''
<!DOCTYPE html>
<html>
<head><title>ProxMenux Monitor - Build Error</title></head>
<body style="font-family: Arial; padding: 2rem; background: #0a0a0a; color: #fff;">
<h1>🚨 ProxMenux Monitor - Build Error</h1>
<p>Next.js application not found. The AppImage may not have been built correctly.</p>
<p>Expected paths checked:</p>
<ul>{''.join([f'<li>{path}</li>' for path in actual_paths])}</ul>
<p>API endpoints are still available:</p>
<ul>
<li><a href="/api/system" style="color: #4f46e5;">/api/system</a></li>
<li><a href="/api/system-info" style="color: #4f46e5;">/api/system-info</a></li>
<li><a href="/api/storage" style="color: #4f46e5;">/api/storage</a></li>
<li><a href="/api/network" style="color: #4f46e5;">/api/network</a></li>
<li><a href="/api/vms" style="color: #4f46e5;">/api/vms</a></li>
<li><a href="/api/health" style="color: #4f46e5;">/api/health</a></li>
</ul>
</body>
</html>
''', 500
except Exception as e:
print(f"Error serving dashboard: {e}")
return jsonify({'error': f'Dashboard not available: {str(e)}'}), 500
@app.route('/manifest.json')
def serve_manifest():
"""Serve PWA manifest"""
try:
manifest_paths = [
os.path.join(os.path.dirname(__file__), '..', 'web', 'public', 'manifest.json'),
os.path.join(os.path.dirname(__file__), '..', 'public', 'manifest.json')
]
for manifest_path in manifest_paths:
if os.path.exists(manifest_path):
return send_file(manifest_path)
# Return default manifest if not found
return jsonify({
"name": "ProxMenux Monitor",
"short_name": "ProxMenux",
"description": "Proxmox System Monitoring Dashboard",
"start_url": "/",
"display": "standalone",
"background_color": "#0a0a0a",
"theme_color": "#4f46e5",
"icons": [
{
"src": "/images/proxmenux-logo.png",
"sizes": "256x256",
"type": "image/png"
}
]
})
except Exception as e:
print(f"Error serving manifest: {e}")
return jsonify({}), 404
@app.route('/sw.js')
def serve_sw():
"""Serve service worker"""
return '''
const CACHE_NAME = 'proxmenux-v1';
const urlsToCache = [
'/',
'/api/system',
'/api/storage',
'/api/network',
'/api/health'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});
''', 200, {'Content-Type': 'application/javascript'}
@app.route('/_next/<path:filename>')
def serve_next_static(filename):
"""Serve Next.js static files"""
try:
appimage_root = os.environ.get('APPDIR')
if not appimage_root:
base_dir = os.path.dirname(os.path.abspath(__file__))
appimage_root = os.path.dirname(base_dir)
static_paths = [
os.path.join(appimage_root, 'web', '_next'), # Ruta principal
os.path.join(appimage_root, 'usr', 'web', '_next'), # Fallback con usr/
os.path.join(appimage_root, 'web', 'out', '_next'), # Fallback con out/
os.path.join(appimage_root, 'usr', 'web', 'out', '_next'), # Fallback con usr/out/
]
for static_dir in static_paths:
file_path = os.path.join(static_dir, filename)
if os.path.exists(file_path):
return send_file(file_path)
return '', 404
except Exception as e:
print(f"Error serving Next.js static file {filename}: {e}")
return '', 404
@app.route('/<path:filename>')
def serve_static_files(filename):
"""Serve static files (icons, etc.)"""
try:
appimage_root = os.environ.get('APPDIR')
if not appimage_root:
base_dir = os.path.dirname(os.path.abspath(__file__))
appimage_root = os.path.dirname(base_dir)
public_paths = [
os.path.join(appimage_root, 'web'), # Raíz web para exportación estática
os.path.join(appimage_root, 'usr', 'web'), # Fallback con usr/
os.path.join(appimage_root, 'web', 'out'), # Fallback con out/
os.path.join(appimage_root, 'usr', 'web', 'out'), # Fallback con usr/out/
]
for public_dir in public_paths:
file_path = os.path.join(public_dir, filename)
if os.path.exists(file_path):
return send_from_directory(public_dir, filename)
return '', 404
except Exception as e:
print(f"Error serving static file {filename}: {e}")
return '', 404
@app.route('/images/<path:filename>')
def serve_images(filename):
"""Serve image files"""
try:
appimage_root = os.environ.get('APPDIR')
if not appimage_root:
base_dir = os.path.dirname(os.path.abspath(__file__))
appimage_root = os.path.dirname(base_dir)
image_paths = [
os.path.join(appimage_root, 'web', 'images'), # Ruta principal para exportación estática
os.path.join(appimage_root, 'usr', 'web', 'images'), # Fallback con usr/
os.path.join(appimage_root, 'web', 'public', 'images'), # Ruta con public/
os.path.join(appimage_root, 'usr', 'web', 'public', 'images'), # Fallback usr/public/
os.path.join(appimage_root, 'public', 'images'), # Ruta directa a public
os.path.join(appimage_root, 'usr', 'public', 'images'), # Fallback usr/public
]
print(f"[v0] Looking for image: {filename}")
for image_dir in image_paths:
file_path = os.path.join(image_dir, filename)
abs_path = os.path.abspath(file_path)
exists = os.path.exists(abs_path)
print(f"[v0] Checking: {abs_path} - {'FOUND' if exists else 'NOT FOUND'}")
if exists:
print(f"[v0] Serving image from: {abs_path}")
return send_from_directory(image_dir, filename)
print(f"[v0] Image not found: {filename}")
return '', 404
except Exception as e:
print(f"Error serving image {filename}: {e}")
return '', 404
def get_system_info():
"""Get basic system information"""
try:
# CPU usage
cpu_percent = psutil.cpu_percent(interval=1)
# Memory usage
memory = psutil.virtual_memory()
temp = 0
try:
if hasattr(psutil, "sensors_temperatures"):
temps = psutil.sensors_temperatures()
if temps:
# Priority order for temperature sensors
sensor_priority = ['coretemp', 'cpu_thermal', 'acpi', 'thermal_zone']
for sensor_name in sensor_priority:
if sensor_name in temps and temps[sensor_name]:
temp = temps[sensor_name][0].current
break
# If no priority sensor found, use first available
if temp == 0:
for name, entries in temps.items():
if entries:
temp = entries[0].current
break
except Exception as e:
print(f"Error reading temperature sensors: {e}")
temp = 0 # Use 0 to indicate no temperature available
# Uptime
boot_time = psutil.boot_time()
uptime_seconds = time.time() - boot_time
uptime_str = str(timedelta(seconds=int(uptime_seconds)))
# Load average
load_avg = os.getloadavg() if hasattr(os, 'getloadavg') else [0, 0, 0]
hostname = socket.gethostname()
node_id = f"pve-{hostname}"
# Try to get Proxmox node info if available
try:
result = subprocess.run(['pvesh', 'get', '/nodes', '--output-format', 'json'],
capture_output=True, text=True, timeout=5)
if result.returncode == 0:
nodes = json.loads(result.stdout)
if nodes and len(nodes) > 0:
node_id = nodes[0].get('node', node_id)
except Exception as e:
print(f"Note: pvesh not available or failed: {e}")
pass # Use default if pvesh not available
return {
'cpu_usage': round(cpu_percent, 1),
'memory_usage': round(memory.percent, 1),
'memory_total': round(memory.total / (1024**3), 1), # GB
'memory_used': round(memory.used / (1024**3), 1), # GB
'temperature': temp,
'uptime': uptime_str,
'load_average': list(load_avg),
'hostname': hostname,
'node_id': node_id,
'timestamp': datetime.now().isoformat()
}
except Exception as e:
print(f"Critical error getting system info: {e}")
return {
'error': f'Unable to access system information: {str(e)}',
'timestamp': datetime.now().isoformat()
}
def get_storage_info():
"""Get storage and disk information"""
try:
storage_data = {
'total': 0,
'used': 0,
'available': 0,
'disks': []
}
# Get disk usage for root partition
disk_usage = psutil.disk_usage('/')
storage_data['total'] = round(disk_usage.total / (1024**3), 1) # GB
storage_data['used'] = round(disk_usage.used / (1024**3), 1) # GB
storage_data['available'] = round(disk_usage.free / (1024**3), 1) # GB
# Get individual disk information
disk_partitions = psutil.disk_partitions()
for partition in disk_partitions:
try:
partition_usage = psutil.disk_usage(partition.mountpoint)
disk_temp = 0
try:
# Try to get disk temperature from sensors
if hasattr(psutil, "sensors_temperatures"):
temps = psutil.sensors_temperatures()
if temps:
for name, entries in temps.items():
if 'disk' in name.lower() or 'hdd' in name.lower() or 'sda' in name.lower():
if entries:
disk_temp = entries[0].current
break
except:
pass
disk_info = {
'name': partition.device,
'mountpoint': partition.mountpoint,
'fstype': partition.fstype,
'total': round(partition_usage.total / (1024**3), 1),
'used': round(partition_usage.used / (1024**3), 1),
'available': round(partition_usage.free / (1024**3), 1),
'usage_percent': round((partition_usage.used / partition_usage.total) * 100, 1),
'health': 'unknown', # Would need SMART data for real health
'temperature': disk_temp
}
storage_data['disks'].append(disk_info)
except PermissionError:
print(f"Permission denied accessing {partition.mountpoint}")
continue
except Exception as e:
print(f"Error accessing partition {partition.device}: {e}")
continue
if not storage_data['disks'] and storage_data['total'] == 0:
return {
'error': 'No storage data available - unable to access disk information',
'total': 0,
'used': 0,
'available': 0,
'disks': []
}
return storage_data
except Exception as e:
print(f"Error getting storage info: {e}")
return {
'error': f'Unable to access storage information: {str(e)}',
'total': 0,
'used': 0,
'available': 0,
'disks': []
}
def get_network_info():
"""Get network interface information"""
try:
network_data = {
'interfaces': [],
'traffic': {'incoming': 0, 'outgoing': 0}
}
# Get network interfaces
net_if_addrs = psutil.net_if_addrs()
net_if_stats = psutil.net_if_stats()
for interface_name, interface_addresses in net_if_addrs.items():
if interface_name == 'lo': # Skip loopback
continue
interface_info = {
'name': interface_name,
'status': 'up' if net_if_stats[interface_name].isup else 'down',
'addresses': []
}
for address in interface_addresses:
if address.family == 2: # IPv4
interface_info['addresses'].append({
'ip': address.address,
'netmask': address.netmask
})
network_data['interfaces'].append(interface_info)
# Get network I/O statistics
net_io = psutil.net_io_counters()
network_data['traffic'] = {
'bytes_sent': net_io.bytes_sent,
'bytes_recv': net_io.bytes_recv,
'packets_sent': net_io.packets_sent,
'packets_recv': net_io.packets_recv
}
return network_data
except Exception as e:
print(f"Error getting network info: {e}")
return {
'error': f'Unable to access network information: {str(e)}',
'interfaces': [],
'traffic': {'bytes_sent': 0, 'bytes_recv': 0, 'packets_sent': 0, 'packets_recv': 0}
}
def get_proxmox_vms():
"""Get Proxmox VM information (requires pvesh command)"""
try:
# Try to get VM list using pvesh command
result = subprocess.run(['pvesh', 'get', '/nodes/localhost/qemu', '--output-format', 'json'],
capture_output=True, text=True, timeout=10)
if result.returncode == 0:
vms = json.loads(result.stdout)
return vms
else:
return {
'error': 'pvesh command not available or failed - Proxmox API not accessible',
'vms': []
}
except Exception as e:
print(f"Error getting VM info: {e}")
return {
'error': f'Unable to access VM information: {str(e)}',
'vms': []
}
@app.route('/api/system', methods=['GET'])
def api_system():
"""Get system information"""
return jsonify(get_system_info())
@app.route('/api/storage', methods=['GET'])
def api_storage():
"""Get storage information"""
return jsonify(get_storage_info())
@app.route('/api/network', methods=['GET'])
def api_network():
"""Get network information"""
return jsonify(get_network_info())
@app.route('/api/vms', methods=['GET'])
def api_vms():
"""Get virtual machine information"""
return jsonify(get_proxmox_vms())
@app.route('/api/logs', methods=['GET'])
def api_logs():
"""Get system logs"""
try:
# Get recent system logs
result = subprocess.run(['journalctl', '-n', '100', '--output', 'json'],
capture_output=True, text=True, timeout=10)
if result.returncode == 0:
logs = []
for line in result.stdout.strip().split('\n'):
if line:
try:
log_entry = json.loads(line)
logs.append({
'timestamp': log_entry.get('__REALTIME_TIMESTAMP', ''),
'level': log_entry.get('PRIORITY', '6'),
'service': log_entry.get('_SYSTEMD_UNIT', 'system'),
'message': log_entry.get('MESSAGE', ''),
'source': 'journalctl'
})
except json.JSONDecodeError:
continue
return jsonify(logs)
else:
return jsonify({
'error': 'journalctl not available or failed',
'logs': []
})
except Exception as e:
print(f"Error getting logs: {e}")
return jsonify({
'error': f'Unable to access system logs: {str(e)}',
'logs': []
})
@app.route('/api/health', methods=['GET'])
def api_health():
"""Health check endpoint"""
return jsonify({
'status': 'healthy',
'timestamp': datetime.now().isoformat(),
'version': '1.0.0'
})
@app.route('/api/system-info', methods=['GET'])
def api_system_info():
"""Get system and node information for dashboard header"""
try:
hostname = socket.gethostname()
node_id = f"pve-{hostname}"
pve_version = None
# Try to get Proxmox version
try:
result = subprocess.run(['pveversion'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
pve_version = result.stdout.strip().split('\n')[0]
except:
pass
# Try to get node info from Proxmox API
try:
result = subprocess.run(['pvesh', 'get', '/nodes', '--output-format', 'json'],
capture_output=True, text=True, timeout=5)
if result.returncode == 0:
nodes = json.loads(result.stdout)
if nodes and len(nodes) > 0:
node_info = nodes[0]
node_id = node_info.get('node', node_id)
hostname = node_info.get('node', hostname)
except:
pass
response = {
'hostname': hostname,
'node_id': node_id,
'status': 'online',
'timestamp': datetime.now().isoformat()
}
if pve_version:
response['pve_version'] = pve_version
else:
response['error'] = 'Proxmox version not available - pveversion command not found'
return jsonify(response)
except Exception as e:
print(f"Error getting system info: {e}")
return jsonify({
'error': f'Unable to access system information: {str(e)}',
'hostname': socket.gethostname(),
'status': 'error',
'timestamp': datetime.now().isoformat()
})
@app.route('/api/info', methods=['GET'])
def api_info():
"""Root endpoint with API information"""
return jsonify({
'name': 'ProxMenux Monitor API',
'version': '1.0.0',
'endpoints': [
'/api/system',
'/api/system-info',
'/api/storage',
'/api/network',
'/api/vms',
'/api/logs',
'/api/health'
]
})
if __name__ == '__main__':
print("Starting ProxMenux Flask Server on port 8008...")
print("Server will be accessible on all network interfaces (0.0.0.0:8008)")
print("API endpoints available at: /api/system, /api/storage, /api/network, /api/vms, /api/logs, /api/health")
app.run(host='0.0.0.0', port=8008, debug=False)

View File

@@ -0,0 +1,89 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: ["./pages/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}"],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
chart: {
1: "hsl(var(--chart-1))",
2: "hsl(var(--chart-2))",
3: "hsl(var(--chart-3))",
4: "hsl(var(--chart-4))",
5: "hsl(var(--chart-5))",
},
sidebar: {
DEFAULT: "hsl(var(--sidebar-background))",
foreground: "hsl(var(--sidebar-foreground))",
primary: "hsl(var(--sidebar-primary))",
"primary-foreground": "hsl(var(--sidebar-primary-foreground))",
accent: "hsl(var(--sidebar-accent))",
"accent-foreground": "hsl(var(--sidebar-accent-foreground))",
border: "hsl(var(--sidebar-border))",
ring: "hsl(var(--sidebar-ring))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
}

27
AppImage/tsconfig.json Normal file
View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "es6"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"baseUrl": ".",
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

View File

@@ -4,22 +4,23 @@
![Shared Resources Menu](https://macrimi.github.io/ProxMenux/share/main-menu.png) ![Shared Resources Menu](https://macrimi.github.io/ProxMenux/share/main-menu.png)
### Added ### Added
- **New Menu: Mount and Share Manager** - **New Menu: Mount and Share Manager**
Introduced a comprehensive new menu for managing shared resources between Proxmox host and LXC containers: Introduced a comprehensive new menu for managing shared resources between Proxmox host and LXC containers:
**Host Configuration Options:** **Host Configuration Options:**
1. **Configure NFS Shared on Host** - Add, view, and remove NFS shared resources on the Proxmox server with automatic export management - **Configure NFS Shared on Host** - Add, view, and remove NFS shared resources on the Proxmox server with automatic export management
2. **Configure Samba Shared on Host** - Add, view, and remove Samba/CIFS shared resources on the Proxmox server with share configuration - **Configure Samba Shared on Host** - Add, view, and remove Samba/CIFS shared resources on the Proxmox server with share configuration
3. **Configure Local Shared on Host** - Create and manage local shared directories with proper permissions on the Proxmox host - **Configure Local Shared on Host** - Create and manage local shared directories with proper permissions on the Proxmox host
**LXC Integration Options:** **LXC Integration Options:**
4. **Configure LXC Mount Points (Host ↔ Container)** - **Core feature** that enables mounting host directories into LXC containers with automatic permission handling. Includes the ability to **view existing mount points** for each container in a clear, organized way and **remove mount points** with proper verification that the process completed successfully. Especially optimized for **unprivileged containers** where UID/GID mapping is critical. - **Configure LXC Mount Points (Host ↔ Container)** - **Core feature** that enables mounting host directories into LXC containers with automatic permission handling. Includes the ability to **view existing mount points** for each container in a clear, organized way and **remove mount points** with proper verification that the process completed successfully. Especially optimized for **unprivileged containers** where UID/GID mapping is critical.
5. **Configure NFS Client in LXC** - Set up NFS client inside privileged containers - **Configure NFS Client in LXC** - Set up NFS client inside privileged containers
6. **Configure Samba Client in LXC** - Set up Samba client inside privileged containers - **Configure Samba Client in LXC** - Set up Samba client inside privileged containers
7. **Configure NFS Server in LXC** - Install NFS server inside privileged containers - **Configure NFS Server in LXC** - Install NFS server inside privileged containers
8. **Configure Samba Server in LXC** - Install Samba server inside privileged containers - **Configure Samba Server in LXC** - Install Samba server inside privileged containers
**Documentation & Support:** **Documentation & Support:**
- **Help & Info (commands)** - Comprehensive guides with step-by-step manual instructions for all sharing scenarios - **Help & Info (commands)** - Comprehensive guides with step-by-step manual instructions for all sharing scenarios

View File

@@ -1,6 +1,6 @@
<div align="center"> <div align="center">
<img src="https://github.com/MacRimi/ProxMenux/blob/main/images/main.png" <img src="https://github.com/MacRimi/ProxMenux/blob/main/images/main.png"
alt="ProxMenu Logo" alt="ProxMenux Logo"
style="max-width: 100%; height: auto;" > style="max-width: 100%; height: auto;" >
</div> </div>
@@ -70,6 +70,12 @@ Then, follow the on-screen options to manage your Proxmox server efficiently.
## ⭐ Support the Project! ## ⭐ Support the Project!
If you find **ProxMenux** useful, consider giving it a ⭐ on GitHub to help others discover it! If you find **ProxMenux** useful, consider giving it a ⭐ on GitHub to help others discover it!
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=MacRimi/ProxMenux&type=Date)](https://www.star-history.com/#MacRimi/ProxMenux&Date)
<div style="display: flex; justify-content: center; align-items: center;"> <div style="display: flex; justify-content: center; align-items: center;">
<a href="https://ko-fi.com/G2G313ECAN" target="_blank" style="display: flex; align-items: center; text-decoration: none;"> <a href="https://ko-fi.com/G2G313ECAN" target="_blank" style="display: flex; align-items: center; text-decoration: none;">
<img src="https://raw.githubusercontent.com/MacRimi/HWEncoderX/main/images/kofi.png" alt="Support me on Ko-fi" style="width:140px; margin-right:40px;"/> <img src="https://raw.githubusercontent.com/MacRimi/HWEncoderX/main/images/kofi.png" alt="Support me on Ko-fi" style="width:140px; margin-right:40px;"/>
@@ -77,5 +83,3 @@ If you find **ProxMenux** useful, consider giving it a ⭐ on GitHub to help oth
</div> </div>
Support the project on Ko-fi! Support the project on Ko-fi!

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi
@@ -101,7 +101,7 @@ check_existing_installation() {
fi fi
} }
uninstall_proxmenu() { uninstall_proxmenux() {
local install_type="$1" local install_type="$1"
local force_clean="$2" local force_clean="$2"
@@ -168,7 +168,7 @@ handle_installation_change() {
if whiptail --title "Installation Type Change" \ if whiptail --title "Installation Type Change" \
--yesno "Switch from Translation to Normal Version?\n\nThis will remove translation components." 10 60; then --yesno "Switch from Translation to Normal Version?\n\nThis will remove translation components." 10 60; then
echo "Preparing for installation type change..." echo "Preparing for installation type change..."
uninstall_proxmenu "translation" "force" >/dev/null 2>&1 uninstall_proxmenux "translation" "force" >/dev/null 2>&1
return 0 return 0
else else
return 1 return 1

View File

@@ -202,7 +202,7 @@
"it": "Giapponese", "it": "Giapponese",
"pt": "Japonês" "pt": "Japonês"
}, },
"Thank you for using ProxMenu. Goodbye!": { "Thank you for using ProxMenux. Goodbye!": {
"es": "Gracias por usar ProxMenu. ¡Adiós!", "es": "Gracias por usar ProxMenu. ¡Adiós!",
"fr": "Merci d'avoir utilisé ProxMenu. Au revoir!", "fr": "Merci d'avoir utilisé ProxMenu. Au revoir!",
"de": "Danke für die Nutzung von ProxMenu. Auf Wiedersehen!", "de": "Danke für die Nutzung von ProxMenu. Auf Wiedersehen!",
@@ -900,7 +900,11 @@
"it": "Trova il tuo dispositivo usando https://finds.synology.com" "it": "Trova il tuo dispositivo usando https://finds.synology.com"
}, },
"Help and Info Commands": { "Help and Info Commands": {
"es": "Comandos de ayuda e información" "es": "Comandos de ayuda e información",
"fr": "Aide et Informations (commandes)",
"de": "Hilfe & Informationen (Befehle)",
"it": "Aiuto e Informazioni (comandi)",
"pt": "Ajuda e Informações (comandos)"
}, },
"Create VM from template or script": { "Create VM from template or script": {
"es": "Crear VM a partir de plantilla o script", "es": "Crear VM a partir de plantilla o script",
@@ -2566,5 +2570,103 @@
"de": "Notfallwiederherstellung:", "de": "Notfallwiederherstellung:",
"it": "Ripristino di emergenza:", "it": "Ripristino di emergenza:",
"pt": "Recuperação de emergência:" "pt": "Recuperação de emergência:"
},
"Mount and Share Manager": {
"es": "Montajes y Recursos Compartidos",
"fr": "Gestionnaire de Montage et Partage",
"de": "Mount- und Share-Manager",
"it": "Gestore di Mount e Condivisioni",
"pt": "Gerenciador de Montagem e Compartilhamento"
},
"HOST": {
"es": "HOST",
"fr": "HÔTE",
"de": "HOST",
"it": "HOST",
"pt": "HOST"
},
"Configure NFS shared on Host": {
"es": "Configurar recursos NFS compartidos en el Host",
"fr": "Configurer les partages NFS sur l'hôte",
"de": "NFS-Freigaben auf Host konfigurieren",
"it": "Configurare condivisioni NFS su Host",
"pt": "Configurar compartilhamentos NFS no Host"
},
"Configure Samba shared on Host": {
"es": "Configurar recursos Samba compartidos en el Host",
"fr": "Configurer les partages Samba sur l'hôte",
"de": "Samba-Freigaben auf Host konfigurieren",
"it": "Configurare condivisioni Samba su Host",
"pt": "Configurar compartilhamentos Samba no Host"
},
"Configure Local Shared on Host": {
"es": "Configurar directorios locales compartidos en el Host",
"fr": "Configurer les répertoires locaux partagés sur l'hôte",
"de": "Lokale geteilte Verzeichnisse auf Host konfigurieren",
"it": "Configurare directory locali condivise su Host",
"pt": "Configurar diretórios locais compartilhados no Host"
},
"LXC": {
"es": "LXC",
"fr": "LXC",
"de": "LXC",
"it": "LXC",
"pt": "LXC"
},
"Configure LXC Mount Points (Host ↔ Container)": {
"es": "Configurar puntos de montaje LXC (Host ↔ LXC)",
"fr": "Configurer les points de montage LXC (Hôte ↔ LXC)",
"de": "LXC-Mount-Punkte konfigurieren (Host ↔ LXC)",
"it": "Configurare punti di mount LXC (Host ↔ LXC)",
"pt": "Configurar pontos de montagem LXC (Host ↔ LXC)"
},
"Configure NFS Client in LXC (only privileged)": {
"es": "Configurar cliente NFS en LXC (solo privilegiados)",
"fr": "Configurer le client NFS dans LXC (privilégiés uniquement)",
"de": "NFS-Client in LXC konfigurieren (nur privilegiert)",
"it": "Configurare client NFS in LXC (solo privilegiati)",
"pt": "Configurar cliente NFS em LXC (apenas privilegiados)"
},
"Configure Samba Client in LXC (only privileged)": {
"es": "Configurar cliente Samba en LXC (solo privilegiados)",
"fr": "Configurer le client Samba dans LXC (privilégiés uniquement)",
"de": "Samba-Client in LXC konfigurieren (nur privilegiert)",
"it": "Configurare client Samba in LXC (solo privilegiati)",
"pt": "Configurar cliente Samba em LXC (apenas privilegiados)"
},
"Configure NFS Server in LXC (only privileged)": {
"es": "Configurar servidor NFS en LXC (solo privilegiados)",
"fr": "Configurer le serveur NFS dans LXC (privilégiés uniquement)",
"de": "NFS-Server in LXC konfigurieren (nur privilegiert)",
"it": "Configurare server NFS in LXC (solo privilegiati)",
"pt": "Configurar servidor NFS em LXC (apenas privilegiados)"
},
"configure Samba Server in LXC (only privileged)": {
"es": "Configurar servidor Samba en LXC (solo privilegiados)",
"fr": "Configurer le serveur Samba dans LXC (privilégiés uniquement)",
"de": "Samba-Server in LXC konfigurieren (nur privilegiert)",
"it": "Configurare server Samba in LXC (solo privilegiati)",
"pt": "Configurar servidor Samba em LXC (apenas privilegiados)"
},
"Help & Info (commands)": {
"es": "Comandos de ayuda e información",
"fr": "Aide et Informations (commandes)",
"de": "Hilfe & Informationen (Befehle)",
"it": "Aiuto e Informazioni (comandi)",
"pt": "Ajuda e Informações (comandos)"
},
"English": {
"es": "Inglés",
"fr": "Anglais",
"de": "Englisch",
"it": "Inglese",
"pt": "Inglês"
},
"Language Change": {
"es": "Cambio de Idioma",
"fr": "Changement de Langue",
"de": "Sprachänderung",
"it": "Cambio Lingua",
"pt": "Mudança de Idioma"
} }
} }

View File

@@ -27,7 +27,7 @@
"type": "ct" "type": "ct"
}, },
{ {
"name": "Proxmox VE LXC Tag", "name": "PVE LXC Tag",
"slug": "add-iptag", "slug": "add-iptag",
"desc": "This script automatically adds IP address as tags to LXC containers or VM's using a systemd service. The service also updates the tags if a LXC/VM IP address is changed.", "desc": "This script automatically adds IP address as tags to LXC containers or VM's using a systemd service. The service also updates the tags if a LXC/VM IP address is changed.",
"script": "tools/pve/add-iptag.sh", "script": "tools/pve/add-iptag.sh",
@@ -84,7 +84,7 @@
5 5
], ],
"notes": [ "notes": [
"Adguard Home can be updated via the user interface." "AdGuard Home can only be updated via the user interface."
], ],
"type": "ct" "type": "ct"
}, },
@@ -635,7 +635,7 @@
"type": "ct" "type": "ct"
}, },
{ {
"name": "Proxmox VE LXC Cleaner", "name": "PVE LXC Cleaner",
"slug": "clean-lxcs", "slug": "clean-lxcs",
"desc": "This script provides options to delete logs and cache, and repopulate apt lists for Ubuntu and Debian systems.", "desc": "This script provides options to delete logs and cache, and repopulate apt lists for Ubuntu and Debian systems.",
"script": "tools/pve/clean-lxcs.sh", "script": "tools/pve/clean-lxcs.sh",
@@ -649,7 +649,7 @@
"type": "pve" "type": "pve"
}, },
{ {
"name": "Proxmox Clean Orphaned LVM", "name": "PVE Clean Orphaned LVM",
"slug": "clean-orphaned-lvm", "slug": "clean-orphaned-lvm",
"desc": "This script helps Proxmox users identify and remove orphaned LVM volumes that are no longer associated with any VM or LXC container. It scans all LVM volumes, detects unused ones, and provides an interactive prompt to delete them safely. System-critical volumes like root, swap, and data are excluded to prevent accidental deletion.", "desc": "This script helps Proxmox users identify and remove orphaned LVM volumes that are no longer associated with any VM or LXC container. It scans all LVM volumes, detects unused ones, and provides an interactive prompt to delete them safely. System-critical volumes like root, swap, and data are excluded to prevent accidental deletion.",
"script": "tools/pve/clean-orphaned-lvm.sh", "script": "tools/pve/clean-orphaned-lvm.sh",
@@ -815,7 +815,9 @@
2, 2,
3 3
], ],
"notes": [], "notes": [
"The file `/etc/sysconfig/CosmosCloud` is optional. If you need custom settings, you can create it yourself."
],
"type": "ct" "type": "ct"
}, },
{ {
@@ -837,9 +839,9 @@
} }
}, },
{ {
"name": "Proxmox VE Cron LXC Updater", "name": "PVE Cron LXC Updater",
"slug": "cron-update-lxcs", "slug": "cron-update-lxcs",
"desc": "This script will add/remove a crontab schedule that updates all LXCs every Sunday at midnight.", "desc": "This script will add/remove a crontab schedule that updates the operating system of all LXCs every Sunday at midnight.",
"script": "tools/pve/cron-update-lxcs.sh", "script": "tools/pve/cron-update-lxcs.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/cron-update-lxcs.sh", "script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/cron-update-lxcs.sh",
"categories": [ "categories": [
@@ -1136,7 +1138,8 @@
], ],
"notes": [ "notes": [
"Type `cat ~/matrix.creds` to see admin username/password.", "Type `cat ~/matrix.creds` to see admin username/password.",
"Synapse-Admin is running on port 5173" "Synapse-Admin is running on port 5173",
"For bridges Installation methods (WhatsApp, Signal, Discord, etc.), see: \u00b4https://docs.mau.fi/bridges/go/setup.html\u00b4"
], ],
"type": "ct" "type": "ct"
}, },
@@ -1222,6 +1225,22 @@
"notes": [], "notes": [],
"type": "ct" "type": "ct"
}, },
{
"name": "PVE LXC Execute Command",
"slug": "lxc-execute",
"desc": "This script allows administrators to execute a custom command inside one or multiple LXC containers on a Proxmox VE node. Containers can be selectively excluded via an interactive checklist. If a container is stopped, the script will automatically start it, run the command, and then shut it down again. Only Debian and Ubuntu based containers are supported.",
"script": "tools/pve/execute.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/execute.sh",
"categories": [
1
],
"notes": [
"Execute within the Proxmox shell.",
"Non-Debian/Ubuntu containers will be skipped automatically.",
"Stopped containers will be started temporarily to run the command, then shut down again."
],
"type": "pve"
},
{ {
"name": "Fenrus", "name": "Fenrus",
"slug": "fenrus", "slug": "fenrus",
@@ -1398,22 +1417,7 @@
"type": "ct" "type": "ct"
}, },
{ {
"name": "Frigate", "name": "PVE LXC Filesystem Trim",
"slug": "frigate",
"desc": "Frigate is an open source NVR built around real-time AI object detection. All processing is performed locally on your own hardware, and your camera feeds never leave your home.",
"script": "ct/frigate.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/frigate.sh",
"categories": [
15
],
"notes": [
"Discussions (explore more advanced methods): `https://github.com/tteck/Proxmox/discussions/2711`",
"go2rtc Interface port:`1984`"
],
"type": "ct"
},
{
"name": "Proxmox VE LXC Filesystem Trim",
"slug": "fstrim", "slug": "fstrim",
"desc": "This maintains SSD performance by managing unused blocks. Thin-provisioned storage systems also require management to prevent unnecessary storage use. VMs automate fstrim, while LXC containers need manual or automated fstrim processes for optimal performance.", "desc": "This maintains SSD performance by managing unused blocks. Thin-provisioned storage systems also require management to prevent unnecessary storage use. VMs automate fstrim, while LXC containers need manual or automated fstrim processes for optimal performance.",
"script": "tools/pve/fstrim.sh", "script": "tools/pve/fstrim.sh",
@@ -1465,6 +1469,23 @@
], ],
"type": "ct" "type": "ct"
}, },
{
"name": "Ghostfolio",
"slug": "ghostfolio",
"desc": "Ghostfolio is an open source wealth management software built with web technology. The application empowers busy people to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions.",
"script": "ct/ghostfolio.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/ghostfolio.sh",
"categories": [
23
],
"notes": [
"Create your first user account by visiting the web interface and clicking 'Get Started'. The first user will automatically get admin privileges.",
"Database and Redis credentials: `cat ~/ghostfolio.creds`",
"Optional: CoinGecko API keys can be added during installation or later in the .env file for enhanced cryptocurrency data.",
"Build process requires 4GB RAM (runtime: ~2GB). A temporary swap file will be created automatically if insufficient memory is detected."
],
"type": "ct"
},
{ {
"name": "Gitea-Mirror", "name": "Gitea-Mirror",
"slug": "gitea-mirror", "slug": "gitea-mirror",
@@ -1515,6 +1536,18 @@
], ],
"type": "addon" "type": "addon"
}, },
{
"name": "GlobaLeaks",
"slug": "globaleaks",
"desc": "GlobaLeaks is a free and open-source whistleblowing software enabling anyone to easily set up and maintain a secure reporting platform.",
"script": "ct/globaleaks.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/globaleaks.sh",
"categories": [
0
],
"notes": [],
"type": "ct"
},
{ {
"name": "GLPI", "name": "GLPI",
"slug": "glpi", "slug": "glpi",
@@ -1543,6 +1576,20 @@
"notes": [], "notes": [],
"type": "ct" "type": "ct"
}, },
{
"name": "GoAway",
"slug": "goaway",
"desc": "Lightweight DNS sinkhole written in Go with a modern dashboard client. Very good looking new alternative to Pi-Hole and Adguard Home.",
"script": "ct/goaway.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/goaway.sh",
"categories": [
5
],
"notes": [
"Type `cat ~/goaway.creds` to see login credentials."
],
"type": "ct"
},
{ {
"name": "Gokapi", "name": "Gokapi",
"slug": "gokapi", "slug": "gokapi",
@@ -1623,7 +1670,7 @@
{ {
"name": "Grist", "name": "Grist",
"slug": "grist", "slug": "grist",
"desc": "Grist is a modern, open source spreadsheet that goes beyond the grid", "desc": "Grist is like a spreadsheet + database hybrid. It lets you store structured data, use relational links between tables, apply formulas (even with Python), build custom layouts (cards, forms, dashboards), set fine-grained access rules, and visualize data with charts or pivot-tables.",
"script": "ct/grist.sh", "script": "ct/grist.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/grist.sh", "script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/grist.sh",
"categories": [ "categories": [
@@ -1770,6 +1817,7 @@
16 16
], ],
"notes": [ "notes": [
"Containerized version doesn't allow Home Assistant add-ons.",
"If the LXC is created Privileged, the script will automatically set up USB passthrough.", "If the LXC is created Privileged, the script will automatically set up USB passthrough.",
"config path: `/var/lib/docker/volumes/hass_config/_data`", "config path: `/var/lib/docker/volumes/hass_config/_data`",
"Portainer interface: $IP: 9443 - User & password must be set manually within 5 minutes, otherwise a restart of Portainer is required!", "Portainer interface: $IP: 9443 - User & password must be set manually within 5 minutes, otherwise a restart of Portainer is required!",
@@ -1849,7 +1897,7 @@
"type": "ct" "type": "ct"
}, },
{ {
"name": "Proxmox VE Host Backup", "name": "PVE Host Backup",
"slug": "host-backup", "slug": "host-backup",
"desc": "This script serves as a versatile backup utility, enabling users to specify both the backup path and the directory they want to work in. This flexibility empowers users to select the specific files and directories they wish to back up, making it compatible with a wide range of hosts, not limited to Proxmox.", "desc": "This script serves as a versatile backup utility, enabling users to specify both the backup path and the directory they want to work in. This flexibility empowers users to select the specific files and directories they wish to back up, making it compatible with a wide range of hosts, not limited to Proxmox.",
"script": "tools/pve/host-backup.sh", "script": "tools/pve/host-backup.sh",
@@ -2077,6 +2125,24 @@
"notes": [], "notes": [],
"type": "ct" "type": "ct"
}, },
{
"name": "Joplin Server",
"slug": "joplin-server",
"desc": "Joplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.",
"script": "ct/joplin-server.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/joplin-server.sh",
"categories": [
12
],
"notes": [
"Application can take some time to build, depending on your host speed. Please be patient."
],
"type": "ct",
"default_credentials": {
"username": "admin@localhost",
"password": "admin"
}
},
{ {
"name": "Jupyter Notebook", "name": "Jupyter Notebook",
"slug": "jupyternotebook", "slug": "jupyternotebook",
@@ -2146,7 +2212,7 @@
"type": "ct" "type": "ct"
}, },
{ {
"name": "Proxmox VE Kernel Clean", "name": "PVE Kernel Clean",
"slug": "kernel-clean", "slug": "kernel-clean",
"desc": "Cleaning unused kernel images is beneficial for reducing the length of the GRUB menu and freeing up disk space. By removing old, unused kernels, the system is able to conserve disk space and streamline the boot process.", "desc": "Cleaning unused kernel images is beneficial for reducing the length of the GRUB menu and freeing up disk space. By removing old, unused kernels, the system is able to conserve disk space and streamline the boot process.",
"script": "tools/pve/kernel-clean.sh", "script": "tools/pve/kernel-clean.sh",
@@ -2160,7 +2226,7 @@
"type": "pve" "type": "pve"
}, },
{ {
"name": "Proxmox VE Kernel Pin", "name": "PVE Kernel Pin",
"slug": "kernel-pin", "slug": "kernel-pin",
"desc": "Kernel Pin is an essential tool for effortlessly managing kernel pinning and unpinning.", "desc": "Kernel Pin is an essential tool for effortlessly managing kernel pinning and unpinning.",
"script": "tools/pve/kernel-pin.sh", "script": "tools/pve/kernel-pin.sh",
@@ -2424,7 +2490,7 @@
"type": "ct" "type": "ct"
}, },
{ {
"name": "Container LXC Deletion", "name": "PVE LXC Deletion",
"slug": "lxc-delete", "slug": "lxc-delete",
"desc": "This script helps manage and delete LXC containers on a Proxmox VE server. It lists all available containers, allowing the user to select one or more for deletion through an interactive menu. Running containers are automatically stopped before deletion, and the user is asked to confirm each action. The script ensures a controlled and efficient container management process.", "desc": "This script helps manage and delete LXC containers on a Proxmox VE server. It lists all available containers, allowing the user to select one or more for deletion through an interactive menu. Running containers are automatically stopped before deletion, and the user is asked to confirm each action. The script ensures a controlled and efficient container management process.",
"script": "tools/pve/lxc-delete.sh", "script": "tools/pve/lxc-delete.sh",
@@ -2641,7 +2707,7 @@
"type": "ct" "type": "ct"
}, },
{ {
"name": "Proxmox VE Processor Microcode", "name": "PVE Processor Microcode",
"slug": "microcode", "slug": "microcode",
"desc": "Processor Microcode is a layer of low-level software that runs on the processor and provides patches or updates to its firmware. Microcode updates can fix hardware bugs, improve performance, and enhance security features of the processor.\r\n\r\nIt's important to note that the availability of firmware update mechanisms, such as Intel's Management Engine (ME) or AMD's Platform Security Processor (PSP), may vary depending on the processor and its specific implementation. Therefore, it's recommended to consult the documentation for your processor to confirm whether firmware updates can be applied through the operating system.", "desc": "Processor Microcode is a layer of low-level software that runs on the processor and provides patches or updates to its firmware. Microcode updates can fix hardware bugs, improve performance, and enhance security features of the processor.\r\n\r\nIt's important to note that the availability of firmware update mechanisms, such as Intel's Management Engine (ME) or AMD's Platform Security Processor (PSP), may vary depending on the processor and its specific implementation. Therefore, it's recommended to consult the documentation for your processor to confirm whether firmware updates can be applied through the operating system.",
"script": "tools/pve/microcode.sh", "script": "tools/pve/microcode.sh",
@@ -2735,7 +2801,7 @@
} }
}, },
{ {
"name": "Proxmox VE Monitor-All", "name": "PVE Monitor-All",
"slug": "monitor-all", "slug": "monitor-all",
"desc": "This script will add Monitor-All to Proxmox VE, which will monitor the status of all your instances, both containers and virtual machines, excluding templates and user-defined ones, and automatically restart or reset them if they become unresponsive. This is particularly useful if you're experiencing problems with Home Assistant becoming non-responsive every few days/weeks. Monitor-All also maintains a log of the entire process, which can be helpful for troubleshooting and monitoring purposes.\r\n\r\n\ud83d\udec8 Virtual machines without the QEMU guest agent installed must be excluded.\r\n\ud83d\udec8 Prior to generating any new CT/VM not found in this repository, it's necessary to halt Proxmox VE Monitor-All by running systemctl stop ping-instances.", "desc": "This script will add Monitor-All to Proxmox VE, which will monitor the status of all your instances, both containers and virtual machines, excluding templates and user-defined ones, and automatically restart or reset them if they become unresponsive. This is particularly useful if you're experiencing problems with Home Assistant becoming non-responsive every few days/weeks. Monitor-All also maintains a log of the entire process, which can be helpful for troubleshooting and monitoring purposes.\r\n\r\n\ud83d\udec8 Virtual machines without the QEMU guest agent installed must be excluded.\r\n\ud83d\udec8 Prior to generating any new CT/VM not found in this repository, it's necessary to halt Proxmox VE Monitor-All by running systemctl stop ping-instances.",
"script": "tools/pve/monitor-all.sh", "script": "tools/pve/monitor-all.sh",
@@ -2785,6 +2851,18 @@
], ],
"type": "ct" "type": "ct"
}, },
{
"name": "MyIP",
"slug": "myip",
"desc": "The best IP Toolbox. Easy to check what's your IPs, IP geolocation, check for DNS leaks, examine WebRTC connections, speed test, ping test, MTR test, check website availability, whois search and more!",
"script": "ct/myip.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/myip.sh",
"categories": [
4
],
"notes": [],
"type": "ct"
},
{ {
"name": "Mylar3", "name": "Mylar3",
"slug": "mylar3", "slug": "mylar3",
@@ -2884,7 +2962,7 @@
"type": "ct" "type": "ct"
}, },
{ {
"name": "Proxmox VE Netdata", "name": "PVE Netdata",
"slug": "netdata", "slug": "netdata",
"desc": "Netdata is an open-source, real-time performance monitoring tool designed to provide insights into the performance and health of systems and applications. It is often used by system administrators, DevOps professionals, and developers to monitor and troubleshoot issues on servers and other devices.", "desc": "Netdata is an open-source, real-time performance monitoring tool designed to provide insights into the performance and health of systems and applications. It is often used by system administrators, DevOps professionals, and developers to monitor and troubleshoot issues on servers and other devices.",
"script": "tools/addon/netdata.sh", "script": "tools/addon/netdata.sh",
@@ -3305,15 +3383,17 @@
"categories": [ "categories": [
20 20
], ],
"notes": [], "notes": [
"Script contains optional installation of Ollama."
],
"type": "ct" "type": "ct"
}, },
{ {
"name": "OpenWrt", "name": "OpenWrt",
"slug": "openwrt", "slug": "openwrt-vm",
"desc": "OpenWrt is a powerful open-source firmware that can transform a wide range of networking devices into highly customizable and feature-rich routers, providing users with greater control and flexibility over their network infrastructure.", "desc": "OpenWrt is a powerful open-source firmware that can transform a wide range of networking devices into highly customizable and feature-rich routers, providing users with greater control and flexibility over their network infrastructure.",
"script": "vm/openwrt.sh", "script": "vm/openwrt-vm.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/openwrt.sh", "script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/openwrt-vm.sh",
"categories": [ "categories": [
4, 4,
2 2
@@ -3449,8 +3529,7 @@
11 11
], ],
"notes": [ "notes": [
"This LXC is very memory-hungry when updating; it requires at least 6GB RAM, but RAM may be reduced to as low as 2GB when running normally", "To use a bind mount for storage, create symlinks to your mount for both `uploads` and `temp-uploads` in `/opt/palmr_data`, and uncomment `CUSTOM_PATH` to add the path to your bind mount",
"To use a bind mount for storage, create symlinks to your mount for both `uploads` and `temp-uploads` in `/opt/palmr_data`",
"To use Palmr with a reverse proxy, uncomment `SECURE_SITE` in `/opt/palmr/apps/server/.env`" "To use Palmr with a reverse proxy, uncomment `SECURE_SITE` in `/opt/palmr/apps/server/.env`"
], ],
"type": "ct" "type": "ct"
@@ -3529,7 +3608,7 @@
} }
}, },
{ {
"name": "Proxmox Backup Server Processor Microcode", "name": "PBS Processor Microcode",
"slug": "pbs-microcode", "slug": "pbs-microcode",
"desc": "Processor Microcode is a layer of low-level software that runs on the processor and provides patches or updates to its firmware. Microcode updates can fix hardware bugs, improve performance, and enhance security features of the processor. This script is adapted for the Proxmox Backup Server environment and will only run on bare metal systems. If running in a virtualized environment, the script will exit. Note that firmware update mechanisms, such as Intel's Management Engine (ME) or AMD's Platform Security Processor (PSP), may vary depending on your processor and its implementation. Please consult your processor's documentation to verify if firmware updates can be applied through the operating system.", "desc": "Processor Microcode is a layer of low-level software that runs on the processor and provides patches or updates to its firmware. Microcode updates can fix hardware bugs, improve performance, and enhance security features of the processor. This script is adapted for the Proxmox Backup Server environment and will only run on bare metal systems. If running in a virtualized environment, the script will exit. Note that firmware update mechanisms, such as Intel's Management Engine (ME) or AMD's Platform Security Processor (PSP), may vary depending on your processor and its implementation. Please consult your processor's documentation to verify if firmware updates can be applied through the operating system.",
"script": "tools/pve/pbs_microcode.sh", "script": "tools/pve/pbs_microcode.sh",
@@ -3659,6 +3738,21 @@
"password": "ipamadmin" "password": "ipamadmin"
} }
}, },
{
"name": "PhpMyAdmin",
"slug": "phpmyadmin",
"desc": "phpMyAdmin is a free software tool written in PHP, intended to handle the administration of MySQL over the Web. phpMyAdmin supports a wide range of operations on MySQL and MariaDB. Frequently used operations (managing databases, tables, columns, relations, indexes, users, permissions, etc) can be performed via the user interface, while you still have the ability to directly execute any SQL statement.",
"script": "tools/addon/phpmyadmin.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/phpmyadmin.sh",
"categories": [
8
],
"notes": [
"Execute within an existing LXC Console",
"To update or uninstall run bash call again"
],
"type": "addon"
},
{ {
"name": "Pi.Alert", "name": "Pi.Alert",
"slug": "pialert", "slug": "pialert",
@@ -3668,9 +3762,7 @@
"categories": [ "categories": [
4 4
], ],
"notes": [ "notes": [],
"WARNING: Installation sources scripts outside of Community Scripts repo. Please check the source before installing."
],
"type": "ct" "type": "ct"
}, },
{ {
@@ -3743,8 +3835,7 @@
13 13
], ],
"notes": [ "notes": [
"With Privileged/Unprivileged Hardware Acceleration Support", "With Privileged/Unprivileged Hardware Acceleration Support"
"WARNING: Installation sources scripts outside of Community Scripts repo. Please check the source before installing."
], ],
"type": "ct" "type": "ct"
}, },
@@ -3786,8 +3877,7 @@
"notes": [ "notes": [
"If the LXC is created Privileged, the script will automatically set up USB passthrough.", "If the LXC is created Privileged, the script will automatically set up USB passthrough.",
"config path: `/var/lib/containers/storage/volumes/hass_config/_data`", "config path: `/var/lib/containers/storage/volumes/hass_config/_data`",
"Options to Install Portainer or Portainer Agent", "Options to Install Portainer or Portainer Agent"
"WARNING: Installation sources scripts outside of Community Scripts repo. Please check the source before installing."
], ],
"type": "ct" "type": "ct"
}, },
@@ -3806,9 +3896,9 @@
"type": "ct" "type": "ct"
}, },
{ {
"name": "Proxmox Backup Server Post Install", "name": "PBS Post Install",
"slug": "post-pbs-install", "slug": "post-pbs-install",
"desc": "The script will give options to Disable the Enterprise Repo, Add/Correct PBS Sources, Enable the No-Subscription Repo, Add Test Repo, Disable Subscription Nag, Update Proxmox Backup Server and Reboot PBS.", "desc": "The script is designed for Proxmox Backup Server (PBS) and will give options to Disable the Enterprise Repo, Add/Correct PBS Sources, Enable the No-Subscription Repo, Add Test Repo, Disable Subscription Nag, Update Proxmox Backup Server and Reboot PBS.",
"script": "tools/pve/post-pbs-install.sh", "script": "tools/pve/post-pbs-install.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/post-pbs-install.sh", "script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/post-pbs-install.sh",
"categories": [ "categories": [
@@ -3822,9 +3912,9 @@
"type": "pve" "type": "pve"
}, },
{ {
"name": "Proxmox Mail Gateway Post Install", "name": "PMG Post Install",
"slug": "post-pmg-install", "slug": "post-pmg-install",
"desc": "The script will give options to Disable the Enterprise Repo, Add/Correct PMG Sources, Enable the No-Subscription Repo, Add Test Repo, Disable Subscription Nag, Update Proxmox Mail Gateway and Reboot PMG.", "desc": "The script is designed for Proxmox Mail Gateway and will give options to Disable the Enterprise Repo, Add/Correct PMG Sources, Enable the No-Subscription Repo, Add Test Repo, Disable Subscription Nag, Update Proxmox Mail Gateway and Reboot PMG.",
"script": "tools/pve/post-pmg-install.sh", "script": "tools/pve/post-pmg-install.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/post-pmg-install.sh", "script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/post-pmg-install.sh",
"categories": [ "categories": [
@@ -3838,7 +3928,7 @@
"type": "pve" "type": "pve"
}, },
{ {
"name": "Proxmox VE Post Install", "name": "PVE Post Install",
"slug": "post-pve-install", "slug": "post-pve-install",
"desc": "This script provides options for managing Proxmox VE repositories, including disabling the Enterprise Repo, adding or correcting PVE sources, enabling the No-Subscription Repo, adding the test Repo, disabling the subscription nag, updating Proxmox VE, and rebooting the system.", "desc": "This script provides options for managing Proxmox VE repositories, including disabling the Enterprise Repo, adding or correcting PVE sources, enabling the No-Subscription Repo, adding the test Repo, disabling the subscription nag, updating Proxmox VE, and rebooting the system.",
"script": "tools/pve/post-pve-install.sh", "script": "tools/pve/post-pve-install.sh",
@@ -3959,7 +4049,7 @@
"type": "ct" "type": "ct"
}, },
{ {
"name": "Proxmox Backup Server", "name": "Proxmox Backup Server (PBS)",
"slug": "proxmox-backup-server", "slug": "proxmox-backup-server",
"desc": "Proxmox Backup Server is an enterprise backup solution, for backing up and restoring VMs, containers, and physical hosts. By supporting incremental, fully deduplicated backups, Proxmox Backup Server significantly reduces network load and saves valuable storage space.", "desc": "Proxmox Backup Server is an enterprise backup solution, for backing up and restoring VMs, containers, and physical hosts. By supporting incremental, fully deduplicated backups, Proxmox Backup Server significantly reduces network load and saves valuable storage space.",
"script": "ct/proxmox-backup-server.sh", "script": "ct/proxmox-backup-server.sh",
@@ -3978,7 +4068,7 @@
} }
}, },
{ {
"name": "Proxmox Datacenter Manager", "name": "Proxmox Datacenter Manager (PDM)",
"slug": "proxmox-datacenter-manager", "slug": "proxmox-datacenter-manager",
"desc": "The Proxmox Datacenter Manager project has been developed with the objective of providing a centralized overview of all your individual nodes and clusters. It also enables basic management like migrations of virtual guests without any cluster network requirements. ", "desc": "The Proxmox Datacenter Manager project has been developed with the objective of providing a centralized overview of all your individual nodes and clusters. It also enables basic management like migrations of virtual guests without any cluster network requirements. ",
"script": "ct/proxmox-datacenter-manager.sh", "script": "ct/proxmox-datacenter-manager.sh",
@@ -3993,7 +4083,7 @@
"type": "ct" "type": "ct"
}, },
{ {
"name": "Proxmox Mail Gateway", "name": "Proxmox Mail Gateway (PMG)",
"slug": "proxmox-mail-gateway", "slug": "proxmox-mail-gateway",
"desc": "Proxmox Mail Gateway is the leading open-source email security solution helping you to protect your mail server against all email threats from the moment they emerge.", "desc": "Proxmox Mail Gateway is the leading open-source email security solution helping you to protect your mail server against all email threats from the moment they emerge.",
"script": "ct/proxmox-mail-gateway.sh", "script": "ct/proxmox-mail-gateway.sh",
@@ -4236,8 +4326,8 @@
"name": "Resilio Sync", "name": "Resilio Sync",
"slug": "resiliosync", "slug": "resiliosync",
"desc": "Fast, reliable, and simple file sync and share solution, powered by P2P technology. Sync files across all your devices without storing them in the cloud.", "desc": "Fast, reliable, and simple file sync and share solution, powered by P2P technology. Sync files across all your devices without storing them in the cloud.",
"script": "ct/resilio-sync.sh", "script": "ct/resiliosync.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/resilio-sync.sh", "script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/resiliosync.sh",
"categories": [ "categories": [
11 11
], ],
@@ -4315,7 +4405,7 @@
"type": "ct" "type": "ct"
}, },
{ {
"name": "Proxmox VE CPU Scaling Governor", "name": "PVE CPU Scaling Governor",
"slug": "scaling-governor", "slug": "scaling-governor",
"desc": "The CPU scaling governor determines how the CPU frequency is adjusted based on the workload, with the goal of either conserving power or improving performance. By scaling the frequency up or down, the operating system can optimize the CPU usage and conserve energy when possible. Generic Scaling Governors", "desc": "The CPU scaling governor determines how the CPU frequency is adjusted based on the workload, with the goal of either conserving power or improving performance. By scaling the frequency up or down, the operating system can optimize the CPU usage and conserve energy when possible. Generic Scaling Governors",
"script": "tools/pve/scaling-governor.sh", "script": "tools/pve/scaling-governor.sh",
@@ -4328,6 +4418,20 @@
], ],
"type": "pve" "type": "pve"
}, },
{
"name": "Scraparr",
"slug": "scraparr",
"desc": "Scraparr is a Prometheus exporter for the *arr suite (Sonarr, Radarr, Lidarr, etc.). It provides metrics that can be scraped by Prometheus to monitor and visualize the health and performance of your *arr applications.",
"script": "ct/scraparr.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/scraparr.sh",
"categories": [
14
],
"notes": [
"Edit config file then restart the scraparr service: `systemctl restart scraparr`"
],
"type": "ct"
},
{ {
"name": "SearXNG", "name": "SearXNG",
"slug": "searxng", "slug": "searxng",
@@ -4401,6 +4505,20 @@
"password": "admin" "password": "admin"
} }
}, },
{
"name": "SigNoz",
"slug": "signoz",
"desc": "SigNoz is an open-source Datadog or New Relic alternative. Get APM, logs, traces, metrics, exceptions, & alerts in a single tool.",
"script": "ct/signoz.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/signoz.sh",
"categories": [
9
],
"notes": [
"The first user you register will be the admin user."
],
"type": "ct"
},
{ {
"name": "Silverbullet", "name": "Silverbullet",
"slug": "silverbullet", "slug": "silverbullet",
@@ -4532,6 +4650,18 @@
"password": "null" "password": "null"
} }
}, },
{
"name": "Stylus",
"slug": "stylus",
"desc": "Stylus (style + status) is a lightweight status page for infrastructure and networks. Configure a set of bash scripts that test the various parts of your infrastructure, set up visualizations with minimal configuration, and Stylus will generate you a dashboard for your system.",
"script": "ct/stylus.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/stylus.sh",
"categories": [
4
],
"notes": [],
"type": "ct"
},
{ {
"name": "Suwayomi-Server", "name": "Suwayomi-Server",
"slug": "suwayomi-server", "slug": "suwayomi-server",
@@ -4678,6 +4808,20 @@
], ],
"type": "ct" "type": "ct"
}, },
{
"name": "Telegraf",
"slug": "telegraf",
"desc": "Telegraf collects and sends time series data from databases, systems, and IoT sensors. It has no external dependencies, is easy to install, and requires minimal memory.",
"script": "ct/telegraf.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/telegraf.sh",
"categories": [
9
],
"notes": [
"Make sure to configure an output for the telegraf config and start the service with `systemctl start telegraf`."
],
"type": "ct"
},
{ {
"name": "The Lounge", "name": "The Lounge",
"slug": "the-lounge", "slug": "the-lounge",
@@ -4742,9 +4886,13 @@
9 9
], ],
"notes": [ "notes": [
"Please check and update the '/opt/tracktor/app/backend/.env' file if using behind reverse proxy." "Please check and update the '/opt/tracktor.env' file if using behind reverse proxy."
], ],
"type": "ct" "type": "ct",
"default_credentials": {
"username": null,
"password": "123456"
}
}, },
{ {
"name": "Traefik", "name": "Traefik",
@@ -4800,6 +4948,18 @@
], ],
"type": "ct" "type": "ct"
}, },
{
"name": "Tunarr",
"slug": "tunarr",
"desc": "Create a classic TV experience using your own media - IPTV backed by Plex/Jellyfin/Emby.",
"script": "ct/tunarr.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/tunarr.sh",
"categories": [
13
],
"notes": [],
"type": "ct"
},
{ {
"name": "TurnKey", "name": "TurnKey",
"slug": "turnkey", "slug": "turnkey",
@@ -4898,6 +5058,18 @@
], ],
"type": "vm" "type": "vm"
}, },
{
"name": "UHF Server",
"slug": "uhf",
"desc": "UHF Server is a powerful companion app that lets you seamlessly schedule and record your favorite shows from the UHF app.",
"script": "ct/uhf.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/uhf.sh",
"categories": [
13
],
"notes": [],
"type": "ct"
},
{ {
"name": "Umami", "name": "Umami",
"slug": "umami", "slug": "umami",
@@ -4987,21 +5159,22 @@
"type": "ct" "type": "ct"
}, },
{ {
"name": "Proxmox VE LXC Updater", "name": "PVE LXC Updater",
"slug": "update-lxcs", "slug": "update-lxcs",
"desc": "This script has been created to simplify and speed up the process of updating all LXC containers across various Linux distributions, such as Ubuntu, Debian, Devuan, Alpine Linux, CentOS-Rocky-Alma, Fedora, and ArchLinux. It's designed to automatically skip templates and specific containers during the update, enhancing its convenience and usability.", "desc": "This script has been created to simplify and speed up the process of updating the operating system running inside LXC containers across various Linux distributions, such as Ubuntu, Debian, Devuan, Alpine Linux, CentOS-Rocky-Alma, Fedora, and ArchLinux. It's designed to automatically skip templates and specific containers during the update, enhancing its convenience and usability.",
"script": "tools/pve/update-lxcs.sh", "script": "tools/pve/update-lxcs.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/update-lxcs.sh", "script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/update-lxcs.sh",
"categories": [ "categories": [
1 1
], ],
"notes": [ "notes": [
"Execute within the Proxmox shell" "Execute within the Proxmox shell",
"The script updates only the operating system of the LXC container. It DOES NOT update the application installed within the container!"
], ],
"type": "pve" "type": "pve"
}, },
{ {
"name": "Proxmox Update Repositories", "name": "PVE Update Repositories",
"slug": "update-repo", "slug": "update-repo",
"desc": "This script updates repository links in LXC containers, replacing old links from the tteck repository with links to the new community-scripts repository to fix issues related to updating scripts.", "desc": "This script updates repository links in LXC containers, replacing old links from the tteck repository with links to the new community-scripts repository to fix issues related to updating scripts.",
"script": "tools/pve/update-repo.sh", "script": "tools/pve/update-repo.sh",
@@ -5014,6 +5187,20 @@
], ],
"type": "pve" "type": "pve"
}, },
{
"name": "UpSnap",
"slug": "upsnap",
"desc": "UpSnap is a self-hosted web app that lets you wake up, manage and monitor devices on your network with ease. Built with SvelteKit, Go and PocketBase, it offers a clean dashboard, scheduled wake-ups, device discovery and secure user management.",
"script": "ct/upsnap.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/upsnap.sh",
"categories": [
4
],
"notes": [
"The first user you register will be the admin user."
],
"type": "ct"
},
{ {
"name": "Uptime Kuma", "name": "Uptime Kuma",
"slug": "uptimekuma", "slug": "uptimekuma",
@@ -5055,6 +5242,20 @@
], ],
"type": "ct" "type": "ct"
}, },
{
"name": "Verdaccio",
"slug": "verdaccio",
"desc": "Verdaccio is a lightweight private npm proxy registry built with Node.js. It allows you to host your own npm registry with minimal configuration, providing a private npm repository for your projects. Verdaccio supports npm, yarn, and pnpm, and can cache packages from the public npm registry, allowing for faster installs and protection against npm registry outages. It includes a web interface for browsing packages, authentication and authorization features, and can be easily integrated into your development workflow.",
"script": "ct/verdaccio.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/verdaccio.sh",
"categories": [
20
],
"notes": [
"To create the first user, run: npm adduser --registry http://<container-ip>:4873"
],
"type": "ct"
},
{ {
"name": "VictoriaMetrics", "name": "VictoriaMetrics",
"slug": "victoriametrics", "slug": "victoriametrics",
@@ -5093,6 +5294,20 @@
"notes": [], "notes": [],
"type": "ct" "type": "ct"
}, },
{
"name": "Warracker",
"slug": "warracker",
"desc": "Warracker is an open source, self-hostable warranty tracker to monitor expirations, store receipts, files. You own the data, your rules!",
"script": "ct/warracker.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/warracker.sh",
"categories": [
12
],
"notes": [
"The first user you register will be the admin user."
],
"type": "ct"
},
{ {
"name": "Wastebin", "name": "Wastebin",
"slug": "wastebin", "slug": "wastebin",
@@ -5322,8 +5537,10 @@
9 9
], ],
"notes": [ "notes": [
"Database credentials: `cat zabbix.creds`", "Database credentials: `cat ~/zabbix.creds`",
"Zabbix agent 2 is used by default" "You can choose between Zabbix agent (classic) and agent2 (modern) during installation",
"For agent2 the PostgreSQL plugin is installed by default; all plugins are optional",
"If agent2 with NVIDIA plugin is installed in an environment without GPU, the installer disables it automatically"
], ],
"type": "ct", "type": "ct",
"default_credentials": { "default_credentials": {

8
menu
View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi
@@ -13,13 +13,13 @@
# This script serves as the main entry point for ProxMenux, # This script serves as the main entry point for ProxMenux,
# a menu-driven tool designed for Proxmox VE management. # a menu-driven tool designed for Proxmox VE management.
# #
# - Displays the ProxMenu logo on startup. # - Displays the ProxMenux logo on startup.
# - Loads necessary configurations and language settings. # - Loads necessary configurations and language settings.
# - Checks for available updates and installs them if confirmed. # - Checks for available updates and installs them if confirmed.
# - Downloads and executes the latest main menu script. # - Downloads and executes the latest main menu script.
# #
# Key Features: # Key Features:
# - Ensures ProxMenu is always up-to-date by fetching the latest version. # - Ensures ProxMenux is always up-to-date by fetching the latest version.
# - Uses whiptail for interactive menus and language selection. # - Uses whiptail for interactive menus and language selection.
# - Loads utility functions and translation support. # - Loads utility functions and translation support.
# - Maintains a cache system to improve performance. # - Maintains a cache system to improve performance.
@@ -67,7 +67,7 @@ check_updates() {
if whiptail --title "$(translate "Update Available")" \ if whiptail --title "$(translate "Update Available")" \
--yesno "$(translate "New version available") ($REMOTE_VERSION)\n\n$(translate "Do you want to update now?")" \ --yesno "$(translate "New version available") ($REMOTE_VERSION)\n\n$(translate "Do you want to update now?")" \
10 60 --defaultno; then 10 60 --defaultno; then
msg_warn "$(translate "Starting ProxMenu update...")" msg_warn "$(translate "Starting ProxMenux update...")"
if wget -qO "$INSTALL_SCRIPT" "$REPO_URL/install_proxmenux.sh"; then if wget -qO "$INSTALL_SCRIPT" "$REPO_URL/install_proxmenux.sh"; then
chmod +x "$INSTALL_SCRIPT" chmod +x "$INSTALL_SCRIPT"

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - Network Management and Repair Tool # ProxMenux - Network Management and Repair Tool
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -0,0 +1,257 @@
#!/bin/bash
# ==========================================================
# Remove Subscription Banner - Proxmox VE 9.x (Clean Version)
# ==========================================================
set -euo pipefail
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
ensure_tools_json() {
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
}
register_tool() {
command -v jq >/dev/null 2>&1 || return 0
local tool="$1" state="$2"
ensure_tools_json
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" \
> "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
}
JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
MIN_JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
MOBILE_TPL="/usr/share/pve-yew-mobile-gui/index.html.tpl"
APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
PATCH_BIN="/usr/local/bin/pve-remove-nag.sh"
MARK_JS="PROXMENUX_NAG_REMOVED_v2"
MARK_MOBILE="<!-- PROXMENUX: MOBILE NAG PATCH v2 -->"
verify_js_integrity() {
local file="$1"
[ -f "$file" ] || return 1
[ -s "$file" ] || return 1
grep -Eq 'Ext|function|var|const|let' "$file" || return 1
if LC_ALL=C grep -qP '\x00' "$file" 2>/dev/null; then
return 1
fi
return 0
}
create_backup() {
local file="$1"
local backup_dir="$BASE_DIR/backups"
local timestamp
timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$backup_dir/$(basename "$file").backup.$timestamp"
mkdir -p "$backup_dir"
if [ -f "$file" ]; then
cp -a "$file" "$backup_file"
ls -t "$backup_dir"/"$(basename "$file")".backup.* 2>/dev/null | tail -n +6 | xargs -r rm -f 2>/dev/null || true
echo "$backup_file"
fi
}
# ----------------------------------------------------
create_patch_script() {
cat > "$PATCH_BIN" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
MIN_JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
MOBILE_TPL="/usr/share/pve-yew-mobile-gui/index.html.tpl"
MARK_JS="PROXMENUX_NAG_REMOVED_v2"
MARK_MOBILE="<!-- PROXMENUX: MOBILE NAG PATCH v2 -->"
BASE_DIR="/usr/local/share/proxmenux"
verify_js_integrity() {
local file="$1"
[ -f "$file" ] && [ -s "$file" ] && grep -Eq 'Ext|function' "$file" && ! LC_ALL=C grep -qP '\x00' "$file" 2>/dev/null
}
patch_web() {
[ -f "$JS_FILE" ] || return 0
grep -q "$MARK_JS" "$JS_FILE" && return 0
local backup_dir="$BASE_DIR/backups"
mkdir -p "$backup_dir"
local backup="$backup_dir/$(basename "$JS_FILE").backup.$(date +%Y%m%d_%H%M%S)"
cp -a "$JS_FILE" "$backup"
trap "cp -a '$backup' '$JS_FILE' 2>/dev/null || true" ERR
sed -i '1s|^|/* '"$MARK_JS"' */\n|' "$JS_FILE"
local patterns_found=0
if grep -q "res\.data\.status\.toLowerCase() !== 'active'" "$JS_FILE"; then
sed -i "s/res\.data\.status\.toLowerCase() !== 'active'/false/g" "$JS_FILE"
patterns_found=$((patterns_found + 1))
fi
if grep -q "subscriptionActive: ''" "$JS_FILE"; then
sed -i "s/subscriptionActive: ''/subscriptionActive: true/g" "$JS_FILE"
patterns_found=$((patterns_found + 1))
fi
if grep -q "title: gettext('No valid subscription')" "$JS_FILE"; then
sed -i "s/title: gettext('No valid subscription')/title: gettext('Community Edition')/g" "$JS_FILE"
patterns_found=$((patterns_found + 1))
fi
if grep -q "icon: Ext\.Msg\.WARNING" "$JS_FILE"; then
sed -i "s/icon: Ext\.Msg\.WARNING/icon: Ext.Msg.INFO/g" "$JS_FILE"
patterns_found=$((patterns_found + 1))
fi
if grep -q "subscription = !(" "$JS_FILE"; then
sed -i "s/subscription = !(/subscription = false \&\& (/g" "$JS_FILE"
patterns_found=$((patterns_found + 1))
fi
# Si nada coincidió (cambio upstream), restaura y sal limpio
if [ "${patterns_found:-0}" -eq 0 ]; then
cp -a "$backup" "$JS_FILE"
return 0
fi
# Verificación final
if ! verify_js_integrity "$JS_FILE"; then
cp -a "$backup" "$JS_FILE"
return 1
fi
# Limpiar artefactos/cachés
rm -f "$MIN_JS_FILE" "$GZ_FILE" 2>/dev/null || true
find /var/cache/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
find /var/lib/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
find /var/cache/nginx/ -type f -delete 2>/dev/null || true
trap - ERR
}
patch_mobile() {
[ -f "$MOBILE_TPL" ] || return 0
grep -q "$MARK_MOBILE" "$MOBILE_TPL" && return 0
local backup_dir="$BASE_DIR/backups"
mkdir -p "$backup_dir"
cp -a "$MOBILE_TPL" "$backup_dir/$(basename "$MOBILE_TPL").backup.$(date +%Y%m%d_%H%M%S)"
cat >> "$MOBILE_TPL" <<EOM
$MARK_MOBILE
<script>
(function() {
'use strict';
function removeSubscriptionElements() {
try {
const dialogs = document.querySelectorAll('dialog.pwt-outer-dialog');
dialogs.forEach(d => {
const text = (d.textContent || '').toLowerCase();
if (text.includes('subscription') || text.includes('no valid')) { d.remove(); }
});
const cards = document.querySelectorAll('.pwt-card.pwt-p-2.pwt-d-flex.pwt-interactive.pwt-justify-content-center');
cards.forEach(c => {
const text = (c.textContent || '').toLowerCase();
const hasButton = c.querySelector('button');
if (!hasButton && (text.includes('subscription') || text.includes('no valid'))) { c.remove(); }
});
const alerts = document.querySelectorAll('[class*="alert"], [class*="warning"], [class*="notice"]');
alerts.forEach(a => {
const text = (a.textContent || '').toLowerCase();
if (text.includes('subscription') || text.includes('no valid')) { a.remove(); }
});
} catch (e) { console.warn('Error removing subscription elements:', e); }
}
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', removeSubscriptionElements); }
else { removeSubscriptionElements(); }
const observer = new MutationObserver(removeSubscriptionElements);
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
const interval = setInterval(removeSubscriptionElements, 500);
setTimeout(() => { try { observer.disconnect(); clearInterval(interval); } catch(e){} }, 30000);
}
})();
</script>
EOM
}
reload_services() {
systemctl is-active --quiet pveproxy 2>/dev/null && {
systemctl reload pveproxy 2>/dev/null || systemctl restart pveproxy 2>/dev/null || true
}
systemctl is-active --quiet nginx 2>/dev/null && {
systemctl reload nginx 2>/dev/null || true
}
systemctl is-active --quiet pvedaemon 2>/dev/null && {
systemctl reload pvedaemon 2>/dev/null || true
}
find /var/cache/pve-manager/ -type f -delete 2>/dev/null || true
find /var/lib/pve-manager/ -type f -delete 2>/dev/null || true
}
main() {
patch_web || return 1
patch_mobile
reload_services
}
main
EOF
chmod 755 "$PATCH_BIN"
}
# ----------------------------------------------------
create_apt_hook() {
cat > "$APT_HOOK" <<'EOF'
/* ProxMenux: reapply nag patch after upgrades */
DPkg::Post-Invoke { "/usr/local/bin/pve-remove-nag.sh || true"; };
EOF
chmod 644 "$APT_HOOK"
apt-config dump >/dev/null 2>&1 || { msg_warn "APT hook syntax issue"; rm -f "$APT_HOOK"; }
}
remove_subscription_banner_pve9() {
local pve_version
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1 || true)
local pve_major="${pve_version%%.*}"
msg_info "$(translate "Detected Proxmox VE ${pve_version:-9.x} removing subscription banner")"
create_patch_script
create_apt_hook
if ! "$PATCH_BIN"; then
msg_error "$(translate "Error applying patches")"
return 1
fi
register_tool "subscription_banner" true
msg_ok "$(translate "Subscription banner removed successfully.")"
msg_ok "$(translate "Refresh your browser to see changes.")"
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
remove_subscription_banner_pve9
fi

View File

@@ -158,8 +158,6 @@ EOF
msg_ok "$(translate "Non-free firmware warnings disabled")" msg_ok "$(translate "Non-free firmware warnings disabled")"
fi fi
cleanup_duplicate_repos
update_output=$(apt-get update 2>&1) update_output=$(apt-get update 2>&1)
update_exit_code=$? update_exit_code=$?

View File

@@ -0,0 +1,128 @@
#!/bin/bash
# ProxMenux - Coral TPU Installer (PVE 9.x)
# =========================================
# Author : MacRimi
# License : MIT
# Version : 1.3 (PVE9, silent build)
# Last Updated: 25/09/2025
# =========================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
LOG_FILE="/tmp/coral_install.log"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
pre_install_prompt() {
if ! dialog --title "$(translate 'Coral TPU Installation')" --yesno \
"\n$(translate 'Installing Coral TPU drivers requires rebooting the server after installation. Do you want to proceed?')" 10 70; then
exit 0
fi
}
install_coral_host() {
show_proxmenux_logo
: >"$LOG_FILE"
msg_info "$(translate 'Installing build dependencies...')"
apt-get update -qq >>"$LOG_FILE" 2>&1
apt-get install -y git devscripts dh-dkms dkms proxmox-headers-$(uname -r) >>"$LOG_FILE" 2>&1
if [[ $? -ne 0 ]]; then msg_error "$(translate 'Error installing build dependencies. Check /tmp/coral_install.log')"; exit 1; fi
msg_ok "$(translate 'Build dependencies installed.')"
cd /tmp || exit 1
rm -rf gasket-driver >>"$LOG_FILE" 2>&1
msg_info "$(translate 'Cloning Google Coral driver repository...')"
git clone https://github.com/google/gasket-driver.git >>"$LOG_FILE" 2>&1
if [[ $? -ne 0 ]]; then msg_error "$(translate 'Could not clone the repository. Check /tmp/coral_install.log')"; exit 1; fi
msg_ok "$(translate 'Repository cloned successfully.')"
cd /tmp/gasket-driver || exit 1
msg_info "$(translate 'Patching source for kernel compatibility...')"
sed -i 's/\.llseek = no_llseek/\.llseek = noop_llseek/' src/gasket_core.c
sed -i 's/^MODULE_IMPORT_NS(DMA_BUF);/MODULE_IMPORT_NS("DMA_BUF");/' src/gasket_page_table.c
sed -i "s/\(linux-headers-686-pae | linux-headers-amd64 | linux-headers-generic | linux-headers\)/\1 | proxmox-headers-$(uname -r) | pve-headers-$(uname -r)/" debian/control
if [[ $? -ne 0 ]]; then msg_error "$(translate 'Patching failed. Check /tmp/coral_install.log')"; exit 1; fi
msg_ok "$(translate 'Source patched successfully.')"
msg_info "$(translate 'Building DKMS package...')"
debuild -us -uc -tc -b >>"$LOG_FILE" 2>&1
if [[ $? -ne 0 ]]; then msg_error "$(translate 'Failed to build DKMS package. Check /tmp/coral_install.log')"; exit 1; fi
msg_ok "$(translate 'DKMS package built successfully.')"
msg_info "$(translate 'Installing DKMS package...')"
dpkg -i ../gasket-dkms_*.deb >>"$LOG_FILE" 2>&1 || true
if ! dpkg -s gasket-dkms >/dev/null 2>&1; then
msg_error "$(translate 'Failed to install DKMS package. Check /tmp/coral_install.log')"; exit 1
fi
msg_ok "$(translate 'DKMS package installed.')"
msg_info "$(translate 'Compiling Coral TPU drivers for current kernel...')"
dkms remove -m gasket -v 1.0 -k "$(uname -r)" >>"$LOG_FILE" 2>&1 || true
dkms add -m gasket -v 1.0 >>"$LOG_FILE" 2>&1 || true
dkms build -m gasket -v 1.0 -k "$(uname -r)" >>"$LOG_FILE" 2>&1
if [[ $? -ne 0 ]]; then
sed -n '1,200p' /var/lib/dkms/gasket/1.0/build/make.log >>"$LOG_FILE" 2>&1 || true
msg_error "$(translate 'DKMS build failed. Check /tmp/coral_install.log')"; exit 1
fi
dkms install -m gasket -v 1.0 -k "$(uname -r)" >>"$LOG_FILE" 2>&1
if [[ $? -ne 0 ]]; then msg_error "$(translate 'DKMS install failed. Check /tmp/coral_install.log')"; exit 1; fi
msg_ok "$(translate 'Drivers compiled and installed via DKMS.')"
msg_info "$(translate 'Loading modules...')"
modprobe gasket >>"$LOG_FILE" 2>&1 || true
modprobe apex >>"$LOG_FILE" 2>&1 || true
if lsmod | grep -q '\bapex\b'; then
msg_ok "$(translate 'Modules loaded.')"
msg_success "$(translate 'Coral TPU drivers installed and loaded successfully.')"
else
msg_warn "$(translate 'Installation finished but drivers are not loaded. Please check dmesg and /tmp/coral_install.log')"
fi
echo "---- dmesg | grep -i apex (last lines) ----" >>"$LOG_FILE"
dmesg | grep -i apex | tail -n 20 >>"$LOG_FILE" 2>&1
}
restart_prompt() {
if whiptail --title "$(translate 'Coral TPU Installation')" --yesno \
"$(translate 'The installation requires a server restart to apply changes. Do you want to restart now?')" 10 70; then
msg_warn "$(translate 'Restarting the server...')"
reboot
else
msg_success "$(translate 'Completed. Press Enter to return to menu...')"
read -r
fi
}
pre_install_prompt
install_coral_host
restart_prompt

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Revision : @Blaspt (USB passthrough via udev rule with persistent /dev/coral) # Revision : @Blaspt (USB passthrough via udev rule with persistent /dev/coral)

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

157
scripts/lxc/jd2_2.sh Normal file
View File

@@ -0,0 +1,157 @@
#!/bin/bash
# Script para instalar JDownloader en un contenedor LXC desde el host Proxmox
# Autor: MacRimi
# Mostrar lista de CTs
CT_LIST=$(pct list | awk 'NR>1 {print $1, $3}')
if [ -z "$CT_LIST" ]; then
whiptail --title "Error" --msgbox "No hay contenedores LXC disponibles en el sistema." 8 50
exit 1
fi
# Seleccionar CT
CTID=$(whiptail --title "Instalación de JDownloader" --menu "Selecciona el contenedor donde instalar JDownloader:" 20 60 10 $CT_LIST 3>&1 1>&2 2>&3)
if [ -z "$CTID" ]; then
whiptail --title "Cancelado" --msgbox "No se ha seleccionado ningún contenedor." 8 40
exit 1
fi
# Solicitar email
EMAIL=$(whiptail --title "Cuenta My JDownloader" --inputbox "Introduce tu correo electrónico para vincular JDownloader:" 10 60 3>&1 1>&2 2>&3)
if [ -z "$EMAIL" ]; then
whiptail --title "Error" --msgbox "No se ha introducido ningún correo." 8 40
exit 1
fi
# Solicitar contraseña con confirmación
while true; do
PASSWORD=$(whiptail --title "Cuenta My JDownloader" --passwordbox "Introduce tu contraseña de My JDownloader:" 10 60 3>&1 1>&2 2>&3)
[ -z "$PASSWORD" ] && whiptail --title "Error" --msgbox "No se ha introducido ninguna contraseña." 8 40 && exit 1
CONFIRM=$(whiptail --title "Confirmación de contraseña" --passwordbox "Repite tu contraseña para confirmar:" 10 60 3>&1 1>&2 2>&3)
[ "$PASSWORD" = "$CONFIRM" ] && break
whiptail --title "Error" --msgbox "Las contraseñas no coinciden. Intenta de nuevo." 8 50
done
# Confirmación final
whiptail --title "Confirmar datos" --yesno "¿Deseas continuar con los siguientes datos?\n\nCorreo: $EMAIL\nContraseña: (oculta)\n\nEsta información se usará para vincular el contenedor con tu cuenta de My.JDownloader." 14 60
[ $? -ne 0 ] && whiptail --title "Cancelado" --msgbox "Instalación cancelada por el usuario." 8 40 && exit 1
clear
echo "🔍 Detectando sistema operativo dentro del CT $CTID..."
OS_ID=$(pct exec "$CTID" -- awk -F= '/^ID=/{gsub("\"",""); print $2}' /etc/os-release)
echo "Sistema detectado: $OS_ID"
echo "🧰 Preparando entorno..."
case "$OS_ID" in
debian)
# Repositorio adicional para Java 8
pct exec "$CTID" -- wget -q http://www.mirbsd.org/~tg/Debs/sources.txt/wtf-bookworm.sources
pct exec "$CTID" -- mv wtf-bookworm.sources /etc/apt/sources.list.d/
pct exec "$CTID" -- apt update -y
pct exec "$CTID" -- apt install -y openjdk-8-jdk wget
JAVA_PATH="/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java"
;;
ubuntu)
pct exec "$CTID" -- apt update -y
pct exec "$CTID" -- apt install -y openjdk-8-jdk wget
JAVA_PATH="/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java"
;;
alpine)
pct exec "$CTID" -- apk update
pct exec "$CTID" -- apk add openjdk8 wget
JAVA_PATH="/usr/lib/jvm/java-1.8-openjdk/bin/java"
;;
*)
echo "❌ Sistema operativo no soportado: $OS_ID"
exit 1
;;
esac
# Crear carpeta de instalación
pct exec "$CTID" -- mkdir -p /opt/jdownloader
pct exec "$CTID" -- bash -lc '
set -e
mkdir -p /opt/jdownloader
cd /opt/jdownloader
if [ ! -f JDownloader.jar ]; then
if ls JDownloader.jar.backup.* >/dev/null 2>&1; then
cp -a "$(ls -t JDownloader.jar.backup.* | head -1)" JDownloader.jar
else
curl -fSLo JDownloader.jar https://installer.jdownloader.org/JDownloader.jar
fi
fi
chown root:root JDownloader.jar
chmod 0644 JDownloader.jar
'
# Crear archivo de configuración JSON para My JDownloader
pct exec "$CTID" -- bash -c "mkdir -p /opt/jdownloader/cfg && cat > /opt/jdownloader/cfg/org.jdownloader.api.myjdownloader.MyJDownloaderSettings.json" <<EOF
{
"email" : "$EMAIL",
"password" : "$PASSWORD",
"enabled" : true
}
EOF
# Crear servicio según sistema
if [[ "$OS_ID" == "alpine" ]]; then
# Servicio OpenRC para Alpine
pct exec "$CTID" -- bash -c 'cat > /etc/init.d/jdownloader <<EOF
#!/sbin/openrc-run
command="/usr/bin/java"
command_args="-jar /opt/jdownloader/JDownloader.jar -norestart"
pidfile="/var/run/jdownloader.pid"
name="JDownloader"
depend() {
need net
}
EOF'
pct exec "$CTID" -- chmod +x /etc/init.d/jdownloader
pct exec "$CTID" -- rc-update add jdownloader default
pct exec "$CTID" -- rc-service jdownloader start
else
# Servicio systemd para Debian/Ubuntu
pct exec "$CTID" -- bash -lc 'cat > /etc/systemd/system/jdownloader.service <<'"'"'EOF'"'"'
[Unit]
Description=JDownloader
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/jdownloader
ExecStartPre=/usr/bin/test -s /opt/jdownloader/JDownloader.jar
ExecStart=/usr/bin/java -jar /opt/jdownloader/JDownloader.jar -norestart
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable jdownloader
systemctl restart jdownloader
systemctl status jdownloader --no-pager || true
'
pct exec "$CTID" -- systemctl daemon-reexec
pct exec "$CTID" -- systemctl daemon-reload
pct exec "$CTID" -- systemctl enable jdownloader
pct exec "$CTID" -- systemctl start jdownloader
fi
pct exec "$CTID" -- reboot
echo -e "\n\033[1;32m✅ JDownloader se ha instalado correctamente en el CT $CTID y está funcionando como servicio.\033[0m"
echo -e "\n➡ Accede a \033[1;34mhttps://my.jdownloader.org\033[0m con tu cuenta para gestionarlo.\n"

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi
@@ -197,7 +197,7 @@ show_version_info() {
info_message+="$(translate "No installation information available.")\n" info_message+="$(translate "No installation information available.")\n"
fi fi
info_message+="\n$(translate "ProxMenu files:")\n" info_message+="\n$(translate "ProxMenux files:")\n"
[ -f "$INSTALL_DIR/$MENU_SCRIPT" ] && info_message+="$MENU_SCRIPT$INSTALL_DIR/$MENU_SCRIPT\n" || info_message+="$MENU_SCRIPT\n" [ -f "$INSTALL_DIR/$MENU_SCRIPT" ] && info_message+="$MENU_SCRIPT$INSTALL_DIR/$MENU_SCRIPT\n" || info_message+="$MENU_SCRIPT\n"
[ -f "$UTILS_FILE" ] && info_message+="✓ utils.sh → $UTILS_FILE\n" || info_message+="✗ utils.sh\n" [ -f "$UTILS_FILE" ] && info_message+="✓ utils.sh → $UTILS_FILE\n" || info_message+="✗ utils.sh\n"
[ -f "$CONFIG_FILE" ] && info_message+="✓ config.json → $CONFIG_FILE\n" || info_message+="✗ config.json\n" [ -f "$CONFIG_FILE" ] && info_message+="✓ config.json → $CONFIG_FILE\n" || info_message+="✗ config.json\n"
@@ -237,8 +237,8 @@ uninstall_proxmenu() {
install_type=$(detect_installation_type) install_type=$(detect_installation_type)
if ! dialog --clear --backtitle "ProxMenux Configuration" \ if ! dialog --clear --backtitle "ProxMenux Configuration" \
--title "Uninstall ProxMenu" \ --title "Uninstall ProxMenux" \
--yesno "\n$(translate "Are you sure you want to uninstall ProxMenu?")" 8 60; then --yesno "\n$(translate "Are you sure you want to uninstall ProxMenux?")" 8 60; then
return return
fi fi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - LXC Conversion Management Menu # ProxMenux - LXC Conversion Management Menu
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi
@@ -99,14 +99,15 @@ show_menu() {
--title "$(translate "$menu_title")" \ --title "$(translate "$menu_title")" \
--menu "$(translate "Select an option:")" 20 70 10 \ --menu "$(translate "Select an option:")" 20 70 10 \
1 "$(translate "Settings post-install Proxmox")" \ 1 "$(translate "Settings post-install Proxmox")" \
2 "$(translate "Help and Info Commands")" \ 2 "$(translate "Hardware: GPUs and Coral-TPU")" \
3 "$(translate "Hardware: GPUs and Coral-TPU")" \ 3 "$(translate "Create VM from template or script")" \
4 "$(translate "Create VM from template or script")" \ 4 "$(translate "Disk and Storage Manager")" \
5 "$(translate "Disk and Storage Manager")" \ 5 "$(translate "Mount and Share Manager")" \
6 "$(translate "Proxmox VE Helper Scripts")" \ 6 "$(translate "Proxmox VE Helper Scripts")" \
7 "$(translate "Network Management")" \ 7 "$(translate "Network Management")" \
8 "$(translate "Utilities and Tools")" \ 8 "$(translate "Utilities and Tools")" \
9 "$(translate "Settings")" \ h "$(translate "Help and Info Commands")" \
s "$(translate "Settings")" \
0 "$(translate "Exit")" 2>"$TEMP_FILE" 0 "$(translate "Exit")" 2>"$TEMP_FILE"
local EXIT_STATUS=$? local EXIT_STATUS=$?
@@ -122,15 +123,16 @@ show_menu() {
case $OPTION in case $OPTION in
1) exec bash <(curl -s "$REPO_URL/scripts/menus/menu_post_install.sh") ;; 1) exec bash <(curl -s "$REPO_URL/scripts/menus/menu_post_install.sh") ;;
2) bash <(curl -s "$REPO_URL/scripts/help_info_menu.sh") ;; 2) exec bash <(curl -s "$REPO_URL/scripts/menus/hw_grafics_menu.sh") ;;
3) exec bash <(curl -s "$REPO_URL/scripts/menus/hw_grafics_menu.sh") ;; 3) exec bash <(curl -s "$REPO_URL/scripts/menus/create_vm_menu.sh") ;;
4) exec bash <(curl -s "$REPO_URL/scripts/menus/create_vm_menu.sh") ;; 4) exec bash <(curl -s "$REPO_URL/scripts/menus/storage_menu.sh") ;;
5) exec bash <(curl -s "$REPO_URL/scripts/menus/storage_menu.sh") ;; 5) exec bash <(curl -s "$REPO_URL/scripts/menus/share_menu.sh") ;;
6) exec bash <(curl -s "$REPO_URL/scripts/menus/menu_Helper_Scripts.sh") ;; 6) exec bash <(curl -s "$REPO_URL/scripts/menus/menu_Helper_Scripts.sh") ;;
7) exec bash <(curl -s "$REPO_URL/scripts/menus/network_menu.sh") ;; 7) exec bash <(curl -s "$REPO_URL/scripts/menus/network_menu.sh") ;;
8) exec bash <(curl -s "$REPO_URL/scripts/menus/utilities_menu.sh") ;; 8) exec bash <(curl -s "$REPO_URL/scripts/menus/utilities_menu.sh") ;;
9) exec bash <(curl -s "$REPO_URL/scripts/menus/config_menu.sh") ;; h) bash <(curl -s "$REPO_URL/scripts/help_info_menu.sh") ;;
0) clear; msg_ok "$(translate "Thank you for using ProxMenu. Goodbye!")"; rm -f "$TEMP_FILE"; exit 0 ;; s) exec bash <(curl -s "$REPO_URL/scripts/menus/config_menu.sh") ;;
0) clear; msg_ok "$(translate "Thank you for using ProxMenux. Goodbye!")"; rm -f "$TEMP_FILE"; exit 0 ;;
*) msg_warn "$(translate "Invalid option")"; sleep 2 ;; *) msg_warn "$(translate "Invalid option")"; sleep 2 ;;
esac esac
done done

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi
@@ -71,7 +71,7 @@ show_menu() {
7) exec bash <(curl -s "$REPO_URL/scripts/menus/network_menu.sh") ;; 7) exec bash <(curl -s "$REPO_URL/scripts/menus/network_menu.sh") ;;
8) exec bash <(curl -s "$REPO_URL/scripts/menus/utilities_menu.sh") ;; 8) exec bash <(curl -s "$REPO_URL/scripts/menus/utilities_menu.sh") ;;
9) exec bash <(curl -s "$REPO_URL/scripts/menus/config_menu.sh") ;; 9) exec bash <(curl -s "$REPO_URL/scripts/menus/config_menu.sh") ;;
0) clear; msg_ok "$(translate "Thank you for using ProxMenu. Goodbye!")"; rm -f "$TEMP_FILE"; exit 0 ;; 0) clear; msg_ok "$(translate "Thank you for using ProxMenux. Goodbye!")"; rm -f "$TEMP_FILE"; exit 0 ;;
*) msg_warn "$(translate "Invalid option")"; sleep 2 ;; *) msg_warn "$(translate "Invalid option")"; sleep 2 ;;
esac esac
done done

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - Network Management and Repair Tool # ProxMenux - Network Management and Repair Tool
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -143,7 +143,7 @@ remove_subscription_banner() {
msg_warn "Banner removal cancelled by user." msg_warn "Banner removal cancelled by user."
return 1 return 1
fi fi
bash <(curl -fsSL "$REPO_URL/scripts/global/remove-banner-pve9.sh") bash <(curl -fsSL "$REPO_URL/scripts/global/remove-banner-pve9_2.sh")
else else
if ! whiptail --title "Proxmox VE 8.x Subscription Banner Removal" \ if ! whiptail --title "Proxmox VE 8.x Subscription Banner Removal" \
--yesno "Do you want to remove the Proxmox subscription banner from the web interface for PVE $pve_version?" 10 70; then --yesno "Do you want to remove the Proxmox subscription banner from the web interface for PVE $pve_version?" 10 70; then

View File

@@ -795,7 +795,7 @@ EOF
) )
local selected local selected
selected=$(dialog --clear --backtitle "ProxMenu - $(translate "System Utilities")" \ selected=$(dialog --clear --backtitle "ProxMenux - $(translate "System Utilities")" \
--title "$(translate "Select utilities to install")" \ --title "$(translate "Select utilities to install")" \
--checklist "$(translate "Use SPACE to select/deselect, ENTER to confirm")" \ --checklist "$(translate "Use SPACE to select/deselect, ENTER to confirm")" \
20 70 12 "${utilities[@]}" 2>&1 >/dev/tty) 20 70 12 "${utilities[@]}" 2>&1 >/dev/tty)
@@ -807,7 +807,7 @@ EOF
local selected="$1" local selected="$1"
if [ -z "$selected" ]; then if [ -z "$selected" ]; then
dialog --clear --backtitle "ProxMenu" \ dialog --clear --backtitle "ProxMenux" \
--title "$(translate "No Selection")" \ --title "$(translate "No Selection")" \
--msgbox "$(translate "No utilities were selected")" 8 40 --msgbox "$(translate "No utilities were selected")" 8 40
return return
@@ -975,7 +975,7 @@ EOF
local selected local selected
selected=$( selected=$(
dialog --clear --backtitle "ProxMenu - $(translate "System Utilities")" \ dialog --clear --backtitle "ProxMenux - $(translate "System Utilities")" \
--title "$(translate "Select utilities to install")" \ --title "$(translate "Select utilities to install")" \
--checklist "$(translate "Use SPACE to select/deselect, ENTER to confirm")" \ --checklist "$(translate "Use SPACE to select/deselect, ENTER to confirm")" \
20 70 12 "${utilities[@]}" 3>&1 1>&2 2>&3 20 70 12 "${utilities[@]}" 3>&1 1>&2 2>&3
@@ -992,7 +992,7 @@ EOF
if [[ -z "$selected" ]]; then if [[ -z "$selected" ]]; then
dialog --clear --backtitle "ProxMenu" \ dialog --clear --backtitle "ProxMenux" \
--title "$(translate "No Selection")" \ --title "$(translate "No Selection")" \
--msgbox "$(translate "No utilities were selected")" 8 40 --msgbox "$(translate "No utilities were selected")" 8 40
return 0 return 0
@@ -1051,7 +1051,7 @@ EOF
local dlg_status=$? local dlg_status=$?
if [[ $dlg_status -ne 0 ]]; then if [[ $dlg_status -ne 0 ]]; then
dialog --clear --backtitle "ProxMenu" \ dialog --clear --backtitle "ProxMenux" \
--title "$(translate "Canceled")" \ --title "$(translate "Canceled")" \
--msgbox "$(translate "Action canceled by user")" 8 40 --msgbox "$(translate "Action canceled by user")" 8 40
@@ -2902,7 +2902,7 @@ remove_subscription_banner() {
if [[ "$pve_version" -ge 9 ]]; then if [[ "$pve_version" -ge 9 ]]; then
bash <(curl -fsSL "$REPO_URL/scripts/global/remove-banner-pve9.sh") bash <(curl -fsSL "$REPO_URL/scripts/global/remove-banner-pve9_2.sh")
else else
bash <(curl -fsSL "$REPO_URL/scripts/global/remove-banner-pve8.sh") bash <(curl -fsSL "$REPO_URL/scripts/global/remove-banner-pve8.sh")

View File

@@ -150,7 +150,7 @@ uninstall_apt_upgrade() {
################################################################ ################################################################
uninstall_subscription_banner() { uninstall_subscription_banner_() {
msg_info "$(translate "Restoring subscription banner...")" msg_info "$(translate "Restoring subscription banner...")"
# Remove APT hook # Remove APT hook
@@ -163,6 +163,111 @@ uninstall_subscription_banner() {
register_tool "subscription_banner" false register_tool "subscription_banner" false
} }
uninstall_subscription_banner() {
msg_info "$(translate "Restoring subscription banner...")"
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
local MOBILE_TPL="/usr/share/pve-manager/templates/index.html.tpl"
local PATCH_BIN="/usr/local/bin/pve-remove-nag.sh"
local BASE_DIR="/opt/pve-nag-buster"
local MIN_JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
local MARK_MOBILE=" PVE9 Mobile Subscription Banner Removal "
local restored=false
# Remove APT hook (both old and new versions)
for hook in /etc/apt/apt.conf.d/*nag* /etc/apt/apt.conf.d/no-nag-script; do
if [[ -e "$hook" ]]; then
rm -f "$hook"
msg_ok "$(translate "Removed APT hook: $hook")"
fi
done
# Remove patch script
if [[ -f "$PATCH_BIN" ]]; then
rm -f "$PATCH_BIN"
msg_ok "$(translate "Removed patch script: $PATCH_BIN")"
fi
# Restore JavaScript file from backups (new script method)
if [[ -d "$BASE_DIR/backups" ]]; then
local latest_backup
latest_backup=$(ls -t "$BASE_DIR/backups"/proxmoxlib.js.backup.* 2>/dev/null | head -1)
if [[ -n "$latest_backup" && -f "$latest_backup" ]]; then
if [[ -s "$latest_backup" ]] && grep -q "Ext\|function" "$latest_backup" && ! grep -q $'\0' "$latest_backup"; then
cp -a "$latest_backup" "$JS_FILE"
msg_ok "$(translate "Restored from backup: $latest_backup")"
restored=true
fi
fi
fi
# Restore from old script backups (if new method didn't work)
if [[ "$restored" == false ]]; then
local old_backup
old_backup=$(ls -t "${JS_FILE}".backup.pve9.* "${JS_FILE}".backup.* 2>/dev/null | head -1)
if [[ -n "$old_backup" && -f "$old_backup" ]]; then
if [[ -s "$old_backup" ]] && grep -q "Ext\|function" "$old_backup" && ! grep -q $'\0' "$old_backup"; then
cp -a "$old_backup" "$JS_FILE"
msg_ok "$(translate "Restored from old backup: $old_backup")"
restored=true
fi
fi
fi
# Restore mobile template if patched
if [[ -f "$MOBILE_TPL" ]] && grep -q "$MARK_MOBILE" "$MOBILE_TPL"; then
local mobile_backup
mobile_backup=$(ls -t "$BASE_DIR/backups"/index.html.tpl.backup.* 2>/dev/null | head -1)
if [[ -n "$mobile_backup" && -f "$mobile_backup" ]]; then
cp -a "$mobile_backup" "$MOBILE_TPL"
msg_ok "$(translate "Restored mobile template from backup")"
else
# Remove the patch manually if no backup available
sed -i "/^$MARK_MOBILE$/,\$d" "$MOBILE_TPL"
msg_ok "$(translate "Removed mobile template patches")"
fi
fi
# If no valid backups found, reinstall packages
if [[ "$restored" == false ]]; then
msg_info "$(translate "No valid backups found, reinstalling packages...")"
if apt --reinstall install proxmox-widget-toolkit -y >/dev/null 2>&1; then
msg_ok "$(translate "Reinstalled proxmox-widget-toolkit")"
restored=true
else
msg_error "$(translate "Failed to reinstall proxmox-widget-toolkit")"
fi
# Also try to reinstall pve-manager if mobile template was patched
if [[ -f "$MOBILE_TPL" ]] && grep -q "$MARK_MOBILE" "$MOBILE_TPL"; then
apt --reinstall install pve-manager -y >/dev/null 2>&1 || true
fi
fi
rm -f "$MIN_JS_FILE" "$GZ_FILE" 2>/dev/null || true
find /var/cache/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
find /var/lib/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
find /var/cache/nginx/ -type f -delete 2>/dev/null || true
register_tool "subscription_banner" false
if [[ "$restored" == true ]]; then
msg_ok "$(translate "Subscription banner restored successfully")"
msg_ok "$(translate "Refresh your browser to see changes (server restart may be required)")"
else
msg_error "$(translate "Failed to restore subscription banner completely")"
return 1
fi
}
################################################################ ################################################################
uninstall_time_sync() { uninstall_time_sync() {
@@ -583,7 +688,6 @@ migrate_installed_tools() {
return return
fi fi
clear
show_proxmenux_logo show_proxmenux_logo
msg_info "$(translate 'Detecting previous optimizations...')" msg_info "$(translate 'Detecting previous optimizations...')"

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu CT - NFS Client Manager for Proxmox LXC # ProxMenux CT - NFS Client Manager for Proxmox LXC
# ========================================================== # ==========================================================
# Based on ProxMenux by MacRimi # Based on ProxMenux by MacRimi
# ========================================================== # ==========================================================

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu Host - NFS Host Manager for Proxmox Host # ProxMenux Host - NFS Host Manager for Proxmox Host
# ========================================================== # ==========================================================
# Based on ProxMenux by MacRimi # Based on ProxMenux by MacRimi
# ========================================================== # ==========================================================

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu CT - NFS Manager for Proxmox LXC (Simple + Universal) # ProxMenux CT - NFS Manager for Proxmox LXC (Simple + Universal)
# ========================================================== # ==========================================================
# Based on ProxMenux by MacRimi # Based on ProxMenux by MacRimi
# ========================================================== # ==========================================================
@@ -201,25 +201,25 @@ get_network_config() {
select_export_options() { select_export_options() {
EXPORT_OPTIONS=$(whiptail --title "$(translate "Export Options")" --menu \ EXPORT_OPTIONS=$(whiptail --title "$(translate "Export Options")" --menu \
"\n$(translate "Select export permissions:")" 15 70 3 \ "\n$(translate "Select export permissions:")" 15 70 3 \
"1" "$(translate "Read-Write (recommended)")" \ "1" "$(translate "Read-Write (universal)")" \
"2" "$(translate "Read-Only")" \ "2" "$(translate "Read-Only")" \
"3" "$(translate "Custom options")" 3>&1 1>&2 2>&3) "3" "$(translate "Custom options")" 3>&1 1>&2 2>&3)
case "$EXPORT_OPTIONS" in case "$EXPORT_OPTIONS" in
1) 1)
OPTIONS="rw,sync,no_subtree_check,all_squash,anonuid=0,anongid=101000" OPTIONS="rw,sync,no_subtree_check,no_root_squash"
;; ;;
2) 2)
OPTIONS="ro,sync,no_subtree_check,all_squash,anonuid=0,anongid=101000" OPTIONS="ro,sync,no_subtree_check,no_root_squash"
;; ;;
3) 3)
OPTIONS=$(whiptail --inputbox "$(translate "Enter custom NFS options:")" \ OPTIONS=$(whiptail --inputbox "$(translate "Enter custom NFS options:")" \
10 70 "rw,sync,no_subtree_check,all_squash,anonuid=0,anongid=101000" \ 10 70 "rw,sync,no_subtree_check,no_root_squash" \
--title "$(translate "Custom Options")" 3>&1 1>&2 2>&3) --title "$(translate "Custom Options")" 3>&1 1>&2 2>&3)
[[ -z "$OPTIONS" ]] && OPTIONS="rw,sync,no_subtree_check,all_squash,anonuid=0,anongid=101000" [[ -z "$OPTIONS" ]] && OPTIONS="rw,sync,no_subtree_check,no_root_squash"
;; ;;
*) *)
OPTIONS="rw,sync,no_subtree_check,all_squash,anonuid=0,anongid=101000" OPTIONS="rw,sync,no_subtree_check,no_root_squash"
;; ;;
esac esac
} }

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu CT - Samba Client Manager for Proxmox LXC # ProxMenux CT - Samba Client Manager for Proxmox LXC
# ========================================================== # ==========================================================
# Based on ProxMenux by MacRimi # Based on ProxMenux by MacRimi
# ========================================================== # ==========================================================

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu Host - Samba Host Manager for Proxmox Host # ProxMenux Host - Samba Host Manager for Proxmox Host
# ========================================================== # ==========================================================
# Based on ProxMenux by MacRimi # Based on ProxMenux by MacRimi
# ========================================================== # ==========================================================

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu CT - Samba Manager for Proxmox LXC # ProxMenux CT - Samba Manager for Proxmox LXC
# ========================================================== # ==========================================================
# Based on ProxMenux by MacRimi # Based on ProxMenux by MacRimi
# ========================================================== # ==========================================================

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - Mount independent disk on Proxmox host # ProxMenux - Mount independent disk on Proxmox host
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - Mount point from host into LXC container (CT) # ProxMenux - Mount point from host into LXC container (CT)
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# License : MIT # License : MIT

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - Mount independent disk on Proxmox host # ProxMenux - Mount independent disk on Proxmox host
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# ========================================================== # ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management # ProxMenux - A menu-driven script for Proxmox VE management
# ========================================================== # ==========================================================
# Author : MacRimi # Author : MacRimi
# Copyright : (c) 2024 MacRimi # Copyright : (c) 2024 MacRimi

View File

@@ -1 +1 @@
1.1.5 1.1.6

View File

@@ -83,7 +83,7 @@ export default function HelpAndInfoPage() {
<div className="space-y-4 mt-6"> <div className="space-y-4 mt-6">
<p className="text-lg text-black"> <p className="text-lg text-black">
ProxMenu provides an interactive command reference menu for Proxmox VE through a dialog-based interface. ProxMenux provides an interactive command reference menu for Proxmox VE through a dialog-based interface.
Select one of the categories below to explore the available commands. Select one of the categories below to explore the available commands.
</p> </p>
@@ -170,12 +170,12 @@ export default function HelpAndInfoPage() {
<div className="mt-16 mb-6"> <div className="mt-16 mb-6">
<div className="flex items-center gap-3 mb-6"> <div className="flex items-center gap-3 mb-6">
<Book className="h-8 w-8 mr-2 text-blue-500" /> <Book className="h-8 w-8 mr-2 text-blue-500" />
<h2 className="text-2xl font-bold text-black">ProxMenu Guides</h2> <h2 className="text-2xl font-bold text-black">ProxMenux Guides</h2>
</div> </div>
<p className="text-lg mb-6 text-black"> <p className="text-lg mb-6 text-black">
Check out the guides section for additional resources, tutorials, and documentation to help you get the most Check out the guides section for additional resources, tutorials, and documentation to help you get the most
out of Proxmox VE and ProxMenu. out of Proxmox VE and ProxMenux.
</p> </p>
<div className="flex justify-center"> <div className="flex justify-center">

View File

@@ -111,7 +111,7 @@ export default function LinuxResourcesPage() {
<p className="text-lg mb-8 text-gray-700"> <p className="text-lg mb-8 text-gray-700">
A collection of useful resources for learning Linux commands, security practices, monitoring tools, and A collection of useful resources for learning Linux commands, security practices, monitoring tools, and
more. These resources complement the commands available in ProxMenu and will help you deepen your knowledge more. These resources complement the commands available in ProxMenux and will help you deepen your knowledge
of Linux system administration. of Linux system administration.
</p> </p>
</div> </div>