feat: sync DB prefs, update history tab, configurable dashboard shortcuts
- auth store fetchMe(): sync theme/sidebar_position/lang from DB to localStorage+stores on login/refresh - profilePage setters: PATCH /api/auth/preferences on every preference change - updatesPage: add history tab (GET /api/updates/history) with job list, click to view output - dashboardPage: load shortcuts from settings API, fall back to defaults if none configured - settingsPage: new Raccourcis tab to add/remove/configure dashboard shortcuts (saved as JSON) - settings.go: expose dashboard_shortcuts in publicSettings for GET/PUT access - pages.css: add .history-table, .shortcut-row styles Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
780e5ec81d
commit
21e1e0ed1e
6 changed files with 230 additions and 10 deletions
|
|
@ -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) },
|
||||
}))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue