Aucune mise à jour effectuée
+diff --git a/backend/internal/api/settings.go b/backend/internal/api/settings.go index c78244d..3c54c7f 100644 --- a/backend/internal/api/settings.go +++ b/backend/internal/api/settings.go @@ -32,6 +32,7 @@ var publicSettings = []string{ "proxmox_url", "ssh_host", "ssh_username", + "dashboard_shortcuts", } // paramètres sensibles : modifiables en écriture seule, stockés chiffrés. diff --git a/frontend/css/pages.css b/frontend/css/pages.css index d05ea25..83ef230 100644 --- a/frontend/css/pages.css +++ b/frontend/css/pages.css @@ -732,3 +732,44 @@ display: block; line-height: 1; } + +/* ── Historique mises à jour ─────────────────────────────────────────────────── */ +.history-table { display: flex; flex-direction: column; gap: 0; } +.history-header, .history-row { + display: grid; + grid-template-columns: 6rem 1fr 6rem 10rem 5rem; + gap: .5rem; + align-items: center; + padding: .5rem .75rem; +} +.history-header { + font-size: .75rem; + font-weight: 600; + color: var(--neu-text-muted); + text-transform: uppercase; + letter-spacing: .04em; + border-bottom: 1px solid var(--neu-border); +} +.history-row { + font-size: .85rem; + cursor: pointer; + border-bottom: 1px solid var(--neu-border); + transition: background .15s; +} +.history-row:last-child { border-bottom: none; } +.history-row:hover { background: var(--neu-bg-hover); border-radius: var(--neu-radius); } +.history-job { font-family: monospace; opacity: .7; } +.history-status.running { color: var(--neu-info); } +.history-status.success { color: var(--neu-success); } +.history-status.error { color: var(--neu-danger); } + +/* ── Éditeur de raccourcis ───────────────────────────────────────────────────── */ +.shortcuts-editor { display: flex; flex-direction: column; gap: .5rem; } +.shortcut-row { + display: grid; + grid-template-columns: 9rem 1fr 1fr 2rem; + gap: .5rem; + align-items: center; +} +.shortcut-icon-sel { padding: .4rem .5rem; font-size: .85rem; } +.shortcut-label, .shortcut-href { font-size: .85rem; } diff --git a/frontend/dashboard.html b/frontend/dashboard.html index 2802048..5982055 100644 --- a/frontend/dashboard.html +++ b/frontend/dashboard.html @@ -140,15 +140,12 @@
diff --git a/frontend/js/app.js b/frontend/js/app.js index 19ca1d2..7ef7708 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -118,6 +118,19 @@ document.addEventListener('alpine:init', () => { const res = await apiFetch('/api/auth/me') if (res.ok) { this.user = await res.json() + // Sync préférences DB → localStorage + stores + const u = this.user + if (u.theme && u.theme !== Alpine.store('ui').theme) { + localStorage.setItem('pxp_theme', u.theme) + Alpine.store('ui').theme = u.theme + Alpine.store('ui').applyTheme() + } + if (u.sidebar_position && u.sidebar_position !== Alpine.store('ui').sidebarPosition) { + Alpine.store('ui').setSidebarPosition(u.sidebar_position) + } + if (u.lang && u.lang !== Alpine.store('i18n').lang) { + Alpine.store('i18n').setLang(u.lang) + } } else { // Token expiré, invalide, ou toute autre erreur → tenter un refresh await this.tryRefresh() @@ -400,6 +413,7 @@ document.addEventListener('alpine:init', () => { dragSrcIdx: null, _dragOriginal: null, hoveredWidgetId: null, + shortcuts: [], widgets: (function() { const defaults = [ @@ -485,12 +499,34 @@ document.addEventListener('alpine:init', () => { async init() { await this.fetchResources() await this.connectWS() + await this.loadShortcuts() }, destroy() { if (this._unsubWS) { this._unsubWS(); this._unsubWS = null } }, + async loadShortcuts() { + try { + const res = await apiFetch('/api/settings') + if (res.ok) { + const data = await res.json() + if (data.dashboard_shortcuts) { + const parsed = JSON.parse(data.dashboard_shortcuts) + if (Array.isArray(parsed) && parsed.length > 0) this.shortcuts = parsed + } + } + } catch(e) { /* use defaults */ } + }, + + get displayShortcuts() { + return this.shortcuts.length ? this.shortcuts : [ + { href: '/proxmox.html', icon: 'lnid-server-1', label: 'Proxmox' }, + { href: '/terminal.html', icon: 'lnid-terminal', label: 'Terminal' }, + { href: '/updates.html', icon: 'lnid-arrow-upward', label: 'Updates' }, + ] + }, + async fetchResources() { try { const res = await apiFetch('/api/proxmox/resources') @@ -543,16 +579,19 @@ document.addEventListener('alpine:init', () => { Alpine.store('ui').theme = t Alpine.store('ui').applyTheme() localStorage.setItem('pxp_theme', t) + apiFetch('/api/auth/preferences', { method: 'PATCH', body: JSON.stringify({ theme: t }) }).catch(() => {}) }, setSidebarPosition(pos) { this.sidebarPosition = pos Alpine.store('ui').setSidebarPosition(pos) + apiFetch('/api/auth/preferences', { method: 'PATCH', body: JSON.stringify({ sidebar_position: pos }) }).catch(() => {}) }, async setLang(lang) { this.lang = lang await Alpine.store('i18n').setLang(lang) + apiFetch('/api/auth/preferences', { method: 'PATCH', body: JSON.stringify({ lang }) }).catch(() => {}) }, async loadSessions() { @@ -664,6 +703,9 @@ document.addEventListener('alpine:init', () => { currentJob: null, output: '', jobStatus: '', + activeTab: 'targets', + history: [], + historyLoading: false, async init() { await this.loadTargets() @@ -791,6 +833,21 @@ document.addEventListener('alpine:init', () => { }) }, + async loadHistory() { + this.historyLoading = true + try { + const res = await apiFetch('/api/updates/history') + if (res.ok) this.history = await res.json() || [] + } catch(e) { /* ignore */ } + this.historyLoading = false + }, + + formatDate(iso) { + if (!iso) return '—' + const d = new Date(iso) + return d.toLocaleString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) + }, + get totalPackages() { return this.targets.reduce((sum, t) => sum + (t.packages?.length || 0), 0) }, @@ -805,6 +862,7 @@ document.addEventListener('alpine:init', () => { saving: false, saved: false, error: '', + shortcuts: [], settings: { instance_name: '', public_url: '', @@ -827,6 +885,9 @@ document.addEventListener('alpine:init', () => { if (res.ok) { const data = await res.json() Object.assign(this.settings, data) + if (data.dashboard_shortcuts) { + try { this.shortcuts = JSON.parse(data.dashboard_shortcuts) } catch(e) { this.shortcuts = [] } + } } } finally { this.loading = false @@ -862,6 +923,36 @@ document.addEventListener('alpine:init', () => { } }, + addShortcut() { + this.shortcuts.push({ label: '', href: '', icon: 'lnid-link-1' }) + }, + + removeShortcut(idx) { + this.shortcuts.splice(idx, 1) + }, + + async saveShortcuts() { + this.saving = true + this.saved = false + this.error = '' + try { + const res = await apiFetch('/api/settings/dashboard_shortcuts', { + method: 'PUT', + body: JSON.stringify({ value: JSON.stringify(this.shortcuts) }), + }) + if (!res.ok) { + const d = await res.json().catch(() => ({})) + throw new Error(d.error || 'Erreur sauvegarde raccourcis') + } + this.saved = true + setTimeout(() => { this.saved = false }, 3000) + } catch(e) { + this.error = e.message + } finally { + this.saving = false + } + }, + t(key) { return Alpine.store('i18n').t(key) }, })) diff --git a/frontend/settings.html b/frontend/settings.html index 6db60ec..b0701e4 100644 --- a/frontend/settings.html +++ b/frontend/settings.html @@ -68,6 +68,9 @@ + @@ -128,8 +131,52 @@ + ++ Ces raccourcis apparaissent dans le widget "Raccourcis" du dashboard. + Laisser vide pour utiliser les raccourcis par défaut. +
+