Update AppImage

This commit is contained in:
MacRimi
2025-11-04 18:07:13 +01:00
parent 8abef33840
commit 6c5eb156a1
5 changed files with 573 additions and 41 deletions

View File

@@ -22,12 +22,9 @@ export function AuthSetup({ onComplete }: AuthSetupProps) {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
useEffect(() => { useEffect(() => {
// Check if onboarding is complete and auth setup is needed
const hasSeenOnboarding = localStorage.getItem("proxmenux-onboarding-seen") const hasSeenOnboarding = localStorage.getItem("proxmenux-onboarding-seen")
const authSetupComplete = localStorage.getItem("proxmenux-auth-setup-complete")
if (hasSeenOnboarding && !authSetupComplete) { if (hasSeenOnboarding) {
// Small delay to show after onboarding closes
setTimeout(() => setOpen(true), 500) setTimeout(() => setOpen(true), 500)
} }
}, []) }, [])
@@ -37,19 +34,25 @@ export function AuthSetup({ onComplete }: AuthSetupProps) {
setError("") setError("")
try { try {
const response = await fetch(getApiUrl("/api/auth/setup"), { console.log("[v0] Skipping authentication setup...")
const response = await fetch(getApiUrl("/api/auth/skip"), {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enable_auth: false }),
}) })
if (!response.ok) throw new Error("Failed to save preference") const data = await response.json()
console.log("[v0] Auth skip response:", data)
localStorage.setItem("proxmenux-auth-setup-complete", "true") if (!response.ok) {
throw new Error(data.error || "Failed to skip authentication")
}
console.log("[v0] Authentication skipped successfully")
setOpen(false) setOpen(false)
onComplete() onComplete()
} catch (err) { } catch (err) {
setError("Failed to save preference. Please try again.") console.error("[v0] Auth skip error:", err)
setError(err instanceof Error ? err.message : "Failed to save preference")
} finally { } finally {
setLoading(false) setLoading(false)
} }
@@ -76,29 +79,32 @@ export function AuthSetup({ onComplete }: AuthSetupProps) {
setLoading(true) setLoading(true)
try { try {
console.log("[v0] Setting up authentication...")
const response = await fetch(getApiUrl("/api/auth/setup"), { const response = await fetch(getApiUrl("/api/auth/setup"), {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({
username, username,
password, password,
enable_auth: true,
}), }),
}) })
const data = await response.json() const data = await response.json()
console.log("[v0] Auth setup response:", data)
if (!response.ok) { if (!response.ok) {
throw new Error(data.error || "Failed to setup authentication") throw new Error(data.error || "Failed to setup authentication")
} }
// Save token if (data.token) {
localStorage.setItem("proxmenux-auth-token", data.token) localStorage.setItem("proxmenux-auth-token", data.token)
localStorage.setItem("proxmenux-auth-setup-complete", "true") console.log("[v0] Authentication setup successful")
}
setOpen(false) setOpen(false)
onComplete() onComplete()
} catch (err) { } catch (err) {
console.error("[v0] Auth setup error:", err)
setError(err instanceof Error ? err.message : "Failed to setup authentication") setError(err instanceof Error ? err.message : "Failed to setup authentication")
} finally { } finally {
setLoading(false) setLoading(false)

View File

@@ -17,8 +17,13 @@ import {
Cpu, Cpu,
FileText, FileText,
Rocket, Rocket,
Zap,
Shield,
Link2,
Gauge,
} from "lucide-react" } from "lucide-react"
import Image from "next/image" import Image from "next/image"
import { Checkbox } from "./ui/checkbox"
interface OnboardingSlide { interface OnboardingSlide {
id: number id: number
@@ -27,6 +32,7 @@ interface OnboardingSlide {
image?: string image?: string
icon: React.ReactNode icon: React.ReactNode
gradient: string gradient: string
features?: { icon: React.ReactNode; text: string }[]
} }
const slides: OnboardingSlide[] = [ const slides: OnboardingSlide[] = [
@@ -40,6 +46,35 @@ const slides: OnboardingSlide[] = [
}, },
{ {
id: 1, id: 1,
title: "What's New in This Version",
description: "We've added exciting new features and improvements to make ProxMenux Monitor even better!",
icon: <Zap className="h-16 w-16" />,
gradient: "from-amber-500 via-orange-500 to-red-500",
features: [
{
icon: <Link2 className="h-5 w-5" />,
text: "Proxy Support - Access ProxMenux through reverse proxies with full functionality",
},
{
icon: <Shield className="h-5 w-5" />,
text: "Authentication System - Secure your dashboard with password protection",
},
{
icon: <Gauge className="h-5 w-5" />,
text: "PCIe Link Speed Detection - View NVMe drive connection speeds and detect performance issues",
},
{
icon: <HardDrive className="h-5 w-5" />,
text: "Enhanced Storage Display - Better formatting for disk sizes (auto-converts GB to TB when needed)",
},
{
icon: <Network className="h-5 w-5" />,
text: "SATA/SAS Information - View detailed interface information for all storage devices",
},
],
},
{
id: 2,
title: "System Overview", title: "System Overview",
description: description:
"Monitor your server's status in real-time: CPU, memory, temperature, system load and more. Everything in an intuitive and easy-to-understand dashboard.", "Monitor your server's status in real-time: CPU, memory, temperature, system load and more. Everything in an intuitive and easy-to-understand dashboard.",
@@ -48,7 +83,7 @@ const slides: OnboardingSlide[] = [
gradient: "from-blue-500 to-cyan-500", gradient: "from-blue-500 to-cyan-500",
}, },
{ {
id: 2, id: 3,
title: "Storage Management", title: "Storage Management",
description: description:
"Visualize the status of all your disks and volumes. Detailed information on capacity, usage, SMART health, temperature and performance of each storage device.", "Visualize the status of all your disks and volumes. Detailed information on capacity, usage, SMART health, temperature and performance of each storage device.",
@@ -57,7 +92,7 @@ const slides: OnboardingSlide[] = [
gradient: "from-cyan-500 to-teal-500", gradient: "from-cyan-500 to-teal-500",
}, },
{ {
id: 3, id: 4,
title: "Network Metrics", title: "Network Metrics",
description: description:
"Monitor network traffic in real-time. Bandwidth statistics, active interfaces, transfer speeds and historical usage graphs.", "Monitor network traffic in real-time. Bandwidth statistics, active interfaces, transfer speeds and historical usage graphs.",
@@ -66,7 +101,7 @@ const slides: OnboardingSlide[] = [
gradient: "from-teal-500 to-green-500", gradient: "from-teal-500 to-green-500",
}, },
{ {
id: 4, id: 5,
title: "Virtual Machines & Containers", title: "Virtual Machines & Containers",
description: description:
"Manage all your VMs and LXC containers from one place. Status, allocated resources, current usage and quick controls for each virtual machine.", "Manage all your VMs and LXC containers from one place. Status, allocated resources, current usage and quick controls for each virtual machine.",
@@ -75,7 +110,7 @@ const slides: OnboardingSlide[] = [
gradient: "from-green-500 to-emerald-500", gradient: "from-green-500 to-emerald-500",
}, },
{ {
id: 5, id: 6,
title: "Hardware Information", title: "Hardware Information",
description: description:
"Complete details of your server hardware: CPU, RAM, GPU, disks, network, UPS and more. Technical specifications, models, serial numbers and status of each component.", "Complete details of your server hardware: CPU, RAM, GPU, disks, network, UPS and more. Technical specifications, models, serial numbers and status of each component.",
@@ -84,7 +119,7 @@ const slides: OnboardingSlide[] = [
gradient: "from-emerald-500 to-blue-500", gradient: "from-emerald-500 to-blue-500",
}, },
{ {
id: 6, id: 7,
title: "System Logs", title: "System Logs",
description: description:
"Access system logs in real-time. Filter by event type, search for specific errors and keep complete track of your server activity. Download the displayed logs for further analysis.", "Access system logs in real-time. Filter by event type, search for specific errors and keep complete track of your server activity. Download the displayed logs for further analysis.",
@@ -93,7 +128,7 @@ const slides: OnboardingSlide[] = [
gradient: "from-blue-500 to-indigo-500", gradient: "from-blue-500 to-indigo-500",
}, },
{ {
id: 7, id: 8,
title: "Ready for the Future!", title: "Ready for the Future!",
description: description:
"ProxMenux Monitor is prepared to receive updates and improvements that will be added gradually, improving the user experience and being able to execute ProxMenux functions from the web panel.", "ProxMenux Monitor is prepared to receive updates and improvements that will be added gradually, improving the user experience and being able to execute ProxMenux functions from the web panel.",
@@ -106,6 +141,7 @@ export function OnboardingCarousel() {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [currentSlide, setCurrentSlide] = useState(0) const [currentSlide, setCurrentSlide] = useState(0)
const [direction, setDirection] = useState<"next" | "prev">("next") const [direction, setDirection] = useState<"next" | "prev">("next")
const [dontShowAgain, setDontShowAgain] = useState(false)
useEffect(() => { useEffect(() => {
const hasSeenOnboarding = localStorage.getItem("proxmenux-onboarding-seen") const hasSeenOnboarding = localStorage.getItem("proxmenux-onboarding-seen")
@@ -119,6 +155,9 @@ export function OnboardingCarousel() {
setDirection("next") setDirection("next")
setCurrentSlide(currentSlide + 1) setCurrentSlide(currentSlide + 1)
} else { } else {
if (dontShowAgain) {
localStorage.setItem("proxmenux-onboarding-seen", "true")
}
setOpen(false) setOpen(false)
} }
} }
@@ -131,11 +170,9 @@ export function OnboardingCarousel() {
} }
const handleSkip = () => { const handleSkip = () => {
setOpen(false) if (dontShowAgain) {
}
const handleDontShowAgain = () => {
localStorage.setItem("proxmenux-onboarding-seen", "true") localStorage.setItem("proxmenux-onboarding-seen", "true")
}
setOpen(false) setOpen(false)
} }
@@ -205,6 +242,20 @@ export function OnboardingCarousel() {
</p> </p>
</div> </div>
{slide.features && (
<div className="space-y-3 py-2">
{slide.features.map((feature, index) => (
<div
key={index}
className="flex items-start gap-3 p-3 rounded-lg bg-muted/50 border border-border/50"
>
<div className="text-blue-500 mt-0.5">{feature.icon}</div>
<p className="text-sm text-foreground leading-relaxed">{feature.text}</p>
</div>
))}
</div>
)}
{/* Progress dots */} {/* Progress dots */}
<div className="flex items-center justify-center gap-2 py-2 md:py-4"> <div className="flex items-center justify-center gap-2 py-2 md:py-4">
{slides.map((_, index) => ( {slides.map((_, index) => (
@@ -255,17 +306,19 @@ export function OnboardingCarousel() {
</div> </div>
</div> </div>
{/* Don't show again */} <div className="flex items-center justify-center gap-2 pt-2">
{currentSlide === slides.length - 1 && ( <Checkbox
<div className="text-center pt-2"> id="dont-show-again"
<button checked={dontShowAgain}
onClick={handleDontShowAgain} onCheckedChange={(checked) => setDontShowAgain(checked as boolean)}
className="text-sm text-muted-foreground hover:text-foreground transition-colors underline" />
<label
htmlFor="dont-show-again"
className="text-sm text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
> >
Don't show again Don't show this again
</button> </label>
</div> </div>
)}
</div> </div>
</div> </div>
</DialogContent> </DialogContent>

View File

@@ -13,6 +13,7 @@ import { SystemLogs } from "./system-logs"
import { OnboardingCarousel } from "./onboarding-carousel" import { OnboardingCarousel } from "./onboarding-carousel"
import { AuthSetup } from "./auth-setup" import { AuthSetup } from "./auth-setup"
import { Login } from "./login" import { Login } from "./login"
import { Settings } from "./settings"
import { getApiUrl } from "../lib/api-config" import { getApiUrl } from "../lib/api-config"
import { import {
RefreshCw, RefreshCw,
@@ -27,6 +28,7 @@ import {
Box, Box,
Cpu, Cpu,
FileText, FileText,
SettingsIcon,
} from "lucide-react" } from "lucide-react"
import Image from "next/image" import Image from "next/image"
import { ThemeToggle } from "./theme-toggle" import { ThemeToggle } from "./theme-toggle"
@@ -220,6 +222,8 @@ export function ProxmoxDashboard() {
return "Hardware" return "Hardware"
case "logs": case "logs":
return "System Logs" return "System Logs"
case "settings":
return "Settings"
default: default:
return "Navigation Menu" return "Navigation Menu"
} }
@@ -285,31 +289,47 @@ export function ProxmoxDashboard() {
useEffect(() => { useEffect(() => {
const checkAuth = async () => { const checkAuth = async () => {
console.log("[v0] Checking authentication status...")
try { try {
const token = localStorage.getItem("proxmenux-auth-token") const token = localStorage.getItem("proxmenux-auth-token")
const headers: HeadersInit = { "Content-Type": "application/json" } const headers: HeadersInit = { "Content-Type": "application/json" }
if (token) { if (token) {
headers["Authorization"] = `Bearer ${token}` headers["Authorization"] = `Bearer ${token}`
console.log("[v0] Found token in localStorage")
} else {
console.log("[v0] No token found in localStorage")
} }
const response = await fetch(getApiUrl("/api/auth/status"), { const apiUrl = getApiUrl("/api/auth/status")
console.log("[v0] Calling auth status API:", apiUrl)
const response = await fetch(apiUrl, {
headers, headers,
}) })
const data = await response.json() const data = await response.json()
console.log("[v0] Auth status response:", data)
const authConfigured = data.auth_enabled || data.authenticated
setAuthRequired(data.auth_enabled) setAuthRequired(data.auth_enabled)
setIsAuthenticated(data.authenticated) setIsAuthenticated(data.authenticated)
setAuthSetupComplete(localStorage.getItem("proxmenux-auth-setup-complete") === "true") setAuthSetupComplete(authConfigured)
setAuthChecked(true) setAuthChecked(true)
// Setup token refresh if authenticated console.log("[v0] Auth state:", {
authRequired: data.auth_enabled,
isAuthenticated: data.authenticated,
authSetupComplete: authConfigured,
})
if (data.authenticated && token) { if (data.authenticated && token) {
setupTokenRefresh() setupTokenRefresh()
} }
} catch (error) { } catch (error) {
console.error("[v0] Failed to check auth status:", error) console.error("[v0] Failed to check auth status:", error)
setAuthSetupComplete(false)
setAuthChecked(true) setAuthChecked(true)
} }
} }
@@ -456,7 +476,7 @@ export function ProxmoxDashboard() {
> >
<div className="container mx-auto px-4 md:px-6 pt-4 md:pt-6"> <div className="container mx-auto px-4 md:px-6 pt-4 md:pt-6">
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-0"> <Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-0">
<TabsList className="hidden md:grid w-full grid-cols-6 bg-card border border-border"> <TabsList className="hidden md:grid w-full grid-cols-7 bg-card border border-border">
<TabsTrigger <TabsTrigger
value="overview" value="overview"
className="data-[state=active]:bg-blue-500 data-[state=active]:text-white data-[state=active]:rounded-md" className="data-[state=active]:bg-blue-500 data-[state=active]:text-white data-[state=active]:rounded-md"
@@ -493,6 +513,12 @@ export function ProxmoxDashboard() {
> >
System Logs System Logs
</TabsTrigger> </TabsTrigger>
<TabsTrigger
value="settings"
className="data-[state=active]:bg-blue-500 data-[state=active]:text-white data-[state=active]:rounded-md"
>
Settings
</TabsTrigger>
</TabsList> </TabsList>
<Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}> <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>
@@ -601,6 +627,21 @@ export function ProxmoxDashboard() {
<FileText className="h-5 w-5" /> <FileText className="h-5 w-5" />
<span>System Logs</span> <span>System Logs</span>
</Button> </Button>
<Button
variant="ghost"
onClick={() => {
setActiveTab("settings")
setMobileMenuOpen(false)
}}
className={`w-full justify-start gap-3 ${
activeTab === "settings"
? "bg-blue-500/10 text-blue-500 border-l-4 border-blue-500 rounded-l-none"
: ""
}`}
>
<SettingsIcon className="h-5 w-5" />
<span>Settings</span>
</Button>
</div> </div>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
@@ -633,6 +674,10 @@ export function ProxmoxDashboard() {
<TabsContent value="logs" className="space-y-4 md:space-y-6 mt-0"> <TabsContent value="logs" className="space-y-4 md:space-y-6 mt-0">
<SystemLogs key={`logs-${componentKey}`} /> <SystemLogs key={`logs-${componentKey}`} />
</TabsContent> </TabsContent>
<TabsContent value="settings" className="space-y-4 md:space-y-6 mt-0">
<Settings key={`settings-${componentKey}`} />
</TabsContent>
</Tabs> </Tabs>
<footer className="mt-8 md:mt-12 pt-4 md:pt-6 border-t border-border text-center text-xs md:text-sm text-muted-foreground"> <footer className="mt-8 md:mt-12 pt-4 md:pt-6 border-t border-border text-center text-xs md:text-sm text-muted-foreground">

View File

@@ -0,0 +1,425 @@
"use client"
import { useState, useEffect } from "react"
import { Button } from "./ui/button"
import { Input } from "./ui/input"
import { Label } from "./ui/label"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card"
import { Shield, Lock, User, AlertCircle, CheckCircle, Info } from "lucide-react"
import { getApiUrl } from "../lib/api-config"
export function Settings() {
const [authEnabled, setAuthEnabled] = useState(false)
const [loading, setLoading] = useState(false)
const [error, setError] = useState("")
const [success, setSuccess] = useState("")
// Setup form state
const [showSetupForm, setShowSetupForm] = useState(false)
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const [confirmPassword, setConfirmPassword] = useState("")
// Change password form state
const [showChangePassword, setShowChangePassword] = useState(false)
const [currentPassword, setCurrentPassword] = useState("")
const [newPassword, setNewPassword] = useState("")
const [confirmNewPassword, setConfirmNewPassword] = useState("")
useEffect(() => {
checkAuthStatus()
}, [])
const checkAuthStatus = async () => {
try {
const response = await fetch(getApiUrl("/api/auth/status"))
const data = await response.json()
setAuthEnabled(data.auth_enabled || false)
} catch (err) {
console.error("Failed to check auth status:", err)
}
}
const handleEnableAuth = async () => {
setError("")
setSuccess("")
if (!username || !password) {
setError("Please fill in all fields")
return
}
if (password !== confirmPassword) {
setError("Passwords do not match")
return
}
if (password.length < 6) {
setError("Password must be at least 6 characters")
return
}
setLoading(true)
try {
const response = await fetch(getApiUrl("/api/auth/setup"), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username,
password,
enable_auth: true,
}),
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || "Failed to enable authentication")
}
// Save token
localStorage.setItem("proxmenux-auth-token", data.token)
localStorage.setItem("proxmenux-auth-setup-complete", "true")
setSuccess("Authentication enabled successfully!")
setAuthEnabled(true)
setShowSetupForm(false)
setUsername("")
setPassword("")
setConfirmPassword("")
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to enable authentication")
} finally {
setLoading(false)
}
}
const handleDisableAuth = async () => {
if (
!confirm(
"Are you sure you want to disable authentication? This will remove password protection from your dashboard.",
)
) {
return
}
setLoading(true)
setError("")
setSuccess("")
try {
const response = await fetch(getApiUrl("/api/auth/setup"), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enable_auth: false }),
})
if (!response.ok) throw new Error("Failed to disable authentication")
localStorage.removeItem("proxmenux-auth-token")
setSuccess("Authentication disabled successfully!")
setAuthEnabled(false)
} catch (err) {
setError("Failed to disable authentication. Please try again.")
} finally {
setLoading(false)
}
}
const handleChangePassword = async () => {
setError("")
setSuccess("")
if (!currentPassword || !newPassword) {
setError("Please fill in all fields")
return
}
if (newPassword !== confirmNewPassword) {
setError("New passwords do not match")
return
}
if (newPassword.length < 6) {
setError("Password must be at least 6 characters")
return
}
setLoading(true)
try {
const response = await fetch(getApiUrl("/api/auth/change-password"), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("proxmenux-auth-token")}`,
},
body: JSON.stringify({
current_password: currentPassword,
new_password: newPassword,
}),
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || "Failed to change password")
}
// Update token if provided
if (data.token) {
localStorage.setItem("proxmenux-auth-token", data.token)
}
setSuccess("Password changed successfully!")
setShowChangePassword(false)
setCurrentPassword("")
setNewPassword("")
setConfirmNewPassword("")
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to change password")
} finally {
setLoading(false)
}
}
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Settings</h1>
<p className="text-muted-foreground mt-2">Manage your dashboard security and preferences</p>
</div>
{/* Authentication Settings */}
<Card>
<CardHeader>
<div className="flex items-center gap-2">
<Shield className="h-5 w-5 text-blue-500" />
<CardTitle>Authentication</CardTitle>
</div>
<CardDescription>Protect your dashboard with username and password authentication</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{error && (
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-3 flex items-start gap-2">
<AlertCircle className="h-5 w-5 text-red-500 flex-shrink-0 mt-0.5" />
<p className="text-sm text-red-500">{error}</p>
</div>
)}
{success && (
<div className="bg-green-500/10 border border-green-500/20 rounded-lg p-3 flex items-start gap-2">
<CheckCircle className="h-5 w-5 text-green-500 flex-shrink-0 mt-0.5" />
<p className="text-sm text-green-500">{success}</p>
</div>
)}
<div className="flex items-center justify-between p-4 bg-muted/50 rounded-lg">
<div className="flex items-center gap-3">
<div
className={`w-10 h-10 rounded-full flex items-center justify-center ${authEnabled ? "bg-green-500/10" : "bg-gray-500/10"}`}
>
<Lock className={`h-5 w-5 ${authEnabled ? "text-green-500" : "text-gray-500"}`} />
</div>
<div>
<p className="font-medium">Authentication Status</p>
<p className="text-sm text-muted-foreground">
{authEnabled ? "Password protection is enabled" : "No password protection"}
</p>
</div>
</div>
<div
className={`px-3 py-1 rounded-full text-sm font-medium ${authEnabled ? "bg-green-500/10 text-green-500" : "bg-gray-500/10 text-gray-500"}`}
>
{authEnabled ? "Enabled" : "Disabled"}
</div>
</div>
{!authEnabled && !showSetupForm && (
<div className="space-y-3">
<div className="bg-blue-500/10 border border-blue-500/20 rounded-lg p-3 flex items-start gap-2">
<Info className="h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" />
<p className="text-sm text-blue-500">
Enable authentication to protect your dashboard when accessing from non-private networks.
</p>
</div>
<Button onClick={() => setShowSetupForm(true)} className="w-full bg-blue-500 hover:bg-blue-600">
<Shield className="h-4 w-4 mr-2" />
Enable Authentication
</Button>
</div>
)}
{!authEnabled && showSetupForm && (
<div className="space-y-4 border border-border rounded-lg p-4">
<h3 className="font-semibold">Setup Authentication</h3>
<div className="space-y-2">
<Label htmlFor="setup-username">Username</Label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="setup-username"
type="text"
placeholder="Enter username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="pl-10"
disabled={loading}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="setup-password">Password</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="setup-password"
type="password"
placeholder="Enter password (min 6 characters)"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="pl-10"
disabled={loading}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="setup-confirm-password">Confirm Password</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="setup-confirm-password"
type="password"
placeholder="Confirm password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="pl-10"
disabled={loading}
/>
</div>
</div>
<div className="flex gap-2">
<Button onClick={handleEnableAuth} className="flex-1 bg-blue-500 hover:bg-blue-600" disabled={loading}>
{loading ? "Enabling..." : "Enable"}
</Button>
<Button onClick={() => setShowSetupForm(false)} variant="outline" className="flex-1" disabled={loading}>
Cancel
</Button>
</div>
</div>
)}
{authEnabled && (
<div className="space-y-3">
{!showChangePassword && (
<Button onClick={() => setShowChangePassword(true)} variant="outline" className="w-full">
<Lock className="h-4 w-4 mr-2" />
Change Password
</Button>
)}
{showChangePassword && (
<div className="space-y-4 border border-border rounded-lg p-4">
<h3 className="font-semibold">Change Password</h3>
<div className="space-y-2">
<Label htmlFor="current-password">Current Password</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="current-password"
type="password"
placeholder="Enter current password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
className="pl-10"
disabled={loading}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="new-password">New Password</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="new-password"
type="password"
placeholder="Enter new password (min 6 characters)"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="pl-10"
disabled={loading}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="confirm-new-password">Confirm New Password</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="confirm-new-password"
type="password"
placeholder="Confirm new password"
value={confirmNewPassword}
onChange={(e) => setConfirmNewPassword(e.target.value)}
className="pl-10"
disabled={loading}
/>
</div>
</div>
<div className="flex gap-2">
<Button
onClick={handleChangePassword}
className="flex-1 bg-blue-500 hover:bg-blue-600"
disabled={loading}
>
{loading ? "Changing..." : "Change Password"}
</Button>
<Button
onClick={() => setShowChangePassword(false)}
variant="outline"
className="flex-1"
disabled={loading}
>
Cancel
</Button>
</div>
</div>
)}
<Button onClick={handleDisableAuth} variant="destructive" className="w-full" disabled={loading}>
Disable Authentication
</Button>
</div>
)}
</CardContent>
</Card>
{/* About Section */}
<Card>
<CardHeader>
<CardTitle>About</CardTitle>
<CardDescription>ProxMenux Monitor information</CardDescription>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex justify-between">
<span className="text-muted-foreground">Version</span>
<span className="font-medium">1.0.1</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Build</span>
<span className="font-medium">Debian Package</span>
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -1,10 +1,13 @@
import { LayoutDashboard, HardDrive, Network, Server, Cpu, FileText } from "path-to-icons" import { LayoutDashboard, HardDrive, Network, Server, Cpu, FileText, SettingsIcon } from "lucide-react"
const menuItems = [ const menuItems = [
{ name: "Overview", href: "/", icon: LayoutDashboard }, { name: "Overview", href: "/", icon: LayoutDashboard },
{ name: "Storage", href: "/storage", icon: HardDrive }, { name: "Storage", href: "/storage", icon: HardDrive },
{ name: "Network", href: "/network", icon: Network }, { name: "Network", href: "/network", icon: Network },
{ name: "Virtual Machines", href: "/virtual-machines", icon: Server }, { name: "Virtual Machines", href: "/virtual-machines", icon: Server },
{ name: "Hardware", href: "/hardware", icon: Cpu }, // New Hardware section { name: "Hardware", href: "/hardware", icon: Cpu },
{ name: "System Logs", href: "/logs", icon: FileText }, { name: "System Logs", href: "/logs", icon: FileText },
{ name: "Settings", href: "/settings", icon: SettingsIcon },
] ]
// ... existing code ...