-
-
- {statusIcon}
- {systemStatus.status}
-
-
-
+ {/* Desktop Actions */}
+
+
+
+
+
Node: {systemStatus.serverName}
+
-
-
- {statusIcon}
-
+
+ {statusIcon}
+ {systemStatus.status}
+
-
+
+ Uptime: {systemStatus.uptime || "N/A"}
+
+
+
e.stopPropagation()}>
+
+
+
+
+ {/* Mobile Actions */}
+
+
+ {statusIcon}
+ {systemStatus.status}
+
+
+
+
e.stopPropagation()}>
-
Uptime: {systemStatus.uptime}
+ {/* Mobile Server Info */}
+
+ Uptime: {systemStatus.uptime || "N/A"}
+
diff --git a/AppImage/components/settings.tsx b/AppImage/components/settings.tsx
index b031017..36ea073 100644
--- a/AppImage/components/settings.tsx
+++ b/AppImage/components/settings.tsx
@@ -5,7 +5,7 @@ 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 { Shield, Lock, User, AlertCircle, CheckCircle, Info, LogOut } from "lucide-react"
import { getApiUrl } from "../lib/api-config"
export function Settings() {
@@ -98,7 +98,7 @@ export function Settings() {
const handleDisableAuth = async () => {
if (
!confirm(
- "¿Estás seguro de que quieres deshabilitar la autenticación? Esto eliminará la protección por contraseña de tu dashboard y tendrás que configurarla de nuevo si quieres reactivarla.",
+ "Are you sure you want to disable authentication? This will remove password protection from your dashboard.",
)
) {
return
@@ -109,35 +109,31 @@ export function Settings() {
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 ${localStorage.getItem("proxmenux-auth-token")}`,
+ Authorization: `Bearer ${token}`,
},
})
const data = await response.json()
if (!response.ok) {
- throw new Error(data.error || "Failed to disable authentication")
+ throw new Error(data.message || "Failed to disable authentication")
}
localStorage.removeItem("proxmenux-auth-token")
- localStorage.removeItem("proxmenux-saved-username")
- localStorage.removeItem("proxmenux-saved-password")
localStorage.removeItem("proxmenux-auth-setup-complete")
- setSuccess("¡Autenticación deshabilitada correctamente! La página se recargará...")
+ setSuccess("Authentication disabled successfully! Reloading...")
- // Recargar la página después de 1.5 segundos
setTimeout(() => {
window.location.reload()
- }, 1500)
+ }, 1000)
} catch (err) {
- setError(
- err instanceof Error ? err.message : "No se pudo deshabilitar la autenticación. Por favor, inténtalo de nuevo.",
- )
+ setError(err instanceof Error ? err.message : "Failed to disable authentication. Please try again.")
} finally {
setLoading(false)
}
@@ -202,6 +198,7 @@ export function Settings() {
const handleLogout = () => {
localStorage.removeItem("proxmenux-auth-token")
+ localStorage.removeItem("proxmenux-auth-setup-complete")
window.location.reload()
}
@@ -338,6 +335,7 @@ export function Settings() {
{authEnabled && (
diff --git a/AppImage/scripts/auth_manager.py b/AppImage/scripts/auth_manager.py
index a36cdb6..9326737 100644
--- a/AppImage/scripts/auth_manager.py
+++ b/AppImage/scripts/auth_manager.py
@@ -26,7 +26,6 @@ AUTH_CONFIG_FILE = CONFIG_DIR / "auth.json"
JWT_SECRET = "proxmenux-monitor-secret-key-change-in-production"
JWT_ALGORITHM = "HS256"
TOKEN_EXPIRATION_HOURS = 24
-TOKEN_EXPIRATION_DAYS_REMEMBER = 30 # Token más largo para "recordar contraseña"
def ensure_config_dir():
@@ -95,18 +94,15 @@ def verify_password(password, password_hash):
return hash_password(password) == password_hash
-def generate_token(username, remember_me=False): # Añadido parámetro remember_me
+def generate_token(username):
"""Generate a JWT token for the given username"""
if not JWT_AVAILABLE:
return None
- expiration_delta = timedelta(days=TOKEN_EXPIRATION_DAYS_REMEMBER) if remember_me else timedelta(hours=TOKEN_EXPIRATION_HOURS)
-
payload = {
'username': username,
- 'exp': datetime.utcnow() + expiration_delta,
- 'iat': datetime.utcnow(),
- 'remember_me': remember_me # Guardar preferencia en el token
+ 'exp': datetime.utcnow() + timedelta(hours=TOKEN_EXPIRATION_HOURS),
+ 'iat': datetime.utcnow()
}
try:
@@ -151,7 +147,7 @@ def get_auth_status():
config = load_auth_config()
return {
"auth_enabled": config.get("enabled", False),
- "auth_configured": config.get("configured", False),
+ "auth_configured": config.get("configured", False), # Frontend expects this field name
"declined": config.get("declined", False),
"username": config.get("username") if config.get("enabled") else None,
"authenticated": False # Will be set to True by the route handler if token is valid
@@ -206,16 +202,15 @@ def disable_auth():
Disable authentication (different from decline - can be re-enabled)
Returns (success: bool, message: str)
"""
- config = {
- "enabled": False,
- "username": None,
- "password_hash": None,
- "declined": False,
- "configured": False
- }
+ config = load_auth_config()
+ config["enabled"] = False
+ config["username"] = None
+ config["password_hash"] = None
+ config["declined"] = False
+ config["configured"] = False
if save_auth_config(config):
- return True, "Authentication disabled successfully"
+ return True, "Authentication disabled"
else:
return False, "Failed to save configuration"
@@ -239,7 +234,7 @@ def enable_auth():
return False, "Failed to save configuration"
-def change_password(current_password, new_password): # Corregido nombre del parámetro
+def change_password(old_password, new_password):
"""
Change the authentication password
Returns (success: bool, message: str)
@@ -249,7 +244,7 @@ def change_password(current_password, new_password): # Corregido nombre del par
if not config.get("enabled"):
return False, "Authentication is not enabled"
- if not verify_password(current_password, config.get("password_hash", "")):
+ if not verify_password(old_password, config.get("password_hash", "")):
return False, "Current password is incorrect"
if len(new_password) < 6:
@@ -263,7 +258,7 @@ def change_password(current_password, new_password): # Corregido nombre del par
return False, "Failed to save new password"
-def authenticate(username, password, remember_me=False): # Añadido parámetro remember_me
+def authenticate(username, password):
"""
Authenticate a user with username and password
Returns (success: bool, token: str or None, message: str)
@@ -279,7 +274,7 @@ def authenticate(username, password, remember_me=False): # Añadido parámetro
if not verify_password(password, config.get("password_hash", "")):
return False, None, "Invalid username or password"
- token = generate_token(username, remember_me)
+ token = generate_token(username)
if token:
return True, token, "Authentication successful"
else:
diff --git a/AppImage/scripts/flask_auth_routes.py b/AppImage/scripts/flask_auth_routes.py
index 4e4fcf5..d8b3691 100644
--- a/AppImage/scripts/flask_auth_routes.py
+++ b/AppImage/scripts/flask_auth_routes.py
@@ -33,47 +33,28 @@ def auth_setup():
username = data.get('username')
password = data.get('password')
- if not username or not password:
- return jsonify({"success": False, "error": "Username and password are required"}), 400
-
success, message = auth_manager.setup_auth(username, password)
- if success:
- # Generate token for immediate login
- token = auth_manager.generate_token(username)
- return jsonify({"success": True, "message": message, "token": token})
- else:
- return jsonify({"success": False, "error": message}), 400
- except Exception as e:
- return jsonify({"success": False, "error": str(e)}), 500
-
-
-@auth_bp.route('/api/auth/skip', methods=['POST'])
-def auth_skip():
- """Skip authentication setup (user declined)"""
- try:
- success, message = auth_manager.decline_auth()
-
if success:
return jsonify({"success": True, "message": message})
else:
- return jsonify({"success": False, "error": message}), 400
+ return jsonify({"success": False, "message": message}), 400
except Exception as e:
- return jsonify({"success": False, "error": str(e)}), 500
+ return jsonify({"success": False, "message": str(e)}), 500
@auth_bp.route('/api/auth/decline', methods=['POST'])
def auth_decline():
- """Decline authentication setup (deprecated, use /api/auth/skip)"""
+ """Decline authentication setup"""
try:
success, message = auth_manager.decline_auth()
if success:
return jsonify({"success": True, "message": message})
else:
- return jsonify({"success": False, "error": message}), 400
+ return jsonify({"success": False, "message": message}), 400
except Exception as e:
- return jsonify({"success": False, "error": str(e)}), 500
+ return jsonify({"success": False, "message": str(e)}), 500
@auth_bp.route('/api/auth/login', methods=['POST'])
@@ -83,19 +64,15 @@ def auth_login():
data = request.json
username = data.get('username')
password = data.get('password')
- remember_me = data.get('remember_me', False) # Soporte para "recordar contraseña"
- success, token, message = auth_manager.authenticate(username, password, remember_me)
+ success, token, message = auth_manager.authenticate(username, password)
if success:
- response_data = {"success": True, "token": token, "message": message}
- if remember_me:
- response_data["remember_me"] = True # Indicar al frontend que guarde las credenciales
- return jsonify(response_data)
+ return jsonify({"success": True, "token": token, "message": message})
else:
- return jsonify({"success": False, "error": message}), 401
+ return jsonify({"success": False, "message": message}), 401
except Exception as e:
- return jsonify({"success": False, "error": str(e)}), 500
+ return jsonify({"success": False, "message": str(e)}), 500
@auth_bp.route('/api/auth/enable', methods=['POST'])
@@ -107,9 +84,9 @@ def auth_enable():
if success:
return jsonify({"success": True, "message": message})
else:
- return jsonify({"success": False, "error": message}), 400
+ return jsonify({"success": False, "message": message}), 400
except Exception as e:
- return jsonify({"success": False, "error": str(e)}), 500
+ return jsonify({"success": False, "message": str(e)}), 500
@auth_bp.route('/api/auth/disable', methods=['POST'])
@@ -117,21 +94,17 @@ def auth_disable():
"""Disable authentication"""
try:
token = request.headers.get('Authorization', '').replace('Bearer ', '')
- if not token:
- return jsonify({"success": False, "error": "Authentication required"}), 401
-
- username = auth_manager.verify_token(token)
- if not username:
- return jsonify({"success": False, "error": "Invalid or expired token"}), 401
-
+ if not token or not auth_manager.verify_token(token):
+ return jsonify({"success": False, "message": "Unauthorized"}), 401
+
success, message = auth_manager.disable_auth()
if success:
return jsonify({"success": True, "message": message})
else:
- return jsonify({"success": False, "error": message}), 400
+ return jsonify({"success": False, "message": message}), 400
except Exception as e:
- return jsonify({"success": False, "error": str(e)}), 500
+ return jsonify({"success": False, "message": str(e)}), 500
@auth_bp.route('/api/auth/change-password', methods=['POST'])
@@ -139,25 +112,28 @@ def auth_change_password():
"""Change authentication password"""
try:
data = request.json
- current_password = data.get('current_password') # Corregido el nombre del campo
+ old_password = data.get('old_password')
new_password = data.get('new_password')
- # Verify current authentication
- token = request.headers.get('Authorization', '').replace('Bearer ', '')
- if not token:
- return jsonify({"success": False, "error": "Authentication required"}), 401
-
- username = auth_manager.verify_token(token)
- if not username:
- return jsonify({"success": False, "error": "Invalid or expired token"}), 401
-
- success, message = auth_manager.change_password(current_password, new_password)
+ success, message = auth_manager.change_password(old_password, new_password)
if success:
- # Generate new token
- new_token = auth_manager.generate_token(username)
- return jsonify({"success": True, "message": message, "token": new_token})
+ return jsonify({"success": True, "message": message})
else:
- return jsonify({"success": False, "error": message}), 400
+ return jsonify({"success": False, "message": message}), 400
except Exception as e:
- return jsonify({"success": False, "error": str(e)}), 500
+ return jsonify({"success": False, "message": str(e)}), 500
+
+
+@auth_bp.route('/api/auth/skip', methods=['POST'])
+def auth_skip():
+ """Skip authentication setup (same as decline)"""
+ try:
+ success, message = auth_manager.decline_auth()
+
+ if success:
+ return jsonify({"success": True, "message": message})
+ else:
+ return jsonify({"success": False, "message": message}), 400
+ except Exception as e:
+ return jsonify({"success": False, "message": str(e)}), 500