Update AppImage

This commit is contained in:
MacRimi
2025-11-07 19:25:36 +01:00
parent fc7d0f2cd5
commit 25fc3d931e
6 changed files with 258 additions and 74 deletions

View File

@@ -2,10 +2,11 @@
import type React from "react"
import { useState } from "react"
import { useState, useEffect } from "react"
import { Button } from "./ui/button"
import { Input } from "./ui/input"
import { Label } from "./ui/label"
import { Checkbox } from "./ui/checkbox"
import { Lock, User, AlertCircle, Server } from "lucide-react"
import { getApiUrl } from "../lib/api-config"
import Image from "next/image"
@@ -17,15 +18,49 @@ interface LoginProps {
export function Login({ onLogin }: LoginProps) {
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const [rememberMe, setRememberMe] = useState(false)
const [error, setError] = useState("")
const [loading, setLoading] = useState(false)
useEffect(() => {
const savedUsername = localStorage.getItem("proxmenux-saved-username")
const savedPassword = localStorage.getItem("proxmenux-saved-password")
if (savedUsername && savedPassword) {
setUsername(savedUsername)
setPassword(savedPassword)
setRememberMe(true)
// Auto-login si hay credenciales guardadas
handleAutoLogin(savedUsername, savedPassword)
}
}, [])
const handleAutoLogin = async (user: string, pass: string) => {
try {
const response = await fetch(getApiUrl("/api/auth/login"), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: user, password: pass, remember_me: true }),
})
const data = await response.json()
if (response.ok && data.token) {
localStorage.setItem("proxmenux-auth-token", data.token)
onLogin()
}
} catch (err) {
console.log("Auto-login failed, showing login form")
}
}
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault()
setError("")
if (!username || !password) {
setError("Please enter username and password")
setError("Por favor, introduce usuario y contraseña")
return
}
@@ -35,20 +70,30 @@ export function Login({ onLogin }: LoginProps) {
const response = await fetch(getApiUrl("/api/auth/login"), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
body: JSON.stringify({ username, password, remember_me: rememberMe }),
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || "Login failed")
throw new Error(data.error || "Fallo en el login")
}
// Save token
localStorage.setItem("proxmenux-auth-token", data.token)
if (rememberMe) {
localStorage.setItem("proxmenux-saved-username", username)
localStorage.setItem("proxmenux-saved-password", password)
} else {
// Limpiar credenciales guardadas si no se marcó recordar
localStorage.removeItem("proxmenux-saved-username")
localStorage.removeItem("proxmenux-saved-password")
}
onLogin()
} catch (err) {
setError(err instanceof Error ? err.message : "Login failed")
setError(err instanceof Error ? err.message : "Fallo en el login")
} finally {
setLoading(false)
}
@@ -128,6 +173,18 @@ export function Login({ onLogin }: LoginProps) {
</div>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="remember-me"
checked={rememberMe}
onCheckedChange={(checked) => setRememberMe(checked as boolean)}
disabled={loading}
/>
<Label htmlFor="remember-me" className="text-sm font-normal cursor-pointer select-none">
Remember me
</Label>
</div>
<Button type="submit" className="w-full bg-blue-500 hover:bg-blue-600" disabled={loading}>
{loading ? "Signing in..." : "Sign In"}
</Button>

View File

@@ -1,5 +1,7 @@
"use client"
import type React from "react"
import { useState, useEffect, useMemo, useCallback } from "react"
import { Badge } from "./ui/badge"
import { Button } from "./ui/button"
@@ -28,6 +30,7 @@ import {
Cpu,
FileText,
SettingsIcon,
LogOut,
} from "lucide-react"
import Image from "next/image"
import { ThemeToggle } from "./theme-toggle"
@@ -74,6 +77,7 @@ export function ProxmoxDashboard() {
const [showNavigation, setShowNavigation] = useState(true)
const [lastScrollY, setLastScrollY] = useState(0)
const [showHealthModal, setShowHealthModal] = useState(false)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const fetchSystemData = useCallback(async () => {
const apiUrl = getApiUrl("/api/system-info")
@@ -184,6 +188,18 @@ export function ProxmoxDashboard() {
setIsRefreshing(false)
}
const handleLogout = (e: React.MouseEvent) => {
e.stopPropagation()
localStorage.removeItem("proxmenux-auth-token")
localStorage.removeItem("proxmenux-saved-username")
localStorage.removeItem("proxmenux-saved-password")
window.location.reload()
}
useEffect(() => {
setIsAuthenticated(!!localStorage.getItem("proxmenux-auth-token"))
}, [])
const statusIcon = useMemo(() => {
switch (systemStatus.status) {
case "healthy":
@@ -323,6 +339,19 @@ export function ProxmoxDashboard() {
Refresh
</Button>
{isAuthenticated && (
<Button
variant="outline"
size="sm"
onClick={handleLogout}
className="border-border/50 bg-transparent hover:bg-secondary"
title="Logout"
>
<LogOut className="h-4 w-4 mr-2" />
Logout
</Button>
)}
<div onClick={(e) => e.stopPropagation()}>
<ThemeToggle />
</div>
@@ -348,6 +377,12 @@ export function ProxmoxDashboard() {
<RefreshCw className={`h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`} />
</Button>
{isAuthenticated && (
<Button variant="ghost" size="sm" onClick={handleLogout} className="h-8 w-8 p-0" title="Logout">
<LogOut className="h-4 w-4" />
</Button>
)}
<div onClick={(e) => e.stopPropagation()}>
<ThemeToggle />
</div>

View File

@@ -98,7 +98,7 @@ export function Settings() {
const handleDisableAuth = async () => {
if (
!confirm(
"Are you sure you want to disable authentication? This will remove password protection from your dashboard.",
"¿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.",
)
) {
return
@@ -109,19 +109,35 @@ export function Settings() {
setSuccess("")
try {
const response = await fetch(getApiUrl("/api/auth/setup"), {
const response = await fetch(getApiUrl("/api/auth/disable"), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enable_auth: false }),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("proxmenux-auth-token")}`,
},
})
if (!response.ok) throw new Error("Failed to disable authentication")
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || "Failed to disable authentication")
}
localStorage.removeItem("proxmenux-auth-token")
setSuccess("Authentication disabled successfully!")
setAuthEnabled(false)
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á...")
// Recargar la página después de 1.5 segundos
setTimeout(() => {
window.location.reload()
}, 1500)
} catch (err) {
setError("Failed to disable authentication. Please try again.")
setError(
err instanceof Error ? err.message : "No se pudo deshabilitar la autenticación. Por favor, inténtalo de nuevo.",
)
} finally {
setLoading(false)
}