fix: couleurs icônes, boutons carrés, sidebar collapsée, langue, SW scope, LXC arrêté

Icônes :
- Sidebar navItems : couleur distincte par section (iconStyle via data-binding)
- Sidebar footer user : couleur primary
- Navbar : logout → danger, soleil → amber, lune → blue
- Panel widgets : œil visible → success, caché → muted

Boutons :
- `.neu-btn--sm:has(> i:only-child)` → carré 2rem×2rem automatiquement
  (theme, logout, mode édition) sans modifier le HTML

Sidebar :
- --sidebar-width-collapsed : 64px → 52px
- Sidebar réduite : icônes centrées (justify-content center)

Langue :
- Setter vide sur `lang` dans navbar() pour corriger x-model avec getter
  (le @change gère la vraie mise à jour du store)

Service Worker :
- Enregistrement depuis /ws.sw.js (scope /) au lieu de /js/ws.sw.js (scope /js/)
- build.mjs : copie ws.sw.js vers dist/ root en plus de dist/js/

LXC arrêté :
- checkTarget() : skip si target.status !== 'running' → évite les 502 SSH

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
enzo 2026-03-21 19:50:58 +01:00
parent 7c57b0ff84
commit b851dc61af
11 changed files with 53 additions and 15 deletions

View file

