add dashboard, home, login, layout, api

This commit is contained in:
2026-06-04 13:58:32 +02:00
parent fb2a7485bb
commit bf36210670
12 changed files with 497 additions and 231 deletions
+161
View File
@@ -0,0 +1,161 @@
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) {
// 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>
);
}
+43
View File
@@ -0,0 +1,43 @@
export default function Home() {
return (
<div style={{ textAlign: 'center', marginTop: '10vh' }}>
<h1 style={{
color: '#FFFFFF',
fontSize: '2.5rem',
letterSpacing: '2px',
textTransform: 'uppercase',
marginBottom: '20px'
}}>
Bienvenue dans l'infrastructure <span style={{ color: '#00E5FF' }}>GISE</span>
</h1>
<p style={{
color: '#A0A0A0',
fontSize: '1.2rem',
maxWidth: '600px',
margin: '0 auto',
lineHeight: '1.6'
}}>
Hébergement web, instances VPS et Stockage Cloud haute sécurité.
<br />Propulsé par une architecture bare-metal locale.
</p>
{/* Un petit bouton d'action pour la suite */}
<div style={{ marginTop: '40px' }}>
<button style={{
backgroundColor: 'transparent',
color: '#00E5FF',
border: '1px solid #00E5FF',
padding: '12px 24px',
fontSize: '1rem',
fontFamily: 'monospace',
cursor: 'pointer',
textTransform: 'uppercase',
letterSpacing: '1px'
}}>
Démarrer le déploiement
</button>
</div>
</div>
);
}
+90
View File
@@ -0,0 +1,90 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { loginClient } from '../../services/api';
export default function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
const handleLogin = async (e) => {
e.preventDefault(); // Empêche la page de se rafraîchir
setError(null);
setLoading(true);
try {
// On envoie la requête à FOSSBilling
await loginClient(email, password);
// Si on arrive ici, le login est un succès !
// On redirige vers l'espace client sécurisé
navigate('/dashboard');
} catch (err) {
setError(err.message || "Accès refusé. Vérifiez vos identifiants.");
} finally {
setLoading(false);
}
};
// Styles CSS en variables pour garder le code lisible
const inputStyle = {
width: '100%', padding: '12px', marginBottom: '15px',
backgroundColor: '#1A1A1A', color: '#00E5FF',
border: '1px solid #333', fontFamily: 'monospace', outline: 'none'
};
const buttonStyle = {
width: '100%', padding: '12px', backgroundColor: loading ? '#333' : '#00E5FF',
color: loading ? '#888' : '#000', border: 'none', cursor: loading ? 'not-allowed' : 'pointer',
fontFamily: 'monospace', fontWeight: 'bold', textTransform: 'uppercase', letterSpacing: '1px'
};
return (
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '10vh' }}>
<div style={{
width: '100%', maxWidth: '400px', backgroundColor: '#242424',
padding: '30px', borderTop: '4px solid #00E5FF', boxShadow: '0 10px 30px rgba(0,0,0,0.5)'
}}>
<h2 style={{ color: '#FFF', textTransform: 'uppercase', marginBottom: '20px', fontSize: '1.5rem' }}>
Authentification
</h2>
{/* Zone d'erreur qui s'affiche si le mot de passe est faux */}
{error && (
<div style={{ backgroundColor: 'rgba(255, 68, 68, 0.1)', color: '#FF4444', padding: '10px', marginBottom: '15px', border: '1px solid #FF4444', fontSize: '0.9rem' }}>
[ ERREUR ] : {error}
</div>
)}
<form onSubmit={handleLogin}>
<label style={{ display: 'block', color: '#888', marginBottom: '5px', fontSize: '0.8rem' }}>IDENTIFIANT (EMAIL)</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
style={inputStyle}
required
placeholder="admin@gise.be"
/>
<label style={{ display: 'block', color: '#888', marginBottom: '5px', fontSize: '0.8rem' }}>CLÉ D'ACCÈS (MOT DE PASSE)</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
style={inputStyle}
required
placeholder="••••••••"
/>
<button type="submit" style={buttonStyle} disabled={loading}>
{loading ? 'VÉRIFICATION...' : 'INITIALISER LA CONNEXION'}
</button>
</form>
</div>
</div>
);
}