/* Service centralisé pour tous les appels API vers le backend FastAPI */ /* Utilise la variable d'environnement Vite si définie, sinon localhost par défaut */ const BASE_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:8000'; /* Clés de stockage local pour le token JWT et l'utilisateur connecté */ const TOKEN_KEY = 'ds_token'; const USER_KEY = 'ds_user'; /* Helpers de gestion du token JWT dans le localStorage */ export function getToken() { return localStorage.getItem(TOKEN_KEY); } export function setToken(token) { localStorage.setItem(TOKEN_KEY, token); } export function clearToken() { localStorage.removeItem(TOKEN_KEY); } /* Construit les en-têtes d'authentification si un token est présent */ function authHeaders(extra = {}) { const token = getToken(); const headers = { ...extra }; if (token) { headers.Authorization = `Bearer ${token}`; } return headers; } /* Gestion d'une réponse 401 : purge de la session et redirection vers /login */ function handleUnauthorized() { clearToken(); localStorage.removeItem(USER_KEY); /* Évite une boucle de redirection si on est déjà sur la page de connexion */ if (window.location.pathname !== '/login') { window.location.href = '/login'; } } /* Fonction utilitaire pour les requêtes fetch (GET) avec gestion d'erreur et auth */ async function fetchApi(path, params = {}) { const url = new URL(`${BASE_URL}${path}`); Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== '') { url.searchParams.append(key, value); } }); try { const response = await fetch(url.toString(), { headers: authHeaders() }); if (response.status === 401) { handleUnauthorized(); throw new Error('Session expirée, veuillez vous reconnecter.'); } if (!response.ok) { const errorText = await response.text(); console.error(`Erreur API [${response.status}] ${path}: ${errorText}`); throw new Error(`Erreur ${response.status}: ${errorText}`); } return await response.json(); } catch (error) { console.error(`Erreur lors de l'appel API ${path}:`, error); throw error; } } /* Helper générique pour les requêtes JSON (POST / PUT / DELETE) avec auth */ async function sendJson(path, method, body) { const url = `${BASE_URL}${path}`; const options = { method, headers: authHeaders(body !== undefined ? { 'Content-Type': 'application/json' } : {}), }; if (body !== undefined) { options.body = JSON.stringify(body); } try { const response = await fetch(url, options); if (response.status === 401) { handleUnauthorized(); throw new Error('Session expirée, veuillez vous reconnecter.'); } if (!response.ok) { const errorText = await response.text(); console.error(`Erreur API [${response.status}] ${path}: ${errorText}`); throw new Error(`Erreur ${response.status}: ${errorText}`); } /* Certaines réponses (204, DELETE) peuvent ne pas contenir de corps JSON */ const text = await response.text(); return text ? JSON.parse(text) : null; } catch (error) { console.error(`Erreur lors de l'appel API ${path}:`, error); throw error; } } /* Authentification : POST /auth/login en x-www-form-urlencoded (OAuth2) */ export async function login(username, password) { const body = new URLSearchParams(); body.append('username', username); body.append('password', password); const response = await fetch(`${BASE_URL}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString(), }); if (response.status === 401) { throw new Error('Identifiants invalides'); } if (response.status === 429) { throw new Error('Trop de tentatives. Veuillez réessayer dans une minute.'); } if (!response.ok) { const errorText = await response.text(); throw new Error(`Erreur ${response.status}: ${errorText}`); } const data = await response.json(); setToken(data.access_token); return data.user; } /* Récupère le profil de l'utilisateur connecté */ export async function getMe() { return fetchApi('/auth/me'); } /* ---------- Administration des utilisateurs ---------- */ export async function getUsers() { return fetchApi('/admin/users'); } export async function createUser({ username, email, password, role }) { return sendJson('/admin/users', 'POST', { username, email, password, role }); } export async function updateUser(id_user, changes) { return sendJson(`/admin/users/${id_user}`, 'PUT', changes); } export async function deleteUser(id_user) { return sendJson(`/admin/users/${id_user}`, 'DELETE'); } export async function resetUserPassword(id_user, password) { return sendJson(`/admin/users/${id_user}/reset-password`, 'POST', { password }); } /* Journal d'audit */ export async function getJournal(limit = 200) { return fetchApi('/admin/journal', { limit }); } /* ---------- RGPD : données personnelles ---------- */ export async function exportMyData() { return fetchApi('/me/data-export'); } export async function deleteMyAccount() { return sendJson('/me', 'DELETE'); } /* Santé de l'API */ export async function getHealth() { return fetchApi('/health'); } /* Récupère la liste des catégories */ export async function getCategories() { return fetchApi('/categories'); } /* Récupère la liste des services */ export async function getServices() { return fetchApi('/services'); } /* Récupère les contacts, filtrable par service */ export async function getContacts(id_service) { return fetchApi('/contacts', { id_service }); } /* Récupère la nomenclature (monitorings), filtrable par service et catégorie */ export async function getMonitorings(id_service, id_categorie) { const data = await fetchApi('/monitorings', { id_service, id_categorie }); return Array.isArray(data) ? data.map(m => ({ id: m.id_monito, id_monito: m.id_monito, nom: m.monito_intitule, monito_intitule: m.monito_intitule, ...m })) : data; } /* Récupère un monitoring par son identifiant */ export async function getMonitoringById(id) { const data = await fetchApi(`/monitorings/${id}`); return data ? { id: data.id_monito, id_monito: data.id_monito, nom: data.monito_intitule, monito_intitule: data.monito_intitule, ...data } : data; } /* Récupère les lignes d'erreur d'un monitoring avec pagination et filtres */ export async function getMonitoringDetails(id, { search, limit = 500, offset = 0 } = {}) { return fetchApi(`/monitorings/${id}/details`, { search, limit, offset }); } /* Récupère le nombre d'erreurs d'un monitoring */ export async function getMonitoringCount(id) { const data = await fetchApi(`/monitorings/${id}/count`); return { id_monito: data.id_monito, count: data.nb_erreurs, nb_erreurs: data.nb_erreurs, ...data }; } /* Récupère les colonnes dynamiques d'un monitoring */ export async function getMonitoringColumns(id) { return fetchApi(`/monitorings/${id}/columns`); } /* Récupère les données du dashboard (VUE_CONSO), filtrable par service et catégorie */ export async function getDashboard(service, categorie) { const data = await fetchApi('/dashboard', { service, categorie }); return Array.isArray(data) ? data.map(m => ({ id: m.id_monito, id_monito: m.id_monito, nom: m.nom_monito, nb_erreurs: m.nb_erreurs, ...m })) : data; } /* Récupère le résumé global KPI du dashboard */ export async function getDashboardSummary() { const data = await fetchApi('/dashboard/summary'); return { total: data.nb_monitorings ?? 0, ok: data.monitorings_ok ?? 0, warning: 0, // À calculer ou ajouter à l'API critical: data.monitorings_en_erreur ?? 0, nb_monitorings: data.nb_monitorings, total_erreurs: data.total_erreurs, max_erreurs: data.max_erreurs, monitoring_critique: data.monitoring_critique, ...data }; } /* Récupère l'historique, filtrable par monitoring, dates et service */ export async function getHistorique(id_monito, date_debut, date_fin, service) { const data = await fetchApi('/historique', { id_monito, date_debut, date_fin, service }); return Array.isArray(data) ? data.map(h => ({ date: h.date_sauvegarde, date_sauvegarde: h.date_sauvegarde, ...h })) : data; } /* Récupère les données d'évolution pour un graphique */ export async function getHistoriqueEvolution(id, date_debut, date_fin) { const data = await fetchApi(`/historique/${id}/evolution`, { date_debut, date_fin }); if (data?.evolution) { return data.evolution.map(point => ({ date: point.date, erreurs: point.nb_erreurs, nb_erreurs: point.nb_erreurs })); } return []; } export async function getEvolutionGlobal(date_debut, date_fin) { const data = await fetchApi('/evolution/global', { date_debut, date_fin }); if (data?.series) { return data.series.map(point => ({ date: point.date, erreurs: point.nb_erreurs, nb_erreurs: point.nb_erreurs })); } return []; } /* Export CSV utilitaire */ export function exportToCSV(data, filename = 'export.csv') { if (!data || data.length === 0) { console.warn('Aucune donnée à exporter'); return; } // Récupère les clés du premier objet const headers = Object.keys(data[0]); // Crée le CSV : en-têtes + lignes const csvLines = [ headers.join(','), ...data.map(row => headers.map(header => { const value = row[header]; // Échappe les valeurs qui contiennent des virgules ou des guillemets if (value === null || value === undefined) return ''; const str = String(value); if (str.includes(',') || str.includes('"') || str.includes('\n')) { return `"${str.replace(/"/g, '""')}"`; } return str; }).join(',') ) ].join('\n'); // Crée un blob et télécharge const blob = new Blob([csvLines], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; link.click(); URL.revokeObjectURL(link.href); }