334 lines
10 KiB
JavaScript
334 lines
10 KiB
JavaScript
/* 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);
|
|
}
|