Skip to content

Interface Web de Gestion MkDocs

Ce guide décrit l'installation et le fonctionnement d'une interface web locale permettant d’éditer les pages MkDocs sans passer par la ligne de commande.


🎯 Objectif

  • Modifier les fichiers .md de MkDocs directement depuis un navigateur.
  • Gérer les pages (édition, création, suppression).
  • Possibilité future : lancer mkdocs serve ou mkdocs build depuis l'interface.

🧰 Prérequis

Avoir un mkdocs, ce tutoriel sera appuyer sur le tuto [[installation_mkdocs_linux]]


🖥️ Étapes d'installation

1. Installer Python

apt install python3-venv -y

2. Créer un dossier pour l'interface

mkdir -p /home/mkdox/interface
cd /home/mkdox/interface

3. Créer un environnement Python

python3 -m venv venv
source venv/bin/activate

4. Installer Flask

pip install flask

5. Créer le fichier app.py

Créer un fichier app.py avec ce contenue

nano app.py

XXXX
ligne 6, mettre le chemin vers le dossier docs

6. Lancer l'application

Toujours dans le dossier interface, lance :

source venv/bin/activate
python app.py

erreur

WARNING: This is a development server. Do not use it in a production deployment.

🔒 Ce message est normal : Flask te prévient juste que son serveur n’est pas prévu pour la production web. Mais pour un usage local ou interne à ton réseau, comme dans ton cas, aucun souci

Ouvre ensuite ton navigateur sur :

http://localhost:5000

Automatisé le démarrage

créer un scrip dans le dossier interface

nano mkdocs-interface-setup.sh
copier ensuite son contenue
#!/bin/bash

SERVICE_NAME="mkdocs-interface"
WORKDIR="/.../interface" # remplacer par le chemin
PYTHON=/.../interface/venv/bin/python # remplacer par le chemin
APP="$WORKDIR/app.py"
SERVICE_PATH="/etc/systemd/system/${SERVICE_NAME}.service"

echo "🔧 Création du service ${SERVICE_NAME}..."

# Créer le fichier systemd
bash -c "cat > $SERVICE_PATH" <<EOF
[Unit]
Description=Interface Web MkDocs
After=network.target

[Service]
User=root
WorkingDirectory=/.../interface # remplacer par le chemin
ExecStart=/.../interface/venv/bin/python -m flask run --host=0.0.0.0 --port=5000 # remplacer par le chemin
Environment=FLASK_APP=app.py
Environment=FLASK_ENV=production
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

echo "✅ Fichier de service créé à $SERVICE_PATH"

# Recharger systemd
systemctl daemon-reexec # ajouter sudo si user
systemctl daemon-reload # ajouter sudo si user

# Activer et démarrer le service
systemctl enable $SERVICE_NAME # ajouter sudo si user
systemctl start $SERVICE_NAME # ajouter sudo si user

# Afficher le statut
echo "🚀 Lancement du service..."
sleep 1
systemctl status $SERVICE_NAME --no-pager # ajouter sudo si user
Modifier la ligne 4, 5, 19 et 20 pour mettre le chemin vers interface

il faut ensuite lui donner les droits :

chmod +x mkdocs-interface-setup.sh

le lancer :

./mkdocs-interface-setup.sh

Pour mettre a jour l'interface après changement

systemctl restart mkdocs-interface

Désinstaller (à ne pas faire)

systemctl disable mkdocs-interface
rm /etc/systemd/system/mkdocs-interface.service
rm /home/CHEMIN_VERS/.../interface

PREMIUM

nano /.../interface/app.py
from flask import Flask, render_template, request, jsonify, redirect, url_for
from markupsafe import Markup
import os
import yaml
import subprocess

app = Flask(__name__)

BASE_DIR = "/home/mkdox/spider"
DOCS_PATH = os.path.join(BASE_DIR, "docs")
MKDOCS_YML = os.path.join(BASE_DIR, "mkdocs.yml")
UPDATE_SCRIPT = "/usr/local/bin/mkdocs-update"

# ------------------------------
# UTILS
# ------------------------------

def parse_nav(nav, indent=0):
    result = []
    for item in nav:
        if isinstance(item, dict):
            for key, value in item.items():
                if isinstance(value, str):
                    result.append("  " * indent + value)
                elif isinstance(value, list):
                    result.append({key: parse_nav(value, indent + 1)})
    return result

# ------------------------------
# ROUTES
# ------------------------------

@app.route("/")
def index():
    with open(MKDOCS_YML, "r", encoding="utf-8") as f:
        config = yaml.safe_load(f)
        nav = config.get("nav", [])
        structure = parse_nav(nav)
    return render_template("index.html", structure=structure)

