import { useState, useEffect, useCallback } from 'react'; import { getMyServices, getServiceDetails, getHostingServiceDetails, resetHostingPassword } from '../../services/api'; import { useVPC } from '../../services/useVPC'; import { Server, Database, Cloud, Globe, Folder, Trash2, ExternalLink, Loader, Plus, Play, Lock, Maximize2 } from 'lucide-react'; // ============================================================================ // COMPOSANT 0 : MODAL DE NOTIFICATION (Remplace les alert() natifs) // ============================================================================ const NotificationModal = ({ notification, onClose }) => { if (!notification) return null; const isError = notification.type === 'error'; return (

{notification.title.toUpperCase()}

{notification.message}

); }; // ============================================================================ // COMPOSANT 1 : INSTANCE CARD (Mode PaaS Pur - IP Masquée) // ============================================================================ const InstanceCard = ({ service, vpcs, onAssignVpc, onRemoveVpc, onOpenConsole, isConnecting }) => { const titleLower = (service.title || '').toLowerCase(); const isVPS = titleLower.includes('vps') || titleLower.includes('compute'); const isCloud = titleLower.includes('cloud') || titleLower.includes('nextcloud'); const isDB = titleLower.includes('db') || titleLower.includes('base') || titleLower.includes('sql'); const isWeb = !isVPS && !isCloud && !isDB; const hasIP = service.hostingDetails?.ip && service.hostingDetails.ip !== '127.0.0.1' && service.hostingDetails.ip !== ''; let buttonText = "CONSOLE D'ADMINISTRATION"; let ButtonIcon = ExternalLink; let buttonStyle = "bg-cyan-400/10 hover:bg-cyan-400 text-cyan-400 hover:text-gray-900 border-cyan-400"; if (isVPS) { if (hasIP) { buttonText = "GÉRER L'INSTANCE"; ButtonIcon = ExternalLink; } else { buttonText = "INITIALISATION"; ButtonIcon = Play; buttonStyle = "bg-yellow-500/10 text-yellow-500 hover:bg-yellow-500 hover:text-gray-900 border-yellow-500/50"; } } else if (isWeb) { buttonText = "GÉRER L'HÉBERGEMENT"; ButtonIcon = Globe; buttonStyle = "bg-emerald-400/10 hover:bg-emerald-400 text-emerald-400 hover:text-gray-900 border-emerald-400"; } else if (isCloud) { buttonText = "ACCÉDER AU CLOUD"; buttonStyle = "bg-blue-400/10 hover:bg-blue-400 text-blue-400 hover:text-gray-900 border-blue-400"; } else if (isDB) { buttonText = "PHPMYADMIN / CLUSTER"; ButtonIcon = Database; buttonStyle = "bg-purple-400/10 hover:bg-purple-400 text-purple-400 hover:text-gray-900 border-purple-400"; } const getServiceIcon = () => { if (isVPS) return ; if (isCloud) return ; if (isDB) return ; return ; }; const baseTitle = (service.title || '').split(/(?: for | pour )/i)[0].trim(); const shortTitle = baseTitle.split(' ').slice(0, 2).join(' '); const titleMatch = (service.title || '').match(/(?: for | pour )(.+)$/i); const domainFromTitle = titleMatch ? titleMatch[1].trim() : null; let displayDomain = service.hostingDetails?.domain && service.hostingDetails.domain !== '127.0.0.1' ? service.hostingDetails.domain : domainFromTitle || service.domain; if (!displayDomain || displayDomain === '127.0.0.1') displayDomain = null; return (
{getServiceIcon()}
{service.status === 'active' ? ( {isVPS && !hasIP ? 'AWAITING INIT' : 'ONLINE'} ) : ( DEPLOYING )}

{shortTitle}

