ajout du dashboard
Deploy Nexus Portal to HestiaCP (FTP) / build-and-deploy (push) Successful in 16s
Deploy Nexus Portal to HestiaCP (FTP) / build-and-deploy (push) Successful in 16s
This commit is contained in:
+143
-142
@@ -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' }}>>_ 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' }}>></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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user