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:
@@ -7,3 +7,4 @@ vendor/*
|
|||||||
!vendor/phpqrcode/
|
!vendor/phpqrcode/
|
||||||
vendor/phpqrcode/cache/
|
vendor/phpqrcode/cache/
|
||||||
html/vendor_OLD/
|
html/vendor_OLD/
|
||||||
|
demandes/
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ define("ANNEE_CREATION", 2019);
|
|||||||
//=====================================================================
|
//=====================================================================
|
||||||
// Répertoires et fichiers :
|
// Répertoires et fichiers :
|
||||||
define("REPERTOIRE_TEMP", REPERTOIRE_HOME."/temp"); // Pas de '/' de terminaison
|
define("REPERTOIRE_TEMP", REPERTOIRE_HOME."/temp"); // Pas de '/' de terminaison
|
||||||
|
define("REPERTOIRE_LOGS", REPERTOIRE_HOME."/logs"); // Pas de '/' de terminaison
|
||||||
|
|
||||||
if (PRODUCTION)
|
if (PRODUCTION)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ require_once(__DIR__.'/fonctions_mail.php'); // Contient les fonctions d'émi
|
|||||||
require_once(__DIR__.'/fonctions_pdf.php'); // Contient les fonctions de création de fichiers PDF
|
require_once(__DIR__.'/fonctions_pdf.php'); // Contient les fonctions de création de fichiers PDF
|
||||||
require_once(__DIR__.'/fonctions_traductions.php');
|
require_once(__DIR__.'/fonctions_traductions.php');
|
||||||
require_once(__DIR__.'/fonctions_utilitaires.php');
|
require_once(__DIR__.'/fonctions_utilitaires.php');
|
||||||
|
require_once(__DIR__.'/fonctions_consultation.php'); // Consultation des données d'un bateau (bases de génération)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ function Ouverture_bdd_bateau($id_bateau)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$requete = "SELECT Nom_base FROM TB_Generations_bateau WHERE ID_Bateau=? AND Date_fin IS NULL";
|
$requete = "SELECT Nom_base FROM TB_Generations_bateaux WHERE ID_Bateau=? AND Date_fin IS NULL";
|
||||||
$stmt = $db_principale->prepare($requete);
|
$stmt = $db_principale->prepare($requete);
|
||||||
$stmt->bind_param('i', $id_bateau);
|
$stmt->bind_param('i', $id_bateau);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|||||||
@@ -0,0 +1,584 @@
|
|||||||
|
<?php
|
||||||
|
//************************************************************************************************************
|
||||||
|
// fonctions_consultation.php - Consultation des données d'un bateau sur le portail, à partir de la base de
|
||||||
|
// génération alimentée par les archives (lot 3, synthèse §50).
|
||||||
|
//
|
||||||
|
// Les règles d'affichage (familles, éléments affichés, conversions d'unités, valeurs textuelles, coordonnées) sont
|
||||||
|
// reprises de l'API du CM5 (route /elements de shipheart_api, elle-même fidèle à l'application native), pour que
|
||||||
|
// le portail montre les valeurs comme l'écran du bord.
|
||||||
|
//
|
||||||
|
// Ces fonctions n'utilisent JAMAIS $_SESSION['db'] pour la base du bateau : une connexion dédiée (mysqli) est
|
||||||
|
// ouverte et refermée par la page, la connexion principale du site reste intacte.
|
||||||
|
//************************************************************************************************************
|
||||||
|
|
||||||
|
// Types de sources (TB_Familles.Type_source) :
|
||||||
|
const CONSULTATION_TYPE_SOURCE = array('circuits' => 1, 'capteurs' => 2, 'reservoirs' => 3, 'batteries' => 4);
|
||||||
|
|
||||||
|
// États d'un circuit (TB_Circuits.Etat), hors 0 = OK :
|
||||||
|
const CONSULTATION_ETATS_CIRCUIT = array(1 => "Limitation", 2 => "Protection", 3 => "Disjoncté", 8 => "Absence de terre", 9 => "Absence de masse");
|
||||||
|
|
||||||
|
// Types d'entrées capteur (TB_Capteurs.Type_entrees) et conversions d'unités (TB_Variables.Conversion_unite) :
|
||||||
|
const CONSULTATION_TYPE_ENTREES_AUX = 1;
|
||||||
|
const CONSULTATION_TYPE_ENTREES_CALCULEE_BOOLEEN = 20;
|
||||||
|
const CONSULTATION_TYPE_UNITE_VOLUME = 1;
|
||||||
|
const CONSULTATION_TYPE_UNITE_TEMPERATURE = 2;
|
||||||
|
const CONSULTATION_TYPE_UNITE_LATITUDE = 3;
|
||||||
|
const CONSULTATION_TYPE_UNITE_LONGITUDE = 4;
|
||||||
|
|
||||||
|
// Historique des déclenchements d'events (Appli du CM5, bdd.h : TYPE_SOURCE_HISTORIQUE_EVENTS, EVENT_DECLENCHEMENT -
|
||||||
|
// ID_Source = clé primaire de l'event). NB : le commentaire de la colonne TB_Historique.Type_source dans le schéma
|
||||||
|
// ("6=usb") ne correspond pas à bdd.h - c'est bdd.h qui fait foi.
|
||||||
|
const CONSULTATION_HISTORIQUE_TYPE_SOURCE_EVENTS = 6;
|
||||||
|
const CONSULTATION_HISTORIQUE_EVENT_DECLENCHEMENT = 1;
|
||||||
|
const CONSULTATION_HISTORIQUE_EVENT_FIN_DECLENCHEMENT = 2; // Fin de la condition (ajouté dans l'Appli en octobre 2026)
|
||||||
|
|
||||||
|
// Période d'archivage par défaut (secondes) si TB_Configuration.Periode_sauvegarde_bdd est absente ou invalide :
|
||||||
|
const CONSULTATION_PERIODE_ARCHIVAGE_DEFAUT = 7200;
|
||||||
|
|
||||||
|
|
||||||
|
//************************************************************************************************************
|
||||||
|
// Fonction : Générations d'un bateau (base principale), de la plus récente à la plus ancienne.
|
||||||
|
// Reçoit : La clé primaire du bateau
|
||||||
|
// Retour : Tableau de lignes (Cle_primaire, Numero_generation, Nom_base, Motif, Version_soft, Date_debut, Date_fin)
|
||||||
|
//************************************************************************************************************
|
||||||
|
function Generations_bateau($id_bateau)
|
||||||
|
{
|
||||||
|
$generations = array();
|
||||||
|
$reponse = Requete_preparee_bdd("SELECT Cle_primaire, Numero_generation, Nom_base, Motif, Version_soft, Date_debut, Date_fin
|
||||||
|
FROM TB_Generations_bateaux WHERE ID_Bateau=? ORDER BY Numero_generation DESC",
|
||||||
|
array(array('i', $id_bateau)), __FILE__, __LINE__);
|
||||||
|
while ($donnees = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC)) $generations[] = $donnees;
|
||||||
|
Liberation_jeu_de_resultat_bdd($reponse);
|
||||||
|
return $generations;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//************************************************************************************************************
|
||||||
|
// Fonction : Connexion DÉDIÉE à la base d'une génération (lecture), sans toucher à $_SESSION['db'].
|
||||||
|
// Reçoit : Le nom de la base - TOUJOURS issu de TB_Generations_bateaux, jamais d'une saisie
|
||||||
|
// Retour : L'objet mysqli, ou null en cas d'échec (journalisé)
|
||||||
|
//************************************************************************************************************
|
||||||
|
function Connexion_generation($nom_base)
|
||||||
|
{
|
||||||
|
if (preg_match('/^[A-Za-z0-9_]+$/', (string)$nom_base) !== 1) return null;
|
||||||
|
// Serveur : celui de la base principale (BDD_HOST), joignable depuis le conteneur du site - les bases des
|
||||||
|
// générations sont sur le même serveur MariaDB. BDD_BATEAUX_HOST/PORT (127.0.0.1:3307) ne conviennent qu'aux
|
||||||
|
// scripts NATIFS du VPS (archivage) : dans le conteneur, 127.0.0.1 désigne le conteneur lui-même (synthèse §50).
|
||||||
|
// Compte : celui des bases des bateaux (à remplacer par un compte en lecture seule - lot transverse).
|
||||||
|
mysqli_report(MYSQLI_REPORT_OFF);
|
||||||
|
$hote = BDD_HOST;
|
||||||
|
$port = null;
|
||||||
|
if (preg_match('/^(.+):(\d+)$/', $hote, $m) === 1) { $hote = $m[1]; $port = (int)$m[2]; }
|
||||||
|
$db = @new mysqli($hote, BDD_BATEAUX_USER, BDD_BATEAUX_PASSWORD, $nom_base, $port);
|
||||||
|
if ($db->connect_errno)
|
||||||
|
{
|
||||||
|
Enregistrement_log("\tConnexion_generation : échec de connexion à ".$nom_base." (".$db->connect_error.")", __FILE__, __LINE__);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$db->set_charset('utf8mb4');
|
||||||
|
// Dates des colonnes TIMESTAMP (TB_Historique...) lues en UTC, comme elles sont produites par le CM5, quel que soit
|
||||||
|
// le fuseau par défaut du serveur MariaDB :
|
||||||
|
$db->query("SET time_zone = '+00:00'");
|
||||||
|
return $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//************************************************************************************************************
|
||||||
|
// Fonction : Valeur d'un paramètre de TB_Configuration de la base du bateau (telle qu'à la dernière archive).
|
||||||
|
// Retour : La valeur (chaîne), ou $defaut si absente
|
||||||
|
//************************************************************************************************************
|
||||||
|
function Configuration_bateau($db, $nom, $defaut = null)
|
||||||
|
{
|
||||||
|
$stmt = $db->prepare("SELECT Valeur FROM TB_Configuration WHERE Nom=?");
|
||||||
|
if (!$stmt) return $defaut;
|
||||||
|
$stmt->bind_param('s', $nom);
|
||||||
|
$stmt->execute();
|
||||||
|
$ligne = $stmt->get_result()->fetch_row();
|
||||||
|
$stmt->close();
|
||||||
|
return $ligne ? $ligne[0] : $defaut;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//************************************************************************************************************
|
||||||
|
// Fonction : Période d'archivage du bateau, en secondes (TB_Configuration.Periode_sauvegarde_bdd) - base des
|
||||||
|
// seuils de l'indicateur de fraîcheur (orange jusqu'à 1,25 fois la période, rouge au-delà).
|
||||||
|
//************************************************************************************************************
|
||||||
|
function Periode_archivage_bateau($db)
|
||||||
|
{
|
||||||
|
$periode = (int)Configuration_bateau($db, 'Periode_sauvegarde_bdd', 0);
|
||||||
|
return ($periode > 0) ? $periode : CONSULTATION_PERIODE_ARCHIVAGE_DEFAUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//************************************************************************************************************
|
||||||
|
// Conversions et formats - traduction de l'API du CM5 (conversion_volume, conversion_temperature,
|
||||||
|
// symbole_unite_volume, symbole_unite_temperature, chaine_latitude, chaine_longitude).
|
||||||
|
//************************************************************************************************************
|
||||||
|
function Conversion_volume($valeur, $unite)
|
||||||
|
{
|
||||||
|
switch ((int)$unite) { case 1: return $valeur / 3.785411784; case 2: return $valeur / 4.546090; default: return $valeur; }
|
||||||
|
}
|
||||||
|
function Symbole_unite_volume($unite)
|
||||||
|
{
|
||||||
|
switch ((int)$unite) { case 1: return "US Ga"; case 2: return "Imp. Ga"; default: return "lit."; }
|
||||||
|
}
|
||||||
|
function Conversion_temperature($valeur, $unite)
|
||||||
|
{
|
||||||
|
return ((int)$unite == 1) ? ($valeur * 1.8) + 32.0 : $valeur;
|
||||||
|
}
|
||||||
|
function Symbole_unite_temperature($unite)
|
||||||
|
{
|
||||||
|
return ((int)$unite == 1) ? "°F" : "°C";
|
||||||
|
}
|
||||||
|
function Chaine_coordonnee($valeur, $positif, $negatif)
|
||||||
|
{
|
||||||
|
// Le signe est porté par la lettre (N/S, E/W) : degrés et minutes en valeur absolue. (L'API du CM5 garde le signe,
|
||||||
|
// ce qui donne par exemple -12°-30.000' S pour -12,5 - à corriger aussi de son côté.)
|
||||||
|
$absolue = abs((float)$valeur);
|
||||||
|
$degres = (int)$absolue;
|
||||||
|
$minutes = ($absolue - $degres) * 60.0;
|
||||||
|
return $degres."°".number_format($minutes, 3, '.', '')."' ".(($valeur >= 0) ? $positif : $negatif);
|
||||||
|
}
|
||||||
|
function Nombre_affiche($valeur, $precision)
|
||||||
|
{
|
||||||
|
return number_format((float)$valeur, max(0, (int)$precision), ',', ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//************************************************************************************************************
|
||||||
|
// Fonction : Éléments d'un domaine, regroupés par famille, avec leurs dernières valeurs (telles qu'à la dernière
|
||||||
|
// archive), prêts à afficher. Mêmes éléments que l'écran du bord : circuits et capteurs dont
|
||||||
|
// Priorite_affichage_accueil > 0, toutes les batteries et tous les réservoirs.
|
||||||
|
// Reçoit : La connexion à la base de génération, le domaine ('circuits', 'capteurs', 'batteries', 'reservoirs')
|
||||||
|
// Retour : Tableau de familles : ['nom' => ..., 'elements' => [['nom'=>..., 'valeurs'=>[...], 'defaut'=>texte|null]]]
|
||||||
|
// Les textes sont BRUTS (à échapper à l'affichage).
|
||||||
|
//************************************************************************************************************
|
||||||
|
function Elements_domaine($db, $domaine)
|
||||||
|
{
|
||||||
|
if (!isset(CONSULTATION_TYPE_SOURCE[$domaine])) return array();
|
||||||
|
$familles = array();
|
||||||
|
|
||||||
|
$stmt = $db->prepare("SELECT Cle_primaire, Nom FROM TB_Familles WHERE Type_source=? ORDER BY Priorite_affichage, Cle_primaire");
|
||||||
|
$type_source = CONSULTATION_TYPE_SOURCE[$domaine];
|
||||||
|
$stmt->bind_param('i', $type_source);
|
||||||
|
$stmt->execute();
|
||||||
|
$resultat = $stmt->get_result();
|
||||||
|
while ($famille = $resultat->fetch_assoc()) $familles[(int)$famille['Cle_primaire']] = array('nom' => $famille['Nom'], 'elements' => array());
|
||||||
|
$stmt->close();
|
||||||
|
if (count($familles) == 0) return array();
|
||||||
|
|
||||||
|
switch ($domaine)
|
||||||
|
{
|
||||||
|
case 'circuits':
|
||||||
|
$requete = "SELECT Cle_primaire, ID_Famille, Nom, Etat_systeme, Etat, Consigne, Tension, Intensite
|
||||||
|
FROM TB_Circuits WHERE Priorite_affichage_accueil > 0 ORDER BY Priorite_affichage_accueil, Cle_primaire";
|
||||||
|
break;
|
||||||
|
case 'capteurs':
|
||||||
|
$requete = "SELECT C.Cle_primaire, C.ID_Famille, C.Nom, C.Etat_systeme, C.Etat, C.Type_entrees, C.Valeur, C.Precision_affichage_accueil,
|
||||||
|
C.Liste_valeurs_textes, V.Unite, V.Conversion_unite
|
||||||
|
FROM TB_Capteurs C LEFT JOIN TB_Variables V ON V.Cle_primaire = C.ID_Variable
|
||||||
|
WHERE C.Priorite_affichage_accueil > 0 ORDER BY C.Priorite_affichage_accueil, C.Nom";
|
||||||
|
$unite_volume = Configuration_bateau($db, 'Unite_volume', 0);
|
||||||
|
$unite_temperature = Configuration_bateau($db, 'Unite_temperature', 0);
|
||||||
|
break;
|
||||||
|
case 'batteries':
|
||||||
|
$requete = "SELECT Cle_primaire, ID_Famille, Nom, Etat_systeme, Soc, Tension FROM TB_Batteries ORDER BY Priorite_affichage_accueil, Cle_primaire";
|
||||||
|
break;
|
||||||
|
case 'reservoirs':
|
||||||
|
$requete = "SELECT Cle_primaire, ID_Famille, Nom, Etat_systeme, Etat_entrees, Niveau, Capacite FROM TB_Reservoirs ORDER BY Priorite_affichage_accueil, Cle_primaire";
|
||||||
|
$unite_volume = Configuration_bateau($db, 'Unite_volume', 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$resultat = $db->query($requete);
|
||||||
|
if (!$resultat) return array();
|
||||||
|
|
||||||
|
while ($e = $resultat->fetch_assoc())
|
||||||
|
{
|
||||||
|
$id_famille = (int)$e['ID_Famille'];
|
||||||
|
if (!isset($familles[$id_famille])) continue; // Élément rattaché à une famille d'un autre domaine ou inexistante
|
||||||
|
$valeurs = array();
|
||||||
|
$defaut = ((int)$e['Etat_systeme'] != 0) ? t("Défaut système") : null;
|
||||||
|
$courbe = true; // Courbe historique disponible (pas pour une latitude ou une longitude)
|
||||||
|
|
||||||
|
switch ($domaine)
|
||||||
|
{
|
||||||
|
case 'circuits':
|
||||||
|
// Consigne en % (100 = marche pleine ; une valeur intermédiaire = variateur) :
|
||||||
|
$consigne = (int)$e['Consigne'];
|
||||||
|
if ($consigne <= 0) $valeurs[] = t("Arrêt");
|
||||||
|
else if ($consigne >= 100) $valeurs[] = t("Marche");
|
||||||
|
else $valeurs[] = t("Marche")." (".$consigne." %)";
|
||||||
|
if ((float)$e['Intensite'] != 0) $valeurs[] = Nombre_affiche($e['Intensite'], 1)." A";
|
||||||
|
if ((float)$e['Tension'] != 0) $valeurs[] = Nombre_affiche($e['Tension'], 1)." V";
|
||||||
|
if ($defaut === null && (int)$e['Etat'] != 0) $defaut = t(CONSULTATION_ETATS_CIRCUIT[(int)$e['Etat']] ?? "Défaut");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'capteurs':
|
||||||
|
$brute = (float)$e['Valeur'];
|
||||||
|
$type_unite = (int)$e['Conversion_unite'];
|
||||||
|
$texte_liste = null;
|
||||||
|
if ((int)$e['Type_entrees'] == CONSULTATION_TYPE_ENTREES_AUX || (int)$e['Type_entrees'] == CONSULTATION_TYPE_ENTREES_CALCULEE_BOOLEEN)
|
||||||
|
{
|
||||||
|
foreach (explode(';', (string)$e['Liste_valeurs_textes']) as $paire)
|
||||||
|
{
|
||||||
|
$champs = explode('/', $paire);
|
||||||
|
if (count($champs) == 2 && (float)$champs[0] == $brute) { $texte_liste = $champs[1]; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($type_unite == CONSULTATION_TYPE_UNITE_LATITUDE) { $valeurs[] = Chaine_coordonnee($brute, "N", "S"); $courbe = false; }
|
||||||
|
else if ($type_unite == CONSULTATION_TYPE_UNITE_LONGITUDE) { $valeurs[] = Chaine_coordonnee($brute, "E", "W"); $courbe = false; }
|
||||||
|
else if ($texte_liste !== null) $valeurs[] = $texte_liste;
|
||||||
|
else if ($type_unite == CONSULTATION_TYPE_UNITE_VOLUME) $valeurs[] = Nombre_affiche(Conversion_volume($brute, $unite_volume), $e['Precision_affichage_accueil'])." ".Symbole_unite_volume($unite_volume);
|
||||||
|
else if ($type_unite == CONSULTATION_TYPE_UNITE_TEMPERATURE) $valeurs[] = Nombre_affiche(Conversion_temperature($brute, $unite_temperature), $e['Precision_affichage_accueil'])." ".Symbole_unite_temperature($unite_temperature);
|
||||||
|
else $valeurs[] = trim(Nombre_affiche($brute, $e['Precision_affichage_accueil'])." ".$e['Unite']);
|
||||||
|
if ($defaut === null && (int)$e['Etat'] > 0) $defaut = t("Défaut capteur");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'batteries':
|
||||||
|
$valeurs[] = Nombre_affiche($e['Soc'], 0)." %";
|
||||||
|
$valeurs[] = Nombre_affiche($e['Tension'], 2)." V";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'reservoirs':
|
||||||
|
$valeurs[] = Nombre_affiche($e['Niveau'], 0)." %";
|
||||||
|
if ((float)$e['Capacite'] > 0)
|
||||||
|
{
|
||||||
|
$valeurs[] = "≈ ".Nombre_affiche(Conversion_volume((float)$e['Niveau'] * (float)$e['Capacite'] / 100.0, $unite_volume), 0)." ".Symbole_unite_volume($unite_volume);
|
||||||
|
}
|
||||||
|
if ($defaut === null && (int)$e['Etat_entrees'] > 0) $defaut = t("Défaut sondes");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$familles[$id_famille]['elements'][] = array('id' => (int)$e['Cle_primaire'], 'nom' => $e['Nom'], 'valeurs' => $valeurs, 'defaut' => $defaut, 'courbe' => $courbe);
|
||||||
|
}
|
||||||
|
$resultat->free();
|
||||||
|
|
||||||
|
// Familles sans élément affiché : retirées
|
||||||
|
return array_values(array_filter($familles, fn($f) => count($f['elements']) > 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//************************************************************************************************************
|
||||||
|
// Fonction : Derniers ÉPISODES d'alarmes (synthèse §50), à partir des déclenchements (EVENT_DECLENCHEMENT) et des fins
|
||||||
|
// de condition (EVENT_FIN_DECLENCHEMENT) enregistrés dans TB_Historique.
|
||||||
|
// - Alarme = event ayant au moins un indicateur Alarme_... actif (son, LED, soutien, source, message, SMS,
|
||||||
|
// e-mail), selon sa définition ACTUELLE - un event sans indicateur (bouton poussoir...) n'apparaît jamais.
|
||||||
|
// - Event à condition sur une source (Type_source 1 à 4) : un épisode = un déclenchement et la fin
|
||||||
|
// suivante du même event, avec sa durée. Cas particuliers signalés : déclenchement le plus récent sans fin
|
||||||
|
// ('en_cours' : en cours à la dernière archive), déclenchement suivi d'un autre déclenchement sans fin
|
||||||
|
// entre les deux ('fin_non_enregistree' : données antérieures à l'enregistrement des fins), fin dont le
|
||||||
|
// déclenchement est hors de la période lue ('debut_anterieur').
|
||||||
|
// - Event horaire (Type_source 0) : ponctuel, sans durée ni fin ('ponctuel').
|
||||||
|
// Reçoit : La connexion à la base de génération, le nombre maximal d'épisodes
|
||||||
|
// Retour : Tableau, du plus récent au plus ancien, de ['debut_utc', 'fin_utc' (ISO 8601 Z ou null), 'duree' (secondes
|
||||||
|
// ou null), 'statut' (termine, en_cours, fin_non_enregistree, debut_anterieur, ponctuel), 'nom',
|
||||||
|
// 'message'] - textes bruts, à échapper à l'affichage
|
||||||
|
//************************************************************************************************************
|
||||||
|
function Episodes_alarmes($db, $limite = 20)
|
||||||
|
{
|
||||||
|
// Les dernières lignes (déclenchements et fins), lues en ordre décroissant puis traitées dans l'ordre chronologique.
|
||||||
|
// Marge de lecture : plusieurs lignes par épisode, et des events qui s'entremêlent.
|
||||||
|
$stmt = $db->prepare("SELECT H.Date, H.Type, H.ID_Source, E.Nom, E.Texte_message, E.Type_source
|
||||||
|
FROM TB_Historique H
|
||||||
|
JOIN TB_Events E ON E.Cle_primaire = H.ID_Source
|
||||||
|
WHERE H.Type_source = ? AND H.Type IN (?, ?)
|
||||||
|
AND (E.Alarme_son > 0 OR E.Alarme_led > 0 OR E.Alarme_soutien > 0 OR E.Alarme_source > 0
|
||||||
|
OR E.Alarme_message > 0 OR E.Alarme_sms > 0 OR E.Alarme_email > 0)
|
||||||
|
ORDER BY H.Date DESC
|
||||||
|
LIMIT ?");
|
||||||
|
if (!$stmt) return array();
|
||||||
|
$type_source = CONSULTATION_HISTORIQUE_TYPE_SOURCE_EVENTS;
|
||||||
|
$debut = CONSULTATION_HISTORIQUE_EVENT_DECLENCHEMENT;
|
||||||
|
$fin = CONSULTATION_HISTORIQUE_EVENT_FIN_DECLENCHEMENT;
|
||||||
|
$nb_lignes = $limite * 10;
|
||||||
|
$stmt->bind_param('iiii', $type_source, $debut, $fin, $nb_lignes);
|
||||||
|
$stmt->execute();
|
||||||
|
$lignes = array_reverse($stmt->get_result()->fetch_all(MYSQLI_ASSOC));
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
$iso = function($date) { return str_replace(' ', 'T', $date).'Z'; };
|
||||||
|
$episodes = array();
|
||||||
|
$ouverts = array(); // id_event => index de l'épisode ouvert (déclenchement sans fin pour l'instant)
|
||||||
|
foreach ($lignes as $l)
|
||||||
|
{
|
||||||
|
$id = (int)$l['ID_Source'];
|
||||||
|
$base = array('nom' => $l['Nom'], 'message' => trim((string)$l['Texte_message']));
|
||||||
|
if ((int)$l['Type_source'] == 0) // Event horaire : ponctuel
|
||||||
|
{
|
||||||
|
if ((int)$l['Type'] == $debut) $episodes[] = $base + array('debut_utc' => $iso($l['Date']), 'fin_utc' => null, 'duree' => null, 'statut' => 'ponctuel', 'tri' => $l['Date']);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((int)$l['Type'] == $debut)
|
||||||
|
{
|
||||||
|
if (isset($ouverts[$id])) $episodes[$ouverts[$id]]['statut'] = 'fin_non_enregistree';
|
||||||
|
$episodes[] = $base + array('debut_utc' => $iso($l['Date']), 'fin_utc' => null, 'duree' => null, 'statut' => 'en_cours', 'tri' => $l['Date']);
|
||||||
|
$ouverts[$id] = count($episodes) - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (isset($ouverts[$id]))
|
||||||
|
{
|
||||||
|
$i = $ouverts[$id];
|
||||||
|
$episodes[$i]['fin_utc'] = $iso($l['Date']);
|
||||||
|
$episodes[$i]['duree'] = max(0, strtotime($l['Date'].' UTC') - strtotime(str_replace(array('T', 'Z'), array(' ', ''), $episodes[$i]['debut_utc']).' UTC'));
|
||||||
|
$episodes[$i]['statut'] = 'termine';
|
||||||
|
$episodes[$i]['tri'] = $l['Date'];
|
||||||
|
unset($ouverts[$id]);
|
||||||
|
}
|
||||||
|
else $episodes[] = $base + array('debut_utc' => null, 'fin_utc' => $iso($l['Date']), 'duree' => null, 'statut' => 'debut_anterieur', 'tri' => $l['Date']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Du plus récent au plus ancien (date de la dernière ligne connue de l'épisode), limité :
|
||||||
|
usort($episodes, fn($a, $b) => strcmp($b['tri'], $a['tri']));
|
||||||
|
return array_slice($episodes, 0, $limite);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//************************************************************************************************************
|
||||||
|
// Fonction : Durée lisible ("45 s", "27 min", "2 h 05 min", "3 j 4 h").
|
||||||
|
//************************************************************************************************************
|
||||||
|
function Duree_lisible($secondes)
|
||||||
|
{
|
||||||
|
$secondes = (int)$secondes;
|
||||||
|
if ($secondes < 60) return $secondes." s";
|
||||||
|
if ($secondes < 3600) return intdiv($secondes, 60)." min";
|
||||||
|
if ($secondes < 86400) return intdiv($secondes, 3600)." h ".sprintf("%02d", intdiv($secondes % 3600, 60))." min";
|
||||||
|
return intdiv($secondes, 86400)." j ".intdiv($secondes % 86400, 3600)." h";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//************************************************************************************************************
|
||||||
|
// COURBES HISTORIQUES (synthèse §50, étape 3c) - logique reprise de la route /mesures de l'API du CM5 (variable
|
||||||
|
// selon le domaine, conversions d'unités, dernière valeur connue reportée au début de la période), avec trois
|
||||||
|
// différences voulues :
|
||||||
|
// - la courbe s'arrête à la DERNIÈRE ARCHIVE (ou à la fin de la génération), pas à "maintenant" : au-delà, il n'y a
|
||||||
|
// pas de données, et prolonger la dernière valeur en inventerait ;
|
||||||
|
// - périodes étendues (24 h à 1 an), le VPS conservant tout l'historique ;
|
||||||
|
// - regroupement par tranche avec MOYENNE, MINIMUM et MAXIMUM : la courbe moyenne est entourée d'une bande min-max,
|
||||||
|
// pour qu'un pic bref (30 V pendant quelques secondes sur une tension de 26 V) reste visible même sur une longue
|
||||||
|
// période - une simple moyenne par tranche l'effaçait. Les statistiques min/max/moyenne sont calculées sur les
|
||||||
|
// mesures BRUTES de la période (et non sur les moyennes par tranche, qui les sous-estimaient).
|
||||||
|
//************************************************************************************************************
|
||||||
|
|
||||||
|
const CONSULTATION_PERIODES_COURBE = array('24h' => 86400, '7j' => 604800, '30j' => 2592000, '3m' => 7776000, '1a' => 31536000);
|
||||||
|
const CONSULTATION_POINTS_CIBLE = 300; // Nombre de tranches visé, quelle que soit la période
|
||||||
|
|
||||||
|
// Variables traçables pour les circuits et les batteries (le client choisit) : code TB_Variables, colonne de la table
|
||||||
|
// permanente (valeur actuelle), précision. Capteurs et réservoirs : variable unique.
|
||||||
|
const CONSULTATION_VARIABLES_CIRCUIT = array(
|
||||||
|
'consigne' => array('code' => 'Circ_consigne', 'precision' => 0),
|
||||||
|
'intensite' => array('code' => 'Circ_courant', 'precision' => 2), // 1 décimale sur SP4AC
|
||||||
|
'tension' => array('code' => 'Circ_tension', 'precision' => 2), // Circ_tension_secteur et 0 décimale sur SP4AC
|
||||||
|
'frequence' => array('code' => 'Circ_frequence', 'precision' => 1));
|
||||||
|
const CONSULTATION_VARIABLES_BATTERIE = array(
|
||||||
|
'soc' => array('code' => 'Batt_soc', 'precision' => 1),
|
||||||
|
'tension' => array('code' => 'Batt_tension', 'precision' => 2),
|
||||||
|
'temperature' => array('code' => 'Batt_temperature', 'precision' => 1),
|
||||||
|
'courant_differentiel' => array('code' => 'Batt_courant_differentiel', 'precision' => 2),
|
||||||
|
'courant_charge' => array('code' => 'Batt_courant_charge', 'precision' => 2),
|
||||||
|
'courant_decharge' => array('code' => 'Batt_courant_decharge', 'precision' => 2),
|
||||||
|
'soh' => array('code' => 'Batt_soh', 'precision' => 1),
|
||||||
|
'autonomie' => array('code' => 'Batt_autonomie', 'precision' => 1));
|
||||||
|
const CONSULTATION_TYPE_MODULE_SP4AC = 3;
|
||||||
|
|
||||||
|
|
||||||
|
//************************************************************************************************************
|
||||||
|
// Fonction : Données d'une courbe historique.
|
||||||
|
// Reçoit : La connexion à la base de génération, le domaine, l'élément (clé primaire), la variable (circuits,
|
||||||
|
// batteries ; ignorée sinon), la période (clé de CONSULTATION_PERIODES_COURBE), la date de fin (UTC,
|
||||||
|
// 'Y-m-d H:i:s' : dernière archive, ou fin de la génération)
|
||||||
|
// Retour : ['debut', 'fin' (ms), 'unite', 'precision', 'points' => [['t' (ms), 'v', 'min', 'max']], 'stats' (ou null)
|
||||||
|
// => ['min' => ['v', 't'], 'max' => ['v', 't'], 'moyenne']] ou ['erreur' => texte]
|
||||||
|
//************************************************************************************************************
|
||||||
|
function Courbe_element($db, $type, $id_element, $variable, $periode, $date_fin_utc)
|
||||||
|
{
|
||||||
|
if (!isset(CONSULTATION_PERIODES_COURBE[$periode])) return array('erreur' => "période invalide");
|
||||||
|
$fin = strtotime($date_fin_utc.' UTC');
|
||||||
|
if ($fin === false) return array('erreur' => "date de fin invalide");
|
||||||
|
$duree = CONSULTATION_PERIODES_COURBE[$periode];
|
||||||
|
$debut = $fin - $duree;
|
||||||
|
$regroupement = max(1, intdiv($duree, CONSULTATION_POINTS_CIBLE));
|
||||||
|
|
||||||
|
$valeur_unique = function($requete, $types, ...$parametres) use ($db) {
|
||||||
|
$stmt = $db->prepare($requete);
|
||||||
|
if (!$stmt) return null;
|
||||||
|
$stmt->bind_param($types, ...$parametres);
|
||||||
|
$stmt->execute();
|
||||||
|
$ligne = $stmt->get_result()->fetch_row();
|
||||||
|
$stmt->close();
|
||||||
|
return $ligne;
|
||||||
|
};
|
||||||
|
$id_variable_code = function($code) use ($valeur_unique) {
|
||||||
|
$ligne = $valeur_unique("SELECT Cle_primaire FROM TB_Variables WHERE Code=?", 's', $code);
|
||||||
|
return $ligne ? (int)$ligne[0] : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Variable et table selon le domaine :
|
||||||
|
switch ($type)
|
||||||
|
{
|
||||||
|
case 'capteurs':
|
||||||
|
$table = "TB_Mesures_capteurs";
|
||||||
|
$ligne = $valeur_unique("SELECT ID_Variable, Precision_affichage_mesures FROM TB_Capteurs WHERE Cle_primaire=?", 'i', $id_element);
|
||||||
|
if (!$ligne) return array('erreur' => "capteur introuvable");
|
||||||
|
$id_variable = (int)$ligne[0];
|
||||||
|
$precision = (int)$ligne[1];
|
||||||
|
break;
|
||||||
|
case 'reservoirs':
|
||||||
|
$table = "TB_Mesures_reservoirs";
|
||||||
|
if (!$valeur_unique("SELECT 1 FROM TB_Reservoirs WHERE Cle_primaire=?", 'i', $id_element)) return array('erreur' => "réservoir introuvable");
|
||||||
|
$id_variable = $id_variable_code('Res_niveau');
|
||||||
|
$precision = 1;
|
||||||
|
break;
|
||||||
|
case 'circuits':
|
||||||
|
$table = "TB_Mesures_circuits";
|
||||||
|
if (!isset(CONSULTATION_VARIABLES_CIRCUIT[$variable])) return array('erreur' => "variable invalide");
|
||||||
|
$ligne = $valeur_unique("SELECT (SELECT Type FROM TB_Modules_config WHERE Adresse_k_bus=TB_Circuits.Adresse_module) FROM TB_Circuits WHERE Cle_primaire=?", 'i', $id_element);
|
||||||
|
if (!$ligne) return array('erreur' => "circuit introuvable");
|
||||||
|
$sp4ac = ((int)$ligne[0] == CONSULTATION_TYPE_MODULE_SP4AC);
|
||||||
|
$code = CONSULTATION_VARIABLES_CIRCUIT[$variable]['code'];
|
||||||
|
$precision = CONSULTATION_VARIABLES_CIRCUIT[$variable]['precision'];
|
||||||
|
if ($sp4ac && $variable == 'tension') { $code = 'Circ_tension_secteur'; $precision = 0; }
|
||||||
|
if ($sp4ac && $variable == 'intensite') $precision = 1;
|
||||||
|
$id_variable = $id_variable_code($code);
|
||||||
|
break;
|
||||||
|
case 'batteries':
|
||||||
|
$table = "TB_Mesures_batteries";
|
||||||
|
if (!isset(CONSULTATION_VARIABLES_BATTERIE[$variable])) return array('erreur' => "variable invalide");
|
||||||
|
if (!$valeur_unique("SELECT 1 FROM TB_Batteries WHERE Cle_primaire=?", 'i', $id_element)) return array('erreur' => "batterie introuvable");
|
||||||
|
$id_variable = $id_variable_code(CONSULTATION_VARIABLES_BATTERIE[$variable]['code']);
|
||||||
|
$precision = CONSULTATION_VARIABLES_BATTERIE[$variable]['precision'];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return array('erreur' => "domaine invalide");
|
||||||
|
}
|
||||||
|
if ($id_variable == 0) return array('erreur' => "variable introuvable dans la configuration du bateau");
|
||||||
|
|
||||||
|
// Unité et conversion (unités du bateau) :
|
||||||
|
$ligne = $valeur_unique("SELECT Unite, Conversion_unite FROM TB_Variables WHERE Cle_primaire=?", 'i', $id_variable);
|
||||||
|
$unite = $ligne ? (string)$ligne[0] : "";
|
||||||
|
$type_unite = $ligne ? (int)$ligne[1] : 0;
|
||||||
|
if ($type_unite == CONSULTATION_TYPE_UNITE_LATITUDE || $type_unite == CONSULTATION_TYPE_UNITE_LONGITUDE) return array('erreur' => "pas de courbe pour une coordonnée");
|
||||||
|
$unite_volume = (int)Configuration_bateau($db, 'Unite_volume', 0);
|
||||||
|
$unite_temperature = (int)Configuration_bateau($db, 'Unite_temperature', 0);
|
||||||
|
if ($type_unite == CONSULTATION_TYPE_UNITE_VOLUME) $unite = Symbole_unite_volume($unite_volume);
|
||||||
|
else if ($type_unite == CONSULTATION_TYPE_UNITE_TEMPERATURE) $unite = Symbole_unite_temperature($unite_temperature);
|
||||||
|
$convertir = function($brut) use ($type_unite, $unite_volume, $unite_temperature) {
|
||||||
|
if ($brut === null) return null;
|
||||||
|
if ($type_unite == CONSULTATION_TYPE_UNITE_VOLUME) return Conversion_volume((float)$brut, $unite_volume);
|
||||||
|
if ($type_unite == CONSULTATION_TYPE_UNITE_TEMPERATURE) return Conversion_temperature((float)$brut, $unite_temperature);
|
||||||
|
return (float)$brut;
|
||||||
|
};
|
||||||
|
|
||||||
|
$date_debut = gmdate('Y-m-d H:i:s', $debut);
|
||||||
|
$date_fin = gmdate('Y-m-d H:i:s', $fin);
|
||||||
|
$points = array();
|
||||||
|
$stats_min = null; $stats_max = null;
|
||||||
|
|
||||||
|
// Dernière valeur connue AVANT la période, reportée au premier instant (élément enregistré seulement sur changement) :
|
||||||
|
$ligne = $valeur_unique("SELECT Valeur FROM ".$table." WHERE ID=? AND ID_Variable=? AND Date<=? AND Valeur IS NOT NULL ORDER BY Date DESC LIMIT 1",
|
||||||
|
'iis', $id_element, $id_variable, $date_debut);
|
||||||
|
if ($ligne)
|
||||||
|
{
|
||||||
|
$v = $convertir($ligne[0]);
|
||||||
|
$points[] = array('t' => $debut * 1000, 'v' => $v, 'min' => $v, 'max' => $v);
|
||||||
|
$stats_min = array('v' => $v, 't' => $debut * 1000);
|
||||||
|
$stats_max = array('v' => $v, 't' => $debut * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tranches : moyenne, minimum, maximum.
|
||||||
|
$stmt = $db->prepare("SELECT UNIX_TIMESTAMP(MIN(Date)), AVG(Valeur), MIN(Valeur), MAX(Valeur) FROM ".$table."
|
||||||
|
WHERE ID=? AND ID_Variable=? AND Date>? AND Date<=? AND Valeur IS NOT NULL
|
||||||
|
GROUP BY UNIX_TIMESTAMP(Date) DIV ? ORDER BY 1");
|
||||||
|
$stmt->bind_param('iissi', $id_element, $id_variable, $date_debut, $date_fin, $regroupement);
|
||||||
|
$stmt->execute();
|
||||||
|
$resultat = $stmt->get_result();
|
||||||
|
while ($l = $resultat->fetch_row())
|
||||||
|
{
|
||||||
|
$points[] = array('t' => (int)$l[0] * 1000, 'v' => $convertir($l[1]), 'min' => $convertir($l[2]), 'max' => $convertir($l[3]));
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
// Statistiques sur les mesures BRUTES de la période (avec la valeur reportée au début) :
|
||||||
|
$ligne = $valeur_unique("SELECT AVG(Valeur), COUNT(*) FROM ".$table." WHERE ID=? AND ID_Variable=? AND Date>? AND Date<=? AND Valeur IS NOT NULL",
|
||||||
|
'iiss', $id_element, $id_variable, $date_debut, $date_fin);
|
||||||
|
$moyenne = ($ligne && (int)$ligne[1] > 0) ? $convertir($ligne[0]) : (($stats_min !== null) ? $stats_min['v'] : null);
|
||||||
|
if ($ligne && (int)$ligne[1] > 0)
|
||||||
|
{
|
||||||
|
foreach (array('ASC' => 'min', 'DESC' => 'max') as $ordre => $cle)
|
||||||
|
{
|
||||||
|
$extreme = $valeur_unique("SELECT Valeur, UNIX_TIMESTAMP(Date) FROM ".$table." WHERE ID=? AND ID_Variable=? AND Date>? AND Date<=? AND Valeur IS NOT NULL ORDER BY Valeur ".$ordre.", Date DESC LIMIT 1",
|
||||||
|
'iiss', $id_element, $id_variable, $date_debut, $date_fin);
|
||||||
|
$candidat = array('v' => $convertir($extreme[0]), 't' => (int)$extreme[1] * 1000);
|
||||||
|
if ($cle == 'min' && ($stats_min === null || $candidat['v'] < $stats_min['v'])) $stats_min = $candidat;
|
||||||
|
if ($cle == 'max' && ($stats_max === null || $candidat['v'] > $stats_max['v'])) $stats_max = $candidat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point de fin : dernière valeur MESURÉE (et non la moyenne de la dernière tranche) prolongée jusqu'à la dernière
|
||||||
|
// archive, pas au-delà :
|
||||||
|
if (count($points) > 0 && end($points)['t'] < $fin * 1000)
|
||||||
|
{
|
||||||
|
$ligne = $valeur_unique("SELECT Valeur FROM ".$table." WHERE ID=? AND ID_Variable=? AND Date<=? AND Valeur IS NOT NULL ORDER BY Date DESC LIMIT 1",
|
||||||
|
'iis', $id_element, $id_variable, $date_fin);
|
||||||
|
$v = $ligne ? $convertir($ligne[0]) : end($points)['v'];
|
||||||
|
$points[] = array('t' => $fin * 1000, 'v' => $v, 'min' => $v, 'max' => $v);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array('debut' => $debut * 1000, 'fin' => $fin * 1000, 'unite' => $unite, 'precision' => $precision, 'points' => $points,
|
||||||
|
'stats' => ($stats_min !== null) ? array('min' => $stats_min, 'max' => $stats_max, 'moyenne' => $moyenne) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//************************************************************************************************************
|
||||||
|
// DEMANDE D'ARCHIVE À L'OUVERTURE DE LA PAGE (synthèse §50, étape 3d).
|
||||||
|
// Le portail (conteneur) ne peut pas lancer le traitement des archives (natif : client mysql, openssl, dossiers
|
||||||
|
// d'archives) : il DÉPOSE une demande - un fichier nommé d'après le numéro du bateau dans /var/www/demandes/a_traiter -
|
||||||
|
// que l'unité systemd ship2net-demandes.path fait traiter aussitôt (traiter_demandes_archivage.php : archive immédiate
|
||||||
|
// demandée au CM5, puis rapatriement et chargement).
|
||||||
|
// Garde-fous : pas de demande si les données ont moins de Delai_demande_archive secondes (TB_Configuration, 300 par
|
||||||
|
// défaut), ni si une demande pour ce bateau date de moins de ce même délai (marqueur /var/www/demandes/dernieres/<N>).
|
||||||
|
//************************************************************************************************************
|
||||||
|
const REPERTOIRE_DEMANDES = "/var/www/demandes";
|
||||||
|
const DELAI_DEMANDE_ARCHIVE_DEFAUT = 300; // secondes
|
||||||
|
|
||||||
|
function Delai_demande_archive()
|
||||||
|
{
|
||||||
|
$reponse = Requete_preparee_bdd("SELECT Valeur FROM TB_Configuration WHERE Nom=? AND Validite=1",
|
||||||
|
array(array('s', 'Delai_demande_archive')), __FILE__, __LINE__);
|
||||||
|
$donnees = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC);
|
||||||
|
Liberation_jeu_de_resultat_bdd($reponse);
|
||||||
|
$delai = $donnees ? (int)$donnees['Valeur'] : 0;
|
||||||
|
return ($delai > 0) ? $delai : DELAI_DEMANDE_ARCHIVE_DEFAUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retour : ['statut' => 'demandee' | 'donnees_recentes' | 'deja_demandee' | 'impossible', 'attente' => secondes avant
|
||||||
|
// qu'une nouvelle demande soit possible (0 si sans objet)] - la page l'affiche, pour que l'utilisateur sache
|
||||||
|
// pourquoi aucune mise à jour n'est demandée.
|
||||||
|
function Demander_archive_si_besoin($id_bateau, $date_derniere_maj_utc)
|
||||||
|
{
|
||||||
|
$id_bateau = (int)$id_bateau;
|
||||||
|
if ($id_bateau <= 0) return array('statut' => 'impossible', 'attente' => 0);
|
||||||
|
$delai = Delai_demande_archive();
|
||||||
|
|
||||||
|
// Données déjà récentes :
|
||||||
|
if ($date_derniere_maj_utc !== null && $date_derniere_maj_utc >= "2020-01-02")
|
||||||
|
{
|
||||||
|
$age = time() - strtotime($date_derniere_maj_utc.' UTC');
|
||||||
|
if ($age < $delai) return array('statut' => 'donnees_recentes', 'attente' => $delai - $age);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demande récente pour ce bateau (quel que soit le nombre de pages ouvertes ou de rechargements) :
|
||||||
|
$marqueur = REPERTOIRE_DEMANDES."/dernieres/".$id_bateau;
|
||||||
|
if (is_file($marqueur))
|
||||||
|
{
|
||||||
|
$age = time() - filemtime($marqueur);
|
||||||
|
if ($age < $delai) return array('statut' => 'deja_demandee', 'attente' => $delai - $age);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!@touch($marqueur) || @file_put_contents(REPERTOIRE_DEMANDES."/a_traiter/".$id_bateau, gmdate('Y-m-d H:i:s')." UTC\n") === false)
|
||||||
|
{
|
||||||
|
Enregistrement_log("\tDemander_archive_si_besoin : dépôt impossible dans ".REPERTOIRE_DEMANDES." (bateau ".$id_bateau.")", __FILE__, __LINE__);
|
||||||
|
return array('statut' => 'impossible', 'attente' => 0);
|
||||||
|
}
|
||||||
|
return array('statut' => 'demandee', 'attente' => 0);
|
||||||
|
}
|
||||||
+16
-73
@@ -108,12 +108,11 @@ function Entete_page_html()
|
|||||||
echo '<link rel="icon" type="image/png" href="images/favicon.png" />';
|
echo '<link rel="icon" type="image/png" href="images/favicon.png" />';
|
||||||
|
|
||||||
|
|
||||||
echo "<link href='https://fonts.googleapis.com/css?family=Montserrat' rel='stylesheet'>";
|
// NB (synthèse §50) : les polices Montserrat et Oswald ne sont plus chargées depuis fonts.googleapis.com - aucune
|
||||||
echo "<link href='https://fonts.googleapis.com/css2?family=Oswald&display=swap' rel='stylesheet'>";
|
// feuille de style ne les utilisait (police du système), et chaque visite transmettait l'adresse IP du visiteur à
|
||||||
echo "<link rel='stylesheet' href='https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' integrity='sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=' crossorigin=''/>";
|
// Google (problème RGPD).
|
||||||
|
// NB (synthèse §50) : Leaflet (cartographie, chargé depuis unpkg.com) et noUiSlider (curseur de dates) ne sont plus
|
||||||
echo "<link rel='stylesheet' href='/vendor/noUiSlider/dist/nouislider.min.css'>";
|
// chargés - ils servaient aux onglets du modèle CM4, retirés au §47 ; aucune page ne les utilise plus.
|
||||||
echo "<script src='/vendor/noUiSlider/dist/nouislider.min.js'></script>";
|
|
||||||
|
|
||||||
// gestion des fichiers CSS (et de scripts spécifiques à certains fichiers) :
|
// gestion des fichiers CSS (et de scripts spécifiques à certains fichiers) :
|
||||||
switch(basename($_SERVER['SCRIPT_FILENAME']))
|
switch(basename($_SERVER['SCRIPT_FILENAME']))
|
||||||
@@ -130,10 +129,13 @@ function Entete_page_html()
|
|||||||
|
|
||||||
echo '<link rel="stylesheet" type="text/css" href="/vendor/bootstrap/bootstrap.min.css" />'; // CSS de Bootstrap
|
echo '<link rel="stylesheet" type="text/css" href="/vendor/bootstrap/bootstrap.min.css" />'; // CSS de Bootstrap
|
||||||
echo '<link rel="stylesheet" type="text/css" media="screen" href="format.css" />'; // CSS général, tous écrans.
|
echo '<link rel="stylesheet" type="text/css" media="screen" href="format.css" />'; // CSS général, tous écrans.
|
||||||
echo '<link rel="stylesheet" type="text/css" media="screen and (max-device-width : 667px)" href="format__telephone.css" />'; // CSS pour les petits écrans (doit être placé à la fin car il override les CSS globaux)
|
// NB (synthèse §50) : "max-width" (largeur de la FENÊTRE) au lieu de "max-device-width" (taille physique de l'écran,
|
||||||
|
// condition dépréciée) - même effet sur un téléphone, et cohérent avec les règles "petit écran" de format.css.
|
||||||
|
echo '<link rel="stylesheet" type="text/css" media="screen and (max-width: 667px)" href="format__telephone.css" />'; // CSS pour les petits écrans (doit être placé à la fin car il override les CSS globaux)
|
||||||
|
|
||||||
// Chargement de srcipts JS généraux :
|
// Chargement de srcipts JS généraux :
|
||||||
echo '<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>'; // Pour Google Charts (graphiques)
|
// NB (synthèse §50) : Google Charts n'est plus chargé (depuis les serveurs de Google, sur toutes les pages) - les
|
||||||
|
// graphiques utilisent Chart.js, hébergé sur le VPS (vendor/chartjs) et chargé seulement par la page qui en a besoin.
|
||||||
echo '<script src="/vendor/jQuery/jquery-3.6.0.min.js"></script>';
|
echo '<script src="/vendor/jQuery/jquery-3.6.0.min.js"></script>';
|
||||||
|
|
||||||
|
|
||||||
@@ -167,11 +169,9 @@ function Entete_page($message_erreur, $message_information)
|
|||||||
//----------------------------------
|
//----------------------------------
|
||||||
// Bandeau gauche :
|
// Bandeau gauche :
|
||||||
echo '<div id="logo_home">
|
echo '<div id="logo_home">
|
||||||
<a id="home" href="">
|
<a href="shipmembers.php" style="padding: 0;" id="logo_heart_a" title="'.t("Retour à l'accueil").'">
|
||||||
<a href="shipmembers.php" style="padding: 0;" id="logo_heart_a" title="'.t("Retour à l'accueil").'">
|
<div id="logo_heart"> </div>
|
||||||
<div id="logo_heart"> </div>
|
<div id="logo_texte"> </div>
|
||||||
<div id="logo_texte"> </div>
|
|
||||||
</a>
|
|
||||||
</a>
|
</a>
|
||||||
</div>';
|
</div>';
|
||||||
|
|
||||||
@@ -209,66 +209,9 @@ function Entete_page($message_erreur, $message_information)
|
|||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------
|
// NB (synthèse §50) : le script du menu mobile de l'ancien site (nav_bar_global, menu_mobile, img_type, portail_pro,
|
||||||
// Menu afficher / masquer -- Mobile
|
// portail_par) a été retiré. Ces éléments n'existent plus : il provoquait une erreur JavaScript à chaque chargement de
|
||||||
//--------------------------------------------------------------------------------------------
|
// page et à chaque clic, et redirigeait le logo vers bienvenue.php / accueil.php, pages qui n'existent plus.
|
||||||
echo '<script>
|
|
||||||
window.onload = function () {
|
|
||||||
pro = document.getElementsByClassName("portail_pro");
|
|
||||||
par = document.getElementsByClassName("portail_par");
|
|
||||||
if ($(window).width() > 850) {
|
|
||||||
if (pro.length > 0) { for (i = 0; i < pro.length ; i++){ pro[i].hidden=true; } document.getElementById(\'par_p\').hidden = true }
|
|
||||||
if (par.length > 0) { for (i = 0; i < par.length ; i++){ par[i].hidden=true; } document.getElementById(\'pro_p\').hidden = true }
|
|
||||||
document.getElementById(\'img_type\').hidden= false;
|
|
||||||
document.getElementById(\'logo_heart_a\').href = "bienvenue.php";
|
|
||||||
} else {
|
|
||||||
if (pro.length > 0) { for (i = 0; i < pro.length ; i++){ pro[i].hidden=false; } document.getElementById(\'par_p\').hidden = false }
|
|
||||||
if (par.length > 0) { for (i = 0; i < par.length ; i++){ par[i].hidden=false; } document.getElementById(\'pro_p\').hidden = false }
|
|
||||||
document.getElementById(\'img_type\').hidden= true;
|
|
||||||
document.getElementById(\'logo_heart_a\').href = "accueil.php";
|
|
||||||
}
|
|
||||||
} ;
|
|
||||||
$(document).ready(function(){
|
|
||||||
function menu_control(){
|
|
||||||
hidden_div = document.getElementById("nav_bar_global").style.display;
|
|
||||||
hidden_div.replace(/[\u200B-\u200D\uFEFF]/g, \'\');
|
|
||||||
|
|
||||||
if (hidden_div == "block") hidden_div = "none";
|
|
||||||
else if (hidden_div == "none") hidden_div = "block";
|
|
||||||
document.getElementById("nav_bar_global").style.display=hidden_div;
|
|
||||||
};
|
|
||||||
$("#menu_mobile").click(menu_control);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener(\'click\', function(e){
|
|
||||||
if (!document.getElementById(\'nav_bar_global\').contains(e.target) && !document.getElementById(\'menu_mobile\').contains(e.target) ){
|
|
||||||
document.getElementById("nav_bar_global").style.display="none";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener(\'touchstart\', function(e){
|
|
||||||
if (!document.getElementById(\'nav_bar_global\').contains(e.target) && !document.getElementById(\'menu_mobile\').contains(e.target) ){
|
|
||||||
document.getElementById("nav_bar_global").style.display="none";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(window).resize(function() {
|
|
||||||
pro = document.getElementsByClassName("portail_pro");
|
|
||||||
par = document.getElementsByClassName("portail_par");
|
|
||||||
if ($(this).width() > 850) {
|
|
||||||
if (pro.length > 0) { for (i = 0; i < pro.length ; i++){ pro[i].hidden=true; } document.getElementById(\'par_p\').hidden = true }
|
|
||||||
if (par.length > 0) { for (i = 0; i < par.length ; i++){ par[i].hidden=true; } document.getElementById(\'pro_p\').hidden = true }
|
|
||||||
document.getElementById(\'img_type\').hidden= false;
|
|
||||||
document.getElementById(\'logo_heart_a\').href = "bienvenue.php";
|
|
||||||
} else {
|
|
||||||
if (pro.length > 0) { for (i = 0; i < pro.length ; i++){ pro[i].hidden=false; } document.getElementById(\'par_p\').hidden = false }
|
|
||||||
if (par.length > 0) { for (i = 0; i < par.length ; i++){ par[i].hidden=false; } document.getElementById(\'pro_p\').hidden = false }
|
|
||||||
document.getElementById(\'img_type\').hidden= true;
|
|
||||||
document.getElementById(\'logo_heart_a\').href = "accueil.php";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>';
|
|
||||||
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -316,33 +316,36 @@ function Conversion_DECtoDMS($lat_lon, $valeur)
|
|||||||
|
|
||||||
//************************************************************************************************************
|
//************************************************************************************************************
|
||||||
// Fonction : Enregistrement dans le fichier de log de l'évènement
|
// Fonction : Enregistrement dans le fichier de log de l'évènement
|
||||||
|
// Ne doit JAMAIS interrompre la page qui l'appelle (synthèse §50) : si le répertoire ou le fichier de log
|
||||||
|
// est inaccessible (cas du conteneur ship2net-php, où /home/koriolan/logs n'était pas monté), le message
|
||||||
|
// part dans le journal d'erreurs de PHP (visible par "docker logs ship2net-php") au lieu de provoquer une
|
||||||
|
// erreur fatale qui masquait l'erreur d'origine.
|
||||||
// Reçoit : Le texte à afficher dans le log, le fichier d'origine et la ligne d'origine
|
// Reçoit : Le texte à afficher dans le log, le fichier d'origine et la ligne d'origine
|
||||||
// Retour : Rien
|
// Retour : Rien
|
||||||
//************************************************************************************************************
|
//************************************************************************************************************
|
||||||
function Enregistrement_log($texte, $fichier, $ligne)
|
function Enregistrement_log($texte, $fichier, $ligne)
|
||||||
{
|
{
|
||||||
$repertoire_logs = REPERTOIRE_LOGS."/www_shipheart";
|
$repertoire_logs = REPERTOIRE_LOGS."/www_shipheart";
|
||||||
if (!file_exists($repertoire_logs) OR !is_dir($repertoire_logs)) // On vérifie d'abord que le répertoire existe, sinon on le créé.
|
|
||||||
{
|
|
||||||
mkdir($repertoire_logs);
|
|
||||||
$message_information .= "Le répertoire de logs [".$repertoire_logs."] n'existait pas. Il vient d'êtrer crééé<br />";
|
|
||||||
}
|
|
||||||
|
|
||||||
$fichier_log = $repertoire_logs.'/log_shipheart_'.date("Y-m-d").'.txt';
|
|
||||||
$prefixe_ligne = date("d/m/Y H:i:s")." [".basename($fichier)." - ".$ligne."] \t: ";
|
$prefixe_ligne = date("d/m/Y H:i:s")." [".basename($fichier)." - ".$ligne."] \t: ";
|
||||||
$suffixe_ligne = "\r\n";
|
$suffixe_ligne = "\r\n";
|
||||||
|
|
||||||
$log = fopen($fichier_log, "a+"); // Ouverture en lecture/écriture en mode append...
|
// Répertoire créé au besoin (y compris les répertoires parents), sans avertissement en cas d'échec :
|
||||||
|
if (!is_dir($repertoire_logs)) @mkdir($repertoire_logs, 0775, true);
|
||||||
|
|
||||||
fputs($log, $prefixe_ligne); // Entete de ligne
|
$log = @fopen($repertoire_logs.'/log_shipheart_'.date("Y-m-d").'.txt', "a");
|
||||||
fputs($log, $texte); // Ecriture du texte de log
|
if ($log !== false)
|
||||||
fputs($log, $suffixe_ligne); // Fin de ligne
|
{
|
||||||
|
fputs($log, $prefixe_ligne.$texte.$suffixe_ligne);
|
||||||
fclose($log); // Fermetue du fichier.
|
fclose($log);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
error_log("Ship2net (fichier de log inaccessible : ".$repertoire_logs.") ".$prefixe_ligne.$texte);
|
||||||
|
}
|
||||||
|
|
||||||
if(SHOW_DEBUG)
|
if(SHOW_DEBUG)
|
||||||
{
|
{
|
||||||
echo $prefixe_ligne . $texte . $suffixe_ligne;
|
echo htmlspecialchars($prefixe_ligne.$texte).$suffixe_ligne;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -315,9 +315,23 @@ function Executer_cron_archivage()
|
|||||||
$boitiers = Lister_boitiers_connus($db);
|
$boitiers = Lister_boitiers_connus($db);
|
||||||
echo count($boitiers) . " boîtier(s) connu(s) avec une adresse NetBird renseignée.\n\n";
|
echo count($boitiers) . " boîtier(s) connu(s) avec une adresse NetBird renseignée.\n\n";
|
||||||
|
|
||||||
|
// Option --bateau=N (synthèse §50, traitement à la demande du portail) : seul le boîtier actuellement affecté au
|
||||||
|
// bateau N est traité. Sans option : tous les boîtiers (passage régulier du timer).
|
||||||
|
$bateau_filtre = 0;
|
||||||
|
foreach ($GLOBALS['argv'] ?? array() as $argument)
|
||||||
|
{
|
||||||
|
if (preg_match('/^--bateau=([1-9][0-9]{0,9})$/', $argument, $m) === 1) $bateau_filtre = (int)$m[1];
|
||||||
|
}
|
||||||
|
if ($bateau_filtre > 0) echo "Traitement limité au bateau {$bateau_filtre}.\n\n";
|
||||||
|
|
||||||
foreach ($boitiers as $boitier)
|
foreach ($boitiers as $boitier)
|
||||||
{
|
{
|
||||||
$adresse_mac = $boitier['Adresse_mac'];
|
$adresse_mac = $boitier['Adresse_mac'];
|
||||||
|
if ($bateau_filtre > 0)
|
||||||
|
{
|
||||||
|
$affectation = Resoudre_affectation_active($db, $adresse_mac);
|
||||||
|
if (!$affectation['existe'] || (int)$affectation['id_bateau'] != $bateau_filtre) continue;
|
||||||
|
}
|
||||||
$verrou = Verrouiller_boitier($adresse_mac);
|
$verrou = Verrouiller_boitier($adresse_mac);
|
||||||
if (!$verrou['ok'])
|
if (!$verrou['ok'])
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ function Creer_generation($db_principale, $id_bateau, $motif, $contenu_sql, $ver
|
|||||||
|
|
||||||
$fichier_temp = tempnam(sys_get_temp_dir(), 'usine_');
|
$fichier_temp = tempnam(sys_get_temp_dir(), 'usine_');
|
||||||
file_put_contents($fichier_temp, $sql_adapte);
|
file_put_contents($fichier_temp, $sql_adapte);
|
||||||
$commande = "mysql --host=" . escapeshellarg(BDD_BATEAUX_HOST) . " --port=" . escapeshellarg(BDD_BATEAUX_PORT)
|
$commande = "mysql --default-character-set=utf8mb4 --host=" . escapeshellarg(BDD_BATEAUX_HOST) . " --port=" . escapeshellarg(BDD_BATEAUX_PORT)
|
||||||
. " --user=" . escapeshellarg(BDD_BATEAUX_USER) . " --password=" . escapeshellarg(BDD_BATEAUX_PASSWORD)
|
. " --user=" . escapeshellarg(BDD_BATEAUX_USER) . " --password=" . escapeshellarg(BDD_BATEAUX_PASSWORD)
|
||||||
. " < " . escapeshellarg($fichier_temp) . " 2>&1";
|
. " < " . escapeshellarg($fichier_temp) . " 2>&1";
|
||||||
exec($commande, $sortie, $code_retour);
|
exec($commande, $sortie, $code_retour);
|
||||||
@@ -449,6 +449,11 @@ function Migrer_generation($db_principale, $generation_active, $version_cible, $
|
|||||||
return ['ok' => true, 'message' => "schéma migré {$version_actuelle} -> {$version_cible} ({$nb_requetes} requête(s))"];
|
return ['ok' => true, 'message' => "schéma migré {$version_actuelle} -> {$version_cible} ({$nb_requetes} requête(s))"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// NB (toutes les commandes mysql de ce fichier, synthèse §50) : --default-character-set=utf8mb4 - sans lui, le
|
||||||
|
// jeu de caractères du client dépend de la configuration du système (latin1 par défaut sur certaines
|
||||||
|
// installations) et un texte UTF-8 sans "SET NAMES" (zone usine, segments de migration) serait stocké
|
||||||
|
// doublement encodé ("Électricité" au lieu de "Électricité").
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Exécute un bloc SQL sur une base bateau via le client mysql en ligne de
|
// Exécute un bloc SQL sur une base bateau via le client mysql en ligne de
|
||||||
// commande (même mécanique que Creer_generation/Charger_contenu_archive - un
|
// commande (même mécanique que Creer_generation/Charger_contenu_archive - un
|
||||||
@@ -459,7 +464,7 @@ function Executer_sql_base($nom_base, $sql)
|
|||||||
{
|
{
|
||||||
$fichier_temp = tempnam(sys_get_temp_dir(), 'migr_');
|
$fichier_temp = tempnam(sys_get_temp_dir(), 'migr_');
|
||||||
file_put_contents($fichier_temp, $sql);
|
file_put_contents($fichier_temp, $sql);
|
||||||
$commande = "mysql --host=" . escapeshellarg(BDD_BATEAUX_HOST) . " --port=" . escapeshellarg(BDD_BATEAUX_PORT)
|
$commande = "mysql --default-character-set=utf8mb4 --host=" . escapeshellarg(BDD_BATEAUX_HOST) . " --port=" . escapeshellarg(BDD_BATEAUX_PORT)
|
||||||
. " --user=" . escapeshellarg(BDD_BATEAUX_USER) . " --password=" . escapeshellarg(BDD_BATEAUX_PASSWORD)
|
. " --user=" . escapeshellarg(BDD_BATEAUX_USER) . " --password=" . escapeshellarg(BDD_BATEAUX_PASSWORD)
|
||||||
. " " . escapeshellarg($nom_base) . " < " . escapeshellarg($fichier_temp) . " 2>&1";
|
. " " . escapeshellarg($nom_base) . " < " . escapeshellarg($fichier_temp) . " 2>&1";
|
||||||
exec($commande, $sortie, $code_retour);
|
exec($commande, $sortie, $code_retour);
|
||||||
@@ -543,7 +548,7 @@ function Charger_contenu_archive($nom_base, $sql_archive)
|
|||||||
|
|
||||||
$fichier_temp = tempnam(sys_get_temp_dir(), 'charge_');
|
$fichier_temp = tempnam(sys_get_temp_dir(), 'charge_');
|
||||||
file_put_contents($fichier_temp, $script);
|
file_put_contents($fichier_temp, $script);
|
||||||
$commande = "mysql --host=" . escapeshellarg(BDD_BATEAUX_HOST) . " --port=" . escapeshellarg(BDD_BATEAUX_PORT)
|
$commande = "mysql --default-character-set=utf8mb4 --host=" . escapeshellarg(BDD_BATEAUX_HOST) . " --port=" . escapeshellarg(BDD_BATEAUX_PORT)
|
||||||
. " --user=" . escapeshellarg(BDD_BATEAUX_USER) . " --password=" . escapeshellarg(BDD_BATEAUX_PASSWORD)
|
. " --user=" . escapeshellarg(BDD_BATEAUX_USER) . " --password=" . escapeshellarg(BDD_BATEAUX_PASSWORD)
|
||||||
. " " . escapeshellarg($nom_base) . " < " . escapeshellarg($fichier_temp) . " 2>&1";
|
. " " . escapeshellarg($nom_base) . " < " . escapeshellarg($fichier_temp) . " 2>&1";
|
||||||
exec($commande, $sortie, $code_retour);
|
exec($commande, $sortie, $code_retour);
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
//************************************************************************************************************
|
||||||
|
// bateau_etat.php - État léger d'un bateau, interrogé toutes les 30 s par la page "Données du bateau" pour détecter
|
||||||
|
// l'arrivée d'une nouvelle archive (synthèse §50). Réponse JSON : {"date": "<date UTC de la dernière archive traitée,
|
||||||
|
// ISO 8601>"} ou {"date": null}. Mêmes droits que la page du bateau : administrateur, ou accès actif à ce bateau.
|
||||||
|
//************************************************************************************************************
|
||||||
|
// Réponse JSON : aucun avertissement PHP ne doit s'y écrire (il la rendrait illisible) - ils vont dans le journal.
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
require_once("../config/config.php");
|
||||||
|
Init_session(basename(__FILE__));
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
header('Cache-Control: no-store');
|
||||||
|
|
||||||
|
if (empty($_SESSION['acces_valide']))
|
||||||
|
{
|
||||||
|
http_response_code(401);
|
||||||
|
echo '{}';
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$id_bateau = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||||
|
$niveau_admin = NIVEAU_ACCES_MINI_ADMINISTRATEUR;
|
||||||
|
$est_administrateur = ((int)$_SESSION['shipmember']['Niveau_acces'] >= $niveau_admin) ? 1 : 0;
|
||||||
|
|
||||||
|
Ouverture_bdd();
|
||||||
|
$reponse = Requete_preparee_bdd("SELECT B.Date_derniere_maj FROM TB_Bateaux B
|
||||||
|
WHERE B.Cle_primaire=? AND B.Validite=1
|
||||||
|
AND (? = 1 OR EXISTS (SELECT 1 FROM TB_Lien_bateau_shipmember L
|
||||||
|
WHERE L.ID_Bateau=B.Cle_primaire AND L.ID_Shipmember=? AND L.Statut='actif' AND L.Fin_validite >= NOW()))",
|
||||||
|
array(array('i', $id_bateau), array('i', $est_administrateur), array('i', $_SESSION['shipmember']['Cle_primaire'])),
|
||||||
|
__FILE__, __LINE__);
|
||||||
|
$donnees = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC);
|
||||||
|
Liberation_jeu_de_resultat_bdd($reponse);
|
||||||
|
Fermeture_bdd();
|
||||||
|
|
||||||
|
if (!$donnees)
|
||||||
|
{
|
||||||
|
http_response_code(403);
|
||||||
|
echo '{}';
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
echo json_encode(array('date' => $donnees['Date_derniere_maj']), JSON_INVALID_UTF8_SUBSTITUTE);
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
//************************************************************************************************************
|
||||||
|
// bateau_mesures.php - Données d'une courbe historique, pour la page "Données du bateau" (synthèse §50, étape 3c).
|
||||||
|
// Paramètres (GET) : bateau, generation (0 = génération active), type (circuits, capteurs, batteries, reservoirs),
|
||||||
|
// element, variable (circuits, batteries), periode (24h, 7j, 30j, 3m, 1a). Réponse JSON (cf Courbe_element()).
|
||||||
|
// Droits : ceux de la page du bateau - administrateur, ou accès actif à ce bateau avec un type d'accès autorisant la
|
||||||
|
// consultation des données ; la génération doit appartenir au bateau.
|
||||||
|
//************************************************************************************************************
|
||||||
|
// Réponse JSON : aucun avertissement PHP ne doit s'y écrire (il la rendrait illisible) - ils vont dans le journal.
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
require_once("../config/config.php");
|
||||||
|
Init_session(basename(__FILE__));
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
header('Cache-Control: no-store');
|
||||||
|
|
||||||
|
function Repondre($code_http, $donnees)
|
||||||
|
{
|
||||||
|
http_response_code($code_http);
|
||||||
|
echo json_encode($donnees, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); // Caractère mal encodé : remplacé, pas d'échec
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($_SESSION['acces_valide'])) Repondre(401, array('erreur' => "session expirée"));
|
||||||
|
|
||||||
|
$id_bateau = (int)($_GET['bateau'] ?? 0);
|
||||||
|
$id_generation = (int)($_GET['generation'] ?? 0);
|
||||||
|
$type = (string)($_GET['type'] ?? '');
|
||||||
|
$id_element = (int)($_GET['element'] ?? 0);
|
||||||
|
$variable = (string)($_GET['variable'] ?? '');
|
||||||
|
$periode = (string)($_GET['periode'] ?? '');
|
||||||
|
$est_administrateur = ((int)$_SESSION['shipmember']['Niveau_acces'] >= NIVEAU_ACCES_MINI_ADMINISTRATEUR) ? 1 : 0;
|
||||||
|
|
||||||
|
Ouverture_bdd();
|
||||||
|
|
||||||
|
// Bateau accessible (et date de la dernière archive) :
|
||||||
|
$reponse = Requete_preparee_bdd("SELECT B.Date_derniere_maj FROM TB_Bateaux B
|
||||||
|
WHERE B.Cle_primaire=? AND B.Validite=1
|
||||||
|
AND (? = 1 OR EXISTS (SELECT 1 FROM TB_Lien_bateau_shipmember L
|
||||||
|
JOIN TB_Types_acces_bateau_shipmember T ON T.Cle_primaire = L.ID_Type_acces_bateau_shipmember
|
||||||
|
WHERE L.ID_Bateau=B.Cle_primaire AND L.ID_Shipmember=? AND L.Statut='actif'
|
||||||
|
AND L.Fin_validite >= NOW() AND T.Consultation_donnees >= 1))",
|
||||||
|
array(array('i', $id_bateau), array('i', $est_administrateur), array('i', $_SESSION['shipmember']['Cle_primaire'])),
|
||||||
|
__FILE__, __LINE__);
|
||||||
|
$bateau = Recherche_ligne_bdd($reponse, MYSQLI_ASSOC);
|
||||||
|
Liberation_jeu_de_resultat_bdd($reponse);
|
||||||
|
if (!$bateau) { Fermeture_bdd(); Repondre(403, array('erreur' => "accès refusé")); }
|
||||||
|
|
||||||
|
// Génération : celle demandée si elle appartient à ce bateau, sinon la génération active :
|
||||||
|
$generation = null;
|
||||||
|
foreach (Generations_bateau($id_bateau) as $g)
|
||||||
|
{
|
||||||
|
if ((int)$g['Cle_primaire'] == $id_generation) { $generation = $g; break; }
|
||||||
|
if ($g['Date_fin'] === null && $generation === null) $generation = $g;
|
||||||
|
}
|
||||||
|
Fermeture_bdd();
|
||||||
|
if ($generation === null) Repondre(404, array('erreur' => "aucune donnée pour ce bateau"));
|
||||||
|
|
||||||
|
// Fin de la courbe : dernière archive (génération active) ou fin de la génération (génération antérieure) :
|
||||||
|
$date_fin = ($generation['Date_fin'] === null) ? $bateau['Date_derniere_maj'] : $generation['Date_fin'];
|
||||||
|
if ($date_fin === null) Repondre(404, array('erreur' => "aucune archive reçue"));
|
||||||
|
|
||||||
|
$db = Connexion_generation($generation['Nom_base']);
|
||||||
|
if ($db === null) Repondre(503, array('erreur' => "données momentanément inaccessibles"));
|
||||||
|
$courbe = Courbe_element($db, $type, $id_element, $variable, $periode, $date_fin);
|
||||||
|
$db->close();
|
||||||
|
|
||||||
|
if (isset($courbe['erreur'])) Repondre(400, $courbe);
|
||||||
|
Repondre(200, $courbe);
|
||||||
+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']['bateau']['Cle_primaire'] = (int)$_GET['id_bateau'];
|
||||||
$_SESSION['Bateaux']['type_details_bateau'] = "resume";
|
$_SESSION['Bateaux']['type_details_bateau'] = "resume";
|
||||||
|
$_SESSION['Bateaux']['generation'] = 0; // Nouveau bateau : génération active
|
||||||
$_SESSION['Bateaux']['page'] = "Details_bateau";
|
$_SESSION['Bateaux']['page'] = "Details_bateau";
|
||||||
|
|
||||||
if (isset($_GET['origine'])) $_SESSION['Bateaux']['origine'] = $_GET['origine'];
|
if (isset($_GET['origine'])) $_SESSION['Bateaux']['origine'] = $_GET['origine'];
|
||||||
@@ -152,6 +153,16 @@ Init_session(basename($_SERVER['SCRIPT_NAME'])); // Fonction ouvrant la session
|
|||||||
break;
|
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":
|
case "modification_bateau":
|
||||||
{
|
{
|
||||||
@@ -370,7 +381,7 @@ $requete .= ' TB_Bateaux.Date_derniere_maj AS Date_derniere_maj, ';
|
|||||||
// Mise en forme de certaines variables :
|
// Mise en forme de certaines variables :
|
||||||
$bateaux[$nb_bateaux]['version'] = ($donnees['version'] == "") ? '<i>inconnue</i>' : htmlspecialchars($donnees['version']);
|
$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>";
|
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 ++;
|
$nb_bateaux ++;
|
||||||
}
|
}
|
||||||
@@ -400,6 +411,7 @@ $requete .= ' TB_Bateaux.Date_derniere_maj AS Date_derniere_maj, ';
|
|||||||
while ($donnees = Recherche_ligne_bdd($reponse))
|
while ($donnees = Recherche_ligne_bdd($reponse))
|
||||||
{
|
{
|
||||||
$bateau = $donnees;
|
$bateau = $donnees;
|
||||||
|
$bateau['Date_derniere_maj_utc'] = $donnees['Date_derniere_maj']; // Brute (UTC), pour l'indicateur de fraîcheur
|
||||||
|
|
||||||
// Mise en forme de textes :
|
// Mise en forme de textes :
|
||||||
if ($donnees['Date_derniere_maj'] < "2020-01-02") $bateau['Date_derniere_maj'] = "<i>".t("Jamais")."</i>";
|
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);
|
Liberation_jeu_de_resultat_bdd($reponse);
|
||||||
break;
|
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'
|
} // 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=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=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=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 '<th>Commentaires</th>';
|
||||||
echo '</tr>
|
echo '</tr>
|
||||||
</thead>
|
</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>';
|
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>';
|
||||||
|
|
||||||
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>';
|
echo '</div>';
|
||||||
//--------------------------------
|
//--------------------------------
|
||||||
// Boutons de haut de page (les anciens onglets Carte/Batteries/Circuits/Réservoirs/Capteurs/Système, liés au
|
// 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) :
|
// 'resume' (et toute ancienne valeur de session) :
|
||||||
default:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1139,6 +1321,257 @@ Pied_de_page();
|
|||||||
|
|
||||||
|
|
||||||
<script>
|
<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(){
|
function toggle_help(){
|
||||||
var help = document.getElementById("help");
|
var help = document.getElementById("help");
|
||||||
|
|||||||
+2
-301
@@ -89,20 +89,9 @@ body {
|
|||||||
position: relative !important;
|
position: relative !important;
|
||||||
width: 30px !important;
|
width: 30px !important;
|
||||||
}
|
}
|
||||||
.update_navbar_pro{
|
|
||||||
position: relative !important;
|
|
||||||
width: 40px !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 1300px) {
|
|
||||||
.elem p{
|
|
||||||
font-size: 14px !important;
|
|
||||||
}
|
|
||||||
.elem{
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#update{
|
#update{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-image: url(images/edit_pen_logo.png);
|
background-image: url(images/edit_pen_logo.png);
|
||||||
@@ -115,9 +104,6 @@ body {
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
}
|
}
|
||||||
.clicked{
|
|
||||||
background-color:var(--rouge);
|
|
||||||
}
|
|
||||||
#home{
|
#home{
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
@@ -150,22 +136,6 @@ body {
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
}
|
}
|
||||||
#user_type{
|
|
||||||
height: 20%;
|
|
||||||
width: 50%;
|
|
||||||
margin-top: -18px;
|
|
||||||
float: right;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
#type_text {
|
|
||||||
color: var(--bleu_koriolan_fonce);
|
|
||||||
font-size: 12px;
|
|
||||||
margin: 0;
|
|
||||||
height: 100%;
|
|
||||||
line-height: 1em !important;
|
|
||||||
text-align: right;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
#switch{
|
#switch{
|
||||||
height: 35px;
|
height: 35px;
|
||||||
float: right;
|
float: right;
|
||||||
@@ -180,24 +150,6 @@ body {
|
|||||||
#switch p{
|
#switch p{
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
#type_text_switch {
|
|
||||||
/* margin-top: 15px;
|
|
||||||
/* margin-left: -43px; */
|
|
||||||
/* float: right;
|
|
||||||
position: absolute;
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid var(--bleu_koriolan); */
|
|
||||||
/* padding-left: 9px;
|
|
||||||
padding-right: 9px; */
|
|
||||||
}
|
|
||||||
#type_text_switch a:hover{
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--bleu_koriolan) !important;
|
|
||||||
}
|
|
||||||
#nav_bar_global{
|
|
||||||
display: block;
|
|
||||||
width: 72%;
|
|
||||||
}
|
|
||||||
#navbar_right{
|
#navbar_right{
|
||||||
width: 7%;
|
width: 7%;
|
||||||
height: 68px;
|
height: 68px;
|
||||||
@@ -279,54 +231,7 @@ body {
|
|||||||
transition-timing-function:ease-in-out;
|
transition-timing-function:ease-in-out;
|
||||||
transition-duration: 1s;
|
transition-duration: 1s;
|
||||||
}
|
}
|
||||||
.middle_part{
|
|
||||||
width: 72%;
|
|
||||||
height: 68px;
|
|
||||||
padding: 0;
|
|
||||||
right:10%;
|
|
||||||
left: 18%;
|
|
||||||
position: absolute;
|
|
||||||
display: inline-flex;
|
|
||||||
}
|
|
||||||
#navbar_par a, #navbar_pro a{
|
|
||||||
width: 25%;
|
|
||||||
height: 60%;
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
border-right: solid 2px var(--bleu_koriolan);
|
|
||||||
border-radius: 0;
|
|
||||||
margin: auto;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.elem{
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.elem p{
|
|
||||||
color : var(--bleu_koriolan_fonce);
|
|
||||||
height: 100%;
|
|
||||||
margin: auto;
|
|
||||||
position: relative;
|
|
||||||
padding: auto;
|
|
||||||
font-weight: 500;
|
|
||||||
padding-top: 5px;
|
|
||||||
transition: 300ms;
|
|
||||||
}
|
|
||||||
.elem p:hover, .active_navbar_elem{
|
|
||||||
font-weight: 700;
|
|
||||||
background-color: var(--bleu_koriolan_transparent);
|
|
||||||
transition-property: color, background-color;
|
|
||||||
transition-timing-function:ease-in-out;
|
|
||||||
transition-duration: 0.5s;
|
|
||||||
}
|
|
||||||
#menu_mobile{
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
@media only screen and (min-width: 850px) {
|
|
||||||
#nav_bar_global{
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media only screen and (max-width: 850px) {
|
@media only screen and (max-width: 850px) {
|
||||||
@@ -334,24 +239,6 @@ body {
|
|||||||
#logo_texte{
|
#logo_texte{
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#user_type{
|
|
||||||
width: 100%;
|
|
||||||
margin: auto;
|
|
||||||
top: 52px;
|
|
||||||
float: none;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
#menu_mobile{
|
|
||||||
display: block;
|
|
||||||
width: 60px;
|
|
||||||
margin-left: 10px;
|
|
||||||
background-image: url(/images/menu_burger_logo.png);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 60%;
|
|
||||||
background-position: center;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#logo_home{
|
#logo_home{
|
||||||
max-width: none;
|
max-width: none;
|
||||||
@@ -366,84 +253,9 @@ body {
|
|||||||
width: 70px;
|
width: 70px;
|
||||||
|
|
||||||
}
|
}
|
||||||
#nav_bar_global{
|
|
||||||
display: block;
|
|
||||||
width: 72%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navbar_pro, #navbar_par{
|
|
||||||
|
|
||||||
width: 70%;
|
|
||||||
max-width: 400px;
|
|
||||||
min-width: 200px;
|
|
||||||
height: calc(100vh - 70px);
|
|
||||||
position: absolute;
|
|
||||||
top : 70px;
|
|
||||||
left : -2px;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
border-right : solid var(--bleu_koriolan), 2px;
|
|
||||||
box-shadow: 2px 0px 5px var(--bleu_koriolan);
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
#type_text {
|
|
||||||
text-align: center;
|
|
||||||
width: 70px !important;
|
|
||||||
margin: auto !important;
|
|
||||||
position: relative !important;
|
|
||||||
line-height: 1em;
|
|
||||||
}
|
|
||||||
#navbar_right{
|
#navbar_right{
|
||||||
width: 20%;
|
width: 20%;
|
||||||
}
|
}
|
||||||
.middle_part{
|
|
||||||
width: 72%;
|
|
||||||
height: 68px;
|
|
||||||
padding: 0;
|
|
||||||
right:10%;
|
|
||||||
left: 18%;
|
|
||||||
position: absolute;
|
|
||||||
display: list-item;
|
|
||||||
}
|
|
||||||
#navbar_par a, #navbar_pro a{
|
|
||||||
width: 25%;
|
|
||||||
height: 60%;
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
border-right: solid 0px ;
|
|
||||||
border-bottom: solid 0px ;
|
|
||||||
border-radius: 0;
|
|
||||||
margin: auto;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.elem{
|
|
||||||
height: 50px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.elem p{
|
|
||||||
color : var(--bleu_koriolan_fonce);
|
|
||||||
height: 100%;
|
|
||||||
width : 60%;
|
|
||||||
margin: auto;
|
|
||||||
line-height: 3em;
|
|
||||||
text-align: left;
|
|
||||||
position: relative;
|
|
||||||
padding: auto;
|
|
||||||
font-weight: 500;
|
|
||||||
padding-top: 5px;
|
|
||||||
transition: 300ms;
|
|
||||||
left: -30px;
|
|
||||||
border-bottom: solid 2px var(--bleu_koriolan);
|
|
||||||
}
|
|
||||||
.elem p:hover{
|
|
||||||
font-weight: 700;
|
|
||||||
background-color: var(--bleu_koriolan_transparent);
|
|
||||||
transition-property: color, background-color;
|
|
||||||
transition-timing-function:ease-in-out;
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
}
|
|
||||||
#menu_mobile{
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/*********************************************************** PIED DE PAGE *********************************************/
|
/*********************************************************** PIED DE PAGE *********************************************/
|
||||||
#pied_de_page {
|
#pied_de_page {
|
||||||
@@ -499,16 +311,6 @@ body {
|
|||||||
#pied_de_page .signature img {
|
#pied_de_page .signature img {
|
||||||
height: 60px;
|
height: 60px;
|
||||||
}
|
}
|
||||||
#pied_de_page .droite .mentions_legales #logos {
|
|
||||||
display: flex;
|
|
||||||
margin: auto 0px auto auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pied_de_page .droite .mentions_legales #logos a img {
|
|
||||||
height: 35px;
|
|
||||||
width: 35px;
|
|
||||||
|
|
||||||
}
|
|
||||||
@media only screen and (min-width: 851px) {
|
@media only screen and (min-width: 851px) {
|
||||||
|
|
||||||
#pied_de_page{
|
#pied_de_page{
|
||||||
@@ -556,9 +358,6 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
#pied_de_page_mobile #fin a img#fin_img {
|
|
||||||
height: 55px !important;
|
|
||||||
}
|
|
||||||
#pied_de_page_mobile .mentions_legales a:hover {
|
#pied_de_page_mobile .mentions_legales a:hover {
|
||||||
color: var(--bleu_koriolan_fonce);
|
color: var(--bleu_koriolan_fonce);
|
||||||
background-color: var(--bleu_koriolan);
|
background-color: var(--bleu_koriolan);
|
||||||
@@ -576,21 +375,6 @@ body {
|
|||||||
#pied_de_page_mobile p#marque_texte {
|
#pied_de_page_mobile p#marque_texte {
|
||||||
padding-top: 15;
|
padding-top: 15;
|
||||||
}
|
}
|
||||||
#pied_de_page_mobile .mentions_legales #logos {
|
|
||||||
display: flex;
|
|
||||||
height: 55px;
|
|
||||||
width: 300px;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pied_de_page_mobile .mentions_legales #logos a img {
|
|
||||||
height: 35px;
|
|
||||||
width: 35px;
|
|
||||||
|
|
||||||
}
|
|
||||||
#pied_de_page_mobile .mentions_legales #logos a img#bpi {
|
|
||||||
margin-top: -6.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -737,11 +521,6 @@ ul li {
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
}
|
}
|
||||||
ul li.retrait {
|
|
||||||
margin-left: 0px;
|
|
||||||
list-style-image: none;
|
|
||||||
list-style-type: square;
|
|
||||||
}
|
|
||||||
|
|
||||||
/********************************************************* PARAGRAPHES ***************************************/
|
/********************************************************* PARAGRAPHES ***************************************/
|
||||||
p {
|
p {
|
||||||
@@ -762,10 +541,6 @@ p {
|
|||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
color: var(--bleu_koriolan); /* Permet de renforcer le gras... */
|
color: var(--bleu_koriolan); /* Permet de renforcer le gras... */
|
||||||
}
|
}
|
||||||
.italique {
|
|
||||||
font-style:italic;
|
|
||||||
color:rgb(150, 150, 150); /* Permet de rnforcer l'effet italique... */
|
|
||||||
}
|
|
||||||
.souligne {
|
.souligne {
|
||||||
text-decoration:underline;
|
text-decoration:underline;
|
||||||
}
|
}
|
||||||
@@ -775,12 +550,6 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.leaflet-touch .leaflet-bar a {
|
|
||||||
padding: unset !important;
|
|
||||||
border-radius: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/************************************* MESSAGE D'ERREUR ***************************************/
|
/************************************* MESSAGE D'ERREUR ***************************************/
|
||||||
.message_erreur {
|
.message_erreur {
|
||||||
display:block !important;
|
display:block !important;
|
||||||
@@ -810,10 +579,6 @@ p {
|
|||||||
color: var(--blanc);
|
color: var(--blanc);
|
||||||
box-shadow: 5px 5px 8px var(--gris_moyen);
|
box-shadow: 5px 5px 8px var(--gris_moyen);
|
||||||
}
|
}
|
||||||
|
|
||||||
.message_information.avec_message_erreur {
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
.message_information .gras {
|
.message_information .gras {
|
||||||
color: var(--bleu_koriolan_fonce); /* Sinon, il a la couleur de fond... */
|
color: var(--bleu_koriolan_fonce); /* Sinon, il a la couleur de fond... */
|
||||||
}
|
}
|
||||||
@@ -936,33 +701,7 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/************************************* CARROUSSEL ***************************************/
|
/************************************* CARROUSSEL ***************************************/
|
||||||
.carousel-dark .carousel-indicators [data-bs-target] {
|
|
||||||
background-color: var(--bleu_koriolan); /* Couleur de fond des bulles de navigation d'une carte à l'autre */
|
|
||||||
}
|
|
||||||
|
|
||||||
.carousel-indicators [data-bs-target] {
|
|
||||||
opacity: 0.2; /* Opacité de la bulle de navigation non active */
|
|
||||||
}
|
|
||||||
.carousel-indicators .active {
|
|
||||||
opacity: 1.0; /* Opacité de la bulle de navigation active */
|
|
||||||
}
|
|
||||||
|
|
||||||
.carousel-indicators [data-bs-target] {
|
|
||||||
width: 50px; /* Largeur de chaque bulle de nav */
|
|
||||||
height: 10px; /* Hauteur de la bulle */
|
|
||||||
background-clip: inherit; /* En fonction du choix, les bulles ne s'affichent pas... */
|
|
||||||
border-top: none;
|
|
||||||
border-bottom: none
|
|
||||||
}
|
|
||||||
.carousel_prev_fleche {
|
|
||||||
filter: none !important;
|
|
||||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2375b9da'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.carousel_next_fleche {
|
|
||||||
filter: none !important;
|
|
||||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2375b9da'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/************************************* GRAPHIQUES ***************************************/
|
/************************************* GRAPHIQUES ***************************************/
|
||||||
.graphique {
|
.graphique {
|
||||||
@@ -981,47 +720,9 @@ p {
|
|||||||
/* font-weight: bold; */
|
/* font-weight: bold; */
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
.graphique .graph {
|
|
||||||
width: 100%;
|
|
||||||
height: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/************************************* Popup de zoom d'image ***************************************/
|
/************************************* Popup de zoom d'image ***************************************/
|
||||||
#zoom {
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
background-color: var(--blanc);
|
|
||||||
padding:20px 40px 20px 20px;
|
|
||||||
border:1px solid var(--gris_pale);
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 30px 30px 8px var(--gris_tres_fonce_transparent);
|
|
||||||
width: 70%;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 19fr 1fr;
|
|
||||||
row-gap: 0px;
|
|
||||||
}
|
|
||||||
#zoom a {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#zoom a img {
|
|
||||||
width: 30px;
|
|
||||||
}
|
|
||||||
#zoom .photo_zoomee {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
#zoom .photo_zoomee img {
|
|
||||||
max-height: 95%;
|
|
||||||
width: 90%;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin: auto;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
/************************************* ANIMATIONS KEYFRAMES ***************************************/
|
/************************************* ANIMATIONS KEYFRAMES ***************************************/
|
||||||
@keyframes heart_pulse {
|
@keyframes heart_pulse {
|
||||||
|
|||||||
+14
-397
@@ -18,17 +18,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/********************************************************* ENTETE **************************************************/
|
/********************************************************* ENTETE **************************************************/
|
||||||
#entete #ligne_icones {
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
|
||||||
}
|
|
||||||
#entete #ligne_icones a {
|
|
||||||
background-size: 50%;
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
#entete #ligne_icones #home {
|
|
||||||
text-align:center;
|
|
||||||
padding-left:0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#entete nav {
|
#entete nav {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -47,13 +37,6 @@ body {
|
|||||||
/* line-height: 2em; */
|
/* line-height: 2em; */
|
||||||
}
|
}
|
||||||
|
|
||||||
#espace_menu.replie {
|
|
||||||
padding-top:40px;
|
|
||||||
}
|
|
||||||
#espace_menu.deploye {
|
|
||||||
padding-top:360px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*********************************************************** PIED DE PAGE *********************************************/
|
/*********************************************************** PIED DE PAGE *********************************************/
|
||||||
#pied_de_page {
|
#pied_de_page {
|
||||||
@@ -113,15 +96,9 @@ select {
|
|||||||
::placeholder {
|
::placeholder {
|
||||||
/* font-size: 1.8em; */
|
/* font-size: 1.8em; */
|
||||||
}
|
}
|
||||||
.boutons_formulaire button {
|
|
||||||
padding:50px 30px;
|
|
||||||
/* font-size: 1.8em; */
|
|
||||||
}
|
|
||||||
|
|
||||||
/********************************************************* LIENS HYPERTEXTES ***************************************/
|
/********************************************************* LIENS HYPERTEXTES ***************************************/
|
||||||
a.renforce {
|
|
||||||
padding:3px 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
padding-inline-start: 20px; /* C'est une valeur par défaut de 40px, ce qui est beaucoup sur un affichage téléphone... */
|
padding-inline-start: 20px; /* C'est une valeur par défaut de 40px, ce qui est beaucoup sur un affichage téléphone... */
|
||||||
@@ -141,10 +118,6 @@ p {
|
|||||||
/* padding-top: 350px; /* Taille du menu déployé */
|
/* padding-top: 350px; /* Taille du menu déployé */
|
||||||
/*margin-top: -350px; Même valeur que ci-dessus pour compenser */
|
/*margin-top: -350px; Même valeur que ci-dessus pour compenser */
|
||||||
}
|
}
|
||||||
.ancre.sans_menu {
|
|
||||||
/*padding-top: 40px;*/ /* Taille du menu replié */
|
|
||||||
/*margin-top: -40px;*/ /* Même valeur que ci-dessus pour compenser */
|
|
||||||
}
|
|
||||||
|
|
||||||
/************************************* MESSAGE D'ERREUR ***************************************/
|
/************************************* MESSAGE D'ERREUR ***************************************/
|
||||||
|
|
||||||
@@ -185,27 +158,7 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/************************************* IMAGES ZOOMEES ***************************************/
|
/************************************* IMAGES ZOOMEES ***************************************/
|
||||||
#zoom {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
row-gap: 20px;
|
|
||||||
width: 95%;
|
|
||||||
padding:5px;
|
|
||||||
}
|
|
||||||
#zoom a {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#zoom a img {
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
#zoom .photo_zoomee {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
#zoom .photo_zoomee img {
|
|
||||||
max-height: 250px;
|
|
||||||
max-width: 90%;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/***********************************************************************************************************************
|
/***********************************************************************************************************************
|
||||||
Fichier accueil.php
|
Fichier accueil.php
|
||||||
@@ -221,101 +174,12 @@ Fichier accueil.php
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#Accueil #bandeau_accueil {
|
|
||||||
height:300px; /* Fige la hauteur du bloc 'bandeau-accueil' sinon on n'arrive à rien... */
|
|
||||||
line-height: 300px; /* Permet ensuite d'utiliser le vertical-align sur le <p> inclus */
|
|
||||||
}
|
|
||||||
#Accueil #bandeau_accueil p {
|
|
||||||
font-size:1.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#Accueil #notre_actualite p {
|
|
||||||
margin-bottom: 2em;
|
|
||||||
}
|
|
||||||
#Accueil #notre_actualite a {
|
|
||||||
width: 90%;
|
|
||||||
border-radius: 1em;
|
|
||||||
padding: 30px 0px;
|
|
||||||
}
|
|
||||||
#Accueil #qu_est_ce_que_shipheart .corps {
|
|
||||||
padding:50px 0px 50px 0px;
|
|
||||||
border-bottom:10px solid var(--bleu_koriolan_transparent);
|
|
||||||
font-weight: 300;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
#Accueil #qu_est_ce_que_shipheart .corps p {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
#Accueil #qu_est_ce_que_shipheart .corps .made_in_france {
|
|
||||||
margin:20px 5% 50px 5%;
|
|
||||||
}
|
|
||||||
#Accueil #qu_est_ce_que_shipheart .corps .made_in_france img {
|
|
||||||
width: 99%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#Accueil #marguerite {
|
|
||||||
padding:0.5em 0px;
|
|
||||||
}
|
|
||||||
#Accueil #marguerite .corps {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
row-gap:60px;
|
|
||||||
}
|
|
||||||
#Accueil #marguerite .corps .rubrique {
|
|
||||||
border: none;
|
|
||||||
border-bottom:10px solid var(--bleu_koriolan_transparent);
|
|
||||||
padding:10px 10px;
|
|
||||||
}
|
|
||||||
#Accueil #boutique .corps a {
|
|
||||||
margin: 20px auto 20px auto;
|
|
||||||
padding: 30px 0px;
|
|
||||||
width: 96%;
|
|
||||||
/* font-size: 2.0em; */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#Accueil #faq .corps .rubriques {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
row-gap: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#Accueil #qui_sommes_nous img {
|
|
||||||
/* width:100%; */
|
|
||||||
}
|
|
||||||
#Accueil #qui_sommes_nous .image_bandeau {
|
|
||||||
height:200px;
|
|
||||||
}
|
|
||||||
#Accueil #qui_sommes_nous .corps {
|
|
||||||
/* padding:50px 0px; */
|
|
||||||
}
|
|
||||||
#Accueil #qui_sommes_nous .carte_de_visite {
|
|
||||||
grid-template-columns: 1% 1fr 2fr;
|
|
||||||
}
|
|
||||||
#Accueil #qui_sommes_nous .carte_de_visite .texte p {
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#Accueil #qui_sommes_nous .contact {
|
|
||||||
/* grid-template-columns: 20px 1fr 20px; */
|
|
||||||
padding-top: 50px;
|
|
||||||
/* font-size: 1.8em; */
|
|
||||||
}
|
|
||||||
#Accueil #qui_sommes_nous .contact a {
|
|
||||||
width: 95%;
|
|
||||||
margin: 60px auto;
|
|
||||||
padding: 30px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/***********************************************************************************************************************
|
/***********************************************************************************************************************
|
||||||
Fichier actualites.php
|
Fichier actualites.php
|
||||||
***********************************************************************************************************************/
|
***********************************************************************************************************************/
|
||||||
/*----------------------------------------*/
|
/*----------------------------------------*/
|
||||||
#Actualites #accueil .photos {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -324,256 +188,37 @@ Fichier actualites.php
|
|||||||
/***********************************************************************************************************************
|
/***********************************************************************************************************************
|
||||||
Fichier boutique.php
|
Fichier boutique.php
|
||||||
***********************************************************************************************************************/
|
***********************************************************************************************************************/
|
||||||
#Boutique .info_mobile {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
#Boutique .boutons_bas_de_page {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-areas:
|
|
||||||
" suivant "
|
|
||||||
" retour ";
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
#Boutique .boutons_bas_de_page a {
|
|
||||||
margin: 30px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*--------------------------------------------------*/
|
|
||||||
#Boutique #bandeau_avancement {
|
|
||||||
grid-template-columns: repeat(7, auto);
|
|
||||||
font-size:0.6em;
|
|
||||||
}
|
|
||||||
#Boutique #bandeau_avancement .etape:first-child {
|
|
||||||
border-radius: 0px;
|
|
||||||
}
|
|
||||||
#Boutique #bandeau_avancement .etape:last-child {
|
|
||||||
border-radius: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*--------------------------------------------------*/
|
/*--------------------------------------------------*/
|
||||||
#Boutique #accueil .famille {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
#Boutique #accueil .famille .produit {
|
|
||||||
/* border-top:10px solid var(--bleu_koriolan_fonce); */
|
|
||||||
grid-template-rows: auto;
|
|
||||||
row-gap:20px;
|
|
||||||
}
|
|
||||||
#Boutique #accueil .famille .produit .image img {
|
|
||||||
height: auto;
|
|
||||||
width: min(80%, 350px);
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
#Boutique #accueil .famille .produit .boutons a {
|
|
||||||
width: calc(96% - 20px);
|
|
||||||
/* padding: 40px 10px; */
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
#Boutique #accueil .famille .produit .boutons a:hover {
|
|
||||||
}
|
|
||||||
|
|
||||||
/*--------------------------------------------------*/
|
|
||||||
#Boutique #panier .liste_produits {
|
|
||||||
grid-row-gap: 10px;
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .titre {
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#Boutique #panier .liste_produits .produit {
|
|
||||||
border: 3px solid var(--bleu_koriolan_fonce);
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
grid-template-rows: auto;
|
|
||||||
row-gap: 10px;
|
|
||||||
grid-template-areas:
|
|
||||||
" image image "
|
|
||||||
" nom nom "
|
|
||||||
" descriptif descriptif "
|
|
||||||
" delai delai "
|
|
||||||
" boutons quantite "
|
|
||||||
" prix_ht prix_ht "
|
|
||||||
" prix_ttc prix_ttc ";
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-items: center;
|
|
||||||
|
|
||||||
margin: 10px 0px 50px 0px;
|
|
||||||
padding: 20px 10px;
|
|
||||||
|
|
||||||
/* font-size: 0.8em; */
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .produit .image {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .produit .image img {
|
|
||||||
height: auto;
|
|
||||||
width: min(80%, 350px);
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .produit .descriptif {
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .produit .quantite {
|
|
||||||
/* font-size:1.5em; */
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .produit .quantite .info_mobile {
|
|
||||||
font-size:0.8em;
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .produit .boutons {
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
grid-template-areas:
|
|
||||||
" moins plus ";
|
|
||||||
grid-column-gap: 20px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .produit .boutons a {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .produit .boutons a img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .produit .delai {
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .produit .prix_ht {
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .produit .prix_ttc {
|
|
||||||
line-height: normal;
|
|
||||||
font-size:1.2em;
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .synthese {
|
|
||||||
grid-template-columns: 2fr 2fr 2fr;
|
|
||||||
}
|
|
||||||
#Boutique #panier .liste_produits .synthese.titres.info_mobile {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*--------------------------------------------------*/
|
/*--------------------------------------------------*/
|
||||||
#Boutique #identification form {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-areas:
|
|
||||||
" shipmember "
|
|
||||||
" new_shipmember ";
|
|
||||||
|
|
||||||
row-gap:10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*--------------------------------------------------*/
|
/*--------------------------------------------------*/
|
||||||
#Boutique #validation .liste_produits .titre {
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#Boutique #validation .liste_produits .produit {
|
|
||||||
border: 3px solid var(--bleu_koriolan_fonce);
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
grid-template-rows: auto;
|
|
||||||
row-gap: 20px;
|
|
||||||
grid-template-areas:
|
|
||||||
" image image "
|
|
||||||
" nom nom "
|
|
||||||
" descriptif descriptif "
|
|
||||||
" delai delai "
|
|
||||||
" prix_ht prix_ht "
|
|
||||||
" prix_ttc prix_ttc "
|
|
||||||
" boutons quantite ";
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-items: center;
|
|
||||||
|
|
||||||
margin: 10px 0px 10px 0px;
|
|
||||||
padding: 20px 10px;
|
|
||||||
|
|
||||||
/* font-size: 0.8em; */
|
|
||||||
}
|
|
||||||
#Boutique #validation .liste_produits .produit .image {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#Boutique #validation .liste_produits .produit .image img {
|
|
||||||
height: auto;
|
|
||||||
width: min(80%, 350px);
|
|
||||||
}
|
|
||||||
#Boutique #validation .liste_produits .produit .descriptif {
|
|
||||||
font-style: normale;
|
|
||||||
}
|
|
||||||
#Boutique #validation .liste_produits .produit .quantite {
|
|
||||||
font-size:1.5em;
|
|
||||||
line-height: 1.5em;
|
|
||||||
}
|
|
||||||
#Boutique #validation .liste_produits .produit .quantite .info_mobile {
|
|
||||||
font-size:0.8em;
|
|
||||||
}
|
|
||||||
#Boutique #validation .liste_produits .produit .boutons {
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
grid-template-areas:
|
|
||||||
" moins plus ";
|
|
||||||
grid-column-gap: 20px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
#Boutique #validation .liste_produits .produit .boutons a {
|
|
||||||
height: 200px;
|
|
||||||
width: 200px;
|
|
||||||
line-height: 200px;
|
|
||||||
}
|
|
||||||
#Boutique #validation .liste_produits .produit .boutons a img {
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
#Boutique #validation .liste_produits .produit .delai {
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
#Boutique #validation .liste_produits .produit .prix_ht {
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
#Boutique #validation .liste_produits .produit .prix_ttc {
|
|
||||||
line-height: normal;
|
|
||||||
font-size:1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#Boutique #validation .liste_produits .synthese {
|
|
||||||
grid-template-columns: 2fr 2fr 2fr;
|
|
||||||
}
|
|
||||||
#Boutique #validation .liste_produits .synthese.titres.info_mobile {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#Boutique #validation .message_validation_cgv input {
|
|
||||||
width: 40px;
|
/*--------------------------------------------------*/
|
||||||
}
|
|
||||||
#Boutique #validation .message_validation_cgv label {
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
/*--------------------------------------------------*/
|
||||||
#Boutique #validation .message_validation_cgv a {
|
|
||||||
padding: 0px 30px;
|
|
||||||
/* background-color: var(--bleu_koriolan_fonce); */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*--------------------------------------------------*/
|
/*--------------------------------------------------*/
|
||||||
#Boutique #paiement .saisie_paiement form {
|
|
||||||
/* padding: 20px 0px 100px 0px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
#Boutique #paiement .saisie_paiement #card-element {
|
|
||||||
width: 90%;
|
|
||||||
margin-bottom: 50px;
|
|
||||||
|
|
||||||
/* margin: 50px 0px 150px 0px; */
|
|
||||||
/* background: var(--bleu_koriolan_transparent); */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*--------------------------------------------------*/
|
/*--------------------------------------------------*/
|
||||||
#Boutique #cloture h2 {
|
|
||||||
margin-top: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -600,9 +245,6 @@ Fichier faq.php
|
|||||||
#Faq #accueil .famille .reponse {
|
#Faq #accueil .famille .reponse {
|
||||||
margin:0px 10px 30px 10px;
|
margin:0px 10px 30px 10px;
|
||||||
}
|
}
|
||||||
#Faq #accueil .famille .reponse .photos {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -611,15 +253,7 @@ Fichier faq.php
|
|||||||
Fichier galerie.php
|
Fichier galerie.php
|
||||||
***********************************************************************************************************************/
|
***********************************************************************************************************************/
|
||||||
|
|
||||||
#Galerie #accueil #photos {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
#Galerie #accueil #photos .photo .legende {
|
|
||||||
/* font-size:1.6em; */
|
|
||||||
}
|
|
||||||
#Galerie #accueil #photos .photo {
|
|
||||||
grid-template-rows: 1fr auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/***********************************************************************************************************************
|
/***********************************************************************************************************************
|
||||||
@@ -642,24 +276,7 @@ Fichier mentions_legales.php
|
|||||||
/***********************************************************************************************************************
|
/***********************************************************************************************************************
|
||||||
Fichier nautic2021.php
|
Fichier nautic2021.php
|
||||||
***********************************************************************************************************************/
|
***********************************************************************************************************************/
|
||||||
#Nautic2021 .logo_sh {
|
|
||||||
height:80px;
|
|
||||||
}
|
|
||||||
#Nautic2021 .logo_nautic {
|
|
||||||
height:80px;
|
|
||||||
}
|
|
||||||
#Nautic2021 .champs .champs_valeur {
|
|
||||||
min-height: 50px;
|
|
||||||
line-height: 50px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
#Nautic2021 .champs .champs_valeur input[type="checkbox"] {
|
|
||||||
margin-left: 30px;
|
|
||||||
}
|
|
||||||
#Nautic2021 .champs .champs_valeur label {
|
|
||||||
width: 70%;
|
|
||||||
min-height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/***********************************************************************************************************************
|
/***********************************************************************************************************************
|
||||||
|
|||||||
+105
-212
@@ -77,19 +77,6 @@ Fichier bateaux.php
|
|||||||
background-color: var(--bleu_koriolan);
|
background-color: var(--bleu_koriolan);
|
||||||
color: var(--bleu_koriolan_fonce);
|
color: var(--bleu_koriolan_fonce);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#Bateaux #details_bateau .graphique .top_part_graph .vars .var{
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .graphique .top_part_graph .vars .var.actif {
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: var(--bleu_koriolan_transparent);
|
|
||||||
color: var(--bleu_koriolan_fonce);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.page_principale {
|
.page_principale {
|
||||||
width: 80% !important;
|
width: 80% !important;
|
||||||
max-width: unset !important;
|
max-width: unset !important;
|
||||||
@@ -122,23 +109,6 @@ Fichier bateaux.php
|
|||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#Bateaux #details_bateau .onglets a {
|
|
||||||
background-color: var(--bleu_koriolan_fonce);
|
|
||||||
border-radius: 1em 1em 0 0;
|
|
||||||
color: var(--blanc);
|
|
||||||
display: inline-block;
|
|
||||||
padding: 3px 20px;
|
|
||||||
margin: 1px 1px 0px 1px;
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .onglets a:hover {
|
|
||||||
color: var(--bleu_koriolan);
|
|
||||||
background-color: var(--bleu_koriolan_fonce);
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .onglets a.actif {
|
|
||||||
background-color: var(--bleu_koriolan);
|
|
||||||
color: var(--bleu_koriolan_fonce);
|
|
||||||
}
|
|
||||||
|
|
||||||
#Bateaux #details_bateau .corps_details_bateau {
|
#Bateaux #details_bateau .corps_details_bateau {
|
||||||
background-color: var(--blanc_casse);
|
background-color: var(--blanc_casse);
|
||||||
}
|
}
|
||||||
@@ -162,105 +132,6 @@ Fichier bateaux.php
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
/* background-color: var(--blanc_casse); */
|
/* background-color: var(--blanc_casse); */
|
||||||
}
|
}
|
||||||
.red {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
.orange {
|
|
||||||
color: #ffa600;
|
|
||||||
}
|
|
||||||
.green {
|
|
||||||
color: #02a602;
|
|
||||||
}
|
|
||||||
.capteur_state{
|
|
||||||
display: block;
|
|
||||||
padding: 0px;
|
|
||||||
height: 60px;
|
|
||||||
line-height: 60px;
|
|
||||||
width: 80px;
|
|
||||||
text-align: center !important;
|
|
||||||
font-size: 1.5em;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
.capteur_off{
|
|
||||||
background-color: rgba(255, 30, 0, 0.2);
|
|
||||||
color: rgb(255,30,0);
|
|
||||||
}
|
|
||||||
.consommation_instantanee {
|
|
||||||
font-size: 1em;
|
|
||||||
padding: 6px;
|
|
||||||
background-image: url(images/icone_consommation_electrique.png);
|
|
||||||
background-size: contain;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
.capteur_on{
|
|
||||||
background-color: rgba(177, 251, 23, 0.3);
|
|
||||||
color: rgb(177, 251, 23);
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .corps_details_bateau .champs .champs_description {
|
|
||||||
grid-column: 1 / span 2;
|
|
||||||
text-align: justify;
|
|
||||||
font-size:0.8em;
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .boutons_graphique {
|
|
||||||
text-align: center;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr ;
|
|
||||||
grid-column-gap: 20px;
|
|
||||||
align-items: center;
|
|
||||||
margin: 20px 0px 20px 0px;
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .boutons_graphique a {
|
|
||||||
background-color: var(--bleu_koriolan_fonce);
|
|
||||||
color: var(--blanc);
|
|
||||||
width: 300px;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .boutons_graphique a:hover {
|
|
||||||
color: var(--bleu_koriolan);
|
|
||||||
background-color: var(--bleu_koriolan_fonce);
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .boutons_graphique a.actif {
|
|
||||||
background-color: var(--bleu_koriolan);
|
|
||||||
color: var(--bleu_koriolan_fonce);
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .corps_details_bateau .titre_composant {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 2fr 3fr 60px;
|
|
||||||
/* height: 70px; */
|
|
||||||
padding: 15px 0px 15px 0px;
|
|
||||||
margin: 2em 0px 0.2em 0px;
|
|
||||||
border-top: 1px solid var(--bleu_koriolan_fonce);
|
|
||||||
border-bottom: 1px solid var(--bleu_koriolan_fonce);
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .corps_details_bateau .titre_composant p{
|
|
||||||
margin: unset;
|
|
||||||
text-align : unset;
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .corps_details_bateau .titre_composant .titre_text{
|
|
||||||
font-size: 1.8em;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .corps_details_bateau .titre_composant .titre_desc{
|
|
||||||
/* font-size: 1.8em; */
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .corps_details_bateau .titre_composant .titre_capt{
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .corps_details_bateau .titre_composant .titre_icon {
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
cursor: pointer;
|
|
||||||
background-position: center;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .corps_details_bateau .titre_composant .more {
|
|
||||||
background-image: url("images/show_more.png");
|
|
||||||
}
|
|
||||||
#Bateaux #details_bateau .corps_details_bateau .titre_composant .less {
|
|
||||||
background-image: url("images/show_less.png");
|
|
||||||
}
|
|
||||||
#Bateaux #creation_bateau .champs {
|
#Bateaux #creation_bateau .champs {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -272,10 +143,6 @@ Fichier bateaux.php
|
|||||||
#Bateaux #creation_bateau .champs .champs_valeur {
|
#Bateaux #creation_bateau .champs .champs_valeur {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
#Bateaux #creation_bateau .champs .champs_valeur select option.selection_actuelle {
|
|
||||||
color: var(--bleu_koriolan_fonce);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
#Bateaux #creation_bateau .champs .champs_valeur select option[disabled] {
|
#Bateaux #creation_bateau .champs .champs_valeur select option[disabled] {
|
||||||
color: var(--gris_tres_pale);
|
color: var(--gris_tres_pale);
|
||||||
background-color: var(--gris_tres_tres_pale);
|
background-color: var(--gris_tres_tres_pale);
|
||||||
@@ -326,18 +193,9 @@ Fichier bateaux.php
|
|||||||
height: unset !important;
|
height: unset !important;
|
||||||
margin: 20px 0px 20px 0px !important;
|
margin: 20px 0px 20px 0px !important;
|
||||||
}
|
}
|
||||||
.graphique .graph {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.graphique .titre{
|
.graphique .titre{
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
.top_part_graph {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.var {
|
.var {
|
||||||
/* display: flex; */
|
/* display: flex; */
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -349,36 +207,6 @@ Fichier bateaux.php
|
|||||||
.var label{
|
.var label{
|
||||||
display: unset !important;
|
display: unset !important;
|
||||||
}
|
}
|
||||||
.date_pick form{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date_pick input {
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date_pick label {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 2fr 3fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date_pick span {
|
|
||||||
position: relative;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date_maj_version{
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date_maj_version input,.date_maj_version select{
|
|
||||||
width: 200px !important;
|
|
||||||
margin-left: 30px !important;
|
|
||||||
margin-right: 30px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gestion_acces{
|
.gestion_acces{
|
||||||
/* height: 100; */
|
/* height: 100; */
|
||||||
@@ -403,17 +231,6 @@ Fichier bateaux.php
|
|||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.download{
|
|
||||||
font-weight: 800;
|
|
||||||
padding: 0;
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
.download:hover {
|
|
||||||
background: red;
|
|
||||||
color: var(--blanc_casse);
|
|
||||||
}
|
|
||||||
#type_acces {
|
#type_acces {
|
||||||
height: unset;
|
height: unset;
|
||||||
}
|
}
|
||||||
@@ -467,10 +284,6 @@ form.variables input{
|
|||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
#maCarte {
|
|
||||||
height: 600px;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------- POP UP EDIT ACCESS----------------------*/
|
/* --------------------- POP UP EDIT ACCESS----------------------*/
|
||||||
#popUpAcces{
|
#popUpAcces{
|
||||||
@@ -545,9 +358,6 @@ form.variables input{
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
#popUpAccesModal form .buttons_acces{
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------- GESTION DES ACCÈS (synthèse §48) ----------------------*/
|
/* --------------------- GESTION DES ACCÈS (synthèse §48) ----------------------*/
|
||||||
|
|
||||||
@@ -688,14 +498,7 @@ form.variables input{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------ Autres ---------------- */
|
/* ------------------ Autres ---------------- */
|
||||||
#dateSlider{
|
|
||||||
margin-bottom: 50px !important;
|
|
||||||
margin-top: 50px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dateSlider .noUi-connect{
|
|
||||||
background-color: var(--bleu_koriolan);
|
|
||||||
}
|
|
||||||
@media only screen and (max-width: 580px) {
|
@media only screen and (max-width: 580px) {
|
||||||
#Bateaux .boutons_haut_de_page {
|
#Bateaux .boutons_haut_de_page {
|
||||||
grid-template-columns: minmax(50px, auto);
|
grid-template-columns: minmax(50px, auto);
|
||||||
@@ -706,10 +509,6 @@ form.variables input{
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding : 0;
|
padding : 0;
|
||||||
}
|
}
|
||||||
.vars {
|
|
||||||
display: grid !important;
|
|
||||||
grid-template-columns: 1fr !important;
|
|
||||||
}
|
|
||||||
#popUpAccesModal{
|
#popUpAccesModal{
|
||||||
width: 95% !important;
|
width: 95% !important;
|
||||||
height: 60% !important;
|
height: 60% !important;
|
||||||
@@ -721,13 +520,6 @@ form.variables input{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 850px) {
|
@media only screen and (max-width: 850px) {
|
||||||
.top_part_graph {
|
|
||||||
flex-direction: column-reverse;
|
|
||||||
}
|
|
||||||
.vars {
|
|
||||||
display: grid !important;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
}
|
|
||||||
.page_principale {
|
.page_principale {
|
||||||
width: 96% !important;
|
width: 96% !important;
|
||||||
}
|
}
|
||||||
@@ -749,10 +541,6 @@ form.variables input{
|
|||||||
width: 80%;
|
width: 80%;
|
||||||
height: 40%;
|
height: 40%;
|
||||||
}
|
}
|
||||||
#dateSlider{
|
|
||||||
margin: auto;
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Petits écrans : formulaire d'invitation sur une colonne. Placé EN FIN de fichier : le bloc "max-width: 850px"
|
/* Petits écrans : formulaire d'invitation sur une colonne. Placé EN FIN de fichier : le bloc "max-width: 850px"
|
||||||
@@ -766,3 +554,108 @@ form.variables input{
|
|||||||
grid-column: auto;
|
grid-column: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --------------------- CONSULTATION DES DONNÉES (synthèse §50) ----------------------*/
|
||||||
|
|
||||||
|
/* Indicateur de fraîcheur (en-tête de la page du bateau) */
|
||||||
|
#Bateaux .fraicheur { padding: 1px 8px; border-radius: 1em; }
|
||||||
|
#Bateaux .fraicheur_verte { background-color: rgba(2, 166, 2, 0.12); color: #027a02; }
|
||||||
|
#Bateaux .fraicheur_orange { background-color: rgba(255, 166, 0, 0.18); color: #8a5a00; }
|
||||||
|
#Bateaux .fraicheur_rouge { background-color: rgba(229, 57, 53, 0.15); color: #b71c1c; }
|
||||||
|
|
||||||
|
/* Choix de la génération consultée */
|
||||||
|
#Bateaux .consultation .choix_generation { text-align: left; margin: 10px 0px 20px 0px; }
|
||||||
|
#Bateaux .consultation .choix_generation select { width: auto; max-width: 100%; display: inline-block; height: 2.4em; padding: 0px 10px; }
|
||||||
|
#Bateaux .consultation .generation_ancienne {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: var(--jaune_post_it_transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Domaines : une grille de familles (cartes) par domaine */
|
||||||
|
#Bateaux .consultation .domaine { margin-bottom: 30px; }
|
||||||
|
#Bateaux .consultation .domaine h2 { text-align: left; margin-bottom: 10px; }
|
||||||
|
#Bateaux .consultation .domaine {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
#Bateaux .consultation .domaine h2 { grid-column: 1 / -1; }
|
||||||
|
#Bateaux .consultation .famille {
|
||||||
|
border: 1px solid var(--bleu_koriolan_transparent);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 5px 15px 10px 15px;
|
||||||
|
background-color: var(--blanc);
|
||||||
|
}
|
||||||
|
#Bateaux .consultation .famille h3 { text-align: left; font-size: 1.05em; margin: 8px 0px; }
|
||||||
|
#Bateaux .consultation .famille table { width: 100%; }
|
||||||
|
#Bateaux .consultation .famille td {
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-top: 1px solid var(--gris_tres_tres_pale);
|
||||||
|
font-size: 0.9em;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
#Bateaux .consultation .famille td.valeurs_element { text-align: right; white-space: pre; font-weight: 600; }
|
||||||
|
#Bateaux .consultation .famille td.defaut_element { text-align: right; color: #b71c1c; font-size: 0.8em; }
|
||||||
|
#Bateaux .consultation .famille tr.en_defaut td.nom_element::before { content: "\26A0\00A0"; color: #e53935; }
|
||||||
|
|
||||||
|
/* Dernières alarmes */
|
||||||
|
#Bateaux .consultation .dernieres_alarmes { margin-bottom: 30px; }
|
||||||
|
#Bateaux .consultation .dernieres_alarmes h2 { text-align: left; margin-bottom: 10px; }
|
||||||
|
#Bateaux .consultation .dernieres_alarmes .aucune_alarme { text-align: left; font-style: italic; color: var(--gris_pale); }
|
||||||
|
#Bateaux .consultation .dernieres_alarmes table { width: 100%; }
|
||||||
|
#Bateaux .consultation .dernieres_alarmes td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-top: 1px solid var(--gris_tres_tres_pale);
|
||||||
|
font-size: 0.9em;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
#Bateaux .consultation .dernieres_alarmes td.date_alarme { white-space: nowrap; width: 1%; }
|
||||||
|
#Bateaux .consultation .dernieres_alarmes th { text-align: left; padding: 4px 8px; font-size: 0.85em; }
|
||||||
|
#Bateaux .consultation .dernieres_alarmes td.duree_alarme { white-space: nowrap; width: 1%; }
|
||||||
|
#Bateaux .consultation .dernieres_alarmes td.nom_alarme { font-weight: 600; color: #b71c1c; white-space: nowrap; width: 1%; }
|
||||||
|
#Bateaux .consultation .dernieres_alarmes .alarme_en_cours { color: #b71c1c; font-weight: 600; }
|
||||||
|
#Bateaux .consultation .dernieres_alarmes tr.alarme_termine td.nom_alarme,
|
||||||
|
#Bateaux .consultation .dernieres_alarmes tr.alarme_ponctuel td.nom_alarme { color: var(--bleu_koriolan_fonce); }
|
||||||
|
|
||||||
|
/* Courbes historiques (étape 3c) */
|
||||||
|
#Bateaux .consultation tr.avec_courbe { cursor: pointer; }
|
||||||
|
#Bateaux .consultation tr.avec_courbe:hover td { background-color: var(--bleu_koriolan_transparent); }
|
||||||
|
#panneau_courbe {
|
||||||
|
position: fixed; inset: 0; z-index: 9999;
|
||||||
|
background: rgba(255, 255, 255, 0.8); backdrop-filter: blur(3px);
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
}
|
||||||
|
#panneau_courbe[hidden] { display: none; }
|
||||||
|
#panneau_courbe .fenetre_courbe {
|
||||||
|
width: min(1000px, 95vw); max-height: 92vh; overflow-y: auto;
|
||||||
|
background: var(--blanc); border-radius: 10px; box-shadow: 0px 0px 7px 2px var(--bleu_koriolan);
|
||||||
|
padding: 10px 20px 15px 20px;
|
||||||
|
}
|
||||||
|
#panneau_courbe .entete_courbe { display: flex; justify-content: space-between; align-items: center; }
|
||||||
|
#panneau_courbe .entete_courbe h3 { margin: 5px 0px; text-align: left; }
|
||||||
|
#panneau_courbe #fermer_courbe {
|
||||||
|
background: none; border: none; color: var(--bleu_koriolan_fonce);
|
||||||
|
font-size: 2em; line-height: 1em; padding: 0px 8px; cursor: pointer;
|
||||||
|
}
|
||||||
|
#panneau_courbe .choix_courbe { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0px; }
|
||||||
|
#panneau_courbe .choix_courbe:empty { display: none; }
|
||||||
|
#panneau_courbe .choix_courbe button {
|
||||||
|
padding: 2px 12px; line-height: 1.6em; font-size: 0.85em; border-radius: 0.8em;
|
||||||
|
background-color: var(--blanc); color: var(--bleu_koriolan_fonce);
|
||||||
|
}
|
||||||
|
#panneau_courbe .choix_courbe button.actif { background-color: var(--bleu_koriolan_fonce); color: var(--blanc); }
|
||||||
|
#panneau_courbe .zone_courbe { position: relative; height: min(55vh, 420px); }
|
||||||
|
#panneau_courbe #message_courbe { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; margin: 0px; font-style: italic; }
|
||||||
|
#panneau_courbe #message_courbe[hidden] { display: none; }
|
||||||
|
#panneau_courbe #stats_courbe { font-size: 0.85em; text-align: left; white-space: pre-wrap; margin: 8px 0px 0px 0px; }
|
||||||
|
|
||||||
|
/* Information sur la mise à jour demandée à l'ouverture (étape 3d) */
|
||||||
|
#Bateaux .entete_bateau .info_mise_a_jour { display: block; font-size: 0.8em; font-style: italic; color: var(--gris_pale); margin-top: 3px; }
|
||||||
|
#Bateaux .entete_bateau .info_mise_a_jour.en_cours::before { content: "\21BB\00A0"; display: inline-block; animation: rotation_mise_a_jour 1.5s linear infinite; }
|
||||||
|
@keyframes rotation_mise_a_jour { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
||||||
|
#Bateaux .entete_bateau { height: auto; min-height: 60px; } /* place pour la ligne d'information ci-dessus */
|
||||||
|
|||||||
@@ -6,40 +6,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.text_contact{
|
|
||||||
height: 50%;
|
|
||||||
padding-left: 20%;
|
|
||||||
padding-right: 20%;
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.text_contact p{
|
|
||||||
font-size: 25px;
|
|
||||||
font-style: unset;
|
|
||||||
position: relative;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
.content_contact{
|
|
||||||
height: 50%;
|
|
||||||
padding-left: 5%;
|
|
||||||
padding-right: 5%;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.linkedin{
|
|
||||||
width: 33%;
|
|
||||||
height: 100%;
|
|
||||||
/* padding: 5%; */
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.linkedin img{
|
|
||||||
height: 50px;
|
|
||||||
position: relative;
|
|
||||||
object-fit: contain;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
.linkedin a {
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
.mail{
|
.mail{
|
||||||
width: 43%;
|
width: 43%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -56,71 +22,15 @@
|
|||||||
color: white;
|
color: white;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
.phone{
|
|
||||||
width: 33%;
|
|
||||||
height: 100%;
|
|
||||||
/* padding: 5%; */
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.phone p{
|
|
||||||
position: relative;
|
|
||||||
margin: auto;
|
|
||||||
padding: 20px;
|
|
||||||
padding-left: 40px;
|
|
||||||
padding-right: 40px;
|
|
||||||
background-color: var(--bleu_koriolan);
|
|
||||||
border-radius: 10px;
|
|
||||||
color: white;
|
|
||||||
font-size: 20px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
a:hover {
|
a:hover {
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
@media only screen and (max-width: 850px) {
|
@media only screen and (max-width: 850px) {
|
||||||
.content_contact{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-top: -10px;
|
|
||||||
padding-left: 10%;
|
|
||||||
padding-right: 10%;
|
|
||||||
}
|
|
||||||
.linkedin{
|
|
||||||
width: 80vw;
|
|
||||||
height: 33%;
|
|
||||||
padding: 5%;
|
|
||||||
}
|
|
||||||
.mail{
|
.mail{
|
||||||
width: 80vw;
|
width: 80vw;
|
||||||
height: 33%;
|
height: 33%;
|
||||||
padding: 5%;
|
padding: 5%;
|
||||||
}
|
}
|
||||||
.phone{
|
|
||||||
width: 80vw;
|
|
||||||
height: 33%;
|
|
||||||
padding: 5%;
|
|
||||||
}
|
|
||||||
.text_contact{
|
|
||||||
height: 50%;
|
|
||||||
padding-left: 5%;
|
|
||||||
padding-right: 5%;
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.text_contact p{
|
|
||||||
font-size: 18px;
|
|
||||||
background-color: var(--bleu_koriolan_transparent);
|
|
||||||
padding: 22px;
|
|
||||||
border: 3px solid var(--bleu_koriolan_transparent);
|
|
||||||
border-radius: 15px;
|
|
||||||
}
|
|
||||||
.linkedin img{
|
|
||||||
height: 50px;
|
|
||||||
position: relative;
|
|
||||||
object-fit: contain;
|
|
||||||
margin: auto;
|
|
||||||
border-radius: 14px;
|
|
||||||
}
|
|
||||||
.mail p{
|
.mail p{
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@@ -135,18 +45,4 @@ a:hover {
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
max-height: 67px;
|
max-height: 67px;
|
||||||
}
|
}
|
||||||
.phone p{
|
|
||||||
position: relative;
|
|
||||||
width: 80vw;
|
|
||||||
margin: auto;
|
|
||||||
margin-top: -10px;
|
|
||||||
padding: 20px;
|
|
||||||
padding-left: 30px;
|
|
||||||
padding-right: 30px;
|
|
||||||
background-color: var(--bleu_koriolan);
|
|
||||||
border-radius: 10px;
|
|
||||||
color: white;
|
|
||||||
font-size: 18px;
|
|
||||||
max-height: 67px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,9 +41,6 @@ Fichier shipmembers.php
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
row-gap:20px;
|
row-gap:20px;
|
||||||
}
|
}
|
||||||
#Shipmembers #login .login .message_login {
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
#Shipmembers #login .login .champs {
|
#Shipmembers #login .login .champs {
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -265,39 +262,6 @@ Fichier shipmembers.php
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#Shipmembers #historique .liste_commandes {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#Shipmembers #historique .liste_commandes .titre {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
|
||||||
background-color: var(--bleu_koriolan_fonce);
|
|
||||||
color: var(--blanc);
|
|
||||||
line-height:40px;
|
|
||||||
}
|
|
||||||
#Shipmembers #historique .liste_commandes .commande {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
|
||||||
line-height:30px;
|
|
||||||
}
|
|
||||||
#Shipmembers #historique .liste_commandes .commande:nth-child(even) {
|
|
||||||
background-color: var(--bleu_koriolan_transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
#Shipmembers #historique .liste_commandes .commande .prix_ttc {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
#Shipmembers #historique .liste_commandes .commande .download a {
|
|
||||||
line-height:30px;
|
|
||||||
}
|
|
||||||
#Shipmembers #historique .liste_commandes .commande .download img {
|
|
||||||
width: 30px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*--------------------------------------------------*/
|
/*--------------------------------------------------*/
|
||||||
#Shipmembers #bateau form {
|
#Shipmembers #bateau form {
|
||||||
|
|||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014-2024 Chart.js Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
Vendored
+14
File diff suppressed because one or more lines are too long
+100
@@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# ============================================================================
|
||||||
|
# purger_css.py - Repère (et retire, sur demande) les règles CSS qui ne s'appliquent à rien (synthèse §50).
|
||||||
|
#
|
||||||
|
# Une règle est retirée si CHACUN de ses sélecteurs cite un identifiant (#...) ou une classe (.…) qui n'apparaît nulle
|
||||||
|
# part dans les fichiers PHP / JS / HTML / QML du site (dossiers vendor exclus). Prudence :
|
||||||
|
# - noms construits par le code : un nom est jugé utilisé si lui-même apparaît, OU si l'un de ses débuts terminés par
|
||||||
|
# "_" est suivi d'un guillemet (".statut_actif" gardé grâce à 'statut_'.$classe), OU si l'une de ses fins commençant
|
||||||
|
# par "_" est précédée d'un guillemet (".EN_acc" gardé grâce à $langue.'_acc') ;
|
||||||
|
# - classes d'état posées par des scripts (Bootstrap...) toujours gardées (show, active, collapse...) ;
|
||||||
|
# - commentaires (y compris des règles mises en commentaire) conservés tels quels dans le CSS ; les lignes de
|
||||||
|
# commentaire du code (//, #, /* ... */) ne comptent pas comme un usage ;
|
||||||
|
# - un bloc @media devenu vide est retiré.
|
||||||
|
#
|
||||||
|
# Usage : python3 purger_css.py fichier.css racine_du_site [--appliquer]
|
||||||
|
# sans --appliquer : rapport seulement (à relire !) ; avec : réécrit fichier.css, l'original étant conservé en
|
||||||
|
# fichier.css.avant_purge.
|
||||||
|
# ============================================================================
|
||||||
|
import os, re, sys
|
||||||
|
|
||||||
|
css_chemin, racine = sys.argv[1], sys.argv[2]
|
||||||
|
appliquer = '--appliquer' in sys.argv
|
||||||
|
|
||||||
|
morceaux = []
|
||||||
|
for dossier, sous_dossiers, fichiers in os.walk(racine):
|
||||||
|
sous_dossiers[:] = [d for d in sous_dossiers if d not in ('vendor', 'vendor_OLD', '.git')]
|
||||||
|
for f in fichiers:
|
||||||
|
if f.endswith(('.php', '.js', '.html', '.qml')):
|
||||||
|
with open(os.path.join(dossier, f), encoding='utf-8', errors='replace') as fh:
|
||||||
|
# Lignes de commentaire ignorées (code mis en commentaire, notes) : un nom qui n'y figure plus que là
|
||||||
|
# n'est plus utilisé.
|
||||||
|
morceaux.extend(l for l in fh if not re.match(r'\s*(//|#|\*|/\*)', l))
|
||||||
|
corpus = '\n'.join(morceaux)
|
||||||
|
|
||||||
|
TOUJOURS_GARDEES = {'show', 'hide', 'active', 'actif', 'fade', 'collapse', 'collapsing', 'collapsed', 'open',
|
||||||
|
'disabled', 'selected', 'hidden', 'visible'}
|
||||||
|
|
||||||
|
def nom_utilise(nom):
|
||||||
|
if nom in TOUJOURS_GARDEES: return True
|
||||||
|
if re.search(r'(?<![\w-])' + re.escape(nom) + r'(?![\w-])', corpus): return True
|
||||||
|
for i, c in enumerate(nom):
|
||||||
|
if c == '_' and i > 0:
|
||||||
|
debut, fin = nom[:i + 1], nom[i:]
|
||||||
|
# Uniquement une construction par concaténation : début suivi d'un guillemet ('statut_'.$x),
|
||||||
|
# ou fin précédée d'un guillemet ($x.'_acc') - "pied_de_page_mobile" ne justifie pas "#menu_mobile".
|
||||||
|
if any(q + fin in corpus or debut + q in corpus for q in ("'", '"')): return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def selecteur_utile(sel):
|
||||||
|
sel = re.sub(r'::?[\w-]+(\([^)]*\))?', '', sel) # pseudo-classes et pseudo-éléments
|
||||||
|
sel = re.sub(r'\[[^\]]*\]', '', sel) # sélecteurs d'attribut
|
||||||
|
noms = re.findall(r'[#.]([A-Za-z_][\w-]*)', sel)
|
||||||
|
return all(nom_utilise(n) for n in noms)
|
||||||
|
|
||||||
|
texte = open(css_chemin, encoding='utf-8', errors='replace').read()
|
||||||
|
|
||||||
|
# Commentaires mis de côté (marqueurs sans accolades), restitués tels quels à la fin :
|
||||||
|
commentaires = []
|
||||||
|
def mettre_de_cote(m):
|
||||||
|
commentaires.append(m.group(0))
|
||||||
|
return '\x00' + str(len(commentaires) - 1) + '\x00'
|
||||||
|
texte = re.sub(r'/\*.*?\*/', mettre_de_cote, texte, flags=re.S)
|
||||||
|
restituer = lambda t: re.sub(r'\x00(\d+)\x00', lambda m: commentaires[int(m.group(1))], t)
|
||||||
|
|
||||||
|
retirees = []
|
||||||
|
def traiter(bloc):
|
||||||
|
res, pos = [], 0
|
||||||
|
for m in re.finditer(r'([^{}]*)\{([^{}]*)\}', bloc):
|
||||||
|
sel_brut = m.group(1)
|
||||||
|
marqueurs = ''.join(re.findall(r'\s*\x00\d+\x00\s*', sel_brut)) # commentaires placés avant la règle
|
||||||
|
sel = re.sub(r'\x00\d+\x00', '', sel_brut).strip()
|
||||||
|
selecteurs = [x.strip() for x in sel.split(',') if x.strip()]
|
||||||
|
res.append(bloc[pos:m.start()])
|
||||||
|
if selecteurs and not any(selecteur_utile(x) for x in selecteurs):
|
||||||
|
retirees.append(' '.join(sel.split()))
|
||||||
|
res.append(marqueurs)
|
||||||
|
else:
|
||||||
|
res.append(m.group(0))
|
||||||
|
pos = m.end()
|
||||||
|
res.append(bloc[pos:])
|
||||||
|
return ''.join(res)
|
||||||
|
|
||||||
|
sortie, pos = [], 0
|
||||||
|
for m in re.finditer(r'@media[^{]*\{((?:[^{}]*\{[^{}]*\})*[^{}]*)\}', texte):
|
||||||
|
sortie.append(traiter(texte[pos:m.start()]))
|
||||||
|
interieur = traiter(m.group(1))
|
||||||
|
if '{' in interieur: sortie.append(texte[m.start():m.start(1)] + interieur + '}')
|
||||||
|
else: retirees.append(' '.join(texte[m.start():m.start(1)].split()) + ' (bloc devenu vide)')
|
||||||
|
pos = m.end()
|
||||||
|
sortie.append(traiter(texte[pos:]))
|
||||||
|
resultat = restituer(''.join(sortie))
|
||||||
|
|
||||||
|
print(f"{len(retirees)} règle(s) sans usage :")
|
||||||
|
try:
|
||||||
|
for r in retirees: print(" -", r[:120])
|
||||||
|
except BrokenPipeError: pass
|
||||||
|
if appliquer:
|
||||||
|
os.replace(css_chemin, css_chemin + '.avant_purge')
|
||||||
|
with open(css_chemin, 'w', encoding='utf-8') as fh: fh.write(resultat)
|
||||||
|
print(f"Écrit : {css_chemin} (original conservé : {css_chemin}.avant_purge)")
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- Délai (secondes) en dessous duquel l'ouverture de la page "Données du bateau" ne demande PAS d'archive immédiate :
|
||||||
|
-- données plus récentes que ce délai, ou demande déjà faite pour ce bateau depuis moins que ce délai (synthèse §50).
|
||||||
|
INSERT INTO TB_Configuration (Validite, Nom, Description, Valeur)
|
||||||
|
VALUES (1, 'Delai_demande_archive',
|
||||||
|
'Délai (secondes) minimal entre deux demandes d''archive immédiate pour un même bateau, et âge minimal des données pour en demander une', '300');
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Ship2net - surveillance des demandes d'archive déposées par le portail
|
||||||
|
|
||||||
|
[Path]
|
||||||
|
DirectoryNotEmpty=/var/www/demandes/a_traiter
|
||||||
|
Unit=ship2net-demandes.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=paths.target
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Ship2net - traitement des demandes d'archive déposées par le portail
|
||||||
|
After=network-online.target docker.service
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
User=koriolan
|
||||||
|
Group=koriolan
|
||||||
|
ExecStart=/usr/bin/php /var/www/traiter_demandes_archivage.php
|
||||||
|
Nice=5
|
||||||
|
TimeoutStartSec=5min
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
// ============================================================================
|
||||||
|
// traiter_demandes_archivage.php - Traitement des demandes d'archive déposées par le portail (synthèse §50, étape 3d).
|
||||||
|
// Exécuté NATIVEMENT sur le VPS par ship2net-demandes.service, lui-même déclenché par ship2net-demandes.path dès
|
||||||
|
// qu'un fichier apparaît dans /var/www/demandes/a_traiter (le portail, dans son conteneur, ne peut pas lancer le
|
||||||
|
// traitement lui-même : il le demande en y déposant un fichier nommé d'après le numéro du bateau).
|
||||||
|
//
|
||||||
|
// Pour chaque demande (fichier consommé AVANT traitement, pour ne jamais boucler) :
|
||||||
|
// 1. demande au CM5 du bateau de produire une archive IMMÉDIATEMENT (POST /archives/demande sur son API, via
|
||||||
|
// NetBird) - l'Appli met TB_Systeme.Demande_sauvegarde_bdd à profit et génère l'archive dans la seconde ;
|
||||||
|
// 2. attend que cette nouvelle archive apparaisse dans la liste de l'API (au plus DELAI_ATTENTE_ARCHIVE secondes) ;
|
||||||
|
// 3. lance le traitement des archives de ce seul bateau (cron_archivage.php --bateau=N).
|
||||||
|
// La page du portail, qui interroge bateau_etat.php toutes les 3 s après sa demande, se met à jour dès le chargement.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
chdir("/var/www/html");
|
||||||
|
require_once __DIR__ . "/config/config.php";
|
||||||
|
require_once __DIR__ . "/config/fonctions_bdd.php";
|
||||||
|
|
||||||
|
const REPERTOIRE_DEMANDES_A_TRAITER = "/var/www/demandes/a_traiter";
|
||||||
|
const PORT_API_CM5_DEMANDE = 8080;
|
||||||
|
const DELAI_ATTENTE_ARCHIVE = 30; // secondes
|
||||||
|
const DELAI_FIN_ECRITURE_ARCHIVE = 2; // secondes laissées au CM5 pour finir d'écrire l'archive une fois listée
|
||||||
|
|
||||||
|
function Appel_cm5($adresse_netbird, $methode, $chemin)
|
||||||
|
{
|
||||||
|
$ch = curl_init("http://".$adresse_netbird.":".PORT_API_CM5_DEMANDE.$chemin);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $methode);
|
||||||
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||||
|
$reponse = curl_exec($ch);
|
||||||
|
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
if ($reponse === false || $code !== 200) return null;
|
||||||
|
$donnees = json_decode($reponse, true);
|
||||||
|
return (is_array($donnees) && ($donnees['status'] ?? '') === 'ok') ? $donnees : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Noms_archives($adresse_netbird)
|
||||||
|
{
|
||||||
|
$donnees = Appel_cm5($adresse_netbird, 'GET', '/archives');
|
||||||
|
if ($donnees === null) return null;
|
||||||
|
return array_map(fn($a) => $a['nom'], $donnees['archives'] ?? array());
|
||||||
|
}
|
||||||
|
|
||||||
|
function Traiter_demande($id_bateau)
|
||||||
|
{
|
||||||
|
Ouverture_bdd("127.0.0.1:3307", BDD_USER, BDD_PASSWORD, BDD_DATABASE);
|
||||||
|
$stmt = $_SESSION['db']->prepare("SELECT C.Adresse_netbird FROM TB_Affectations_bateaux A JOIN TB_Cartes C ON C.Adresse_mac = A.Adresse_mac
|
||||||
|
WHERE A.ID_Bateau=? AND A.Date_fin IS NULL");
|
||||||
|
$stmt->bind_param('i', $id_bateau);
|
||||||
|
$stmt->execute();
|
||||||
|
$ligne = $stmt->get_result()->fetch_row();
|
||||||
|
$stmt->close();
|
||||||
|
Fermeture_bdd();
|
||||||
|
|
||||||
|
$adresse = $ligne ? trim((string)$ligne[0]) : '';
|
||||||
|
if (filter_var($adresse, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false)
|
||||||
|
{
|
||||||
|
echo "Bateau {$id_bateau} : aucun module joignable affecté - demande ignorée.\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Archive immédiate (si le CM5 ne connaît pas encore cette route - ancienne version de l'API -, on se contente de
|
||||||
|
// rapatrier ce qui est déjà en attente) :
|
||||||
|
$avant = Noms_archives($adresse);
|
||||||
|
if ($avant === null) { echo "Bateau {$id_bateau} : CM5 injoignable ({$adresse}) - demande abandonnée.\n"; return; }
|
||||||
|
if (Appel_cm5($adresse, 'POST', '/archives/demande') === null)
|
||||||
|
{
|
||||||
|
echo "Bateau {$id_bateau} : demande d'archive immédiate refusée ou non prise en charge - rapatriement de l'existant.\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 2. Attente de la nouvelle archive :
|
||||||
|
$nouvelle = false;
|
||||||
|
for ($attente = 0; $attente < DELAI_ATTENTE_ARCHIVE && !$nouvelle; $attente += 2)
|
||||||
|
{
|
||||||
|
sleep(2);
|
||||||
|
$apres = Noms_archives($adresse);
|
||||||
|
if ($apres !== null && count(array_diff($apres, $avant)) > 0) $nouvelle = true;
|
||||||
|
}
|
||||||
|
echo "Bateau {$id_bateau} : ".($nouvelle ? "nouvelle archive produite." : "aucune nouvelle archive après ".DELAI_ATTENTE_ARCHIVE." s - rapatriement de l'existant.")."\n";
|
||||||
|
if ($nouvelle) sleep(DELAI_FIN_ECRITURE_ARCHIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Traitement des archives de ce seul bateau :
|
||||||
|
passthru("/usr/bin/php ".escapeshellarg(__DIR__."/cron_archivage.php")." --bateau=".(int)$id_bateau);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir(REPERTOIRE_DEMANDES_A_TRAITER)) { echo "Répertoire ".REPERTOIRE_DEMANDES_A_TRAITER." absent.\n"; exit(1); }
|
||||||
|
|
||||||
|
// Toutes les demandes présentes (plusieurs bateaux possibles), chacune consommée AVANT son traitement :
|
||||||
|
foreach (scandir(REPERTOIRE_DEMANDES_A_TRAITER) as $nom)
|
||||||
|
{
|
||||||
|
if ($nom === '.' || $nom === '..') continue;
|
||||||
|
$chemin = REPERTOIRE_DEMANDES_A_TRAITER."/".$nom;
|
||||||
|
@unlink($chemin);
|
||||||
|
if (preg_match('/^[1-9][0-9]{0,9}$/', $nom) !== 1) { echo "Demande '{$nom}' invalide - supprimée.\n"; continue; }
|
||||||
|
try { Traiter_demande((int)$nom); }
|
||||||
|
catch (Throwable $e) { echo "Bateau {$nom} : erreur - ".$e->getMessage()."\n"; } // N'empêche pas les demandes suivantes
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user