No description
  • TypeScript 71.9%
  • Svelte 27.2%
  • CSS 0.3%
  • Dockerfile 0.3%
  • HTML 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Matthieu Peterschmitt dcda961d04
Some checks failed
Publish / image (release) Successful in 2m12s
CI / check (push) Failing after 2m56s
feat(metrics): add prometheus endpoint
2026-09-24 10:03:16 +02:00
.forgejo/workflows ci: type-check scripts, install Playwright browser for e2e 2026-09-24 09:18:03 +02:00
docs feat(metrics): add prometheus endpoint 2026-09-24 10:03:16 +02:00
drizzle feat(public): settings, shared cache built once per change, metrics 2026-09-23 22:31:48 +02:00
e2e test(e2e): public page follows the day and can be cut; clearer day status 2026-09-23 22:41:33 +02:00
scripts feat(loadtest): phone-like bots, simulated control room, multi-machine reports 2026-09-23 23:09:47 +02:00
src feat(metrics): add prometheus endpoint 2026-09-24 10:03:16 +02:00
static feat(robot): change allowlist for robot 2026-09-24 09:39:25 +02:00
.dockerignore build: Docker image and compose (app only, own reverse proxy) 2026-09-23 20:57:18 +02:00
.env.example feat(metrics): add prometheus endpoint 2026-09-24 10:03:16 +02:00
.gitignore fix(ui): detect concurrent corrections; test: end-to-end match day 2026-09-23 20:59:54 +02:00
.npmrc chore: scaffold SvelteKit app with sv create 2026-09-23 19:57:45 +02:00
.oxfmtrc.json chore: set up oxlint, oxfmt and TypeScript 7 (tsgo) 2026-09-23 19:59:28 +02:00
.oxlintrc.json feat(domain): rotation scheduler, planning adapter and timeline 2026-09-23 20:16:20 +02:00
.renovate.json format(oxfmt): format docs and renovate.json 2026-09-23 23:35:01 +02:00
compose.yaml fix(login): per-client IP behind the proxy, reject off-site next 2026-09-24 09:16:51 +02:00
Containerfile fix(db): migrate and storage 2026-09-24 00:04:07 +02:00
docker-entrypoint.sh fix(db): migrate and storage 2026-09-24 00:04:07 +02:00
drizzle.config.ts feat(server): schema, PIN auth, SSE bus, backups and services 2026-09-23 20:24:53 +02:00
package.json docs: load test walkthrough; public page in README; type-check scripts in CI 2026-09-24 00:06:13 +02:00
playwright.config.ts fix(ui): detect concurrent corrections; test: end-to-end match day 2026-09-23 20:59:54 +02:00
pnpm-lock.yaml feat(public): live page for everyone, admin tab with QR code and server health 2026-09-23 22:34:58 +02:00
pnpm-workspace.yaml chore: scaffold SvelteKit app with sv create 2026-09-23 19:57:45 +02:00
README.md feat(metrics): add prometheus endpoint 2026-09-24 10:03:16 +02:00
tsconfig.json chore: scaffold SvelteKit app with sv create 2026-09-23 19:57:45 +02:00
vite.config.ts chore: set up oxlint, oxfmt and TypeScript 7 (tsgo) 2026-09-23 19:59:28 +02:00

FLBX Cup

Webapp pour organiser des tournois de thèque (scouts) le jour J : plusieurs tournois en parallèle, poules, arbres (simple/double élimination, arbres incomplets), terrains, rotations lancées au micro, goûter, pauses, saisie des scores à plusieurs en temps réel.

Démarrer en local

