add hosting plans via fossbilling
Deploy Nexus Portal to HestiaCP (FTP) / build-and-deploy (push) Successful in 33s

This commit is contained in:
maximus
2026-06-12 23:25:17 +02:00
parent 33c8babdd2
commit 48793034b1
8 changed files with 676 additions and 156 deletions
+120 -155
View File
@@ -1,163 +1,128 @@
// src/pages/app/Dashboard.jsx
import { Link } from 'react-router-dom';
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { getClientOrders } from '../../services/api';
import { Server, Database, Cloud, Globe, Plus, AlertCircle, Loader } from 'lucide-react';
export default function Dashboard() {
// --- DESIGN SYSTEM DU DASHBOARD ---
const gridStyle = {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
gap: '25px',
marginTop: '30px'
};
const navigate = useNavigate();
const [orders, setOrders] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const cardStyle = {
backgroundColor: '#121212',
border: '1px solid #222',
borderTop: '4px solid #00E5FF',
padding: '25px',
position: 'relative',
overflow: 'hidden'
};
// Chargement des données à l'ouverture du Sas
useEffect(() => {
const fetchInventory = async () => {
try {
// Appel à FOSSBilling via notre api.js
const data = await getClientOrders();
// FOSSBilling renvoie souvent la liste dans data.list
setOrders(data.list || []);
} catch (err) {
setError(err.message || "Impossible de récupérer la télémétrie des services.");
} finally {
setIsLoading(false);
}
};
const statusBadgeStyle = {
position: 'absolute',
top: '20px',
right: '20px',
backgroundColor: 'rgba(0, 229, 255, 0.1)',
color: '#00E5FF',
padding: '4px 8px',
fontSize: '0.7rem',
fontWeight: 'bold',
letterSpacing: '1px'
};
fetchInventory();
}, []);
const btnStyle = {
display: 'inline-block',
width: '100%',
padding: '10px',
marginTop: '20px',
backgroundColor: 'transparent',
color: '#00E5FF',
border: '1px solid #00E5FF',
textAlign: 'center',
textDecoration: 'none',
textTransform: 'uppercase',
fontSize: '0.8rem',
cursor: 'pointer',
transition: 'all 0.2s',
boxSizing: 'border-box'
};
// Fonction Radar : Détecte le type de service selon son nom pour afficher la bonne icône
const getServiceIcon = (title) => {
const t = title.toLowerCase();
if (t.includes('vps') || t.includes('serveur')) return <Server className="w-10 h-10 text-cyan-400" />;
if (t.includes('cloud') || t.includes('nextcloud')) return <Cloud className="w-10 h-10 text-blue-400" />;
if (t.includes('db') || t.includes('base') || t.includes('sql')) return <Database className="w-10 h-10 text-purple-400" />;
return <Globe className="w-10 h-10 text-emerald-400" />; // Par défaut : Web / Hestia
};
return (
<div style={{ fontFamily: 'monospace' }}>
{/* --- EN-TÊTE DU TABLEAU DE BORD --- */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', borderBottom: '1px solid #222', paddingBottom: '20px' }}>
<div>
<h2 style={{ color: '#FFF', margin: '0 0 5px 0', fontSize: '2rem', textTransform: 'uppercase' }}>
Console Opérationnelle
</h2>
<span style={{ color: '#888' }}>ID Réseau: <span style={{ color: '#E0E0E0' }}>usr_8472_alpha</span></span>
// Fonction d'état : Formate le statut du service
const getStatusBadge = (status) => {
switch (status) {
case 'active': return <span className="px-2 py-1 text-xs text-green-400 bg-green-400/10 border border-green-400/20 rounded">ACTIF</span>;
case 'pending_setup': return <span className="px-2 py-1 text-xs text-orange-400 bg-orange-400/10 border border-orange-400/20 rounded">EN PRÉPARATION</span>;
case 'suspended': return <span className="px-2 py-1 text-xs text-red-400 bg-red-400/10 border border-red-400/20 rounded">SUSPENDU</span>;
default: return <span className="px-2 py-1 text-xs text-gray-400 bg-gray-400/10 border border-gray-400/20 rounded">{status.toUpperCase()}</span>;
}
};
return (
<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>
<p className="text-gray-400 mt-2">Aperçu de vos accréditations réseau et infrastructures.</p>
</header>
{/* GESTION DES ERREURS & CHARGEMENT */}
{isLoading && (
<div className="flex items-center space-x-3 text-cyan-400">
<Loader className="w-6 h-6 animate-spin" />
<span>Synchronisation avec l'orchestrateur 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">
<AlertCircle className="w-6 h-6" />
<span>{error}</span>
</div>
)}
{/* GRILLE DES SERVICES */}
{!isLoading && !error && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{/* Boucle sur les services FOSSBilling */}
{orders.map((order) => (
<div
key={order.id}
className="bg-gray-900 border border-gray-800 p-6 rounded-xl hover:border-cyan-400/50 transition-colors group cursor-pointer flex flex-col justify-between"
onClick={() => navigate(`/services/${order.id}`)} // Redirection future vers les détails
>
<div>
<div className="flex justify-between items-start mb-4">
<div className="p-3 bg-black/50 rounded-lg group-hover:scale-110 transition-transform">
{getServiceIcon(order.title)}
</div>
{getStatusBadge(order.status)}
</div>
<h3 className="text-lg font-semibold text-white truncate" title={order.title}>
{order.title}
</h3>
<p className="text-sm text-gray-500 mt-1">
Facturation : {order.period}
</p>
</div>
<div className="mt-6 pt-4 border-t border-gray-800 flex justify-between items-center text-sm">
<span className="text-gray-400">ID Réseau: #{order.id}</span>
<span className="text-cyan-400 group-hover:underline">Gérer &gt;</span>
</div>
</div>
))}
{/* LA CARTE : OBTENIR UN NOUVEAU PRODUIT */}
<div
onClick={() => navigate('/store')} // Remplace /store par l'URL de ton catalogue
className="bg-transparent border-2 border-dashed border-gray-700 hover:border-cyan-400 p-6 rounded-xl transition-colors cursor-pointer flex flex-col items-center justify-center text-center group min-h-[200px]"
>
<div className="p-3 bg-gray-800/50 rounded-full group-hover:bg-cyan-400/20 transition-colors mb-4">
<Plus className="w-8 h-8 text-gray-400 group-hover:text-cyan-400" />
</div>
<h3 className="text-lg font-semibold text-white group-hover:text-cyan-400">
Demander une accréditation
</h3>
<p className="text-sm text-gray-500 mt-2">
Déployer un nouveau serveur Web, VPS ou Cloud.
</p>
</div>
</div>
)}
</div>
<div style={{ textAlign: 'right' }}>
<div style={{ color: '#00FF66', fontSize: '0.85rem', marginBottom: '5px' }}> SYSTÈME NOMINAL</div>
<div style={{ color: '#555', fontSize: '0.75rem' }}>Dernière connexion : Aujourd'hui</div>
</div>
</div>
{/* --- GRILLE DES MODULES DE L'INFRASTRUCTURE --- */}
<div style={gridStyle}>
{/* MODULE 1 : BARE-METAL (HESTIACP) */}
<div style={cardStyle}>
<div style={statusBadgeStyle}>ACTIF</div>
<h3 style={{ color: '#FFF', marginTop: 0, fontSize: '1.2rem' }}>// Serveur Web</h3>
<p style={{ color: '#888', fontSize: '0.85rem', lineHeight: '1.5' }}>
Nœud HestiaCP (panel.gise.be). Supervision des partitions web, DNS et bases de données.
</p>
<div style={{ margin: '20px 0', fontSize: '0.85rem', color: '#A0A0A0' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<span>Domaines Actifs:</span> <span style={{ color: '#FFF' }}>1 / 5</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Bases SQL:</span> <span style={{ color: '#FFF' }}>2 / 10</span>
</div>
</div>
<a href="https://panel.gise.be" target="_blank" rel="noopener noreferrer" style={btnStyle}
onMouseOver={(e) => { e.target.style.backgroundColor = '#00E5FF'; e.target.style.color = '#000'; }}
onMouseOut={(e) => { e.target.style.backgroundColor = 'transparent'; e.target.style.color = '#00E5FF'; }}>
Accéder au Panel
</a>
</div>
{/* MODULE 2 : CLOUD (NEXTCLOUD) */}
<div style={cardStyle}>
<div style={statusBadgeStyle}>ACTIF</div>
<h3 style={{ color: '#FFF', marginTop: 0, fontSize: '1.2rem' }}>// Sanctuaire Cloud</h3>
<p style={{ color: '#888', fontSize: '0.85rem', lineHeight: '1.5' }}>
Stockage chiffré Nextcloud (cloud.gise.be). Synchronisation des terminaux et partage sécurisé.
</p>
<div style={{ margin: '20px 0', fontSize: '0.85rem', color: '#A0A0A0' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<span>Espace Alloué:</span> <span style={{ color: '#FFF' }}>50 Go</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Espace Utilisé:</span> <span style={{ color: '#FFF' }}>1.2 Go</span>
</div>
</div>
<a href="https://cloud.gise.be" target="_blank" rel="noopener noreferrer" style={btnStyle}
onMouseOver={(e) => { e.target.style.backgroundColor = '#00E5FF'; e.target.style.color = '#000'; }}
onMouseOut={(e) => { e.target.style.backgroundColor = 'transparent'; e.target.style.color = '#00E5FF'; }}>
Ouvrir le Cloud
</a>
</div>
{/* MODULE 3 : FACTURATION (FOSSBILLING) */}
<div style={cardStyle}>
<div style={statusBadgeStyle} style={{ ...statusBadgeStyle, color: '#FFB800', backgroundColor: 'rgba(255, 184, 0, 0.1)' }}>EN ATTENTE</div>
<h3 style={{ color: '#FFF', marginTop: 0, fontSize: '1.2rem' }}>// Facturation</h3>
<p style={{ color: '#888', fontSize: '0.85rem', lineHeight: '1.5' }}>
Centre de gestion FOSSBilling. Historique des paiements et renouvellement des baux réseau.
</p>
<div style={{ margin: '20px 0', fontSize: '0.85rem', color: '#A0A0A0' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<span>Solde Compte:</span> <span style={{ color: '#FFF' }}>0.00 </span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Prochaine Échéance:</span> <span style={{ color: '#FFF' }}>12/07/2026</span>
</div>
</div>
<Link to="/billing" style={{ ...btnStyle, borderColor: '#FFB800', color: '#FFB800' }}
onMouseOver={(e) => { e.target.style.backgroundColor = '#FFB800'; e.target.style.color = '#000'; }}
onMouseOut={(e) => { e.target.style.backgroundColor = 'transparent'; e.target.style.color = '#FFB800'; }}>
Régler la facture
</Link>
</div>
</div>
{/* --- TERMINAL DE LOGS (ESTHÉTIQUE SYSADMIN) --- */}
<div style={{ marginTop: '40px', backgroundColor: '#0A0A0A', border: '1px solid #1A1A1A', padding: '20px' }}>
<h4 style={{ color: '#555', marginTop: 0, fontSize: '0.8rem', letterSpacing: '1px' }}>&gt;_ SYSTEM_LOGS</h4>
<div style={{ color: '#444', fontSize: '0.75rem', lineHeight: '1.8' }}>
<div>[ OK ] Connexion chiffrée établie via TLS v1.3</div>
<div>[ OK ] Jetons d'authentification synchronisés avec FOSSBilling API</div>
<div>[ INFO ] Vérification des quotas de stockage Nextcloud... Terminée.</div>
<div>[ INFO ] 0 ticket(s) de support en attente.</div>
<div><span style={{ color: '#00E5FF' }}>&gt;</span> En attente d'instructions...<span style={{ animation: 'blink 1s step-end infinite' }}>_</span></div>
</div>
</div>
{/* Animation CSS pour le curseur clignotant du terminal */}
<style>
{`
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
`}
</style>
</div>
);
);
}