unify design
Deploy Nexus Portal to HestiaCP (FTP) / build-and-deploy (push) Has been cancelled

This commit is contained in:
maximus
2026-06-24 20:21:16 +02:00
parent de14810f49
commit da7ee2ba1a
5 changed files with 92 additions and 79 deletions
+1 -2
View File
@@ -134,9 +134,8 @@ export default function AppLayout() {
fontSize: '0.7rem',
letterSpacing: '1px',
fontWeight: 'normal',
textTransform: 'lowercase'
}}>
by gise
by GISE
</span>
</h1>
<p style={{ color: '#555', fontSize: '0.7rem', margin: '8px 0 0 0' }}>
+1 -1
View File
@@ -7,7 +7,7 @@ export default function PublicLayout() {
<nav style={{ padding: '20px', borderBottom: '1px solid #242424', display: 'flex', gap: '15px', alignItems: 'center' }}>
<Link to="/" style={{ display: 'flex', alignItems: 'baseline', gap: '6px', textDecoration: 'none' }}>
<span style={{ color: '#FFF', fontSize: '1.2rem', letterSpacing: '2px', fontWeight: 'bold' }}>NEXUS</span>
<span style={{ color: '#00E5FF', fontSize: '0.65rem', letterSpacing: '1px', fontWeight: 'normal', textTransform: 'lowercase' }}>by gise</span>
<span style={{ color: '#00E5FF', fontSize: '0.65rem', letterSpacing: '1px', fontWeight: 'normal'}}>by GISE</span>
</Link>
<Link to="/offres" style={{ color: '#E0E0E0', textDecoration: 'none', marginLeft: '20px' }}>Catalogue</Link>
+1 -3
View File
@@ -71,9 +71,7 @@ export default function Dashboard() {
<div className="w-full max-w-6xl p-6 mx-auto">
<header className="mb-8">
<h1 className="text-3xl font-bold text-white tracking-widest">
TERMINAL <span className="text-cyan-400">NEXUS</span>
</h1>
<h1 className="text-3xl font-black text-white tracking-wider">TABLEAU DE <span className="text-cyan-400">BORD</span></h1>
<p className="text-gray-400 mt-2">Aperçu de vos accréditations réseau et infrastructures.</p>
</header>
+85 -68
View File
@@ -1,7 +1,7 @@
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';
import { Server, Database, Cloud, Globe, Folder, Trash2, ExternalLink, Loader, Plus, Play, Lock, Maximize2, AlertCircle } from 'lucide-react'; // Ajout de AlertCircle
// ============================================================================
// COMPOSANT 0 : MODAL DE NOTIFICATION (Remplace les alert() natifs)
@@ -290,7 +290,6 @@ const VpsManager = ({ details, orderId, onRefresh, onAlert }) => {
</div>
)}
{/* ACCÈS DE FAUT REMIS À JOUR */}
<div className="mt-4 bg-black/60 border border-gray-900 rounded p-3 text-[11px] space-y-1">
<span className="text-gray-500 font-bold uppercase tracking-wider block mb-1 text-[10px]">Identifiants d'usine :</span>
<p className="text-gray-400 font-mono">Console SSH : <span className="text-cyan-400 font-bold">root</span> / <span className="text-gray-500 italic">Clé choisie à l'initialisation</span></p>
@@ -320,7 +319,6 @@ const VpsManager = ({ details, orderId, onRefresh, onAlert }) => {
<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">NEXUS TERMINAL ({details.domain})</span>
<div className="flex items-center space-x-2">
{/* OPTION AJOUTÉE : OUVRIR EN PLEIN ÉCRAN DANS UN NOUVEL ONGLET */}
<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>
@@ -471,7 +469,7 @@ export default function Services() {
const [isConnecting, setIsConnecting] = useState(null);
const [ssoVault, setSsoVault] = useState(null);
const [activeServiceModal, setActiveServiceModal] = useState(null);
const [customAlert, setCustomAlert] = useState(null); // Gère les pop-ups personnalisés
const [customAlert, setCustomAlert] = useState(null);
const { vpcs, createVPC, deleteVPC, assignToVPC, removeFromVPC } = useVPC();
@@ -544,14 +542,16 @@ export default function Services() {
const assignedServiceIds = vpcs.flatMap(vpc => vpc.services);
const freeServices = services.filter(s => !assignedServiceIds.includes(s.id));
if (isLoading) return <div className="text-center mt-20 text-cyan-400 animate-pulse font-mono tracking-widest">ANALYSE DU RÉSEAU...</div>;
return (
<div className="max-w-7xl mx-auto mt-8 p-6">
<div className="flex flex-col md:flex-row justify-between items-start md:items-end mb-10 gap-4">
<div className="w-full max-w-6xl p-6 mx-auto"> {/* Alignement avec le Dashboard */}
<div className="flex flex-col md:flex-row justify-between items-start md:items-end mb-8 gap-4">
<div>
<h1 className="text-3xl font-black text-white tracking-wider">INVENTAIRE RÉSEAU</h1>
<p className="text-gray-400 mt-1">Orchestration des environnements et des Virtual Private Clouds.</p>
<header>
<h1 className="text-3xl font-black text-white tracking-wider"> {/* Typographie Dashboard */}
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">
@@ -562,63 +562,82 @@ export default function Services() {
</div>
</div>
{error && <div className="text-red-400 bg-red-400/10 p-4 rounded-xl border border-red-500/20 mb-6">{error}</div>}
<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é", "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">
<Trash2 className="w-4 h-4 mr-1" /> Démanteler VPC
</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">
Réseau virtuel vide. Assigner des instances depuis le pool libre.
</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 D'INSTANCES LIBRES
</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. Toutes vos accréditations sont assignées à des VPC.
</div>
) : (
freeServices.map(service => (
<InstanceCard
key={service.id} service={service} vpcs={vpcs}
onAssignVpc={assignToVPC} onRemoveVpc={removeFromVPC}
onOpenConsole={handleOpenConsole} isConnecting={isConnecting}
/>
))
)}
{/* GESTION DU CHARGEMENT IDENTIQUE AU DASHBOARD */}
{isLoading && (
<div className="flex items-center space-x-3 text-cyan-400 mb-8">
<Loader className="w-6 h-6 animate-spin" />
<span>Analyse du réseau et des instances en cours...</span>
</div>
</div>
)}
{/* GESTION DES ERREURS IDENTIQUE AU DASHBOARD */}
{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>
)}
{/* CONTENU (Caché pendant le chargement pour rester propre) */}
{!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é", "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">
<Trash2 className="w-4 h-4 mr-1" /> Démanteler VPC
</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">
Réseau virtuel vide. Assigner des instances depuis le pool libre.
</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 D'INSTANCES LIBRES
</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. Toutes vos accréditations sont assignées à des VPC.
</div>
) : (
freeServices.map(service => (
<InstanceCard
key={service.id} service={service} vpcs={vpcs}
onAssignVpc={assignToVPC} onRemoveVpc={removeFromVPC}
onOpenConsole={handleOpenConsole} isConnecting={isConnecting}
/>
))
)}
</div>
</div>
</>
)}
{/* MODAL 1 : HESTIACP SSO VAULT */}
{ssoVault && (
@@ -626,7 +645,6 @@ export default function Services() {
<div className="bg-gray-900 border border-emerald-500/50 rounded-lg shadow-2xl shadow-emerald-500/20 max-w-md w-full p-6 text-gray-200">
<div className="flex justify-between items-center mb-6">
<h3 className="text-xl font-bold text-emerald-400 font-mono tracking-wider">ACCÈS AUTORISÉ</h3>
{/* La croix permet de fermer manuellement le modal après copie */}
<button onClick={() => setSsoVault(null)} className="text-gray-400 hover:text-white transition text-lg">✖</button>
</div>
<p className="text-sm text-gray-400 mb-6">Le pare-feu HestiaCP bloque les injections directes. Un mot de passe <strong>jetable</strong> a été généré. Copiez-le et connectez-vous.</p>
@@ -646,7 +664,6 @@ export default function Services() {
</div>
</div>
</div>
{/* REFIXÉ : Pas de setSsoVault(null) ici pour garder le modal ouvert au retour de l'onglet */}
<a href={ssoVault.url} target="_blank" rel="noopener noreferrer" className="block w-full bg-emerald-500 hover:bg-emerald-400 text-gray-950 font-bold py-3 text-center rounded transition font-mono tracking-widest uppercase">
Ouvrir le Panel Web ↗
</a>
+2 -3
View File
@@ -19,10 +19,9 @@ export default function Home() {
color: '#00E5FF',
fontSize: '1.4rem',
letterSpacing: '2px',
fontWeight: 'normal',
textTransform: 'lowercase'
fontWeight: 'normal'
}}>
by gise
by GISE
</span>
</h1>