make server-token.
+ diff --git a/CHANGELOG.md b/CHANGELOG.md index ef76847..f417218 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,32 @@ Toutes les évolutions notables de Noxely sont documentées ici. Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/) et le versionnage [SemVer](https://semver.org/lang/fr/). +## [2.3.0] — 2026-09-13 + +### Ajouté + +- **Page « À propos » du serveur.** Un onglet `ℹ️ À propos` avec en-tête d'identité et cinq + sections : ce que le serveur fait, ce qu'il garantit, avec quoi il est construit, ce qui a + changé, et qui en répond. Le contenu vit dans un module de constantes séparé du rendu. +- Le journal est **lu** et non recopié : une route `/api/changelog` sert le `CHANGELOG.md` du + dépôt, que la page interprète pour ce qu'il emploie réellement — titres, puces et gras. + +### Modifié + +- **Le champ « jeton » quitte l'en-tête du serveur**, où il n'avait rien à faire, pour l'onglet + `⚙️ Réglages`. Une carte y donne aussi le nom du serveur, sa version et le nombre d'appareils + appairés. Sans jeton connu, la pastille d'état indique où le saisir. +- **Le serveur annonce la version du dépôt.** Il lisait une constante figée à `1.0.0`, restée en + arrière de six publications ; il lit maintenant le fichier `VERSION`, comme le client. + +### Corrigé + +- `saveToken()` relançait l'amorçage du jeton au lieu de rafraîchir la page : un bloc y avait été + déplacé par erreur lors d'une édition antérieure. +- Une garantie annoncée à tort. La page affirmait que toute route hors `/api/health` exigeait un + jeton ; la page elle-même, ses ressources et le journal répondent sans. Les sept garanties ont + été confrontées au code une à une, celle-ci reformulée. + ## [2.2.0] — 2026-09-13 ### Ajouté diff --git a/VERSION b/VERSION index ccbccc3..276cbf9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2.0 +2.3.0 diff --git a/server/noxely_server/api.py b/server/noxely_server/api.py index 20b2703..2295096 100644 --- a/server/noxely_server/api.py +++ b/server/noxely_server/api.py @@ -24,14 +24,17 @@ from contextlib import asynccontextmanager from pathlib import Path from fastapi import Body, Depends, FastAPI, Header, HTTPException, Query, Request -from fastapi.responses import FileResponse, HTMLResponse +from fastapi.responses import FileResponse, HTMLResponse, PlainTextResponse, Response from . import config as config_module from .sources import catalogs, radio from .storage import Storage from .workers import Downloader, Watcher, safe_name -VERSION = "1.0.0" +# Client et serveur sortent du même dépôt, à la même version : on lit le fichier +# que la chaîne de publication incrémente, plutôt qu'un numéro à tenir à jour ici. +REPO_ROOT = Path(__file__).resolve().parents[2] +VERSION = (REPO_ROOT / "VERSION").read_text().strip() if (REPO_ROOT / "VERSION").exists() else "0.0.0" PAIRING_WINDOW_SECONDS = 120 WEB_DIR = Path(__file__).parent / "web" @@ -278,6 +281,21 @@ def bootstrap(request: Request) -> dict: return {"token": config.token} +@app.get("/api/changelog", response_class=PlainTextResponse) +def changelog() -> str: + """Le journal du dépôt, lu à la demande plutôt que recopié dans la page.""" + path = REPO_ROOT / "CHANGELOG.md" + return path.read_text(encoding="utf-8") if path.exists() else "" + + +@app.get("/about.js", response_class=PlainTextResponse) +def about_script() -> Response: + return Response( + (WEB_DIR / "about.js").read_text(encoding="utf-8"), + media_type="application/javascript; charset=utf-8", + ) + + @app.get("/", response_class=HTMLResponse) def index() -> str: return (WEB_DIR / "index.html").read_text(encoding="utf-8") diff --git a/server/noxely_server/web/about.js b/server/noxely_server/web/about.js new file mode 100644 index 0000000..7fdd4e8 --- /dev/null +++ b/server/noxely_server/web/about.js @@ -0,0 +1,73 @@ +// Noxely — collecte côté serveur, écoute hors connexion côté mobile. +// Copyright © 2026 Johnny (tuxgyver) +// +// Ce programme est un logiciel libre : vous pouvez le redistribuer et le modifier +// selon les termes de la GNU General Public License, version 3 ou ultérieure, +// telle que publiée par la Free Software Foundation. +// +// Il est distribué dans l'espoir d'être utile, mais SANS AUCUNE GARANTIE. +// Voir le fichier LICENSE à la racine du dépôt pour le texte complet. + +// Contenu de la page « À propos », séparé de son rendu. +// Tout ce qui est écrit ici se vérifie dans le dépôt : une fonction décrite existe, une garantie +// annoncée est tenue par du code. La version vient de /api/health, jamais recopiée. +const ABOUT = { + name: 'Noxely — serveur', + tagline: 'Il cherche, il guette, il enregistre. Le téléphone n\'a plus qu\'à écouter.', + repository: 'https://git.tuxtech.fr/tuxgyver/Noxely', + author: 'Johnny', + authorRole: 'Conception, direction et supervision', + authorship: + 'Application développée avec l\'assistance de l\'IA, sous la direction et la supervision de Johnny.', + license: 'GPL v3', + copyright: 'Copyright © 2026 Johnny (tuxgyver)', + + // Ce que fait le serveur, du point de vue de qui s'en sert. + features: [ + ['Chercher partout à la fois', + 'Internet Archive, Underground (netlabels et Free Music Archive), LibriVox et Wikimedia ' + + 'Commons sont interrogés en parallèle, les résultats fondus et les plus légers mis en tête.'], + ['Choisir avant de télécharger', + 'Un album ou un livre affiche ses pistes avec leur taille : on met en file ce qu\'on veut, ' + + 'pas l\'ensemble.'], + ['Guetter un morceau sur les radios', + 'On nomme un titre sans désigner de station : les plus écoutées sont balayées en continu, ' + + 'et l\'enregistrement part dès que l\'une le diffuse.'], + ['Enregistrer un direct', + 'La capture prend le nom du morceau annoncé par la station, et s\'arrête quand il cède la ' + + 'place au suivant.'], + ['Tenir la file', + 'Les téléchargements s\'enchaînent seuls ; un échec est repris, son état reste visible.'], + ['Servir le mobile', + 'L\'application demande ce qui a changé depuis son dernier passage et récupère des fichiers ' + + 'déjà prêts — c\'est ce qui lui épargne sa batterie.'], + ], + + guaranteesIntro: + 'Noxely est un outil personnel : rien n\'y transite par un service tiers, et le serveur ne ' + + 'répond qu\'à ce qui présente un jeton valide.', + + // Chaque promesse est tenue par du code. + guarantees: [ + 'Aucun compte, aucune clé d\'API : les quatre catalogues et l\'annuaire radio sont publics.', + 'Toute route de données exige un jeton — celui du serveur, ou celui d\'un appareil appairé ; ' + + 'seuls l\'état de santé, la page et ses ressources répondent sans.', + 'Chaque appareil reçoit son propre jeton : en révoquer un ne coupe pas les autres.', + 'Les jetons d\'appareil ne sont jamais renvoyés par la route qui les liste.', + 'L\'appairage n\'est possible que pendant une fenêtre ouverte depuis cette interface, deux ' + + 'minutes durant, une seule fois, et depuis le réseau local uniquement.', + 'Les catalogues ne proposent que des œuvres libres ou du domaine public ; les éléments en ' + + 'accès restreint sont écartés avant même d\'être affichés.', + 'Une synchronisation partielle ne fait pas avancer le curseur du client : rien n\'est perdu.', + ], + + // Couche → outils. + stack: [ + ['Service', 'Python, FastAPI, uvicorn, unité systemd'], + ['Données', 'SQLite pour l\'index, fichiers sur disque pour les médias'], + ['Collecte', 'httpx asynchrone, flux ICY pour les titres de radio'], + ['Catalogues', 'Internet Archive, netlabels, LibriVox, Wikimedia Commons'], + ['Interface', 'HTML, CSS et JavaScript sans dépendance'], + ['Synchronisation', 'HTTP avec jeton, curseur incrémental côté client'], + ], +}; diff --git a/server/noxely_server/web/index.html b/server/noxely_server/web/index.html index 2ae8cc1..cd4e2d7 100644 --- a/server/noxely_server/web/index.html +++ b/server/noxely_server/web/index.html @@ -48,6 +48,25 @@ .pill.ok { background:rgba(17,168,148,.16); color:var(--mint); } .pill.warn { background:rgba(224,98,155,.16); color:#e0629b; } .empty { color:var(--muted); text-align:center; padding:40px 10px; } + /* Page « À propos » : en-tête d'identité figé, corps en sous-onglets. */ + .about-head { display:flex; gap:16px; align-items:center; margin-bottom:16px; } + .about-logo { width:80px; height:80px; border-radius:22px; background:var(--accent); + display:grid; place-items:center; font-size:40px; flex:none; } + .about-head h2 { margin:0 0 2px; font-size:20px; } + .about-head p { margin:0 0 8px; color:var(--muted); font-size:14px; } + .pill.accent { background:var(--accent); color:#fff; } + .subtabs { display:flex; gap:6px; flex-wrap:wrap; margin-bottom:14px; } + .subtabs button { background:none; border:1px solid var(--line); border-radius:999px; + padding:5px 12px; color:var(--muted); cursor:pointer; font-size:13px; } + .subtabs button[aria-selected="true"] { border-color:var(--accent); color:var(--accent); } + .about-body { max-height:none; } + .about-body .card { display:block; } + .about-body ul { margin:0; padding-left:20px; } + .about-body li { margin:5px 0; } + .tech { width:100%; border-collapse:collapse; } + .tech td { padding:9px 4px; border-bottom:1px solid var(--line); vertical-align:top; } + .tech td:first-child { color:var(--muted); width:38%; } + .log h4 { margin:0 0 4px; font-size:15px; }
@@ -55,8 +74,6 @@make server-token.
+