Mise à jour des fichiers CSS, ajout des mécanismes de mise à jour de Ship2net avec info sur la page du bateau.
This commit is contained in:
+438
-5
@@ -135,6 +135,7 @@ Init_session(basename($_SERVER['SCRIPT_NAME'])); // Fonction ouvrant la session
|
||||
{
|
||||
$_SESSION['Bateaux']['bateau']['Cle_primaire'] = (int)$_GET['id_bateau'];
|
||||
$_SESSION['Bateaux']['type_details_bateau'] = "resume";
|
||||
$_SESSION['Bateaux']['generation'] = 0; // Nouveau bateau : génération active
|
||||
$_SESSION['Bateaux']['page'] = "Details_bateau";
|
||||
|
||||
if (isset($_GET['origine'])) $_SESSION['Bateaux']['origine'] = $_GET['origine'];
|
||||
@@ -152,6 +153,16 @@ Init_session(basename($_SERVER['SCRIPT_NAME'])); // Fonction ouvrant la session
|
||||
break;
|
||||
}
|
||||
|
||||
//===================================
|
||||
// Génération consultée (synthèse §50) : 0 = génération active. La valeur n'est utilisée qu'après vérification
|
||||
// qu'elle appartient bien au bateau affiché (préparation de la page).
|
||||
case "choix_generation":
|
||||
{
|
||||
$_SESSION['Bateaux']['generation'] = isset($_GET['generation']) ? (int)$_GET['generation'] : 0;
|
||||
$_SESSION['Bateaux']['type_details_bateau'] = "resume";
|
||||
break;
|
||||
}
|
||||
|
||||
//===================================
|
||||
case "modification_bateau":
|
||||
{
|
||||
@@ -370,7 +381,7 @@ $requete .= ' TB_Bateaux.Date_derniere_maj AS Date_derniere_maj, ';
|
||||
// Mise en forme de certaines variables :
|
||||
$bateaux[$nb_bateaux]['version'] = ($donnees['version'] == "") ? '<i>inconnue</i>' : htmlspecialchars($donnees['version']);
|
||||
if ($donnees['Date_derniere_maj'] < "2020-01-02") $bateaux[$nb_bateaux]['Date_derniere_maj'] = "<i>Jamais</i>";
|
||||
else $bateaux[$nb_bateaux]['Date_derniere_maj'] = Formatage_date($donnees['Date_derniere_maj']);
|
||||
else $bateaux[$nb_bateaux]['Date_derniere_maj'] = '<span data-date-utc="'.htmlspecialchars(str_replace(' ', 'T', $donnees['Date_derniere_maj']).'Z').'">'.htmlspecialchars($donnees['Date_derniere_maj']).' UTC</span>';
|
||||
|
||||
$nb_bateaux ++;
|
||||
}
|
||||
@@ -400,6 +411,7 @@ $requete .= ' TB_Bateaux.Date_derniere_maj AS Date_derniere_maj, ';
|
||||
while ($donnees = Recherche_ligne_bdd($reponse))
|
||||
{
|
||||
$bateau = $donnees;
|
||||
$bateau['Date_derniere_maj_utc'] = $donnees['Date_derniere_maj']; // Brute (UTC), pour l'indicateur de fraîcheur
|
||||
|
||||
// Mise en forme de textes :
|
||||
if ($donnees['Date_derniere_maj'] < "2020-01-02") $bateau['Date_derniere_maj'] = "<i>".t("Jamais")."</i>";
|
||||
@@ -632,7 +644,41 @@ $requete .= ' TB_Bateaux.Date_derniere_maj AS Date_derniere_maj, ';
|
||||
Liberation_jeu_de_resultat_bdd($reponse);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
//-------------------------------------------------------------------------------------------------------
|
||||
// Données du bateau (synthèse §50) : lues dans la base de la génération consultée (active par défaut), par
|
||||
// une connexion dédiée - la connexion principale du site n'est pas touchée.
|
||||
default:
|
||||
{
|
||||
$consultation = array('autorisee' => ($est_administrateur || (int)($acces_profil['Consultation_donnees'] ?? 0) >= 1),
|
||||
'generations' => array(), 'generation' => null, 'domaines' => array(), 'erreur' => null,
|
||||
'periode_archivage' => CONSULTATION_PERIODE_ARCHIVAGE_DEFAUT);
|
||||
if (!$consultation['autorisee']) break;
|
||||
|
||||
$consultation['generations'] = Generations_bateau($bateau['Cle_primaire']);
|
||||
foreach ($consultation['generations'] as $generation)
|
||||
{
|
||||
// Génération demandée si elle appartient bien à ce bateau, sinon la génération active :
|
||||
if ((int)$generation['Cle_primaire'] == (int)($_SESSION['Bateaux']['generation'] ?? 0)) { $consultation['generation'] = $generation; break; }
|
||||
if ($generation['Date_fin'] === null && $consultation['generation'] === null) $consultation['generation'] = $generation;
|
||||
}
|
||||
if ($consultation['generation'] === null) break; // Aucune archive reçue pour ce bateau
|
||||
|
||||
$db_generation = Connexion_generation($consultation['generation']['Nom_base']);
|
||||
if ($db_generation === null) { $consultation['erreur'] = t("Les données de ce bateau sont momentanément inaccessibles."); break; }
|
||||
$consultation['periode_archivage'] = Periode_archivage_bateau($db_generation);
|
||||
$consultation['alarmes'] = Episodes_alarmes($db_generation, 20);
|
||||
foreach (array('circuits' => t("Circuits"), 'capteurs' => t("Capteurs"), 'batteries' => t("Batteries"), 'reservoirs' => t("Réservoirs")) as $domaine => $titre)
|
||||
{
|
||||
$consultation['domaines'][$domaine] = array('titre' => $titre, 'familles' => Elements_domaine($db_generation, $domaine));
|
||||
}
|
||||
$db_generation->close();
|
||||
|
||||
// Génération active : demande d'archive immédiate (si les données ne sont pas déjà récentes - étape 3d) :
|
||||
$consultation['demande'] = ($consultation['generation']['Date_fin'] === null)
|
||||
? Demander_archive_si_besoin($bateau['Cle_primaire'], $bateau['Date_derniere_maj_utc']) : null;
|
||||
$consultation['demande_archive'] = (($consultation['demande']['statut'] ?? '') == 'demandee');
|
||||
break;
|
||||
}
|
||||
|
||||
} // Fin du switch 'type_details_bateau'
|
||||
|
||||
@@ -741,7 +787,7 @@ $requete .= ' TB_Bateaux.Date_derniere_maj AS Date_derniere_maj, ';
|
||||
// echo '<th><a href="bateaux.php?action=tri_liste&colonne=Carte">ShipBase</a></th>';
|
||||
echo '<th><a href="bateaux.php?action=tri_liste&colonne=Adresse_mac">Adresse MAC</a></th>';
|
||||
echo '<th><a href="bateaux.php?action=tri_liste&colonne=Version">Version</a></th>';
|
||||
echo '<th><a href="bateaux.php?action=tri_liste&colonne=Date_maj">Dernière mise à jour [UTC]</a></th>';
|
||||
echo '<th><a href="bateaux.php?action=tri_liste&colonne=Date_maj">'.t("Dernières données reçues").'</a></th>';
|
||||
// echo '<th>Commentaires</th>';
|
||||
echo '</tr>
|
||||
</thead>
|
||||
@@ -808,7 +854,30 @@ $requete .= ' TB_Bateaux.Date_derniere_maj AS Date_derniere_maj, ';
|
||||
else echo '<span class="live_indisponible" title="'.t("Aucun module ShipBase joignable n'est affecté à ce bateau").'">'.t("Live indisponible").'</span>';
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="derniere_maj">'.t("Dernières données reçues").' : <span class="gras">'.$bateau['Date_derniere_maj'].' [UTC]</span></div>';
|
||||
// Indicateur de fraîcheur (synthèse §44.6, §50) : date UTC transmise au navigateur, qui l'affiche dans le fuseau
|
||||
// de l'utilisateur et colore selon l'ancienneté (seuil orange/rouge = 1,25 x la période d'archivage du bateau) :
|
||||
echo '<div class="derniere_maj">'.t("Dernières données reçues").' : ';
|
||||
if ($bateau['Date_derniere_maj_utc'] === null || $bateau['Date_derniere_maj_utc'] < "2020-01-02") echo '<span class="gras"><i>'.t("Jamais").'</i></span>';
|
||||
else echo '<span class="gras fraicheur" data-date-utc="'.htmlspecialchars(str_replace(' ', 'T', $bateau['Date_derniere_maj_utc']).'Z').'" data-seuil-rouge="'
|
||||
.(int)round(1.25 * ($consultation['periode_archivage'] ?? CONSULTATION_PERIODE_ARCHIVAGE_DEFAUT)).'">'.htmlspecialchars($bateau['Date_derniere_maj_utc']).' UTC</span>';
|
||||
|
||||
// Mise à jour à l'ouverture (étape 3d) : ce qui a été fait, et pourquoi (synthèse §50) :
|
||||
$minutes = function($secondes) { return max(1, (int)ceil($secondes / 60)); };
|
||||
switch ($consultation['demande']['statut'] ?? '')
|
||||
{
|
||||
case 'demandee':
|
||||
echo '<span class="info_mise_a_jour en_cours" id="info_mise_a_jour">'.t("Mise à jour demandée au bateau...").'</span>';
|
||||
break;
|
||||
case 'donnees_recentes':
|
||||
echo '<span class="info_mise_a_jour">'.str_replace("[[M]]", $minutes($consultation['demande']['attente']),
|
||||
t("Données récentes : pas de nouvelle demande au bateau avant [[M]] min.")).'</span>';
|
||||
break;
|
||||
case 'deja_demandee':
|
||||
echo '<span class="info_mise_a_jour">'.str_replace("[[M]]", $minutes($consultation['demande']['attente']),
|
||||
t("Mise à jour déjà demandée récemment : nouvelle demande possible dans [[M]] min.")).'</span>';
|
||||
break;
|
||||
}
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
//--------------------------------
|
||||
// Boutons de haut de page (les anciens onglets Carte/Batteries/Circuits/Réservoirs/Capteurs/Système, liés au
|
||||
@@ -968,7 +1037,120 @@ $requete .= ' TB_Bateaux.Date_derniere_maj AS Date_derniere_maj, ';
|
||||
// 'resume' (et toute ancienne valeur de session) :
|
||||
default:
|
||||
{
|
||||
echo '<p>'.t("La consultation des données du bateau sera disponible prochainement.").'</p>';
|
||||
if (!$consultation['autorisee']) { echo '<p>'.t("Votre type d'accès ne permet pas de consulter les données de ce bateau.").'</p>'; break; }
|
||||
if ($consultation['generation'] === null) { echo '<p>'.t("Aucune donnée n'a encore été reçue de ce bateau.").'</p>'; break; }
|
||||
if ($consultation['erreur'] !== null) { echo '<p>'.htmlspecialchars($consultation['erreur']).'</p>'; break; }
|
||||
|
||||
echo '<div class="consultation">';
|
||||
|
||||
// Rafraîchissement à l'arrivée d'une nouvelle archive (synthèse §50) - génération active seulement :
|
||||
if ($consultation['generation']['Date_fin'] === null)
|
||||
{
|
||||
echo '<span id="surveillance_archive" hidden data-id-bateau="'.(int)$bateau['Cle_primaire'].'" data-date="'.htmlspecialchars((string)$bateau['Date_derniere_maj_utc']).'"'
|
||||
.(!empty($consultation['demande_archive']) ? ' data-demande="1"' : '').'></span>';
|
||||
}
|
||||
|
||||
//--------------------------------
|
||||
// Génération consultée, et choix d'une génération antérieure :
|
||||
$format_date = function($date) { return ($date === null) ? '' : date_format(date_create($date), 'd/m/Y'); };
|
||||
echo '<form class="choix_generation" action="bateaux.php" method="get">';
|
||||
echo '<input type="hidden" name="action" value="choix_generation">';
|
||||
echo '<label for="generation">'.t("Données").' : </label>';
|
||||
echo '<select name="generation" id="generation" onchange="this.form.submit()">';
|
||||
foreach ($consultation['generations'] as $generation)
|
||||
{
|
||||
$libelle = str_replace("[[N]]", (int)$generation['Numero_generation'], t("Génération [[N]]"));
|
||||
if ($generation['Date_fin'] === null) $libelle .= ' - '.str_replace("[[Date]]", $format_date($generation['Date_debut']), t("en cours, depuis le [[Date]]"));
|
||||
else $libelle .= ' - '.str_replace(array("[[Debut]]", "[[Fin]]"), array($format_date($generation['Date_debut']), $format_date($generation['Date_fin'])), t("du [[Debut]] au [[Fin]]"));
|
||||
if ($generation['Version_soft'] !== null) $libelle .= ' (v'.$generation['Version_soft'].')';
|
||||
echo '<option value="'.(int)$generation['Cle_primaire'].'"'.(((int)$generation['Cle_primaire'] == (int)$consultation['generation']['Cle_primaire']) ? ' selected' : '').'>'.htmlspecialchars($libelle).'</option>';
|
||||
}
|
||||
echo '</select>';
|
||||
echo '<noscript><button type="submit">'.t("Afficher").'</button></noscript>';
|
||||
echo '</form>';
|
||||
if ($consultation['generation']['Date_fin'] !== null)
|
||||
{
|
||||
echo '<p class="generation_ancienne">'.t("Vous consultez une génération antérieure : les valeurs ci-dessous sont celles de sa dernière archive, pas l'état actuel du bateau.").'</p>';
|
||||
}
|
||||
|
||||
//--------------------------------
|
||||
// Dernières alarmes (synthèse §50) : déclenchements, du plus récent au plus ancien, dates dans le fuseau du
|
||||
// navigateur. (Pas d'état "alarme en cours" : vieux de jusqu'à une période d'archivage, il ne serait ni
|
||||
// fiable ni exploitable.)
|
||||
echo '<section class="dernieres_alarmes">';
|
||||
echo '<h2>'.t("Dernières alarmes").'</h2>';
|
||||
if (count($consultation['alarmes'] ?? array()) == 0) echo '<p class="aucune_alarme">'.t("Aucune alarme enregistrée.").'</p>';
|
||||
else
|
||||
{
|
||||
$date_affichee = function($iso) { return '<span data-date-utc="'.htmlspecialchars($iso).'">'.htmlspecialchars($iso).'</span>'; };
|
||||
echo '<table>';
|
||||
echo '<tr><th>'.t("Début").'</th><th>'.t("Fin").'</th><th>'.t("Durée").'</th><th>'.t("Alarme").'</th><th></th></tr>';
|
||||
foreach ($consultation['alarmes'] as $alarme)
|
||||
{
|
||||
switch ($alarme['statut'])
|
||||
{
|
||||
case 'termine': $fin = $date_affichee($alarme['fin_utc']); break;
|
||||
case 'en_cours': $fin = '<span class="alarme_en_cours">'.t("en cours à la dernière archive").'</span>'; break;
|
||||
case 'fin_non_enregistree': $fin = '<i>'.t("fin non enregistrée").'</i>'; break;
|
||||
case 'debut_anterieur': $fin = $date_affichee($alarme['fin_utc']); break;
|
||||
default: $fin = '<i>'.t("ponctuel").'</i>'; break;
|
||||
}
|
||||
echo '<tr class="alarme_'.$alarme['statut'].'">';
|
||||
echo '<td class="date_alarme">'.(($alarme['debut_utc'] !== null) ? $date_affichee($alarme['debut_utc']) : '<i>'.t("début antérieur").'</i>').'</td>';
|
||||
echo '<td class="date_alarme">'.$fin.'</td>';
|
||||
echo '<td class="duree_alarme">'.(($alarme['duree'] !== null) ? htmlspecialchars(Duree_lisible($alarme['duree'])) : '').'</td>';
|
||||
echo '<td class="nom_alarme">'.htmlspecialchars($alarme['nom']).'</td>';
|
||||
echo '<td class="message_alarme">'.htmlspecialchars($alarme['message']).'</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
}
|
||||
echo '</section>';
|
||||
|
||||
//--------------------------------
|
||||
// Les quatre domaines, par famille, avec leurs dernières valeurs :
|
||||
foreach ($consultation['domaines'] as $domaine => $donnees_domaine)
|
||||
{
|
||||
if (count($donnees_domaine['familles']) == 0) continue;
|
||||
echo '<section class="domaine domaine_'.$domaine.'">';
|
||||
echo '<h2>'.htmlspecialchars($donnees_domaine['titre']).'</h2>';
|
||||
foreach ($donnees_domaine['familles'] as $famille)
|
||||
{
|
||||
echo '<div class="famille">';
|
||||
echo '<h3>'.htmlspecialchars($famille['nom']).'</h3>';
|
||||
echo '<table>';
|
||||
foreach ($famille['elements'] as $element)
|
||||
{
|
||||
// Élément avec courbe historique : ligne cliquable (synthèse §50, étape 3c)
|
||||
$classes = array();
|
||||
if ($element['defaut'] !== null) $classes[] = 'en_defaut';
|
||||
if ($element['courbe']) $classes[] = 'avec_courbe';
|
||||
echo '<tr'.((count($classes) > 0) ? ' class="'.implode(' ', $classes).'"' : '')
|
||||
.($element['courbe'] ? ' data-type="'.$domaine.'" data-element="'.(int)$element['id'].'" data-nom="'.htmlspecialchars($element['nom']).'" title="'.t("Afficher la courbe").'"' : '').'>';
|
||||
echo '<td class="nom_element">'.htmlspecialchars($element['nom']).'</td>';
|
||||
echo '<td class="valeurs_element">'.htmlspecialchars(implode(' ', $element['valeurs'])).'</td>';
|
||||
echo '<td class="defaut_element">'.(($element['defaut'] !== null) ? htmlspecialchars($element['defaut']) : '').'</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</section>';
|
||||
}
|
||||
//--------------------------------
|
||||
// Panneau de courbe historique (synthèse §50, étape 3c), ouvert au clic sur un élément :
|
||||
echo '<div id="panneau_courbe" hidden data-bateau="'.(int)$bateau['Cle_primaire'].'" data-generation="'.(int)$consultation['generation']['Cle_primaire'].'">';
|
||||
echo '<div class="fenetre_courbe" role="dialog" aria-modal="true" aria-labelledby="titre_courbe">';
|
||||
echo '<div class="entete_courbe"><h3 id="titre_courbe"></h3><button type="button" id="fermer_courbe" title="'.t("Fermer").'">×</button></div>';
|
||||
echo '<div class="choix_courbe" id="choix_variable"></div>';
|
||||
echo '<div class="choix_courbe" id="choix_periode"></div>';
|
||||
echo '<div class="zone_courbe"><canvas id="canvas_courbe"></canvas><p id="message_courbe" hidden></p></div>';
|
||||
echo '<p id="stats_courbe"></p>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
echo '<script src="/vendor/chartjs/chart.umd.min.js"></script>';
|
||||
|
||||
echo '</div>'; // Fin de class="consultation"
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1139,6 +1321,257 @@ Pied_de_page();
|
||||
|
||||
|
||||
<script>
|
||||
//------------------------------------------------------------------------------------------------------------
|
||||
// Dates UTC (attribut data-date-utc) affichées dans le fuseau du navigateur ; indicateur de fraîcheur coloré si
|
||||
// l'élément porte un seuil (data-seuil-rouge, en secondes) : vert < 5 min, orange jusqu'au seuil, rouge au-delà.
|
||||
// Mis à jour toutes les 30 s : la couleur évolue sans recharger la page (synthèse §44.6, §50).
|
||||
//------------------------------------------------------------------------------------------------------------
|
||||
const TEXTES_FRAICHEUR = <?php echo json_encode(array('instant' => t("à l'instant"), 'minutes' => t("il y a [[M]] min"), 'heures' => t("il y a [[H]] h [[M]] min"), 'jours' => t("il y a [[J]] j")), JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); ?>;
|
||||
function Afficher_dates_et_fraicheur()
|
||||
{
|
||||
document.querySelectorAll('[data-date-utc]').forEach(function (element) {
|
||||
const date = new Date(element.dataset.dateUtc);
|
||||
if (isNaN(date)) return;
|
||||
let texte = date.toLocaleString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
||||
if (element.dataset.seuilRouge)
|
||||
{
|
||||
const age = Math.max(0, (Date.now() - date.getTime()) / 1000);
|
||||
let relatif;
|
||||
if (age < 60) relatif = TEXTES_FRAICHEUR.instant;
|
||||
else if (age < 3600) relatif = TEXTES_FRAICHEUR.minutes.replace('[[M]]', Math.floor(age / 60));
|
||||
else if (age < 86400) relatif = TEXTES_FRAICHEUR.heures.replace('[[H]]', Math.floor(age / 3600)).replace('[[M]]', Math.floor((age % 3600) / 60));
|
||||
else relatif = TEXTES_FRAICHEUR.jours.replace('[[J]]', Math.floor(age / 86400));
|
||||
texte += ' (' + relatif + ')';
|
||||
element.classList.remove('fraicheur_verte', 'fraicheur_orange', 'fraicheur_rouge');
|
||||
element.classList.add(age < 300 ? 'fraicheur_verte' : (age <= Number(element.dataset.seuilRouge) ? 'fraicheur_orange' : 'fraicheur_rouge'));
|
||||
}
|
||||
element.textContent = texte;
|
||||
});
|
||||
}
|
||||
Afficher_dates_et_fraicheur();
|
||||
setInterval(Afficher_dates_et_fraicheur, 30000);
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------
|
||||
// Courbes historiques (synthèse §50, étape 3c). Un clic sur un élément ouvre sa courbe (données : bateau_mesures.php).
|
||||
// Courbe moyenne entourée d'une bande min-max (un pic bref reste visible sur une longue période). L'état de la
|
||||
// courbe ouverte est mémorisé dans l'adresse (#courbe=type.element.variable.periode) : après un rechargement de la
|
||||
// page (nouvelle archive), elle se rouvre d'elle-même.
|
||||
//------------------------------------------------------------------------------------------------------------
|
||||
const TEXTES_COURBE = <?php echo json_encode(array(
|
||||
'periodes' => array('24h' => t("24 h"), '7j' => t("7 jours"), '30j' => t("30 jours"), '3m' => t("3 mois"), '1a' => t("1 an")),
|
||||
'variables' => array(
|
||||
'circuits' => array('intensite' => t("Intensité"), 'tension' => t("Tension"), 'consigne' => t("Consigne"), 'frequence' => t("Fréquence")),
|
||||
'batteries' => array('soc' => t("SOC"), 'tension' => t("Tension"), 'temperature' => t("Température"), 'courant_charge' => t("Courant de charge"),
|
||||
'courant_decharge' => t("Courant de décharge"), 'courant_differentiel' => t("Courant différentiel"), 'soh' => t("SOH"), 'autonomie' => t("Autonomie"))),
|
||||
'moyenne' => t("Moyenne"), 'bande' => t("Min - max"), 'min' => t("Min"), 'max' => t("Max"),
|
||||
'aucune' => t("Aucune mesure sur cette période."), 'erreur' => t("Courbe indisponible."), 'chargement' => t("Chargement..."),
|
||||
'bibliotheque' => t("Bibliothèque de graphiques introuvable (vendor/chartjs).")),
|
||||
JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); ?>;
|
||||
const VARIABLE_PAR_DEFAUT = { circuits: 'intensite', batteries: 'soc' };
|
||||
|
||||
(function () {
|
||||
const panneau = document.getElementById('panneau_courbe');
|
||||
if (!panneau) return;
|
||||
let etat = null, graphique = null, requete = 0;
|
||||
|
||||
const nombre = (v, p) => (v === null || v === undefined) ? '' : Number(v).toLocaleString(undefined, { minimumFractionDigits: p, maximumFractionDigits: p });
|
||||
const date_heure = (ms) => new Date(ms).toLocaleString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
||||
const format_axe = function (ms) {
|
||||
const d = new Date(ms);
|
||||
if (etat.periode === '24h') return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
|
||||
if (etat.periode === '7j') return d.toLocaleDateString(undefined, { weekday: 'short', day: '2-digit' }); // graduations à minuit
|
||||
if (etat.periode === '1a') return d.toLocaleDateString(undefined, { month: 'short', year: '2-digit' });
|
||||
return d.toLocaleDateString(undefined, { day: '2-digit', month: '2-digit' });
|
||||
};
|
||||
|
||||
// Graduations de l'axe du temps sur des instants "ronds" de l'heure LOCALE (toutes les 3 h sur 24 h, chaque jour
|
||||
// à minuit sur 7 jours, tous les 5 jours sur 30 jours, le 1er et le 15 sur 3 mois, chaque 1er du mois sur 1 an) :
|
||||
function Graduations(debut, fin)
|
||||
{
|
||||
const graduations = [];
|
||||
const d = new Date(debut);
|
||||
d.setMinutes(0, 0, 0);
|
||||
if (etat.periode === '24h') d.setHours(Math.ceil(d.getHours() / 3) * 3); // prochaine heure multiple de 3
|
||||
else { d.setHours(0); d.setDate(d.getDate() + 1); } // minuit suivant
|
||||
for (let garde = 0; d.getTime() <= fin && garde < 400; garde++) {
|
||||
const jour = d.getDate();
|
||||
let retenir = true;
|
||||
if (etat.periode === '30j') retenir = (jour % 5 === 1);
|
||||
else if (etat.periode === '3m') retenir = (jour === 1 || jour === 15);
|
||||
else if (etat.periode === '1a') retenir = (jour === 1);
|
||||
if (retenir && d.getTime() >= debut) graduations.push({ value: d.getTime() });
|
||||
if (etat.periode === '24h') d.setHours(d.getHours() + 3); else d.setDate(d.getDate() + 1);
|
||||
}
|
||||
return graduations;
|
||||
}
|
||||
|
||||
function Boutons(conteneur, choix, actif, action)
|
||||
{
|
||||
conteneur.innerHTML = '';
|
||||
Object.keys(choix).forEach(function (cle) {
|
||||
const bouton = document.createElement('button');
|
||||
bouton.type = 'button';
|
||||
bouton.textContent = choix[cle];
|
||||
if (cle === actif) bouton.classList.add('actif');
|
||||
bouton.addEventListener('click', function () { action(cle); });
|
||||
conteneur.appendChild(bouton);
|
||||
});
|
||||
}
|
||||
|
||||
function Message(texte)
|
||||
{
|
||||
const p = document.getElementById('message_courbe');
|
||||
p.textContent = texte || '';
|
||||
p.hidden = !texte;
|
||||
}
|
||||
|
||||
function Afficher()
|
||||
{
|
||||
document.getElementById('titre_courbe').textContent = etat.nom;
|
||||
const variables = TEXTES_COURBE.variables[etat.type];
|
||||
const zone_variables = document.getElementById('choix_variable');
|
||||
if (variables) Boutons(zone_variables, variables, etat.variable, function (v) { etat.variable = v; Afficher(); });
|
||||
else zone_variables.innerHTML = '';
|
||||
Boutons(document.getElementById('choix_periode'), TEXTES_COURBE.periodes, etat.periode, function (p) { etat.periode = p; Afficher(); });
|
||||
history.replaceState(null, '', '#courbe=' + [etat.type, etat.element, etat.variable || '-', etat.periode].join('.'));
|
||||
document.getElementById('stats_courbe').textContent = '';
|
||||
Message(TEXTES_COURBE.chargement);
|
||||
|
||||
const numero = ++requete; // Seule la réponse à la DERNIÈRE demande est affichée
|
||||
const parametres = new URLSearchParams({ bateau: panneau.dataset.bateau, generation: panneau.dataset.generation, type: etat.type,
|
||||
element: etat.element, variable: etat.variable || '', periode: etat.periode });
|
||||
// Causes distinguées (synthèse §50) : bibliothèque absente, refus motivé du serveur, erreur technique (détail
|
||||
// dans la console du navigateur) - un message unique masquait la vraie raison.
|
||||
if (typeof Chart === 'undefined') { Message(TEXTES_COURBE.bibliotheque); return; }
|
||||
fetch('bateau_mesures.php?' + parametres.toString(), { cache: 'no-store', credentials: 'same-origin' })
|
||||
.then(r => r.json().then(d => ({ ok: r.ok, d: d })))
|
||||
.then(function (reponse) {
|
||||
if (numero !== requete) return;
|
||||
if (!reponse.ok) { Message(TEXTES_COURBE.erreur + (reponse.d && reponse.d.erreur ? ' (' + reponse.d.erreur + ')' : '')); return; }
|
||||
Dessiner(reponse.d);
|
||||
})
|
||||
.catch(function (erreur) {
|
||||
console.error('Courbe :', erreur);
|
||||
if (numero === requete) Message(TEXTES_COURBE.erreur);
|
||||
});
|
||||
}
|
||||
|
||||
function Dessiner(donnees)
|
||||
{
|
||||
if (graphique) { graphique.destroy(); graphique = null; }
|
||||
if (donnees.points.length === 0) { Message(TEXTES_COURBE.aucune); return; }
|
||||
Message('');
|
||||
const p = donnees.precision, u = donnees.unite ? ' ' + donnees.unite : '';
|
||||
const serie = (cle) => donnees.points.map(pt => ({ x: pt.t, y: pt[cle] }));
|
||||
graphique = new Chart(document.getElementById('canvas_courbe'), {
|
||||
type: 'line',
|
||||
data: { datasets: [
|
||||
{ label: TEXTES_COURBE.bande, data: serie('max'), borderWidth: 0, pointRadius: 0, fill: '+1', backgroundColor: 'rgba(115, 183, 214, 0.30)' },
|
||||
{ label: 'min', data: serie('min'), borderWidth: 0, pointRadius: 0, fill: false },
|
||||
{ label: TEXTES_COURBE.moyenne, data: serie('v'), borderColor: '#004563', borderWidth: 1.5, pointRadius: 0, fill: false }
|
||||
] },
|
||||
options: {
|
||||
animation: false, maintainAspectRatio: false, normalized: true,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
scales: {
|
||||
x: { type: 'linear', min: donnees.debut, max: donnees.fin, afterBuildTicks: (axe) => { axe.ticks = Graduations(donnees.debut, donnees.fin); },
|
||||
ticks: { autoSkip: false, maxRotation: 0, callback: (v) => format_axe(v) } },
|
||||
y: { title: { display: !!donnees.unite, text: donnees.unite }, ticks: { callback: (v) => nombre(v, p) } }
|
||||
},
|
||||
plugins: {
|
||||
legend: { labels: { filter: (item) => item.datasetIndex !== 1 } },
|
||||
tooltip: { callbacks: {
|
||||
title: (items) => items.length ? date_heure(items[0].parsed.x) : '',
|
||||
label: function (item) {
|
||||
if (item.datasetIndex === 1) return null;
|
||||
if (item.datasetIndex === 0) {
|
||||
const pt = donnees.points[item.dataIndex];
|
||||
return TEXTES_COURBE.bande + ' : ' + nombre(pt.min, p) + ' / ' + nombre(pt.max, p) + u;
|
||||
}
|
||||
return TEXTES_COURBE.moyenne + ' : ' + nombre(item.parsed.y, p) + u;
|
||||
}
|
||||
} }
|
||||
}
|
||||
}
|
||||
});
|
||||
if (donnees.stats) {
|
||||
const s = donnees.stats;
|
||||
document.getElementById('stats_courbe').textContent =
|
||||
TEXTES_COURBE.min + ' : ' + nombre(s.min.v, p) + u + ' (' + date_heure(s.min.t) + ') ' +
|
||||
TEXTES_COURBE.max + ' : ' + nombre(s.max.v, p) + u + ' (' + date_heure(s.max.t) + ') ' +
|
||||
TEXTES_COURBE.moyenne + ' : ' + nombre(s.moyenne, p) + u;
|
||||
}
|
||||
}
|
||||
|
||||
function Ouvrir(type, element, nom, variable, periode)
|
||||
{
|
||||
etat = { type: type, element: element, nom: nom,
|
||||
variable: (TEXTES_COURBE.variables[type] && TEXTES_COURBE.variables[type][variable]) ? variable : (VARIABLE_PAR_DEFAUT[type] || null),
|
||||
periode: TEXTES_COURBE.periodes[periode] ? periode : '24h' };
|
||||
panneau.hidden = false;
|
||||
Afficher();
|
||||
}
|
||||
|
||||
function Fermer()
|
||||
{
|
||||
panneau.hidden = true;
|
||||
requete++;
|
||||
if (graphique) { graphique.destroy(); graphique = null; }
|
||||
etat = null;
|
||||
history.replaceState(null, '', location.pathname + location.search);
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
const ligne = e.target.closest('tr.avec_courbe');
|
||||
if (ligne && !panneau.contains(ligne)) Ouvrir(ligne.dataset.type, ligne.dataset.element, ligne.dataset.nom, null, null);
|
||||
});
|
||||
document.getElementById('fermer_courbe').addEventListener('click', Fermer);
|
||||
panneau.addEventListener('click', function (e) { if (e.target === panneau) Fermer(); });
|
||||
document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !panneau.hidden) Fermer(); });
|
||||
|
||||
// Réouverture après rechargement (adresse #courbe=type.element.variable.periode), si l'élément existe toujours :
|
||||
const m = location.hash.match(/^#courbe=(\w+)\.(\d+)\.([\w-]+)\.(\w+)$/);
|
||||
if (m) {
|
||||
const ligne = document.querySelector('tr.avec_courbe[data-type="' + m[1] + '"][data-element="' + m[2] + '"]');
|
||||
if (ligne) Ouvrir(m[1], m[2], ligne.dataset.nom, m[3], m[4]);
|
||||
}
|
||||
})();
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------
|
||||
// Arrivée d'une nouvelle archive (synthèse §50) : sur la vue "Données du bateau" (génération active), toutes les
|
||||
// 30 s, la page demande au portail la date de la dernière archive traitée pour ce bateau (bateau_etat.php : une
|
||||
// requête légère, droits contrôlés) ; si elle a changé depuis l'affichage, la page se recharge. Arrêt en cas de
|
||||
// session expirée ou de refus.
|
||||
//------------------------------------------------------------------------------------------------------------
|
||||
// Juste après une demande d'archive (étape 3d, data-demande) : vérification toutes les 3 s pendant 90 s, pour une mise
|
||||
// à jour quasi immédiate dès que l'archive est chargée ; ensuite (ou sans demande) : toutes les 30 s.
|
||||
const TEXTE_SANS_REPONSE = <?php echo json_encode(t("Pas de réponse du bateau pour l'instant (hors connexion ?) : affichage des dernières données reçues."), JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); ?>;
|
||||
(function () {
|
||||
const surveillance = document.getElementById('surveillance_archive');
|
||||
if (!surveillance) return;
|
||||
const fin_rapide = (surveillance.dataset.demande === '1') ? Date.now() + 90000 : 0;
|
||||
function Verifier()
|
||||
{
|
||||
fetch('bateau_etat.php?id=' + encodeURIComponent(surveillance.dataset.idBateau), { cache: 'no-store', credentials: 'same-origin' })
|
||||
.then(function (reponse) {
|
||||
if (!reponse.ok) return 'arret'; // session expirée ou accès refusé : on arrête de vérifier
|
||||
return reponse.json();
|
||||
})
|
||||
.then(function (etat) {
|
||||
if (etat === 'arret') return;
|
||||
if (etat && (etat.date || '') !== surveillance.dataset.date) { location.reload(); return; }
|
||||
const info = document.getElementById('info_mise_a_jour');
|
||||
if (info && fin_rapide && Date.now() >= fin_rapide && info.classList.contains('en_cours')) {
|
||||
info.classList.remove('en_cours');
|
||||
info.textContent = TEXTE_SANS_REPONSE;
|
||||
}
|
||||
setTimeout(Verifier, Date.now() < fin_rapide ? 3000 : 30000);
|
||||
})
|
||||
.catch(function () { setTimeout(Verifier, 30000); }); // réseau momentanément indisponible
|
||||
}
|
||||
setTimeout(Verifier, fin_rapide ? 3000 : 30000);
|
||||
})();
|
||||
|
||||
|
||||
function toggle_help(){
|
||||
var help = document.getElementById("help");
|
||||
|
||||
Reference in New Issue
Block a user