Files
innotexBoard/frontend/src/views/ContainersView.vue

261 lines
7.9 KiB
Vue

<template>
<div class="p-8">
<h1 class="text-3xl font-bold mb-8 text-gray-100">🐳 Gestion Docker</h1>
<!-- Statut Docker -->
<div class="mb-6">
<div v-if="dockerStatus.connected" class="p-4 bg-green-500/20 border border-green-500/50 rounded-lg text-green-400">
Docker est connecté et accessible
</div>
<div v-else class="p-4 bg-red-500/20 border border-red-500/50 rounded-lg text-red-400">
Docker n'est pas accessible. Vérifiez la configuration.
</div>
</div>
<!-- Boutons d'action -->
<div class="mb-6 flex space-x-3">
<button
@click="fetchContainers"
:disabled="loading"
class="btn btn-primary"
>
🔄 Rafraîchir
</button>
<button
@click="toggleShowAll"
:class="showAll ? 'btn btn-secondary' : 'btn btn-primary'"
>
{{ showAll ? 'Afficher actifs' : 'Afficher tous' }}
</button>
</div>
<!-- Liste des conteneurs -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div
v-for="container in containers"
:key="container.id"
class="card hover:border-blue-500/50 transition"
>
<!-- En-tête du conteneur -->
<div class="flex items-start justify-between mb-4">
<div>
<h3 class="text-lg font-semibold text-blue-400">{{ container.name }}</h3>
<p class="text-gray-400 text-sm">{{ container.image }}</p>
<p class="text-gray-500 text-xs mt-1">ID: {{ container.id }}</p>
</div>
<span :class="getStatusBadgeClass(container.state)" class="px-3 py-1 rounded-full text-xs font-medium">
{{ container.state }}
</span>
</div>
<!-- Ressources -->
<div class="grid grid-cols-2 gap-3 mb-4 text-sm">
<div class="bg-gray-700/50 p-2 rounded">
<p class="text-gray-400 text-xs">CPU</p>
<p class="text-blue-400 font-semibold">{{ container.cpu_percent }}%</p>
</div>
<div class="bg-gray-700/50 p-2 rounded">
<p class="text-gray-400 text-xs">Mémoire</p>
<p class="text-green-400 font-semibold">{{ container.memory_usage }}</p>
</div>
</div>
<!-- Ports -->
<div v-if="container.ports.length > 0" class="mb-4">
<p class="text-gray-400 text-xs mb-2">Ports:</p>
<div class="space-y-1">
<div
v-for="(port, idx) in container.ports"
:key="idx"
class="text-xs text-gray-300 bg-gray-700/30 px-2 py-1 rounded"
>
{{ port.public_port || '-' }}:{{ port.private_port }}/{{ port.type }}
</div>
</div>
</div>
<!-- Boutons d'action -->
<div class="flex space-x-2 pt-4 border-t border-gray-700">
<button
v-if="container.state !== 'running'"
@click="actionContainer(container.id, 'start')"
:disabled="actionLoading"
class="btn btn-secondary btn-small flex-1"
>
▶ Démarrer
</button>
<button
v-if="container.state === 'running'"
@click="actionContainer(container.id, 'stop')"
:disabled="actionLoading"
class="btn btn-danger btn-small flex-1"
>
⏹ Arrêter
</button>
<button
@click="actionContainer(container.id, 'restart')"
:disabled="actionLoading"
class="btn btn-primary btn-small"
>
🔄
</button>
<button
@click="actionContainer(container.id, 'delete')"
:disabled="actionLoading"
class="btn btn-danger btn-small"
>
🗑
</button>
</div>
</div>
</div>
<!-- Message si aucun conteneur -->
<div v-if="containers.length === 0 && !loading" class="text-center py-12">
<p class="text-gray-400">Aucun conteneur {{ showAll ? '' : 'actif' }} trouvé</p>
</div>
<!-- Notification d'action -->
<div
v-if="notification.show"
:class="notification.type === 'success' ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'"
class="fixed bottom-4 right-4 p-4 rounded-lg border transition"
>
{{ notification.message }}
</div>
</div>
</template>
<script setup>
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 })
const loading = ref(false)
const actionLoading = ref(false)
const showAll = ref(true)
const notification = ref({ show: false, message: '', type: 'success' })
let refreshInterval = null
const fetchContainers = async (forceRefresh = false) => {
loading.value = true
try {
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')
console.error(error)
} finally {
loading.value = false
}
}
const checkDockerStatus = async () => {
try {
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)
}
}
const actionContainer = async (containerId, action) => {
actionLoading.value = true
try {
if (action === 'start') {
await api.post(`/docker/containers/${containerId}/start`)
} else if (action === 'stop') {
await api.post(`/docker/containers/${containerId}/stop`)
} else if (action === 'restart') {
await api.post(`/docker/containers/${containerId}/restart`)
} else if (action === 'delete') {
if (confirm('Êtes-vous sûr de vouloir supprimer ce conteneur ?')) {
await api.delete(`/docker/containers/${containerId}`)
} else {
actionLoading.value = false
return
}
}
showNotification(`Conteneur ${action}é avec succès`, 'success')
// 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)
} finally {
actionLoading.value = false
}
}
const toggleShowAll = () => {
showAll.value = !showAll.value
invalidateCache('/docker/containers')
fetchContainers(true)
}
const getStatusBadgeClass = (state) => {
const baseClass = 'px-2 py-1 rounded-full text-xs font-medium'
if (state === 'running') return baseClass + ' bg-green-500/30 text-green-400'
if (state === 'exited') return baseClass + ' bg-red-500/30 text-red-400'
return baseClass + ' bg-gray-600/30 text-gray-400'
}
const showNotification = (message, type = 'success') => {
notification.value = { show: true, message, type }
setTimeout(() => {
notification.value.show = false
}, 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>