"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, LogOut, Wrench, Package, Key, Copy, Eye, EyeOff, Ruler, } from "lucide-react"; import { APP_VERSION } from "./release-notes-modal"; import { getApiUrl, fetchApi } from "../lib/api-config"; import { TwoFactorSetup } from "./two-factor-setup"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; interface ProxMenuxTool { key: string; name: string; enabled: boolean; } export function Settings() { const [authEnabled, setAuthEnabled] = useState(false); const [totpEnabled, setTotpEnabled] = 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(""); const [show2FASetup, setShow2FASetup] = useState(false); const [show2FADisable, setShow2FADisable] = useState(false); const [disable2FAPassword, setDisable2FAPassword] = useState(""); const [proxmenuxTools, setProxmenuxTools] = useState([]); const [loadingTools, setLoadingTools] = useState(true); const [expandedVersions, setExpandedVersions] = useState< Record >({ [APP_VERSION]: true, // Current version expanded by default }); const [networkUnitSettings, setNetworkUnitSettings] = useState("Bytes"); const [loadingUnitSettings, setLoadingUnitSettings] = useState(true); // API Token state management const [showApiTokenSection, setShowApiTokenSection] = useState(false); const [apiToken, setApiToken] = useState(""); const [apiTokenVisible, setApiTokenVisible] = useState(false); const [tokenPassword, setTokenPassword] = useState(""); const [tokenTotpCode, setTokenTotpCode] = useState(""); const [generatingToken, setGeneratingToken] = useState(false); const [tokenCopied, setTokenCopied] = useState(false); useEffect(() => { checkAuthStatus(); loadProxmenuxTools(); getUnitsSettings(); }, []); const changeNetworkUnit = (unit: string) => { localStorage.setItem("proxmenux-network-unit", unit); setNetworkUnitSettings(unit); }; const getUnitsSettings = () => { const networkUnit = localStorage.getItem("proxmenux-network-unit") || "Bytes"; console.log("[v0] Loaded network unit from localStorage:", networkUnit); setNetworkUnitSettings(networkUnit); setLoadingUnitSettings(false); }; const checkAuthStatus = async () => { try { const response = await fetch(getApiUrl("/api/auth/status")); const data = await response.json(); setAuthEnabled(data.auth_enabled || false); setTotpEnabled(data.totp_enabled || false); // Get 2FA status } catch (err) { console.error("Failed to check auth status:", err); } }; const loadProxmenuxTools = async () => { try { const response = await fetch(getApiUrl("/api/proxmenux/installed-tools")); const data = await response.json(); if (data.success) { setProxmenuxTools(data.installed_tools || []); } } catch (err) { console.error("Failed to load ProxMenux tools:", err); } finally { setLoadingTools(false); } }; 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 token = localStorage.getItem("proxmenux-auth-token"); const response = await fetch(getApiUrl("/api/auth/disable"), { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, }); const data = await response.json(); if (!response.ok) { throw new Error(data.message || "Failed to disable authentication"); } localStorage.removeItem("proxmenux-auth-token"); localStorage.removeItem("proxmenux-auth-setup-complete"); setSuccess("Authentication disabled successfully! Reloading..."); setTimeout(() => { window.location.reload(); }, 1000); } catch (err) { setError( err instanceof Error ? err.message : "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); } }; const handleDisable2FA = async () => { setError(""); setSuccess(""); if (!disable2FAPassword) { setError("Please enter your password"); return; } setLoading(true); try { const token = localStorage.getItem("proxmenux-auth-token"); const response = await fetch(getApiUrl("/api/auth/totp/disable"), { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ password: disable2FAPassword }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.message || "Failed to disable 2FA"); } setSuccess("2FA disabled successfully!"); setTotpEnabled(false); setShow2FADisable(false); setDisable2FAPassword(""); checkAuthStatus(); } catch (err) { setError(err instanceof Error ? err.message : "Failed to disable 2FA"); } finally { setLoading(false); } }; const handleLogout = () => { localStorage.removeItem("proxmenux-auth-token"); localStorage.removeItem("proxmenux-auth-setup-complete"); window.location.reload(); }; const handleGenerateApiToken = async () => { setError(""); setSuccess(""); if (!tokenPassword) { setError("Please enter your password"); return; } if (totpEnabled && !tokenTotpCode) { setError("Please enter your 2FA code"); return; } setGeneratingToken(true); try { const data = await fetchApi("/api/auth/generate-api-token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ password: tokenPassword, totp_token: totpEnabled ? tokenTotpCode : undefined, }), }); if (!data.success) { setError(data.message || data.error || "Failed to generate API token"); return; } if (!data.token) { setError("No token received from server"); return; } setApiToken(data.token); setSuccess( "API token generated successfully! Make sure to copy it now as you won't be able to see it again." ); setTokenPassword(""); setTokenTotpCode(""); } catch (err) { setError( err instanceof Error ? err.message : "Failed to generate API token. Please try again." ); } finally { setGeneratingToken(false); } }; const copyApiToken = () => { navigator.clipboard.writeText(apiToken); setTokenCopied(true); setTimeout(() => setTokenCopied(false), 2000); }; const toggleVersion = (version: string) => { setExpandedVersions((prev) => ({ ...prev, [version]: !prev[version], })); }; return (

Settings

Manage your dashboard security and preferences

{/* Authentication Settings */}
Authentication
Protect your dashboard with username and password authentication
{error && (

{error}

)} {success && (

{success}

)}

Authentication Status

{authEnabled ? "Password protection is enabled" : "No password protection"}

{authEnabled ? "Enabled" : "Disabled"}
{!authEnabled && !showSetupForm && (

Enable authentication to protect your dashboard when accessing from non-private networks.

)} {!authEnabled && showSetupForm && (

Setup Authentication

setUsername(e.target.value)} className="pl-10" disabled={loading} />
setPassword(e.target.value)} className="pl-10" disabled={loading} />
setConfirmPassword(e.target.value)} className="pl-10" disabled={loading} />
)} {authEnabled && (
{!showChangePassword && ( )} {showChangePassword && (

Change Password

setCurrentPassword(e.target.value)} className="pl-10" disabled={loading} />
setNewPassword(e.target.value)} className="pl-10" disabled={loading} />
setConfirmNewPassword(e.target.value)} className="pl-10" disabled={loading} />
)} {!totpEnabled && (

Two-Factor Authentication (2FA)

Add an extra layer of security by requiring a code from your authenticator app in addition to your password.

)} {totpEnabled && (

2FA is enabled

{!show2FADisable && ( )} {show2FADisable && (

Disable Two-Factor Authentication

Enter your password to confirm

setDisable2FAPassword(e.target.value) } className="pl-10" disabled={loading} />
)}
)}
)}
{/* API Access Tokens */} {authEnabled && (
API Access Tokens
Generate long-lived API tokens for external integrations like Homepage and Home Assistant
{error && (

{error}

)} {success && (

{success}

)}

About API Tokens

  • Tokens are valid for 1 year
  • Use them to access APIs from external services
  • Include in Authorization header: Bearer YOUR_TOKEN
  • See README.md for complete integration examples
{!showApiTokenSection && !apiToken && ( )} {showApiTokenSection && !apiToken && (

Generate API Token

Enter your credentials to generate a new long-lived API token

setTokenPassword(e.target.value)} className="pl-10" disabled={generatingToken} />
{totpEnabled && (
setTokenTotpCode(e.target.value)} className="pl-10" maxLength={6} disabled={generatingToken} />
)}
)} {apiToken && (

Your API Token

⚠️ Important: Save this token now!

You won't be able to see it again. Store it securely.

{tokenCopied && (

Copied to clipboard!

)}

How to use this token:

# Add to request headers:

Authorization: Bearer YOUR_TOKEN_HERE

See the README documentation for complete integration examples with Homepage and Home Assistant.

)}
)} {/* ProxMenux unit settings*/}
ProxMenux unit settings
Change settings related to units
{loadingUnitSettings ? (
) : (
Network Unit Display
)} {/* ProxMenux Optimizations */}
ProxMenux Optimizations
System optimizations and utilities installed via ProxMenux
{loadingTools ? (
) : proxmenuxTools.length === 0 ? (

No ProxMenux optimizations installed yet

Run ProxMenux to configure system optimizations

) : (
Installed Tools {proxmenuxTools.length} active
{proxmenuxTools.map((tool) => (
{tool.name}
))}
)} setShow2FASetup(false)} onSuccess={() => { setSuccess("2FA enabled successfully!"); checkAuthStatus(); }} />
); }