Afficher l'IP client (x-forwarded-for) dans les logs des requêtes API

This commit is contained in:
Antoine Jacquin
2026-09-14 19:30:08 +02:00
parent df462ea86e
commit cea671c0ac
4 changed files with 219 additions and 21 deletions

View File

@ -1389,6 +1389,11 @@ const STATS = {stats_json};
_APP_CSS = """/* Interface de la carte LiDAR — barre latérale à onglets type SIG. */
:root {
/* Le color-scheme impose au navigateur le rendu sombre des contrôles
natifs (popup des <select>, barres de défilement) : sans cela, le
menu déroulant des préconfigurations s'affiche en thème clair sur
une interface sombre. */
color-scheme: dark;
--bg: #0b0e13;
--panel: rgba(16, 20, 27, 0.93);
--panel-border: rgba(255, 255, 255, 0.09);
@ -1465,6 +1470,10 @@ select, input[type="checkbox"] { accent-color: var(--accent); }
border: 1px solid var(--panel-border); border-radius: 6px;
padding: 3px 6px; font-size: 12px; cursor: pointer;
}
.gen-select:disabled { opacity: 0.4; cursor: not-allowed; }
/* Options du menu déroulant : fallback pour les navigateurs sans
color-scheme (le popup natif hérite de ces couleurs). */
.gen-select option { background-color: #10141b; color: var(--text); }
/* --- Barre latérale (gauche, pleine hauteur) --- */
#sidebar {
@ -2246,10 +2255,49 @@ document.getElementById('osmRow').querySelector('.layer-name').title =
// visibles, leurs niveaux de transparence (opacités des
// visualisations + fond de carte OSM) et l'ordre de pile
// (bas → haut). Les presets intégrés couvrent les couches du
// panneau ; les presets personnels (persistés en localStorage)
// se suppriment depuis la liste.
// panneau ; les presets personnels sont stockés sur le serveur
// (/api/presets, partagés entre tous les navigateurs connectés)
// et en écho dans le localStorage du navigateur (repli si l'interface
// est servie sans webapp).
// ============================================================
const PRESETS_LS = 'lidarPresets_v1';
const PRESETS_URL = '/api/presets';
// Écho local des presets : s'il n'y a pas de webapp (serveur statique),
// ils ne vivent que dans le localStorage du navigateur.
let customPresets = (() => {
try {
const p = JSON.parse(localStorage.getItem(PRESETS_LS) || '[]');
return Array.isArray(p) ? p : [];
} catch (e) { return []; }
})();
function persistCustomPresets() {
try { localStorage.setItem(PRESETS_LS, JSON.stringify(customPresets)); } catch (e) { /* privé */ }
}
// Les presets stockés sur le serveur sont partagés entre tous les
// navigateurs connectés à la webapp ; à l'arrivée, fusion avec ceux
// déjà en local (même id = la version serveur l'emporte). Webapp
// injoignable : la liste locale est conservée telle quelle.
function upsertCustomPreset(p) {
const i = customPresets.findIndex(x => x.id === p.id);
if (i === -1) customPresets.push(p); else customPresets[i] = p;
}
function removeCustomPreset(id) {
customPresets = customPresets.filter(p => p.id !== id);
}
async function syncCustomPresets() {
try {
const r = await fetch(PRESETS_URL, { cache: 'no-store' });
if (!r.ok) return;
const data = await r.json();
const server = Array.isArray(data.presets) ? data.presets : [];
const byId = new Map(customPresets.map(p => [p.id, p]));
for (const p of server) if (p && p.id) byId.set(p.id, p);
customPresets = [...byId.values()];
persistCustomPresets();
renderPresetSelect(presetSelectEl.value);
} catch (e) { /* pas de webapp : presets locaux uniquement */ }
}
// Les presets portent les niveaux de transparence : opacité de chaque
// visualisation + fond de carte OSM (visibilité, opacité, mode sombre).
const BUILTIN_PRESETS = [
@ -2270,16 +2318,6 @@ const BUILTIN_PRESETS = [
osm: { on: true, opacity: 0.85, dark: true } },
];
function loadCustomPresets() {
try {
const p = JSON.parse(localStorage.getItem(PRESETS_LS) || '[]');
return Array.isArray(p) ? p : [];
} catch (e) { return []; }
}
function saveCustomPresets(list) {
try { localStorage.setItem(PRESETS_LS, JSON.stringify(list)); } catch (e) { /* privé */ }
}
// Niveaux de transparence d'un preset, lisibles dans la liste
// (opacité de chaque visualisation + fond de carte OSM).
function presetDetail(p) {
@ -2331,7 +2369,7 @@ function renderPresetSelect(selectedValue) {
o.textContent = p.label + ' — ' + presetDetail(p);
presetSelectEl.appendChild(o);
}
const customs = loadCustomPresets();
const customs = customPresets;
if (customs.length) {
const g = document.createElement('optgroup');
g.label = 'Mes presets';
@ -2352,7 +2390,7 @@ presetSelectEl.addEventListener('change', () => {
if (!v) return;
let p = null;
if (v.startsWith('builtin:')) p = BUILTIN_PRESETS.find(x => x.label === v.slice(8));
else if (v.startsWith('custom:')) p = loadCustomPresets().find(x => x.id === v.slice(7));
else if (v.startsWith('custom:')) p = customPresets.find(x => x.id === v.slice(7));
if (p) applyPreset(p);
});
@ -2369,23 +2407,34 @@ presetSaveEl.addEventListener('click', () => {
// Niveaux de transparence du fond de carte
osm: { on: osm.on, opacity: osm.opacity, dark: osm.dark },
};
const customs = loadCustomPresets();
customs.push(p);
saveCustomPresets(customs);
upsertCustomPreset(p);
persistCustomPresets();
renderPresetSelect('custom:' + p.id);
// Persistance serveur (partagée entre navigateurs) ; en échec
// (pas de webapp), le preset reste dans le stockage local.
fetch(PRESETS_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(p),
}).then(r => {
if (!r.ok) console.warn('Preset non enregistré sur le serveur : ' + r.status);
}).catch(() => {});
});
presetDeleteEl.addEventListener('click', () => {
const v = presetSelectEl.value;
if (!v.startsWith('custom:')) return;
if (!confirm('Supprimer ce preset ?')) return;
saveCustomPresets(loadCustomPresets().filter(p => p.id !== v.slice(7)));
const id = v.slice(7);
removeCustomPreset(id);
persistCustomPresets();
renderPresetSelect('');
fetch(PRESETS_URL + '/' + encodeURIComponent(id), { method: 'DELETE' })
.catch(() => {});
});
renderPresetSelect();
// Barre latérale : repli + onglets (Couches / Génération)
syncCustomPresets();
const sidebar = document.getElementById('sidebar');
function setSidebarCollapsed(collapsed) {
sidebar.classList.toggle('collapsed', collapsed);