feat: initialisation complète du CORE ProxmoxPanel

Backend Go 1.23+ :
- API REST + WebSocket (chi, gorilla/websocket)
- Authentification PAM via SSH + JWT RS256
- Chiffrement AES-256-GCM pour secrets SQLite
- Pool SSH, client Proxmox REST, hub WebSocket pub/sub
- Système de modules compilés à initialisation conditionnelle
- Audit log, migrations SQLite versionnées

Frontend Vue 3 + Vite + TypeScript :
- Thème Neumorphism sombre/clair (CSS custom properties)
- Wizard d'installation, Dashboard drag-drop, Terminal xterm.js
- Toutes les vues CORE + stubs modules optionnels
- i18n EN/FR (vue-i18n v11)

Infrastructure :
- Docker multi-stage (Go → alpine, Node → nginx)
- docker-compose.yml, .gitattributes, LICENSE MIT, README

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
enzo 2026-03-20 21:08:53 +01:00
commit 5dbcb1df07
66 changed files with 10370 additions and 0 deletions

View file

@ -0,0 +1,180 @@
// Store d'authentification — gère la session JWT, le profil utilisateur et l'état d'installation.
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export interface User {
id: number
username: string
is_admin: boolean
lang: string
theme: string
sidebar_position: string
}
export const useAuthStore = defineStore('auth', () => {
// État
const user = ref<User | null>(null)
const accessToken = ref<string | null>(localStorage.getItem('pxp_token'))
const isInstalled = ref(false)
const installChecked = ref(false)
// Computed
const isAuthenticated = computed(() => !!accessToken.value && !!user.value)
// ── Actions ──────────────────────────────────────────────────────────────
/**
* Vérifie si l'application est installée via l'API.
* Appelé une seule fois au démarrage par le router guard.
*/
async function checkInstallation(): Promise<void> {
try {
const res = await fetch('/api/install/check')
if (res.ok) {
const data = await res.json()
isInstalled.value = data.installed
}
} catch {
// En cas d'erreur réseau, on suppose installé pour éviter une boucle
isInstalled.value = true
} finally {
installChecked.value = true
}
}
/**
* Authentifie l'utilisateur avec ses credentials Linux.
*/
async function login(username: string, password: string): Promise<void> {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
})
if (!res.ok) {
const err = await res.json()
throw new Error(err.error || 'Erreur d\'authentification')
}
const data = await res.json()
accessToken.value = data.access_token
localStorage.setItem('pxp_token', data.access_token)
user.value = data.user
// Planifier le renouvellement automatique avant expiration (14 min)
scheduleRefresh(14 * 60 * 1000)
}
/**
* Tente de renouveler le token via le cookie httpOnly (pxp_refresh).
* Appelé au démarrage de l'application.
*/
async function tryRefresh(): Promise<void> {
const token = localStorage.getItem('pxp_token')
if (!token) return
try {
const res = await fetch('/api/auth/refresh', {
method: 'POST',
credentials: 'include', // Inclure le cookie httpOnly
})
if (res.ok) {
const data = await res.json()
accessToken.value = data.access_token
localStorage.setItem('pxp_token', data.access_token)
// Charger le profil utilisateur
await fetchMe()
scheduleRefresh(14 * 60 * 1000)
} else {
// Refresh échoué — nettoyer la session
clearSession()
}
} catch {
clearSession()
}
}
/**
* Charge le profil de l'utilisateur connecté.
*/
async function fetchMe(): Promise<void> {
if (!accessToken.value) return
const res = await fetch('/api/auth/me', {
headers: { Authorization: `Bearer ${accessToken.value}` },
})
if (res.ok) {
user.value = await res.json()
}
}
/**
* Déconnecte l'utilisateur.
*/
async function logout(): Promise<void> {
try {
await fetch('/api/auth/logout', {
method: 'POST',
headers: { Authorization: `Bearer ${accessToken.value}` },
credentials: 'include',
})
} finally {
clearSession()
}
}
/**
* Met à jour les préférences de l'utilisateur (thème, langue, sidebar).
*/
async function updatePreferences(prefs: Partial<Pick<User, 'lang' | 'theme' | 'sidebar_position'>>): Promise<void> {
if (!accessToken.value) return
await fetch('/api/auth/preferences', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken.value}`,
},
body: JSON.stringify(prefs),
})
// Mettre à jour localement
if (user.value) {
Object.assign(user.value, prefs)
}
}
// ── Helpers privés ────────────────────────────────────────────────────────
let refreshTimer: ReturnType<typeof setTimeout> | null = null
function scheduleRefresh(delayMs: number): void {
if (refreshTimer) clearTimeout(refreshTimer)
refreshTimer = setTimeout(() => tryRefresh(), delayMs)
}
function clearSession(): void {
user.value = null
accessToken.value = null
localStorage.removeItem('pxp_token')
if (refreshTimer) clearTimeout(refreshTimer)
}
return {
user,
accessToken,
isInstalled,
installChecked,
isAuthenticated,
checkInstallation,
login,
logout,
tryRefresh,
fetchMe,
updatePreferences,
}
})