-
-
- NEXUS TERMINAL ({details.domain})
-
-
- window.open(terminalUrl, '_blank')}
- className="text-xs bg-gray-800 hover:bg-gray-700 text-cyan-400 px-3 py-1 rounded transition flex items-center gap-1"
- title="Ouvrir dans un nouvel onglet"
- >
- Plein écran
-
- setShowTerminal(false)}
- className="text-xs bg-red-900/30 hover:bg-red-900/60 text-white px-3 py-1 rounded transition"
- >
- Fermer
-
-
+
NEXUS TERMINAL ({details.domain})
+
setShowTerminal(false)} className="text-xs bg-red-900/30 text-white px-3 py-1 rounded">Fermer
-
- {/* Le conteneur iFrame qui charge TTYD (xterm.js) */}
-
- {/* Message d'aide qui s'affiche brièvement pendant le chargement de l'iframe */}
-
-
-
Chargement du moteur xterm.js...
-
Identifiant : root
-
Mot de passe : Celui d'initialisation
-
-
-
+
+
)}
@@ -307,6 +302,132 @@ const VpsManager = ({ details }) => {
);
};
+// ============================================================================
+// COMPOSANT 4 : WEB MANAGER (HestiaCP & Day-2 Operations)
+// ============================================================================
+const WebManager = ({ details, orderId, onRefresh, onOpenSso }) => {
+ const [isEditingDomain, setIsEditingDomain] = useState(false);
+ const [newDomain, setNewDomain] = useState(details.domain);
+ const [isUpdating, setIsUpdating] = useState(false);
+ const [sslStatus, setSslStatus] = useState(null);
+ const [isGeneratingSso, setIsGeneratingSso] = useState(false);
+
+ const isCustomDomain = details.domain && !details.domain.endsWith('.gise.be');
+ const siteUrl = (isCustomDomain && sslStatus !== 'success') ? `http://${details.domain}` : `https://${details.domain}`;
+
+ 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();
+ alert("Domaine web mis à jour avec succès sur le serveur HestiaCP !");
+ } else alert("Erreur: " + data.error);
+ } catch (err) {
+ alert("Erreur de connexion avec l'API.");
+ } 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');
+ alert("Certificat SSL Let's Encrypt généré !");
+ } else {
+ setSslStatus('error');
+ alert("Échec Let's Encrypt. Vérifiez vos DNS.");
+ }
+ } catch (e) { setSslStatus('error'); }
+ };
+
+ const handleOpenPanel = async () => {
+ setIsGeneratingSso(true);
+ try {
+ const secureHash = Math.random().toString(36).slice(-8) + Math.random().toString(36).slice(-4).toUpperCase();
+ const rollingPassword = `Nx${secureHash}`;
+ await resetHostingPassword(orderId, rollingPassword);
+
+ // On déclenche le modal SSO du composant parent
+ onOpenSso({ username: details.username, password: rollingPassword, url: 'https://panel.gise.be/login/' });
+ } catch (err) {
+ alert("Erreur lors de la génération du SSO HestiaCP.");
+ } finally {
+ setIsGeneratingSso(false);
+ }
+ };
+
+ return (
+
+
+
Domaine du Site Web
+ {!isEditingDomain ? (
+
+
+
+ {details.domain}
+
+ setIsEditingDomain(true)} className="text-xs border border-gray-700 hover:border-emerald-500 text-gray-400 hover:text-emerald-400 px-3 py-1.5 rounded transition">MODIFIER
+
+ ) : (
+
+ 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-emerald-500 outline-none" disabled={isUpdating} />
+
+ {isUpdating ? 'SYNC...' : 'SAUVEGARDER'}
+
+ setIsEditingDomain(false)} className="bg-gray-800 text-white px-3 py-1.5 rounded text-xs hover:bg-gray-700">ANNULER
+
+ )}
+
+ {isCustomDomain && (
+
+
Domaine personnalisé détecté. Sécurisez après pointage DNS.
+
+ {sslStatus === 'loading' ? 'GÉNÉRATION...' : <> GÉNÉRER SSL>}
+
+
+ )}
+
+
+
+
+ Visiter le Site
+ window.open(siteUrl, '_blank')} className="w-full bg-gray-950 hover:bg-gray-900 border border-gray-800 hover:border-emerald-500/50 rounded p-3 flex justify-between items-center transition group">
+ Ouvrir dans le navigateur
+
+
+
+
+ Panel de Contrôle
+
+ {isGeneratingSso ? (
+ GÉNÉRATION SSO...
+ ) : (
+ <>
+ Gérer l'hébergement
+
+ >
+ )}
+
+
+
+
+ );
+};
+
// ============================================================================
// COMPOSANT PRINCIPAL : SERVICES
// ============================================================================
@@ -315,78 +436,68 @@ export default function Services() {
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const [newVpcName, setNewVpcName] = useState("");
- const [isConnecting, setIsConnecting] = useState(null);
+ const [isConnecting, setIsConnecting] = useState(null);
const [ssoVault, setSsoVault] = useState(null);
- const [activeVpsModal, setActiveVpsModal] = useState(null);
+ const [activeServiceModal, setActiveServiceModal] = useState(null); // Gère VPS et WEB
const { vpcs, createVPC, deleteVPC, assignToVPC, removeFromVPC } = useVPC();
- useEffect(() => {
- const fetchServices = async () => {
- try {
- const data = await getMyServices();
+ // Fonction extraite pour pouvoir être rappelée par les Managers
+ const fetchServices = useCallback(async () => {
+ try {
+ const data = await getMyServices();
+ 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 getHostingServiceDetails(order.id); }
+ catch (e) { console.warn(`Détails inaccessibles pour ${order.id}:`, e); }
+ }
+ return { ...order, hostingDetails: hDetails };
+ })
+ );
- if (data.list && data.list.length > 0) {
- const detailedServices = await Promise.all(
- data.list.map(async (order) => {
- let hDetails = null;
- if (order.plugin === 'hosting' || (order.title||'').toLowerCase().includes('vps')) {
- try { hDetails = await getHostingServiceDetails(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;
+ });
- const filteredServices = detailedServices.filter(s => {
- const type = (s.type || '').toLowerCase();
- const title = (s.title || '').toLowerCase();
- // Ajout du mot-clé "enregistrement" au filtre d'exclusion
- 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 des services.");
- } finally {
- setIsLoading(false);
+ setServices(filteredServices);
}
- };
- fetchServices();
+ } catch (err) {
+ setError(err.message || "Impossible de charger la télémétrie des services.");
+ } 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 (isVPS) {
- const freshDetails = await getHostingServiceDetails(service.id);
- setActiveVpsModal({ ...service, hostingDetails: freshDetails });
- } else if (isCloud) {
- // Redirection Cloud
+ 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');
+ if (domainUrl) window.open(`https://${domainUrl}`, '_blank');
} else if (isDB) {
- // Redirection Base de données
window.open('https://pma.gise.be/', '_blank');
- } else {
- // Redirection Web / HestiaCP
- const hostingDetails = await getHostingServiceDetails(service.id);
- const username = hostingDetails.username;
- if (!username) throw new Error("Infrastructure non synchronisée avec le métal.");
-
- const secureHash = Math.random().toString(36).slice(-8) + Math.random().toString(36).slice(-4).toUpperCase();
- const rollingPassword = `Nx${secureHash}`;
- await resetHostingPassword(service.id, rollingPassword);
-
- setSsoVault({ username: username, password: rollingPassword, url: 'https://panel.gise.be/login/' });
+ } else if (isVPS || isWeb) {
+ // Pour VPS et Web, on récupère les détails frais et on ouvre le Modal Générique
+ const freshDetails = await getHostingServiceDetails(service.id);
+ setActiveServiceModal({ type: isVPS ? 'vps' : 'web', data: service, details: freshDetails });
}
} catch (err) {
alert("Échec du protocole : " + err.message);
@@ -440,14 +551,10 @@ export default function Services() {
) : (
vpcServices.map(service => (
-
))
)}
@@ -468,14 +575,10 @@ export default function Services() {