feat: grisage automatique des tuiles selon état conteneurs

This commit is contained in:
manus-admin 2026-06-10 09:08:29 +00:00
parent eefb94a61d
commit d7d6207b6a
1 changed files with 121 additions and 29 deletions

View File

@ -240,13 +240,43 @@
margin-top: 6px; margin-top: 6px;
font-size: 0.7rem; font-size: 0.7rem;
font-weight: 600; font-weight: 600;
color: #10b981; color: #6366f1;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.8px; letter-spacing: 0.8px;
background: rgba(16,185,129,0.1); background: rgba(99,102,241,0.1);
padding: 3px 10px; padding: 3px 10px;
border-radius: 10px; border-radius: 10px;
border: 1px solid rgba(16,185,129,0.2); border: 1px solid rgba(99,102,241,0.2);
}
/* ===== APP OFFLINE (grisage) ===== */
.app-card.offline {
opacity: 0.42;
pointer-events: none;
cursor: not-allowed;
filter: grayscale(80%);
}
.app-card.offline .card-image-wrapper {
box-shadow: none;
}
.card-status-badge {
display: none;
margin-top: 4px;
font-size: 0.68rem;
font-weight: 700;
color: #ef4444;
text-transform: uppercase;
letter-spacing: 0.8px;
background: rgba(239,68,68,0.12);
padding: 2px 9px;
border-radius: 10px;
border: 1px solid rgba(239,68,68,0.3);
}
.app-card.offline .card-status-badge {
display: inline-block;
} }
/* ===== FOOTER ===== */ /* ===== FOOTER ===== */
@ -331,7 +361,7 @@
</button> </button>
<button class="tab-btn" onclick="switchTab('santinova', this)"> <button class="tab-btn" onclick="switchTab('santinova', this)">
Applications Santinova Applications Santinova
<span class="tab-count">2</span> <span class="tab-count">3</span>
</button> </button>
</div> </div>
@ -345,49 +375,54 @@
<p>Cliquez sur une application pour l'ouvrir dans un nouvel onglet</p> <p>Cliquez sur une application pour l'ouvrir dans un nouvel onglet</p>
</div> </div>
<div class="apps-grid"> <div class="apps-grid">
<!-- Itinova Contacts -->
<a class="app-card" href="https://contacts.santinova-soft.org" target="_blank" rel="noopener noreferrer">
<div class="card-image-wrapper">
<img src="images/itinova-contacts.png" alt="Itinova Contacts" />
</div>
<div class="card-name">Itinova Contacts</div>
<div class="card-env-badge">Production</div>
</a>
<!-- Itinova Podcasts -->
<a class="app-card" href="https://podcasts.santinova-soft.org" target="_blank" rel="noopener noreferrer">
<div class="card-image-wrapper">
<img src="images/itinova-podcasts.png" alt="Itinova Podcasts" />
</div>
<div class="card-name">Itinova Podcasts</div>
<div class="card-env-badge">Production</div>
</a>
<!-- Veille Stratégique -->
<a class="app-card" href="https://veille.santinova-soft.org" target="_blank" rel="noopener noreferrer">
<div class="card-image-wrapper">
<img src="images/veille-reglementaire.png" alt="Veille Stratégique" />
</div>
<div class="card-name">Veille Stratégique</div>
<div class="card-env-badge">Production</div>
</a>
<!-- Gestion des Formations --> <!-- Gestion des Formations -->
<a class="app-card" href="https://formations.itinova.org" target="_blank" rel="noopener noreferrer"> <a class="app-card" data-app-id="formation-manager-itinova" href="https://formations.santinova-soft.org" target="_blank" rel="noopener noreferrer">
<div class="card-image-wrapper"> <div class="card-image-wrapper">
<img src="images/formation-manager-itinova.png" alt="Gestion des Formations" /> <img src="images/formation-manager-itinova.png" alt="Gestion des Formations" />
</div> </div>
<div class="card-name">Gestion des Formations</div> <div class="card-name">Gestion des Formations</div>
<div class="card-env-badge">Production</div> <div class="card-env-badge">Production</div>
<div class="card-status-badge">Hors ligne</div>
</a>
<!-- Itinova Contacts -->
<a class="app-card" data-app-id="itinova-contacts" href="https://contacts.santinova-soft.org" target="_blank" rel="noopener noreferrer">
<div class="card-image-wrapper">
<img src="images/itinova-contacts.png" alt="Itinova Contacts" />
</div>
<div class="card-name">Itinova Contacts</div>
<div class="card-env-badge">Production</div>
<div class="card-status-badge">Hors ligne</div>
</a>
<!-- Itinova Podcasts -->
<a class="app-card" data-app-id="itinova-podcasts" href="https://podcasts.santinova-soft.org" target="_blank" rel="noopener noreferrer">
<div class="card-image-wrapper">
<img src="images/itinova-podcasts.png" alt="Itinova Podcasts" />
</div>
<div class="card-name">Itinova Podcasts</div>
<div class="card-env-badge">Production</div>
<div class="card-status-badge">Hors ligne</div>
</a>
<!-- Veille Stratégique -->
<a class="app-card" data-app-id="veille-reglementaire" href="https://veille.santinova-soft.org" target="_blank" rel="noopener noreferrer">
<div class="card-image-wrapper">
<img src="images/veille-reglementaire.png" alt="Veille Stratégique" />
</div>
<div class="card-name">Veille Stratégique</div>
<div class="card-env-badge">Production</div>
<div class="card-status-badge">Hors ligne</div>
</a> </a>
<!-- Itinova Gestion de Flotte --> <!-- Itinova Gestion de Flotte -->
<a class="app-card" href="https://flotte.santinova-soft.org" target="_blank" rel="noopener noreferrer"> <a class="app-card" data-app-id="itinova-vehicle-exchange" href="https://flotte.santinova-soft.org" target="_blank" rel="noopener noreferrer">
<div class="card-image-wrapper"> <div class="card-image-wrapper">
<img src="images/itinova-flotte.png" alt="Itinova Gestion de Flotte" /> <img src="images/itinova-flotte.png" alt="Itinova Gestion de Flotte" />
</div> </div>
<div class="card-name">Itinova Gestion de Flotte</div> <div class="card-name">Itinova Gestion de Flotte</div>
<div class="card-env-badge">Production</div> <div class="card-env-badge">Production</div>
<div class="card-status-badge">Hors ligne</div>
</a> </a>
</div> </div>
@ -400,25 +435,38 @@
<p>Cliquez sur une application pour l'ouvrir dans un nouvel onglet</p> <p>Cliquez sur une application pour l'ouvrir dans un nouvel onglet</p>
</div> </div>
<div class="apps-grid"> <div class="apps-grid">
<!-- SONUM -->
<a class="app-card" href="https://sonum.santinova-soft.org" target="_blank" rel="noopener noreferrer"> <!-- Sonum -->
<a class="app-card" data-app-id="sonum" href="https://sonum.santinova-soft.org" target="_blank" rel="noopener noreferrer">
<div class="card-image-wrapper"> <div class="card-image-wrapper">
<img src="images/sonum.png" alt="SONUM" /> <img src="images/sonum.png" alt="Sonum" />
</div> </div>
<div class="card-name">SONUM</div> <div class="card-name">Sonum</div>
<div class="card-env-badge">Production</div> <div class="card-env-badge">Production</div>
<div class="card-status-badge">Hors ligne</div>
</a> </a>
<!-- Démat Facturation DSI --> <!-- Facturation Santinova -->
<a class="app-card" href="https://demat-facturation.santinova-soft.org" target="_blank" rel="noopener noreferrer"> <a class="app-card" data-app-id="facturation-santinova" href="https://facturation.santinova-soft.org" target="_blank" rel="noopener noreferrer">
<div class="card-image-wrapper">
<img src="images/facturation.png" alt="Facturation Santinova" />
</div>
<div class="card-name">Facturation Santinova</div>
<div class="card-env-badge">Production</div>
<div class="card-status-badge">Hors ligne</div>
</a>
<!-- Démat Facturation DSI -->
<a class="app-card" data-app-id="demat-facturation" href="https://demat-facturation.santinova-soft.org" target="_blank" rel="noopener noreferrer">
<div class="card-image-wrapper"> <div class="card-image-wrapper">
<img src="images/demat-facturation-dsi.jpg" alt="Démat Facturation DSI" /> <img src="images/demat-facturation-dsi.jpg" alt="Démat Facturation DSI" />
</div> </div>
<div class="card-name">Démat Facturation DSI</div> <div class="card-name">Démat Facturation DSI</div>
<div class="card-env-badge">Production</div> <div class="card-env-badge">Production</div>
</a> <div class="card-status-badge">Hors ligne</div>
</a>
</div> </div>
</div> </div>
</main> </main>
@ -434,8 +482,10 @@
<script> <script>
// URL de l'API de statut du dashboard (même serveur, port 3001)
var DASHBOARD_STATUS_URL = 'https://dashboard.santinova-soft.org/api/public/status';
function updateTabCounts() { function updateTabCounts() {
// Compter les app-card dans chaque panel et mettre à jour les badges
document.querySelectorAll('.tab-btn').forEach(function(btn) { document.querySelectorAll('.tab-btn').forEach(function(btn) {
var onclick = btn.getAttribute('onclick') || ''; var onclick = btn.getAttribute('onclick') || '';
var match = onclick.match(/switchTab\('(\w+)'/); var match = onclick.match(/switchTab\('(\w+)'/);
@ -456,10 +506,52 @@
document.getElementById('panel-' + tabName).classList.add('active'); document.getElementById('panel-' + tabName).classList.add('active');
} }
// Mise à jour au chargement /**
document.addEventListener('DOMContentLoaded', updateTabCounts); * Met à jour l'état visuel des tuiles en fonction des statuts reçus du dashboard.
* Une tuile est grisée si son statut est 'offline' ou si le conteneur n'est pas en cours d'exécution.
*/
function applyStatuses(statuses) {
var cards = document.querySelectorAll('.app-card[data-app-id]');
cards.forEach(function(card) {
var appId = card.getAttribute('data-app-id');
var appStatus = statuses.find(function(s) { return s.id === appId; });
if (appStatus) {
var isOffline = appStatus.status === 'offline' || !appStatus.containerRunning;
if (isOffline) {
card.classList.add('offline');
} else {
card.classList.remove('offline');
}
}
});
}
/**
* Interroge le dashboard pour obtenir les statuts et met à jour les tuiles.
*/
function fetchAndApplyStatuses() {
fetch(DASHBOARD_STATUS_URL, { cache: 'no-store' })
.then(function(response) {
if (!response.ok) throw new Error('HTTP ' + response.status);
return response.json();
})
.then(function(statuses) {
applyStatuses(statuses);
})
.catch(function(err) {
// En cas d'erreur (dashboard inaccessible), on ne grise rien
console.warn('[Portail] Impossible de récupérer les statuts:', err.message);
});
}
document.addEventListener('DOMContentLoaded', function() {
updateTabCounts();
// Premier appel immédiat
fetchAndApplyStatuses();
// Polling toutes les 30 secondes
setInterval(fetchAndApplyStatuses, 30000);
});
</script> </script>
</body> </body>
</html> </html>