Afficher l'IP client (x-forwarded-for) dans les logs des requêtes API
This commit is contained in:
@ -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);
|
||||
|
||||
Reference in New Issue
Block a user