pnpm install
cp .env.example .env      # puis changez les codes
pnpm dev                  # http://localhost:5173
Variable Rôle
ADMIN_PIN code administrateur (configuration + tout le reste)
REFEREE_PIN code arbitre (saisie des scores)
SESSION_SECRET chaîne aléatoire qui signe les cookies (openssl rand -hex 32)
DATABASE_URL fichier SQLite (data/flbxcup.db)
PUBLIC_TIME_ZONE fuseau d’affichage des heures (Europe/Paris)
ORIGIN URL publique en production (ex. https://tournoi.example.org)
METRICS_TOKEN facultatif : jeton des métriques Prometheus (voir Supervision)

Déployer (VPS + Docker)

cp .env.example .env         # codes + SESSION_SECRET
ORIGIN=https://tournoi.example.org docker compose up -d --build
  • L’app écoute sur 127.0.0.1:3000 ; mettez votre reverse proxy devant (HTTPS, HTTP/2 recommandé : chaque écran ouvert garde une connexion SSE).
  • Désactivez le buffering du proxy pour /api/stream (l’app envoie déjà X-Accel-Buffering: no).
  • Le proxy doit transmettre l’IP du visiteur dans X-Forwarded-For (Caddy le fait tout seul ; nginx : proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;). Sinon tous les visiteurs partagent la même IP et 5 codes faux d’affilée bloquent la connexion de tout le monde pendant une minute. Plusieurs proxys en chaîne : XFF_DEPTH = leur nombre.
  • La base et les sauvegardes sont dans ./data (volume).
  • GET /healthz répond ok si la base répond.

Supervision (Grafana)

GET /api/metrics/prometheus expose l’état du serveur au format Prometheus (mémoire, CPU, réactivité, flux SSE ouverts/fermés, cache public, requêtes). Accès avec une session admin ou l’en-tête Authorization: Bearer $METRICS_TOKEN (sans METRICS_TOKEN, seul l’admin y accède). Exemple pour vmagent ou Prometheus, puis une source VictoriaMetrics/Prometheus dans Grafana :

scrape_configs:
  - job_name: flbxcup
    scrape_interval: 15s
    metrics_path: /api/metrics/prometheus
    authorization:
      credentials: <METRICS_TOKEN> # ou credentials_file
    static_configs:
      - targets: ['127.0.0.1:3000'] # ou tournoi.example.org avec scheme: https

Tableau de bord prêt à l’emploi : dans Grafana, Dashboards → New → Import, fichier docs/grafana/flbxcup.json, puis choisir la source en haut du tableau (téléphones connectés, réactivité, mémoire, flux SSE, cache public, CPU, redémarrages).

Depuis un autre conteneur, 127.0.0.1 n’est pas l’app : passer par le proxy (HTTPS) ou mettre les deux conteneurs sur le même réseau Docker (cible flbxcup:3000).

Fiabilité

  • Sauvegardes automatiques toutes les 5 minutes (s’il y a eu des changements) dans data/backups/, les 60 dernières sont gardées. Liste, sauvegarde immédiate et téléchargement sur la page d’accueil admin.
  • Restaurer une sauvegarde : docker compose stop, remplacer data/flbxcup.db par le fichier sauvegardé (et supprimer flbxcup.db-wal / -shm), docker compose start.
  • Export d’un événement (JSON, page Historique) et import sur un autre serveur (page d’accueil).
  • Historique + annulation de chaque action ; une annulation est refusée si les matchs concernés ont changé depuis.
  • Plan B papier : bouton « Imprimer » du planning.
  • Répétition : Configuration → « Créer une répétition », puis à la régie « Simuler les scores en cours » pour dérouler toute la journée à blanc la veille.
  • Page publique (/direct, sans code, QR code dans l'onglet « Page publique ») : données calculées une fois par changement et partagées par tous les téléphones, bouton pour la couper instantanément, état du serveur en direct (téléphones connectés, mémoire, réactivité).
  • Test de charge avant le jour J : bots qui imitent des téléphones + régie simulée, voir docs/test-de-charge.md (pnpm loadtest …). Variable facultative PUBLIC_MAX_STREAMS (3000 par défaut) : au-delà, les téléphones relisent toutes les 20 s au lieu de rester connectés.

Déroulé du jour J

  1. Configuration : terrains (attribués à un tournoi), tournois, équipes (coller une liste), format (modèles : élimination directe, double élimination, championnat, poules puis arbre, poules puis 2 arbres), aperçu, puis « Générer tous les matchs ».
  2. Régie (ordinateur) : « Lancer la rotation N » au moment de l’annonce au micro (texte à lire fourni). Les scores arrivent en direct depuis les arbitres, ou se saisissent directement.
  3. Arbitres (téléphone) : se connecter avec le code arbitre, choisir son terrain, saisir le score après le coup de sifflet.
  4. Fin des poules : la régie propose de valider le classement (égalités parfaites tranchées à la main), ce qui remplit les arbres.
  5. Goûter : page « Goûter & pauses », cocher les équipes et les rotations ; leurs matchs sont décalés, les terrains libérés sont utilisés par les autres.
  6. Imprévu : planning glisser-déposer (📌 = placé à la main), terrain fermé, équipe retirée (forfaits automatiques), correction de score (bloquée si la suite a déjà été jouée).
  7. Écran géant : sur l’ordinateur branché à l’écran, se connecter avec le code admin, ouvrir l’onglet « Écran géant » puis « Ouvrir l’écran », et passer en plein écran (F11 ou double-clic). Les diapos tournent tournoi par tournoi (en cours, à suivre, résultats, record du jour, goûter : chacune se désactive) avec le compte à rebours de la rotation lancée (« Durée d’un match »). Page pensée pour un mur LED d’environ 576×432 px. Si le boîtier LED ne reprend qu’une zone du bureau pixel pour pixel, indiquer une taille fixe (ou ?w=576&h=432 dans l’adresse) : l’image est alors collée en haut à gauche. En cas de coupure, l’écran garde les dernières infos (point rouge) et reprend tout seul.

Développement

pnpm run ci          # format:check + lint (oxlint) + check (svelte-check --tsgo) + tests unitaires
pnpm test:e2e        # Playwright sur un build de production
pnpm format          # oxfmt
pnpm db:generate     # nouvelle migration Drizzle après un changement de schéma

Structure :

  • src/lib/domain/ : logique pure testée (poules, classements, seeding, arbres, résolution des sources, planificateur rotation × terrain, horaires).
  • src/lib/server/ : base (Drizzle + SQLite WAL), services (configuration, jour J, audit, planification), SSE, sauvegardes.
  • src/routes/ : admin/ (bureau), arbitre/ (mobile), imprimer/.