+
+
+ {/* C'est ici que les pages (Accueil, Login, etc.) vont s'afficher */}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/app/Dashboard.jsx b/src/pages/app/Dashboard.jsx
new file mode 100644
index 0000000..82aaa35
--- /dev/null
+++ b/src/pages/app/Dashboard.jsx
@@ -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 (
+
+
+ {/* HEADER DU DASHBOARD */}
+
+
CENTRE DE CONTRÔLE GISE
+ {profile && (
+
+
Opérateur : {profile.email}
+
Crédits : {profile.balance} {profile.currency}
+
+ )}
+
+
+ {loading &&
[ Synchronisation des données en cours... ]
}
+
+ {error && (
+
+ [ ERREUR ] : {error}
+
+ )}
+
+ {/* ZONE DES SERVICES ACTIFS */}
+ {!loading && !error && (
+ <>
+
INFRASTRUCTURE ACTIVE
+
+ {orders.length === 0 ? (
+
+ Aucun service actif détecté.
+
+
+ ) : (
+
+ {orders.map((order) => (
+
+
+ {order.title}
+
+ {order.status}
+
+
+
Renouvellement : {order.expires_at || 'N/A'}
+
Montant : {order.total} {order.currency}
+
+
+
+ ))}
+
+ )}
+ >
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/public/Home.jsx b/src/pages/public/Home.jsx
new file mode 100644
index 0000000..cbd16cc
--- /dev/null
+++ b/src/pages/public/Home.jsx
@@ -0,0 +1,43 @@
+export default function Home() {
+ return (
+
+
+ Bienvenue dans l'infrastructure GISE
+
+
+
+ Hébergement web, instances VPS et Stockage Cloud haute sécurité.
+ Propulsé par une architecture bare-metal locale.
+
+
+ {/* Un petit bouton d'action pour la suite */}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/public/Login.jsx b/src/pages/public/Login.jsx
new file mode 100644
index 0000000..244605f
--- /dev/null
+++ b/src/pages/public/Login.jsx
@@ -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 (
+
+
+
+ Authentification
+
+
+ {/* Zone d'erreur qui s'affiche si le mot de passe est faux */}
+ {error && (
+
+ [ ERREUR ] : {error}
+
+ )}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/services/api.js b/src/services/api.js
new file mode 100644
index 0000000..27ed264
--- /dev/null
+++ b/src/services/api.js
@@ -0,0 +1,31 @@
+// src/services/api.js
+
+const apiCall = async (url, body = {}) => {
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ },
+ credentials: 'include', // Garde bien ça pour envoyer le cookie de session
+ body: JSON.stringify(body)
+ });
+
+ const data = await response.json();
+ if (data.error) throw new Error(data.error.message);
+ return data.result;
+};
+
+export const loginClient = (email, password) =>
+ apiCall('/api/guest/client/login', { email, password });
+
+export const getClientProfile = () =>
+ apiCall('/api/client/profile/get');
+
+// Récupère la liste des services/commandes du client
+export const getClientOrders = () =>
+ apiCall('/api/client/order/get_list');
+
+// Récupère les détails techniques du service rattaché à une commande
+export const getOrderService = (order_id) =>
+ apiCall('/api/client/order/service', { id: order_id });
\ No newline at end of file
diff --git a/structure.txt b/structure.txt
new file mode 100644
index 0000000..6e4ce85
--- /dev/null
+++ b/structure.txt
@@ -0,0 +1,53 @@
+src/
+├── assets/ # Images, logo GISE
+├── components/ # Les petits morceaux réutilisables (Boutons, Cartes, Navbar, Footer)
+├── layouts/ # Les "moules" des pages
+│ ├── PublicLayout.jsx # Moule avec Header/Footer standard
+│ └── AppLayout.jsx # Moule avec Barre latérale (Sidebar) pour l'espace client
+├── pages/ # Les grandes pages
+│ ├── public/ # Accueil, Login, Register...
+│ └── app/ # Dashboard, Services, Factures...
+├── services/ # Le "moteur" : fonctions pour parler à l'API FOSSBilling/Hestia
+└── App.jsx # Le routeur principal
+
+
+1. Les Routes Publiques (La Vitrine)
+Ces pages sont accessibles à tout le monde.
+
+/ (Accueil) : La page d'atterrissage. Une grande image forte (le Bunker), les 3 piliers (Web, VPS, Cloud), et un gros appel à l'action.
+
+/offres (Catalogue global) : Une page qui résume tous les produits.
+
+/offres/web : Détail des offres d'hébergement HestiaCP.
+
+/offres/vps : Détail des serveurs n8n, etc.
+
+/offres/cloud : Détail des offres Nextcloud.
+
+/login (Connexion) : Le formulaire pour entrer dans le Bunker.
+
+/register (Inscription) : Le formulaire pour créer un compte.
+
+/password-reset (Mot de passe oublié) : Essentiel.
+
+2. Les Routes Privées (L'Espace Client protégé)
+Ces routes nécessitent que l'utilisateur ait un token valide (il est connecté). Si un visiteur non connecté essaie d'y aller, il sera redirigé vers /login.
+
+/dashboard (Vue d'ensemble) : Le centre de contrôle.
+
+/services (Inventaire global) : La liste de tout ce que possède le client.
+
+/services/web : Ses sites hébergés sur HestiaCP (avec le bouton SSO magique).
+
+/services/vps : Ses serveurs.
+
+/services/cloud : Son stockage Nextcloud.
+
+/billing (Facturation) : Historique, factures impayées (relié à FOSSBilling).
+
+/support (Tickets) : Ouvrir et suivre des demandes d'aide.
+
+/settings (Profil) : Gérer ses infos perso et mots de passe.
+
+III. Comment structurer ça dans ton code React ?
+Pour garder un code propre, tu vas créer une arborescence de dossiers logique dans ton projet Vite (dossier src/).
\ No newline at end of file
diff --git a/vite.config.js b/vite.config.js
index 8b0f57b..9e7ae9b 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,7 +1,16 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
-// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
-})
+ server: {
+ proxy: {
+ // Toutes les requêtes qui commencent par /api iront vers ton FOSSBilling
+ '/api': {
+ target: 'https://web.gise.be',
+ changeOrigin: true,
+ secure: false
+ }
+ }
+ }
+})
\ No newline at end of file
Connect with us
-Join the Vite community
---
-
-
- GitHub
-
-
- -
-
-
- Discord
-
-
- -
-
-
- X.com
-
-
- -
-
-
- Bluesky
-
-
-
-