From f2316fdd3a7d24e11629876e99e268a63dea592c Mon Sep 17 00:00:00 2001 From: riri-314 Date: Tue, 18 Nov 2025 10:58:06 +0100 Subject: [PATCH 1/6] Add setting to change network unit --- AppImage/components/settings.tsx | 601 ++++++++++++++++++++----------- 1 file changed, 390 insertions(+), 211 deletions(-) diff --git a/AppImage/components/settings.tsx b/AppImage/components/settings.tsx index 81b6fe8..ed482b4 100644 --- a/AppImage/components/settings.tsx +++ b/AppImage/components/settings.tsx @@ -1,10 +1,16 @@ -"use client" +"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 { 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, @@ -19,106 +25,130 @@ import { Copy, Eye, EyeOff, -} from "lucide-react" -import { APP_VERSION } from "./release-notes-modal" -import { getApiUrl, fetchApi } from "../lib/api-config" -import { TwoFactorSetup } from "./two-factor-setup" + 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 + 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("") + 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("") + 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 [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 [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>({ + 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) + 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() - }, []) + 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"; + // wait 2 seconds to simulate loading + console.log("[v0] Loaded network unit from localStorage:", networkUnit); + setTimeout(() => { + setNetworkUnitSettings(networkUnit); + setLoadingUnitSettings(false); + }, 1000); + }; 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 + 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) + 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() + const response = await fetch(getApiUrl("/api/proxmenux/installed-tools")); + const data = await response.json(); if (data.success) { - setProxmenuxTools(data.installed_tools || []) + setProxmenuxTools(data.installed_tools || []); } } catch (err) { - console.error("Failed to load ProxMenux tools:", err) + console.error("Failed to load ProxMenux tools:", err); } finally { - setLoadingTools(false) + setLoadingTools(false); } - } + }; const handleEnableAuth = async () => { - setError("") - setSuccess("") + setError(""); + setSuccess(""); if (!username || !password) { - setError("Please fill in all fields") - return + setError("Please fill in all fields"); + return; } if (password !== confirmPassword) { - setError("Passwords do not match") - return + setError("Passwords do not match"); + return; } if (password.length < 6) { - setError("Password must be at least 6 characters") - return + setError("Password must be at least 6 characters"); + return; } - setLoading(true) + setLoading(true); try { const response = await fetch(getApiUrl("/api/auth/setup"), { @@ -129,145 +159,155 @@ export function Settings() { password, enable_auth: true, }), - }) + }); - const data = await response.json() + const data = await response.json(); if (!response.ok) { - throw new Error(data.error || "Failed to enable authentication") + 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") + 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("") + setSuccess("Authentication enabled successfully!"); + setAuthEnabled(true); + setShowSetupForm(false); + setUsername(""); + setPassword(""); + setConfirmPassword(""); } catch (err) { - setError(err instanceof Error ? err.message : "Failed to enable authentication") + setError( + err instanceof Error ? err.message : "Failed to enable authentication" + ); } finally { - setLoading(false) + setLoading(false); } - } + }; const handleDisableAuth = async () => { if ( !confirm( - "Are you sure you want to disable authentication? This will remove password protection from your dashboard.", + "Are you sure you want to disable authentication? This will remove password protection from your dashboard." ) ) { - return + return; } - setLoading(true) - setError("") - setSuccess("") + setLoading(true); + setError(""); + setSuccess(""); try { - const token = localStorage.getItem("proxmenux-auth-token") + 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() + const data = await response.json(); if (!response.ok) { - throw new Error(data.message || "Failed to disable authentication") + throw new Error(data.message || "Failed to disable authentication"); } - localStorage.removeItem("proxmenux-auth-token") - localStorage.removeItem("proxmenux-auth-setup-complete") + localStorage.removeItem("proxmenux-auth-token"); + localStorage.removeItem("proxmenux-auth-setup-complete"); - setSuccess("Authentication disabled successfully! Reloading...") + setSuccess("Authentication disabled successfully! Reloading..."); setTimeout(() => { - window.location.reload() - }, 1000) + window.location.reload(); + }, 1000); } catch (err) { - setError(err instanceof Error ? err.message : "Failed to disable authentication. Please try again.") + setError( + err instanceof Error + ? err.message + : "Failed to disable authentication. Please try again." + ); } finally { - setLoading(false) + setLoading(false); } - } + }; const handleChangePassword = async () => { - setError("") - setSuccess("") + setError(""); + setSuccess(""); if (!currentPassword || !newPassword) { - setError("Please fill in all fields") - return + setError("Please fill in all fields"); + return; } if (newPassword !== confirmNewPassword) { - setError("New passwords do not match") - return + setError("New passwords do not match"); + return; } if (newPassword.length < 6) { - setError("Password must be at least 6 characters") - return + setError("Password must be at least 6 characters"); + return; } - setLoading(true) + 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")}`, + Authorization: `Bearer ${localStorage.getItem( + "proxmenux-auth-token" + )}`, }, body: JSON.stringify({ current_password: currentPassword, new_password: newPassword, }), - }) + }); - const data = await response.json() + const data = await response.json(); if (!response.ok) { - throw new Error(data.error || "Failed to change password") + throw new Error(data.error || "Failed to change password"); } // Update token if provided if (data.token) { - localStorage.setItem("proxmenux-auth-token", data.token) + localStorage.setItem("proxmenux-auth-token", data.token); } - setSuccess("Password changed successfully!") - setShowChangePassword(false) - setCurrentPassword("") - setNewPassword("") - setConfirmNewPassword("") + setSuccess("Password changed successfully!"); + setShowChangePassword(false); + setCurrentPassword(""); + setNewPassword(""); + setConfirmNewPassword(""); } catch (err) { - setError(err instanceof Error ? err.message : "Failed to change password") + setError( + err instanceof Error ? err.message : "Failed to change password" + ); } finally { - setLoading(false) + setLoading(false); } - } + }; const handleDisable2FA = async () => { - setError("") - setSuccess("") + setError(""); + setSuccess(""); if (!disable2FAPassword) { - setError("Please enter your password") - return + setError("Please enter your password"); + return; } - setLoading(true) + setLoading(true); try { - const token = localStorage.getItem("proxmenux-auth-token") + const token = localStorage.getItem("proxmenux-auth-token"); const response = await fetch(getApiUrl("/api/auth/totp/disable"), { method: "POST", headers: { @@ -275,47 +315,47 @@ export function Settings() { Authorization: `Bearer ${token}`, }, body: JSON.stringify({ password: disable2FAPassword }), - }) + }); - const data = await response.json() + const data = await response.json(); if (!response.ok) { - throw new Error(data.message || "Failed to disable 2FA") + throw new Error(data.message || "Failed to disable 2FA"); } - setSuccess("2FA disabled successfully!") - setTotpEnabled(false) - setShow2FADisable(false) - setDisable2FAPassword("") - checkAuthStatus() + setSuccess("2FA disabled successfully!"); + setTotpEnabled(false); + setShow2FADisable(false); + setDisable2FAPassword(""); + checkAuthStatus(); } catch (err) { - setError(err instanceof Error ? err.message : "Failed to disable 2FA") + setError(err instanceof Error ? err.message : "Failed to disable 2FA"); } finally { - setLoading(false) + setLoading(false); } - } + }; const handleLogout = () => { - localStorage.removeItem("proxmenux-auth-token") - localStorage.removeItem("proxmenux-auth-setup-complete") - window.location.reload() - } + localStorage.removeItem("proxmenux-auth-token"); + localStorage.removeItem("proxmenux-auth-setup-complete"); + window.location.reload(); + }; const handleGenerateApiToken = async () => { - setError("") - setSuccess("") + setError(""); + setSuccess(""); if (!tokenPassword) { - setError("Please enter your password") - return + setError("Please enter your password"); + return; } if (totpEnabled && !tokenTotpCode) { - setError("Please enter your 2FA code") - return + setError("Please enter your 2FA code"); + return; } - setGeneratingToken(true) + setGeneratingToken(true); try { const data = await fetchApi("/api/auth/generate-api-token", { @@ -325,47 +365,55 @@ export function Settings() { password: tokenPassword, totp_token: totpEnabled ? tokenTotpCode : undefined, }), - }) + }); if (!data.success) { - setError(data.message || data.error || "Failed to generate API token") - return + setError(data.message || data.error || "Failed to generate API token"); + return; } if (!data.token) { - setError("No token received from server") - return + 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("") + 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.") + setError( + err instanceof Error + ? err.message + : "Failed to generate API token. Please try again." + ); } finally { - setGeneratingToken(false) + setGeneratingToken(false); } - } + }; const copyApiToken = () => { - navigator.clipboard.writeText(apiToken) - setTokenCopied(true) - setTimeout(() => setTokenCopied(false), 2000) - } + 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

