Files
portail-gise/src/pages/app/Services.jsx
T
LathanDevers 15213bba21 change api
2026-07-16 14:51:57 +02:00

149 lines
10 KiB
React

import { useState, useEffect, useCallback } from 'react';
import { getOrdersList, getOrderDetails } from '../../services/billing_api';
import { useVPC } from '../../services/useVPC';
import { Server, Globe, Folder, Trash2, Loader, Plus, AlertCircle } from 'lucide-react';
// Importation de tes nouveaux composants modulaires !
import NotificationModal from '../../components/ui/NotificationModal';
import InstanceCard from '../../components/services/InstanceCard';
import VpsDeployer from '../../components/services/VpsDeployer';
import VpsManager from '../../components/services/VpsManager';
import WebManager from '../../components/services/WebManager';
export default function Services() {
const [services, setServices] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const [newVpcName, setNewVpcName] = useState("");
const [isConnecting, setIsConnecting] = useState(null);
const [activeServiceModal, setActiveServiceModal] = useState(null);
const [customAlert, setCustomAlert] = useState(null);
const { vpcs, createVPC, deleteVPC, assignToVPC, removeFromVPC } = useVPC();
const triggerAlert = (title, message, type = "info") => setCustomAlert({ title, message, type });
const fetchServices = useCallback(async () => {
try {
const data = await getOrdersList();
if (data.list && data.list.length > 0) {
const detailedServices = await Promise.all(data.list.map(async (order) => {
let hDetails = null;
if (order.status === 'active' && (order.plugin === 'hosting' || (order.title || '').toLowerCase().includes('vps'))) {
try { hDetails = await getOrderDetails(order.id); } catch (e) {}
}
return { ...order, hostingDetails: hDetails };
}));
const filteredServices = detailedServices.filter(s => {
const type = (s.type || '').toLowerCase();
const title = (s.title || '').toLowerCase();
const isGhostProduct = type === 'domain' || title.includes('domain') || title.includes('domaine') || title.includes('enregistrement');
return (s.status === 'active' || s.status === 'pending_setup') && !isGhostProduct;
});
setServices(filteredServices);
}
} catch (err) { setError(err.message || "Impossible de charger la télémétrie."); }
finally { setIsLoading(false); }
}, []);
useEffect(() => { fetchServices(); }, [fetchServices]);
const handleOpenConsole = async (service) => {
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;
setIsConnecting(service.id);
try {
if (isCloud) {
const titleMatch = (service.title || '').match(/(?: for | pour )(.+)$/i);
const domainUrl = titleMatch ? titleMatch[1].trim() : service.domain;
if (domainUrl) window.open(`https://${domainUrl}`, '_blank');
} else if (isDB) {
window.open('https://pma.gise.be/', '_blank');
} else if (isVPS || isWeb) {
const freshDetails = await getOrderDetails(service.id);
setActiveServiceModal({ type: isVPS ? 'vps' : 'web', data: service, details: freshDetails });
}
} catch (err) { triggerAlert("Erreur", err.message, "error"); }
finally { setIsConnecting(null); }
};
const assignedServiceIds = vpcs.flatMap(vpc => vpc.services);
const freeServices = services.filter(s => !assignedServiceIds.includes(s.id));
return (
<div className="w-full max-w-6xl p-6 mx-auto">
<div className="flex flex-col md:flex-row justify-between items-start md:items-end mb-8 gap-4">
<div>
<header>
<h1 className="text-3xl font-black text-white tracking-wider">TERMINAL <span className="text-cyan-400">NEXUS</span></h1>
<p className="text-gray-400 mt-2">Orchestration des environnements et des Virtual Private Clouds.</p>
</header>
</div>
<div className="flex space-x-2 bg-gray-900 p-2 rounded-xl border border-gray-800">
<input type="text" placeholder="Nom du nouveau VPC..." value={newVpcName} onChange={(e) => setNewVpcName(e.target.value)} className="bg-black border border-gray-800 rounded-lg px-4 py-2 text-sm text-white focus:outline-none focus:border-cyan-400 w-64" />
<button onClick={() => { createVPC(newVpcName); setNewVpcName(""); triggerAlert("VPC", "Nouveau groupe créé !", "success"); }} disabled={!newVpcName.trim()} className="bg-cyan-400 text-gray-900 px-4 py-2 rounded-lg font-bold text-sm disabled:opacity-50 hover:bg-cyan-300 flex items-center">
<Plus className="w-4 h-4 mr-1" /> CRÉER GROUPE
</button>
</div>
</div>
{isLoading && <div className="flex items-center space-x-3 text-cyan-400 mb-8"><Loader className="w-6 h-6 animate-spin" /><span>Analyse en cours...</span></div>}
{error && <div className="flex items-center space-x-3 text-red-400 bg-red-400/10 border border-red-400 p-4 rounded-lg mb-8"><AlertCircle className="w-6 h-6" /><span>{error}</span></div>}
{!isLoading && !error && (
<>
<div className="space-y-8 mb-12">
{vpcs.map(vpc => {
const vpcServices = services.filter(s => vpc.services.includes(s.id));
return (
<div key={vpc.id} className="bg-gray-900/40 border border-gray-800 rounded-2xl p-6">
<div className="flex justify-between items-center mb-6 border-b border-gray-800 pb-4">
<div className="flex items-center space-x-3 text-white">
<Folder className="w-6 h-6 text-cyan-400" /><h2 className="text-xl font-bold tracking-wider">{vpc.name.toUpperCase()}</h2><span className="bg-gray-800 text-gray-400 px-2 py-0.5 rounded text-xs font-mono">{vpcServices.length} INSTANCES</span>
</div>
<button onClick={() => { 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"><Trash2 className="w-4 h-4 mr-1" /> Démanteler</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{vpcServices.length === 0 ? <div className="col-span-full text-gray-600 text-sm border border-dashed border-gray-800 rounded-xl p-6 text-center font-mono">Vide.</div> : vpcServices.map(service => <InstanceCard key={service.id} service={service} vpcs={vpcs} onAssignVpc={assignToVPC} onRemoveVpc={removeFromVPC} onOpenConsole={handleOpenConsole} isConnecting={isConnecting} />)}
</div>
</div>
);
})}
</div>
<div>
<h2 className="text-lg font-bold text-gray-500 tracking-wider mb-6 flex items-center"><Server className="w-5 h-5 mr-2" /> POOL LIBRE</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{freeServices.length === 0 ? <div className="col-span-full text-gray-600 text-sm border border-gray-900 bg-gray-900/20 rounded-xl p-6 text-center font-mono">Aucune instance libre.</div> : freeServices.map(service => <InstanceCard key={service.id} service={service} vpcs={vpcs} onAssignVpc={assignToVPC} onRemoveVpc={removeFromVPC} onOpenConsole={handleOpenConsole} isConnecting={isConnecting} />)}
</div>
</div>
</>
)}
{activeServiceModal && (
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div className={`bg-gray-900 border rounded-lg shadow-2xl max-w-2xl w-full p-6 text-gray-200 ${activeServiceModal.type === 'vps' ? 'border-cyan-500/50 shadow-cyan-500/20' : 'border-emerald-500/50 shadow-emerald-500/20'}`}>
<div className="flex justify-between items-center mb-6 border-b border-gray-800 pb-4">
<h3 className="text-xl font-bold text-white tracking-wider flex items-center gap-3">
{activeServiceModal.type === 'vps' ? <><Server className="w-6 h-6 text-cyan-400" /> GESTION VPS</> : <><Globe className="w-6 h-6 text-emerald-400" /> GESTION WEB</>}
</h3>
<button onClick={() => setActiveServiceModal(null)} className="text-gray-400 hover:text-white transition text-xl"></button>
</div>
{activeServiceModal.type === 'vps' ? (
activeServiceModal.details?.ip && activeServiceModal.details.ip !== '127.0.0.1' ? <VpsManager details={activeServiceModal.details} orderId={activeServiceModal.data.id} onRefresh={fetchServices} onAlert={triggerAlert} /> : <VpsDeployer serviceId={activeServiceModal.data.id} onDeploySuccess={() => { setActiveServiceModal(null); fetchServices(); }} onAlert={triggerAlert} />
) : (
<WebManager details={activeServiceModal.details} orderId={activeServiceModal.data.id} onRefresh={fetchServices} onAlert={triggerAlert} />
)}
</div>
</div>
)}
<NotificationModal notification={customAlert} onClose={() => setCustomAlert(null)} />
</div>
);
}