All checks were successful
Deploy / deploy (push) Successful in 2m30s
- Filtre toolbar : Échecs / Terminés / Tous (défaut : Échecs) - Badge statut sur chaque LogItem (vert Terminé / rouge Échec) - deleteAllLogs respecte le filtre actif
166 lines
5.6 KiB
Vue
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>
|