diff --git a/.dockerignore b/.dockerignore index cd967fc..59c3614 100644 --- a/.dockerignore +++ b/.dockerignore @@ -18,7 +18,7 @@ **/Dockerfile* **/node_modules **/npm-debug.log -**/obj +**/obj **/secrets.dev.yaml **/values.dev.yaml LICENSE diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..c58c5c7 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,99 @@ +name: Deploy Webzine + +on: + push: + branches: + - main + - dev + +jobs: + # ───────────────────────────────────────────── + # COMPILATION — commun aux deux branches + # ───────────────────────────────────────────── + build: + name: Build & Push Docker Image + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # Le tag d'image dépend de la branche : + # main → webzine:latest + - name: Set image tag + id: vars + run: | + echo "IMAGE_TAG=latest" >> $GITHUB_OUTPUT + echo "ENV_LABEL=production" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # Connexion au registry Gitea intégré + - name: Log in to Gitea Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ vars.REGISTRY_URL }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + # Construction et publication de l'image Docker + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Webzine.WebApplication/Dockerfile + push: true + tags: ${{ vars.REGISTRY_URL }}/webzine/webzine:${{ steps.vars.outputs.IMAGE_TAG }} + cache-from: type=registry,ref=${{ vars.REGISTRY_URL }}/webzine/webzine:buildcache-${{ steps.vars.outputs.IMAGE_TAG }} + cache-to: type=registry,ref=${{ vars.REGISTRY_URL }}/webzine/webzine:buildcache-${{ steps.vars.outputs.IMAGE_TAG }},mode=max + + outputs: + image_tag: ${{ steps.vars.outputs.IMAGE_TAG }} + env_label: ${{ steps.vars.outputs.ENV_LABEL }} + + # ───────────────────────────────────────────── + # DÉPLOIEMENT — Serveur de PRODUCTION (branche main) + # ───────────────────────────────────────────── + deploy-production: + name: Deploy to Production + needs: build + runs-on: ubuntu-latest + + steps: + - name: Deploy via SSH to PRODUCTION server + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.PROD_HOST }} + username: ${{ secrets.PROD_USER }} + key: ${{ secrets.PROD_SSH_KEY }} + port: ${{ secrets.PROD_SSH_PORT || 22 }} + script: | + set -e + + echo "=== [PROD] Récupération de l'image ===" + docker login ${{ vars.REGISTRY_URL }} \ + -u ${{ secrets.REGISTRY_USERNAME }} \ + -p ${{ secrets.REGISTRY_PASSWORD }} + + docker pull ${{ vars.REGISTRY_URL }}/webzine/webzine:latest + + echo "=== [PROD] Arrêt de l'ancien conteneur ===" + docker stop webzine-prod 2>/dev/null || true + docker rm webzine-prod 2>/dev/null || true + + echo "=== [PROD] Démarrage du nouveau conteneur ===" + docker run -d \ + --name webzine-prod \ + --restart unless-stopped \ + -p 80:8080 \ + -p 443:8081 \ + -v /opt/webzine/prod/data:/app/Data \ + -v /opt/webzine/prod/logs:/Logs \ + -e ASPNETCORE_ENVIRONMENT=Production \ + ${{ vars.REGISTRY_URL }}/webzine/webzine:latest + + echo "=== [PROD] Nettoyage des anciennes images ===" + docker image prune -f + + echo "=== [PROD] Déploiement terminé ===" \ No newline at end of file diff --git a/.gitea/workflows/pr-endpoint-check.yml b/.gitea/workflows/pr-endpoint-check.yml new file mode 100644 index 0000000..c67fab3 --- /dev/null +++ b/.gitea/workflows/pr-endpoint-check.yml @@ -0,0 +1,132 @@ +name: PR Endpoint Performance Check + +on: + pull_request: + branches: + - main + - master + - develop + +jobs: + endpoint-performance-check: + name: Test All Endpoints (< 1s) + runs-on: ubuntu-latest + + steps: + # ───────────────────────────────────────────── + # Récupération du code source + # ───────────────────────────────────────────── + - name: Checkout PR branch + uses: actions/checkout@v4 + + # ───────────────────────────────────────────── + # Installation de .NET 10 + # ───────────────────────────────────────────── + - name: Setup .NET 10 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + + # ───────────────────────────────────────────── + # Restauration des dépendances et compilation + # ───────────────────────────────────────────── + - name: Restore dependencies + run: dotnet restore Webzine.sln + + - name: Build solution + run: dotnet build Webzine.sln --no-restore --configuration Release + + # ───────────────────────────────────────────── + # Exécution des tests unitaires (entités) + # ───────────────────────────────────────────── + - name: Run unit tests + run: | + dotnet test Webzine.Entity.Tests/Webzine.Entity.Tests.csproj \ + --no-build \ + --configuration Release \ + --logger "console;verbosity=normal" + + # ───────────────────────────────────────────── + # Démarrage de l'application web en arrière-plan + # ───────────────────────────────────────────── + - name: Start Webzine application + run: | + dotnet run \ + --project Webzine.WebApplication/Webzine.WebApplication.csproj \ + --configuration Release \ + --no-build \ + -- --urls "http://localhost:5038" & + + echo "Attente du démarrage de l'application..." + timeout 60 bash -c ' + until curl -sf http://localhost:5038 > /dev/null 2>&1; do + sleep 1 + done + ' + echo "Application prête !" + + # ───────────────────────────────────────────── + # Exécution des tests de performance des endpoints + # ───────────────────────────────────────────── + - name: Test endpoint response times + id: perf_test + run: | + chmod +x scripts/test-endpoints.sh + bash scripts/test-endpoints.sh http://localhost:5038 1000 || true + FAIL_COUNT=$(grep -c "^\[FAIL\]\|^\[SLOW\]" /tmp/webzine_endpoint_report.txt || echo 0) + echo "failed=$FAIL_COUNT" >> "$GITHUB_OUTPUT" + + # ───────────────────────────────────────────── + # Publication du rapport en commentaire de PR + # ───────────────────────────────────────────── + - name: Post performance report as PR comment + if: always() + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_SERVER_URL: ${{ gitea.server_url }} + REPO: ${{ gitea.repository }} + PR_NUMBER: ${{ gitea.event.pull_request.number }} + run: | + REPORT_CONTENT=$(cat /tmp/webzine_endpoint_report.txt 2>/dev/null || echo "Aucun rapport généré.") + FAILED_COUNT="${{ steps.perf_test.outputs.failed }}" + + if [ "${FAILED_COUNT:-0}" -gt 0 ]; then + HEADER="## ❌ Vérification des performances ÉCHOUÉE" + INTRO="${FAILED_COUNT} endpoint(s) ont dépassé 1 seconde ou retourné une erreur serveur." + else + HEADER="## ✅ Vérification des performances RÉUSSIE" + INTRO="Tous les endpoints ont répondu en moins d'une seconde." + fi + + BODY=$(cat < Seuil : **1000ms** | Vérifié par le workflow **PR Endpoint Performance**. + EOF + ) + + curl -s -X POST \ + -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$(jq -n --arg body "$BODY" '{body: $body}')" \ + "$GITEA_SERVER_URL/api/v1/repos/$REPO/issues/$PR_NUMBER/comments" + + # ───────────────────────────────────────────── + # Blocage de la PR si un endpoint a échoué + # ───────────────────────────────────────────── + - name: Enforce performance gate + run: | + FAILED="${{ steps.perf_test.outputs.failed }}" + if [ "${FAILED:-0}" -gt 0 ]; then + echo "❌ PR REJETÉE : ${FAILED} endpoint(s) n'ont pas respecté le seuil d'une seconde." + echo " Corrigez les endpoints lents ou en erreur avant de fusionner." + exit 1 + else + echo "✅ Tous les endpoints ont passé le contrôle de performance." + fi \ No newline at end of file diff --git a/Webzine.WebApplication/Areas/Administration/Views/Artiste/Index.cshtml b/Webzine.WebApplication/Areas/Administration/Views/Artiste/Index.cshtml index 78c392b..73aabac 100644 --- a/Webzine.WebApplication/Areas/Administration/Views/Artiste/Index.cshtml +++ b/Webzine.WebApplication/Areas/Administration/Views/Artiste/Index.cshtml @@ -16,7 +16,7 @@ Nom - Actions + Actions diff --git a/Webzine.WebApplication/Areas/Administration/Views/Artiste/_Form.cshtml b/Webzine.WebApplication/Areas/Administration/Views/Artiste/_Form.cshtml index 3a3101e..f5c84bc 100644 --- a/Webzine.WebApplication/Areas/Administration/Views/Artiste/_Form.cshtml +++ b/Webzine.WebApplication/Areas/Administration/Views/Artiste/_Form.cshtml @@ -2,7 +2,7 @@
-
+
@@ -10,14 +10,15 @@
-
- +
+
- +
+
diff --git a/Webzine.WebApplication/Areas/Administration/Views/Commentaire/Index.cshtml b/Webzine.WebApplication/Areas/Administration/Views/Commentaire/Index.cshtml index 4f696e8..c8608b7 100644 --- a/Webzine.WebApplication/Areas/Administration/Views/Commentaire/Index.cshtml +++ b/Webzine.WebApplication/Areas/Administration/Views/Commentaire/Index.cshtml @@ -14,10 +14,10 @@ Titre - Auteur + Nom Commentaire Date de création - Actions + Actions @@ -25,7 +25,9 @@ { - @commentaire.Titre.Libelle + + @commentaire.Titre.Libelle + @commentaire.Auteur diff --git a/Webzine.WebApplication/Areas/Administration/Views/Dashboard/Index.cshtml b/Webzine.WebApplication/Areas/Administration/Views/Dashboard/Index.cshtml index 861c59b..5498169 100644 --- a/Webzine.WebApplication/Areas/Administration/Views/Dashboard/Index.cshtml +++ b/Webzine.WebApplication/Areas/Administration/Views/Dashboard/Index.cshtml @@ -12,19 +12,19 @@
+ @@ -33,17 +33,17 @@ + @@ -52,19 +52,20 @@ + @@ -72,19 +73,20 @@
+ @@ -94,19 +96,20 @@ asp-controller="Titre" asp-action="Details" asp-route-id="@Model.IdMusiqueLaPlusJouee"> +
-
- +
+ -

+

@Model.MusiqueLaPlusJouee -

+

titre le plus lu

- +
@@ -114,19 +117,20 @@
+ @@ -134,50 +138,57 @@
+
-
- +
-

- @Model.NombreLectures -

+
+ -

- lectures -

+

+ @Model.NombreLectures +

+ +

+ lectures +

+
-
- +
-

+
+ + +

@Model.NombreLikes -

+

likes

+
diff --git a/Webzine.WebApplication/Areas/Administration/Views/Style/Create.cshtml b/Webzine.WebApplication/Areas/Administration/Views/Style/Create.cshtml index 0ac8105..c0cb8f7 100644 --- a/Webzine.WebApplication/Areas/Administration/Views/Style/Create.cshtml +++ b/Webzine.WebApplication/Areas/Administration/Views/Style/Create.cshtml @@ -22,7 +22,7 @@ @* Input *@
- +
@* Bouton *@ diff --git a/Webzine.WebApplication/Areas/Administration/Views/Style/Edit.cshtml b/Webzine.WebApplication/Areas/Administration/Views/Style/Edit.cshtml index e9b763b..446cabe 100644 --- a/Webzine.WebApplication/Areas/Administration/Views/Style/Edit.cshtml +++ b/Webzine.WebApplication/Areas/Administration/Views/Style/Edit.cshtml @@ -25,7 +25,7 @@ @* Input *@
- +
@* Bouton *@ diff --git a/Webzine.WebApplication/Areas/Administration/Views/Style/Index.cshtml b/Webzine.WebApplication/Areas/Administration/Views/Style/Index.cshtml index 4155386..e0141dd 100644 --- a/Webzine.WebApplication/Areas/Administration/Views/Style/Index.cshtml +++ b/Webzine.WebApplication/Areas/Administration/Views/Style/Index.cshtml @@ -20,7 +20,7 @@ Libellé - Actions + Actions @@ -28,11 +28,11 @@ { @foreach (Webzine.Entity.Style style in Model) { - - + + @style.Libelle - + diff --git a/Webzine.WebApplication/Areas/Administration/Views/Titre/_Form.cshtml b/Webzine.WebApplication/Areas/Administration/Views/Titre/_Form.cshtml index ac6eef5..d1f4f8c 100644 --- a/Webzine.WebApplication/Areas/Administration/Views/Titre/_Form.cshtml +++ b/Webzine.WebApplication/Areas/Administration/Views/Titre/_Form.cshtml @@ -62,7 +62,7 @@
- +
@@ -102,13 +102,13 @@
-
+
@Model.NbLectures
-
+
@Model.NbLikes diff --git a/Webzine.WebApplication/Controllers/ApiController.cs b/Webzine.WebApplication/Controllers/ApiController.cs index 2f68337..982d69a 100644 --- a/Webzine.WebApplication/Controllers/ApiController.cs +++ b/Webzine.WebApplication/Controllers/ApiController.cs @@ -28,7 +28,7 @@ public class ApiController : ControllerBase return Ok(new { nom = "webzine", - version = "1.0", + version = "2.0", }); } } \ No newline at end of file diff --git a/Webzine.WebApplication/Views/Accueil/Index.cshtml b/Webzine.WebApplication/Views/Accueil/Index.cshtml index 912021a..49357a2 100644 --- a/Webzine.WebApplication/Views/Accueil/Index.cshtml +++ b/Webzine.WebApplication/Views/Accueil/Index.cshtml @@ -38,7 +38,7 @@

- @titre.Chronique + @(titre.Chronique.Length > 200 ? titre.Chronique.Substring(0, 200) + "..." : titre.Chronique)

diff --git a/Webzine.WebApplication/Views/Artiste/Index.cshtml b/Webzine.WebApplication/Views/Artiste/Index.cshtml index a3de4e3..59716b8 100644 --- a/Webzine.WebApplication/Views/Artiste/Index.cshtml +++ b/Webzine.WebApplication/Views/Artiste/Index.cshtml @@ -58,7 +58,7 @@ else + class="text-primary"> @titre.Libelle diff --git a/Webzine.WebApplication/Views/Contact/Index.cshtml b/Webzine.WebApplication/Views/Contact/Index.cshtml index 1817b16..55ef4d4 100644 --- a/Webzine.WebApplication/Views/Contact/Index.cshtml +++ b/Webzine.WebApplication/Views/Contact/Index.cshtml @@ -4,14 +4,15 @@

Contact

-
+
C.U.C.D.B - DIIAGE
69 Avenue Aristide Briand
21000 Dijon
-
+ +
Phone : 03 80 40 50 60
- secretariat@cucdb.fr + secretariat@cucdb.fr
@@ -21,43 +22,43 @@

Suivez-nous

diff --git a/Webzine.WebApplication/wwwroot/images/avatar.png b/Webzine.WebApplication/wwwroot/images/avatar.png new file mode 100644 index 0000000..42127ef Binary files /dev/null and b/Webzine.WebApplication/wwwroot/images/avatar.png differ diff --git a/scripts/test-endpoints.sh b/scripts/test-endpoints.sh new file mode 100644 index 0000000..146cfcb --- /dev/null +++ b/scripts/test-endpoints.sh @@ -0,0 +1,227 @@ +#!/usr/bin/env bash +# ============================================================================= +# test-endpoints.sh +# Teste tous les endpoints du Webzine et signale ceux qui dépassent le seuil. +# +# Usage : +# ./scripts/test-endpoints.sh [URL_BASE] [MAX_MS] +# +# Exemples : +# ./scripts/test-endpoints.sh # défauts : localhost:5038, 1000ms +# ./scripts/test-endpoints.sh http://localhost:5038 500 +# ============================================================================= + +set -euo pipefail + +BASE_URL="${1:-http://localhost:5038}" +MAX_MS="${2:-1000}" +RAPPORT_FICHIER="/tmp/webzine_rapport_endpoints.txt" +ECHECS=0 +TOTAL=0 + +# ── Couleurs ────────────────────────────────────────────────────────────────── +ROUGE='\033[0;31m' +JAUNE='\033[1;33m' +VERT='\033[0;32m' +CYAN='\033[0;36m' +GRAS='\033[1m' +RESET='\033[0m' + +# ── Fonctions utilitaires ────────────────────────────────────────────────────── +log() { echo -e "$*"; } +succes() { log "${VERT}[OK]${RESET} $*"; } +echec() { log "${ROUGE}[ÉCHEC]${RESET} $*"; ECHECS=$((ECHECS + 1)); } +lent() { log "${JAUNE}[LENT]${RESET} $*"; ECHECS=$((ECHECS + 1)); } +info() { log "${CYAN}$*${RESET}"; } + +# ── verifier_endpoint ───────────────────────────────────────────────────────── +# Arguments : +# $1 Méthode HTTP (GET | POST) +# $2 URL (absolue) +# $3 Libellé (texte lisible) +# $4 Corps (optionnel, pour POST) +# $5 Content-Type (optionnel, défaut : application/x-www-form-urlencoded) +# ───────────────────────────────────────────────────────────────────────────── +verifier_endpoint() { + local METHODE="${1:-GET}" + local URL="$2" + local LIBELLE="$3" + local CORPS="${4:-}" + local TYPE_CONTENU="${5:-application/x-www-form-urlencoded}" + + TOTAL=$((TOTAL + 1)) + + # Exécution de la requête selon la méthode HTTP + if [ "$METHODE" = "POST" ] && [ -n "$CORPS" ]; then + REPONSE=$(curl -s -o /dev/null \ + -w "%{http_code}|%{time_total}" \ + -X POST \ + -H "Content-Type: $TYPE_CONTENU" \ + -d "$CORPS" \ + --max-time 10 \ + "$URL" 2>&1) || REPONSE="000|9.999" + else + REPONSE=$(curl -s -o /dev/null \ + -w "%{http_code}|%{time_total}" \ + -X GET \ + --max-time 10 \ + --location \ + "$URL" 2>&1) || REPONSE="000|9.999" + fi + + # Extraction du code HTTP et du temps de réponse + CODE_HTTP=$(echo "$REPONSE" | cut -d'|' -f1) + TEMPS_TOTAL=$(echo "$REPONSE" | cut -d'|' -f2) + + # Conversion en millisecondes entières — awk évite les problèmes de locale décimale + TEMPS_MS=$(awk "BEGIN {printf \"%.0f\", $TEMPS_TOTAL * 1000}") + + # Évaluation du résultat : erreur serveur, dépassement de seuil ou succès + if [ "${CODE_HTTP:-0}" -ge 500 ] 2>/dev/null; then + echec "$LIBELLE → HTTP $CODE_HTTP (${TEMPS_MS}ms)" + echo "[ÉCHEC] $LIBELLE → HTTP $CODE_HTTP (${TEMPS_MS}ms)" >> "$RAPPORT_FICHIER" + elif [ "${TEMPS_MS:-99999}" -gt "$MAX_MS" ] 2>/dev/null; then + lent "$LIBELLE → ${TEMPS_MS}ms dépasse le seuil de ${MAX_MS}ms" + echo "[LENT] $LIBELLE → ${TEMPS_MS}ms (limite : ${MAX_MS}ms)" >> "$RAPPORT_FICHIER" + else + succes "$LIBELLE → ${TEMPS_MS}ms" + echo "[OK] $LIBELLE → ${TEMPS_MS}ms" >> "$RAPPORT_FICHIER" + fi +} + +# ============================================================================= +# PROGRAMME PRINCIPAL +# ============================================================================= + +# Initialisation du fichier de rapport +> "$RAPPORT_FICHIER" +cat >> "$RAPPORT_FICHIER" </dev/null || echo "$STYLE") + verifier_endpoint GET "$BASE_URL/titre/style/$ENCODE" "GET /titre/style/$STYLE" +done + +log "" +info "── Artiste ───────────────────────────────────────────────" +# Test des pages artiste avec des noms en kebab-case +ARTISTES=("fatal-bazooka" "daft-punk" "justice" "kraftwerk") +for ARTISTE in "${ARTISTES[@]}"; do + verifier_endpoint GET "$BASE_URL/artiste/$ARTISTE" "GET /artiste/$ARTISTE" +done + +log "" +info "── Recherche (POST) ──────────────────────────────────────" +# Test de la recherche plein texte par mots-clés +MOTS=("rock" "jazz" "pop" "metal") +for MOT in "${MOTS[@]}"; do + verifier_endpoint POST "$BASE_URL/recherche" \ + "POST /recherche (mot=$MOT)" \ + "mot=$MOT" +done + +log "" + +# ── SECTION ADMINISTRATION ──────────────────────────────────────────────────── +info "── Administration – Tableau de bord ──────────────────────" +verifier_endpoint GET "$BASE_URL/Administration/Dashboard" "GET /Administration/Dashboard" + +log "" +info "── Administration – Artiste ──────────────────────────────" +verifier_endpoint GET "$BASE_URL/Administration/Artiste" "GET /Administration/Artiste (liste)" +verifier_endpoint GET "$BASE_URL/Administration/Artiste/Create" "GET /Administration/Artiste/Create" +verifier_endpoint GET "$BASE_URL/Administration/Artiste/Edit/1" "GET /Administration/Artiste/Edit/1" +verifier_endpoint GET "$BASE_URL/Administration/Artiste/Delete/1" "GET /Administration/Artiste/Delete/1" + +log "" +info "── Administration – Commentaire ──────────────────────────" +verifier_endpoint GET "$BASE_URL/Administration/Commentaire" "GET /Administration/Commentaire (liste)" +verifier_endpoint GET "$BASE_URL/Administration/Commentaire/Delete/1" "GET /Administration/Commentaire/Delete/1" + +log "" +info "── Administration – Style ────────────────────────────────" +verifier_endpoint GET "$BASE_URL/Administration/Style" "GET /Administration/Style (liste)" +verifier_endpoint GET "$BASE_URL/Administration/Style/Create" "GET /Administration/Style/Create" +verifier_endpoint GET "$BASE_URL/Administration/Style/Edit/1" "GET /Administration/Style/Edit/1" +verifier_endpoint GET "$BASE_URL/Administration/Style/Delete/1" "GET /Administration/Style/Delete/1" + +log "" +info "── Administration – Titre ────────────────────────────────" +verifier_endpoint GET "$BASE_URL/Administration/Titre" "GET /Administration/Titre (liste)" +verifier_endpoint GET "$BASE_URL/Administration/Titre/Create" "GET /Administration/Titre/Create" +verifier_endpoint GET "$BASE_URL/Administration/Titre/Edit/1" "GET /Administration/Titre/Edit/1" +verifier_endpoint GET "$BASE_URL/Administration/Titre/Delete/1" "GET /Administration/Titre/Delete/1" + +# ── RÉCAPITULATIF ───────────────────────────────────────────────────────────── +REUSSIS=$((TOTAL - ECHECS)) + +log "" +info "╔══════════════════════════════════════════════════════════╗" +info "║ Résultats ║" +info "╚══════════════════════════════════════════════════════════╝" +log " Total : ${TOTAL}" +log " ${VERT}Réussis${RESET} : ${REUSSIS}" + +if [ "$ECHECS" -gt 0 ]; then + log " ${ROUGE}Échecs${RESET} : ${ECHECS}" + log "" + log "${ROUGE}${GRAS}❌ ENDPOINTS EN ÉCHEC :${RESET}" + # Affichage de tous les endpoints ayant échoué ou dépassé le seuil + grep -E "^\[(ÉCHEC|LENT)\]" "$RAPPORT_FICHIER" | while IFS= read -r ligne; do + log " ${ROUGE}→${RESET} $ligne" + done + log "" + log "${ROUGE}La PR doit être rejetée. Corrigez les endpoints ci-dessus.${RESET}" +else + log " ${VERT}Échecs${RESET} : 0" + log "" + log "${VERT}${GRAS}✅ Tous les endpoints respectent le seuil de ${MAX_MS}ms.${RESET}" +fi + +log "" +log "Rapport complet enregistré dans : ${RAPPORT_FICHIER}" +log "" + +# Écriture du récapitulatif dans le rapport +cat >> "$RAPPORT_FICHIER" <