Feat/manage webservices #2
+3
-3
@@ -1,9 +1,9 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { CreditCard, Check, Globe, Loader, ToggleLeft, ShieldAlert, Calendar, TrendingUp, TrendingDown, RefreshCcw } from 'lucide-react';
|
||||
import { getProductList, getHostingServiceDetails } from '../../services/api';
|
||||
import { getProductList, getServiceDetails } from '../../services/api';
|
||||
const CUSTOM_API_BASE_URL = import.meta.env.VITE_CUSTOM_API_BASE_URL || '';
|
||||
|
||||
export default function SubscriptionManager({ order, onClose, onRefresh, onAlert }) {
|
||||
export default function WebServiceSubscriptionManager({ order, onClose, onRefresh, onAlert }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [cancelLoading, setCancelLoading] = useState(false);
|
||||
const [catalog, setCatalog] = useState([]);
|
||||
@@ -20,7 +20,7 @@ export default function SubscriptionManager({ order, onClose, onRefresh, onAlert
|
||||
const fetchDetails = async () => {
|
||||
setIsDetailsLoading(true);
|
||||
try {
|
||||
const data = await getHostingServiceDetails(order.id);
|
||||
const data = await getServiceDetails(order.id);
|
||||
setServiceDetails(data);
|
||||
} catch (err) {
|
||||
console.error("Erreur détails service:", err);
|
||||
+95
-45
@@ -1,11 +1,13 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getClientOrders } from '../../services/api';
|
||||
import { AlertCircle, Loader } from 'lucide-react';
|
||||
import { AlertCircle, Loader, FileText, X } from 'lucide-react';
|
||||
|
||||
// Importation des composants isolés
|
||||
// Importation des composants
|
||||
import DashboardServiceCard from '../../components/dashboard/DashboardServiceCard';
|
||||
import NewServiceCard from '../../components/dashboard/NewServiceCard';
|
||||
import NotificationModal from '../../components/ui/NotificationModal';
|
||||
import WebServiceSubscriptionManager from '../../components/dashboard/WebServiceSubscriptionManager';
|
||||
|
||||
export default function Dashboard() {
|
||||
const navigate = useNavigate();
|
||||
@@ -13,54 +15,82 @@ export default function Dashboard() {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// Chargement des données à l'ouverture du Sas
|
||||
useEffect(() => {
|
||||
const fetchInventory = async () => {
|
||||
try {
|
||||
const data = await getClientOrders();
|
||||
|
||||
if (data.list) {
|
||||
// LE FILTRE CHIRURGICAL PAR PREFIXE
|
||||
const filteredOrders = data.list.filter(order => {
|
||||
const title = (order.title || '').toLowerCase();
|
||||
const type = (order.type || '').toLowerCase();
|
||||
const [activeSubscriptionModal, setActiveSubscriptionModal] = useState(null);
|
||||
const [customAlert, setCustomAlert] = useState(null);
|
||||
|
||||
const isGhostProduct =
|
||||
type === 'domain' ||
|
||||
title.startsWith('domain ') ||
|
||||
title.startsWith('domaine ') ||
|
||||
title.startsWith('enregistrement ');
|
||||
|
||||
return !isGhostProduct;
|
||||
});
|
||||
const triggerAlert = (title, message, type = "info") => setCustomAlert({ title, message, type });
|
||||
|
||||
setOrders(filteredOrders);
|
||||
} else {
|
||||
setOrders([]);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.message || "Impossible de récupérer la télémétrie des services.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
const fetchInventory = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await getClientOrders();
|
||||
|
||||
if (data.list) {
|
||||
// LE FILTRE CHIRURGICAL PAR PREFIXE
|
||||
const filteredOrders = data.list.filter(order => {
|
||||
const title = (order.title || '').toLowerCase();
|
||||
const type = (order.type || '').toLowerCase();
|
||||
|
||||
const isGhostProduct =
|
||||
type === 'domain' ||
|
||||
title.startsWith('domain ') ||
|
||||
title.startsWith('domaine ') ||
|
||||
title.startsWith('enregistrement ');
|
||||
|
||||
return !isGhostProduct;
|
||||
});
|
||||
|
||||
setOrders(filteredOrders);
|
||||
} else {
|
||||
setOrders([]);
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
setError(err.message || "Impossible de récupérer la télémétrie des services.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchInventory();
|
||||
}, []);
|
||||
|
||||
// 🌟 Plus besoin de charger les IPs, on ouvre juste la modale comptable !
|
||||
const handleManageSubscription = (order) => {
|
||||
// 1. Détection du type de service
|
||||
const titleLower = (order.title || '').toLowerCase();
|
||||
const isVPS = titleLower.includes('vps') || titleLower.includes('compute');
|
||||
const isCloud = titleLower.includes('cloud') || titleLower.includes('nextcloud');
|
||||
const isDB = titleLower.includes('db') || titleLower.includes('base') || titleLower.includes('sql');
|
||||
|
||||
// C'est un hébergement web s'il ne correspond à aucun des cas ci-dessus
|
||||
const isWeb = !isVPS && !isCloud && !isDB;
|
||||
|
||||
// 2. Logique de blocage
|
||||
if (isWeb) {
|
||||
// On ouvre la modale comptable uniquement pour le WEB
|
||||
setActiveSubscriptionModal(order);
|
||||
} else {
|
||||
// On affiche une notification pour les autres types d'instances
|
||||
triggerAlert(
|
||||
"Action non disponible",
|
||||
"La modification autonome d'abonnement est actuellement exclusive aux Hébergements Web. Pour restructurer cette instance, veuillez contacter les ingénieurs via le Centre de Support.",
|
||||
"info"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-6xl p-6 mx-auto">
|
||||
|
||||
<div className="w-full max-w-6xl p-6 mx-auto relative">
|
||||
<header className="mb-8">
|
||||
<h1 className="text-3xl font-black text-white tracking-wider">TABLEAU DE <span className="text-cyan-400">BORD</span></h1>
|
||||
<p className="text-gray-400 mt-2">Aperçu de vos accréditations réseau et infrastructures.</p>
|
||||
<p className="text-gray-400 mt-2">Gestion financière et accréditations de vos infrastructures.</p>
|
||||
</header>
|
||||
|
||||
{/* GESTION DES ERREURS & CHARGEMENT */}
|
||||
{isLoading && (
|
||||
<div className="flex items-center space-x-3 text-cyan-400">
|
||||
<Loader className="w-6 h-6 animate-spin" />
|
||||
<span>Synchronisation avec l'orchestrateur en cours...</span>
|
||||
<span>Synchronisation avec le registre comptable...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -71,24 +101,44 @@ export default function Dashboard() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* GRILLE DES SERVICES */}
|
||||
{!isLoading && !error && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
|
||||
{/* Boucle sur les composants isolés */}
|
||||
{orders.map((order) => (
|
||||
<DashboardServiceCard
|
||||
key={order.id}
|
||||
order={order}
|
||||
onClick={() => navigate(`/services/${order.id}`)}
|
||||
<DashboardServiceCard
|
||||
key={order.id}
|
||||
order={order}
|
||||
onClick={() => handleManageSubscription(order)}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Le composant carte d'ajout */}
|
||||
<NewServiceCard onClick={() => navigate('/store')} />
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 🌟 LA MODALE DE GESTION D'ABONNEMENT */}
|
||||
{activeSubscriptionModal && (
|
||||
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
||||
<div className="bg-gray-900 border border-cyan-500/50 shadow-2xl shadow-cyan-500/10 rounded-lg max-w-xxl w-full p-6 text-gray-200">
|
||||
<div className="flex justify-between items-center mb-6 border-b border-gray-800 pb-4">
|
||||
<h3 className="text-xl font-bold text-white tracking-wider flex items-center gap-3">
|
||||
<FileText className="w-6 h-6 text-cyan-400" />
|
||||
GESTION DE L'ABONNEMENT
|
||||
</h3>
|
||||
<button onClick={() => setActiveSubscriptionModal(null)} className="p-2 text-gray-500 hover:text-white bg-gray-900 hover:bg-gray-800 rounded-lg transition-colors">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<WebServiceSubscriptionManager
|
||||
order={activeSubscriptionModal}
|
||||
onClose={() => setActiveSubscriptionModal(null)}
|
||||
onRefresh={fetchInventory}
|
||||
onAlert={triggerAlert}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<NotificationModal notification={customAlert} onClose={() => setCustomAlert(null)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { getMyServices, getHostingServiceDetails } from '../../services/api';
|
||||
import { getClientOrders, getOrderDetails } from '../../services/api';
|
||||
import { useVPC } from '../../services/useVPC';
|
||||
import { Server, Globe, Folder, Trash2, Loader, Plus, AlertCircle } from 'lucide-react';
|
||||
|
||||
@@ -25,12 +25,12 @@ export default function Services() {
|
||||
|
||||
const fetchServices = useCallback(async () => {
|
||||
try {
|
||||
const data = await getMyServices();
|
||||
const data = await getClientOrders();
|
||||
if (data.list && data.list.length > 0) {
|
||||
const detailedServices = await Promise.all(data.list.map(async (order) => {
|
||||
let hDetails = null;
|
||||
if (order.status === 'active' && (order.plugin === 'hosting' || (order.title || '').toLowerCase().includes('vps'))) {
|
||||
try { hDetails = await getHostingServiceDetails(order.id); } catch (e) {}
|
||||
try { hDetails = await getOrderDetails(order.id); } catch (e) {}
|
||||
}
|
||||
return { ...order, hostingDetails: hDetails };
|
||||
}));
|
||||
@@ -65,7 +65,7 @@ export default function Services() {
|
||||
} else if (isDB) {
|
||||
window.open('https://pma.gise.be/', '_blank');
|
||||
} else if (isVPS || isWeb) {
|
||||
const freshDetails = await getHostingServiceDetails(service.id);
|
||||
const freshDetails = await getOrderDetails(service.id);
|
||||
setActiveServiceModal({ type: isVPS ? 'vps' : 'web', data: service, details: freshDetails });
|
||||
}
|
||||
} catch (err) { triggerAlert("Erreur", err.message, "error"); }
|
||||
|
||||
+151
-61
@@ -1,7 +1,7 @@
|
||||
const BASE_URL = import.meta.env.VITE_API_BASE_URL || '';
|
||||
const CUSTOM_API_BASE_URL = import.meta.env.VITE_CUSTOM_API_BASE_URL || '';
|
||||
|
||||
// Le moteur de requête unifié et intelligent
|
||||
// Le moteur de requête
|
||||
const apiCall = async (endpoint, param2 = 'GET', param3 = null) => {
|
||||
let method = 'GET';
|
||||
let body = null;
|
||||
@@ -64,24 +64,6 @@ const apiCall = async (endpoint, param2 = 'GET', param3 = null) => {
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// ROUTES FOSSBILLING NATIVES (BACKTICKS INTÉGRÉS)
|
||||
// ==========================================
|
||||
|
||||
export const loginClient = (email, password) =>
|
||||
apiCall(`${BASE_URL}/api/guest/client/login`, { email, password });
|
||||
|
||||
// Récupère la liste des services/commandes du client
|
||||
export const getClientOrders = () =>
|
||||
apiCall(`${BASE_URL}/api/client/order/get_list`);
|
||||
|
||||
// Récupère les détails techniques du service rattaché à une commande
|
||||
export const getOrderService = (order_id) =>
|
||||
apiCall(`${BASE_URL}/api/client/order/service`, { id: order_id });
|
||||
|
||||
// Récupère le catalogue public des produits FOSSBilling
|
||||
export const getProductList = () =>
|
||||
apiCall(`${BASE_URL}/api/guest/product/get_list`);
|
||||
|
||||
// Vide le panier (Action PUBLIQUE : on passe par l'API Guest)
|
||||
export const resetCart = async () => {
|
||||
@@ -100,22 +82,6 @@ export const resetCart = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Ajoute un produit au panier avec ses options étalées à la racine
|
||||
export const addToCart = (productId, period, additionalData = {}) =>
|
||||
apiCall(`${BASE_URL}/api/guest/cart/add_item`, 'POST', {
|
||||
id: productId,
|
||||
period: period,
|
||||
...additionalData // Les 3 petits points "étalent" le contenu de l'objet
|
||||
});
|
||||
|
||||
// Valide le panier (Action PRIVÉE : on reste sur l'API Client pour générer la facture)
|
||||
export const checkoutCart = () =>
|
||||
apiCall(`${BASE_URL}/api/client/cart/checkout`, 'POST');
|
||||
|
||||
// Récupère les informations du client connecté
|
||||
export const getClientProfile = () =>
|
||||
apiCall(`${BASE_URL}/api/client/profile/get`, 'GET');
|
||||
|
||||
// ==========================================
|
||||
// ROUTES PERSONNALISÉES (CUSTOM API)
|
||||
// ==========================================
|
||||
@@ -144,18 +110,6 @@ export const registerUnifiedClient = async (email, username, password, firstName
|
||||
}
|
||||
}
|
||||
|
||||
// Récupère la liste de toutes les commandes actives du client
|
||||
export const getMyServices = () =>
|
||||
apiCall(`${BASE_URL}/api/client/order/get_list`, 'GET');
|
||||
|
||||
// Récupère les détails secrets d'un service (dont le mot de passe HestiaCP/VPS)
|
||||
export const getServiceDetails = (orderId) =>
|
||||
apiCall(`${BASE_URL}/api/client/order/get`, 'POST', { id: orderId });
|
||||
|
||||
// Récupère les secrets spécifiques du service physique attaché à une commande
|
||||
export const getHostingServiceDetails = (orderId) =>
|
||||
apiCall(`${BASE_URL}/api/client/order/service`, 'POST', { id: orderId });
|
||||
|
||||
// Force la réinitialisation du mot de passe sur le serveur distant (HestiaCP)
|
||||
export const resetHostingPassword = (orderId, newPassword) =>
|
||||
apiCall(`${BASE_URL}/api/client/servicehosting/change_password`, 'POST', {
|
||||
@@ -191,33 +145,169 @@ export const launchSSOGateway = (username, password) => {
|
||||
document.body.removeChild(form);
|
||||
};
|
||||
|
||||
// #######################################################################################
|
||||
|
||||
// ==========================================
|
||||
// ROUTES SUPPORT FOSSBILLING (NATIVES)
|
||||
// GESTION DES ACCES
|
||||
// ==========================================
|
||||
|
||||
// Récupère la liste de tous les tickets du client
|
||||
export const getClientTickets = () =>
|
||||
apiCall(`${BASE_URL}/api/client/support/ticket_get_list`, 'GET');
|
||||
// Connexion
|
||||
export const loginClient = (email, password) =>
|
||||
apiCall(`${BASE_URL}/api/guest/client/login`, { email, password });
|
||||
|
||||
// Récupère le contenu et les messages d'un ticket spécifique
|
||||
export const getTicketDetails = (ticketId) =>
|
||||
apiCall(`${BASE_URL}/api/client/support/ticket_get`, 'POST', { id: ticketId });
|
||||
// Deconnexion
|
||||
export const logoutClient = () =>
|
||||
apiCall(`${BASE_URL}/api/client/profile/logout`);
|
||||
|
||||
// Crée un nouveau ticket de support
|
||||
|
||||
// ==========================================
|
||||
// GESTION DU CLIENT
|
||||
// ==========================================
|
||||
|
||||
// Recuperer les informations du client
|
||||
export const getClientProfile = () =>
|
||||
apiCall(`${BASE_URL}/api/client/profile/get`, 'GET');
|
||||
|
||||
// Update les informations du client
|
||||
export const updateClientProfile = (email, last_name, aid, gender, country, city, birthday, company, company_vat, company_number, type, address_1, address_2, postcode, state, phone, phone_cc, document_type, document_nr, notes, lang, custom_1, custom_2, custom_3, custom_4, custom_5, custom_6, custom_7, custom_8, custom_9, custom_10) =>
|
||||
apiCall(`${BASE_URL}/api/client/profile/update`, {
|
||||
email: email,
|
||||
last_name: last_name,
|
||||
aid: aid,
|
||||
gender: gender,
|
||||
country: country,
|
||||
city: city,
|
||||
birthday: birthday,
|
||||
company: company,
|
||||
company_vat: company_vat,
|
||||
company_number: company_number,
|
||||
type: type,
|
||||
address_1: address_1,
|
||||
address_2: address_2,
|
||||
postcode: postcode,
|
||||
state: state,
|
||||
phone: phone,
|
||||
phone_cc: phone_cc,
|
||||
document_type: document_type,
|
||||
document_nr: document_nr,
|
||||
notes: notes,
|
||||
lang: lang,
|
||||
custom_1: custom_1,
|
||||
custom_2: custom_2,
|
||||
custom_3: custom_3,
|
||||
custom_4: custom_4,
|
||||
custom_5: custom_5,
|
||||
custom_6: custom_6,
|
||||
custom_7: custom_7,
|
||||
custom_8: custom_8,
|
||||
custom_9: custom_9,
|
||||
custom_10: custom_10
|
||||
|
||||
});
|
||||
|
||||
// Changer le mot de passe
|
||||
export const changeClientPassword = (current_password, new_password, confirm_password) =>
|
||||
apiCall(`${BASE_URL}/api/client/profile/change_password`, {current_password:current_password, new_password:new_password, confirm_password:confirm_password});
|
||||
|
||||
|
||||
|
||||
// ==========================================
|
||||
// GESTION DES ABONNEMENTS
|
||||
// ==========================================
|
||||
|
||||
// Souscription
|
||||
|
||||
// Resiliation
|
||||
|
||||
// Suppression
|
||||
export const deleteWrongOrder = (order_id) =>
|
||||
apiCall(`${BASE_URL}/api/client/order/delete`, { id: order_id });
|
||||
|
||||
// Promotion (Surclassement)
|
||||
// Lister
|
||||
export const getUpgrades = (order_id) =>
|
||||
apiCall(`${BASE_URL}/api/client/order/upgradables`, { id: order_id });
|
||||
|
||||
// Demotion (Declassement)
|
||||
|
||||
// Lister les abonnements du client
|
||||
export const getClientOrders = () =>
|
||||
apiCall(`${BASE_URL}/api/client/order/get_list`);
|
||||
|
||||
// Lister les produits disponibles
|
||||
export const getProductList = () =>
|
||||
apiCall(`${BASE_URL}/api/guest/product/get_list`);
|
||||
|
||||
// Recuperer les details d'un abonnement
|
||||
export const getOrderDetails = (order_id) =>
|
||||
apiCall(`${BASE_URL}/api/client/order/service`, { id: order_id });
|
||||
|
||||
// Recuperer les details secrets d'un abonnement
|
||||
export const getServiceDetails = (orderId) =>
|
||||
apiCall(`${BASE_URL}/api/client/order/get`, 'POST', { id: orderId });
|
||||
|
||||
|
||||
// ==========================================
|
||||
// GESTION DU PANIER
|
||||
// ==========================================
|
||||
|
||||
// Ajouter au panier
|
||||
export const addToCart = (productId, period, additionalData = {}) =>
|
||||
apiCall(`${BASE_URL}/api/guest/cart/add_item`, 'POST', {
|
||||
id: productId,
|
||||
period: period,
|
||||
...additionalData // Les 3 petits points "étalent" le contenu de l'objet
|
||||
});
|
||||
|
||||
// Checkout
|
||||
export const checkoutCart = () =>
|
||||
apiCall(`${BASE_URL}/api/client/cart/checkout`, 'POST');
|
||||
|
||||
|
||||
// ==========================================
|
||||
// GESTION DES FACTURES
|
||||
// ==========================================
|
||||
|
||||
// Creation
|
||||
// Suppression
|
||||
// Modification
|
||||
// Lister
|
||||
export const getInvoiceList = () =>
|
||||
apiCall(`${BASE_URL}/api/client/invoice/get_list`, 'GET');
|
||||
|
||||
// Lire
|
||||
export const getInvoiceDetails = (InvoiceHash) =>
|
||||
apiCall(`${BASE_URL}/api/client/invoice/get`, 'GET');
|
||||
|
||||
|
||||
// ==========================================
|
||||
// GESTION DES TICKETS
|
||||
// ==========================================
|
||||
|
||||
// Creation
|
||||
export const createTicket = (subject, message, helpdesk_id) =>
|
||||
apiCall(`${BASE_URL}/api/client/support/ticket_create`, 'POST', {
|
||||
support_helpdesk_id: helpdesk_id,
|
||||
subject: subject,
|
||||
content: message
|
||||
});
|
||||
// Suppression
|
||||
|
||||
// Répond à un ticket existant
|
||||
// Lister les tickets
|
||||
export const getClientTickets = () =>
|
||||
apiCall(`${BASE_URL}/api/client/support/ticket_get_list`, 'GET');
|
||||
|
||||
// Lister les helpdesks
|
||||
export const getHelpdesks = () =>
|
||||
apiCall(`${BASE_URL}/api/client/support/helpdesk_get_pairs`, 'GET');
|
||||
|
||||
// Lire
|
||||
export const getTicketDetails = (ticketId) =>
|
||||
apiCall(`${BASE_URL}/api/client/support/ticket_get`, 'POST', { id: ticketId });
|
||||
|
||||
// Repondre
|
||||
export const replyTicket = (ticketId, message) =>
|
||||
apiCall(`${BASE_URL}/api/client/support/ticket_reply`, 'POST', {
|
||||
id: ticketId,
|
||||
content: message
|
||||
});
|
||||
|
||||
// Récupère la liste dynamique des départements (Helpdesks) configurés sur FOSSBilling
|
||||
export const getHelpdesks = () =>
|
||||
apiCall(`${BASE_URL}/api/client/support/helpdesk_get_pairs`, 'GET');
|
||||
});
|
||||
Reference in New Issue
Block a user