[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:
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user