{displayDomain ? (

{displayDomain}

) : (

En attente de déploiement

)}
Projet VPC:
); }; // ============================================================================ // COMPOSANT 2 : VPS DEPLOYER (Instanciation) // ============================================================================ const VpsDeployer = ({ serviceId, onDeploySuccess, onAlert }) => { const [domain, setDomain] = useState(''); const [password, setPassword] = useState(''); const [isDeploying, setIsDeploying] = useState(false); const handleDeploy = async () => { if (password.length < 8) { onAlert("Sécurité", "Le mot de passe doit faire au moins 8 caractères.", "error"); return; } setIsDeploying(true); try { const response = await fetch('https://web.gise.be/custom_api/proxmox_create_vps.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ service_id: serviceId, domain: domain, password: password }) }); const data = await response.json(); if (data.status === 'success') { onDeploySuccess({ ip: data.ip, domain: data.domain, password: password }); } else { onAlert("Échec", "Erreur de provisionnement : " + (data.message || 'Inconnue'), "error"); } } catch (err) { onAlert("Erreur Réseau", "Erreur de communication avec l'API Proxmox Gateway.", "error"); } finally { setIsDeploying(false); } }; 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" />
setPassword(e.target.value)} placeholder="••••••••" 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" />
); }; // ============================================================================ // COMPOSANT 3 : VPS MANAGER (Day-2 Operations) // ============================================================================ const VpsManager = ({ details, orderId, onRefresh, onAlert }) => { const [showTerminal, setShowTerminal] = useState(false); const [isEditingDomain, setIsEditingDomain] = useState(false); const [newDomain, setNewDomain] = useState(details.domain); const [isUpdating, setIsUpdating] = useState(false); const [sslStatus, setSslStatus] = useState(null); const isInternal = details.domain && details.domain.endsWith('.gise.be'); const isCustomDomain = details.domain && !isInternal; const fileManagerUrl = isInternal ? `https://file-${details.domain}` : `http://${details.domain}:8080`; const terminalUrl = isInternal ? `https://terminal-${details.domain}` : `http://${details.domain}:8081`; const handleUpdateDomain = async () => { if (!newDomain || newDomain === details.domain) return; setIsUpdating(true); try { const response = await fetch('https://web.gise.be/custom_api/update_service_domain.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ order_id: orderId, new_domain: newDomain }) }); const data = await response.json(); if (data.status === 'success') { setIsEditingDomain(false); if (onRefresh) onRefresh(); onAlert("Succès", "Domaine VPS mis à jour avec succès sur l'infrastructure !", "success"); } else onAlert("Erreur", data.error, "error"); } catch (err) { onAlert("Erreur", "Erreur de connexion avec l'API.", "error"); } finally { setIsUpdating(false); } }; const handleGenerateSSL = async () => { setSslStatus('loading'); try { const response = await fetch('https://web.gise.be/custom_api/generate_custom_ssl.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ domain: details.domain }) }); const data = await response.json(); if (data.status === 'success') { setSslStatus('success'); onAlert("SSL Activé", "Certificat SSL Let's Encrypt généré et activé avec succès !", "success"); } else { setSslStatus('error'); onAlert("Challenge DNS Échoué", "Vérifiez que votre domaine pointe bien vers notre IP publique.", "error"); } } catch (e) { setSslStatus('error'); } }; return (
{!showTerminal ? ( <>
{!isEditingDomain ? (
{details.domain}
) : (
setNewDomain(e.target.value.toLowerCase())} className="flex-1 bg-black border border-gray-700 rounded px-3 py-1.5 text-white font-mono text-sm focus:border-cyan-500 outline-none" disabled={isUpdating} />
)} {isCustomDomain && (

Domaine externe détecté. Sécurisez après pointage DNS.

)} {/* ACCÈS DE FAUT REMIS À JOUR */}
Identifiants d'usine :

Console SSH : root / Clé choisie à l'initialisation

Explorateur : admin / admin

) : (
NEXUS TERMINAL ({details.domain})
{/* OPTION AJOUTÉE : OUVRIR EN PLEIN ÉCRAN DANS UN NOUVEL ONGLET */}