App Flask complète pour https://dynamic.alpinux.org : - 10 quiz Linux, 5 niveaux (Découverte → Expert), 50+ questions - Public : Découverte, Débutant, Intermédiaire (6 quiz) - Membres AlpID : Avancé, Expert (4 quiz — Git, Admin, Sécurité, Bash) - Navigation question par question avec avance automatique après choix - Score calculé côté serveur, enregistré en SQLite si connecté - Page profil : meilleurs scores par quiz + historique des tentatives Authentification : - OIDC via authlib + AlpID (Keycloak), SSO partagé avec Gitea/Nextcloud - Décorateur @login_required, redirection post-login sur l'URL d'origine - /auth/login, /auth/callback, /auth/logout Structure : - dynamic/app.py, db.py, quiz.py, auth_utils.py - dynamic/routes/ (public.py, auth.py, protected.py) - dynamic/templates/ (base, index, quiz/*, profil/) - dynamic/static/ (style.css thème Alpinux, quiz.js vanilla) - dynamic/data/quizzes.json (source de vérité des questions) - dynamic/.env.example Infrastructure : - scripts/dynamic.alpinux.org.vhost.conf (Apache reverse proxy) - scripts/dynamic.alpinux.org.service (systemd Gunicorn) - docs/technique/deploiement-dynamic.md (procédure complète) - mkdocs.yml : page de déploiement ajoutée à la nav Technique - .gitignore : exclut venv/ et .env Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
59 lines
2.3 KiB
HTML
59 lines
2.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{% block title %}Quiz Linux{% endblock %} — Alpinux</title>
|
|
<meta name="description" content="{% block description %}Quiz interactifs sur Linux, du niveau découverte à expert — par l'association Alpinux, LUG de Savoie.{% endblock %}">
|
|
<link rel="icon" type="image/x-icon" href="https://static.alpinux.org/logo/favicon.ico">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="https://static.alpinux.org/logo/favicon-32.png">
|
|
<link rel="apple-touch-icon" sizes="192x192" href="https://static.alpinux.org/logo/favicon-192.png">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
{% block head %}{% endblock %}
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<div class="header-inner">
|
|
<a href="{{ url_for('public.index') }}" class="brand">
|
|
<img src="https://static.alpinux.org/logo/alpinux-logo.png"
|
|
alt="Alpinux" width="44" height="44" class="brand-logo">
|
|
<span>A<strong>l</strong>p<strong>inux</strong> <span class="brand-sub">Quiz</span></span>
|
|
</a>
|
|
<nav class="main-nav">
|
|
<a href="{{ url_for('public.index') }}">Accueil</a>
|
|
<a href="{{ url_for('public.quiz_list') }}">Tous les quiz</a>
|
|
{% if user %}
|
|
<a href="{{ url_for('protected.profil') }}">Mon profil</a>
|
|
<a href="{{ url_for('auth.logout') }}" class="btn-nav-outline">Déconnexion</a>
|
|
{% else %}
|
|
<a href="{{ url_for('auth.login') }}" class="btn-nav">Se connecter</a>
|
|
{% endif %}
|
|
</nav>
|
|
<button class="nav-toggle" aria-label="Menu" onclick="this.closest('header').classList.toggle('nav-open')">☰</button>
|
|
</div>
|
|
</header>
|
|
|
|
<main>
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% for category, message in messages %}
|
|
<div class="flash flash-{{ category }}">{{ message }}</div>
|
|
{% endfor %}
|
|
{% endwith %}
|
|
{% block content %}{% endblock %}
|
|
</main>
|
|
|
|
<footer>
|
|
<div class="footer-inner">
|
|
<span>© Alpinux — LUG de Savoie</span>
|
|
<span class="footer-links">
|
|
<a href="https://alpinux.org">alpinux.org</a>
|
|
<a href="https://wiki.alpinux.org">Wiki</a>
|
|
<a href="https://mamot.fr/@alpinux">Mastodon</a>
|
|
</span>
|
|
</div>
|
|
</footer>
|
|
|
|
{% block scripts %}{% endblock %}
|
|
</body>
|
|
</html>
|