162 lines
6.4 KiB
React
162 lines
6.4 KiB
React
import { useState, useEffect } from 'react';
|
|
import { getClientProfile, getClientOrders, getOrderService } from '../../services/api';
|
|
|
|
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'
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
console.log("[SYS] Création du pont SSO vers HestiaCP...");
|
|
|
|
// 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}`);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div style={{ padding: '40px', color: '#E0E0E0', fontFamily: 'monospace', backgroundColor: '#121212', minHeight: '100vh' }}>
|
|
|
|
{/* 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>
|
|
)}
|
|
</div>
|
|
|
|
{loading && <p style={{ color: '#888' }}>[ Synchronisation des données en cours... ]</p>}
|
|
|
|
{error && (
|
|
<div style={{ backgroundColor: 'rgba(255, 68, 68, 0.1)', color: '#FF4444', padding: '15px', border: '1px solid #FF4444', marginBottom: '20px' }}>
|
|
[ ERREUR ] : {error}
|
|
</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>
|
|
</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>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
} |