diff --git a/images/ATTRIBUTIONS.md b/images/ATTRIBUTIONS.md index 2e52ec2..449f51a 100644 --- a/images/ATTRIBUTIONS.md +++ b/images/ATTRIBUTIONS.md @@ -1,8 +1,10 @@ -# Crédits photographiques — tuiles Infrastructure +# Crédits visuels — tuiles du Portail Applicatif -| Fichier | Tuile | Source | Licence | +| Fichier | Tuile | Source | Licence / usage | |---|---|---|---| | `portail.png` | Portail Applicatif | Photo de collaboration en environnement numérique, Unsplash | [Licence Unsplash](https://unsplash.com/license) | | `dashboard.png` | Dashboard Production | Photo de réseau et de supervision technique, Unsplash | [Licence Unsplash](https://unsplash.com/license) | +| `budget-si.jpg` | Gestion budget informatique | Photo « Budgeting Pictures », Unsplash | [Licence Unsplash](https://unsplash.com/license) | +| `studio-portee.png` | Studio Portée | Visuel original généré pour le portail, inspiré du langage éditorial fourni par le client | Usage interne Santinova | -Les photos sont utilisées pour illustrer les deux tuiles d’infrastructure. Aucun logo, texte ni donnée métier n’est incorporé dans ces visuels. +Les visuels sont destinés à illustrer les tuiles applicatives. Ils ne contiennent ni donnée métier ni secret. diff --git a/images/budget-si.jpg b/images/budget-si.jpg new file mode 100644 index 0000000..0735a3c Binary files /dev/null and b/images/budget-si.jpg differ diff --git a/images/studio-portee.png b/images/studio-portee.png index ef8c00b..e437ed0 100644 Binary files a/images/studio-portee.png and b/images/studio-portee.png differ diff --git a/index.html b/index.html index 235914b..fce5575 100644 --- a/index.html +++ b/index.html @@ -402,6 +402,14 @@ var APPS_REFRESH_MS = 300000; var hasRenderedApps = false; + /* Les applications déployées restent issues du dashboard. Cette liste + * ne sert qu'aux exceptions de visibilité explicites du portail prod. */ + var PORTAL_EXCLUDED_APP_IDS = new Set(['santelis-gestion']); + + function isPortalVisible(app) { + return app && !PORTAL_EXCLUDED_APP_IDS.has(app.id); + } + function switchTab(tabName, button) { document.querySelectorAll('.tab-btn').forEach(function(tab) { tab.classList.remove('active'); }); document.querySelectorAll('.tab-panel').forEach(function(panel) { panel.classList.remove('active'); }); @@ -449,7 +457,7 @@ // Le dashboard local est la source de vérité : toutes les catégories // déployées, y compris INFRA, restent publiées dans leur onglet dédié. var deployableApps = apps.filter(function(app) { - return isHttpsUrl(app.urls && app.urls[ENV]); + return isPortalVisible(app) && isHttpsUrl(app.urls && app.urls[ENV]); }); var itinovaApps = deployableApps.filter(function(app) { return app.category === 'ITINOVA'; }); var santinovaApps = deployableApps.filter(function(app) { return app.category === 'SANTINOVA'; }); diff --git a/test/portal-contract.test.js b/test/portal-contract.test.js index 2e2e853..23fc6e1 100644 --- a/test/portal-contract.test.js +++ b/test/portal-contract.test.js @@ -1,21 +1,91 @@ /* - * Contrat de non-régression du portail dynamique. - * Il bloque les marqueurs de conflit, les doublons de configuration - * et le mélange des URLs recette/production. + * Contrat de non-régression du portail de production. + * Il vérifie la séparation stricte des environnements, la publication + * dynamique des applications et les exceptions explicitement demandées. */ const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); +const vm = require('node:vm'); -const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8'); +const root = path.join(__dirname, '..'); +const html = fs.readFileSync(path.join(root, 'index.html'), 'utf8'); +const attributions = fs.readFileSync(path.join(root, 'images', 'ATTRIBUTIONS.md'), 'utf8'); assert.equal(/^(<<<<<<<|=======|>>>>>>>)/m.test(html), false, 'aucun marqueur de conflit Git ne doit être livré'); assert.equal((html.match(/var DASHBOARD_APPS_URL/g) || []).length, 1, 'une seule URL API applications doit être déclarée'); assert.equal((html.match(/var DASHBOARD_STATUS_URL/g) || []).length, 1, 'une seule URL API statuts doit être déclarée'); -assert.match(html, /var ENV = 'prod';/, 'la version recette doit déclarer explicitement son environnement'); +assert.match(html, /var ENV = 'prod';/, 'la version production doit déclarer explicitement son environnement'); assert.match(html, /app\.urls\[ENV\]/, 'les liens doivent être sélectionnés depuis l’environnement déclaré'); -assert.match(html, /app\.category !== 'INFRA'/, 'les tuiles INFRA doivent être filtrées côté portail'); +assert.match(html, /app\.category === 'INFRA'/, 'les tuiles INFRA doivent rester dans leur onglet dédié'); assert.match(html, /var STATUS_REFRESH_MS = 30000;/, 'le délai de grisage doit être explicite'); assert.match(html, /setInterval\(fetchAndApplyStatuses, STATUS_REFRESH_MS\)/, 'le grisage doit être réactualisé périodiquement'); +for (const imageName of ['budget-si.jpg', 'studio-portee.png']) { + const imagePath = path.join(root, 'images', imageName); + assert.equal(fs.existsSync(imagePath), true, `l’asset ${imageName} doit être publié`); + assert.ok(fs.statSync(imagePath).size > 2048, `l’asset ${imageName} ne doit pas être vide`); + assert.match(attributions, new RegExp(imageName.replace('.', '\\.')), `l’attribution de ${imageName} doit être documentée`); +} + +const script = html.match(/