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>
131 lines
3.2 KiB
HTML
131 lines
3.2 KiB
HTML
<!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>
|