- timeout fail2ban-client 5 s → 30 s (sous charge le résultat vide
était mis en cache 60 s, causant « Aucune IP bannie »)
- log explicite en cas d'erreur (returncode, stderr, exception)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ip-api.com → Team Cymru whois (TCP 43) → ipinfo.io (timeout 2s).
Évite 5s de blocage par IP quand HTTPS sortant est filtré.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3e service de résolution après ip-api.com et ipinfo.io.
Cymru whois utilise TCP port 43 (pas HTTPS), ce qui fonctionne même
quand les sorties HTTPS sont bloquées côté serveur.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CIDRs bannis via "Ban AS" : résolus depuis as_cache/*.json (0 appel API)
+ nom/pays récupérés en 1 seule requête PostgreSQL (DISTINCT ON asn)
- IPs/CIDRs bannis individuellement : _batch_lookup_ip_asn avec fallback
ipinfo.io (résout les cas où ip-api.com retourne vide, ex: 103.51.13.0)
- Entrées avec asn='' exclues du cache (AND asn != '') → re-tentée à chaque fois
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Si ip-api.com ne retourne pas d'AS, on retente sur ipinfo.io (champ org).
Les entrées sans ASN ne sont plus mises en cache (AND asn != '' sur les
lectures), donc les IP inconnues sont automatiquement re-tentées au prochain
chargement du tab Bannis.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Chaque AS est replié au chargement (▶). Clic sur la ligne titre pour
déplier/replier. Bouton "Tout déplier / Tout replier". La recherche
dynamique déplie automatiquement les blocs qui contiennent un résultat.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
La route /errors/ ne calculait plus les groupes ASN au chargement (N×SQL
pour chaque CIDR banni). Le tab Bannissements est désormais lazy-chargé via
/errors/banned-groups avec un unique SELECT ANY() en PostgreSQL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
La page /errors/ intègre désormais deux onglets (Erreurs 404 et Bannissements)
activés via hash URL (#errors / #banned). Le lien "Bannis" disparaît de la nav,
la route /errors/banned/ redirige vers /errors/#banned.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- /errors/banned/ : bannissements groupés par AS (nom, pays, nb entrées)
- Déblocage IP seule ou AS entier via POST /errors/unban
- Filtre dynamique par IP/CIDR/nom AS, mise à jour DOM sans rechargement
- Nav header + sudoers unbanip
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Clic 🔨 : lookup AS via ip-api.com, propose 🔨 IP ou 🔨 AS (N préfixes)
- Ban AS : récupère les CIDRs via RIPE Stat, cache 30 j dans as_cache/
- IPs déjà bannies (global-blacklist) masquées du tableau et du détail AJAX
- ignoreip fail2ban : 82.65.88.34 protégée sur toutes les jails
- Sudoers : permission status global-blacklist pour static-cdn
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Champ de filtre en temps réel au-dessus du tableau des 404 ;
ferme le panneau de détail des lignes masquées.
Corrige aussi rsync via sudo pour préserver les droits static-cdn.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Le service tourne sous static-cdn — il ne pouvait pas écrire ignored_ips.json.
Corrige aussi /var/log/static-cdn.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Invalidation du cache 404 basée sur mtime de ignored_ips.json (tous les workers gunicorn voient la mise à jour)
- Chemins complets /usr/bin/sudo et /usr/bin/fail2ban-client pour éviter les erreurs PATH dans systemd
- Version 1.5.1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Onglet "Erreurs" dans la navigation
- Analyse des logs Apache des 7 derniers jours (.gz inclus)
- Tableau trié par nombre de requêtes avec badge statut (résolu/actif)
- Détail AJAX par chemin : IPs, compteurs, referers
- Vérification live au clic sur le point de statut
- Ignorer une IP (persisté dans ignored_ips.json, cache invalidé)
- Bannir une IP via fail2ban-client (global-blacklist)
- Section IPs ignorées avec suppression depuis la page
Closes#37#38#39#40#41
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
STATS_FILE pointe vers /stats/goaindex.html généré chaque nuit par ISPConfig.
STATS_GENERATE_CMD et STATS_JSON vidés (génération déléguée à ISPConfig).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
max-width:none + margin:auto sur flex child causait le rétrécissement
de main. Remplacé par width:100% + sélecteurs d'attribut html[data-cw].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Ajout de type="button" sur les boutons du sélecteur (évite le submit)
- Initialisation de --content-width dans <head> pour éviter le flash
- Séparation init CSS (head) / gestion active (body)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fichier VERSION (1.4.0) lu par l'app au démarrage
- CHANGELOG.md versionné (v1.0.0 → v1.4.0)
- Route /changelog avec parsing du markdown et rendu structuré
- Lien cliquable sur le numéro de version dans le footer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ajoute 4 boutons dans le header pour choisir la largeur du contenu :
- S : ~900 px M : ~1200 px L : ~1600 px ∞ : plein écran
La préférence est mémorisée en localStorage et appliquée instantanément
via la CSS custom property --content-width sur <main>.
Closes#33
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Les cards occupent maintenant toute la largeur disponible avec juste
1.5rem de marge sur les côtés, quelle que soit la taille d'écran.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Header, contenu principal et footer alignés à 1400px pour mieux
utiliser l'espace horizontal sur les écrans larges.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Footer sombre (même couleur que le header) avec logo, liens nav et
version git (commit court). Body en flex-column + main flex:1
garantit que le footer reste en bas même sur les pages courtes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Route /trash/preview/<path> : réutilise preview_image/text/other.html
avec from_trash=True (← Corbeille, pas de rename ni de resize)
- Nom de fichier cliquable dans la liste corbeille → aperçu
- Suppression du lien "Tableau de bord" du header (doublon avec le logo)
- Ferme #25
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
user et humansize injectés via _inject_globals() pour toutes les pages.
Supprime les 8 injections manuelles redondantes dans les routes.
Corrige le bug : /trash n'avait pas user → header incomplet.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Réduit le padding nav (wrap évité sur "Tableau de bord"), remplace
"Nom complet + bouton Déconnexion" par un avatar cercle + prénom +
icône ⎋ avec tooltip, libérant ~120px horizontaux.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Nouvelle stat box cliquable (→ /trash) affichant le nombre de fichiers,
la taille totale et la date du plus ancien fichier en corbeille.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Suppression déplace dans .trash/ (arborescence préservée + .trashinfo)
- /trash : liste, restauration (conflit overwrite/rename), suppression
définitive, vidage complet
- Purge automatique des fichiers > 30 jours à chaque visite /trash
- Badge rouge dans la nav avec le nombre de fichiers en corbeille
- Extraction du tableau de fichiers en partial _file_table.html
partagé entre browse et trash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Convertit le mode palette (ICO, GIF) en RGBA avant LANCZOS pour
éviter l'erreur de resampling sur les sources ICO
- Sans dimension sélectionnée : conserve les dimensions d'origine
- Sans format sélectionné : conserve le format d'origine
- Bouton toujours actif, texte dynamique "Générer la copie / N copies"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ajout d'un champ W×H dans la carte resize, contraint à la résolution
source. Option "carré" synchronise les deux valeurs. Le bouton Générer
s'active si au moins un format est sélectionné et une taille valide
(prédéfinie ou libre) est renseignée.
Supprime le code mort dans la route /resize (errors_pre).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Route POST /check-resize : pre-check des fichiers cibles avant génération
(utilise déjà le strip _NxN sur le stem)
- Route /resize : strip du suffixe _NxN dans le stem source
(logo_1024x1024.png → 500x500 = logo_500x500.png)
- preview_image.html : bloc conflit masqué par défaut
Au clic Générer → pre-check AJAX → si conflit : panneau jaune identique
à l'upload avec Backup/Écraser/Renommer/Ignorer puis Confirmer
Sans conflit → génération directe sans interruption
Ferme #10.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ajoute une carte de propriétés au-dessus de l'image :
- Dimensions (largeur × hauteur), format, mode couleur, résolution DPI
- Données EXIF complètes si présentes (appareil, exposition, ISO, focale,
balance des blancs, auteur, copyright…)
- Coordonnées GPS avec lien OpenStreetMap si le champ est renseigné
Nouvelle fonction _image_meta() et _parse_gps() dans app.py (Pillow).
Grille CSS responsive, n'apparaît pas si aucune métadonnée disponible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Au lieu d'un sélecteur statique pré-affiché, le formulaire d'upload
vérifie maintenant les conflits côté serveur au clic sur Envoyer :
- Aucun conflit → envoi immédiat sans interruption
- Conflits détectés → panneau jaune avec la liste des fichiers existants
et un sélecteur segmenté Écraser / Backup / Renommer / Ignorer
- L'utilisateur confirme ou annule avant que le formulaire parte
Ajout de la route POST /check-upload (retourne {"conflicts": [...]}).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ajoute un sélecteur de stratégie dans le formulaire de dépôt CDN,
identique à celui du redimensionnement :
- Écraser (défaut) : comportement précédent, écrase silencieusement
- Backup : renomme l'existant en {stem}_bak_{timestamp}{ext} avant dépôt
- Renommer : auto-incrémente le nom du fichier uploadé ({stem}_1, _2…)
- Ignorer : ne dépose pas si le fichier existe déjà
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Route POST /rename : renomme un fichier CDN avec validation sécurité,
retourne JSON (name, path, browse_url)
- Route /resize : accepte param `conflict` (backup | overwrite | rename | skip)
backup → renomme l'existant en {stem}_bak_{timestamp}{ext} avant création
rename → auto-incrémente le nom de la copie ({stem}_1, _2…)
overwrite → écrase silencieusement
skip → ignore (signalé dans les erreurs)
- browse.html : bouton ✏️ par fichier, renommage inline avec Entrée/Échap
- preview_image.html : bouton ✏️ dans l'en-tête, champ inline + redirect
après validation ; radio segmenté pour la stratégie de conflit
- app.css : styles btn-rename, rename-inline, radio-chips segmentés
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ajoute une carte interactive sous chaque aperçu d'image permettant de
générer des copies redimensionnées directement dans le même dossier CDN.
- Route POST /resize avec Pillow (PNG, JPG, ICO) et cairosvg optionnel (SVG)
- Tailles disponibles : 32, 64, 100, 128, 200, 300, 500, 600, 1024 px (carré)
- Formats : png, jpg, ico (svg uniquement si la source est déjà SVG)
- Nommage automatique : {nom}_{taille}x{taille}.{ext}
- UI chips cliquables, soumission AJAX, retour avec liens directs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
session.clear() seul ne déconnectait pas la session Keycloak,
provoquant une reconnexion automatique immédiate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- stats.html : bouton "Générer et ouvrir" avec polling async ; supprime
le window.open automatique (bloqué par les navigateurs)
- app.py : routes POST /stats/generate et GET /stats/status ; exécution
GoAccess en thread daemon, verrou anti-doublon
- .env.example : documente STATS_LOG_FILE et STATS_GENERATE_CMD
- README.md : flux de publication local→git→serveur, variables d'env,
procédure première installation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>