134 lines
10 KiB
React
134 lines
10 KiB
React
import { useState } from 'react';
|
|
import { Folder, ExternalLink, Play, Maximize2, Lock, Copy, Check } from 'lucide-react';
|
|
|
|
export default function 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 [copiedField, setCopiedField] = useState(null);
|
|
|
|
const isInternal = details.domain && details.domain.endsWith('.gise.be');
|
|
const fileManagerUrl = isInternal ? `https://file-${details.domain}` : `http://${details.domain}:8080`;
|
|
const terminalUrl = isInternal ? `https://terminal-${details.domain}` : `http://${details.domain}:8081`;
|
|
|
|
const handleCopy = (text, field) => {
|
|
navigator.clipboard.writeText(text);
|
|
setCopiedField(field);
|
|
setTimeout(() => setCopiedField(null), 2000);
|
|
};
|
|
|
|
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 !", "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 généré !", "success"); }
|
|
else { setSslStatus('error'); onAlert("Erreur DNS", "Vérifiez votre pointage.", "error"); }
|
|
} catch (e) { setSslStatus('error'); }
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
{!showTerminal ? (
|
|
<>
|
|
<div className="mb-6 bg-gray-950 border border-gray-800 rounded p-4">
|
|
<label className="block text-xs uppercase tracking-widest text-cyan-500 mb-2">Domaine de l'instance VPS</label>
|
|
{!isEditingDomain ? (
|
|
<div className="flex justify-between items-center">
|
|
<span className="font-mono text-xl text-green-400 font-bold flex items-center gap-2">
|
|
<span className="h-3 w-3 rounded-full bg-green-500 shadow-[0_0_10px_#22c55e]"></span>{details.domain}
|
|
</span>
|
|
<button onClick={() => setIsEditingDomain(true)} className="text-xs border border-gray-700 hover:border-cyan-500 text-gray-400 hover:text-cyan-400 px-3 py-1.5 rounded transition">MODIFIER</button>
|
|
</div>
|
|
) : (
|
|
<div className="flex gap-2">
|
|
<input type="text" value={newDomain} onChange={(e) => 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} />
|
|
<button onClick={handleUpdateDomain} disabled={isUpdating} className="bg-cyan-500 text-gray-900 px-4 py-1.5 rounded text-xs font-bold hover:bg-cyan-400">{isUpdating ? 'SYNC...' : 'SAUVEGARDER'}</button>
|
|
<button onClick={() => setIsEditingDomain(false)} className="bg-gray-800 text-white px-3 py-1.5 rounded text-xs hover:bg-gray-700">ANNULER</button>
|
|
</div>
|
|
)}
|
|
{!isInternal && (
|
|
<div className="mt-4 border-t border-gray-900 pt-3 flex justify-between items-center">
|
|
<p className="text-[11px] text-gray-500">Domaine externe détecté.</p>
|
|
<button onClick={handleGenerateSSL} disabled={sslStatus === 'loading'} className="text-xs bg-purple-600/20 hover:bg-purple-600 text-purple-400 hover:text-white border border-purple-500/30 px-3 py-1 rounded transition flex items-center gap-1">
|
|
{sslStatus === 'loading' ? 'GÉNÉRATION...' : <><Lock className="w-3 h-3" /> GÉNÉRER SSL</>}
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* ENCART DES IDENTIFIANTS */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
|
<div className="bg-black/60 border border-gray-800 rounded p-3">
|
|
<span className="text-gray-500 font-bold uppercase tracking-wider block mb-2 text-[10px] flex items-center gap-1">
|
|
<Folder className="w-3 h-3" /> Accès Explorateur
|
|
</span>
|
|
<div className="flex justify-between items-center mb-1">
|
|
<span className="text-xs font-mono text-gray-400">User: <strong className="text-cyan-400">root</strong></span>
|
|
<button onClick={() => handleCopy('root', 'fileUser')} className="text-gray-500 hover:text-cyan-400 p-1">
|
|
{copiedField === 'fileUser' ? <Check className="w-3 h-3 text-cyan-400" /> : <Copy className="w-3 h-3" />}
|
|
</button>
|
|
</div>
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-xs font-mono text-gray-400">Pass: <strong className="text-cyan-400">NexusGise2026!</strong></span>
|
|
<button onClick={() => handleCopy('NexusGise2026!', 'filePass')} className="text-gray-500 hover:text-cyan-400 p-1">
|
|
{copiedField === 'filePass' ? <Check className="w-3 h-3 text-cyan-400" /> : <Copy className="w-3 h-3" />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="bg-black/60 border border-gray-800 rounded p-3">
|
|
<span className="text-gray-500 font-bold uppercase tracking-wider block mb-2 text-[10px] flex items-center gap-1">
|
|
<Play className="w-3 h-3" /> Accès Console / SSH
|
|
</span>
|
|
<div className="flex justify-between items-center mb-1">
|
|
<span className="text-xs font-mono text-gray-400">User: <strong className="text-cyan-400">root</strong></span>
|
|
<button onClick={() => handleCopy('root', 'sshUser')} className="text-gray-500 hover:text-cyan-400 p-1">
|
|
{copiedField === 'sshUser' ? <Check className="w-3 h-3 text-cyan-400" /> : <Copy className="w-3 h-3" />}
|
|
</button>
|
|
</div>
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-xs font-mono text-gray-400">Pass: <span className="text-gray-600 italic">Défini à l'installation</span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* BOUTONS D'ACTIONS */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-2">
|
|
<button onClick={() => window.open(fileManagerUrl, '_blank')} className="bg-gray-950 hover:bg-gray-900 border border-gray-800 hover:border-cyan-500/50 rounded p-3 flex justify-between items-center transition group">
|
|
<span className="font-mono text-gray-300 text-sm flex items-center gap-2"><Folder className="w-4 h-4 text-cyan-500" /> Explorateur</span><ExternalLink className="w-4 h-4 text-gray-600 group-hover:text-cyan-400" />
|
|
</button>
|
|
<button onClick={() => setShowTerminal(true)} className="bg-cyan-900/20 hover:bg-cyan-900/40 border border-cyan-800/50 hover:border-cyan-500 rounded p-3 flex justify-between items-center transition group">
|
|
<span className="font-mono text-cyan-400 text-sm flex items-center gap-2"><span>{">_"}</span> Terminal</span><Play className="w-4 h-4 text-cyan-600 group-hover:text-cyan-400" />
|
|
</button>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<div>
|
|
<div className="flex justify-between items-center mb-4 border-b border-gray-800 pb-2">
|
|
<span className="text-cyan-400 font-mono text-sm tracking-widest">TERMINAL ({details.domain})</span>
|
|
<div className="flex items-center space-x-2">
|
|
<button onClick={() => { window.open(terminalUrl, '_blank'); setShowTerminal(false); }} className="text-xs bg-cyan-600/30 text-cyan-400 hover:bg-cyan-600 hover:text-white border border-cyan-500/30 px-3 py-1 rounded transition flex items-center gap-1"><Maximize2 className="w-3 h-3" /> Plein Écran ↗</button>
|
|
<button onClick={() => setShowTerminal(false)} className="text-xs bg-red-900/30 text-white px-3 py-1 rounded">Fermer</button>
|
|
</div>
|
|
</div>
|
|
<div className="bg-black border border-gray-800 rounded h-[400px]">
|
|
<iframe src={terminalUrl} className="w-full h-full border-none" title="Terminal" />
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
} |