Files
Mangarr/assets/vue/app/domain/system/presentation/pages/LogsPage.vue
ext.jeremy.guillot@maxicoffee.domains ec4a8be934
All checks were successful
Deploy / deploy (push) Successful in 2m30s
feat(system): ajouter filtre par statut dans la page Logs
- Filtre toolbar : Échecs / Terminés / Tous (défaut : Échecs)
- Badge statut sur chaque LogItem (vert Terminé / rouge Échec)
- deleteAllLogs respecte le filtre actif
2026-03-16 15:00:12 +01:00

166 lines
5.6 KiB
Vue

<template>
<div class="flex flex-col h-full">
<Toolbar :config="toolbarConfig" />
<div class="overflow-y-auto flex-1">
<section class="border-t border-gray-200 dark:border-gray-700">
<!-- Loading -->
<div v-if="isLoading" 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="hasError" class="px-6 py-8">
<div class="bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-700 p-4">
<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="logsStore.loadLogs()"
class="mt-3 px-4 py-2 bg-red-600 text-white hover:bg-red-700">
Réessayer
</button>
</div>
</div>
<!-- Empty -->
<div v-else-if="!isLoading && logs.length === 0" class="flex flex-col items-center justify-center py-20 text-gray-400 dark:text-gray-500">
<ExclamationCircleIcon class="w-12 h-12 mb-3" />
<p class="text-base">Aucune erreur de scraping</p>
</div>
<!-- List -->
<template v-else>
<LogItem
v-for="log in logs"
:key="log.id"
:log="log"
:source="getSource(log)"
@delete="handleDelete" />
</template>
</section>
<!-- Pagination -->
<Pagination
v-if="totalPages > 1"
:current-page="currentPage"
:total-pages="totalPages"
:total="total"
:limit="limit"
:has-next-page="hasNextPage"
:has-previous-page="hasPreviousPage"
@page-change="logsStore.goToPage" />
</div>
</div>
</template>
<script setup>
import { ArrowPathIcon, ExclamationCircleIcon, TrashIcon } from '@heroicons/vue/24/outline';
import { BarsArrowDownIcon } from '@heroicons/vue/24/outline';
import { storeToRefs } from 'pinia';
import { computed, onMounted } from 'vue';
import Toolbar from '../../../../shared/components/ui/Toolbar.vue';
import Pagination from '../../../../shared/components/ui/Pagination.vue';
import { useContentSourceStore } from '../../../setting/application/store/contentSourceStore';
import { useLogsStore } from '../../application/store/logsStore';
import LogItem from '../components/LogItem.vue';
const logsStore = useLogsStore();
const contentSourceStore = useContentSourceStore();
const { sources } = storeToRefs(contentSourceStore);
const {
logs,
loading: isLoading,
error,
currentPage,
totalPages,
total,
limit,
hasNextPage,
hasPreviousPage,
sortBy,
sortOrder,
statusFilter,
} = storeToRefs(logsStore);
const hasError = computed(() => !!error.value);
onMounted(() => {
logsStore.loadLogs();
contentSourceStore.loadSources();
});
function getSource(log) {
const sourceId = log.context?.sourceId;
if (!sourceId) return null;
// eslint-disable-next-line eqeqeq
return sources.value.find(s => s.id == sourceId) ?? null;
}
const isSortSelected = (by, order) => sortBy.value === by && sortOrder.value === order;
const STATUS_FILTERS = [
{ key: 'failed', label: 'Échecs' },
{ key: 'completed', label: 'Terminés' },
{ key: 'all', label: 'Tous' },
];
const toolbarConfig = computed(() => ({
leftSection: [
{ type: 'label', text: 'Logs', class: 'text-sm font-medium' },
{ type: 'label', text: `(${total.value})`, class: 'text-sm text-gray-400' },
],
rightSection: [
...STATUS_FILTERS.map(f => ({
type: 'button',
label: f.label,
active: statusFilter.value === f.key,
onClick: () => logsStore.setStatusFilter(f.key),
})),
{ type: 'divider' },
{
type: 'dropdown',
icon: BarsArrowDownIcon,
label: 'Trier',
items: [
{
label: 'Plus récent',
isSelected: isSortSelected('createdAt', 'DESC'),
onClick: () => logsStore.updateSort('createdAt', 'DESC'),
},
{
label: 'Plus ancien',
isSelected: isSortSelected('createdAt', 'ASC'),
onClick: () => logsStore.updateSort('createdAt', 'ASC'),
},
],
},
{
type: 'button',
icon: ArrowPathIcon,
label: 'Rafraîchir',
disabled: isLoading.value,
onClick: () => logsStore.loadLogs(),
},
{
type: 'button',
icon: TrashIcon,
label: 'Tout supprimer',
disabled: isLoading.value || total.value === 0,
onClick: handleDeleteAll,
},
],
}));
async function handleDelete(id) {
await logsStore.deleteLog(id);
}
async function handleDeleteAll() {
if (!confirm('Supprimer tous les logs d\'erreur ? Cette action est irréversible.')) return;
await logsStore.deleteAllLogs();
}
</script>