@ -71,6 +71,10 @@ for (const f of fs.readdirSync('js')) {
fs.copyFileSync(`js/${f}`, `${dist}/js/${f}`)
}
}
// Service Worker doit être servi depuis la racine pour avoir le bon scope
if (fs.existsSync('js/ws.sw.js')) {
fs.copyFileSync('js/ws.sw.js', `${dist}/ws.sw.js`)
}
// 5. Copy CSS
for (const f of fs.readdirSync('css')) {

View file

@ -78,7 +78,7 @@ input, select, textarea { font-family: inherit; font-size: inherit; }
/* Sidebar */
--sidebar-width: 240px;
--sidebar-width-collapsed: 64px;
--sidebar-width-collapsed: 52px;
/* Z-index */
--z-sidebar: 100;
@ -215,6 +215,14 @@ input, select, textarea { font-family: inherit; font-size: inherit; }
border-radius: var(--neu-radius-md);
}
/* Bouton icône carré taille sm se déclenche automatiquement si le bouton
ne contient qu'un <i> (ex: theme, logout, edit mode) */
.neu-btn--sm:has(> i:only-child) {
width: 2rem;
height: 2rem;
padding: 0;
}
.neu-btn--ghost {
background: transparent;
box-shadow: none;
@ -423,6 +431,12 @@ input, select, textarea { font-family: inherit; font-size: inherit; }
width: var(--sidebar-width-collapsed);
}
.sidebar.collapsed .sidebar-link {
justify-content: center;
padding-left: 0;
padding-right: 0;
}
.sidebar-header {
display: flex;
align-items: center;
@ -619,3 +633,13 @@ html.is-animating .transition-fade {
margin-right: var(--sidebar-width-collapsed);
}
}
/* ── Couleurs icônes contextuelles ─────────────────────────────────────────── */
/* Navbar : logout, thème, édition */
.navbar .lnid-power-button { color: var(--neu-danger); }
.navbar .lnid-sun-1 { color: #f59e0b; }
.navbar .lnid-moon-half-left-1 { color: #60a5fa; }
/* Sidebar footer : utilisateur */
.sidebar-footer .sidebar-icon { color: var(--neu-primary); }

View file

@ -621,6 +621,7 @@
transition: color .15s;
}
.widget-panel-toggle:hover { color: var(--neu-primary); }
.widget-panel-item.is-visible .widget-panel-toggle { color: var(--neu-success); }
/* Resize handle (coin bas-droit de la tuile, edit mode) */
.widget-resize-handle {

View file

@ -25,7 +25,7 @@
<nav class="sidebar-nav">
<template x-for="item in navItems" :key="item.id">
<a class="sidebar-link" :class="{ active: isActive(item.id) }" :href="item.href" @click.prevent="navigate(item.href)">
<i class="sidebar-icon" :class="item.iconClass"></i>
<i class="sidebar-icon" :class="item.iconClass" :style="iconStyle(item)"></i>
<span class="sidebar-label" x-show="!collapsed" x-text="t(item.labelKey)"></span>
</a>
</template>

View file

@ -13,7 +13,7 @@ const WsProxy = {
init() {
if (!('serviceWorker' in navigator)) return
navigator.serviceWorker.register('/js/ws.sw.js')
navigator.serviceWorker.register('/ws.sw.js')
.catch(e => console.warn('[WsProxy] SW registration failed:', e))
navigator.serviceWorker.addEventListener('message', event => {
const { channel, type, data, status } = event.data || {}
@ -253,14 +253,18 @@ document.addEventListener('alpine:init', () => {
get currentPage() { return Alpine.store('ui').currentPage },
navItems: [
{ id: 'dashboard', iconClass: 'lnid-dashboard-square-1', labelKey: 'nav.dashboard', href: '/dashboard.html' },
{ id: 'proxmox', iconClass: 'lnid-server-1', labelKey: 'nav.proxmox', href: '/proxmox.html' },
{ id: 'updates', iconClass: 'lnid-arrow-upward', labelKey: 'nav.updates', href: '/updates.html' },
{ id: 'terminal', iconClass: 'lnid-terminal', labelKey: 'nav.terminal', href: '/terminal.html' },
{ id: 'settings', iconClass: 'lnid-gear-1', labelKey: 'nav.settings', href: '/settings.html' },
{ id: 'modules', iconClass: 'lnid-puzzle', labelKey: 'nav.modules', href: '/modules.html' },
{ id: 'dashboard', iconClass: 'lnid-dashboard-square-1', iconColor: '#6c8ef4', labelKey: 'nav.dashboard', href: '/dashboard.html' },
{ id: 'proxmox', iconClass: 'lnid-server-1', iconColor: '#22c55e', labelKey: 'nav.proxmox', href: '/proxmox.html' },
{ id: 'updates', iconClass: 'lnid-arrow-upward', iconColor: '#f59e0b', labelKey: 'nav.updates', href: '/updates.html' },
{ id: 'terminal', iconClass: 'lnid-terminal', iconColor: '#a78bfa', labelKey: 'nav.terminal', href: '/terminal.html' },
{ id: 'settings', iconClass: 'lnid-gear-1', iconColor: '#94a3b8', labelKey: 'nav.settings', href: '/settings.html' },
{ id: 'modules', iconClass: 'lnid-puzzle', iconColor: '#f472b6', labelKey: 'nav.modules', href: '/modules.html' },
],
iconStyle(item) {
return this.isActive(item.id) ? '' : `color: ${item.iconColor}`
},
isActive(id) {
return this.currentPage === id
},
@ -282,6 +286,7 @@ document.addEventListener('alpine:init', () => {
get theme() { return Alpine.store('ui').theme },
get user() { return Alpine.store('auth').user },
get lang() { return Alpine.store('i18n').lang },
set lang(v) { /* x-model a besoin d'un setter ; @change gère la vraie MAJ */ },
toggleTheme() { Alpine.store('ui').toggleTheme() },
logout() { Alpine.store('auth').logout() },
@ -657,15 +662,19 @@ document.addEventListener('alpine:init', () => {
},
async checkTarget(target) {
if (target.status !== 'running') return // container arrêté → pas de SSH possible
target.checking = true
target.packages = null
try {
const res = await apiFetch(`/api/updates/packages?target=${encodeURIComponent(target.id)}`)
if (res.ok) {
target.packages = await res.json()
} else {
target.packages = []
}
} catch(e) {
console.error('checkTarget', e)
target.packages = []
} finally {
target.checking = false
}

View file

@ -25,7 +25,7 @@
<nav class="sidebar-nav">
<template x-for="item in navItems" :key="item.id">
<a class="sidebar-link" :class="{ active: isActive(item.id) }" :href="item.href" @click.prevent="navigate(item.href)">
<i class="sidebar-icon" :class="item.iconClass"></i>
<i class="sidebar-icon" :class="item.iconClass" :style="iconStyle(item)"></i>
<span class="sidebar-label" x-show="!collapsed" x-text="t(item.labelKey)"></span>
</a>
</template>

View file

@ -25,7 +25,7 @@
<nav class="sidebar-nav">
<template x-for="item in navItems" :key="item.id">
<a class="sidebar-link" :class="{ active: isActive(item.id) }" :href="item.href" @click.prevent="navigate(item.href)">
<i class="sidebar-icon" :class="item.iconClass"></i>
<i class="sidebar-icon" :class="item.iconClass" :style="iconStyle(item)"></i>
<span class="sidebar-label" x-show="!collapsed" x-text="t(item.labelKey)"></span>
</a>
</template>

View file

@ -25,7 +25,7 @@
<nav class="sidebar-nav">
<template x-for="item in navItems" :key="item.id">
<a class="sidebar-link" :class="{ active: isActive(item.id) }" :href="item.href" @click.prevent="navigate(item.href)">
<i class="sidebar-icon" :class="item.iconClass"></i>
<i class="sidebar-icon" :class="item.iconClass" :style="iconStyle(item)"></i>
<span class="sidebar-label" x-show="!collapsed" x-text="t(item.labelKey)"></span>
</a>
</template>

View file

@ -25,7 +25,7 @@
<nav class="sidebar-nav">
<template x-for="item in navItems" :key="item.id">
<a class="sidebar-link" :class="{ active: isActive(item.id) }" :href="item.href" @click.prevent="navigate(item.href)">
<i class="sidebar-icon" :class="item.iconClass"></i>
<i class="sidebar-icon" :class="item.iconClass" :style="iconStyle(item)"></i>
<span class="sidebar-label" x-show="!collapsed" x-text="t(item.labelKey)"></span>
</a>
</template>

View file

@ -28,7 +28,7 @@
<nav class="sidebar-nav">
<template x-for="item in navItems" :key="item.id">
<a class="sidebar-link" :class="{ active: isActive(item.id) }" :href="item.href" @click.prevent="navigate(item.href)">
<i class="sidebar-icon" :class="item.iconClass"></i>
<i class="sidebar-icon" :class="item.iconClass" :style="iconStyle(item)"></i>
<span class="sidebar-label" x-show="!collapsed" x-text="t(item.labelKey)"></span>
</a>
</template>

View file

@ -25,7 +25,7 @@
<nav class="sidebar-nav">
<template x-for="item in navItems" :key="item.id">
<a class="sidebar-link" :class="{ active: isActive(item.id) }" :href="item.href" @click.prevent="navigate(item.href)">
<i class="sidebar-icon" :class="item.iconClass"></i>
<i class="sidebar-icon" :class="item.iconClass" :style="iconStyle(item)"></i>
<span class="sidebar-label" x-show="!collapsed" x-text="t(item.labelKey)"></span>
</a>
</template>