+ );
+}
\ No newline at end of file
diff --git a/src/components/ui/ConfirmLogoutModal.jsx b/src/components/ui/ConfirmLogoutModal.jsx
new file mode 100644
index 0000000..159d4b2
--- /dev/null
+++ b/src/components/ui/ConfirmLogoutModal.jsx
@@ -0,0 +1,32 @@
+export default function ConfirmLogoutModal({ isOpen, onClose, onConfirm }) {
+ if (!isOpen) return null;
+
+ return (
+
+
+
+ DÉCONNEXION DU TERMINAL
+
+
+ Êtes-vous sûr de vouloir fermer la session sécurisée et quitter l'environnement cloud ?
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/ui/NotificationModal.jsx b/src/components/ui/NotificationModal.jsx
new file mode 100644
index 0000000..d914274
--- /dev/null
+++ b/src/components/ui/NotificationModal.jsx
@@ -0,0 +1,18 @@
+export default function NotificationModal({ notification, onClose }) {
+ if (!notification) return null;
+ const isError = notification.type === 'error';
+
+ return (
+
+
+
+ {notification.title.toUpperCase()}
+
+
{notification.message}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/layouts/AppLayout.jsx b/src/layouts/AppLayout.jsx
index e51ac3e..7342376 100644
--- a/src/layouts/AppLayout.jsx
+++ b/src/layouts/AppLayout.jsx
@@ -2,58 +2,7 @@
import { useState, useEffect } from 'react';
import { Outlet, NavLink, useNavigate } from 'react-router-dom';
import { getClientTickets } from '../services/api';
-
-// ============================================================================
-// COMPOSANT : MODAL DE CONFIRMATION DE DÉCONNEXION
-// ============================================================================
-const ConfirmLogoutModal = ({ isOpen, onClose, onConfirm }) => {
- if (!isOpen) return null;
-
- return (
-
-
-
- DÉCONNEXION DU TERMINAL
-
-
- Êtes-vous sûr de vouloir fermer la session sécurisée et quitter l'environnement cloud ?
-
-
-
-
-
-
-
- );
-};
+import ConfirmLogoutModal from '../components/ui/ConfirmLogoutModal';
// ============================================================================
// COMPOSANT PRINCIPAL : LAYOUT DU PORTAIL
diff --git a/src/pages/app/Dashboard.jsx b/src/pages/app/Dashboard.jsx
index 3e539d4..b78f41b 100644
--- a/src/pages/app/Dashboard.jsx
+++ b/src/pages/app/Dashboard.jsx
@@ -1,7 +1,11 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { getClientOrders } from '../../services/api';
-import { Server, Database, Cloud, Globe, Plus, AlertCircle, Loader } from 'lucide-react';
+import { AlertCircle, Loader } from 'lucide-react';
+
+// Importation des composants isolés
+import DashboardServiceCard from '../../components/dashboard/DashboardServiceCard';
+import NewServiceCard from '../../components/dashboard/NewServiceCard';
export default function Dashboard() {
const navigate = useNavigate();
@@ -21,16 +25,12 @@ export default function Dashboard() {
const title = (order.title || '').toLowerCase();
const type = (order.type || '').toLowerCase();
- // On masque la commande SI :
- // 1. Le type technique est "domain"
- // 2. OU le titre COMMENCE par "domain", "domaine" ou "enregistrement"
const isGhostProduct =
type === 'domain' ||
title.startsWith('domain ') ||
title.startsWith('domaine ') ||
title.startsWith('enregistrement ');
- // On retourne true (on affiche) uniquement si ce n'est pas un produit fantôme
return !isGhostProduct;
});
@@ -48,25 +48,6 @@ export default function Dashboard() {
fetchInventory();
}, []);
- // Fonction Radar : Détecte le type de service selon son nom pour afficher la bonne icône
- const getServiceIcon = (title) => {
- const t = title.toLowerCase();
- if (t.includes('vps') || t.includes('serveur')) return ;
- if (t.includes('cloud') || t.includes('nextcloud')) return ;
- if (t.includes('db') || t.includes('base') || t.includes('sql')) return ;
- return ; // Par défaut : Web / Hestia
- };
-
- // Fonction d'état : Formate le statut du service
- const getStatusBadge = (status) => {
- switch (status) {
- case 'active': return ACTIF;
- case 'pending_setup': return EN PRÉPARATION;
- case 'suspended': return SUSPENDU;
- default: return {status.toUpperCase()};
- }
- };
-
return (
- PHASE D'INITIALISATION REQUISE
- La facturation est activée. Veuillez définir un mot de passe Root pour lancer la création de l'instance sur l'hyperviseur.
-
-
-
-
-
- setDomain(e.target.value.toLowerCase())} placeholder="Laissez vide pour le défaut" className="w-full bg-gray-950 border border-gray-800 text-white p-3 rounded font-mono outline-none focus:border-cyan-500 transition-colors" />
-
- {/* GESTION DU CHARGEMENT IDENTIQUE AU DASHBOARD */}
- {isLoading && (
-
-
- Analyse du réseau et des instances en cours...
-
- )}
+ {isLoading &&
Analyse en cours...
}
+ {error &&
{error}
}
- {/* GESTION DES ERREURS IDENTIQUE AU DASHBOARD */}
- {error && (
-
-
- {error}
-
- )}
-
- {/* CONTENU (Caché pendant le chargement pour rester propre) */}
{!isLoading && !error && (
<>
@@ -588,110 +105,48 @@ export default function Services() {
-
-
{vpc.name.toUpperCase()}
- {vpcServices.length} INSTANCES
+
{vpc.name.toUpperCase()}
{vpcServices.length} INSTANCES
- { deleteVPC(vpc.id); triggerAlert("VPC Démantelé", "Le groupe de routage a été dissous. Les instances ont été reversées dans le pool libre.", "info"); }} className="text-gray-500 hover:text-red-400 transition-colors flex items-center text-sm">
- Démanteler VPC
-
+ { deleteVPC(vpc.id); triggerAlert("VPC Démantelé", "Instances reversées.", "info"); }} className="text-gray-500 hover:text-red-400 transition-colors flex items-center text-sm"> Démanteler
- {vpcServices.length === 0 ? (
-
- Réseau virtuel vide. Assigner des instances depuis le pool libre.
-
handleOpenTicket(ticket)} // Modification ici : Appel de la fonction de chargement au lieu du setter direct
- className="bg-gray-900 border border-gray-800 rounded-xl p-5 hover:border-cyan-400/50 hover:bg-gray-800/50 transition-all cursor-pointer group flex flex-col justify-between min-h-[160px]"
- >
-
-
-
- {ticket.id}
-
- {/* LE RADAR : Pastille clignotante en cas de réponse */}
- {ticket.status === 'on_hold' && (
-
-
-
-
- )}
-
- Ce serveur est maintenant rattaché à votre compte. Vous pouvez vous y connecter en SSH via l'IP ou le domaine (une fois les DNS propagés).
-
-
- )}
-
- );
-};
-
-export default VpsDeployer;
\ No newline at end of file
diff --git a/src/pages/app/VpsManager.jsx b/src/pages/app/VpsManager.jsx
deleted file mode 100644
index 213ef97..0000000
--- a/src/pages/app/VpsManager.jsx
+++ /dev/null
@@ -1,112 +0,0 @@
-import React, { useState } from 'react';
-
-// Ce composant reçoit les détails du service depuis ton API FOSSBilling habituelle
-const VpsManager = ({ serviceDetails }) => {
- const [isActionPending, setIsActionPending] = useState(false);
-
- // Fonction pour envoyer des ordres d'alimentation à Proxmox (via ton API)
- const handlePowerAction = async (action) => {
- if (!window.confirm(`Êtes-vous sûr de vouloir ${action} ce VPS ?`)) return;
- setIsActionPending(true);
-
- try {
- // Tu devras créer un petit fichier 'proxmox_power.php' plus tard pour gérer ça
- alert(`Signal d'alimentation "${action}" envoyé à l'hyperviseur.`);
- // const response = await fetch('https://web.gise.be/custom_api/proxmox_power.php', { ... })
- } catch (error) {
- alert("Erreur de communication avec l'infrastructure.");
- } finally {
- setIsActionPending(false);
- }
- };
-
- // Si Proxmox n'a pas encore fini de configurer, on affiche un loader
- if (!serviceDetails.ip) {
- return (
-
-
-
-
PROVISIONNEMENT EN COURS...
-
Votre serveur est en cours d'assemblage sur le VLAN 60. Cela prend environ 10 secondes.
- );
-};
-
-export default VpsManager;
\ No newline at end of file
diff --git a/src/pages/app/VpsServiceDetail.jsx b/src/pages/app/VpsServiceDetail.jsx
deleted file mode 100644
index 8c63118..0000000
--- a/src/pages/app/VpsServiceDetail.jsx
+++ /dev/null
@@ -1,34 +0,0 @@
-// composant VpsServiceDetail.jsx
-import React, { useState } from 'react';
-// Tu as déjà ces composants de la discussion précédente :
-// import VpsDeployer from './VpsDeployer';
-// import VpsManager from './VpsManager';
-
-const VpsServiceDetail = ({ serviceDetails }) => {
-
- // Le test logique : Si le service a une IP, il est déjà créé sur Proxmox.
- const isProvisioned = serviceDetails.ip && serviceDetails.ip !== '';
-
- return (
-
-
Gestion VPS
-
- {isProvisioned ? (
- // Étape 4 (après création) : Le tableau de bord
-
- ) : (
- // Étape 3 (avant création) : Le formulaire de déploiement
-
-
-
Votre VPS est prêt à être instancié !
-
La facturation est activée. Veuillez configurer les accès initiaux pour lancer la création sur l'infrastructure (VLAN 60).
-
- {/* On passe le service_id au composant Deployer */}
-
-
- )}
-
- );
-};
-
-export default VpsServiceDetail;
\ No newline at end of file
diff --git a/src/pages/public/Register.jsx b/src/pages/public/Register.jsx
index 560f67f..8022bd9 100644
--- a/src/pages/public/Register.jsx
+++ b/src/pages/public/Register.jsx
@@ -1,46 +1,7 @@
import { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { registerUnifiedClient } from '../../services/api';
-
-// ============================================================================
-// COMPOSANT : MODAL DE NOTIFICATION (Succès / Erreur)
-// ============================================================================
-const NotificationModal = ({ notification, onClose }) => {
- if (!notification) return null;
- const isError = notification.type === 'error';
-
- return (
-
-
-
- {notification.title.toUpperCase()}
-
-
- {notification.message}
-
-
- COMPRIS
-
-
-
- );
-};
+import NotificationModal from '../../components/ui/NotificationModal';
// ============================================================================
// COMPOSANT PRINCIPAL : REGISTER