[webapp] Optimise le chargement des snapshots renderers avec batch controlé

- Ajoute la fonction fetchBatchSnapshots pour limiter le nombre de requêtes simultanées (concurrency=3) et évite la saturation réseau avec un délai entre les batches
- Remplace le chargement séquentiel des snapshots par une approche batchée dans DashboardView.vue
- Introduit un état de chargement (isLoadingSnapshots) et une indiqueur visuel avec Loader2 animé
- Charge les renderers/servers en parallèle au montage via Promise.all
This commit is contained in:
2026-04-03 22:32:02 +02:00
parent 037ddc99ee
commit 0cb3cbe470
2 changed files with 90 additions and 8 deletions

View File

@@ -345,6 +345,46 @@ async function fetchRendererSnapshot(
} }
} }
/**
* Fetch les snapshots de plusieurs renderers en parallèle controlée.
* - Limite le nombre de requêtes simultanées (concurrency)
* - Ajoute un délai entre chaque batch pour ne pas saturer le réseau
* - Continue même si certaines requêtes échouent
*/
async function fetchBatchSnapshots(
rendererIds: string[],
options: {
concurrency?: number; // Nombre max de requêtes parallèles (défaut: 3)
batchDelay?: number; // Délai entre les batches en ms (défaut: 100ms)
force?: boolean; // Forcer le refetch même en cache
} = {}
): Promise<void> {
const { concurrency = 3, batchDelay = 100, force = false } = options;
// Filtrer les rendererIds valides
const validIds = rendererIds.filter(id => id && typeof id === 'string');
if (validIds.length === 0) return;
// Fonction pour traiter un batch
const processBatch = async (batch: string[]): Promise<void> => {
await Promise.allSettled(
batch.map(id => fetchRendererSnapshot(id, { force }))
);
};
// Exécuter par batches avec controlled concurrency
for (let i = 0; i < validIds.length; i += concurrency) {
const batch = validIds.slice(i, i + concurrency);
await processBatch(batch);
// Délai entre les batches (sauf pour le dernier)
if (i + concurrency < validIds.length) {
await new Promise(resolve => setTimeout(resolve, batchDelay));
}
}
}
// Transport controls // Transport controls
async function play(id: string) { async function play(id: string) {
await api.play(id); await api.play(id);
@@ -477,6 +517,7 @@ export function useRenderers() {
// Fetchers // Fetchers
fetchRenderers, fetchRenderers,
fetchRendererSnapshot, fetchRendererSnapshot,
fetchBatchSnapshots,
// Transport controls // Transport controls
play, play,
resumeOrPlayFromQueue, resumeOrPlayFromQueue,

View File

@@ -1,17 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from 'vue' import { onMounted, ref } from 'vue'
import { useRenderers } from '@/composables/useRenderers' import { useRenderers } from '@/composables/useRenderers'
import { useMediaServers } from '@/composables/useMediaServers' import { useMediaServers } from '@/composables/useMediaServers'
import RendererCard from '@/components/pmocontrol/RendererCard.vue' import RendererCard from '@/components/pmocontrol/RendererCard.vue'
import MediaServerCard from '@/components/pmocontrol/MediaServerCard.vue' import MediaServerCard from '@/components/pmocontrol/MediaServerCard.vue'
import { Radio, Server } from 'lucide-vue-next' import { Radio, Server, Loader2 } from 'lucide-vue-next'
const { const {
allRenderers: renderers, allRenderers: renderers,
onlineRenderers, onlineRenderers,
getStateById, getStateById,
fetchRenderers, fetchRenderers,
fetchRendererSnapshot fetchBatchSnapshots
} = useRenderers() } = useRenderers()
const { const {
@@ -20,15 +20,34 @@ const {
fetchServers fetchServers
} = useMediaServers() } = useMediaServers()
// État de chargement pour l'UI
const isLoadingSnapshots = ref(false)
// Charger les données au montage // Charger les données au montage
onMounted(async () => { onMounted(async () => {
await fetchRenderers() // Charger les listes de renderers et servers en parallèle
await fetchServers() await Promise.all([
fetchRenderers(),
fetchServers()
])
for (const renderer of renderers.value) { // Charger les snapshots en batch controlé
fetchRendererSnapshot(renderer.id, { force: true }) // On récupère les IDs des renderers découverts
const rendererIds = renderers.value.map(r => r.id)
if (rendererIds.length > 0) {
isLoadingSnapshots.value = true
// Appel batch avec concurrency controlée
await fetchBatchSnapshots(rendererIds, {
concurrency: 3, // Max 3 requêtes simultanées
batchDelay: 100, // 100ms entre chaque batch
force: true
})
isLoadingSnapshots.value = false
} }
}) });
</script> </script>
<template> <template>
@@ -55,6 +74,10 @@ onMounted(async () => {
<Radio :size="24" /> <Radio :size="24" />
<span>Renderers Audio</span> <span>Renderers Audio</span>
</h2> </h2>
<span v-if="isLoadingSnapshots" class="loading-indicator">
<Loader2 :size="16" class="spin" />
Chargement...
</span>
</div> </div>
<div v-if="renderers.length" class="renderers-grid"> <div v-if="renderers.length" class="renderers-grid">
@@ -175,6 +198,24 @@ onMounted(async () => {
color: var(--color-primary); color: var(--color-primary);
} }
/* Loading indicator */
.loading-indicator {
display: flex;
align-items: center;
gap: var(--spacing-xs);
font-size: var(--text-sm);
color: var(--color-text-secondary);
}
.loading-indicator .spin {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Grids */ /* Grids */
.renderers-grid, .renderers-grid,
.servers-grid { .servers-grid {