+

+ Manage your dashboard security and preferences +

{/* Authentication Settings */} @@ -375,7 +423,9 @@ export function Settings() { Authentication
- Protect your dashboard with username and password authentication + + Protect your dashboard with username and password authentication + {error && ( @@ -395,19 +445,31 @@ export function Settings() {
- +

Authentication Status

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

{authEnabled ? "Enabled" : "Disabled"}
@@ -418,10 +480,14 @@ export function Settings() {

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

- @@ -481,10 +547,19 @@ export function Settings() {
- -
@@ -493,13 +568,21 @@ export function Settings() { {authEnabled && (
- {!showChangePassword && ( - @@ -542,7 +625,9 @@ export function Settings() {
- +
-

Two-Factor Authentication (2FA)

+

+ Two-Factor Authentication (2FA) +

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

- @@ -601,11 +692,17 @@ export function Settings() {
-

2FA is enabled

+

+ 2FA is enabled +

{!show2FADisable && ( - @@ -613,8 +710,12 @@ export function Settings() { {show2FADisable && (
-

Disable Two-Factor Authentication

-

Enter your password to confirm

+

+ Disable Two-Factor Authentication +

+

+ Enter your password to confirm +

@@ -625,7 +726,9 @@ export function Settings() { type="password" placeholder="Enter your password" value={disable2FAPassword} - onChange={(e) => setDisable2FAPassword(e.target.value)} + onChange={(e) => + setDisable2FAPassword(e.target.value) + } className="pl-10" disabled={loading} /> @@ -633,14 +736,19 @@ export function Settings() {
-
)} -
@@ -671,7 +784,8 @@ export function Settings() { API Access Tokens
- Generate long-lived API tokens for external integrations like Homepage and Home Assistant + Generate long-lived API tokens for external integrations like + Homepage and Home Assistant @@ -705,7 +819,10 @@ export function Settings() {
{!showApiTokenSection && !apiToken && ( - @@ -763,10 +880,10 @@ export function Settings() { - @@ -831,18 +961,21 @@ export function Settings() {

How to use this token:

-

# Add to request headers:

+

+ # Add to request headers: +

Authorization: Bearer YOUR_TOKEN_HERE

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

@@ -544,19 +500,10 @@ export function Settings() {
- -
@@ -565,21 +512,13 @@ export function Settings() { {authEnabled && (
- {!showChangePassword && ( - @@ -622,9 +561,7 @@ export function Settings() {
- +
-

- Two-Factor Authentication (2FA) -

+

Two-Factor Authentication (2FA)

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

- @@ -689,17 +620,11 @@ export function Settings() {
-

- 2FA is enabled -

+

2FA is enabled

{!show2FADisable && ( - @@ -707,12 +632,8 @@ export function Settings() { {show2FADisable && (
-

- Disable Two-Factor Authentication -

-

- Enter your password to confirm -

+

Disable Two-Factor Authentication

+

Enter your password to confirm

@@ -723,9 +644,7 @@ export function Settings() { type="password" placeholder="Enter your password" value={disable2FAPassword} - onChange={(e) => - setDisable2FAPassword(e.target.value) - } + onChange={(e) => setDisable2FAPassword(e.target.value)} className="pl-10" disabled={loading} /> @@ -733,19 +652,14 @@ export function Settings() {
-
)} -
@@ -781,8 +690,7 @@ export function Settings() { API Access Tokens
- Generate long-lived API tokens for external integrations like - Homepage and Home Assistant + Generate long-lived API tokens for external integrations like Homepage and Home Assistant @@ -816,10 +724,7 @@ export function Settings() {
{!showApiTokenSection && !apiToken && ( - @@ -877,10 +782,10 @@ export function Settings() { - @@ -958,21 +850,18 @@ export function Settings() {

How to use this token:

-

- # Add to request headers: -

+

# Add to request headers:

Authorization: Bearer YOUR_TOKEN_HERE

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