amélioration fluidité + ajout en cache des différentes pages pour chargement plus rapide
This commit is contained in:
@@ -89,10 +89,20 @@
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1 overflow-auto">
|
||||
<router-view />
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition name="fade" mode="out-in">
|
||||
<keep-alive :include="['DashboardView', 'ContainersView', 'DisksView', 'PackagesView', 'ShortcutsView']">
|
||||
<component :is="Component" :key="route.path" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</main>
|
||||
</div>
|
||||
<router-view v-else />
|
||||
<router-view v-else v-slot="{ Component }">
|
||||
<transition name="fade" mode="out-in">
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ const api = axios.create({
|
||||
baseURL: API_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
timeout: 10000 // Timeout de 10 secondes
|
||||
})
|
||||
|
||||
// Interceptor pour ajouter le token JWT
|
||||
|
||||
126
frontend/src/composables/useApiCache.js
Normal file
126
frontend/src/composables/useApiCache.js
Normal file
@@ -0,0 +1,126 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
// Cache global pour les données API
|
||||
const cache = new Map()
|
||||
const pendingRequests = new Map()
|
||||
|
||||
/**
|
||||
* Composable pour gérer le cache des requêtes API
|
||||
* Améliore les performances en évitant les requêtes redondantes
|
||||
*/
|
||||
export function useApiCache() {
|
||||
const getCacheKey = (url, params = {}) => {
|
||||
return `${url}_${JSON.stringify(params)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère des données avec cache
|
||||
* @param {string} url - URL de l'API
|
||||
* @param {Function} fetchFn - Fonction pour récupérer les données
|
||||
* @param {Object} options - Options de cache
|
||||
* @returns {Promise} - Données en cache ou nouvelles données
|
||||
*/
|
||||
const fetchWithCache = async (url, fetchFn, options = {}) => {
|
||||
const {
|
||||
ttl = 30000, // Time to live: 30 secondes par défaut
|
||||
forceRefresh = false,
|
||||
params = {}
|
||||
} = options
|
||||
|
||||
const cacheKey = getCacheKey(url, params)
|
||||
|
||||
// Si pas de rafraîchissement forcé, vérifier le cache
|
||||
if (!forceRefresh && cache.has(cacheKey)) {
|
||||
const cached = cache.get(cacheKey)
|
||||
const now = Date.now()
|
||||
|
||||
// Si les données sont encore valides
|
||||
if (now - cached.timestamp < ttl) {
|
||||
return cached.data
|
||||
}
|
||||
}
|
||||
|
||||
// Si une requête est déjà en cours pour cette clé, la réutiliser
|
||||
if (pendingRequests.has(cacheKey)) {
|
||||
return pendingRequests.get(cacheKey)
|
||||
}
|
||||
|
||||
// Créer une nouvelle requête
|
||||
const requestPromise = fetchFn()
|
||||
.then(data => {
|
||||
// Mettre en cache
|
||||
cache.set(cacheKey, {
|
||||
data,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
|
||||
// Supprimer de la liste des requêtes en cours
|
||||
pendingRequests.delete(cacheKey)
|
||||
|
||||
return data
|
||||
})
|
||||
.catch(error => {
|
||||
// En cas d'erreur, supprimer de la liste des requêtes en cours
|
||||
pendingRequests.delete(cacheKey)
|
||||
throw error
|
||||
})
|
||||
|
||||
// Ajouter à la liste des requêtes en cours
|
||||
pendingRequests.set(cacheKey, requestPromise)
|
||||
|
||||
return requestPromise
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalide le cache pour une URL spécifique ou tout le cache
|
||||
* @param {string} url - URL à invalider (optionnel)
|
||||
*/
|
||||
const invalidateCache = (url = null) => {
|
||||
if (url) {
|
||||
// Invalider toutes les entrées qui commencent par cette URL
|
||||
for (const key of cache.keys()) {
|
||||
if (key.startsWith(url)) {
|
||||
cache.delete(key)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Vider tout le cache
|
||||
cache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Précharge des données dans le cache
|
||||
* @param {string} url - URL de l'API
|
||||
* @param {Function} fetchFn - Fonction pour récupérer les données
|
||||
*/
|
||||
const prefetch = async (url, fetchFn, params = {}) => {
|
||||
const cacheKey = getCacheKey(url, params)
|
||||
|
||||
if (!cache.has(cacheKey) && !pendingRequests.has(cacheKey)) {
|
||||
await fetchWithCache(url, fetchFn, { params })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère la taille du cache
|
||||
*/
|
||||
const getCacheSize = () => cache.size
|
||||
|
||||
/**
|
||||
* Récupère les statistiques du cache
|
||||
*/
|
||||
const getCacheStats = () => ({
|
||||
size: cache.size,
|
||||
pendingRequests: pendingRequests.size,
|
||||
keys: Array.from(cache.keys())
|
||||
})
|
||||
|
||||
return {
|
||||
fetchWithCache,
|
||||
invalidateCache,
|
||||
prefetch,
|
||||
getCacheSize,
|
||||
getCacheStats
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,51 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import LoginView from '../views/LoginView.vue'
|
||||
import HomelabView from '../views/HomelabView.vue'
|
||||
import DashboardView from '../views/DashboardView.vue'
|
||||
import ContainersView from '../views/ContainersView.vue'
|
||||
import DisksView from '../views/DisksView.vue'
|
||||
import PackagesView from '../views/PackagesView.vue'
|
||||
import ShortcutsView from '../views/ShortcutsView.vue'
|
||||
|
||||
// Lazy loading pour améliorer les performances
|
||||
const LoginView = () => import('../views/LoginView.vue')
|
||||
const HomelabView = () => import('../views/HomelabView.vue')
|
||||
const DashboardView = () => import('../views/DashboardView.vue')
|
||||
const ContainersView = () => import('../views/ContainersView.vue')
|
||||
const DisksView = () => import('../views/DisksView.vue')
|
||||
const PackagesView = () => import('../views/PackagesView.vue')
|
||||
const ShortcutsView = () => import('../views/ShortcutsView.vue')
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Homelab',
|
||||
component: HomelabView,
|
||||
meta: { requiresAuth: false }
|
||||
meta: { requiresAuth: false, keepAlive: false }
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: LoginView,
|
||||
meta: { requiresAuth: false }
|
||||
meta: { requiresAuth: false, keepAlive: false }
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
component: DashboardView,
|
||||
meta: { requiresAuth: true }
|
||||
meta: { requiresAuth: true, keepAlive: true }
|
||||
},
|
||||
{
|
||||
path: '/containers',
|
||||
name: 'Containers',
|
||||
component: ContainersView,
|
||||
meta: { requiresAuth: true }
|
||||
meta: { requiresAuth: true, keepAlive: true }
|
||||
},
|
||||
{
|
||||
path: '/disks',
|
||||
name: 'Disks',
|
||||
component: DisksView,
|
||||
meta: { requiresAuth: true }
|
||||
meta: { requiresAuth: true, keepAlive: true }
|
||||
},
|
||||
{
|
||||
path: '/packages',
|
||||
name: 'Packages',
|
||||
component: PackagesView,
|
||||
meta: { requiresAuth: true }
|
||||
meta: { requiresAuth: true, keepAlive: true }
|
||||
},
|
||||
{
|
||||
path: '/shortcuts',
|
||||
|
||||
@@ -127,8 +127,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, onActivated, onBeforeUnmount } from 'vue'
|
||||
import api from '../api'
|
||||
import { useApiCache } from '../composables/useApiCache'
|
||||
|
||||
const { fetchWithCache, invalidateCache } = useApiCache()
|
||||
|
||||
const containers = ref([])
|
||||
const dockerStatus = ref({ connected: false })
|
||||
@@ -137,12 +140,20 @@ const actionLoading = ref(false)
|
||||
const showAll = ref(true)
|
||||
const notification = ref({ show: false, message: '', type: 'success' })
|
||||
|
||||
const fetchContainers = async () => {
|
||||
let refreshInterval = null
|
||||
|
||||
const fetchContainers = async (forceRefresh = false) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await api.get('/docker/containers', {
|
||||
params: { all: showAll.value }
|
||||
})
|
||||
const response = await fetchWithCache(
|
||||
'/docker/containers',
|
||||
() => api.get('/docker/containers', { params: { all: showAll.value } }),
|
||||
{
|
||||
ttl: 5000, // Cache de 5 secondes
|
||||
forceRefresh,
|
||||
params: { all: showAll.value }
|
||||
}
|
||||
)
|
||||
containers.value = response.data
|
||||
} catch (error) {
|
||||
showNotification('Erreur lors du chargement des conteneurs', 'error')
|
||||
@@ -154,7 +165,11 @@ const fetchContainers = async () => {
|
||||
|
||||
const checkDockerStatus = async () => {
|
||||
try {
|
||||
const response = await api.get('/docker/status')
|
||||
const response = await fetchWithCache(
|
||||
'/docker/status',
|
||||
() => api.get('/docker/status'),
|
||||
{ ttl: 10000 } // Cache de 10 secondes
|
||||
)
|
||||
dockerStatus.value = response.data
|
||||
} catch (error) {
|
||||
console.error('Erreur statut Docker:', error)
|
||||
@@ -180,7 +195,10 @@ const actionContainer = async (containerId, action) => {
|
||||
}
|
||||
|
||||
showNotification(`Conteneur ${action}é avec succès`, 'success')
|
||||
await fetchContainers()
|
||||
|
||||
// Invalider le cache et rafraîchir
|
||||
invalidateCache('/docker/containers')
|
||||
await fetchContainers(true)
|
||||
} catch (error) {
|
||||
showNotification(`Erreur lors du ${action} du conteneur`, 'error')
|
||||
console.error(error)
|
||||
@@ -191,7 +209,8 @@ const actionContainer = async (containerId, action) => {
|
||||
|
||||
const toggleShowAll = () => {
|
||||
showAll.value = !showAll.value
|
||||
fetchContainers()
|
||||
invalidateCache('/docker/containers')
|
||||
fetchContainers(true)
|
||||
}
|
||||
|
||||
const getStatusBadgeClass = (state) => {
|
||||
@@ -208,8 +227,34 @@ const showNotification = (message, type = 'success') => {
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
// Auto-refresh toutes les 10 secondes quand le composant est actif
|
||||
const startAutoRefresh = () => {
|
||||
refreshInterval = setInterval(() => {
|
||||
fetchContainers()
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
const stopAutoRefresh = () => {
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval)
|
||||
refreshInterval = null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkDockerStatus()
|
||||
fetchContainers()
|
||||
startAutoRefresh()
|
||||
})
|
||||
|
||||
// Appelé quand le composant est réactivé depuis keep-alive
|
||||
onActivated(() => {
|
||||
fetchContainers()
|
||||
startAutoRefresh()
|
||||
})
|
||||
|
||||
// Nettoyage
|
||||
onBeforeUnmount(() => {
|
||||
stopAutoRefresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user