avant 50
This commit is contained in:
@@ -1,121 +1,181 @@
|
||||
<template>
|
||||
<div class="p-4 max-w-3xl mx-auto">
|
||||
<h1 class="text-2xl font-bold text-green mb-4">Réglages</h1>
|
||||
<div class="p-4 max-w-[1800px] mx-auto space-y-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-green tracking-tight">Réglages Système</h1>
|
||||
<p class="text-text-muted text-xs mt-1">Configurez l'interface, la maintenance et la sécurité de votre application.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="bg-bg-soft border border-bg-hard rounded-xl p-4 mb-4">
|
||||
<h2 class="text-text font-semibold mb-2">Interface</h2>
|
||||
<p class="text-text-muted text-sm mb-4">Ajustez les tailles d'affichage. Les changements sont appliqués instantanément.</p>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<div v-for="s in uiSizeSettings" :key="s.key" class="flex items-center gap-3">
|
||||
<label class="text-sm text-text w-44 shrink-0">{{ s.label }}</label>
|
||||
<input
|
||||
type="range"
|
||||
:min="s.min" :max="s.max" :step="s.step"
|
||||
v-model.number="uiSizes[s.key]"
|
||||
class="flex-1 accent-green"
|
||||
@input="applyUiSizes"
|
||||
/>
|
||||
<span class="text-text-muted text-xs w-12 text-right">{{ uiSizes[s.key] }}{{ s.unit }}</span>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-6">
|
||||
<!-- Section Interface -->
|
||||
<section class="card-jardin flex flex-col h-full border-green/20">
|
||||
<div class="flex items-center gap-3 mb-6 border-b border-bg-hard pb-4">
|
||||
<span class="text-2xl">🎨</span>
|
||||
<div>
|
||||
<h2 class="text-text font-bold uppercase tracking-widest text-xs">Interface Graphique</h2>
|
||||
<p class="text-[10px] text-text-muted font-bold">Ajustez les échelles visuelles.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex items-center gap-2">
|
||||
<button
|
||||
class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90"
|
||||
:disabled="savingUi"
|
||||
@click="saveUiSettings"
|
||||
>{{ savingUi ? 'Enregistrement...' : 'Enregistrer' }}</button>
|
||||
<button
|
||||
class="text-text-muted text-xs hover:text-text px-2"
|
||||
@click="resetUiSettings"
|
||||
>Réinitialiser</button>
|
||||
<span v-if="uiSavedMsg" class="text-xs text-aqua">{{ uiSavedMsg }}</span>
|
||||
</div>
|
||||
</section>
|
||||
<div class="flex-1 space-y-6">
|
||||
<div v-for="s in uiSizeSettings" :key="s.key" class="space-y-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<label class="text-[10px] font-black uppercase tracking-widest text-text-muted">{{ s.label }}</label>
|
||||
<span class="text-xs font-mono text-green">{{ uiSizes[s.key] }}{{ s.unit }}</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
:min="s.min" :max="s.max" :step="s.step"
|
||||
v-model.number="uiSizes[s.key]"
|
||||
class="w-full h-1.5 bg-bg-hard rounded-lg appearance-none cursor-pointer accent-green"
|
||||
@input="applyUiSizes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="bg-bg-soft border border-bg-hard rounded-xl p-4 mb-4">
|
||||
<h2 class="text-text font-semibold mb-2">Général</h2>
|
||||
<p class="text-text-muted text-sm mb-3">Options globales de l'application.</p>
|
||||
<div class="mt-8 pt-4 border-t border-bg-hard flex items-center justify-between">
|
||||
<button
|
||||
class="text-[10px] font-black uppercase tracking-widest text-text-muted hover:text-text transition-colors"
|
||||
@click="resetUiSettings"
|
||||
>Réinitialiser</button>
|
||||
<div class="flex items-center gap-3">
|
||||
<span v-if="uiSavedMsg" class="text-[10px] font-bold text-aqua animate-pulse">{{ uiSavedMsg }}</span>
|
||||
<button
|
||||
class="btn-primary !py-2 !px-6 text-xs"
|
||||
:disabled="savingUi"
|
||||
@click="saveUiSettings"
|
||||
>{{ savingUi ? '...' : 'Enregistrer' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<label class="inline-flex items-center gap-2 text-sm text-text">
|
||||
<input v-model="debugMode" type="checkbox" class="accent-green" />
|
||||
Activer le mode debug (affichage CPU / RAM / disque en header)
|
||||
</label>
|
||||
<!-- Section Général / Debug -->
|
||||
<section class="card-jardin flex flex-col h-full border-yellow/20">
|
||||
<div class="flex items-center gap-3 mb-6 border-b border-bg-hard pb-4">
|
||||
<span class="text-2xl">⚙️</span>
|
||||
<div>
|
||||
<h2 class="text-text font-bold uppercase tracking-widest text-xs">Général & Debug</h2>
|
||||
<p class="text-[10px] text-text-muted font-bold">Options globales du système.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center gap-2">
|
||||
<button
|
||||
class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90"
|
||||
:disabled="saving"
|
||||
@click="saveSettings"
|
||||
>
|
||||
{{ saving ? 'Enregistrement...' : 'Enregistrer' }}
|
||||
</button>
|
||||
<span v-if="savedMsg" class="text-xs text-aqua">{{ savedMsg }}</span>
|
||||
</div>
|
||||
</section>
|
||||
<div class="flex-1 space-y-6">
|
||||
<label class="flex items-start gap-4 cursor-pointer group bg-bg-hard/30 p-4 rounded-2xl border border-bg-soft/50 hover:border-yellow/30 transition-all">
|
||||
<div class="relative mt-1">
|
||||
<input v-model="debugMode" type="checkbox" class="sr-only peer" />
|
||||
<div class="w-10 h-5 bg-bg-hard rounded-full peer peer-checked:bg-yellow transition-colors"></div>
|
||||
<div class="absolute left-1 top-1 w-3 h-3 bg-text-muted peer-checked:bg-bg peer-checked:translate-x-5 rounded-full transition-all"></div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-bold text-text group-hover:text-yellow transition-colors">Mode Debug Interactif</div>
|
||||
<p class="text-[10px] text-text-muted mt-1 leading-relaxed italic">Affiche les statistiques vitales (CPU, RAM, Disque) dans la barre de navigation supérieure.</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<section class="bg-bg-soft border border-bg-hard rounded-xl p-4 mb-4">
|
||||
<h2 class="text-text font-semibold mb-2">Maintenance météo</h2>
|
||||
<p class="text-text-muted text-sm mb-3">Déclenche un rafraîchissement immédiat des jobs météo backend.</p>
|
||||
<button
|
||||
class="bg-blue text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90"
|
||||
:disabled="refreshingMeteo"
|
||||
@click="refreshMeteo"
|
||||
>
|
||||
{{ refreshingMeteo ? 'Rafraîchissement...' : 'Rafraîchir maintenant' }}
|
||||
</button>
|
||||
</section>
|
||||
<div class="mt-8 pt-4 border-t border-bg-hard flex items-center justify-end gap-3">
|
||||
<span v-if="savedMsg" class="text-[10px] font-bold text-aqua">{{ savedMsg }}</span>
|
||||
<button
|
||||
class="btn-primary !bg-yellow !text-bg !py-2 !px-6 text-xs"
|
||||
:disabled="saving"
|
||||
@click="saveSettings"
|
||||
>
|
||||
{{ saving ? '...' : 'Appliquer' }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bg-bg-soft border border-bg-hard rounded-xl p-4 mb-4">
|
||||
<h2 class="text-text font-semibold mb-2">Test API backend</h2>
|
||||
<p class="text-text-muted text-sm mb-2">
|
||||
Ouvre la documentation interactive de l'API et un test rapide de santé.
|
||||
</p>
|
||||
<p class="text-text-muted text-xs mb-3">Base API détectée: <span class="text-text">{{ apiBaseUrl }}</span></p>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
class="bg-blue text-bg px-3 py-2 rounded-lg text-xs font-semibold hover:opacity-90"
|
||||
@click="openApiDocs"
|
||||
>
|
||||
Ouvrir Swagger (/docs)
|
||||
</button>
|
||||
<button
|
||||
class="bg-aqua text-bg px-3 py-2 rounded-lg text-xs font-semibold hover:opacity-90"
|
||||
@click="openApiRedoc"
|
||||
>
|
||||
Ouvrir ReDoc (/redoc)
|
||||
</button>
|
||||
<button
|
||||
class="bg-bg border border-bg-hard text-text px-3 py-2 rounded-lg text-xs font-semibold hover:border-text-muted"
|
||||
@click="openApiHealth"
|
||||
>
|
||||
Tester /api/health
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Section Maintenance -->
|
||||
<section class="card-jardin flex flex-col h-full border-blue/20">
|
||||
<div class="flex items-center gap-3 mb-6 border-b border-bg-hard pb-4">
|
||||
<span class="text-2xl">🌦️</span>
|
||||
<div>
|
||||
<h2 class="text-text font-bold uppercase tracking-widest text-xs">Maintenance Météo</h2>
|
||||
<p class="text-[10px] text-text-muted font-bold">Synchronisation des données externes.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="bg-bg-soft border border-bg-hard rounded-xl p-4">
|
||||
<h2 class="text-text font-semibold mb-2">Sauvegarde des données</h2>
|
||||
<p class="text-text-muted text-sm mb-3">
|
||||
Exporte un ZIP téléchargeable contenant la base SQLite, les images/vidéos uploadées et les fichiers texte utiles.
|
||||
</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="bg-aqua text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90"
|
||||
:disabled="downloadingBackup"
|
||||
@click="downloadBackup"
|
||||
>
|
||||
{{ downloadingBackup ? 'Préparation du ZIP...' : 'Télécharger la sauvegarde (.zip)' }}
|
||||
</button>
|
||||
<span v-if="backupMsg" class="text-xs text-aqua">{{ backupMsg }}</span>
|
||||
</div>
|
||||
</section>
|
||||
<div class="flex-1">
|
||||
<p class="text-xs text-text-muted leading-relaxed mb-6">
|
||||
Force le rafraîchissement des prévisions Open-Meteo et des relevés de la station locale WeeWX. Les données sont normalement mises à jour toutes les heures automatiquement.
|
||||
</p>
|
||||
|
||||
<button
|
||||
class="btn-outline w-full py-4 border-blue/20 text-blue hover:bg-blue/10 flex flex-col items-center gap-2"
|
||||
:disabled="refreshingMeteo"
|
||||
@click="refreshMeteo"
|
||||
>
|
||||
<span class="text-lg">{{ refreshingMeteo ? '🔄' : '⚡' }}</span>
|
||||
<span class="text-[10px] font-black uppercase tracking-widest">{{ refreshingMeteo ? 'Rafraîchissement en cours...' : 'Forcer la mise à jour' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Section Sauvegarde -->
|
||||
<section class="card-jardin flex flex-col h-full border-aqua/20">
|
||||
<div class="flex items-center gap-3 mb-6 border-b border-bg-hard pb-4">
|
||||
<span class="text-2xl">📦</span>
|
||||
<div>
|
||||
<h2 class="text-text font-bold uppercase tracking-widest text-xs">Sauvegarde & Export</h2>
|
||||
<p class="text-[10px] text-text-muted font-bold">Protégez vos données.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 space-y-6">
|
||||
<p class="text-xs text-text-muted leading-relaxed">
|
||||
Génère une archive complète (.zip) incluant votre base de données SQLite et tous les médias (photos/vidéos) uploadés.
|
||||
</p>
|
||||
|
||||
<button
|
||||
class="btn-primary !bg-aqua !text-bg w-full py-4 flex flex-col items-center gap-2 shadow-lg hover:shadow-aqua/20"
|
||||
:disabled="downloadingBackup"
|
||||
@click="downloadBackup"
|
||||
>
|
||||
<span class="text-xl">💾</span>
|
||||
<span class="text-[10px] font-black uppercase tracking-widest">{{ downloadingBackup ? 'Préparation...' : 'Télécharger le Pack Complet' }}</span>
|
||||
</button>
|
||||
|
||||
<div v-if="backupMsg" class="text-[10px] text-center font-bold text-aqua animate-bounce">{{ backupMsg }}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Section API Docs (Largeur double sur XL+) -->
|
||||
<section class="card-jardin xl:col-span-2 flex flex-col border-bg-soft/50">
|
||||
<div class="flex items-center gap-3 mb-6 border-b border-bg-hard pb-4">
|
||||
<span class="text-2xl">🛠️</span>
|
||||
<div>
|
||||
<h2 class="text-text font-bold uppercase tracking-widest text-xs">Outils Développeur & API</h2>
|
||||
<p class="text-[10px] text-text-muted font-bold">Documentation technique et monitoring.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
<button @click="openApiDocs" class="btn-outline flex flex-col items-center gap-2 py-6 border-bg-soft hover:bg-bg-hard transition-all">
|
||||
<span class="text-xl">📖</span>
|
||||
<span class="text-[10px] font-bold uppercase">Swagger UI</span>
|
||||
</button>
|
||||
<button @click="openApiRedoc" class="btn-outline flex flex-col items-center gap-2 py-6 border-bg-soft hover:bg-bg-hard transition-all">
|
||||
<span class="text-xl">📄</span>
|
||||
<span class="text-[10px] font-bold uppercase">ReDoc Docs</span>
|
||||
</button>
|
||||
<button @click="openApiHealth" class="btn-outline flex flex-col items-center gap-2 py-6 border-bg-soft hover:bg-bg-hard transition-all">
|
||||
<span class="text-xl">💓</span>
|
||||
<span class="text-[10px] font-bold uppercase">Santé API</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 p-3 bg-bg-hard/50 rounded-xl border border-bg-soft/30 flex items-center justify-between">
|
||||
<span class="text-[9px] font-bold text-text-muted uppercase tracking-widest">Base URL API détectée</span>
|
||||
<span class="text-xs font-mono text-aqua">{{ apiBaseUrl }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { settingsApi } from '@/api/settings'
|
||||
import { meteoApi } from '@/api/meteo'
|
||||
import { UI_SIZE_DEFAULTS, applyUiSizesToRoot } from '@/utils/uiSizeDefaults'
|
||||
@@ -130,10 +190,12 @@ const apiBaseUrl = detectApiBaseUrl()
|
||||
|
||||
// --- UI Size settings ---
|
||||
const uiSizeSettings = [
|
||||
{ key: 'ui_font_size', label: 'Taille texte', min: 12, max: 20, step: 1, unit: 'px' },
|
||||
{ key: 'ui_menu_font_size', label: 'Texte menu latéral', min: 11, max: 18, step: 1, unit: 'px' },
|
||||
{ key: 'ui_menu_icon_size', label: 'Icônes menu', min: 14, max: 28, step: 1, unit: 'px' },
|
||||
{ key: 'ui_thumb_size', label: 'Miniatures images/vidéo', min: 60, max: 200, step: 4, unit: 'px' },
|
||||
{ key: 'ui_font_size', label: 'Corps de texte', min: 12, max: 24, step: 1, unit: 'px' },
|
||||
{ key: 'ui_menu_font_size', label: 'Texte menu latéral', min: 11, max: 20, step: 1, unit: 'px' },
|
||||
{ key: 'ui_menu_icon_size', label: 'Icônes menu', min: 14, max: 32, step: 1, unit: 'px' },
|
||||
{ key: 'ui_thumb_size', label: 'Miniatures médias', min: 60, max: 300, step: 4, unit: 'px' },
|
||||
{ key: 'ui_weather_icon_size', label: 'Icônes Météo', min: 32, max: 128, step: 4, unit: 'px' },
|
||||
{ key: 'ui_dashboard_icon_size', label: 'Icônes Dashboard', min: 16, max: 64, step: 2, unit: 'px' },
|
||||
]
|
||||
|
||||
const uiSizes = ref<Record<string, number>>({ ...UI_SIZE_DEFAULTS })
|
||||
@@ -189,17 +251,9 @@ function openInNewTab(path: string) {
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
|
||||
function openApiDocs() {
|
||||
openInNewTab('/docs')
|
||||
}
|
||||
|
||||
function openApiRedoc() {
|
||||
openInNewTab('/redoc')
|
||||
}
|
||||
|
||||
function openApiHealth() {
|
||||
openInNewTab('/api/health')
|
||||
}
|
||||
function openApiDocs() { openInNewTab('/docs') }
|
||||
function openApiRedoc() { openInNewTab('/redoc') }
|
||||
function openApiHealth() { openInNewTab('/api/health') }
|
||||
|
||||
function toBool(value: unknown): boolean {
|
||||
if (typeof value === 'boolean') return value
|
||||
@@ -223,7 +277,7 @@ async function loadSettings() {
|
||||
}
|
||||
applyUiSizes()
|
||||
} catch {
|
||||
// Laisse la valeur locale si l'API n'est pas disponible.
|
||||
// Laisse la valeur locale
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +287,7 @@ async function saveSettings() {
|
||||
try {
|
||||
await settingsApi.update({ debug_mode: debugMode.value ? '1' : '0' })
|
||||
notifyDebugChanged(debugMode.value)
|
||||
savedMsg.value = 'Enregistré'
|
||||
savedMsg.value = 'Pris en compte'
|
||||
window.setTimeout(() => { savedMsg.value = '' }, 1800)
|
||||
} finally {
|
||||
saving.value = false
|
||||
@@ -262,9 +316,9 @@ async function downloadBackup() {
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
window.URL.revokeObjectURL(url)
|
||||
backupMsg.value = 'Téléchargement lancé.'
|
||||
backupMsg.value = 'ZIP prêt !'
|
||||
} catch {
|
||||
backupMsg.value = 'Erreur lors de la sauvegarde.'
|
||||
backupMsg.value = 'Erreur export.'
|
||||
} finally {
|
||||
downloadingBackup.value = false
|
||||
window.setTimeout(() => { backupMsg.value = '' }, 2200)
|
||||
|
||||
Reference in New Issue
Block a user