2026-08-14 18:02:25 +02:00
<!DOCTYPE html>
< html lang = "fr" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
2026-08-14 20:55:47 +02:00
< meta name = "csrf-token" content = "{{ csrf_token() }}" >
2026-08-14 18:02:25 +02:00
< title > {% block title %}GMAO Collège{% endblock %}< / title >
< link rel = "icon" type = "image/svg+xml" href = "{{ url_for('static', filename='favicon.svg') }}" >
< link rel = "shortcut icon" href = "{{ url_for('static', filename='favicon.ico') }}" type = "image/x-icon" >
< link href = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel = "stylesheet" >
< link href = "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel = "stylesheet" >
< link href = "{{ url_for('static', filename='css/gmao.css') }}" rel = "stylesheet" >
< style >
/* ── Styles specifiques non couverts par gmao.css ── */
/* ── Navbar ── */
.navbar-brand i { font-size: 1.3rem; }
.navbar .dropdown-divider { border-color: rgba(255,255,255,0.15); }
.nav-group-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: rgba(255,255,255,0.5); padding: 0.5rem 1rem 0.2rem; }
@media (min-width: 992px) {
.nav-group-label { display: none; }
}
/* ── Content ── */
body { background: #f4f6f9; }
.main-content { padding: 1rem; }
@media (min-width: 768px) { .main-content { padding: 1.5rem 2rem; } }
/* ── Cards ── */
.stat-card { transition: transform 0.2s; border-radius: 0.75rem; }
.stat-card:hover { transform: translateY(-3px); }
.bg-purple { background-color: #6f42c1 !important; }
/* ── Tables responsive ── */
.table-responsive-stack { width: 100%; }
@media (max-width: 767px) {
.table-responsive-stack thead { display: none; }
.table-responsive-stack tbody tr {
display: block;
margin-bottom: 0.75rem;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
padding: 0.75rem;
background: #fff;
}
.table-responsive-stack tbody td {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 0.3rem 0;
border: none;
text-align: right;
}
.table-responsive-stack tbody td::before {
content: attr(data-label);
font-weight: 600;
text-align: left;
padding-right: 1rem;
flex-shrink: 0;
}
.table-responsive-stack tbody td:last-child { border-bottom: none; }
}
/* ── Filtres sur mobile ── */
.filter-bar .row > div { margin-bottom: 0.5rem; }
/* ── Workflow bar ── */
.workflow-step { font-size: 0.8rem; padding: 0.3rem 0.5rem; }
@media (max-width: 767px) {
.workflow-steps { flex-wrap: wrap; gap: 0.3rem; }
.workflow-step { font-size: 0.7rem; }
}
/* ── Planning cards ── */
.planning-section .badge { font-size: 0.75rem; }
< / style >
{% block extra_head %}{% endblock %}
{% if config.get('WTF_CSRF_ENABLED') %}
< meta name = "csrf-token" content = "{{ csrf_token() }}" >
{% endif %}
< / head >
< body >
{% if config.get('WTF_CSRF_ENABLED') %}
< script >
// Auto-inject CSRF token dans tous les fetch() POST/PUT/DELETE
(function() {
const meta = document.querySelector('meta[name="csrf-token"]');
if (!meta) return;
const token = meta.getAttribute('content');
const origFetch = window.fetch;
window.fetch = function(url, opts) {
opts = opts || {};
const method = (opts.method || 'GET').toUpperCase();
if (method !== 'GET' & & method !== 'HEAD') {
opts.headers = opts.headers || {};
if (!opts.headers['X-CSRFToken'] & & !opts.headers['X-CSRFToken']) {
opts.headers['X-CSRFToken'] = token;
}
}
return origFetch.call(this, url, opts);
};
})();
< / script >
{% endif %}
<!-- ── Navbar ── -->
{% if current_user.is_authenticated %}
< nav class = "navbar navbar-expand-lg navbar-dark" style = "background: #2c3e50;" >
< div class = "container-fluid" >
< a class = "navbar-brand fw-bold" href = "{{ url_for('dashboard.index') }}" >
< i class = "bi bi-tools" > < / i > < span class = "d-none d-sm-inline" > GMAO Collège< / span >
< / a >
< div class = "d-flex align-items-center order-lg-last" >
<!-- Alertes badge -->
{% set unread = stats.alerts_unread if stats is defined else 0 %}
{% if unread %}
< a href = "{{ url_for('dashboard.alerts') }}" class = "btn btn-sm btn-outline-light me-2 position-relative" >
< i class = "bi bi-bell" > < / i >
< span class = "position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" style = "font-size:0.6rem;" >
{{ unread }}
< / span >
< / a >
{% endif %}
<!-- Theme toggle -->
< button class = "theme-toggle" id = "theme-toggle" title = "Basculer le theme" >
< i class = "bi bi-moon-fill" id = "theme-icon" > < / i >
< / button >
<!-- Notifications -->
< a href = "/logs/" class = "btn btn-sm btn-outline-light me-2 position-relative" title = "Notifications" >
< i class = "bi bi-bell" > < / i >
< span class = "position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" id = "notif-count" style = "font-size:0.6rem; display:none;" > 0< / span >
< / a >
<!-- Utilisateur -->
{% if current_user.is_authenticated %}
< div class = "dropdown" >
< button class = "btn btn-sm btn-outline-light dropdown-toggle" type = "button" data-bs-toggle = "dropdown" >
< i class = "bi bi-person-circle" > < / i >
< span class = "d-none d-md-inline ms-1" > {{ current_user.full_name }}< / span >
< / button >
< ul class = "dropdown-menu dropdown-menu-end" >
< li > < span class = "dropdown-item-text small text-muted" > {{ current_user.role_label }}< / span > < / li >
< li > < hr class = "dropdown-divider" > < / li >
2026-08-15 00:13:37 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('auth.profile') }}" > < i class = "bi bi-person-vcard" > < / i > Mon profil< / a > < / li >
2026-08-14 18:02:25 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('auth.change_own_password') }}" > < i class = "bi bi-key" > < / i > Changer mon mot de passe< / a > < / li >
< li > < hr class = "dropdown-divider" > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('auth.logout') }}" > < i class = "bi bi-box-arrow-right" > < / i > Déconnexion< / a > < / li >
< / ul >
< / div >
{% endif %}
<!-- Hamburger -->
< button class = "navbar-toggler ms-2" type = "button" data-bs-toggle = "collapse" data-bs-target = "#mainNav" >
< span class = "navbar-toggler-icon" > < / span >
< / button >
< / div >
<!-- Menu collapsible -->
< div class = "collapse navbar-collapse" id = "mainNav" >
< ul class = "navbar-nav me-auto mb-2 mb-lg-0" >
< li class = "nav-group-label d-lg-none mt-2" > Principal< / li >
< li class = "nav-item" >
< a class = "nav-link {% if request.path == '/' %}active{% endif %}" href = "{{ url_for('dashboard.index') }}" >
< i class = "bi bi-speedometer2" > < / i > < span > Tableau de bord< / span >
< / a >
< / li >
< li class = "nav-item" >
< a class = "nav-link {% if '/messagerie' in request.path %}active{% endif %}" href = "/messagerie" >
< i class = "bi bi-envelope-paper" > < / i > < span > Messagerie< / span >
{% if pending_interpretations_count is defined and pending_interpretations_count > 0 %}
< span class = "badge bg-danger ms-1" > {{ pending_interpretations_count }}< / span >
{% endif %}
< / a >
< / li >
< li class = "nav-item dropdown" >
< a class = "nav-link dropdown-toggle {% if '/interventions' in request.path or '/planning' in request.path %}active{% endif %}" href = "#" role = "button" data-bs-toggle = "dropdown" >
< i class = "bi bi-wrench-adjustable" > < / i > < span > Interventions< / span >
< / a >
< ul class = "dropdown-menu dropdown-menu-dark" style = "background:#34495e;" >
< li > < a class = "dropdown-item" href = "{{ url_for('interventions.index') }}" > < i class = "bi bi-list-ul" > < / i > Toutes les interventions< / a > < / li >
2026-08-20 01:15:34 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('planning.index') }}" > < i class = "bi bi-calendar-check" > < / i > Planning unifié< / a > < / li >
2026-08-14 18:02:25 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('planning.tasks') }}" > < i class = "bi bi-list-check" > < / i > Tâches préventives< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('planning.scheduled') }}" > < i class = "bi bi-calendar-week" > < / i > Tâches planifiées< / a > < / li >
< li > < hr class = "dropdown-divider" style = "border-color:rgba(255,255,255,0.15)" > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('planning.meters') }}" > < i class = "bi bi-speedometer2" > < / i > Compteurs< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('planning.consumables') }}" > < i class = "bi bi-box" > < / i > Consommables< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('planning.availability') }}" > < i class = "bi bi-clock" > < / i > Disponibilités< / a > < / li >
2026-08-20 19:31:13 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('planning.time_tracking') }}" > < i class = "bi bi-clock-history" > < / i > Horaires et suivi annuel< / a > < / li >
2026-08-14 18:02:25 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('planning.admin_tasks') }}" > < i class = "bi bi-clipboard" > < / i > Tâches administratives< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('planning.rules') }}" > < i class = "bi bi-shield-check" > < / i > Règles d'accès< / a > < / li >
< li > < hr class = "dropdown-divider" style = "border-color:rgba(255,255,255,0.15)" > < / li >
2026-08-14 23:43:13 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('prevention.index') }}" > < i class = "bi bi-shield-check" > < / i > Prévention / DUERP< / a > < / li >
2026-08-14 18:02:25 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('trainings.index') }}" > < i class = "bi bi-mortarboard" > < / i > Formations< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('trainings.my_trainings') }}" > < i class = "bi bi-calendar-check" > < / i > Mes formations< / a > < / li >
< li > < hr class = "dropdown-divider" style = "border-color:rgba(255,255,255,0.15)" > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('exports.export_interventions') }}" > < i class = "bi bi-file-earmark-pdf" > < / i > Export interventions< / a > < / li >
2026-08-15 01:26:22 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('exports.export_costs_csv') }}" > < i class = "bi bi-cash-stack" > < / i > Export coûts et stock< / a > < / li >
2026-08-14 18:02:25 +02:00
< li > < hr class = "dropdown-divider" style = "border-color:rgba(255,255,255,0.15)" > < / li >
2026-08-20 01:15:34 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('scheduler.index') }}" > < i class = "bi bi-calendar-week" > < / i > Génération automatique< / a > < / li >
2026-08-14 18:02:25 +02:00
< / ul >
< / li >
< li class = "nav-item dropdown" >
< a class = "nav-link dropdown-toggle {% if '/equipments' in request.path or '/companies' in request.path or '/parts' in request.path or '/lots' in request.path or '/services' in request.path %}active{% endif %}" href = "#" role = "button" data-bs-toggle = "dropdown" >
< i class = "bi bi-pc-display" > < / i > < span > Équipements< / span >
< / a >
< ul class = "dropdown-menu dropdown-menu-dark" style = "background:#34495e;" >
< li > < a class = "dropdown-item" href = "{{ url_for('equipments.index') }}" > < i class = "bi bi-pc-display" > < / i > Tous les équipements< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('equipments.categories') }}" > < i class = "bi bi-tags" > < / i > Catégories< / a > < / li >
< li > < hr class = "dropdown-divider" style = "border-color:rgba(255,255,255,0.15)" > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('buildings.index') }}" > < i class = "bi bi-buildings" > < / i > Bâtiments< / a > < / li >
2026-08-15 00:13:37 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('zones.index') }}" > < i class = "bi bi-diagram-3" > < / i > Zones< / a > < / li >
2026-08-14 18:02:25 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('rooms.index') }}" > < i class = "bi bi-door-open" > < / i > Salles< / a > < / li >
2026-08-14 23:23:33 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('housing.index') }}" > < i class = "bi bi-house-door" > < / i > Logements de fonction< / a > < / li >
2026-08-14 18:02:25 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('room_types.index') }}" > < i class = "bi bi-tags" > < / i > Types de salles< / a > < / li >
< li > < hr class = "dropdown-divider" style = "border-color:rgba(255,255,255,0.15)" > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('companies.index') }}" > < i class = "bi bi-building" > < / i > Entreprises< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('contracts.index') }}" > < i class = "bi bi-file-earmark-text" > < / i > Contrats< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('lots.index') }}" > < i class = "bi bi-boxes" > < / i > Lots< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('services.index') }}" > < i class = "bi bi-building-gear" > < / i > Services< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('parts.index') }}" > < i class = "bi bi-box-seam" > < / i > Pièces< / a > < / li >
2026-08-15 00:13:37 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('meters.index') }}" > < i class = "bi bi-graph-up-arrow" > < / i > Historique des compteurs< / a > < / li >
2026-08-14 18:02:25 +02:00
< li > < hr class = "dropdown-divider" style = "border-color:rgba(255,255,255,0.15)" > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('equipments.to_trash') }}" > < i class = "bi bi-trash" > < / i > À jeter< / a > < / li >
2026-08-20 00:49:15 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('equipments.lifecycle_dashboard') }}" > < i class = "bi bi-activity" > < / i > Cycle de vie< / a > < / li >
2026-08-14 18:02:25 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('equipments.trashed') }}" > < i class = "bi bi-archive" > < / i > Anciens< / a > < / li >
< / ul >
< / li >
2026-08-14 20:53:03 +02:00
{% if current_user.is_admin() %}
2026-08-14 18:02:25 +02:00
< li class = "nav-group-label d-lg-none mt-2" > Administration< / li >
< li class = "nav-item dropdown" >
< a class = "nav-link dropdown-toggle {% if '/gmao-config' in request.path or '/outlook' in request.path or '/ent' in request.path or '/pronote' in request.path or '/yeastar' in request.path or '/status' in request.path or '/logs' in request.path or '/auth/users' in request.path or '/setup-wizard' in request.path %}active{% endif %}" href = "#" role = "button" data-bs-toggle = "dropdown" >
< i class = "bi bi-gear" > < / i > < span > Configuration< / span >
< / a >
< ul class = "dropdown-menu dropdown-menu-dark" style = "background:#34495e;" >
< li > < a class = "dropdown-item" href = "{{ url_for('gmao_config.index') }}" > < i class = "bi bi-gear" > < / i > Contexte GMAO< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('ai_config.index') }}" > < i class = "bi bi-robot" > < / i > Configuration IA< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('constraints.index') }}" > < i class = "bi bi-shield-exclamation" > < / i > Contraintes de zone< / a > < / li >
< li > < hr class = "dropdown-divider" style = "border-color:rgba(255,255,255,0.15)" > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('outlook_pages.index') }}" > < i class = "bi bi-envelope-at" > < / i > Comptes Outlook< / a > < / li >
2026-08-15 00:13:37 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('outlook_pages.add_account') }}" > < i class = "bi bi-person-plus" > < / i > Ajouter un compte Outlook< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('outlook_dashboard.processed_folders_config') }}" > < i class = "bi bi-folder-check" > < / i > Dossiers Outlook surveillés< / a > < / li >
2026-08-14 18:02:25 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('ent.index') }}" > < i class = "bi bi-broadcast" > < / i > ENT77< / a > < / li >
2026-08-15 00:13:37 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('ent.messages') }}" > < i class = "bi bi-inbox" > < / i > Messages ENT< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('ent.interpretations') }}" > < i class = "bi bi-stars" > < / i > Interprétations ENT< / a > < / li >
2026-08-14 18:02:25 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('ent.staff') }}" > < i class = "bi bi-people" > < / i > Personnels ENT< / a > < / li >
< li > < hr class = "dropdown-divider" style = "border-color:rgba(255,255,255,0.15)" > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('pronote.connect') }}" > < i class = "bi bi-qr-code" > < / i > PRONOTE (QR)< / a > < / li >
2026-08-14 20:55:47 +02:00
< li > < form method = "post" action = "{{ url_for('pronote.sync_salles') }}" > < button class = "dropdown-item" type = "submit" > < i class = "bi bi-download" > < / i > PRONOTE (sync salles)< / button > < / form > < / li >
2026-08-14 18:02:25 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('pronote.planning') }}" > < i class = "bi bi-calendar3" > < / i > PRONOTE (plannings)< / a > < / li >
2026-08-15 00:13:37 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('pronote.rooms') }}" > < i class = "bi bi-door-open" > < / i > Salles PRONOTE< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('pronote.personnels') }}" > < i class = "bi bi-person-badge" > < / i > Personnels PRONOTE< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('pronote.professeurs') }}" > < i class = "bi bi-person-workspace" > < / i > Professeurs PRONOTE< / a > < / li >
2026-08-14 18:02:25 +02:00
< li > < hr class = "dropdown-divider" style = "border-color:rgba(255,255,255,0.15)" > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('yeastar.index') }}" > < i class = "bi bi-telephone" > < / i > Téléphone P520< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('yeastar.config_page') }}" > < i class = "bi bi-telephone-gear" > < / i > Config téléphone< / a > < / li >
2026-08-15 00:13:37 +02:00
< li > < a class = "dropdown-item" href = "{{ url_for('yeastar.dnd_config_page') }}" > < i class = "bi bi-bell-slash" > < / i > Watchdog DND< / a > < / li >
2026-08-14 18:02:25 +02:00
< li > < hr class = "dropdown-divider" style = "border-color:rgba(255,255,255,0.15)" > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('status.index') }}" > < i class = "bi bi-activity" > < / i > Status système< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('logs.index') }}" > < i class = "bi bi-journal-text" > < / i > Logs watchdogs< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('chatbot.chatbot_interface') }}" > < i class = "bi bi-robot" > < / i > Chatbot< / a > < / li >
< li > < hr class = "dropdown-divider" style = "border-color:rgba(255,255,255,0.15)" > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('auth.list_users') }}" > < i class = "bi bi-people" > < / i > Utilisateurs< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('admin.data_management') }}" > < i class = "bi bi-database-exclamation text-danger" > < / i > Gestion des données< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('setup_wizard.admin_panel') }}" > < i class = "bi bi-database" > < / i > Gestion bases< / a > < / li >
< li > < a class = "dropdown-item" href = "{{ url_for('setup_wizard.index') }}" > < i class = "bi bi-magic" > < / i > Setup Wizard< / a > < / li >
< / ul >
< / li >
<!-- Mobile : liens config à plat -->
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('gmao_config.index') }}" > < i class = "bi bi-gear" > < / i > Contexte GMAO< / a > < / li >
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('outlook_pages.index') }}" > < i class = "bi bi-envelope-at" > < / i > Outlook< / a > < / li >
2026-08-15 00:13:37 +02:00
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('outlook_dashboard.processed_folders_config') }}" > < i class = "bi bi-folder-check" > < / i > Dossiers Outlook< / a > < / li >
2026-08-14 18:02:25 +02:00
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('ent.index') }}" > < i class = "bi bi-broadcast" > < / i > ENT77< / a > < / li >
2026-08-15 00:13:37 +02:00
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('ent.messages') }}" > < i class = "bi bi-inbox" > < / i > Messages ENT< / a > < / li >
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('ent.interpretations') }}" > < i class = "bi bi-stars" > < / i > Interprétations ENT< / a > < / li >
2026-08-14 18:02:25 +02:00
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('pronote.connect') }}" > < i class = "bi bi-qr-code" > < / i > PRONOTE< / a > < / li >
2026-08-15 00:13:37 +02:00
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('pronote.rooms') }}" > < i class = "bi bi-door-open" > < / i > Salles PRONOTE< / a > < / li >
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('pronote.personnels') }}" > < i class = "bi bi-person-badge" > < / i > Personnels PRONOTE< / a > < / li >
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('pronote.professeurs') }}" > < i class = "bi bi-person-workspace" > < / i > Professeurs PRONOTE< / a > < / li >
2026-08-14 18:02:25 +02:00
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('yeastar.index') }}" > < i class = "bi bi-telephone" > < / i > Téléphone< / a > < / li >
2026-08-15 00:13:37 +02:00
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('yeastar.config_page') }}" > < i class = "bi bi-telephone-gear" > < / i > Config téléphone< / a > < / li >
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('yeastar.dnd_config_page') }}" > < i class = "bi bi-bell-slash" > < / i > Watchdog DND< / a > < / li >
2026-08-14 18:02:25 +02:00
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('status.index') }}" > < i class = "bi bi-activity" > < / i > Status< / a > < / li >
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('logs.index') }}" > < i class = "bi bi-journal-text" > < / i > Logs< / a > < / li >
< li class = "nav-item d-lg-none" > < a class = "nav-link" href = "{{ url_for('auth.list_users') }}" > < i class = "bi bi-people" > < / i > Utilisateurs< / a > < / li >
{% endif %}
< li class = "nav-group-label d-lg-none mt-2" > Alertes< / li >
< li class = "nav-item d-lg-none" >
< a class = "nav-link" href = "{{ url_for('dashboard.alerts') }}" >
< i class = "bi bi-bell" > < / i > Alertes
{% if unread %}
< span class = "badge bg-danger ms-1" > {{ unread }}< / span >
{% endif %}
< / a >
< / li >
< / ul >
< / div >
< / div >
< / nav >
{% endif %}
<!-- ── Flash messages ── -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
< div class = "container-fluid main-content pb-0" >
{% for category, message in messages %}
< div class = "alert alert-{{ category }} alert-dismissible fade show" role = "alert" >
{{ message }}
< button type = "button" class = "btn-close" data-bs-dismiss = "alert" > < / button >
< / div >
{% endfor %}
< / div >
{% endif %}
{% endwith %}
<!-- ── Contenu principal ── -->
< div class = "main-content" >
{% block content %}{% endblock %}
< / div >
< script src = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" > < / script >
<!-- ── Overlay de progression ── -->
< div class = "modal fade" id = "progressModal" tabindex = "-1" data-bs-backdrop = "static" data-bs-keyboard = "false" aria-hidden = "true" >
< div class = "modal-dialog modal-dialog-centered modal-sm" >
< div class = "modal-content" style = "border:none;background:transparent;" >
< div class = "text-center text-white" >
< div class = "spinner-border mb-3" role = "status" style = "width:3rem;height:3rem;" >
< span class = "visually-hidden" > Chargement…< / span >
< / div >
< p id = "progressText" class = "fw-semibold" style = "text-shadow:0 1px 4px rgba(0,0,0,.7);" > Chargement en cours…< / p >
< div class = "progress mx-auto" style = "max-width:200px;height:6px;" >
< div class = "progress-bar progress-bar-striped progress-bar-animated" role = "progressbar" style = "width:100%" > < / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< script >
2026-08-14 20:55:47 +02:00
/* Ajoute le jeton CSRF aux formulaires HTML et aux requêtes fetch mutantes. */
(function() {
const tokenNode = document.querySelector('meta[name="csrf-token"]');
const csrfToken = tokenNode ? tokenNode.content : '';
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('form').forEach(function(form) {
const method = (form.getAttribute('method') || 'GET').toUpperCase();
if (method !== 'GET' & & !form.querySelector('input[name="csrf_token"]')) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'csrf_token';
input.value = csrfToken;
form.appendChild(input);
}
});
});
const originalFetch = window.fetch.bind(window);
window.fetch = function(resource, options) {
const opts = Object.assign({}, options || {});
const method = (opts.method || 'GET').toUpperCase();
if (!['GET', 'HEAD', 'OPTIONS'].includes(method)) {
const headers = new Headers(opts.headers || {});
if (!headers.has('X-CSRFToken')) headers.set('X-CSRFToken', csrfToken);
opts.headers = headers;
}
return originalFetch(resource, opts);
};
})();
2026-08-14 18:02:25 +02:00
/* ── Overlay de progression ──
Usage JS : showProgress('Texte') / hideProgress()
Ou sur un < form > : class="show-progress-form" data-progress-text="Texte optionnel"
*/
function showProgress(text) {
var el = document.getElementById('progressText');
if (el & & text) el.textContent = text;
var modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('progressModal'));
modal.show();
}
function hideProgress() {
var modal = bootstrap.Modal.getInstance(document.getElementById('progressModal'));
if (modal) modal.hide();
}
/* Auto-show sur les formulaires marqués .show-progress-form */
/* Si le < form > a data-confirm="message", on fait le confirm() en JS avant d'afficher l'overlay.
Les anciens onsubmit="return confirm(...)" sont gérés : si le confirm retourne false,
le submit event ne se déclenche pas, donc l'overlay ne s'affiche pas non plus.
*/
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('form.show-progress-form').forEach(function(form) {
/* Si data-confirm est défini, on remplace le comportement inline */
if (form.dataset.confirm) {
form.addEventListener('submit', function(e) {
if (!confirm(form.dataset.confirm)) {
e.preventDefault();
return;
}
var text = form.dataset.progressText || 'Chargement en cours…';
showProgress(text);
});
} else {
form.addEventListener('submit', function() {
var text = form.dataset.progressText || 'Chargement en cours…';
showProgress(text);
});
}
});
});
/* Auto-show sur les liens marqués .show-progress-link */
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('a.show-progress-link').forEach(function(a) {
a.addEventListener('click', function() {
showProgress(a.dataset.progressText || 'Chargement en cours…');
});
});
});
< / script >
< script >
// Theme sombre toggle avec memorisation
(function() {
const saved = localStorage.getItem('gmao-theme') || 'light';
if (saved === 'dark') {
document.documentElement.setAttribute('data-bs-theme', 'dark');
const icon = document.getElementById('theme-icon');
if (icon) icon.className = 'bi bi-sun-fill';
}
const btn = document.getElementById('theme-toggle');
if (btn) {
btn.addEventListener('click', function() {
const current = document.documentElement.getAttribute('data-bs-theme') || 'light';
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-bs-theme', next);
localStorage.setItem('gmao-theme', next);
const icon = document.getElementById('theme-icon');
if (icon) icon.className = next === 'dark' ? 'bi bi-sun-fill' : 'bi bi-moon-fill';
});
}
})();
// Notifications polling (toutes les 60s)
(function() {
function pollNotifications() {
fetch('/api/notifications').then(r => r.json()).then(data => {
const badge = document.getElementById('notif-count');
if (badge) {
badge.textContent = data.count;
badge.style.display = data.count > 0 ? 'inline' : 'none';
}
}).catch(err => console.error('Notif error:', err));
}
pollNotifications();
setInterval(pollNotifications, 60000);
})();
< / script >
{% block extra_scripts %}{% endblock %}
< / body >
< / html >