feat: réécriture frontend Alpine.js + HTMX + Swup (branche frontend/alpine)

Remplace Vue 3 / Vite / TypeScript par une stack légère statique :
- Alpine.js v3 : réactivité inline, stores auth/ui/i18n, composants par page
- HTMX v2 : interactions serveur via attributs HTML
- Swup v4 : transitions de page (bundlé via esbuild, IIFE browser-loadable)
- xterm.js v5 : terminal PTY (bundlé via esbuild)

Structure : HTML statiques + js/app.js + js/terminal.js + css/ + locales/
Build : esbuild (bundle Swup + xterm seulement) → dist/ → Nginx
Dockerfile simplifié : node:22-alpine build → nginx:1.27-alpine serve

Pages : index, install, login, dashboard, proxmox, updates, terminal, settings, modules
URLs propres via nginx try_files $uri.html

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
enzo 2026-03-21 16:19:24 +01:00
parent 7ba0ff143c
commit 2098c80ec1
48 changed files with 2446 additions and 5317 deletions

131
frontend/login.html Normal file
View file

@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ProxmoxPanel — Connexion</title>
<link rel="stylesheet" href="/css/neu.css" />
<link rel="stylesheet" href="/css/dark.css" />
<link rel="stylesheet" href="/css/light.css" />
<script src="/js/vendors/htmx.min.js"></script>
<script src="/js/vendors/swup.iife.js"></script>
<script src="/js/app.js"></script>
<script src="/js/vendors/alpine.min.js" defer></script>
</head>
<body x-data>
<div class="auth-layout" x-data="loginPage()" x-cloak>
<div class="auth-card neu-card">
<div class="auth-logo">
<span class="logo-icon"></span>
<h1 class="auth-title">ProxmoxPanel</h1>
<p class="auth-subtitle" x-text="t('login.subtitle')"></p>
</div>
<form @submit.prevent="submit" class="auth-form">
<div class="form-group">
<label class="form-label" x-text="t('login.username')"></label>
<input
class="neu-input"
type="text"
x-model="username"
:placeholder="t('login.usernamePlaceholder')"
autocomplete="username"
required
/>
</div>
<div class="form-group">
<label class="form-label" x-text="t('login.password')"></label>
<input
class="neu-input"
type="password"
x-model="password"
:placeholder="t('login.passwordPlaceholder')"
autocomplete="current-password"
required
/>
</div>
<div class="form-error" x-show="error" x-text="error" role="alert"></div>
<button class="neu-btn neu-btn--primary auth-submit" type="submit" :disabled="loading">
<span x-show="!loading" x-text="t('login.submit')"></span>
<span x-show="loading" class="spinner"></span>
</button>
</form>
</div>
</div>
<style>
.auth-layout {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.auth-card {
width: 100%;
max-width: 400px;
padding: 2rem;
}
.auth-logo {
text-align: center;
margin-bottom: 2rem;
}
.logo-icon {
font-size: 3rem;
color: var(--accent-primary);
}
.auth-title {
font-size: 1.5rem;
font-weight: 700;
margin: 0.5rem 0 0.25rem;
}
.auth-subtitle {
font-size: 0.875rem;
color: var(--text-secondary);
margin: 0;
}
.auth-form {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-label {
font-size: 0.875rem;
font-weight: 600;
color: var(--text-secondary);
}
.form-error {
background: rgba(239,68,68,0.1);
border: 1px solid var(--color-error, #ef4444);
border-radius: 0.5rem;
padding: 0.75rem;
font-size: 0.875rem;
color: var(--color-error, #ef4444);
}
.auth-submit {
width: 100%;
padding: 0.875rem;
margin-top: 0.5rem;
}
.spinner {
display: inline-block;
width: 1rem;
height: 1rem;
border: 2px solid transparent;
border-top-color: currentColor;
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
[x-cloak] { display: none !important; }
</style>
</body>
</html>