@app.route("/edit/<path:filename>", methods=["GET", "POST"])
def edit(filename):
    full_path = os.path.join(DOCS_PATH, filename)
    if request.method == "POST":
        new_name = request.form.get("new_filename")
        if new_name and new_name != filename:
            new_path = os.path.join(DOCS_PATH, new_name)
            os.makedirs(os.path.dirname(new_path), exist_ok=True)
            os.rename(full_path, new_path)
            full_path = new_path
            filename = new_name
        with open(full_path, "w", encoding="utf-8") as f:
            f.write(request.form["content"])
        return redirect(url_for("index"))
    with open(full_path, "r", encoding="utf-8") as f:
        content = f.read()
    return render_template("edit.html", filename=filename, content=content)

@app.route("/deploy", methods=["POST"])
def deploy():
    try:
        output = subprocess.check_output(["/bin/bash", "-c", f"cd {BASE_DIR} && {UPDATE_SCRIPT}"], stderr=subprocess.STDOUT)
        return jsonify({"status": "ok", "output": output.decode("utf-8")})
    except subprocess.CalledProcessError as e:
        return jsonify({"status": "error", "output": e.output.decode("utf-8")}), 500

@app.route("/new_file", methods=["POST"])
def new_file():
    filename = request.form["filename"]
    full_path = os.path.join(DOCS_PATH, filename)
    os.makedirs(os.path.dirname(full_path), exist_ok=True)
    with open(full_path, "w", encoding="utf-8") as f:
        f.write("# Nouveau fichier\n")
    return redirect(url_for("index"))

@app.route("/new_folder", methods=["POST"])
def new_folder():
    folder = request.form["folder"]
    full_path = os.path.join(DOCS_PATH, folder)
    os.makedirs(full_path, exist_ok=True)
    return redirect(url_for("index"))
nano /.../interface/templates/index.html
<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>Structure MkDocs</title>
  <style>
    body {
      font-family: sans-serif;
      padding: 20px;
    }
    ul {
      list-style-type: none;
      padding-left: 0;
    }
    .folder, .file {
      margin: 4px 0;
      padding: 6px 10px;
      border: 1px solid #ccc;
      background: #f9f9f9;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .folder {
      font-weight: bold;
      background: #e0e0ff;
    }
    .indent-0 { margin-left: 0px; }
    .indent-1 { margin-left: 20px; }
    .indent-2 { margin-left: 40px; }
    .indent-3 { margin-left: 60px; }
    .indent-4 { margin-left: 80px; }
    button {
      margin-left: 10px;
    }
  </style>
</head>
<body>
  <h1>✨ Gestion de la structure MkDocs</h1>

  <ul>
    {% macro render_item(item, indent=0) %}
      {% if item is string %}
        <li class="file indent-{{ indent }}">
          📄 {{ item }}
          <span>
            <a href="/edit/{{ item }}"><button>✏️ Modifier</button></a>
          </span>
        </li>
      {% elif item is mapping %}
        {% for key, children in item.items() %}
          <li class="folder indent-{{ indent }}">📁 {{ key }}</li>
          <ul>
            {% for child in children %}
              {{ render_item(child, indent + 1) }}
            {% endfor %}
          </ul>
        {% endfor %}
      {% endif %}
    {% endmacro %}

    {% for item in structure %}
      {{ render_item(item, 0) }}
    {% endfor %}
  </ul>

  <br>
  <form action="/new_file" method="post">
    <input type="text" name="filename" placeholder="Nouveau fichier .md">
    <button type="submit">📄 Créer un fichier</button>
  </form>

  <form action="/new_folder" method="post">
    <input type="text" name="folder" placeholder="Nouveau dossier">
    <button type="submit">📁 Créer un dossier</button>
  </form>

  <br>
  <form id="deployForm" method="post" action="/deploy">
    <button type="submit">🚀 Déployer</button>
  </form>

  <script>
    document.getElementById("deployForm").addEventListener("submit", async function(e) {
      e.preventDefault();
      const res = await fetch("/deploy", { method: "POST" });
      const data = await res.json();
      if (data.status === "ok") {
        alert("✅ Déploiement réussi !");
      } else {
        alert("❌ Erreur :\n" + data.output);
      }
    });
  </script>
</body>
</html>
nano /.../interface/templates/edit.html
<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>Modifier {{ filename }}</title>
  <style>
    body {
      font-family: sans-serif;
      padding: 20px;
    }
    textarea {
      width: 100%;
      height: 80vh;
      font-family: monospace;
      font-size: 14px;
    }
    input[type="text"] {
      width: 100%;
      padding: 6px;
      margin-bottom: 10px;
    }
  </style>
</head>
<body>
  <h1>✏️ Modifier {{ filename }}</h1>
  <form method="POST">
    <label>Nom du fichier (relatif à /docs) :</label>
    <input type="text" name="new_filename" value="{{ filename }}">
    <textarea name="content">{{ content }}</textarea><br><br>
    <button type="submit">💾 Enregistrer</button>
    <a href="{{ url_for('index') }}">⬅ Retour</a>
  </form>
</body>
</html>

mettre à jour

systemctl restart mkdocs-interface

Créé pour le projet Spider Web — Corentin VARACHAUD