feat: changelog + versioning sémantique

- 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>
This commit is contained in:
Alpinux 2026-05-06 11:43:26 +02:00
parent 7f5d86449e
commit 6d25cab295
6 changed files with 170 additions and 5 deletions

67
app/CHANGELOG.md Normal file
View file

@ -0,0 +1,67 @@
# Changelog — Alpinux Static
## [1.4.0] — 2026-05-06
### Ajouté
- Sélecteur de largeur du contenu dans le header : Étroit (900 px), Normal (1 200 px), Large (1 600 px), Plein — préférence mémorisée dans le navigateur
- Changelog avec numéro de version sémantique, accessible depuis le footer
### Corrigé
- Décalage de largeur entre les pages dû à l'apparition/disparition de la scrollbar (`scrollbar-gutter: stable`)
- Labels EXIF non traduits : `ResolutionUnit`, `XResolution`, `YResolution`
- Valeur numérique brute pour `ColorSpace` → libellé lisible (sRGB, Adobe RGB, Non calibré)
### Modifié
- Contenu principal en pleine largeur (suppression du `max-width` fixe sur `<main>`)
---
## [1.3.0] — 2026-05-06
### Ajouté
- Prévisualisation des fichiers depuis la corbeille (propriétés, métadonnées, téléchargement)
- Footer sur toutes les pages avec liens de navigation et numéro de version
- Stats corbeille dans le tableau de bord (nombre de fichiers, taille totale, date du plus ancien)
### Modifié
- Header compact : avatar initiale + prénom + icône déconnexion (suppression du bouton texte)
- Suppression du lien « Tableau de bord » dans la navigation (doublon avec le logo)
- `user`, `humansize`, `trash_count` centralisés dans le context processor Flask (disponibles sur toutes les pages)
---
## [1.2.0] — 2026-05-06
### Ajouté
- Corbeille : mise à la corbeille depuis `/browse`, restauration avec gestion des conflits (écraser / renommer), suppression définitive
- Purge automatique des éléments en corbeille depuis plus de 30 jours
- Badge dans la navigation indiquant le nombre d'éléments en corbeille
- Page `/trash` avec tableau partagé (mode `browse` / `trash`) et bouton « Vider la corbeille »
---
## [1.1.0] — 2026-05-06
### Ajouté
- Redimensionnement d'images depuis la prévisualisation : tailles prédéfinies, dimension libre, formats PNG/JPG/ICO
- Gestion des conflits lors du redimensionnement (backup, écraser, renommer, ignorer)
- Renommage de fichiers inline dans `/browse` et la page de prévisualisation
- Affichage des métadonnées image (dimensions, format, mode couleur, DPI, EXIF)
- Dimension libre avec contrainte de proportions (mode « carré »)
### Corrigé
- Échec du redimensionnement sur les fichiers ICO en mode palette (conversion RGBA avant LANCZOS)
- Aucune sélection de taille ou format → copie à l'identique (comportement par défaut)
---
## [1.0.0] — 2026-05-03
### Ajouté
- Upload de fichiers par glisser-déposer avec gestion des conflits (écraser, backup, renommer, ignorer)
- Parcourir les assets CDN depuis `/browse` avec fil d'Ariane
- Statistiques de consultation via GoAccess (`/stats`), générées à la demande
- Authentification SSO via AlpID (Keycloak / OpenID Connect)
- Recherche dans les fichiers
- Scripts rsync `push-assets.sh` et `pull-assets.sh` pour la synchronisation locale ↔ serveur
- Script `deploy-app.sh` pour le déploiement de l'application Flask

1
app/VERSION Normal file
View file

@ -0,0 +1 @@
1.4.0

View file

