fix: CSS reset, settings API, modules champs, proxmox token

- CSS: ajout reset (box-sizing, margin, font-family, body background)
- Settings: save par PUT /api/settings/{key} (pas bulk), un appel par clé
- Settings: proxmox_token champ unique (format user@realm!id=secret)
- Modules: is_enabled/is_core (champs backend réels, pas enabled/core)
- Proxmox: supprime bouton reboot (route inexistante)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
enzo 2026-03-21 17:28:55 +01:00
parent 65c8bf332f
commit a4b5b06f04
5 changed files with 58 additions and 30 deletions

View file

@ -4,6 +4,32 @@
Utilisé par tous les composants et les modules.
============================================================================= */
/* ── Reset / Base ───────────────────────────────────────────────────────────── */
*, *::before, *::after {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Inter', sans-serif;
font-size: 14px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
body {
min-height: 100vh;
background-color: var(--neu-bg);
color: var(--neu-text);
}
h1, h2, h3, h4, h5, h6 { margin: 0; }
p { margin: 0; }
a { color: inherit; }
button { font-family: inherit; font-size: inherit; }
input, select, textarea { font-family: inherit; font-size: inherit; }
/* ── Variables CSS (surchargées par dark.css et light.css) ─────────────────── */
:root {
/* Couleurs de base */

View file

@ -544,10 +544,9 @@ document.addEventListener('alpine:init', () => {
default_lang: 'fr',
ssh_host: '',
ssh_username: '',
ssh_password: '',
ssh_password: '', // chiffré, laisser vide = pas de changement
proxmox_url: '',
proxmox_token_id: '',
proxmox_token_secret: '',
proxmox_token: '', // chiffré, format: user@realm!tokenid=secret
},
async init() {
@ -572,13 +571,20 @@ document.addEventListener('alpine:init', () => {
this.saved = false
this.error = ''
try {
const res = await apiFetch('/api/settings', {
method: 'PUT',
body: JSON.stringify(this.settings),
})
if (!res.ok) {
const d = await res.json().catch(() => ({}))
throw new Error(d.error || 'Erreur sauvegarde')
// Backend: PUT /api/settings/{key} avec { value: "..." } — un appel par clé
const keys = Object.keys(this.settings)
for (const key of keys) {
const val = this.settings[key]
// Ignorer les champs vides pour les secrets (ne pas écraser l'existant)
if (val === '' && (key === 'ssh_password' || key === 'proxmox_token')) continue
const res = await apiFetch(`/api/settings/${key}`, {
method: 'PUT',
body: JSON.stringify({ value: val }),
})
if (!res.ok) {
const d = await res.json().catch(() => ({}))
throw new Error(d.error || `Erreur sauvegarde de ${key}`)
}
}
this.saved = true
setTimeout(() => { this.saved = false }, 3000)
@ -617,10 +623,11 @@ document.addEventListener('alpine:init', () => {
async toggle(mod) {
this.toggling[mod.id] = true
try {
const action = mod.enabled ? 'disable' : 'enable'
// Backend: is_enabled (pas enabled)
const action = mod.is_enabled ? 'disable' : 'enable'
const res = await apiFetch(`/api/modules/${mod.id}/${action}`, { method: 'POST' })
if (res.ok) {
mod.enabled = !mod.enabled
mod.is_enabled = !mod.is_enabled
}
} catch(e) {
console.error(e)

View file

@ -55,7 +55,7 @@
<div class="modules-grid" x-show="!loading">
<template x-for="mod in modules" :key="mod.id">
<div class="neu-card module-card" :class="{ disabled: !mod.enabled }">
<div class="neu-card module-card" :class="{ disabled: !mod.is_enabled }">
<div class="module-header">
<div class="module-icon" x-text="mod.icon || '⬡'"></div>
<div class="module-info">
@ -63,14 +63,14 @@
<span class="module-desc" x-text="mod.description || ''"></span>
</div>
<div class="module-toggle">
<span class="core-badge" x-show="mod.core">CORE</span>
<button class="toggle-btn" :class="{ on: mod.enabled }"
@click="toggle(mod)" :disabled="mod.core || toggling[mod.id]"
x-show="!mod.core">
<span class="core-badge" x-show="mod.is_core">CORE</span>
<button class="toggle-btn" :class="{ on: mod.is_enabled }"
@click="toggle(mod)" :disabled="mod.is_core || toggling[mod.id]"
x-show="!mod.is_core">
<span class="toggle-track">
<span class="toggle-thumb"></span>
</span>
<span class="toggle-label" x-text="mod.enabled ? 'Activé' : 'Désactivé'"></span>
<span class="toggle-label" x-text="mod.is_enabled ? 'Activé' : 'Désactivé'"></span>
</button>
</div>
</div>

View file

@ -86,10 +86,6 @@
@click="action(r.vmid,'lxc','stop')" :disabled="actionLoading[r.vmid+'-stop']">
⏹ Stop
</button>
<button class="neu-btn neu-btn--sm" x-show="r.status==='running'"
@click="action(r.vmid,'lxc','reboot')" :disabled="actionLoading[r.vmid+'-reboot']">
↺ Reboot
</button>
</div>
</div>
</template>

View file

@ -110,14 +110,13 @@
<label class="form-label" x-text="t('install.proxmoxUrl')"></label>
<input class="neu-input" type="url" x-model="settings.proxmox_url" />
</div>
<div class="form-group">
<label class="form-label" x-text="t('install.proxmoxTokenId')"></label>
<input class="neu-input" type="text" x-model="settings.proxmox_token_id" placeholder="user@realm!tokenid" />
</div>
<div class="form-group">
<label class="form-label" x-text="t('install.proxmoxTokenSecret')"></label>
<input class="neu-input" type="text" x-model="settings.proxmox_token_secret"
placeholder="Laisser vide pour ne pas modifier" />
<div class="form-group" style="grid-column: 1 / -1">
<label class="form-label">Token API Proxmox</label>
<input class="neu-input" type="text" x-model="settings.proxmox_token"
placeholder="Laisser vide pour ne pas modifier — format: user@realm!tokenid=secret" />
<span style="font-size:.75rem;color:var(--neu-text-muted)">
Exemple : enzo@pam!panel=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
</span>
</div>
</div>
</div>