Déploiement, maintenance et extension
Ce document décrit l'architecture de la plateforme, son déploiement sur Apache, sa maintenance et les procédures d'extension : contenu, langue, module, formation et passage à un backend.
1. Architecture générale
La plateforme est une application web entièrement statique. Aucun processus serveur ne lui est nécessaire : Apache sert des fichiers, le navigateur fait le reste.
- Rendu : HTML5, CSS3 et JavaScript en modules ES natifs. Aucun bundler, aucun gestionnaire de paquets, aucune dépendance externe ni CDN.
- Routage : par fragment d'URL (
#/module/rcs). Le serveur ne voit jamais les chemins applicatifs, donc aucune règle de réécriture n'est indispensable. Le site fonctionne à l'identique derrière une adresse IP et derrière un domaine. - Données : découplées dans
/data/*.jsonet lues via une couche d'abstraction unique,assets/js/core/dataService.js. - État utilisateur : progression, scores, langue et thème sont conservés dans
localStorage, derrière le modulecore/store.js. Rien n'est transmis. - Visualisations : chaque outil calcule réellement les équations du domaine et dessine en Canvas 2D ou en SVG. Aucune image de graphique n'est préfabriquée.
- Sécurité frontend : pas de
eval, pas deinnerHTMLsur des données non filtrées (esc()etsanitizeInline()danscore/utils.js), aucun secret côté client.
2. Arborescence des fichiers
index.html point d'entrée unique, à la racine du site .htaccess compression, cache, en-têtes de sécurité, MIME robots.txt / sitemap.xml référencement assets/ css/ tokens.css base.css layout.css components.css pages.css viz.css js/ app.js amorçage et déclaration des routes core/ router, i18n, theme, store, dataService, utils components/ header, blocks, search, quiz pages/ une page par route viz/ plot.js + registre + implémentations i18n/ fr.json en.json dictionnaires d'interface icons/ favicon.svg og-image.svg fonts/ polices auto-hébergées (facultatif) images/ data/ contenu : modules, parcours, glossaire, quiz, fiches downloads/ guides HTML imprimables et PDF docs/ ce document config/ manifest.webmanifest components/ modules/ pages/ points d'extension réservés (voir README)
3. Installation
Copier le contenu du dossier dans la racine web d'Apache. Sur Debian ou Ubuntu :
sudo apt install apache2
sudo rsync -a --delete ./site/ /var/www/html/
sudo chown -R www-data:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;
sudo service apache2 start # ou : sudo systemctl enable --now apache2
Vérification immédiate : curl -I http://localhost/ doit répondre
200 et curl -s http://localhost/data/radar.json | head doit
renvoyer du JSON. Si le JSON est servi en text/plain, le module
mod_mime ou le .htaccess n'est pas pris en compte.
4. Configuration d'Apache
Le fichier .htaccess fourni suffit, à condition qu'Apache l'autorise.
Activer les modules utiles puis permettre les surcharges :
sudo a2enmod deflate expires headers rewrite mime sudo nano /etc/apache2/sites-available/000-default.conf
<VirtualHost *:80>
ServerName radar-ew.exemple.fr
DocumentRoot /var/www/html
<Directory /var/www/html>
Options -Indexes +FollowSymLinks
AllowOverride All # indispensable pour .htaccess
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/radar-ew-error.log
CustomLog ${APACHE_LOG_DIR}/radar-ew-access.log combined
</VirtualHost>
sudo apache2ctl configtest sudo service apache2 reload
En production, recopier le contenu du .htaccess directement dans le bloc
<Directory> et passer AllowOverride None : Apache évite ainsi
de relire le fichier à chaque requête.
5. Accès par adresse IP ou nom de domaine
Aucune adaptation n'est nécessaire. Tous les chemins du projet sont relatifs et le routage passe par le fragment d'URL :
| Contexte | URL | Action |
|---|---|---|
| Adresse IP | http://192.0.2.10/ | aucune |
| Sous-dossier | http://192.0.2.10/radar/ | aucune : les chemins sont résolus sur document.baseURI |
| Nom de domaine | https://radar-ew.exemple.fr/ | enregistrement DNS de type A vers l'IP, puis ServerName dans le VirtualHost |
6. Mise en place de HTTPS
sudo apt install certbot python3-certbot-apache
sudo certbot --apache -d radar-ew.exemple.fr
sudo systemctl status certbot.timer # renouvellement automatique
Une fois le certificat validé et le site accessible en HTTPS, décommenter la ligne
Strict-Transport-Security du .htaccess. La politique de sécurité
du contenu fournie n'autorise que les ressources du site lui-même ; si vous ajoutez une
ressource externe, elle devra être déclarée explicitement.
7. Maintenance courante
- Mise à jour du contenu : remplacer les fichiers de
/datapuis vider le cache navigateur. Les JSON sont mis en cache une heure par défaut. - Validation systématique avant publication :
python3 -m json.tool data/radar.json > /dev/nullsur chaque fichier. - Sauvegarde : le site est constitué uniquement de fichiers ; une archive du
dossier suffit (
tar czf sauvegarde.tgz /var/www/html). - Journalisation :
tail -f /var/log/apache2/radar-ew-error.log. - Régénération des guides :
python3 generate_guides.pypuiswkhtmltopdfpour les versions PDF. Les guides sont construits à partir des mêmes JSON : aucune resaisie n'est nécessaire.
8. Ajouter du contenu
Un module de connaissance est un objet du tableau modules dans
data/radar.json, data/electronic_warfare.json ou
data/modeling.json :
{
"id": "mon-module",
"cat": "radar", radar | ew | modeling
"levels": ["manager", "expert"],
"duration": 25,
"tags": ["antenne", "gain"],
"title": { "fr": "...", "en": "..." },
"summary": { "fr": "...", "en": "..." },
"source": { "ref": "eGuide p. 12", "doc": "eguide" },
"blocks": [
{ "type": "p", "fr": "...", "en": "..." },
{ "type": "list", "label": {...}, "fr": ["..."], "en": ["..."] },
{ "type": "eq", "label": {...}, "html": "...", "vars": [{ "s": "G", "fr": "...", "en": "..." }] },
{ "type": "table","head": { "fr": [...], "en": [...] }, "rows": [[...]] },
{ "type": "note" | "example" | "complement", "fr": "...", "en": "..." },
{ "type": "viz", "viz": "antenna-pattern" }
]
}
Types de blocs disponibles : p, list, steps,
eq, table, note, example,
complement, viz. Le bloc complement est réservé aux
apports qui ne proviennent pas des documents sources ; il est affiché comme tel.
Le module devient immédiatement visible dans la recherche et le glossaire des tags ;
pour l'inscrire dans un parcours, ajouter son identifiant aux units du
chapitre voulu.
9. Ajouter une langue
- Copier
assets/i18n/fr.jsonenassets/i18n/es.jsonet traduire les valeurs, sans toucher aux clés. - Déclarer la langue dans
assets/js/core/i18n.js:AVAILABLE = [{ code: 'fr', ... }, { code: 'en', ... }, { code: 'es', label: 'Español' }]. - Ajouter la clé correspondante dans les objets de contenu des fichiers
/data({ "fr": ..., "en": ..., "es": ... }). En son absence, la fonctionpick()retombe automatiquement sur le français : le site reste fonctionnel pendant la traduction.
Le bouton de langue de l'en-tête parcourt la liste AVAILABLE ; aucune autre
modification n'est nécessaire.
10. Ajouter un module de visualisation
- Écrire une fabrique dans un fichier de
assets/js/viz/en utilisant le cadre commun :import { vizFrame } from './plot.js'; export function monOutil() { return vizFrame({ id: 'mon-outil', title: { fr: '...', en: '...' }, caption: { fr: '...', en: '...' }, controls: [ { key: 'gain', label: { fr: 'Gain', en: 'Gain' }, min: 0, max: 40, step: 1, value: 20, unit: 'dB' } ], draw(p, plot) { const points = []; // calcul réel plot.setRange({ xMin: 0, xMax: 10, yMin: -40, yMax: 10 }) .clear().axes({ xLabel: 'x', yLabel: 'dB' }).line(points); return [{ label: { fr: 'Résultat', en: 'Result' }, value: '…' }]; } }); } - L'inscrire dans
assets/js/viz/index.js(titre, description, catégorie, module lié, import dynamique). - La référencer depuis un module de contenu avec un bloc
{ "type": "viz", "viz": "mon-outil" }. Elle apparaît alors dans la galerie et dans le module, sans autre intervention.
11. Ajouter un parcours de formation
- Créer
data/analyste.jsonsur le modèle dedata/operator.json:id,code,title,tagline,description,prerequisites,objectives,chapters[{ title, units[] }],quizzes[]. - Déclarer le fichier dans
ENDPOINTSpuis dansgetPaths()(assets/js/core/dataService.js). - Ajouter les quiz correspondants dans
data/quiz.jsonavec le champlevelégal à l'identifiant du parcours. - Compléter les libellés
common.<id>etdocs.guide<Id>dans les dictionnaires i18n, ainsi que la couleur de niveau danscomponents.css(.path-card[data-level="analyste"]). - Régénérer les guides : ajouter l'entrée dans
TITLESdegenerate_guides.py.
12. Passer à un backend
Deux modules concentrent tous les accès externes ; aucune page ne connaît le format de transport.
| Besoin | Fichier à modifier | Nature du changement |
|---|---|---|
| Contenu servi par une API REST | core/dataService.js |
Remplacer les chemins de ENDPOINTS par des URL d'API et, si besoin,
ajouter l'authentification dans request(). Les fonctions exposées
(getModules, getPaths…) conservent leur signature. |
| Progression synchronisée entre appareils | core/store.js |
Remplacer read() et write() par des appels réseau et
rendre asynchrones les fonctions exportées ; les appelants utilisent déjà des
accesseurs, pas localStorage directement. |
| Recherche côté serveur | components/search.js |
Remplacer search() par un appel à l'index distant en respectant la
forme de retour { modules, glossary, sheets, total }. |
Si un backend est introduit, ajuster la directive
Content-Security-Policy du .htaccess : la valeur actuelle
connect-src 'self' interdit tout appel vers un autre domaine.