ajout du dashboard
Deploy Nexus Portal to HestiaCP (FTP) / build-and-deploy (push) Successful in 16s

This commit is contained in:
maximus
2026-06-12 15:53:16 +02:00
parent cab5d749d2
commit bfa3161646
10 changed files with 400 additions and 195 deletions
+143 -142
View File
@@ -1,162 +1,163 @@
import { useState, useEffect } from 'react';
import { getClientProfile, getClientOrders, getOrderService } from '../../services/api';
// src/pages/app/Dashboard.jsx
import { Link } from 'react-router-dom';
export default function Dashboard() {
const [profile, setProfile] = useState(null);
const [orders, setOrders] = useState([]);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchDashboardData = async () => {
try {
// On lance les deux requêtes en même temps pour aller plus vite
const profileData = await getClientProfile();
const ordersData = await getClientOrders();
setProfile(profileData);
// FOSSBilling renvoie une liste paginée, on prend le tableau 'list'
setOrders(ordersData.list || []);
} catch (err) {
setError(err.message || "Erreur de synchronisation avec le serveur central.");
} finally {
setLoading(false);
}
};
fetchDashboardData();
}, []);
const cardStyle = {
backgroundColor: '#1A1A1A',
border: '1px solid #333',
padding: '20px',
borderRadius: '4px',
display: 'flex',
flexDirection: 'column',
gap: '10px'
// --- DESIGN SYSTEM DU DASHBOARD ---
const gridStyle = {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
gap: '25px',
marginTop: '30px'
};
const handleManageInstance = async (orderId) => {
try {
console.log(`[SYS] Demande d'accès au service #${orderId}...`);
const serviceData = await getOrderService(orderId);
if (serviceData && serviceData.server && serviceData.server.login_url) {
window.open(serviceData.server.login_url, '_blank');
}
else if (serviceData && serviceData.server) {
console.log(serviceData);
// L'URL d'action du formulaire de connexion HestiaCP
const hestiaLoginUrl = "https://panel.gise.be/login/";
const username = serviceData.username;
const password = serviceData.password;
if (!username || !password) {
alert("Identifiants introuvables. Le serveur est-il bien provisionné ?");
return;
}
const cardStyle = {
backgroundColor: '#121212',
border: '1px solid #222',
borderTop: '4px solid #00E5FF',
padding: '25px',
position: 'relative',
overflow: 'hidden'
};
console.log("[SYS] Création du pont SSO vers HestiaCP...");
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'
};
// 1. On crée un formulaire invisible
const form = document.createElement('form');
form.method = 'POST';
form.action = hestiaLoginUrl;
form.target = '_blank'; // Pour ouvrir dans un nouvel onglet
// 2. On crée le champ utilisateur (Hestia attend le nom 'user')
const userField = document.createElement('input');
userField.type = 'hidden';
userField.name = 'user';
userField.value = username;
// 3. On crée le champ mot de passe (Hestia attend le nom 'password')
const passField = document.createElement('input');
passField.type = 'hidden';
passField.name = 'password';
passField.value = password;
// 4. On assemble et on injecte dans la page
form.appendChild(userField);
form.appendChild(passField);
document.body.appendChild(form);
// 5. On valide le formulaire (BAM ! Connexion)
form.submit();
// 6. On efface les traces du formulaire fantôme pour la sécurité
document.body.removeChild(form);
} else {
alert("Configuration serveur introuvable pour cette instance.");
}
} catch (err) {
alert(`[ERREUR D'ACCÈS] : ${err.message}`);
}
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'
};
return (
<div style={{ padding: '40px', color: '#E0E0E0', fontFamily: 'monospace', backgroundColor: '#121212', minHeight: '100vh' }}>
<div style={{ fontFamily: 'monospace' }}>
{/* HEADER DU DASHBOARD */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid #333', paddingBottom: '20px', marginBottom: '30px' }}>
<h2 style={{ color: '#00E5FF', margin: 0 }}>CENTRE DE CONTRÔLE GISE</h2>
{profile && (
<div style={{ textAlign: 'right' }}>
<div>Opérateur : <span style={{ color: '#FFF' }}>{profile.email}</span></div>
<div>Crédits : <span style={{ color: '#00FF00' }}>{profile.balance} {profile.currency}</span></div>
</div>
)}
{/* --- 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>
</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>
{loading && <p style={{ color: '#888' }}>[ Synchronisation des données en cours... ]</p>}
{/* --- GRILLE DES MODULES DE L'INFRASTRUCTURE --- */}
<div style={gridStyle}>
{error && (
<div style={{ backgroundColor: 'rgba(255, 68, 68, 0.1)', color: '#FF4444', padding: '15px', border: '1px solid #FF4444', marginBottom: '20px' }}>
[ ERREUR ] : {error}
{/* 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>
)}
{/* ZONE DES SERVICES ACTIFS */}
{!loading && !error && (
<>
<h3 style={{ color: '#FFF', marginBottom: '20px' }}>INFRASTRUCTURE ACTIVE</h3>
{orders.length === 0 ? (
<div style={{ padding: '30px', textAlign: 'center', border: '1px dashed #333', color: '#888' }}>
Aucun service actif détecté. <br/><br/>
<button style={{ backgroundColor: '#00E5FF', color: '#000', border: 'none', padding: '10px 20px', cursor: 'pointer', fontFamily: 'monospace', fontWeight: 'bold' }}>
+ DÉPLOYER UN NOUVEAU SERVICE
</button>
{/* 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: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: '20px' }}>
{orders.map((order) => (
<div key={order.id} style={cardStyle}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<strong style={{ color: '#FFF', fontSize: '1.1rem' }}>{order.title}</strong>
<span style={{
color: order.status === 'active' ? '#00FF00' : '#FF4444',
fontSize: '0.8rem', textTransform: 'uppercase', border: `1px solid ${order.status === 'active' ? '#00FF00' : '#FF4444'}`, padding: '2px 6px'
}}>
{order.status}
</span>
</div>
<div style={{ fontSize: '0.9rem', color: '#A0A0A0' }}>Renouvellement : {order.expires_at || 'N/A'}</div>
<div style={{ fontSize: '0.9rem', color: '#A0A0A0' }}>Montant : {order.total} {order.currency}</div>
<button
onClick={() => handleManageInstance(order.id)}
style={{ marginTop: '10px', backgroundColor: 'transparent', color: '#00E5FF', border: '1px solid #00E5FF', padding: '8px', cursor: 'pointer', fontFamily: 'monospace' }}>
GÉRER L'INSTANCE
</button>
</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>
);
}