Initial commit
This commit is contained in:
215
frontend/src/views/ContainersView.vue
Normal file
215
frontend/src/views/ContainersView.vue
Normal file
@@ -0,0 +1,215 @@
|
||||
<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 } from 'vue'
|
||||
import api from '../api'
|
||||
|
||||
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' })
|
||||
|
||||
const fetchContainers = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await api.get('/docker/containers', {
|
||||
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 api.get('/docker/status')
|
||||
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')
|
||||
await fetchContainers()
|
||||
} catch (error) {
|
||||
showNotification(`Erreur lors du ${action} du conteneur`, 'error')
|
||||
console.error(error)
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const toggleShowAll = () => {
|
||||
showAll.value = !showAll.value
|
||||
fetchContainers()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkDockerStatus()
|
||||
fetchContainers()
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user