feat(setting): étendre ContentSource avec champs de test et domain model
- Ajouter testSlug, testChapterNumber, baseUrl sur ContentSource (entité, domain model, migration) - Exposer ces champs dans les Resources, Processors, Providers et Mapper - Mettre à jour store Pinia, repository API et composants Vue (form, card, liste)
This commit is contained in:
parent
b0ce36096f
commit
795cbeccc3
@@ -30,6 +30,14 @@
|
||||
class="px-2 py-1 text-xs font-medium">
|
||||
{{ getOrientation(source) }}
|
||||
</span>
|
||||
|
||||
<!-- Badge health status -->
|
||||
<span
|
||||
:class="getHealthBadgeClass(source.healthStatus)"
|
||||
class="px-2 py-1 text-xs font-medium"
|
||||
:title="source.healthLastError || ''">
|
||||
{{ getHealthLabel(source.healthStatus) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -39,6 +47,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/outline';
|
||||
import { ScraperHealthStatus } from '../../domain/model/ScraperHealthStatus';
|
||||
|
||||
defineProps({
|
||||
source: {
|
||||
@@ -86,4 +95,26 @@ const getOrientationBadgeClass = (source) => {
|
||||
return 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300';
|
||||
}
|
||||
};
|
||||
|
||||
const getHealthLabel = (status) => {
|
||||
switch (status) {
|
||||
case ScraperHealthStatus.OK: return '✓ ok';
|
||||
case ScraperHealthStatus.KO: return '✗ ko';
|
||||
case ScraperHealthStatus.TESTING: return '⟳ test';
|
||||
default: return '? unknown';
|
||||
}
|
||||
};
|
||||
|
||||
const getHealthBadgeClass = (status) => {
|
||||
switch (status) {
|
||||
case ScraperHealthStatus.OK:
|
||||
return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300';
|
||||
case ScraperHealthStatus.KO:
|
||||
return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300';
|
||||
case ScraperHealthStatus.TESTING:
|
||||
return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300';
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -108,17 +108,17 @@
|
||||
<div class="border-t border-gray-200 dark:border-gray-700 pt-6 mt-6">
|
||||
<div class="flex items-center space-x-2 mb-6">
|
||||
<WrenchScrewdriverIcon class="w-5 h-5 text-gray-600 dark:text-gray-400" />
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white">Test de la configuration</h3>
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white">Configuration de test (health check)</h3>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label for="testMangaSlug" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Manga Slug
|
||||
<label for="testSlug" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Manga Slug <span class="text-gray-500">(enregistré)</span>
|
||||
</label>
|
||||
<input
|
||||
id="testMangaSlug"
|
||||
v-model="testData.mangaSlug"
|
||||
id="testSlug"
|
||||
v-model="form.testSlug"
|
||||
type="text"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="manga-slug" />
|
||||
@@ -126,11 +126,11 @@
|
||||
|
||||
<div>
|
||||
<label for="testChapterNumber" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Numéro de chapitre
|
||||
Numéro de chapitre <span class="text-gray-500">(enregistré)</span>
|
||||
</label>
|
||||
<input
|
||||
id="testChapterNumber"
|
||||
v-model="testData.chapterNumber"
|
||||
v-model="form.testChapterNumber"
|
||||
type="number"
|
||||
step="0.1"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
@@ -151,7 +151,7 @@
|
||||
class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-medium transition-colors duration-200 flex items-center justify-center space-x-2">
|
||||
<ArrowPathIcon v-if="testing" class="w-4 h-4 animate-spin" />
|
||||
<PlayIcon v-else class="w-4 h-4" />
|
||||
<span>Lancer le test</span>
|
||||
<span>Tester maintenant</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -191,12 +191,9 @@ const form = ref({
|
||||
nextPageSelector: '',
|
||||
chapterSelector: '',
|
||||
scrapingType: 'html',
|
||||
token: ''
|
||||
});
|
||||
|
||||
const testData = ref({
|
||||
mangaSlug: '',
|
||||
chapterNumber: ''
|
||||
token: '',
|
||||
testSlug: '',
|
||||
testChapterNumber: '',
|
||||
});
|
||||
|
||||
const testing = ref(false);
|
||||
@@ -204,17 +201,17 @@ const testing = ref(false);
|
||||
const canTest = computed(() => {
|
||||
return form.value.baseUrl &&
|
||||
form.value.chapterUrlFormat &&
|
||||
testData.value.mangaSlug &&
|
||||
testData.value.chapterNumber;
|
||||
form.value.testSlug &&
|
||||
form.value.testChapterNumber;
|
||||
});
|
||||
|
||||
const generatedTestUrl = computed(() => {
|
||||
if (!form.value.chapterUrlFormat || !testData.value.mangaSlug || !testData.value.chapterNumber) {
|
||||
if (!form.value.chapterUrlFormat || !form.value.testSlug || !form.value.testChapterNumber) {
|
||||
return '';
|
||||
}
|
||||
return form.value.chapterUrlFormat
|
||||
.replace('{slug}', testData.value.mangaSlug)
|
||||
.replace('{chapterNumber}', testData.value.chapterNumber);
|
||||
.replace('{slug}', form.value.testSlug)
|
||||
.replace('{chapterNumber}', form.value.testChapterNumber);
|
||||
});
|
||||
|
||||
watch(() => props.source, (newSource) => {
|
||||
@@ -226,7 +223,9 @@ watch(() => props.source, (newSource) => {
|
||||
nextPageSelector: newSource.nextPageSelector || '',
|
||||
chapterSelector: newSource.chapterSelector || '',
|
||||
scrapingType: (newSource.scrapingType || 'html').toLowerCase(),
|
||||
token: newSource.token || ''
|
||||
token: newSource.token || '',
|
||||
testSlug: newSource.testSlug || '',
|
||||
testChapterNumber: newSource.testChapterNumber ?? '',
|
||||
};
|
||||
} else {
|
||||
form.value = {
|
||||
@@ -236,7 +235,9 @@ watch(() => props.source, (newSource) => {
|
||||
nextPageSelector: '',
|
||||
chapterSelector: '',
|
||||
scrapingType: 'html',
|
||||
token: ''
|
||||
token: '',
|
||||
testSlug: '',
|
||||
testChapterNumber: '',
|
||||
};
|
||||
}
|
||||
}, { immediate: true });
|
||||
@@ -253,8 +254,9 @@ const testConfiguration = async () => {
|
||||
await emit('test', {
|
||||
configuration: { ...form.value },
|
||||
testData: {
|
||||
...testData.value,
|
||||
testUrl: generatedTestUrl.value
|
||||
mangaSlug: form.value.testSlug,
|
||||
chapterNumber: form.value.testChapterNumber,
|
||||
testUrl: generatedTestUrl.value,
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
|
||||
@@ -91,10 +91,11 @@ import {
|
||||
ArrowPathIcon,
|
||||
ArrowUpTrayIcon,
|
||||
ExclamationTriangleIcon,
|
||||
HeartIcon,
|
||||
PlusIcon
|
||||
} from '@heroicons/vue/24/outline';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import Toolbar from '../../../../shared/components/ui/Toolbar.vue';
|
||||
import { useContentSourceStore } from '../../application/store/contentSourceStore';
|
||||
@@ -108,9 +109,13 @@ const {
|
||||
loadingSources,
|
||||
sourcesError,
|
||||
importing,
|
||||
exporting
|
||||
exporting,
|
||||
checkingHealth,
|
||||
} = storeToRefs(contentSourceStore);
|
||||
|
||||
// Mercure — écoute des mises à jour health
|
||||
let mercureEventSource = null;
|
||||
|
||||
// Local state
|
||||
const showImportModal = ref(false);
|
||||
const showExportSuccess = ref(false);
|
||||
@@ -120,12 +125,32 @@ const importData = ref('');
|
||||
// Load sources on mount and clear current source
|
||||
onMounted(async () => {
|
||||
try {
|
||||
contentSourceStore.clearCurrentSource(); // Clear any previously loaded source
|
||||
contentSourceStore.clearErrors(); // Clear any previous errors
|
||||
contentSourceStore.clearCurrentSource();
|
||||
contentSourceStore.clearErrors();
|
||||
await contentSourceStore.loadSources();
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des sources:', error);
|
||||
}
|
||||
|
||||
// Écoute Mercure pour les mises à jour de health status
|
||||
const url = new URL('/.well-known/mercure', window.location.href);
|
||||
sources.value.forEach(source => {
|
||||
url.searchParams.append('topic', `scrapers/health/${source.id}`);
|
||||
});
|
||||
|
||||
mercureEventSource = new EventSource(url.toString());
|
||||
mercureEventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
contentSourceStore.updateSourceHealth(data.sourceId, data.status, data.error);
|
||||
} catch (e) {
|
||||
console.error('Erreur parsing Mercure event:', e);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
mercureEventSource?.close();
|
||||
});
|
||||
|
||||
// Toolbar configuration
|
||||
@@ -135,6 +160,7 @@ const toolbarConfig = computed(() => ({
|
||||
],
|
||||
rightSection: [
|
||||
{ type: 'button', icon: ArrowPathIcon, label: 'Actualiser', onClick: () => contentSourceStore.loadSources(), disabled: loadingSources.value },
|
||||
{ type: 'button', icon: HeartIcon, label: 'Tester tous', onClick: handleCheckAllHealth, disabled: checkingHealth.value },
|
||||
{ type: 'button', icon: ArrowDownTrayIcon, label: 'Exporter', onClick: handleExport, disabled: exporting.value },
|
||||
{ type: 'button', icon: ArrowUpTrayIcon, label: 'Importer', onClick: () => showImportModal.value = true },
|
||||
],
|
||||
@@ -156,6 +182,14 @@ const openSourceLink = (url) => {
|
||||
window.open(url, '_blank');
|
||||
};
|
||||
|
||||
async function handleCheckAllHealth() {
|
||||
try {
|
||||
await contentSourceStore.checkAllHealth();
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du health check:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleExport() {
|
||||
try {
|
||||
const exportData = await contentSourceStore.exportSources();
|
||||
|
||||
Reference in New Issue
Block a user