From 4366db94edb6d0f6acb1b8b9638333ed5aac9703 Mon Sep 17 00:00:00 2001 From: Fabrice Date: Fri, 9 Oct 2026 14:49:47 +0000 Subject: [PATCH] Administration depuis le site : bouton Connexion sur l'accueil MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit site.toml [administration] adresse : l'accueil affiche un bouton « Connexion » vers l'interface. Celle-ci peut tourner derrière le serveur web du site (rtg interface --proxy --prefixe /admin) : adresses préfixées, mot de passe obligatoire, visiteur identifié par X-Forwarded-For, cookie Secure limité au préfixe quand le site est en HTTPS. Exemples nginx et Apache dans le README. Le bouton Arrêter ne remplace plus le bloc de la tâche en même temps que la relève. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim --- README.md | 37 ++++++++++ rtg/accueil.py | 1 + rtg/cli.py | 7 +- rtg/config.py | 3 + rtg/interface/modeles/_analyse.html | 10 +-- rtg/interface/modeles/_bandeau.html | 4 +- rtg/interface/modeles/_dossiers.html | 8 +- rtg/interface/modeles/_infos_utiles.html | 2 +- rtg/interface/modeles/_sources.html | 6 +- rtg/interface/modeles/_tache.html | 4 +- rtg/interface/modeles/base.html | 22 +++--- rtg/interface/modeles/connexion.html | 9 ++- rtg/interface/modeles/nouveau.html | 4 +- rtg/interface/modeles/reglages.html | 10 ++- rtg/interface/modeles/tache.html | 2 +- rtg/interface/modeles/voyage.html | 18 ++--- rtg/interface/modeles/voyages.html | 10 +-- rtg/interface/securite.py | 11 +-- rtg/interface/serveur.py | 93 +++++++++++++++++------- rtg/interface/static/editeur.js | 5 +- rtg/templates/base_accueil.html | 6 ++ tests/test_interface.py | 43 +++++++++++ 22 files changed, 229 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index e6b0b0d..650247d 100644 --- a/README.md +++ b/README.md @@ -143,8 +143,44 @@ L'interface permet de modifier le site et de lire les fichiers de l'ordinateur : - servir l'interface derrière le serveur web du site, en HTTPS, sur une adresse à part ; - `--certificat cert.pem --cle cle.pem` ; - le plus simple : un tunnel SSH. Sur le serveur, lancer `rtg interface --sans-navigateur` (sans `--distant`). Sur le Mac, ouvrir `ssh -L 8765:127.0.0.1:8765 serveur`, puis l'adresse avec la clé affichée sur le serveur. +- **Bouton « Connexion » sur l'accueil du site** : voir ci-dessous. - Les fichiers de réglages (`site.toml`, `voyage.toml`) sont dans les dossiers du site. Si le serveur web publie ce dossier tel quel, ils sont lisibles par tous, avec les chemins de tes disques. Rien de secret n'y est écrit, mais on peut les refuser au serveur web (par exemple `location ~ \.toml$ { deny all; }` pour nginx, ou ` Require all denied ` pour Apache). +### Administration depuis le site + +Pour administrer le site depuis son adresse publique (bouton « Connexion » en haut à droite de l'accueil, qui mène à `https://roadtrip.ouaibe.fr/admin/`), l'interface tourne sur la machine qui sert le site, derrière son serveur web en HTTPS. Les sources (rushs, traces, photos) doivent alors être accessibles depuis cette machine pour encoder et générer. + +1. Sur cette machine, une fois : `rtg mot-de-passe`. +2. Lancer l'interface, en permanence de préférence (service, `tmux`…) : + ``` + rtg --racine /chemin/vers/Road-Trips interface --proxy --prefixe /admin --port 8765 + ``` + Elle n'écoute que sur 127.0.0.1 : seul le serveur web peut la joindre. Le mot de passe est obligatoire. +3. Transmettre `/admin/` à l'interface depuis le serveur web. + - nginx, dans le bloc `server` du site en HTTPS : + ```nginx + location /admin/ { + proxy_pass http://127.0.0.1:8765; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + } + location ~ \.toml$ { deny all; } + ``` + - Apache (modules `proxy`, `proxy_http`, `headers`) : + ```apache + ProxyPass /admin/ http://127.0.0.1:8765/admin/ + ProxyPassReverse /admin/ http://127.0.0.1:8765/admin/ + RequestHeader set X-Forwarded-Proto "https" + + Require all denied + + ``` +4. Dans l'interface, Réglages → « Administration depuis le site » : adresse `/admin/`. Après « Refaire l'accueil », le bouton « Connexion » apparaît sur la page d'accueil. + +Le bouton n'est qu'un lien : la protection est celle de l'interface (mot de passe, blocage après plusieurs essais faux, cookie de session limité à `/admin/`, envoyé seulement en HTTPS). + Tout ce que fait l'interface s'écrit dans `site.toml` et les `voyage.toml`. On peut toujours utiliser les commandes ci-dessous ou modifier ces fichiers à la main. Une réécriture par l'interface conserve les valeurs mais pas les commentaires. ## Utilisation : la commande `rtg` @@ -160,6 +196,7 @@ rtg analyser Balades/Vexin-10-2026 # ce que RTG trouve dans les sources rtg verifier # tout est-il installé ? rtg interface # l'interface dans le navigateur rtg mot-de-passe # mot de passe de l'interface (accès distant) +rtg interface --proxy --prefixe /admin # derrière le serveur web du site (bouton Connexion) ``` Les sources d'un voyage peuvent rester où elles sont, nommées n'importe comment : on les indique dans son `voyage.toml`. diff --git a/rtg/accueil.py b/rtg/accueil.py index 39db77d..5c76bfe 100644 --- a/rtg/accueil.py +++ b/rtg/accueil.py @@ -150,6 +150,7 @@ def generer(racine=".", cfg=None, mettre_a_jour_mymaps=False): ecrire("index.html", env.get_template("accueil.html").render( titre_site=TITRE_SITE, rubriques=rubriques, nb_recents=NB_RECENTS, + administration=cfg["administration"]["adresse"], carte_globale=CARTE_GLOBALE if any(r['titre'] == TITRE_SECTION_RACINE for r in rubriques) else None, sous_titre=f"{total} voyage{'s' if total > 1 else ''}" if total else "")) for r in rubriques: diff --git a/rtg/cli.py b/rtg/cli.py index 8266b47..a724bd1 100644 --- a/rtg/cli.py +++ b/rtg/cli.py @@ -54,6 +54,9 @@ def main(argv=None): help="accepter les connexions depuis le réseau (mot de passe obligatoire)") i.add_argument("--certificat", help="certificat TLS (PEM) pour servir en HTTPS") i.add_argument("--cle", help="clé privée du certificat TLS (PEM)") + i.add_argument("--proxy", action="store_true", + help="derrière le serveur web du site (mot de passe obligatoire)") + i.add_argument("--prefixe", default="", help="chemin de l'interface sur le site, par exemple /admin") mp = sp.add_parser("mot-de-passe", help="définir ou supprimer le mot de passe de l'interface") mp.add_argument("--supprimer", action="store_true") @@ -105,8 +108,8 @@ def main(argv=None): from rtg.interface.serveur import lancer if bool(args.certificat) != bool(args.cle): sys.exit("❌ --certificat et --cle vont ensemble.") - lancer(racine, args.port, ouvrir=not args.sans_navigateur, distant=args.distant, - certificat=args.certificat, cle=args.cle) + lancer(racine, args.port, ouvrir=not (args.sans_navigateur or args.proxy), distant=args.distant, + certificat=args.certificat, cle=args.cle, proxy=args.proxy, prefixe=args.prefixe) except KeyboardInterrupt: print("\nInterrompu.") sys.exit(130) diff --git a/rtg/config.py b/rtg/config.py index fb293b9..a79536f 100644 --- a/rtg/config.py +++ b/rtg/config.py @@ -32,6 +32,9 @@ DEFAUTS = { "photos": {"grand_cote": 1600, "qualite": 80, "positions_deduites": True}, "journees": {"fuseau": "Europe/Paris", "coupure_heure": 4}, "carte_globale": {"mymaps": []}, + # Adresse de l'interface (rtg interface --proxy) : bouton « Connexion » + # sur l'accueil du site. Vide = pas de bouton. + "administration": {"adresse": ""}, # Propres à un voyage "rubrique": "", "moto": "", diff --git a/rtg/interface/modeles/_analyse.html b/rtg/interface/modeles/_analyse.html index 332e741..1c3f520 100644 --- a/rtg/interface/modeles/_analyse.html +++ b/rtg/interface/modeles/_analyse.html @@ -16,7 +16,7 @@ {{ nom }} {{ d.km|round|int }} km -
+
@@ -24,7 +24,7 @@ {% set s = suggestions.get(nom) %} {% if s and s[0] %} - {% else %}pas de suggestion{% endif %} @@ -34,7 +34,7 @@ {% if non_datees|length > 1 %} -
+ Ou bien, dans l'ordre des noms, un jour par trace à partir du @@ -66,13 +66,13 @@ {{ d.nom }}{% if d.erreur %} illisible{% endif %} {% for j in d.jours %}{{ j|date_courte }}{{ ", " if not loop.last }}{% else %}—{% endfor %} - +
{% if d.impose %} - {% endif %} diff --git a/rtg/interface/modeles/_bandeau.html b/rtg/interface/modeles/_bandeau.html index 7ba72f6..c3a3a0a 100644 --- a/rtg/interface/modeles/_bandeau.html +++ b/rtg/interface/modeles/_bandeau.html @@ -1,5 +1,5 @@ {% if tache and tache.en_cours %} -⏳ {{ tache.libelle }} · {{ tache.etape }}{% if tache.etape_voyage %} ({{ tache.etape_voyage }}){% endif %}{% if tache.video and tache.video.rang %} · vidéo {{ tache.video.rang[0] }}/{{ tache.video.rang[1] }} ({{ tache.video.pct|int }} %){% endif %} +⏳ {{ tache.libelle }} · {{ tache.etape }}{% if tache.etape_voyage %} ({{ tache.etape_voyage }}){% endif %}{% if tache.video and tache.video.rang %} · vidéo {{ tache.video.rang[0] }}/{{ tache.video.rang[1] }} ({{ tache.video.pct|int }} %){% endif %} {% elif tache %} -{{ "✓" if tache.ok else "⚠️" }} {{ tache.libelle }} : {{ tache.etape|lower }} +{{ "✓" if tache.ok else "⚠️" }} {{ tache.libelle }} : {{ tache.etape|lower }} {% endif %} diff --git a/rtg/interface/modeles/_dossiers.html b/rtg/interface/modeles/_dossiers.html index 85b00ca..31b3efc 100644 --- a/rtg/interface/modeles/_dossiers.html +++ b/rtg/interface/modeles/_dossiers.html @@ -3,14 +3,14 @@