@ -46,12 +46,12 @@ _gen_lock = threading.Lock()
_gen_state = {"generating": False}
try:
_APP_VERSION = subprocess.check_output(
["git", "rev-parse", "--short", "HEAD"], stderr=subprocess.DEVNULL, text=True
).strip()
_APP_VERSION = (Path(__file__).parent / "VERSION").read_text().strip()
except Exception:
_APP_VERSION = ""
_CHANGELOG_FILE = Path(__file__).parent / "CHANGELOG.md"
_HIDDEN = frozenset({
".git", "scripts", "app",
@ -434,6 +434,52 @@ def dashboard():
)
@app.route("/changelog")
def changelog():
redir = _require_admin()
if redir:
return redir
sections = _parse_changelog()
return render_template("changelog.html", sections=sections)
def _parse_changelog():
"""Parse CHANGELOG.md into a list of version dicts."""
try:
text = _CHANGELOG_FILE.read_text()
except Exception:
return []
sections = []
current = None
current_group = None
for line in text.splitlines():
if line.startswith("## "):
if current:
if current_group:
current["groups"].append(current_group)
sections.append(current)
m = re.match(r"## \[(.+?)\] — (.+)", line)
current = {"version": m.group(1) if m else line[3:],
"date": m.group(2) if m else "",
"groups": []}
current_group = None
elif line.startswith("### ") and current is not None:
if current_group:
current["groups"].append(current_group)
current_group = {"title": line[4:], "items": []}
elif line.startswith("- ") and current_group is not None:
current_group["items"].append(line[2:])
if current:
if current_group:
current["groups"].append(current_group)
sections.append(current)
return sections
# ── Navigateur de fichiers ────────────────────────────────────────────
@app.route("/browse/")

View file

@ -283,7 +283,27 @@ footer { background: var(--blue-dark); color: rgba(255,255,255,.6); margin-top:
.footer-nav a { color: rgba(255,255,255,.55); font-size: .82rem; }
.footer-nav a:hover { color: rgba(255,255,255,.9); text-decoration: none; }
.footer-version { margin-left: auto; font-size: .75rem; color: rgba(255,255,255,.35);
font-family: monospace; }
font-family: monospace; text-decoration: none; }
.footer-version:hover { color: rgba(255,255,255,.7); text-decoration: none; }
/* ── Changelog ──────────────────────────────────────────────────────── */
.cl-section { padding: .25rem 0 1rem; }
.cl-section--latest .cl-header { margin-bottom: .75rem; }
.cl-header { display: flex; align-items: baseline; gap: .75rem; margin-bottom: .6rem; flex-wrap: wrap; }
.cl-version { font-size: 1.05rem; font-weight: 700; color: var(--blue-dark); font-family: monospace; }
.cl-date { font-size: .82rem; color: var(--muted); }
.cl-badge { background: var(--blue); color: #fff; font-size: .68rem; font-weight: 700;
padding: .15rem .5rem; border-radius: 20px; letter-spacing: .03em; }
.cl-group { margin: .6rem 0 0 0; }
.cl-group-title { display: inline-block; font-size: .72rem; font-weight: 700; text-transform: uppercase;
letter-spacing: .06em; padding: .1rem .5rem; border-radius: 3px; margin-bottom: .35rem; }
.cl-group-title--ajouté { background: #dcfce7; color: #166534; }
.cl-group-title--corrigé { background: #fee2e2; color: #991b1b; }
.cl-group-title--modifié { background: #fef3c7; color: #92400e; }
.cl-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .3rem; }
.cl-list li { font-size: .88rem; color: var(--text); padding-left: 1.1rem; position: relative; }
.cl-list li::before { content: ""; position: absolute; left: 0; color: var(--muted); }
.cl-sep { border: none; border-top: 1px solid var(--border); margin: 1rem 0; }
/* ── Corbeille ──────────────────────────────────────────────────────── */
.trash-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: .75rem; margin-bottom: .5rem; }

View file

@ -61,7 +61,7 @@
<a href="{{ url_for('stats') }}">Statistiques</a>
<a href="{{ url_for('trash_list') }}">Corbeille</a>
</nav>
<span class="footer-version">v&nbsp;{{ app_version }}</span>
<a href="{{ url_for('changelog') }}" class="footer-version">v&nbsp;{{ app_version }}</a>
</div>
</footer>

View file

@ -0,0 +1,31 @@
{% extends "base.html" %}
{% block title %}Changelog{% endblock %}
{% block content %}
<section class="card">
<h1 style="font-size:1.2rem; color:var(--blue-dark); margin-bottom:1.5rem">
Historique des versions — Alpinux Static
</h1>
{% for sec in sections %}
<div class="cl-section{% if loop.first %} cl-section--latest{% endif %}">
<div class="cl-header">
<span class="cl-version">v {{ sec.version }}</span>
{% if sec.date %}<span class="cl-date">{{ sec.date }}</span>{% endif %}
{% if loop.first %}<span class="cl-badge">Actuelle</span>{% endif %}
</div>
{% for grp in sec.groups %}
<div class="cl-group">
<span class="cl-group-title cl-group-title--{{ grp.title | lower | replace(' ','_') }}">{{ grp.title }}</span>
<ul class="cl-list">
{% for item in grp.items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
{% if not loop.last %}<hr class="cl-sep">{% endif %}
{% endfor %}
</section>
{% endblock %}