Date : 15/02/2026
Status : IMPLEMENTEE
Auditeur : Claude Opus 4.6
Projet : dashboard-33800 (/stock_8to/33800-stack/monitoring/)
Scope : generate.d/.sh, assets/, config.json
Le projet dashboard-33800 est un ensemble de scripts bash generant des pages HTML statiques deployees sur O2switch (https://dashboard.nowhere84.com). L'audit a revele 42 findings repartis en 5 categories : navigation inconstante, CSS fragmente, variables CSS manquantes, hardcoding et problemes JavaScript.
Le probleme principal est l'absence de systeme de navigation unifie : il existe 4 mecanismes de navigation differents cohabitant dans le projet, chacun avec sa propre liste de liens, ce qui garantit des incoherences permanentes a chaque ajout de page.
Le projet contient 4 sources de verite pour la navigation, toutes differentes :
| Source | Fichier | Pages listees |
|---|---|---|
A gen_header() |
_common.sh (L88-114) |
14 liens |
B _gen_header() |
01-index.sh (L17-57) |
17 liens |
C nav.js |
assets/nav.js (L3-23) |
18 liens |
| D Inline hardcode | Chaque module (.sh) | Variable |
Detail des divergences :
| Page | _common.sh (A) | 01-index.sh (B) | nav.js (C) | 02-alerts.sh | 13-models.sh | 18-ai-tools.sh | 19-ai-orchestrator.sh | 20-ai-jobs.sh |
|---|---|---|---|---|---|---|---|---|
| Dashboard | oui | oui | oui | oui | oui | oui | oui | oui |
| Alertes | oui | -- | oui | oui (active) | oui | oui | -- | oui |
| Liens | oui | -- | oui | oui | oui | oui | -- | -- |
| Montages | oui | oui | oui | oui | oui | oui | -- | -- |
| Nginx | oui | oui | oui | oui | oui | oui | -- | -- |
| API | oui | oui | oui | oui | oui | oui | oui | oui |
| Archi | oui | -- | -- | -- | -- | -- | oui | -- |
| Doc | oui | oui | oui | oui | oui | oui | oui | -- |
| Modeles | oui | oui | oui | oui | oui (active) | oui | oui | -- |
| AI Tools | -- | oui | oui | -- | -- | oui (active) | oui | -- |
| AI Orchestrator | -- | oui | oui | -- | -- | -- | -- | -- |
| AI Jobs | oui | -- | oui | -- | -- | oui | oui (active) | oui (active) |
| Secrets | oui | oui | oui | oui | oui | oui | -- | -- |
| Crons | oui | oui | oui | oui | oui | oui | -- | oui |
| RAIDZ | oui | oui | oui | oui | oui | oui | -- | oui |
| Services | oui | oui | oui | oui | oui | oui | -- | oui |
| Syncthing | -- | oui | oui | -- | -- | -- | -- | -- |
| -- | oui | oui | -- | -- | -- | -- | -- | |
| Schema | -- | oui | oui | -- | -- | -- | -- | -- |
| O2switch | -- | oui | oui | -- | -- | -- | -- | -- |
| Claude | oui | -- | -- | -- | -- | -- | -- | -- |
nav.js jamais utiliseLe fichier assets/nav.js (L1-44) fournit un systeme de navigation centralisee via JavaScript (cherche un element #main-nav). Mais aucun module ne genere de <nav id="main-nav">. Ce fichier n'est jamais inclus dans le <script> des pages generees. Il est donc mort.
architecture.html est dans certaines navs mais absente d'autresLa page architecture.html est referencee dans :
_common.sh gen_header (L101) : ouinav.js : non01-index.sh : non19-ai-orchestrator.sh inline : oui14-syncthing.sh inline : ouiclaude.html est uniquement dans _common.shLa page claude.html est generee par 26-claude.sh et apparait uniquement dans la nav _common.sh gen_header (L109). Elle est absente de nav.js, de 01-index.sh, et de toutes les navigations inline des autres modules.
shortlinks.html referencee mais generation non trouvee dans generate.d/Le lien shortlinks.html figure dans la plupart des navigations, mais aucun fichier *shortlinks*.sh n'existe dans generate.d/. La page est peut-etre generee dans le generate.sh principal ou deploye manuellement. Le deploiement SCP dans 01-index.sh (L1372-1374) copie shortlinks.html depuis MONITORING_DIR si existant.
assets/dashboard.css (689 lignes)assets/style.css (1210 lignes)Les 689 premieres lignes de style.css sont identiques a dashboard.css. Puis style.css ajoute ~520 lignes supplementaires (sections 21-30 : metrics, info-rows, VM lists, ZFS pools, crons, etc.).
Probleme : Les pages referent assets/style.css. dashboard.css semble etre un vestige ou une version anterieure non nettoyee.
style.css vs dashboard.css -- divergences mineures dans la partie communeDans la partie commune (sections 1-20), style.css contient quelques differences :
.header-nav : style.css a flex-wrap: wrap; gap: 4px 8px; justify-content: flex-end; vs dashboard.css a gap: var(--space-xs);.header-nav a : style.css a font-size: 0.8rem; white-space: nowrap; vs dashboard.css a font-size: 0.9rem;.nav-secondary : style.css ajoute flex-wrap: wrap;style.css ajoute [id] { scroll-margin-top: 150px; } (L163-169)Cela suggere que style.css est la version active/evoluee et dashboard.css est obsolete.
Plusieurs modules utilisent des variables CSS qui n'existent PAS dans style.css :
| Variable utilisee | Module(s) | Existe dans style.css ? |
|---|---|---|
--bg-secondary |
18-ai-tools.sh (L26), 13-models.sh | NON |
--bg-tertiary |
18-ai-tools.sh (L77), 13-models.sh | NON |
--border-color |
18-ai-tools.sh (L30,38), 13-models.sh | NON |
--text-primary |
18-ai-tools.sh (L42) | NON |
--text-secondary |
13-models.sh (L26,41,56) | NON |
--accent-blue |
18-ai-tools.sh (L89), 13-models.sh (L37) | NON |
--accent-green |
18-ai-tools.sh (L66), 13-models.sh | NON |
--accent-orange |
18-ai-tools.sh (L60), 13-models.sh | NON |
--accent-red |
18-ai-tools.sh (L144) | NON |
--bg-hover |
26-claude.sh (L98) | NON |
--bg-main |
26-claude.sh (L129,153) | NON |
--text-dim |
20-ai-jobs.sh (L339,461) | NON |
--card-bg |
01-index.sh (L730) | NON |
Les variables definies dans style.css sont : --bg-dark, --bg-card, --bg-card-hover, --bg-input, --border, --border-hover, --text, --text-muted, --text-bright, --green, --yellow, --red, --blue, --purple, --orange, --cyan, --space-*, --radius-*, --shadow-*, --header-height.
Consequence : Les proprietes utilisant ces variables non definies sont ignorees par le navigateur, causant un rendu inconsistant (fond transparent, texte invisible, bordures manquantes).
Le module 25-services.sh ne charge PAS assets/style.css. Il integre un design systeme entierement different et autonome (L49-240), avec :
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);#10b981, #ef4444, #60a5fa, etc.)Cette page a un look totalement different de toutes les autres.
# Ligne 13
local API_URL="http://192.168.1.12:5501"
Devrait utiliser get_host_ip "prod-portainer" et get_config ".services.ai-orchestrator.port".
Toute la page ai-tools.html est un heredoc statique, y compris :
192.168.1.30 en dur dans le texte (L226)192.168.1.10:8006, 192.168.1.12:9443, 192.168.1.51:9443Contrairement a 13-models.sh qui applique un sed post-generation pour remplacer les IPs (L246-249), 18-ai-tools.sh ne fait aucune substitution.
Les modules suivants ont des footers avec IPs en dur au lieu d'utiliser gen_footer_full() :
18-ai-tools.sh (L1000-1002)13-models.sh (L203-207) -- corrige par sed post-generation14-syncthing.sh -- pas d'IPs dans le footer13-models.sh contient une table HTML statique de modeles (llama3.2, mistral, qwen2.5:7b, etc.) directement dans le heredoc (L112-137). Ces modeles sont figes au moment de l'ecriture du script. Aucune mise a jour dynamique.
Egalement dans 01-index.sh : la recommendation dans le texte (L143-166, qwen3:4b) ne correspond pas aux modeles de la table de 13-models.sh.
15-linkedin.sh (L16) hardcode BROWSER_PORT=5401. Ce port devrait etre lu depuis config.json. Nota : config.json liste browser-connector sur le port 5401, mais le CLAUDE.md mentionne que le port reel est 5404, pas 5401. Inconsistance potentielle.
// Ligne 650
setInterval(refreshAll, 15000);
La page effectue un refreshAll() toutes les 15s, qui fait 2 appels API a chaque fois (/api/gpu et /api/tools/refresh). Cela represente ~11520 appels/jour pour un utilisateur qui laisse l'onglet ouvert. Le polling devrait idealement utiliser document.visibilityState pour se desactiver quand l'onglet n'est pas visible.
Ligne 49 du heredoc :
<!-- Auto-refresh disabled - using JS polling instead to preserve modal state -->
Mais je n'ai pas trouve de setInterval ou equivalent dans la partie JS de la page (la partie JS est injectee plus loin dans le fichier, au-dela de la ligne 1400 que je n'ai pas lue integralement). Le meta-refresh est desactive mais si aucun polling n'est present, la page est statique apres chargement initial sauf pour le refresh GPU live.
--orange non definie utilisee dans le CSSLigne 523 :
background: linear-gradient(135deg, var(--yellow), var(--orange, #f97316));
La notation var(--orange, #f97316) fournit un fallback, ce qui est correct. Cependant, --orange EST definie dans style.css comme #f0883e, donc le fallback est inutile mais pas nuisible.
Le fichier assets/data-loader.js (L1-142) est un systeme generique de chargement de donnees via data/current.json avec data-binding sur le DOM. Mais aucune page ne l'inclut en <script>. Il utilise des conventions (data-bind, data-format) qu'aucune page n'emploie. Ce fichier est mort.
updateAlertsBadge() reference un element inexistant// Ligne 1239
var badge = document.getElementById('alerts-badge');
Mais aucun element avec id="alerts-badge" n'est genere dans le HTML de 01-index.sh. La fonction countAlerts() est appelee mais le badge n'est jamais mis a jour car l'element n'existe pas.
Le module 01-index.sh fait :
deploy_page "$OUTPUT_FILE" "index.html" (L1363) -- copie dans le repo git pour O2switchscp direct vers nginx (L1373-1378) -- deploiement de shortlinks.html et assets/Cela signifie que la page index va vers O2switch mais que les assets et shortlinks sont aussi pousses vers le nginx local. Ce double deploiement est inconsistant avec le principe "dashboard = O2switch uniquement".
gen_footer_full() dans _common.sh non utiliseLa fonction gen_footer_full() (L124-180) de _common.sh genere un footer dynamique depuis config.json. Mais les modules n'utilisent pas cette fonction :
01-index.sh : genere son propre footer inline (L1318-1357)18-ai-tools.sh : footer hardcode dans le heredoc13-models.sh : footer hardcode dans le heredoc19-ai-orchestrator.sh : footer minimal inline20-ai-jobs.sh : pas de footer visible dans les portions lues25-services.sh : footer minimal inline26-claude.sh : pas de footer (ferme directement le HTML)gen_html_head() dans _common.sh partiellement utiliseeLa fonction gen_html_head() genere un <head> standard avec assets/style.css?v=$(date +%s). Mais la plupart des modules utilisent leur propre heredoc sans appeler cette fonction, avec des timestamps figes (?v=1767876396) ou sans cache-buster du tout.
Le module 25-services.sh ne contient aucun appel a deploy_page() a la fin de sa fonction generate_services(). La page est generee dans $OUTPUT_DIR mais jamais copiee vers le repo git. Elle depend du script parent pour etre deployee.
shortlinks.html : reference dans les navs mais pas de module dans generate.d/gitlab-jobs.html : mentionne dans le contexte utilisateur mais pas de module, pas de reference dans les navsstyle.css version parameter hardcodePlusieurs modules utilisent ?v=1767876396 comme cache-buster (L22 de 02-alerts.sh, 04-secrets.sh, 05-endpoints.sh, etc.) au lieu de ?v=$(date +%s) que la fonction gen_html_head() genererait dynamiquement. Le timestamp 1767876396 correspond a environ juin 2026, donc c'est un timestamp futur force.
nameMap hardcodeLigne 84 de 02-alerts.sh :
const nameMap = { 'pve': 'PVE', 'proxmox1': 'Proxmox', 'docker-prod': 'prod-portainer', ... };
Contrairement a 01-index.sh qui genere le nameMap depuis CONFIG.hosts dynamiquement, 02-alerts.sh le hardcode.
Le <body> HTML commence differemment selon les modules :
_common.sh gen_header : <header class="sticky-header"><div class="header-main">...25-services.sh : <div class="container"><div class="header"><h1>... (pas de sticky-header)19-ai-orchestrator.sh : header inline sans nav secondaire</body> ou </html>Le module 20-ai-jobs.sh fait plus de 1500 lignes. Il contient le CSS (770+ lignes), le HTML statique, la generation bash des job cards, le JavaScript complet, et les formulaires pour tous les outils. Ce fichier devrait etre decoupe.
Ligne 18 :
local device_id=$(ssh ... || echo "SRJERHI-YFX33A4-EOSJJZZ-P37FO7P-JXSA7X7-P3FBPWR-QNWJT4Z-NLBZ5AO")
Le device ID Syncthing est hardcode en fallback. Ce n'est pas un secret (les device IDs sont conçus pour etre partages), mais le hardcoder signifie qu'un changement de device ne se reflete pas dans la page si SSH echoue.
| # | Finding | Impact |
|---|---|---|
| 1.1 | 4 systemes de navigation differents | Pages inaccessibles, liens manquants, UX cassee |
| 2.3 | 14+ variables CSS non definies | Rendu visuel casse sur ai-tools, models, claude, ai-jobs |
| # | Finding | Impact |
|---|---|---|
| 1.2 | nav.js jamais utilise | Code mort |
| 1.4 | claude.html absent de presque toutes les navs | Page orpheline |
| 2.1 | Deux fichiers CSS (dashboard.css = obsolete) | Confusion maintenance |
| 2.4 | services.html design totalement different | Rupture visuelle |
| 3.1 | IP hardcodee dans ai-orchestrator.sh | Cassera si IP change |
| 3.2 | IPs hardcodees dans ai-tools.sh | Cassera si IP change |
| # | Finding | Impact |
|---|---|---|
| 4.1 | Polling 15s sans check visibilite onglet | Charge API inutile |
| 4.4 | data-loader.js jamais utilise | Code mort |
| 4.5 | updateAlertsBadge reference element inexistant | Fonctionnalite silencieusement cassee |
| 5.1 | Double deploiement (O2switch + SCP nginx) | Confusion deploiement |
| 5.2 | gen_footer_full() jamais utilisee | Code mort, footers inconsistants |
| 5.3 | gen_html_head() rarement utilisee | Cache-busters figes |
| # | Finding | Impact |
|---|---|---|
| 6.1 | Cache-buster fige ?v=1767876396 |
Cache navigateur pas invalide |
| 6.2 | nameMap hardcode dans alerts.sh | Maintenance manuelle |
| 6.3 | Template HTML non unifie | Qualite code |
| 6.4 | 20-ai-jobs.sh > 1500 lignes | Maintenabilite |
Choisir UNE SEULE source de verite pour la navigation. Deux options :
gen_header() de _common.sh partout, mettre a jour sa liste de liensnav.js avec un <nav id="main-nav"> standardiseSupprimer les navigations inline hardcodees dans chaque module.
Ajouter dans style.css les aliases manquants :
:root {
--bg-secondary: var(--bg-card);
--bg-tertiary: var(--bg-input);
--bg-hover: var(--bg-card-hover);
--bg-main: var(--bg-dark);
--border-color: var(--border);
--text-primary: var(--text-bright);
--text-secondary: var(--text-muted);
--text-dim: var(--text-muted);
--accent-blue: var(--blue);
--accent-green: var(--green);
--accent-orange: var(--orange);
--accent-red: var(--red);
--accent: var(--cyan);
--card-bg: var(--bg-card);
}
Verifier qu'aucune page ne le reference, puis le supprimer.
Remplacer les IPs en dur dans 18-ai-tools.sh et 19-ai-orchestrator.sh par les fonctions get_host_ip() / get_config().
assets/nav.js (si option A choisie pour R1)assets/data-loader.jsassets/dashboard.cssAjouter <link rel="stylesheet" href="assets/style.css"> et utiliser les classes standard.
visibilitychange au polling (MINEUR)document.addEventListener('visibilitychange', () => {
if (document.hidden) clearInterval(pollInterval);
else pollInterval = setInterval(refreshAll, 15000);
});
| Fichier | Lignes | Lu en entier |
|---|---|---|
generate.d/_common.sh |
196 | oui |
generate.d/01-index.sh |
1393 | oui |
generate.d/02-alerts.sh |
139 | oui |
generate.d/03-doc.sh |
~800 | partiel (30 lignes) |
generate.d/04-secrets.sh |
~400 | partiel (30 lignes) |
generate.d/05-endpoints.sh |
~500 | partiel (30 lignes) |
generate.d/06-montages.sh |
~300 | partiel (30 lignes) |
generate.d/07-nginx.sh |
~400 | partiel (30 lignes) |
generate.d/08-crons.sh |
~300 | partiel (30 lignes) |
generate.d/09-architecture.sh |
~500 | partiel (30 lignes) |
generate.d/12-raidz.sh |
~300 | partiel (30 lignes) |
generate.d/13-models.sh |
272 | oui |
generate.d/14-syncthing.sh |
433 | oui |
generate.d/15-linkedin.sh |
~400 | partiel (50 lignes) |
generate.d/16-o2switch.sh |
~300 | partiel (50 lignes) |
generate.d/17-schema.sh |
~200 | partiel (50 lignes) |
generate.d/18-ai-tools.sh |
1052 | oui |
generate.d/19-ai-orchestrator.sh |
663 | oui |
generate.d/20-ai-jobs.sh |
~1500+ | partiel (1400 lignes) |
generate.d/25-services.sh |
625 | oui |
generate.d/26-claude.sh |
323 | oui |
assets/style.css |
1210 | oui |
assets/dashboard.css |
689 | oui |
assets/nav.js |
44 | oui |
assets/data-loader.js |
142 | oui |
config.json |
229 | oui |
Total : 26 fichiers audites, ~11000 lignes lues.