Choisir un dossier de {{ libelle }}

{% for nom, c in raccourcis %} - + {% endfor %}

{{ chemin }}

- -
+ + diff --git a/rtg/interface/modeles/_tache.html b/rtg/interface/modeles/_tache.html index e05b8a4..2ef2baf 100644 --- a/rtg/interface/modeles/_tache.html +++ b/rtg/interface/modeles/_tache.html @@ -1,13 +1,13 @@ {% if message %}
{{ message }}
{% endif %} {% if tache %}
+ {% if tache.en_cours %}hx-get="{{ base }}/tache/fragment{{ '?complet=1' if complet }}" hx-trigger="every 1s" hx-swap="outerHTML"{% endif %}>
{{ tache.libelle }} {{ "⏳ " if tache.en_cours }}{{ tache.etape }}{% if tache.etape_voyage %} — {{ tache.etape_voyage }}{% endif %} {{ tache.duree }} {% if tache.en_cours %} - + {% endif %}
{% if tache.video and tache.video.rang %} diff --git a/rtg/interface/modeles/base.html b/rtg/interface/modeles/base.html index 63a53e8..4b3a3b6 100644 --- a/rtg/interface/modeles/base.html +++ b/rtg/interface/modeles/base.html @@ -4,21 +4,21 @@ {% block titre %}{% endblock %} · RTG - + {% block tete %}{% endblock %} - + - +
- + -
- +
+
{% block contenu %}{% endblock %} @@ -26,7 +26,7 @@
RoadTripsGenerator {{ version }} · {{ racine }}
- + {% block fin %}{% endblock %} diff --git a/rtg/interface/modeles/connexion.html b/rtg/interface/modeles/connexion.html index 81661c6..fd072f3 100644 --- a/rtg/interface/modeles/connexion.html +++ b/rtg/interface/modeles/connexion.html @@ -4,15 +4,15 @@ Connexion · RTG - - + +
{% if connecte %}

Vous êtes connecté.

-

Ouvrir l'interface

+

Ouvrir l'interface

{% else %}

Accès à l'interface

{% if not distant %} @@ -20,7 +20,7 @@ elle contient la clé d'accès de cette session{% if mdp %}, ou entrez le mot de passe{% endif %}.

{% endif %} {% if mdp %} - + @@ -32,6 +32,7 @@ rtg mot-de-passe.

{% endif %} {% endif %} + {% if site %}

← Retour au site

{% endif %}
diff --git a/rtg/interface/modeles/nouveau.html b/rtg/interface/modeles/nouveau.html index ed81941..169543c 100644 --- a/rtg/interface/modeles/nouveau.html +++ b/rtg/interface/modeles/nouveau.html @@ -2,7 +2,7 @@ {% block titre %}Nouveau voyage{% endblock %} {% block contenu %}

Nouveau voyage

- + {% if erreur %}
{{ erreur }}
{% endif %}