feat(system): page Status avec endpoint API Platform et composants Vue

- Nouveau domaine System/Domain/Model/SystemStatus (value object)
- QueryHandler agrégeant métriques mangas, chapitres, jobs (global/24h/7j), stockage et sources
- Endpoint GET /api/system/status via API Platform (singleton)
- Calcul de l'espace disque par RecursiveDirectoryIterator sur public/images
- Page Vue /system/status avec 6 cards (Mangas, Chapitres, Jobs, Stockage, Sources, Système)
- Nettoyage du router : suppression des PlaceholderComponent et routes placeholder
- Sidebar : suppression des entrées sans page réelle
This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2026-03-17 22:04:48 +01:00
parent c2b55e9018
commit ca8791cc0d
21 changed files with 825 additions and 61 deletions

View File

@@ -0,0 +1,71 @@
<template>
<div class="flex flex-col h-full">
<Toolbar :config="toolbarConfig" />
<div class="overflow-y-auto flex-1">
<!-- Loading -->
<div v-if="loading" class="flex justify-center py-12">
<div class="animate-spin h-10 w-10 border-b-2 border-blue-500 rounded-full"></div>
</div>
<!-- Error -->
<div v-else-if="error" class="px-6 py-8">
<div class="bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-700 p-4 rounded">
<div class="flex items-center">
<ExclamationCircleIcon class="w-5 h-5 text-red-400 mr-2 shrink-0" />
<p class="text-red-800 dark:text-red-200">{{ error }}</p>
</div>
<button
@click="statusStore.loadStatus()"
class="mt-3 px-4 py-2 bg-red-600 text-white hover:bg-red-700 rounded">
Réessayer
</button>
</div>
</div>
<!-- Données -->
<div v-else-if="status" class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 p-4">
<MangasStatusCard :status="status" />
<ChaptersStatusCard :status="status" />
<JobsStatusCard :status="status" />
<StorageStatusCard :status="status" />
<SourcesStatusCard :status="status" />
<SystemInfoCard :status="status" />
</div>
</div>
</div>
</template>
<script setup>
import { ArrowPathIcon, ExclamationCircleIcon } from '@heroicons/vue/24/outline';
import { storeToRefs } from 'pinia';
import { computed, onMounted } from 'vue';
import Toolbar from '../../../../shared/components/ui/Toolbar.vue';
import { useStatusStore } from '../../application/store/statusStore';
import ChaptersStatusCard from '../components/ChaptersStatusCard.vue';
import JobsStatusCard from '../components/JobsStatusCard.vue';
import MangasStatusCard from '../components/MangasStatusCard.vue';
import SourcesStatusCard from '../components/SourcesStatusCard.vue';
import StorageStatusCard from '../components/StorageStatusCard.vue';
import SystemInfoCard from '../components/SystemInfoCard.vue';
const statusStore = useStatusStore();
const { status, loading, error } = storeToRefs(statusStore);
onMounted(() => statusStore.loadStatus());
const toolbarConfig = computed(() => ({
leftSection: [
{ type: 'label', text: 'Statut système', class: 'text-sm font-medium' },
],
rightSection: [
{
type: 'button',
icon: ArrowPathIcon,
label: 'Rafraîchir',
disabled: loading.value,
onClick: () => statusStore.loadStatus(),
},
],
}));
</script>