158 lines
7.6 KiB
HTML
158 lines
7.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<script>(function(){document.documentElement.setAttribute("data-theme",localStorage.getItem("pxp_theme")||"dark");document.documentElement.setAttribute("data-sidebar",localStorage.getItem("pxp_sidebar_pos")||"left")})()</script>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>ProxmoxPanel — Modules</title>
|
|
<link rel="stylesheet" href="/css/neu.css" />
|
|
<link rel="stylesheet" href="/css/dark.css" />
|
|
<link rel="stylesheet" href="/css/light.css" />
|
|
<link rel="stylesheet" href="/css/pages.css" />
|
|
<link rel="stylesheet" href="/css/lineicons-duotone.css" />
|
|
<link rel="manifest" href="/manifest.json" />
|
|
<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 x-init="$store.ui.init()">
|
|
|
|
<aside class="sidebar" x-data="sidebar()" :class="{ collapsed: collapsed }" x-cloak>
|
|
<div class="sidebar-header" @click="toggle()">
|
|
<i class="sidebar-logo lnid-layout-1"></i>
|
|
<span class="sidebar-title" x-show="!collapsed">ProxmoxPanel</span>
|
|
</div>
|
|
<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" :style="iconStyle(item)"></i>
|
|
<span class="sidebar-label" x-show="!collapsed" x-text="t(item.labelKey)"></span>
|
|
</a>
|
|
</template>
|
|
</nav>
|
|
<div class="sidebar-footer">
|
|
<a class="sidebar-link" href="/profile.html" @click.prevent="navigate('/profile.html')">
|
|
<i class="sidebar-icon lnid-user-circle-1"></i>
|
|
<span class="sidebar-label" x-show="!collapsed" x-text="$store.auth.user?.username || t('nav.profile')"></span>
|
|
</a>
|
|
</div>
|
|
</aside>
|
|
|
|
<div class="main-layout">
|
|
<nav class="navbar" x-data="navbar()" x-cloak>
|
|
<h2 class="navbar-title" x-text="t('nav.modules')"></h2>
|
|
<div class="navbar-actions">
|
|
<button class="neu-btn neu-btn--sm neu-btn--icon-sm" @click="toggleTheme()" :title="theme==='dark'?t('navbar.lightMode'):t('navbar.darkMode')">
|
|
<i :class="theme==='dark' ? 'lnid-sun-1' : 'lnid-moon-half-left-1'"></i>
|
|
</button>
|
|
<button class="neu-btn neu-btn--sm neu-btn--icon-sm" @click="logout()" :title="t('navbar.logout')">
|
|
<i class="lnid-power-button"></i>
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
|
|
<main id="swup" class="page-content transition-fade">
|
|
<div x-data="modulesPage()" x-cloak>
|
|
|
|
<!-- Modules installés -->
|
|
<h3 class="section-title"><i class="lnid-puzzle"></i> Modules installés</h3>
|
|
|
|
<div class="loading-state" x-show="loading">
|
|
<div class="spinner-lg"></div><span>Chargement…</span>
|
|
</div>
|
|
|
|
<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.is_enabled }">
|
|
<div class="module-header">
|
|
<div class="module-icon">
|
|
<i :class="mod.nav_icon || 'lnid-puzzle'"></i>
|
|
</div>
|
|
<div class="module-info">
|
|
<span class="module-name" x-text="mod.name || mod.id"></span>
|
|
<span class="module-version" x-text="mod.version"></span>
|
|
<span class="module-desc" x-text="mod.description"></span>
|
|
</div>
|
|
<div class="module-toggle">
|
|
<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] === true"
|
|
x-show="!mod.is_core">
|
|
<span class="toggle-track"><span class="toggle-thumb"></span></span>
|
|
<span class="toggle-label" x-text="mod.is_enabled ? 'Activé' : 'Désactivé'"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<p class="empty-state" x-show="modules.length === 0">Aucun module installé</p>
|
|
</div>
|
|
|
|
<!-- Store : modules disponibles -->
|
|
<div style="display:flex;align-items:center;gap:.75rem;margin-top:2rem">
|
|
<h3 class="section-title" style="margin:0"><i class="lnid-download-2"></i> Store</h3>
|
|
<button class="neu-btn neu-btn--sm neu-btn--icon-sm" @click="loadStore()" :disabled="storeLoading" title="Rafraîchir le store">
|
|
<i class="lnid-refresh-circle-1-clockwise" :class="{ 'spin': storeLoading }"></i>
|
|
</button>
|
|
</div>
|
|
<p class="section-desc">Modules disponibles depuis <a href="https://git.geronzi.fr/proxmoxPanel" target="_blank">git.geronzi.fr/proxmoxPanel</a></p>
|
|
|
|
<!-- Erreur store -->
|
|
<div class="neu-card" x-show="storeError && !storeLoading"
|
|
style="margin-bottom:1rem;border-left:3px solid var(--neu-danger);display:flex;align-items:center;gap:.75rem">
|
|
<i class="lnid-warning-circle-1" style="color:var(--neu-danger)"></i>
|
|
<span x-text="storeError"></span>
|
|
</div>
|
|
|
|
<div class="loading-state" x-show="storeLoading">
|
|
<div class="spinner-lg"></div><span>Chargement du store…</span>
|
|
</div>
|
|
|
|
<div class="modules-grid" x-show="!storeLoading">
|
|
<template x-for="mod in storeModules" :key="mod.id">
|
|
<div class="neu-card module-card">
|
|
<div class="module-header">
|
|
<div class="module-icon"><i class="lnid-puzzle"></i></div>
|
|
<div class="module-info">
|
|
<span class="module-name" x-text="mod.id"></span>
|
|
<span class="module-desc" x-text="mod.description || '—'"></span>
|
|
<a class="module-repo-link" :href="mod.repo_url" target="_blank" x-text="mod.repo_url"></a>
|
|
</div>
|
|
<div class="module-toggle">
|
|
<button class="neu-btn neu-btn--sm neu-btn--primary"
|
|
@click="install(mod)"
|
|
:disabled="installing[mod.id] === true">
|
|
<span x-show="installing[mod.id] === true" class="spinner-sm"></span>
|
|
<i x-show="installing[mod.id] !== true" :class="mod.installed ? 'lnid-refresh-circle-1-clockwise' : 'lnid-download-2'"></i>
|
|
<span x-text="mod.installed ? 'Réinstaller' : 'Installer'"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<p class="empty-state" x-show="!storeLoading && !storeError && storeModules.length === 0">
|
|
Aucun module disponible dans le store
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Bannière rebuild en cours -->
|
|
<div class="neu-card rebuild-notice" x-show="rebuilding"
|
|
style="margin-top:1rem;border-left:3px solid var(--neu-accent);display:flex;align-items:center;gap:.75rem">
|
|
<div class="spinner-sm" style="flex-shrink:0"></div>
|
|
<span>Rebuild du container en cours (~1-2 min) — l'interface redémarrera automatiquement.</span>
|
|
</div>
|
|
|
|
<!-- Rebuild terminé (backend revenu) -->
|
|
<div class="neu-card rebuild-notice" x-show="rebuildDone"
|
|
style="margin-top:1rem;border-left:3px solid var(--neu-success);display:flex;align-items:center;gap:.75rem">
|
|
<i class="lnid-circle-check-1" style="color:var(--neu-success)"></i>
|
|
<span>Rebuild terminé. <button class="neu-btn neu-btn--sm" @click="window.location.reload()">Recharger la page</button></span>
|
|
</div>
|
|
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
</body>
|
|
</html>
|