309 lines
No EOL
12 KiB
HTML
309 lines
No EOL
12 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Status Système - GMAO{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid py-4">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h1><i class="bi bi-speedometer2"></i> Status du Système</h1>
|
|
<div>
|
|
<span id="refresh-timer" class="text-muted me-2" style="font-size:0.85rem;"></span>
|
|
<button id="btn-refresh" onclick="refreshAll()" class="btn btn-outline-light">
|
|
<i class="bi bi-arrow-clockwise"></i> Actualiser
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loader -->
|
|
<div id="loader" class="text-center py-5">
|
|
<div class="spinner-border text-light" role="status" style="width:3rem;height:3rem;">
|
|
<span class="visually-hidden">Chargement...</span>
|
|
</div>
|
|
<p class="mt-3 text-muted">Vérification des connexions en temps réel...</p>
|
|
</div>
|
|
|
|
<!-- Contenu (caché pendant le chargement) -->
|
|
<div id="content" style="display:none;">
|
|
|
|
<!-- Résumé -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card bg-dark text-white h-100">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title"><i class="bi bi-hdd-stack"></i> Services</h5>
|
|
<p class="display-4 mb-0" id="total-services">-</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card bg-info text-white h-100">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title"><i class="bi bi-check-circle"></i> Configurés</h5>
|
|
<p class="display-4 mb-0" id="configured-count">-</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-white h-100" id="connected-card">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title"><i class="bi bi-plug"></i> Connectés</h5>
|
|
<p class="display-4 mb-0" id="connected-count">-</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-white h-100" id="watchdog-card">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title"><i class="bi bi-shield-exclamation"></i> Watchdogs</h5>
|
|
<p class="display-4 mb-0" id="watchdog-count">-</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Services -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0"><i class="bi bi-hdd-stack"></i> État des services</h5>
|
|
<small id="last-check" class="text-muted"></small>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-dark table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Service</th>
|
|
<th>Configuration</th>
|
|
<th>Connexion</th>
|
|
<th>Détails</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="services-body">
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Watchdogs -->
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0"><i class="bi bi-shield-exclamation"></i> Watchdogs</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-dark table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Watchdog</th>
|
|
<th>Statut</th>
|
|
<th>PID</th>
|
|
<th>Détails</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="watchdogs-body">
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Liens rapides -->
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0"><i class="bi bi-link-45deg"></i> Configuration rapide</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-2 mb-2">
|
|
<a href="{{ url_for('outlook_pages.index') }}" class="btn btn-outline-info w-100">
|
|
<i class="bi bi-envelope"></i> Outlook
|
|
</a>
|
|
</div>
|
|
<div class="col-md-2 mb-2">
|
|
<a href="{{ url_for('ent.index') }}" class="btn btn-outline-info w-100">
|
|
<i class="bi bi-person-badge"></i> ENT
|
|
</a>
|
|
</div>
|
|
<div class="col-md-2 mb-2">
|
|
<a href="{{ url_for('pronote.connect') }}" class="btn btn-outline-info w-100">
|
|
<i class="bi bi-calendar-week"></i> PRONOTE
|
|
</a>
|
|
</div>
|
|
<div class="col-md-2 mb-2">
|
|
<a href="{{ url_for('yeastar.config_page') }}" class="btn btn-outline-info w-100">
|
|
<i class="bi bi-telephone"></i> Yeastar
|
|
</a>
|
|
</div>
|
|
<div class="col-md-2 mb-2">
|
|
<a href="{{ url_for('gmao_config.index') }}" class="btn btn-outline-info w-100">
|
|
<i class="bi bi-gear"></i> GMAO
|
|
</a>
|
|
</div>
|
|
<div class="col-md-2 mb-2">
|
|
<a href="{{ url_for('logs.index') }}" class="btn btn-outline-info w-100">
|
|
<i class="bi bi-journal-text"></i> Logs
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- fin #content -->
|
|
</div>
|
|
|
|
<script>
|
|
const ICONS = {
|
|
'bi-database': 'base de données',
|
|
'bi-envelope': 'email',
|
|
'bi-person-badge': 'ENT',
|
|
'bi-calendar-week': 'PRONOTE',
|
|
'bi-telephone': 'téléphonie',
|
|
'bi-gear': 'configuration'
|
|
};
|
|
|
|
let refreshInterval = null;
|
|
let countdown = 30;
|
|
|
|
function renderServices(systems) {
|
|
const tbody = document.getElementById('services-body');
|
|
tbody.innerHTML = '';
|
|
systems.forEach(s => {
|
|
let configBadge, connBadge;
|
|
if (s.configured) {
|
|
configBadge = '<span class="badge bg-success"><i class="bi bi-check"></i> Configuré</span>';
|
|
} else {
|
|
configBadge = '<span class="badge bg-secondary"><i class="bi bi-x"></i> Non configuré</span>';
|
|
}
|
|
if (s.connected) {
|
|
connBadge = '<span class="badge bg-success"><i class="bi bi-plug-fill"></i> Connecté</span>';
|
|
} else if (s.configured) {
|
|
connBadge = '<span class="badge bg-danger"><i class="bi bi-plug"></i> Déconnecté</span>';
|
|
} else {
|
|
connBadge = '<span class="badge bg-secondary"><i class="bi bi-dash"></i> N/A</span>';
|
|
}
|
|
tbody.innerHTML += `<tr>
|
|
<td><i class="bi ${s.icon} me-2"></i><strong>${s.name}</strong></td>
|
|
<td>${configBadge}</td>
|
|
<td>${connBadge}</td>
|
|
<td><small>${s.message}</small></td>
|
|
</tr>`;
|
|
});
|
|
}
|
|
|
|
function renderWatchdogs(watchdogs) {
|
|
const tbody = document.getElementById('watchdogs-body');
|
|
tbody.innerHTML = '';
|
|
watchdogs.forEach(w => {
|
|
let statusBadge;
|
|
if (w.running) {
|
|
statusBadge = '<span class="badge bg-success"><i class="bi bi-circle-fill"></i> Actif</span>';
|
|
} else {
|
|
statusBadge = '<span class="badge bg-secondary"><i class="bi bi-circle"></i> Inactif</span>';
|
|
}
|
|
const pid = w.pid ? `<code>${w.pid}</code>` : '<span class="text-muted">—</span>';
|
|
tbody.innerHTML += `<tr>
|
|
<td><i class="bi bi-gear me-2"></i>${w.name}</td>
|
|
<td>${statusBadge}</td>
|
|
<td>${pid}</td>
|
|
<td><small>${w.message}</small></td>
|
|
</tr>`;
|
|
});
|
|
}
|
|
|
|
function updateSummary(data) {
|
|
const total = data.systems.length;
|
|
const configured = data.configured_count;
|
|
const connected = data.connected_count;
|
|
const wdRunning = data.watchdogs.filter(w => w.running).length;
|
|
const wdTotal = data.watchdogs.length;
|
|
|
|
document.getElementById('total-services').textContent = total;
|
|
document.getElementById('configured-count').textContent = `${configured}/${total}`;
|
|
document.getElementById('connected-count').textContent = `${connected}/${total}`;
|
|
document.getElementById('watchdog-count').textContent = `${wdRunning}/${wdTotal}`;
|
|
|
|
const connCard = document.getElementById('connected-card');
|
|
connCard.className = 'card text-white h-100';
|
|
if (connected === total) connCard.classList.add('bg-success');
|
|
else if (connected > 0) connCard.classList.add('bg-warning');
|
|
else connCard.classList.add('bg-danger');
|
|
|
|
const wdCard = document.getElementById('watchdog-card');
|
|
wdCard.className = 'card text-white h-100';
|
|
if (wdRunning === wdTotal) wdCard.classList.add('bg-success');
|
|
else if (wdRunning > 0) wdCard.classList.add('bg-warning');
|
|
else wdCard.classList.add('bg-danger');
|
|
|
|
if (data.timestamp) {
|
|
const dt = new Date(data.timestamp);
|
|
document.getElementById('last-check').textContent =
|
|
'Dernière vérification : ' + dt.toLocaleTimeString('fr-FR');
|
|
}
|
|
}
|
|
|
|
async function refreshAll() {
|
|
const btn = document.getElementById('btn-refresh');
|
|
const icon = btn.querySelector('i');
|
|
icon.classList.add('spin');
|
|
icon.classList.replace('bi-arrow-clockwise', 'bi-hourglass-split');
|
|
btn.disabled = true;
|
|
|
|
try {
|
|
const resp = await fetch('/status/api/check');
|
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
const data = await resp.json();
|
|
|
|
renderServices(data.systems);
|
|
renderWatchdogs(data.watchdogs);
|
|
updateSummary(data);
|
|
|
|
document.getElementById('loader').style.display = 'none';
|
|
document.getElementById('content').style.display = 'block';
|
|
} catch (e) {
|
|
console.error('Erreur refresh status:', e);
|
|
} finally {
|
|
icon.classList.replace('bi-hourglass-split', 'bi-arrow-clockwise');
|
|
icon.classList.remove('spin');
|
|
btn.disabled = false;
|
|
countdown = 30;
|
|
}
|
|
}
|
|
|
|
function startAutoRefresh() {
|
|
countdown = 30;
|
|
refreshInterval = setInterval(() => {
|
|
countdown--;
|
|
document.getElementById('refresh-timer').textContent =
|
|
`Prochaine vérification dans ${countdown}s`;
|
|
if (countdown <= 0) {
|
|
refreshAll();
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
// Premier chargement + auto-refresh toutes les 30s
|
|
refreshAll().then(() => startAutoRefresh());
|
|
</script>
|
|
|
|
<style>
|
|
.spin {
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
@keyframes spin {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
</style>
|
|
{% endblock %} |