Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3403b567ba | ||
|
|
a37b90aa13 | ||
|
|
a1c7f45b03 | ||
|
|
cd106484d5 | ||
|
|
e846e90bb8 | ||
|
|
519fca42ce | ||
|
|
5f72228f13 | ||
|
|
cb3b7cb643 | ||
|
|
d144c10e3c | ||
|
|
e3504840aa | ||
|
|
67e88e500f | ||
|
|
233cd4bc2a | ||
|
|
f1de87ff58 | ||
|
|
d5a4d2d8a3 | ||
|
|
587fee8b88 | ||
|
|
d877b32fdc | ||
|
|
5dc87c2692 | ||
|
|
248ed63fda | ||
|
|
98977796dd | ||
|
|
491193d537 | ||
|
|
1feaaee153 | ||
|
|
eea89a04e2 | ||
|
|
5719ed26e7 | ||
|
|
1b262d07b3 | ||
|
|
b912c7bb15 | ||
|
|
af4a835489 | ||
|
|
601e4d63aa | ||
|
|
b7a68c5772 | ||
|
|
7eb7eb036f | ||
|
|
a5f7b15473 | ||
|
|
a8647daa6e | ||
|
|
7fe140666b | ||
|
|
b60441e3f4 | ||
|
|
f705cbb77c | ||
|
|
fcba43f0ac | ||
|
|
27df9c3519 | ||
|
|
b84006251f | ||
|
|
73437dacdd | ||
|
|
6e089e01d7 | ||
|
|
28507f52b8 | ||
|
|
e2c428936d | ||
|
|
82e59bcace | ||
|
|
aee2e10158 | ||
|
|
d8afee6f2c | ||
|
|
dac29e9656 | ||
|
|
ff6f23a467 | ||
|
|
c976da8b71 | ||
|
|
0ae1cabaa5 | ||
|
|
6ac1cd4b8c | ||
|
|
af29a55697 | ||
|
|
b627c5a6b4 | ||
|
|
6410b758f2 | ||
|
|
06546b1066 | ||
|
|
fbca970f5b | ||
|
|
dc33852fd3 | ||
|
|
793ca83a71 | ||
|
|
947df82c64 | ||
|
|
ee5ed69b9f | ||
|
|
d04ab264c1 | ||
|
|
ed097ea659 | ||
|
|
2514427da5 | ||
|
|
129f047567 |
@@ -1,39 +0,0 @@
|
|||||||
# OS
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
desktop.ini
|
|
||||||
|
|
||||||
# Editors
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
*~
|
|
||||||
|
|
||||||
# Backups
|
|
||||||
*.bak
|
|
||||||
*.bak-*
|
|
||||||
*.orig
|
|
||||||
*.old
|
|
||||||
|
|
||||||
# Build artifacts (les ZIP/XPI livrés ne sont pas dans le repo, ils sont buildés à la demande)
|
|
||||||
*.zip
|
|
||||||
*.xpi
|
|
||||||
*.crx
|
|
||||||
|
|
||||||
# Node (si jamais utilisé pour build)
|
|
||||||
node_modules/
|
|
||||||
package-lock.json
|
|
||||||
npm-debug.log*
|
|
||||||
|
|
||||||
# Logs
|
|
||||||
*.log
|
|
||||||
rebuild.log
|
|
||||||
|
|
||||||
# Dossiers de travail temporaires
|
|
||||||
extracted/
|
|
||||||
temp/
|
|
||||||
tmp/
|
|
||||||
|
|
||||||
# Tests
|
|
||||||
test-output/
|
|
||||||
@@ -1,16 +1,599 @@
|
|||||||
# CHANGELOG — Extension Planification EasyVista Canton de Vaud
|
# CHANGELOG : extension Planification (EasyVista, Canton de Vaud)
|
||||||
|
|
||||||
> Ce changelog documente l'évolution de l'extension Chrome/Firefox "Planification"
|
> Ce changelog documente l'évolution de l'extension Chrome/Firefox "Planification"
|
||||||
> développée par Quentin Rouiller pour les techniciens IT du Canton de Vaud.
|
> développée par Quentin Rouiller pour les coordinateurs DGNSI (Canton de Vaud).
|
||||||
>
|
>
|
||||||
> Les versions documentées ci-dessous sont celles dont les détails sont connus.
|
> Les versions documentées ci-dessous sont celles dont les détails sont connus.
|
||||||
> Pour les versions plus anciennes, Claude Code se basera sur l'analyse du code
|
> Pour les versions plus anciennes, l'analyse du code source permet de
|
||||||
> source pour déterminer un message de commit pertinent.
|
> reconstituer un message de version pertinent.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## v2026.5.37 — Refonte vue horizontale (sidebar complète)
|
## v2026.5.50 : Texte des actions rétabli (format EasyVista du 29.09.2026), catégories Roll Out et Déménagement
|
||||||
**Branche** : current
|
|
||||||
|
> Correctif urgent : depuis le 29 septembre 2026, le texte complet des actions ne s'affichait plus. EasyVista renvoie désormais les actions avec une cellule de moins, et l'extension les lisait par position. Elle les retrouve maintenant par leur contenu. Cette version ajoute aussi deux catégories de réservation.
|
||||||
|
|
||||||
|
### Texte des actions
|
||||||
|
|
||||||
|
- **Texte complet des actions rétabli** dans les bulles et les fiches, avec l'ancien comme avec le nouveau format d'EasyVista.
|
||||||
|
- **Plus robuste aux prochains changements** : la description est repérée par le nom de sa colonne, et l'identifiant de l'action par son contenu, plus par leur position.
|
||||||
|
- **Changement de format signalé** : si EasyVista modifie encore la structure au point qu'aucune action ne soit lisible, un avertissement apparaît dans la console (une seule fois, sans aucune donnée personnelle). Les fiches ou historiques qui ne se chargent pas sont aussi signalés dans la console.
|
||||||
|
|
||||||
|
### Réservations
|
||||||
|
|
||||||
|
- **Deux nouvelles catégories : Roll Out et Déménagement.** Elles sont propres à la Planification : dans EasyVista, la réservation reste « Evènements spéciaux ». Elles sont partagées entre tous les postes par le dossier partagé, comme le n°, la ville et la rue.
|
||||||
|
- **Catégorie proposée à la saisie du n°** : le n° d'une intervention Roll Out propose Roll Out.
|
||||||
|
- **Catégorie inconnue conservée** : une catégorie posée par une version plus récente n'est plus effacée quand on modifie la réservation.
|
||||||
|
|
||||||
|
### Déploiement
|
||||||
|
|
||||||
|
- **Mettre à jour tous les postes le même jour.** Un poste resté en 2026.5.49 qui modifie une réservation Roll Out ou Déménagement effacerait cette catégorie sur tous les postes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v2026.5.49 : Sécurité hors session, onglet Email clarifié, lecture au défilement, affichage et synchronisation fiabilisés
|
||||||
|
|
||||||
|
> Cette version renforce la **sécurité** quand aucune session EasyVista n'est active, clarifie l'**onglet Email** (déplacement et annulation bien séparés, aperçu de la copie), améliore la **lecture du planning au défilement** (le nom du technicien, son compteur et la frise horaire restent visibles), et corrige l'**affichage de la reconnexion** ainsi que plusieurs comportements de la synchronisation et des réservations.
|
||||||
|
|
||||||
|
### Sécurité et confidentialité (sans session EasyVista)
|
||||||
|
|
||||||
|
- **Aucune écriture dans le dossier partagé sans session active** : tant que vous n'êtes pas identifié dans EasyVista, l'extension travaille sur le **cache local seul** et ne touche plus au dossier partagé. Un ancien cache reste consultable, mais il n'est plus alimenté ni relu depuis le partage.
|
||||||
|
- **Aucune modification sans session active** : déplacement d'intervention, suppression, création ou édition d'absence, réservation : tout est bloqué proprement tant que la session n'est pas rétablie, avec un message clair. Cela évite d'écrire des données incohérentes qui seraient ensuite propagées aux autres postes.
|
||||||
|
|
||||||
|
### Onglet Email clarifié
|
||||||
|
|
||||||
|
- **Déplacement et annulation séparés** : l'onglet Email présente désormais deux sections distinctes (déplacement d'une intervention / annulation-suppression), chacune avec sa case d'activation et son propre modèle de message.
|
||||||
|
- **Copie (CC) commune avec aperçu en direct** : un bloc « Copie (CC) » en tête montre, sous forme de puces, les adresses effectivement retenues (les adresses invalides sont barrées) : vous voyez immédiatement à qui la copie sera envoyée.
|
||||||
|
- **Variables cliquables en colonnes** pour les deux modèles (déplacement et annulation), rangées par thème pour une insertion rapide.
|
||||||
|
|
||||||
|
### Lecture du planning au défilement (vue classique)
|
||||||
|
|
||||||
|
- **En-tête de carte collant** : quand vous faites défiler une carte, le **nom du technicien**, son **nombre d'interventions** et la **frise horaire** restent visibles en haut : plus besoin de remonter pour savoir de qui il s'agit.
|
||||||
|
- **Survol d'une pause** : passer la souris sur une pause met la **frise horaire en évidence** (sans faner les interventions), pour bien distinguer le créneau libre.
|
||||||
|
|
||||||
|
### Affichage et reconnexion fiabilisés
|
||||||
|
|
||||||
|
- La bannière **« Session EasyVista expirée »** s'affiche au bon endroit (dans le corps, sous la barre de recherche) et **uniquement quand de vraies cartes sont affichées** ; sinon un message centré prend la place du planning. Plus de fausse bannière au-dessus d'un écran vide.
|
||||||
|
- **Plus de bulles de techniciens vides** : lors d'un rafraîchissement automatique alors que la session est morte, l'extension détecte la page de connexion EasyVista et évite d'afficher une équipe vide.
|
||||||
|
- La **barre de progression** ne recouvre plus le nom du technicien pendant un rafraîchissement lorsqu'on a fait défiler.
|
||||||
|
|
||||||
|
### Corrections
|
||||||
|
|
||||||
|
- **Noms de contact nettoyés** : les caractères parasites (« ? » et caractères de remplacement) parfois collés aux noms venant d'EasyVista sont retirés.
|
||||||
|
- **Deux interventions du même technicien le même jour** (deux actions distinctes) s'affichent désormais chacune sur sa propre carte, sans en perdre.
|
||||||
|
- **Caractères parasités depuis EasyVista** (dans les libellés d'intervention, contacts, lieux) corrigés à la source.
|
||||||
|
- **Indicateur d'écart d'adresse** remis à jour après correction : actualiser une intervention dont l'adresse a été corrigée retire bien l'alerte.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v2026.5.48 : Notification par email des déplacements, onglet Email, fiches réservation fiables entre postes, recherche et reconnexion durcies
|
||||||
|
|
||||||
|
> Après le déplacement d'une intervention (autre jour ou autre technicien), l'extension peut ouvrir un **brouillon d'email pré-rempli** dans votre application mail pour prévenir les techniciens concernés : l'envoi reste un geste manuel. Elle corrige aussi la circulation des informations de réservation entre postes, le bouton de reconnexion quand il n'y a aucun cache, et plusieurs comportements de la recherche.
|
||||||
|
|
||||||
|
### Email après un déplacement (nouveau)
|
||||||
|
|
||||||
|
- **Brouillon automatique** : après un déplacement vers un **autre jour** ou un **autre technicien** (fenêtre de modification ou glisser-déposer), un brouillon pré-adressé aux deux techniciens s'ouvre dans l'application mail (ex. Outlook). Un simple ajustement d'heures n'ouvre rien. **Rien n'est envoyé automatiquement.**
|
||||||
|
- **Case « Prévenir les techniciens par email »** dans les fenêtres de déplacement : quand l'option est active, la décocher ne vaut que pour **ce déplacement-là** : le prochain déplacement la retrouve cochée. L'option ne se désactive durablement que dans les Paramètres.
|
||||||
|
- **Destinataires** séparés par « ; » (compatibilité Outlook) ; si une adresse manque, le brouillon s'ouvre quand même, à compléter directement dans l'email (une notification indique qui manque).
|
||||||
|
- **Nouvel onglet « Email »** dans les Paramètres : option, et **modèle du message** entièrement personnalisable (sujet + texte), avec **variables cliquables** rangées par thème (Intervention / Avant / Après) et **aperçu en direct** sur un exemple. Le modèle de base présente le déplacement en deux blocs AVANT / APRÈS bien lisibles.
|
||||||
|
- **Adresses des techniciens** dans l'onglet Équipe (colonne Email, boutons au-dessus de la liste) : bouton « **Récupérer les emails** » qui remplit les adresses manquantes depuis l'annuaire EasyVista, et **complète au passage un nom affiché tronqué**, avec un garde-fou contre les homonymes. Une adresse saisie à la main n'est jamais écrasée. Champ vide = adresse proposée `prenom.nom@domaine` (domaine réglable au même endroit, appliqué à tous).
|
||||||
|
|
||||||
|
### Informations de réservation fiables entre postes
|
||||||
|
|
||||||
|
- Les informations saisies sur une réservation (n° d'intervention, ville, rue, catégorie) **voyagent désormais avec la réservation** dans le dossier partagé, horodatées : la modification la plus récente gagne, sur tous les postes.
|
||||||
|
- Fin des « fuites » d'informations : une réservation posée par un autre coordinateur ne récupère plus les infos d'une réservation précédente au même créneau, un identifiant recyclé par EasyVista ne fait plus hériter les infos d'un autre technicien, et un champ volontairement vidé ne « ressuscite » plus.
|
||||||
|
- Une réservation **re-planifiée directement dans EasyVista** conserve ses informations quand le rapprochement est sans ambiguïté.
|
||||||
|
- Divers : le pompier du jour n'est plus compté en double dans les statistiques ; « réservé toute la journée » se calcule sur les heures affichées ; les champs pré-remplis du formulaire ne sont plus écrasés par le remplissage automatique.
|
||||||
|
|
||||||
|
### Reconnexion et recherche
|
||||||
|
|
||||||
|
- **Bouton « Ouvrir EasyVista »** (écran « Session expirée » sans aucun cache) : après la reconnexion, l'écran se ferme réellement et le planning s'affiche ; re-cliquer relance le chargement au lieu d'être ignoré.
|
||||||
|
- **Recherche** : la recherche part de façon fiable quand on arrête de taper (les relances au survol ne l'interrompent plus), **Entrée** lance la recherche immédiatement, la roue de chargement est visible dès le début, déplier « Planifiée (passé) » ne ferme plus la zone de résultats, et seuls les statuts **Clôturé / Fait** sont affichés sur les résultats.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v2026.5.47 : Vue « échelle de temps exacte », aide à la saisie, erreurs mises en évidence, corrections de fiabilité
|
||||||
|
|
||||||
|
> Cette version peaufine la vue horizontale « échelle de temps exacte » : un retour visuel accompagne la pose d'un rendez-vous ou d'une absence, et les erreurs de saisie (rendez-vous dont le début est égal à la fin) sont désormais bien visibles et signalées. Elle corrige aussi plusieurs problèmes de navigation entre dates et de synchronisation du dossier partagé, réorganise le panneau de synchronisation, et ajoute un bouton de test de la communication avec le script du poste.
|
||||||
|
|
||||||
|
### Vue « échelle de temps exacte » : aide à la saisie
|
||||||
|
|
||||||
|
- **Retour visuel au survol d'un créneau libre** : une zone surlignée suit la souris et affiche **l'heure exacte** (début → fin) du rendez-vous ou de l'absence qu'un clic droit poserait. Une bulle fixe, en haut de la ligne, rappelle la **plage libre et la durée disponible** du créneau (« Libre 12:00–18:00 / 6 h disponible »). Elle se place toujours sur un espace libre, jamais par-dessus un rendez-vous, et ne masque pas l'heure choisie.
|
||||||
|
- **Erreurs de saisie mises en évidence** : un rendez-vous dont le **début est égal à la fin** (ex. 10:00 → 10:00) est désormais affiché en **rouge** et reste **visible** (avant, il disparaissait sur l'échelle exacte). Survoler une telle zone pour en poser un autre affiche un **avertissement** et **empêche** la création tant que l'erreur n'est pas corrigée.
|
||||||
|
- **Interventions courtes visibles** : une intervention de 1 à 5 minutes garde une largeur minimale et reste visible.
|
||||||
|
- **Zone du nom** : le fond du nom du technicien remplit toute la hauteur de la ligne (nom centré).
|
||||||
|
- **Barre des heures** : reste collée en haut au défilement et passe sous la banderole de reconnexion (au lieu de disparaître derrière).
|
||||||
|
|
||||||
|
### Corrections de fiabilité
|
||||||
|
|
||||||
|
- **Navigation rapide entre dates** : le jour que l'on quitte ne peut plus écraser le jour qui vient de s'afficher.
|
||||||
|
- **Technicien retiré de l'équipe** : n'apparaît plus sur les jours relus depuis le dossier partagé.
|
||||||
|
- **Synchronisation du dossier partagé** : correction d'un blocage où l'écriture n'aboutissait jamais (conflit permanent entre l'index et le fichier relu) ; l'index se répare de lui-même.
|
||||||
|
- **Jour présent au dossier mais pas en local** : lu depuis le dossier d'emblée : plus de re-remplissage visible inutile.
|
||||||
|
- **Absences** : plus de doublon « absence récurrente » + « vraie absence » le même jour ; le fond des cartes d'absence remplit toute la carte en échelle exacte.
|
||||||
|
- **Réservations multiples le même jour** : les deux créneaux coexistent ; saisir le n° d'intervention dans le formulaire remplit automatiquement ville, rue et catégorie.
|
||||||
|
- **Barre de progression** et **message de reconnexion** : bien positionnés en vue horizontale (barre en haut de la sidebar, message centré dans le contenu).
|
||||||
|
|
||||||
|
### Synchronisation multi-postes
|
||||||
|
|
||||||
|
- **Nouveau bouton « Tester la communication »** (onglet Synchro) : diagnostic de bout en bout extension → script du poste → dossier partagé.
|
||||||
|
- **Panneau Synchro réorganisé** en sections claires : état de la synchro, dossier partagé, script sur le poste, journal d'activité.
|
||||||
|
- **Script du poste (native messaging host) 2026.1.8** : verrou dédié sur l'index du dossier partagé (protège l'écriture simultanée par plusieurs postes ou onglets) et icône de la zone de notification ré-affichée au redémarrage de l'explorateur Windows. Le bouton « Télécharger le script » récupère cette version.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v2026.5.46 : Refonte de la couche données, vue « échelle de temps exacte », session fiabilisée, réservations enrichies
|
||||||
|
|
||||||
|
> Version majeure par son contenu : la couche données est entièrement refondue (store unifié par jour, un seul verdict par intervention, synchronisation du dossier partagé en sous-dossier `v2`), une nouvelle disposition « échelle de temps exacte » s'ajoute à la vue horizontale, la détection de session EasyVista est fiabilisée de bout en bout, et les réservations portent leurs informations (n°, lieu, catégorie) sur tous les postes. Le script PowerShell du dossier partagé est désormais publié avec l'extension et téléchargeable depuis les Paramètres.
|
||||||
|
|
||||||
|
### Couche données v2 (refonte complète)
|
||||||
|
|
||||||
|
- **Un store par jour** (`planv2_<date>`) : liste plate d'interventions dédoublonnées par identité stable (requestId > actionId > réf) : la seule source de vérité ; le rendu, le cache local, le dossier partagé et EasyVista n'en sont que des projections ou des sources.
|
||||||
|
- **Un seul verdict par intervention** (`live / done / suspended / closed / gone / moved / unknown`) qui remplace les anciens marqueurs dispersés (ghost, statuts de disparition, diagnostics).
|
||||||
|
- **Une seule fusion** (modèle vie/mort) utilisée partout : EV↔store, local↔dossier, import. Une vie strictement postérieure à une mort lève les tombstones (réapparition EV autoritaire).
|
||||||
|
- **Dossier partagé** : transport natif inchangé (verrous + écriture atomique + compare-and-swap), fichiers v2 dans le sous-dossier `v2` du partage : les postes en ancienne version n'interfèrent plus. Ordre des sources refondu : EasyVista s'affiche immédiatement, le dossier habille les cartes sans re-télécharger, le fetch ne traite que ce qui manque, l'enrichissement est repoussé au dossier en fin de chargement.
|
||||||
|
- Les interventions déplacées par l'extension portent un verdict « déplacée » propagé (plus d'analyse inutile), avec fenêtre anti cache-serveur EV.
|
||||||
|
- Les disparues en attente de verdict restent affichées (grisées, spinner) au lieu de disparaître puis réapparaître.
|
||||||
|
- Outil réservé à l'auteur (onglet Test) : conversion de l'ancien cache dossier v1 vers le modèle v2, sans modifier les anciens fichiers.
|
||||||
|
- Les anciens caches (`planning_cache_*`) sont purgés au démarrage.
|
||||||
|
|
||||||
|
### Vue horizontale : nouvelle disposition « échelle de temps exacte »
|
||||||
|
|
||||||
|
- **Nouvelle option** dans Paramètres → Apparence (exclusive avec « Afficher les pauses » : cocher l'une décoche l'autre). La vue par défaut reste inchangée.
|
||||||
|
- Le bandeau timeline disparaît : chaque intervention/réservation/absence partielle est posée **en position réelle** sur une bande continue de la plage affichée (position et largeur proportionnelles à l'heure et à la durée).
|
||||||
|
- **Règle des heures fixe** en haut (reste collée au défilement), **lignes verticales continues** sur toute la hauteur du tableau : trait net à chaque heure, pointillé discret aux demi-heures, césure rayée à midi ; les traits passent derrière les bulles et sautent les techniciens absents.
|
||||||
|
- **Ligne rouge « maintenant »** continue sur toute la hauteur (jour courant), reposée instantanément à chaque rendu ; elle saute aussi les absents.
|
||||||
|
- **Chevauchements** : les éléments qui se recouvrent s'empilent en sous-lignes de même hauteur : la zone du technicien s'agrandit seulement dans ce cas ; tolérance de 10 minutes pour ne pas empiler de simples enchaînements.
|
||||||
|
- **Cartes** : fond neutre, le bord gauche porte la couleur de catégorie et sert de **poignée** (cliquer = fenêtre de modification, glisser = déplacer ; présente sur toutes les cartes, déplacement bloqué pour les faites/clôturées, réservations et absences). Numéro d'intervention en tête, ville + adresse groupées ; cartes étroites : texte compact (ville + fin d'adresse, « Rue de l'Exemple 5 » → « Exemple 5 »), jamais vides tant qu'il reste de la place.
|
||||||
|
- **Réservations** : affichent sous l'intitulé le lieu renseigné à la création, sinon le nom de la personne qui a réservé.
|
||||||
|
- **Zones libres** : survol = « Libre HH:MM–HH:MM » (y compris sur les journées totalement vides), clic droit = Réservation/Absence à l'heure du curseur, dépôt du drag précis (pas de 30 min) avec marqueur vert sur toute la bande.
|
||||||
|
- Alignement règle/cartes mesuré au pixel réel (indépendant du zoom et de la taille de texte).
|
||||||
|
|
||||||
|
### Session EasyVista fiabilisée (fin des faux « déconnecté »)
|
||||||
|
|
||||||
|
- **Cookie réécrit ≠ supprimé** : le navigateur émet toujours une « suppression » technique juste avant de reposer un cookie mis à jour : l'extension basculait à tort en « session expirée » à chaque rafraîchissement du PHPSESSID (permission cookies active). Désormais ignoré ; une vraie suppression attend 3 s (relog possible) puis vérifie au serveur avant d'alarmer.
|
||||||
|
- **Expiration sur preuve uniquement** : une erreur transitoire (réseau, HTTP 5xx) ne déclenche plus la bannière : seule une preuve le fait (page de login à la sonde, temps restant nul, plus aucun PHPSESSID) ; sinon nouvel essai dans 30 s.
|
||||||
|
- **Onglet EasyVista facultatif** avec la permission cookies : le cookie suffit aux requêtes ; fermer l'onglet EV ne « perd » plus la session.
|
||||||
|
- **Contre-vérification HTTP** : avant de classer un 401/403/404 en « session expirée », l'endpoint d'autorité `session_time` est sondé ; si la session vit, l'erreur est reclassée « EV inaccessible » (transitoire).
|
||||||
|
- **Compteur calé sur le serveur** : recalage périodique sur la vraie valeur EasyVista (au démarrage, toutes les 90 s, au retour de visibilité), plus de recalage erroné sur l'expiration du cookie ; badge session en direct (MM:SS) avec « Prolonger la session » dans le popup.
|
||||||
|
|
||||||
|
### Réservations
|
||||||
|
|
||||||
|
- **Modal enrichie** : préréglages Matin/Après-midi/Journée puis heures ; champs n° d'intervention, ville, rue, catégorie ; recherche automatique des informations dans EasyVista à partir du n° (catégorie et localisation pré-remplies).
|
||||||
|
- **Meta persistante et partagée** : ces informations survivent aux rafraîchissements (EV ne les stocke pas) et remontent désormais au dossier partagé : tous les postes voient le n°, le lieu et la catégorie d'une réservation, quel que soit le poste qui l'a créée ou modifiée.
|
||||||
|
- Édition d'une réservation (heures ou infos seules), suppression, tooltip détaillé avec ouverture/copie du n° lié.
|
||||||
|
|
||||||
|
### Pompier
|
||||||
|
|
||||||
|
- **Assignations « pompier du mois »** : définition par période (bouton Ajouter → Pompier), affichage dans la barre de stats, liste des pompiers actuels/à venir, édition/déplacement de période.
|
||||||
|
- La bandelette « En pompier du … au … » reste fixée en haut de la carte ; un pompier absent toute la journée est exclu du compteur.
|
||||||
|
|
||||||
|
### Coordinateur (en développement)
|
||||||
|
|
||||||
|
- Badge « Mes actions » dans la barre de stats + onglet dédié listant les interventions où le coordinateur a une action ; outil d'impersonation (réservé à l'auteur) pour tester.
|
||||||
|
|
||||||
|
### Douchette
|
||||||
|
|
||||||
|
- **Vraie confirmation EasyVista** : l'envoi vérifie le marqueur de prise en compte renvoyé par EV : toast « confirmé par EasyVista » fiable.
|
||||||
|
- **Resynchronisation automatique** après un déplacement d'intervention (jour source + jour cible, regroupés si même jour), avec compte-rendu clair et relance possible.
|
||||||
|
|
||||||
|
### Recherche
|
||||||
|
|
||||||
|
- **Opérateurs** : espace = tous les mots, `%` joker, `"expression exacte"`, `-mot` exclusion, `tech:/lieu:/ref:/cat:/contact:` ciblage de champ.
|
||||||
|
- **Pagination** 15 par 15 au défilement ; passé replié par défaut ; badge « Jour affiché » ; aide sur la syntaxe.
|
||||||
|
- **Mise en avant stabilisée** : « Jour ↗ » navigue et surligne la carte pendant 15 s, survit aux re-rendus, un seul défilement animé.
|
||||||
|
- La croix d'effacement native du navigateur (en doublon avec celle de l'extension) est masquée.
|
||||||
|
|
||||||
|
### Interventions & affichage
|
||||||
|
|
||||||
|
- **Multi-jours** : badge pilule (« 📅 ⇢ 16/06 » / « ⇠ 12/06 »), heures d'affichage par jour, période complète au survol, déplacement bloqué (à modifier dans EasyVista).
|
||||||
|
- **Conflit au déplacement** : le modal de confirmation signale toute occupation du technicien cible (détail en rouge) et **propose le créneau libre le plus proche** (même durée) en un bouton.
|
||||||
|
- **Vérification d'adresse** : la localisation officielle de la fiche devient l'adresse affichée ; si elle diverge de celle écrite dans l'action, le lieu passe en rouge avec explication au survol (comparaison tolérante ; exception Roll Out).
|
||||||
|
- **Contacts** : deux numéros de téléphone dans une action ne sont plus collés à l'affichage (désormais « 021 000 00 84 / 079 000 00 21 ») ; labels de fiche collés au contact séparés proprement.
|
||||||
|
- **Ouverture de fiche** : les interventions faites/clôturées se rouvrent à nouveau dans EasyVista (le lien interne est régénéré via la référence).
|
||||||
|
- Sections Matin / Après-midi / « Journée » harmonisées sur les deux vues ; éléments chevauchant midi non découpés ; catégorie « Autre » en violet foncé (fini le gris confondu avec les cartes faites).
|
||||||
|
- Interventions « faites » plus ré-analysées à chaque actualisation (économie réseau).
|
||||||
|
|
||||||
|
### Dossier partagé : script PowerShell publié
|
||||||
|
|
||||||
|
- Le script hôte (native messaging) est publié avec cette version et **téléchargeable depuis Paramètres → Dossier partagé**.
|
||||||
|
|
||||||
|
### Qualité
|
||||||
|
|
||||||
|
- Nettoyage complet : fonctions mortes retirées, aucun doublon de code, tous les journaux passés au logger maison (console propre en production), plus aucun emoji dans les logs, marqueurs de révision internes retirés.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v2026.5.45 : Dock latéral drag&drop, fix verdicts ghost, multi-onglets EZV résolu
|
||||||
|
|
||||||
|
> Refonte de l'expérience drag&drop avec dock latéral pour parquer des interventions entre les jours, fix critique du parser de fiches qui marquait à tort des interventions terminées comme annulées, résolution des 6 issues ouvertes (multi-onglets EZV, absences récurrentes, popups épinglés, pompier absent), et nombreux ajustements d'ergonomie.
|
||||||
|
|
||||||
|
### Issues résolues
|
||||||
|
|
||||||
|
- **#3** Coches « Absences récurrentes » : merge propre avec l'état stocké au lieu d'écrasement, les coches sont retenues lors d'un changement de groupe ou d'une réouverture des paramètres.
|
||||||
|
- **#4** Perte de session EZV multi-onglets : permission optionnelle `cookies` + listener `cookieChanged` côté background, toggle dans Paramètres → Diagnostics. La session reste valable même après reconnexion et fermeture d'un onglet EZV.
|
||||||
|
- **#5** Bouton de copie de référence dans une popup épinglée : handler `copy-ref` ajouté.
|
||||||
|
- **#6** Popup épinglée au premier plan : clic sur une popup recalcule le z-index pour la passer au-dessus des autres.
|
||||||
|
- **#7** Notification « +2 min » fantôme : reset des flags d'alerte slide au retour de la prolongation.
|
||||||
|
- **#8** Compteur pompier : un pompier absent toute la journée est exclu du compteur.
|
||||||
|
|
||||||
|
### Dock latéral drag&drop
|
||||||
|
|
||||||
|
Le dock à droite permet de mettre des interventions de côté pendant qu'on navigue entre les jours, puis de les redéposer plus tard.
|
||||||
|
|
||||||
|
- Apparition graduelle pendant un drag : peep-min (sans contenu), peep (avec cartes), expanded (au survol ou au bord droit).
|
||||||
|
- **Délai 500 ms** pour expand/collapse pour éviter le flicker quand le curseur effleure le bord du dock.
|
||||||
|
- Card du dock : référence + durée prévue (`1h`, `1h30`, `45min`) avec barre verticale 4 px sur la gauche dans la couleur de la catégorie. Fond transparent.
|
||||||
|
- Bouton de retrait `×` : **appui long 2 s** avec animation `conic-gradient`. Un clic simple ne fait rien.
|
||||||
|
- Drag depuis le dock : retrait différé à l'activation effective du drag (5 px). Ghost flottant sans heure à gauche. Drop sur tech → modal de confirmation. Drop hors zone / Échap / sur le même slot d'origine → restauration dans le dock.
|
||||||
|
- Plus de scrollbar horizontale en bas du dock, plus de clic-through dans la zone autour du bouton « Tout annuler ».
|
||||||
|
|
||||||
|
### Verdicts ghost : fix critique
|
||||||
|
|
||||||
|
Une intervention terminée par le tech mais dont la fiche était passée en statut « Redirigé » / « Finalisation » / « Exécution » était à tort marquée comme « annulée » et retirée du planning, parce que le parser de fiche cherchait les dates d'action aux indices `[8]`/`[9]` du tableau `rows` alors que le layout EV récent les place en `[6]`/`[7]` (et la description en `[9]` au lieu de `[11]`).
|
||||||
|
|
||||||
|
- **Détection robuste** : scan des valeurs pour le pattern `DD/MM/YYYY HH:MM:SS`, garde des 2 dernières occurrences comme (création, fin). Survit aux variations de layout EV.
|
||||||
|
- **Décorrélation logs / KEEP-forcé** : nouvelle case dédiée « Garder les disparitions » dans Paramètres → Diagnostics, indépendante des « Logs verbeux ». Mode prod par défaut → verdict `REMOVE` appliqué (statut Annulé/Supprimé, ou statut clos sans commentaire du tech, ou action sans commentaire `login:`).
|
||||||
|
|
||||||
|
### Drag&drop bloqué pour les interventions non-déplaçables
|
||||||
|
|
||||||
|
`_canRescheduleIv` refuse maintenant explicitement le drag pour :
|
||||||
|
|
||||||
|
- iv en verdict `terminated-pending` (gris « fait »).
|
||||||
|
- iv en verdict `terminated-clos` (vert ✓✓).
|
||||||
|
- iv dont le statut EV est dans `CLOSED_STATUS` ou `RESOLVED_STATUS`.
|
||||||
|
- iv en cours d'analyse de disparition (`_disappearChecking`).
|
||||||
|
|
||||||
|
### Popups dépinglés
|
||||||
|
|
||||||
|
- Auto-fermeture du popup dépinglé quand la souris quitte sa zone et celle des cards liées de la même iv (300 ms de grâce). N'interfère pas avec un drag de planning en cours.
|
||||||
|
|
||||||
|
### Tooltip / contact
|
||||||
|
|
||||||
|
- Le contact n'inclut plus les labels « fiche » (`Étage`, `Bureau`, `Service`, `Matériel`, `Problème`, `TFS`, `Date`, `Heure`, `Lieu`, `Bénéficiaire`, `Nom utilisateur`) qui se collent parfois à la valeur du contact à cause de séparateurs perdus dans la source EV. Le tooltip insère un saut de ligne avant chaque label collé pour la lisibilité.
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.5.44 : Refonte topbar, personnalisation Apparence, onboarding équipe, refresh séquentiel
|
||||||
|
|
||||||
|
> Refonte visuelle de la topbar (vue classique + horizontale), nouveau panneau
|
||||||
|
> de personnalisation (couleur de la barre du haut + police de l'application
|
||||||
|
> sur toute la page), nouvelle expérience d'onboarding quand aucun technicien
|
||||||
|
> n'est sélectionné, refonte du système de verdicts ghost (✓✓ clos / ✓ Fait /
|
||||||
|
> ✓ Suspendu), refresh strictement séquentiel avec arrêt instantané, et
|
||||||
|
> plusieurs corrections.
|
||||||
|
|
||||||
|
### Refresh / cache / verdicts ghost
|
||||||
|
|
||||||
|
- Rafraîchissement **séquentiel** (1 fiche à la fois) au lieu de 5 workers
|
||||||
|
parallèles → arrêt instantané via le bouton « ✕ Arrêter » (AbortController),
|
||||||
|
plus de races DOM, ordre d'affichage cohérent (pompier d'abord, puis alpha,
|
||||||
|
puis matin → après-midi).
|
||||||
|
- Re-fetch du checksum frais via `basicAutoComplete` + `redirectHeader`
|
||||||
|
(plus de fiche périmée entre sessions).
|
||||||
|
- Cache merge robuste (fallback `cachedByRef` quand `actionId` change) et
|
||||||
|
cache écrit toutes les 5 fiches pendant le refresh (incrémental).
|
||||||
|
- **Système de verdicts ghost unifié** : ✓✓ vert (clos / résolu officiel),
|
||||||
|
✓ gris « Fait » (terminated-pending), ✓ jaune « Suspendu »
|
||||||
|
(terminated-suspended), retrait silencieux pour cancelled / cancelled-
|
||||||
|
reservation / cancelled-absence.
|
||||||
|
- Statuts EV (clos / résolu / annulé / suspendu) éditables depuis Paramètres
|
||||||
|
→ EasyVista avec matching insensible à la casse, accents et conjugaisons.
|
||||||
|
- Mise à jour live du tooltip et du popup épinglé après un verdict (plus
|
||||||
|
besoin de fermer/réouvrir).
|
||||||
|
- Clic immédiat sur la carte dès que le verdict tombe (avant la fin du
|
||||||
|
refresh complet).
|
||||||
|
- Boutons « Actualiser » (rapide, ne re-télécharge pas les fiches déjà
|
||||||
|
connues) vs « Tout recharger » (force tout sauf les ✓✓ déjà clos).
|
||||||
|
- **Mode diagnostic optionnel** (Paramètres → Diagnostics) : aucune
|
||||||
|
intervention disparue n'est retirée silencieusement, tout est tracé sous
|
||||||
|
le préfixe `[disparition]` dans la console F12 pour debug. En PROD
|
||||||
|
(par défaut), les iv `cancelled` sont bien retirées comme avant.
|
||||||
|
|
||||||
|
### Topbar : vue classique
|
||||||
|
|
||||||
|
- Sélecteur de date du planning **ancré au centre absolu** : il ne se décale
|
||||||
|
plus quand le bouton « ✕ Arrêter » apparaît à droite pendant un
|
||||||
|
rafraîchissement.
|
||||||
|
- Bouton **« Aujourd'hui »** affiché en toutes lettres (au lieu de « Auj. »).
|
||||||
|
- Horloge contextuelle (date du jour + heure) réduite et discrète, à côté
|
||||||
|
du bouton Aujourd'hui dans un cadre encadré.
|
||||||
|
- Date du planning agrandie et neutre (couleur stable, plus de bascule
|
||||||
|
selon la date sélectionnée).
|
||||||
|
|
||||||
|
### Personnalisation : Paramètres → Apparence
|
||||||
|
|
||||||
|
- **Couleur de la barre du haut** : 12 presets cliquables (Défaut, Blanc,
|
||||||
|
Gris clair, Anthracite, Bleu DGNSI, Marine, Vert sapin, Brique, Violet,
|
||||||
|
Rouge, Bleu pastel, Vert pastel) + picker custom + champ hex `#rrggbb`
|
||||||
|
+ bouton « Réinitialiser ».
|
||||||
|
- La couleur s'applique uniquement à la topbar (et à la sidebar quand on
|
||||||
|
est en vue horizontale).
|
||||||
|
- Le texte de la topbar (titre, horloge, date, capture-info, badges,
|
||||||
|
boutons) s'adapte automatiquement (clair/foncé) selon la **luminance**
|
||||||
|
de la couleur choisie pour rester toujours lisible.
|
||||||
|
- **Police de l'application** : 28 choix organisés en familles
|
||||||
|
(sans-serif : Arial, Helvetica, Verdana, Tahoma, Trebuchet, Calibri,
|
||||||
|
Segoe UI, Gill Sans, Futura, Optima ; serif : Times New Roman, Georgia,
|
||||||
|
Cambria, Garamond, Palatino, Bookman ; monospace : Courier New, Consolas,
|
||||||
|
Lucida Console, JetBrains Mono ; display : Comic Sans MS, Impact,
|
||||||
|
Brush Script, Copperplate ; condensée : Arial Narrow). La police choisie
|
||||||
|
s'applique à **toute la page** (topbar, cards, popups, tooltips, panel
|
||||||
|
admin) et chaque option du select s'affiche dans sa propre police pour
|
||||||
|
prévisualiser le rendu, avec un aperçu live à droite.
|
||||||
|
- Export / import du cache et de `admin_config` depuis Paramètres →
|
||||||
|
Diagnostics.
|
||||||
|
|
||||||
|
### Vue horizontale
|
||||||
|
|
||||||
|
- Bloc « Aujourd'hui + horloge » empilé verticalement dans la sidebar, dans
|
||||||
|
le même cadre encadré que la vue classique.
|
||||||
|
- Date sélectionnée mise en avant (taille augmentée, en gras), date du
|
||||||
|
jour et heure réduites à la même petite taille pour rester discrètes.
|
||||||
|
- **Barre verticale verte** ajoutée à droite des mini-cards quand le
|
||||||
|
ticket est officiellement clôturé / résolu (✓✓), avec léger décalage du
|
||||||
|
✓✓ pour ne pas chevaucher la barre.
|
||||||
|
- Quand l'utilisateur a choisi une couleur de topbar, la sidebar prend
|
||||||
|
aussi la couleur : titre, horloge, capture-info, stats, today-block,
|
||||||
|
date sélectionnée, boutons, theme-toggle et séparateurs adoptent une
|
||||||
|
teinte translucide cohérente (via `color-mix`) qui contraste correctement
|
||||||
|
sur n'importe quel fond.
|
||||||
|
|
||||||
|
### Statistiques globales
|
||||||
|
|
||||||
|
- Nouveau compteur **« X faits / Y clos »** entre `(matin · après-midi)`
|
||||||
|
et `tech. dispo`. Inclut tous les tickets terminés (clos/résolus officiels
|
||||||
|
+ verdicts ghost « Fait » / « Suspendu »).
|
||||||
|
- En vue classique, séparateur `//` après `clos` (au lieu de `·`).
|
||||||
|
- En vue horizontale (sidebar), une **barre horizontale 1px** sépare le
|
||||||
|
bloc interventions/faits/clos du bloc tech. dispo + pompiers / absents.
|
||||||
|
|
||||||
|
### Onboarding équipe (1ʳᵉ install ou config vide)
|
||||||
|
|
||||||
|
- L'erreur générique « Aucun technicien sélectionné » est remplacée par une
|
||||||
|
**carte d'onboarding centrée** comprenant :
|
||||||
|
- icône (👥) cerclée en couleur accent du thème ;
|
||||||
|
- titre « Aucune équipe configurée » ;
|
||||||
|
- description claire ;
|
||||||
|
- bouton primary **« Ouvrir paramètres »** qui ouvre directement le panel
|
||||||
|
admin sur la section Équipe.
|
||||||
|
- Carte centrée verticalement et horizontalement dans la zone disponible,
|
||||||
|
identique en vue classique et horizontale.
|
||||||
|
|
||||||
|
### Bugfix
|
||||||
|
|
||||||
|
- **Issue #1 (Pompier + Absence)** : si un tech est à la fois pompier ET
|
||||||
|
absent, les deux badges s'affichent désormais avec un séparateur `/` au
|
||||||
|
lieu de masquer l'absence derrière le badge pompier.
|
||||||
|
- **Absences récurrentes** : quand on changeait de groupe puis revenait au
|
||||||
|
groupe initial, les jours d'absence cochés pour les techniciens
|
||||||
|
disparaissaient visuellement (la donnée elle-même restait en storage).
|
||||||
|
Correction : restauration depuis `cfg.recurringAbsences` à chaque
|
||||||
|
re-render.
|
||||||
|
- **Barre de progression / bannière session expirée** : suivent désormais
|
||||||
|
la hauteur dynamique de la topbar (variable CSS `--topbar-height` mesurée
|
||||||
|
par un `ResizeObserver`). Plus de chevauchement quand on scrolle.
|
||||||
|
- **STATUS_FR regex** : limite augmentée de 30 à 200 caractères (battait
|
||||||
|
sur « Suspendu : Attente info bénéficiaire/demandeur »).
|
||||||
|
- **Description action** : décodage `" → "`, `<br> → \n`, HTML
|
||||||
|
strippé. Préfixe « login: » retiré du commentaire technicien dans le
|
||||||
|
tooltip / popup.
|
||||||
|
- **Tooltip référence** : flèche « ↗ » retirée du lien cliquable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v2026.5.43 : Fix Firefox, positionnement menu dock + stabilité popup pin/unpin
|
||||||
|
|
||||||
|
### Menu hover sur pastille du dock (popup réduit)
|
||||||
|
- Bug Firefox uniquement : quand un popup épinglé était réduit dans la
|
||||||
|
taskbar du bas, le menu qui apparaît au survol de la pastille
|
||||||
|
(Agrandir / Fermer) se positionnait trop haut, pas juste au-dessus de
|
||||||
|
la pastille.
|
||||||
|
- Cause : `getBoundingClientRect()` était appelé immédiatement après
|
||||||
|
`appendChild`, avant que Firefox n'ait calculé la mise en page.
|
||||||
|
Combiné avec un `transform: translateY(4px)` dans l'animation
|
||||||
|
`pill-hover-menu-appear`, Firefox lisait des dimensions décalées.
|
||||||
|
- Fix : positionnement hors écran initial, force-layout via
|
||||||
|
`void offsetHeight`, mesure des dimensions, puis pose finale. CSS de
|
||||||
|
l'animation simplifiée en opacité-only (plus de transform).
|
||||||
|
|
||||||
|
### Stabilité popup au pin/unpin
|
||||||
|
- Bug : la popup épinglée bougeait visuellement et changeait légèrement
|
||||||
|
de taille quand on la dé-épinglait avec le bouton 📌 (puis l'inverse).
|
||||||
|
- Cause : `.pinned-popup` avait `padding-top: 28px` (place pour la
|
||||||
|
dragbar) et `border: 2px`, alors que `.soft-unpinned` avait
|
||||||
|
`padding-top: 12px` et `border: 1px`. Le contenu se décalait de 16px
|
||||||
|
vers le haut et la popup devenait 1px plus fine de chaque côté.
|
||||||
|
- Fix : `.soft-unpinned` conserve désormais `padding-top: 28px` et
|
||||||
|
`border: 2px` comme `.pinned-popup`. Bordure passe juste en
|
||||||
|
`--border-strong` (gris discret) plutôt que `--accent` (bleu) pour
|
||||||
|
signaler visuellement le mode "détaché". Position et taille stables.
|
||||||
|
|
||||||
|
## v2026.5.42 : Nettoyage de commentaires + exemples génériques
|
||||||
|
|
||||||
|
- Passage en revue des commentaires de `src/viewer.js` : les exemples qui
|
||||||
|
illustraient le parsing des contacts/lieux/références/codes-barres ont été
|
||||||
|
uniformisés en placeholders abstraits (`Nom1 Prénom1 +41XXXXXXXXX`,
|
||||||
|
`SYYMMDD_NNNNN`, `XXXX_NNNNNNNN`, etc.) plutôt que des chaînes spécifiques.
|
||||||
|
Comportement runtime strictement inchangé : uniquement de la documentation
|
||||||
|
et des commentaires.
|
||||||
|
- Mise à jour cohérente du README, du CHANGELOG et des pages wiki Versions /
|
||||||
|
Utilisation pour utiliser les mêmes notations génériques dans les
|
||||||
|
exemples de référence.
|
||||||
|
|
||||||
|
## v2026.5.41 : Suppression des hardcodes (groupe / domaines / équipe) → tout depuis l'admin
|
||||||
|
|
||||||
|
### Plus aucun hardcode au runtime
|
||||||
|
- Le groupe EasyVista, les domaines (interne/externe) et la liste des
|
||||||
|
techniciens ne sont **plus codés en dur** dans `background.js` /
|
||||||
|
`viewer.js`. Tout est lu depuis `admin_config` (chrome.storage.local),
|
||||||
|
alimenté par les onglets **Équipe** et **EasyVista** du panel admin.
|
||||||
|
- `chrome.storage.local` survit aux mises à jour d'extension → la
|
||||||
|
configuration de l'utilisateur (groupe, équipe, absences récurrentes,
|
||||||
|
domaines) est conservée d'une version à l'autre.
|
||||||
|
|
||||||
|
### Domaines EasyVista (interne / externe)
|
||||||
|
- Défaut hardcodé conservé comme **filet de sécurité** au 1er install
|
||||||
|
(`https://itsma.etat-de-vaud.ch` + `https://itsma.vd.ch`).
|
||||||
|
- L'utilisateur peut les remplacer dans Paramètres → EasyVista. Le
|
||||||
|
service worker (`findEasyVistaSession`, `evFetch`, etc.) lit la valeur
|
||||||
|
effective via `getEvOrigins()`.
|
||||||
|
|
||||||
|
### Group ID EasyVista
|
||||||
|
- Défaut hardcodé conservé comme filet de sécurité (`191` = SI-CSS).
|
||||||
|
- Lu via `getGroupId()` dans `fetchPlanningXml`, `detectTeamFromEV` et
|
||||||
|
partout où c'était hardcodé.
|
||||||
|
- Le sélecteur de Paramètres → Équipe alimente `cfg.groupId`.
|
||||||
|
|
||||||
|
### Liste des techniciens
|
||||||
|
- **Aucun défaut hardcodé**. Sur un install vierge, `cfg.team` est
|
||||||
|
vide → le service worker lève `no_team_configured` plutôt que de
|
||||||
|
fetcher avec une liste fictive.
|
||||||
|
- Le viewer affiche : *"Aucun technicien sélectionné. Ouvrez ⚙
|
||||||
|
Paramètres → Équipe pour choisir le groupe EasyVista et cocher les
|
||||||
|
techniciens à afficher."*
|
||||||
|
- Lu via `getSupportIds()` (CSV des clés de `cfg.team`).
|
||||||
|
- Côté `viewer.js` : `TEAM` et `RECURRING_ABSENCES` sont des `let`
|
||||||
|
vides au démarrage, repeuplés par `_initTeamFromConfig()` appelé tôt
|
||||||
|
dans `init()`.
|
||||||
|
|
||||||
|
### Coulisses
|
||||||
|
- Nouveau dans `background.js` : helpers `getAdminConfig()`,
|
||||||
|
`getEvOrigins()`, `getGroupId()`, `getSupportIds()`,
|
||||||
|
`getDayBounds()` qui centralisent la lecture de la config persistée.
|
||||||
|
- `fetchPlanningXml()` lève `Error("no_team_configured")` quand la
|
||||||
|
liste de techs est vide ; le handler `fetchPlanning` propage l'erreur
|
||||||
|
au viewer via `err.kind`.
|
||||||
|
- Toutes les anciennes constantes hardcodées (`EV_ORIGINS`,
|
||||||
|
`DEFAULT_SUPPORT_IDS` interne à `detectTeamFromEV`,
|
||||||
|
`isXXXAbsentFriday`) ont été remplacées ou retirées.
|
||||||
|
|
||||||
|
### Conflits absence/réservation × intervention
|
||||||
|
- Nouveau code visuel : si une intervention est planifiée pendant
|
||||||
|
qu'un tech a une **absence** (toute la journée ou demi-journée) ou
|
||||||
|
une **réservation** sur le même créneau, sa carte (row classique +
|
||||||
|
mini-card en vue horizontale) est peinte en **rouge plein** avec
|
||||||
|
texte blanc. Logique : full-day → toutes les interv en rouge ;
|
||||||
|
partiel → seules celles en chevauchement.
|
||||||
|
|
||||||
|
### Synchronisation des heures EV ↔ admin
|
||||||
|
- Les paramètres `day_start_hour` / `day_end_hour` envoyés à
|
||||||
|
`planning_xhr.php` et `begin_hour` / `end_hour` envoyés à
|
||||||
|
`plan_set_holidays_popup.php` (création absence) et
|
||||||
|
`plan_set_tech_planif_popup.php` (douchette) lisent désormais
|
||||||
|
`cfg.dayStart` / `cfg.dayEnd` (Paramètres → Apparence → Heures de la
|
||||||
|
journée). Avant : `8` / `18` / `19` figés en dur, ce qui rendait le
|
||||||
|
réglage des heures côté UI partiellement effectif (la timeline se
|
||||||
|
redessinait, mais les requêtes EV continuaient sur la plage hardcodée).
|
||||||
|
|
||||||
|
### Édition des domaines EV → permissions runtime
|
||||||
|
- `manifest.json` : ajout de `"optional_host_permissions":
|
||||||
|
["https://*/*"]` pour permettre l'édition des domaines EasyVista
|
||||||
|
vers des origines non prévues à l'install.
|
||||||
|
- Quand l'utilisateur saisit un domaine custom dans Paramètres →
|
||||||
|
EasyVista et clique sur Enregistrer, l'extension appelle
|
||||||
|
`chrome.permissions.request()` pour demander la permission au
|
||||||
|
navigateur. Si refus → toast d'avertissement, les fetches échoueront
|
||||||
|
jusqu'à acceptation.
|
||||||
|
- Les deux domaines hardcodés (`itsma.etat-de-vaud.ch` +
|
||||||
|
`itsma.vd.ch`) restent dans `host_permissions` (toujours accordés à
|
||||||
|
l'install), pas besoin de redemander la permission pour eux.
|
||||||
|
|
||||||
|
## v2026.5.40 : Vue horizontale enrichie (ref + ville + barre couleur)
|
||||||
|
- En vue horizontale, chaque segment timeline d'intervention contient
|
||||||
|
désormais :
|
||||||
|
- Une **barre verticale couleur catégorie** à gauche (mêmes teintes que
|
||||||
|
les `.intervention-dot` de la vue classique : livraison/recup/
|
||||||
|
remplacement/incident/rollout/réservation/autre)
|
||||||
|
- La **référence** (ex: `SYYMMDD_NNNNN`) en gras
|
||||||
|
- La **ville** en gris muted
|
||||||
|
- Hauteur de la timeline en horizontale passée de 22px à 32px pour laisser
|
||||||
|
la place au texte
|
||||||
|
- Fond des segments d'intervention : `--bg-elevated` neutre + bordure 1px
|
||||||
|
pour que le texte reste lisible (la couleur catégorie n'est plus en fond
|
||||||
|
plein, juste en barre gauche)
|
||||||
|
- Vue classique inchangée
|
||||||
|
- Réorganisation interne du repo : `src/` pour les sources, `dist/`
|
||||||
|
généré, `Autres/` pour build.sh + meta files (LICENSE, README,
|
||||||
|
CHANGELOG)
|
||||||
|
|
||||||
|
## v2026.5.39 : Séparation Matin / Après-midi + Apparence (thème, zoom, cache)
|
||||||
|
|
||||||
|
### Séparation matin / après-midi
|
||||||
|
- Séparateur visuel "MATIN" / "APRÈS-MIDI" entre les interventions
|
||||||
|
dans la vue classique : pill grise neutre, ligne 3px épaisse.
|
||||||
|
- Affiché aussi entre les absences partielles (demi-journée).
|
||||||
|
- Si une période est vide, son séparateur n'est pas affiché.
|
||||||
|
- Caché en vue horizontale (les rows sont masquées de toute façon).
|
||||||
|
|
||||||
|
### Timeline : coupure midi très visible
|
||||||
|
- Bande verticale composée d'un trait massif central (couleur --text)
|
||||||
|
+ stripes diagonales en arrière-plan (effet "césure"). 6 px de large
|
||||||
|
(7 px en vue horizontale). Visible immédiatement, pas de label superflu.
|
||||||
|
|
||||||
|
### Vue horizontale (sidebar)
|
||||||
|
- Boutons (Absence, Douchette, Actualiser, Tout recharger, Vider cache,
|
||||||
|
Thème) maintenant **vraiment** poussés en bas via `min-height: 100vh`
|
||||||
|
sur la sidebar.
|
||||||
|
- Bouton "Aujourd'hui" : style cohérent avec les flèches ◀ ▶ (même
|
||||||
|
padding, font-size, hauteur), texte centré, libellé complet
|
||||||
|
"Aujourd'hui" (au lieu de "Auj.").
|
||||||
|
- Espace visuel entre `Actualisé à HH:MM` et le bouton Absence (fine
|
||||||
|
bordure top + padding).
|
||||||
|
|
||||||
|
### Vue classique (topbar)
|
||||||
|
- Ordre verrouillé via CSS `order` : badge user → titre → date-nav →
|
||||||
|
capture-info → refresh-check. Évite les déplacements au retour de
|
||||||
|
vue horizontale.
|
||||||
|
|
||||||
|
### Section Apparence (admin) : refondue + en première position
|
||||||
|
- **Thème** : sélecteur Auto / Clair / Sombre (s'enregistre direct).
|
||||||
|
- **Durée du cache (jours)** : configurable, défaut 7 jours, range 1-365.
|
||||||
|
Lue par viewer.js (purge auto) ET background.js (au boot).
|
||||||
|
- **Taille du texte** : 5 niveaux (-20%, -10%, 100%, +10%, +20%) via CSS
|
||||||
|
`zoom` sur body. Persisté dans admin_config.textZoom et appliqué dès
|
||||||
|
le boot.
|
||||||
|
- Section "Apparence" est maintenant **la première** dans le panel admin.
|
||||||
|
|
||||||
|
## v2026.5.38 : Attribution auteur + nettoyage code
|
||||||
|
### Attribution auteur
|
||||||
|
- Ajout en-têtes copyright dans tous les fichiers source
|
||||||
|
(viewer.js, viewer.html, viewer.css, background.js)
|
||||||
|
- Ajout `@author Quentin` sur les fonctions principales
|
||||||
|
(loadForDate, buildCard, buildTooltipHTML, pinTooltip, _softUnpinPopup,
|
||||||
|
positionTooltipAnchored, _applyViewMode, _moveElementsToSidebar,
|
||||||
|
_restoreElementsToTopbar, fetchAndShowCurrentUser, _maybeRetryFetchUser,
|
||||||
|
initAppClock, initAppFooter, bindTimelinePopover,
|
||||||
|
openPersistentTimelinePopup, showTooltip, _findFreePopupPosition,
|
||||||
|
_clampPopupInSafeArea, findEasyVistaSession, fetchPlanningXml,
|
||||||
|
fetchCurrentUser, detectNetworkContext)
|
||||||
|
- Ajout signature "Développé par Quentin" en bas du popup
|
||||||
|
user-badge (style cohérent avec footer version : 11px, italique,
|
||||||
|
gris atténué, séparateur fin)
|
||||||
|
- Mise à jour `description` du manifest pour mentionner DGNSI
|
||||||
|
|
||||||
|
### Nettoyage et optimisation
|
||||||
|
- Retrait fonction vide `initAdminMenu()` (inutile depuis v2026.5.25,
|
||||||
|
l'admin passe par le bouton ⚙ Paramètres du popup user-badge)
|
||||||
|
- Retrait classe CSS orpheline `.date-picker-day` (déjà remplacée par
|
||||||
|
`.date-custom` en v2026.5.17)
|
||||||
|
- Retrait anciens styles CSS `.intervention` (layout v1, jamais générés
|
||||||
|
depuis le passage à `.intervention-v2`)
|
||||||
|
- Retrait commentaire orphelin `.intervention-v2.is-ghost` (classe
|
||||||
|
retirée en v4.3.3)
|
||||||
|
- Retrait 14× `console.log("[viewMode]")` debug verbose (gardé
|
||||||
|
uniquement les `console.warn` utiles pour erreurs)
|
||||||
|
- Retrait 5× `console.log("[bg]")` debug verbose dans
|
||||||
|
fetchPlanningXml / fetchFicheHtml / fetchSessionTimeRemaining /
|
||||||
|
extendSessionKeepAlive (gardé warnings + logs critiques)
|
||||||
|
- Remplacement `extendBtn.onclick` par `addEventListener("click", ...)`
|
||||||
|
pour plus de cohérence
|
||||||
|
|
||||||
|
### Builds
|
||||||
|
- `dist/chromium/` et `dist/firefox/` prêts à charger en mode dev
|
||||||
|
- `planification-v2026.5.38-chromium.zip` (~144 Ko)
|
||||||
|
- `planification-v2026.5.38-firefox.xpi` (~144 Ko, à signer sur AMO)
|
||||||
|
|
||||||
|
## v2026.5.37 : Refonte vue horizontale (sidebar complète)
|
||||||
|
|
||||||
- Topbar en haut supprimée en vue horizontale
|
- Topbar en haut supprimée en vue horizontale
|
||||||
- User-badge + titre déplacés tout en haut de la sidebar
|
- User-badge + titre déplacés tout en haut de la sidebar
|
||||||
@@ -26,7 +609,7 @@
|
|||||||
- Barre de rafraîchissement en overlay top-left
|
- Barre de rafraîchissement en overlay top-left
|
||||||
- Banderole pompier masquée en vue horizontale (badge + barre rouge à gauche conservés)
|
- Banderole pompier masquée en vue horizontale (badge + barre rouge à gauche conservés)
|
||||||
|
|
||||||
## v2026.5.36 — Sidebar verticale en vue horizontale
|
## v2026.5.36 : Sidebar verticale en vue horizontale
|
||||||
- Création wrapper flex-row #horizontal-wrapper contenant [sidebar] + [main]
|
- Création wrapper flex-row #horizontal-wrapper contenant [sidebar] + [main]
|
||||||
- Sidebar 200px (170px sur <1400px), sticky, bg-muted
|
- Sidebar 200px (170px sur <1400px), sticky, bg-muted
|
||||||
- Déplacement physique des éléments via JS (ELEMENTS_TO_RELOCATE)
|
- Déplacement physique des éléments via JS (ELEMENTS_TO_RELOCATE)
|
||||||
@@ -34,7 +617,7 @@
|
|||||||
- Restauration propre en vue classique
|
- Restauration propre en vue classique
|
||||||
- Zone nom tech : 140px → 120px
|
- Zone nom tech : 140px → 120px
|
||||||
|
|
||||||
## v2026.5.35 — Fix popup épinglé position vue horizontale + stats gauche
|
## v2026.5.35 : Fix popup épinglé position vue horizontale + stats gauche
|
||||||
- Fix popup épinglé qui partait en haut à gauche en vue horizontale
|
- Fix popup épinglé qui partait en haut à gauche en vue horizontale
|
||||||
- Cause : rows .intervention-v2 cachées (display: none) → getBoundingClientRect (0,0,0,0)
|
- Cause : rows .intervention-v2 cachées (display: none) → getBoundingClientRect (0,0,0,0)
|
||||||
- Solution : priorité 1 tooltip visible, priorité 2 segment timeline, fallback srcEl
|
- Solution : priorité 1 tooltip visible, priorité 2 segment timeline, fallback srcEl
|
||||||
@@ -42,7 +625,7 @@
|
|||||||
- Position sticky, fond bg-muted, séparateurs · masqués
|
- Position sticky, fond bg-muted, séparateurs · masqués
|
||||||
- Zone nom tech 200px → 140px (vue horizontale)
|
- Zone nom tech 200px → 140px (vue horizontale)
|
||||||
|
|
||||||
## v2026.5.34 — Bouton 📌 restauré + badge user cliquable
|
## v2026.5.34 : Bouton 📌 restauré + badge user cliquable
|
||||||
- HTML : badge user toujours visible avec "?" par défaut (retiré class hidden)
|
- HTML : badge user toujours visible avec "?" par défaut (retiré class hidden)
|
||||||
- _softUnpinPopup refait en 8 étapes loggées
|
- _softUnpinPopup refait en 8 étapes loggées
|
||||||
- Popup reste visible après désépinglage (plus de suppression auto au mouseleave)
|
- Popup reste visible après désépinglage (plus de suppression auto au mouseleave)
|
||||||
@@ -54,13 +637,13 @@
|
|||||||
- Fonction positionTooltipAnchored unifiée (4 candidats droite/gauche/dessous/dessus)
|
- Fonction positionTooltipAnchored unifiée (4 candidats droite/gauche/dessous/dessus)
|
||||||
- popup._linkedIv stocké pour ré-épinglage
|
- popup._linkedIv stocké pour ré-épinglage
|
||||||
|
|
||||||
## v2026.5.33 — Interactions vue horizontale différenciées
|
## v2026.5.33 : Interactions vue horizontale différenciées
|
||||||
- Hover segment timeline en vue horizontale → grande popup directement (openPersistentTimelinePopup)
|
- Hover segment timeline en vue horizontale → grande popup directement (openPersistentTimelinePopup)
|
||||||
- Clic segment timeline en vue horizontale → ouvre fiche EasyVista
|
- Clic segment timeline en vue horizontale → ouvre fiche EasyVista
|
||||||
- Popup absence en vue horizontale : hover uniquement sur badge .card-tech-badge (pas sur carte entière)
|
- Popup absence en vue horizontale : hover uniquement sur badge .card-tech-badge (pas sur carte entière)
|
||||||
- Vue classique : comportement inchangé
|
- Vue classique : comportement inchangé
|
||||||
|
|
||||||
## v2026.5.32 — Vue horizontale togglable
|
## v2026.5.32 : Vue horizontale togglable
|
||||||
- Bouton ⊞ "Vue" dans popup user-badge (à côté ⚙ Paramètres)
|
- Bouton ⊞ "Vue" dans popup user-badge (à côté ⚙ Paramètres)
|
||||||
- Toggle Vue classique ↔ Vue horizontale persisté localStorage "view_mode"
|
- Toggle Vue classique ↔ Vue horizontale persisté localStorage "view_mode"
|
||||||
- HTML class "view-classic" ou "view-horizontal" sur <html>
|
- HTML class "view-classic" ou "view-horizontal" sur <html>
|
||||||
@@ -70,29 +653,30 @@
|
|||||||
- Timeline horizontale pleine largeur
|
- Timeline horizontale pleine largeur
|
||||||
- Stats rapides .tech-row-stats ajoutés au header (nb interv, Xm · Ya)
|
- Stats rapides .tech-row-stats ajoutés au header (nb interv, Xm · Ya)
|
||||||
|
|
||||||
## v2026.5.31 — Sarcelle pour absence récurrente (REJETÉ par utilisateur)
|
## v2026.5.31 : Sarcelle pour absence récurrente (essai abandonné)
|
||||||
- Couleur Pillonel vendredi : sarcelle foncée #0f766e / soft #ccfbf1
|
- Couleur absence récurrente (jour fixe) : sarcelle foncée #0f766e / soft #ccfbf1
|
||||||
- Variables --c-recurring, --c-recurring-soft
|
- Variables --c-recurring, --c-recurring-soft
|
||||||
- Layout 4 colonnes forcées + scroll interne cartes (REJETÉ : "scroll en continu")
|
- Layout 4 colonnes forcées + scroll interne cartes
|
||||||
|
- Essai abandonné dans les versions suivantes.
|
||||||
|
|
||||||
## v2026.5.30 — Absence récurrente cyan + mode compact 24"
|
## v2026.5.30 : Absence récurrente cyan + mode compact 24"
|
||||||
- Absence récurrente Pillonel vendredi en cyan
|
- Absences récurrentes (configurées par tech) en cyan
|
||||||
- Mode compact @media (max-width: 1920px) avec grid-template-columns: repeat(4, 1fr)
|
- Mode compact @media (max-width: 1920px) avec grid-template-columns: repeat(4, 1fr)
|
||||||
|
|
||||||
## v2026.5.29 — Contraste++ + footer
|
## v2026.5.29 : Contraste++ + footer
|
||||||
- Contrastes encore plus forts (text-muted #d0d5de dark, #2e3642 light)
|
- Contrastes encore plus forts (text-muted #d0d5de dark, #2e3642 light)
|
||||||
- Footer QRO/version : 13px badge avec fond bg-muted + bordure
|
- Footer FRS/version : 13px badge avec fond bg-muted + bordure
|
||||||
- Fix highlight row : selector .intervention-v2[data-iv-idx]
|
- Fix highlight row : selector .intervention-v2[data-iv-idx]
|
||||||
- Scroll-into-view automatique au hover segment timeline
|
- Scroll-into-view automatique au hover segment timeline
|
||||||
|
|
||||||
## v2026.5.28 — Ajustements visuels absences
|
## v2026.5.28 : Ajustements visuels absences
|
||||||
- Retrait pastille ronde (.tech-name-dot supprimée) — barre gauche + badge suffisent
|
- Retrait pastille ronde (.tech-name-dot supprimée) : barre gauche + badge suffisent
|
||||||
- "Maladie" → "Maladie/Accident"
|
- "Maladie" → "Maladie/Accident"
|
||||||
- Contraste textes secondaires +30%
|
- Contraste textes secondaires +30%
|
||||||
- Popups épinglés width fixe 520px (ne rétrécit plus au resize fenêtre)
|
- Popups épinglés width fixe 520px (ne rétrécit plus au resize fenêtre)
|
||||||
- _clampPopupInSafeArea ne rétrécit plus si popup > zone dispo
|
- _clampPopupInSafeArea ne rétrécit plus si popup > zone dispo
|
||||||
|
|
||||||
## v2026.5.27 — Classification absences (Maladie/Congé/Pompier)
|
## v2026.5.27 : Classification absences (Maladie/Congé/Pompier)
|
||||||
- Topbar une ligne : "Jeudi 23.04.26 • 21:55" (gros point •, même taille 22px)
|
- Topbar une ligne : "Jeudi 23.04.26 • 21:55" (gros point •, même taille 22px)
|
||||||
- Fermeture auto popups non-épinglés au survol autre popup/carte
|
- Fermeture auto popups non-épinglés au survol autre popup/carte
|
||||||
- Texte +20% topbar/stats/boutons
|
- Texte +20% topbar/stats/boutons
|
||||||
@@ -100,20 +684,20 @@
|
|||||||
- Classification absences (ABSENCE_LABELS) + absenceCategory : "maladie"|"conge"|"pompier"|null
|
- Classification absences (ABSENCE_LABELS) + absenceCategory : "maladie"|"conge"|"pompier"|null
|
||||||
- Couleurs : Maladie #4338ca indigo foncé, Congé #06b6d4 cyan, Pompier #b03030 rouge
|
- Couleurs : Maladie #4338ca indigo foncé, Congé #06b6d4 cyan, Pompier #b03030 rouge
|
||||||
- Badge + barre gauche + dégradé fond pour catégorie
|
- Badge + barre gauche + dégradé fond pour catégorie
|
||||||
- Libellé "Absent du DD.MM au DD.MM — Maladie/Accident"
|
- Libellé "Absent du DD.MM au DD.MM" suivi de la catégorie (Maladie/Accident)
|
||||||
- Suffixe `s` adaptatif (Congé/Congés)
|
- Suffixe `s` adaptatif (Congé/Congés)
|
||||||
|
|
||||||
## v2026.5.26 — Badge user inconnu cliquable + retry
|
## v2026.5.26 : Badge user inconnu cliquable + retry
|
||||||
- En cas d'échec fetch user, afficher rond gris "?" cliquable
|
- En cas d'échec fetch user, afficher rond gris "?" cliquable
|
||||||
- Bouton ⚙ Paramètres accessible même quand user inconnu
|
- Bouton ⚙ Paramètres accessible même quand user inconnu
|
||||||
- Retry automatique 60s (max 10 essais = 10 min)
|
- Retry automatique 60s (max 10 essais = 10 min)
|
||||||
- Reset compteur au succès
|
- Reset compteur au succès
|
||||||
|
|
||||||
## v2026.5.25 — Bouton Paramètres dans popup user-badge
|
## v2026.5.25 : Bouton Paramètres dans popup user-badge
|
||||||
- Remplace les 5 clics sur le titre pour ouvrir admin
|
- Remplace les 5 clics sur le titre pour ouvrir admin
|
||||||
- Bouton ⚙ Paramètres explicite dans le popup user-badge
|
- Bouton ⚙ Paramètres explicite dans le popup user-badge
|
||||||
|
|
||||||
## v2026.5.16-v2026.5.24 — Évolutions diverses (à compléter)
|
## v2026.5.16 à v2026.5.24 : Évolutions diverses
|
||||||
- v2026.5.17 : popup user-badge avec ligne session (MM:SS), couleur selon seuil
|
- v2026.5.17 : popup user-badge avec ligne session (MM:SS), couleur selon seuil
|
||||||
- v2026.5.18 : dock pastilles popups épinglés avec couleur catégorie
|
- v2026.5.18 : dock pastilles popups épinglés avec couleur catégorie
|
||||||
- v2026.5.19 : drag popup épinglé
|
- v2026.5.19 : drag popup épinglé
|
||||||
@@ -125,8 +709,9 @@
|
|||||||
|
|
||||||
## Versions antérieures (v5.x et v4.x)
|
## Versions antérieures (v5.x et v4.x)
|
||||||
|
|
||||||
> Ces versions sont à analyser par Claude Code à partir des fichiers source.
|
> Ces versions ne sont pas documentées en détail. Pour les analyser à partir
|
||||||
> Indices clés à chercher dans le viewer.js :
|
> des fichiers source historiques (consultables via les tags git), voici les
|
||||||
|
> indices clés à chercher dans `viewer.js` :
|
||||||
>
|
>
|
||||||
> - Présence de `pinTooltip` → version >= v4.x
|
> - Présence de `pinTooltip` → version >= v4.x
|
||||||
> - Présence de `_softUnpinPopup` → version >= v4.3.3
|
> - Présence de `_softUnpinPopup` → version >= v4.3.3
|
||||||
@@ -137,31 +722,31 @@
|
|||||||
> - Présence de `openPersistentTimelinePopup` → version >= v4.2.3
|
> - Présence de `openPersistentTimelinePopup` → version >= v4.2.3
|
||||||
> - Commentaires `// vX.Y.Z` au-dessus des fonctions = version d'introduction
|
> - Commentaires `// vX.Y.Z` au-dessus des fonctions = version d'introduction
|
||||||
|
|
||||||
### v5.0.0 — Refonte topbar (horloge, menu admin)
|
### v5.0.0 : Refonte topbar (horloge, menu admin)
|
||||||
- initAppClock : horloge HH:MM au milieu topbar
|
- initAppClock : horloge HH:MM au milieu topbar
|
||||||
- initAdminMenu : menu admin caché (5 clics sur titre)
|
- initAdminMenu : menu admin caché (5 clics sur titre)
|
||||||
- initSessionTimer : compteur de session EV (tick 1s)
|
- initSessionTimer : compteur de session EV (tick 1s)
|
||||||
|
|
||||||
### v4.x — Fonctions tooltip avancées
|
### v4.x : Fonctions tooltip avancées
|
||||||
- v4.1.12 : moveTooltip devenu no-op (popup statique)
|
- v4.1.12 : moveTooltip devenu no-op (popup statique)
|
||||||
- v4.1.15 : pendant épinglage, ne pas remplacer contenu sur hover autre iv
|
- v4.1.15 : pendant épinglage, ne pas remplacer contenu sur hover autre iv
|
||||||
- v4.2.3 : grande popup timeline persistante (clic), suit-souris (hover)
|
- v4.2.3 : grande popup timeline persistante (clic), suit-souris (hover)
|
||||||
- v4.2.3 : bindTimelinePopover, showTimelinePopover, moveTimelineTooltip
|
- v4.2.3 : bindTimelinePopover, showTimelinePopover, moveTimelineTooltip
|
||||||
- v4.2.4 : setTooltipViewportPosition (détection auto fixed/abs)
|
- v4.2.4 : setTooltipViewportPosition (détection auto fixed/abs)
|
||||||
- v4.2.9 : pied de page discret QRO/version
|
- v4.2.9 : pied de page discret FRS/version
|
||||||
- v4.2.9 : initModalScrollLock (bloquer scroll arrière modal)
|
- v4.2.9 : initModalScrollLock (bloquer scroll arrière modal)
|
||||||
- v4.3.0 : tooltip live libéré après épinglage (réutilisable autres survols)
|
- v4.3.0 : tooltip live libéré après épinglage (réutilisable autres survols)
|
||||||
- v4.3.3 : _softUnpinPopup (désépinglage mou)
|
- v4.3.3 : _softUnpinPopup (désépinglage mou)
|
||||||
|
|
||||||
### v3.x et antérieures — Versions de base
|
### v3.x et antérieures : Versions de base
|
||||||
- À analyser par Claude Code
|
- Code historique consultable via les tags git correspondants.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Notes techniques persistantes (toutes versions)
|
## Notes techniques persistantes (toutes versions)
|
||||||
|
|
||||||
- 8 techs hardcodés : "76272,83725,66635,92235,90070,40944,72485,86874"
|
- 8 techs hardcodés à l'origine (depuis v2026.5.41 : retirés, alimentés par admin_config)
|
||||||
- Pillonel Olivier (ID 40944) absent tous les vendredis (hardcodé)
|
- Absences récurrentes (un tech absent un jour fixe par semaine) hardcodées à l'origine, depuis v2026.5.41 configurables via Paramètres → Équipe
|
||||||
- Group ID EasyVista : 191
|
- Group ID EasyVista : 191
|
||||||
- Domaines cibles : itsma.etat-de-vaud.ch (interne), itsma.vd.ch (externe)
|
- Domaines cibles : itsma.etat-de-vaud.ch (interne), itsma.vd.ch (externe)
|
||||||
- SSO : Canton ForgeRock OpenAM
|
- SSO : Canton ForgeRock OpenAM
|
||||||
@@ -174,6 +759,6 @@
|
|||||||
|
|
||||||
## Auteur
|
## Auteur
|
||||||
|
|
||||||
**Quentin Rouiller** (QRO)
|
**Quentin Rouiller**
|
||||||
Canton de Vaud — Service IT
|
Technicien DGNSI, Canton de Vaud
|
||||||
Email pour commits Git : `[email protected]`
|
Contact : voir [page wiki Contact](https://gitea.netaplaid.ch/FroSteel/Planification/wiki/Contact)
|
||||||
|
|||||||
@@ -1,182 +1,39 @@
|
|||||||
# Planification — Extension EasyVista Canton de Vaud
|
# Planification
|
||||||
|
|
||||||
Extension Chrome / Firefox pour visualiser de manière claire et rapide le planning des techniciens IT du Canton de Vaud dans EasyVista.
|
Extension Firefox et Chrome pour les coordinateurs de la DGNSI (Canton de Vaud). Elle affiche le planning EasyVista de l'équipe technicienne : une carte par technicien, une frise horaire, les absences et les conflits en couleur.
|
||||||
|
|
||||||
## Aperçu rapide
|
**Version publiée : [2026.5.50](https://gitea.netaplaid.ch/FroSteel/Planification/releases/tag/v2026.5.50)** · [Documentation](https://gitea.netaplaid.ch/FroSteel/Planification/wiki) · [Signaler un problème](https://gitea.netaplaid.ch/FroSteel/Planification/issues/new)
|
||||||
|
|
||||||
- **Auteur** : Quentin Rouiller (QRO)
|
|
||||||
- **Cible** : techniciens IT Canton de Vaud, EasyVista (`itsma.etat-de-vaud.ch` / `itsma.vd.ch`)
|
|
||||||
- **Démarrage projet** : jeudi 16 avril 2026
|
|
||||||
- **Version actuelle** : `v2026.5.37`
|
|
||||||
- **Manifest** : V3 (Chrome/Edge/Firefox)
|
|
||||||
- **Format** : `.zip` (Chromium) + `.xpi` signé (Firefox)
|
|
||||||
|
|
||||||
## Fonctionnalités principales
|
|
||||||
|
|
||||||
### Vue planning
|
|
||||||
- Affichage des interventions et réservations groupées par technicien
|
|
||||||
- Horaires, contact, lieu, catégorie, statut visibles d'un coup d'œil
|
|
||||||
- 8 techniciens hardcodés (équipe IT canton)
|
|
||||||
- Cache local pour réduire les requêtes serveur
|
|
||||||
|
|
||||||
### Modes d'affichage
|
|
||||||
- **Vue classique** (depuis v1.0.0) : cards en grille, mode compact écran 24" (depuis v2026.5.30)
|
|
||||||
- **Vue horizontale** (depuis v2026.5.32) : timeline par tech, sidebar verticale (depuis v2026.5.36)
|
|
||||||
- Toggle Vue classique ↔ Vue horizontale via bouton ⊞ dans popup user-badge
|
|
||||||
- Persistance localStorage (`view_mode`)
|
|
||||||
|
|
||||||
### Tooltips et popups
|
|
||||||
- Tooltips au survol (hover) sur chaque intervention
|
|
||||||
- Popups épinglables (📌) pour garder ouvert (depuis v4.1.3)
|
|
||||||
- Popups timeline persistantes au clic (depuis v4.2.3)
|
|
||||||
- Drag-and-drop des popups épinglés (depuis v2026.5.19)
|
|
||||||
- Safe area : popups jamais cachés sous topbar/dock (depuis v2026.5.20)
|
|
||||||
- Position auto adaptative (4 candidats : droite/gauche/dessous/dessus)
|
|
||||||
|
|
||||||
### Classification des absences (depuis v2026.5.27)
|
|
||||||
- **Maladie/Accident** : indigo `#4338ca`
|
|
||||||
- **Congé / Congés** : cyan `#06b6d4` (suffixe `s` adaptatif)
|
|
||||||
- **Pompier** : rouge `#b03030`
|
|
||||||
- Badge + barre gauche colorée + dégradé fond
|
|
||||||
- Absence récurrente Pillonel vendredi : cyan (depuis v2026.5.30)
|
|
||||||
|
|
||||||
### User et session
|
|
||||||
- Badge user avec photo/initiales en topbar
|
|
||||||
- Badge cliquable (depuis v2026.5.26) : popup avec ⚙ Paramètres + ⊞ Vue + compteur session MM:SS
|
|
||||||
- Retry automatique en cas d'échec fetch user (60s, max 10 essais)
|
|
||||||
- Compteur de session EasyVista (tick 1s, depuis v5.0.0)
|
|
||||||
- Reconnexion automatique
|
|
||||||
|
|
||||||
### Admin et configuration
|
|
||||||
- Mode admin caché : bouton ⚙ Paramètres dans popup user-badge (depuis v2026.5.25, remplace les 5 clics secrets sur le titre)
|
|
||||||
- Configuration persistée dans `localStorage` (`admin_config`)
|
|
||||||
- Catégories interventions personnalisables (livraison/recup/remplacement/incident/rollout/reservation/autre)
|
|
||||||
|
|
||||||
## Versionning — historique et conventions
|
|
||||||
|
|
||||||
L'extension a connu **3 systèmes de versionning successifs** :
|
|
||||||
|
|
||||||
| Période | Format | Exemple |
|
|
||||||
|---|---|---|
|
|
||||||
| 16-17 avril 2026 | Versions de base | `1.0.0`, `2.0.0`, `3.0.0` |
|
|
||||||
| 18-20 avril 2026 | SemVer classique | `4.1.3`, `4.2.8`, `5.0.12` |
|
|
||||||
| 21 avril 2026 → maintenant | **Année + mois + patch** | `2026.5.16` → `2026.5.37` |
|
|
||||||
|
|
||||||
### Pourquoi le passage à `YYYY.M.PATCH` ?
|
|
||||||
|
|
||||||
À partir de la **v2026.5.16** (21 avril 2026), l'extension est passée au versionning par année :
|
|
||||||
- Plus lisible pour les utilisateurs (l'année indique immédiatement la fraîcheur)
|
|
||||||
- Plus de débat sur ce qui constitue un "majeur" vs "mineur"
|
|
||||||
- Bump du `PATCH` à chaque livraison
|
|
||||||
|
|
||||||
⚠️ **Important** : `v2026.5.16` succède chronologiquement à `v5.0.12`, malgré le numéro qui semble plus petit. Le préfixe `2026` indique l'année.
|
|
||||||
|
|
||||||
## Versions notables
|
|
||||||
|
|
||||||
### `v2026.5.37` (latest, 25 avril 2026) — Refonte vue horizontale
|
|
||||||
- Topbar supprimée en vue horizontale, tout passe en sidebar
|
|
||||||
- User-badge + titre + bouton "Aujourd'hui" + date/heure + sélecteur + flèches + stats dans sidebar
|
|
||||||
- Banderole pompier masquée (badge + barre rouge gauche conservés)
|
|
||||||
|
|
||||||
### `v2026.5.36` — Sidebar verticale
|
|
||||||
- Wrapper flex-row `#horizontal-wrapper` [sidebar 200px] + [main]
|
|
||||||
- Déplacement physique des éléments via `ELEMENTS_TO_RELOCATE`
|
|
||||||
- Restauration propre en vue classique
|
|
||||||
|
|
||||||
### `v2026.5.32` — Vue horizontale togglable
|
|
||||||
- Bouton ⊞ "Vue" dans popup user-badge
|
|
||||||
- Chaque tech = 1 ligne horizontale compacte
|
|
||||||
- localStorage `view_mode`
|
|
||||||
|
|
||||||
### `v2026.5.27` — Classification absences
|
|
||||||
- ABSENCE_LABELS : `^(cong[ée]s|maladie|pompier)$`
|
|
||||||
- Couleurs catégories
|
|
||||||
- Topbar une ligne : "Jeudi 23.04.26 • 21:55"
|
|
||||||
|
|
||||||
### `v4.2.3` — Grande popup timeline persistante
|
|
||||||
- Clic segment timeline = popup persistante
|
|
||||||
- Hover = popup qui suit la souris
|
|
||||||
|
|
||||||
### `v4.1.3` — Tooltips épinglables
|
|
||||||
- Introduction de `pinTooltip`
|
|
||||||
|
|
||||||
### `v1.0.0` (16 avril 2026) — Initiale
|
|
||||||
- Premier viewer EasyVista pour le canton
|
|
||||||
|
|
||||||
Voir [CHANGELOG.md](CHANGELOG.md) pour l'historique complet (40 versions taggées).
|
|
||||||
|
|
||||||
## Architecture technique
|
|
||||||
|
|
||||||
```
|
|
||||||
manifest.json # Manifest V3 (Chrome) + gecko_settings (Firefox)
|
|
||||||
background.js # Worker fond : fetch planning XML, gestion session, fetch fiches
|
|
||||||
viewer.html # Interface principale
|
|
||||||
viewer.js # Logique (~9000 lignes) — voir détail ci-dessous
|
|
||||||
viewer.css # Styles + thèmes clair/sombre
|
|
||||||
icons/ # icon16, icon48, icon128
|
|
||||||
```
|
|
||||||
|
|
||||||
### `viewer.js` — fonctions clés
|
|
||||||
|
|
||||||
| Fonction | Introduite | Rôle |
|
|
||||||
|---|---|---|
|
|
||||||
| `loadForDate` | v1.0.0 | Fetch + parse planning pour une date donnée |
|
|
||||||
| `buildTooltipHTML` | v1.0.0 | Construction HTML du tooltip d'intervention |
|
|
||||||
| `pinTooltip` | v4.1.3 | Épingler un tooltip (le rendre permanent) |
|
|
||||||
| `bindTimelinePopover` | v4.2.3 | Lier popover timeline aux segments |
|
|
||||||
| `showTimelinePopover` | v4.2.3 | Afficher popover persistante |
|
|
||||||
| `openPersistentTimelinePopup` | v4.2.3 | Grande popup détaillée |
|
|
||||||
| `setTooltipViewportPosition` | v4.2.4 | Détection auto fixed/abs |
|
|
||||||
| `_softUnpinPopup` | v4.3.3 | Désépinglage mou (popup reste visible) |
|
|
||||||
| `initAppClock` | v5.0.0 | Horloge HH:MM topbar |
|
|
||||||
| `initSessionTimer` | v5.0.0 | Compteur session EV (tick 1s) |
|
|
||||||
| `initAdminMenu` | v5.0.0 | Menu admin (5 clics titre) |
|
|
||||||
| `_applyViewMode` | v2026.5.32 | Toggle vue classique/horizontale |
|
|
||||||
| `_maybeRetryFetchUser` | v2026.5.34 | Relance opportuniste fetch user |
|
|
||||||
| `positionTooltipAnchored` | v2026.5.34 | Positionnement unifié (4 candidats) |
|
|
||||||
|
|
||||||
### Constantes persistantes (toutes versions)
|
|
||||||
|
|
||||||
- 8 techs hardcodés : `76272,83725,66635,92235,90070,40944,72485,86874`
|
|
||||||
- Pillonel Olivier (ID 40944) : absent tous les vendredis (hardcodé)
|
|
||||||
- Group ID EasyVista : `191`
|
|
||||||
- GUIDs forms EV :
|
|
||||||
- Demande : `S={C99ECD05-3D48-4C62-ABF0-66292053AED6}`
|
|
||||||
- Incident : `I={07ED9C68-6172-48EA-8A58-90912B0A283E}`
|
|
||||||
- SSO : Canton ForgeRock OpenAM
|
|
||||||
- Storage keys : `admin_config`, `view_mode` (depuis v2026.5.32)
|
|
||||||
- Domaines : `itsma.etat-de-vaud.ch` (interne), `itsma.vd.ch` (externe SSO)
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Firefox
|
- **Firefox 140 ou supérieur** : installer le fichier `.xpi` de la [dernière publication](https://gitea.netaplaid.ch/FroSteel/Planification/releases/latest). Mozilla signe ce fichier et Firefox applique les mises à jour de lui-même.
|
||||||
Télécharger le `.xpi` signé depuis le serveur de mises à jour interne, ou drag-and-drop dans `about:addons`.
|
- **Chrome et Edge** : extraire le `.zip` de la dernière publication dans un dossier permanent, puis le charger comme extension non empaquetée (mode développeur). Les mises à jour se font à la main.
|
||||||
|
|
||||||
### Chrome / Edge
|
Le pas à pas, le choix de l'équipe et la synchronisation entre postes sont décrits dans la page [Installation](https://gitea.netaplaid.ch/FroSteel/Planification/wiki/Installation) du wiki.
|
||||||
Mode développeur : décompresser le ZIP et charger en tant qu'extension non empaquetée.
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
| Page | Contenu |
|
||||||
|
|---|---|
|
||||||
|
| [Installation](https://gitea.netaplaid.ch/FroSteel/Planification/wiki/Installation) | Firefox, Chrome et Edge, premier réglage, synchronisation entre postes |
|
||||||
|
| [Utilisation](https://gitea.netaplaid.ch/FroSteel/Planification/wiki/Utilisation) | Lire le planning, déplacer une intervention, rechercher, emails |
|
||||||
|
| [Réglages](https://gitea.netaplaid.ch/FroSteel/Planification/wiki/R%C3%A9glages) | Chaque onglet des Paramètres |
|
||||||
|
| [Dépannage](https://gitea.netaplaid.ch/FroSteel/Planification/wiki/D%C3%A9pannage) | Problèmes connus et marche à suivre |
|
||||||
|
| [Versions](https://gitea.netaplaid.ch/FroSteel/Planification/wiki/Versions) | Ce que change chaque version |
|
||||||
|
| [Architecture](https://gitea.netaplaid.ch/FroSteel/Planification/wiki/Architecture) | Structure du code, données stockées, réseau, publication |
|
||||||
|
|
||||||
## Développement
|
## Développement
|
||||||
|
|
||||||
|
Le code est du JavaScript sans framework ni bundler, dans `src/`. Le script `build.sh` (bash, python3, zip) produit les paquets Chromium et Firefox dans `dist/`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://gitea.netaplaid.ch/FroSteel/Planification.git
|
git clone https://gitea.netaplaid.ch/FroSteel/Planification.git
|
||||||
cd Planification
|
cd Planification
|
||||||
|
./build.sh
|
||||||
# Pour packager une nouvelle version :
|
|
||||||
# 1. modifier le code
|
|
||||||
# 2. bump version dans manifest.json
|
|
||||||
# 3. zip + xpi
|
|
||||||
git add -A
|
|
||||||
git commit -m "Version YYYY.M.PATCH — description"
|
|
||||||
git tag vYYYY.M.PATCH
|
|
||||||
git push origin main
|
|
||||||
git push --tags
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Licence
|
L'historique détaillé des versions se trouve dans [CHANGELOG.md](CHANGELOG.md).
|
||||||
|
|
||||||
[MIT License](LICENSE) — Copyright (c) 2026 Quentin Rouiller
|
## Licence et auteur
|
||||||
|
|
||||||
## Auteur
|
Code publié sous licence [MIT](LICENSE). Auteur : Quentin Rouiller, technicien DGNSI, Canton de Vaud.
|
||||||
|
|
||||||
**Quentin Rouiller** (QRO)
|
|
||||||
Canton de Vaud — Service IT
|
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
###############################################################################
|
||||||
|
# build.sh — génère dist/chromium/, dist/firefox/, et les archives .zip / .xpi
|
||||||
|
# à partir du code source dans src/.
|
||||||
|
#
|
||||||
|
# Usage : ./build.sh
|
||||||
|
###############################################################################
|
||||||
|
set -e
|
||||||
|
# Le script est dans Autres/ — on remonte d'un cran pour se placer à la
|
||||||
|
# racine du projet, où se trouvent src/ et dist/.
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
VERSION=$(python3 -c "import json; print(json.load(open('src/manifest.json'))['version'])")
|
||||||
|
echo "==> Build Planification v$VERSION"
|
||||||
|
|
||||||
|
rm -rf dist
|
||||||
|
mkdir -p dist/chromium dist/firefox
|
||||||
|
|
||||||
|
# ---- Chromium : copie src/ tel quel (manifest sans gecko_settings) ----
|
||||||
|
cp -r src/* dist/chromium/
|
||||||
|
echo " ✓ dist/chromium/ ($(du -sh dist/chromium | cut -f1))"
|
||||||
|
|
||||||
|
# ---- Firefox : copie src/ + manifest avec browser_specific_settings ----
|
||||||
|
cp -r src/* dist/firefox/
|
||||||
|
python3 - <<EOF
|
||||||
|
import json
|
||||||
|
with open('src/manifest.json', 'r') as f: m = json.load(f)
|
||||||
|
# Le champ 'key' (ID Chromium déterministe) n'a pas de sens côté Firefox et
|
||||||
|
# fait râler le linter AMO → on le retire du build Firefox.
|
||||||
|
m.pop('key', None)
|
||||||
|
m['browser_specific_settings'] = {
|
||||||
|
'gecko': {
|
||||||
|
'id': '[email protected]',
|
||||||
|
'strict_min_version': '140.0',
|
||||||
|
'update_url': 'https://gitea.netaplaid.ch/FroSteel/Planification/raw/branch/main/firefox-updates.json',
|
||||||
|
'data_collection_permissions': {'required': ['none']}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Firefox MV3 ne supporte pas (encore) 'service_worker' → AMO rejette.
|
||||||
|
# On ajoute 'scripts' (event page) comme fallback compatible Firefox.
|
||||||
|
# Chrome ignore 'scripts' quand 'service_worker' est présent ; Firefox
|
||||||
|
# ignore 'service_worker' et utilise 'scripts'. Les deux navigateurs
|
||||||
|
# chargent ainsi le même background.js.
|
||||||
|
bg = m.get('background', {})
|
||||||
|
if 'scripts' not in bg:
|
||||||
|
bg['scripts'] = ['background.js']
|
||||||
|
m['background'] = bg
|
||||||
|
with open('dist/firefox/manifest.json', 'w') as f:
|
||||||
|
json.dump(m, f, indent=2, ensure_ascii=False)
|
||||||
|
f.write('\n')
|
||||||
|
EOF
|
||||||
|
echo " ✓ dist/firefox/ ($(du -sh dist/firefox | cut -f1))"
|
||||||
|
|
||||||
|
# ---- Archives ZIP / XPI prêtes à distribuer ----
|
||||||
|
cd dist/chromium && zip -rq "../planification-v${VERSION}-chromium.zip" . && cd ../..
|
||||||
|
cd dist/firefox && zip -rq "../planification-v${VERSION}-firefox.xpi" . && cd ../..
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> Builds prêts dans dist/"
|
||||||
|
ls -la dist/*.zip dist/*.xpi 2>/dev/null
|
||||||
|
|
||||||
|
# ---- firefox-updates.json : ajout/mise à jour de l'entrée pour cette version
|
||||||
|
# (sha256 du .xpi NON SIGNÉ — sera remplacé par celui du .xpi signé après AMO).
|
||||||
|
python3 - <<EOF
|
||||||
|
import json, hashlib, os
|
||||||
|
xpi = f"dist/planification-v${VERSION}-firefox.xpi"
|
||||||
|
with open(xpi, 'rb') as f: sha = hashlib.sha256(f.read()).hexdigest()
|
||||||
|
|
||||||
|
JSON_PATH = "firefox-updates.json"
|
||||||
|
ADDON_ID = "[email protected]"
|
||||||
|
update_link = f"https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v${VERSION}/planification-v${VERSION}-firefox.xpi"
|
||||||
|
|
||||||
|
if os.path.exists(JSON_PATH):
|
||||||
|
with open(JSON_PATH) as f: data = json.load(f)
|
||||||
|
else:
|
||||||
|
data = {"addons": {ADDON_ID: {"updates": []}}}
|
||||||
|
|
||||||
|
addon = data.setdefault("addons", {}).setdefault(ADDON_ID, {"updates": []})
|
||||||
|
updates = addon.setdefault("updates", [])
|
||||||
|
|
||||||
|
# Retirer toute entrée existante pour cette version (idempotent)
|
||||||
|
updates = [u for u in updates if u.get("version") != "${VERSION}"]
|
||||||
|
# Ajouter en tête
|
||||||
|
updates.insert(0, {
|
||||||
|
"version": "${VERSION}",
|
||||||
|
"update_link": update_link,
|
||||||
|
"update_hash": "sha256:" + sha,
|
||||||
|
})
|
||||||
|
addon["updates"] = updates
|
||||||
|
|
||||||
|
with open(JSON_PATH, 'w') as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
f.write('\n')
|
||||||
|
|
||||||
|
print(f" ✓ firefox-updates.json mis à jour (sha256 NON SIGNÉ : {sha[:16]}…)")
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Pour Chrome : charger dist/chromium/ en mode développeur"
|
||||||
|
echo "Pour Firefox : signer dist/planification-v${VERSION}-firefox.xpi sur AMO"
|
||||||
|
echo " Après signature, remplacer le sha256 dans firefox-updates.json par celui du .xpi signé."
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"addons": {
|
||||||
|
"[email protected]": {
|
||||||
|
"updates": [
|
||||||
|
{
|
||||||
|
"version": "2026.5.50",
|
||||||
|
"update_link": "https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v2026.5.50/planification-v2026.5.50-firefox.xpi",
|
||||||
|
"update_hash": "sha256:314ae3cba7e31d1f6eb89c419a666d49d156cd78a83c116de03f5e48b8a094df"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2026.5.49",
|
||||||
|
"update_link": "https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v2026.5.49/planification-v2026.5.49-firefox.xpi",
|
||||||
|
"update_hash": "sha256:73ac4e44e9fdfb1c3c179c7cc5d2e497b5cf27d207947cc559ab620bddd09fa1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2026.5.48",
|
||||||
|
"update_link": "https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v2026.5.48/planification-v2026.5.48-firefox.xpi",
|
||||||
|
"update_hash": "sha256:be687aa2795649d7f1f9883f3384365bd224e0ab94987ff852b0901f5554a51a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2026.5.47",
|
||||||
|
"update_link": "https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v2026.5.47/planification-v2026.5.47-firefox.xpi",
|
||||||
|
"update_hash": "sha256:0418062357b7c29bdd7a01b6d6e06eeb20e2ee419c67684a84855862af113cc2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2026.5.46",
|
||||||
|
"update_link": "https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v2026.5.46/planification-v2026.5.46-firefox.xpi",
|
||||||
|
"update_hash": "sha256:673dc0527073523750de348414ae1d3b5c0cb42cc8128a7839bbb09bffb6242c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2026.5.45",
|
||||||
|
"update_link": "https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v2026.5.45/planification-v2026.5.45-firefox.xpi",
|
||||||
|
"update_hash": "sha256:42ac47aeda23912e80051fc941ba45a0dec74ec4a6c509a25270c27b73dddead"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2026.5.44",
|
||||||
|
"update_link": "https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v2026.5.44/planification-v2026.5.44-firefox.xpi",
|
||||||
|
"update_hash": "sha256:e56e87d59c465e5df828b18d74376f561bf34e81e21bf4d70989a709e89217e0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2026.5.43",
|
||||||
|
"update_link": "https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v2026.5.43/planification-v2026.5.43-firefox.xpi",
|
||||||
|
"update_hash": "sha256:7052200fab3c9266d5b809398a00dac768679ab2e96e4e147e4bb86c4ab648e5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2026.5.42",
|
||||||
|
"update_link": "https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v2026.5.42/planification-v2026.5.42-firefox.xpi",
|
||||||
|
"update_hash": "sha256:a5291a1eab6768d430dfc1cf032f93aeb788083da620ae0568b9dee68f14734d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2026.5.41",
|
||||||
|
"update_link": "https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v2026.5.41/planification-v2026.5.41-firefox.xpi",
|
||||||
|
"update_hash": "sha256:fbf7d8a57ad060306cb43f3db3a5d5b599bb07c75c4ef0dbd0346406bdb6c65b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2026.5.40",
|
||||||
|
"update_link": "https://gitea.netaplaid.ch/FroSteel/Planification/releases/download/v2026.5.40/planification-v2026.5.40-firefox.xpi",
|
||||||
|
"update_hash": "sha256:2ba0758960b931f4211c613c75bbf21b3a250572dddc70d854ff1ecca3220421"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
cd /d "%~dp0"
|
||||||
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -Command "Get-ChildItem -LiteralPath '%~dp0' -Recurse -File | Unblock-File -ErrorAction SilentlyContinue; & '%~dp0bin\manage.ps1'"
|
||||||
|
Before Width: | Height: | Size: 444 B After Width: | Height: | Size: 444 B |
@@ -0,0 +1,387 @@
|
|||||||
|
# Planification - native messaging host // 2026.1.4 - FRS
|
||||||
|
# Developpe par Quentin (FRS) - DGNSI, Canton de Vaud.
|
||||||
|
# Licence MIT - voir le fichier LICENSE.
|
||||||
|
#
|
||||||
|
# Pont entre l'extension et un dossier reseau partage pour le cache du planning.
|
||||||
|
#
|
||||||
|
# Layout: <share>\AAAA\MM\AAAA-MM-JJ.json (une image par jour, par annee/mois).
|
||||||
|
# Index racine <share>\_index.json : date -> savedAt (stat/list rapides).
|
||||||
|
# Anciens fichiers plats (<share>\AAAA-MM-JJ.json) restent lus (compat).
|
||||||
|
#
|
||||||
|
# Protocole: stdio = prefixe longueur 4 octets (little-endian) + corps JSON UTF-8.
|
||||||
|
# Boucle jusqu'a EOF (marche pour sendNativeMessage et connectNative).
|
||||||
|
#
|
||||||
|
# Actions:
|
||||||
|
# {action:"version"} -> {ok,version}
|
||||||
|
# {action:"stat", path, date} -> {ok,exists,savedAt}
|
||||||
|
# {action:"read", path, date, offset, note} -> {ok,exists,total,offset,chunk,more}
|
||||||
|
# {action:"save", path, date, json, savedAt, note} -> {ok}
|
||||||
|
# {action:"list", path} -> {ok,days:[{date,savedAt}]}
|
||||||
|
# {action:"log", verbose} -> {ok,version,log}
|
||||||
|
# {action:"logmsg",level,message} -> {ok}
|
||||||
|
#
|
||||||
|
# Journal d'activite (active.log) : lignes "JJ/MM/AAAA HH:MM:SS [NIVEAU] message".
|
||||||
|
# Niveaux: INFO (evenements utiles), DEBUG (detail technique), ERREUR.
|
||||||
|
# Les messages par defaut de l'hote sont SANS accent (encodage .ps1 PS 5.1) ;
|
||||||
|
# les "note" envoyees par l'extension transitent en UTF-8 et gardent les accents.
|
||||||
|
#
|
||||||
|
# IMPORTANT: rien ne doit atteindre stdout sauf via Write-Message.
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
|
||||||
|
$HOST_VERSION = '2026.1.8'
|
||||||
|
# v2026.1.7 - le host n'est plus persistant a vie : s'il ne recoit AUCUN message
|
||||||
|
# pendant ce delai, il se ferme proprement (le navigateur le relance a la
|
||||||
|
# prochaine activite de l'extension). Il lance aussi le tray a la demande.
|
||||||
|
$IDLE_TIMEOUT_MS = 2 * 60 * 60 * 1000 # 2 heures
|
||||||
|
$CHUNK_BYTES = 600000
|
||||||
|
$DATE_RE = '^\d{4}-\d{2}-\d{2}$'
|
||||||
|
$INDEX_NAME = '_index.json'
|
||||||
|
$MAX_IN = 67108864
|
||||||
|
|
||||||
|
$stdin = [Console]::OpenStandardInput()
|
||||||
|
$stdout = [Console]::OpenStandardOutput()
|
||||||
|
$Utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||||
|
|
||||||
|
# host.ps1 vit dans <install>\bin -> la racine de l'install est le parent.
|
||||||
|
$LOG_DIR = if ($PSScriptRoot) { Split-Path -Parent $PSScriptRoot } else { Join-Path $env:LOCALAPPDATA 'Planification' }
|
||||||
|
$LOG_FILE = Join-Path $LOG_DIR 'activity.log'
|
||||||
|
|
||||||
|
function Format-DateFR([string]$iso) {
|
||||||
|
if ($iso -match '^(\d{4})-(\d{2})-(\d{2})$') { return "$($Matches[3])/$($Matches[2])/$($Matches[1])" }
|
||||||
|
return $iso
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Activity([string]$level, [string]$msg) {
|
||||||
|
try {
|
||||||
|
if ((Test-Path -LiteralPath $LOG_FILE) -and ((Get-Item -LiteralPath $LOG_FILE).Length -gt 1048576)) {
|
||||||
|
Move-Item -LiteralPath $LOG_FILE -Destination ($LOG_FILE + '.1') -Force
|
||||||
|
}
|
||||||
|
$ts = (Get-Date).ToString('dd/MM/yyyy HH:mm:ss')
|
||||||
|
[System.IO.File]::AppendAllText($LOG_FILE, "$ts [$level] $msg`r`n", $Utf8NoBom)
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Read-Exactly([int]$n) {
|
||||||
|
$buf = New-Object byte[] $n
|
||||||
|
$off = 0
|
||||||
|
while ($off -lt $n) {
|
||||||
|
$r = $stdin.Read($buf, $off, $n - $off)
|
||||||
|
if ($r -le 0) { return $null }
|
||||||
|
$off += $r
|
||||||
|
}
|
||||||
|
return $buf
|
||||||
|
}
|
||||||
|
|
||||||
|
# v2026.1.7 - lit les 4 octets de prefixe de longueur AVEC un timeout sur le
|
||||||
|
# 1er octet (= attente du prochain message). Renvoie le byte[], $null sur EOF,
|
||||||
|
# ou la chaine 'TIMEOUT' si rien n'arrive dans le delai -> le host se ferme.
|
||||||
|
function Read-LengthOrTimeout([int]$timeoutMs) {
|
||||||
|
$buf = New-Object byte[] 4
|
||||||
|
$off = 0
|
||||||
|
while ($off -lt 4) {
|
||||||
|
$ar = $stdin.BeginRead($buf, $off, 4 - $off, $null, $null)
|
||||||
|
if ($off -eq 0) {
|
||||||
|
if (-not $ar.AsyncWaitHandle.WaitOne($timeoutMs)) { return 'TIMEOUT' }
|
||||||
|
} else {
|
||||||
|
[void]$ar.AsyncWaitHandle.WaitOne()
|
||||||
|
}
|
||||||
|
$r = $stdin.EndRead($ar)
|
||||||
|
if ($r -le 0) { return $null }
|
||||||
|
$off += $r
|
||||||
|
}
|
||||||
|
return $buf
|
||||||
|
}
|
||||||
|
|
||||||
|
function Read-Message {
|
||||||
|
$lenBytes = Read-LengthOrTimeout $IDLE_TIMEOUT_MS
|
||||||
|
if ($lenBytes -is [string]) { return $lenBytes } # 'TIMEOUT' -> arret propre
|
||||||
|
if ($null -eq $lenBytes) { return $null }
|
||||||
|
$len = [BitConverter]::ToInt32($lenBytes, 0)
|
||||||
|
if ($len -le 0 -or $len -gt $MAX_IN) { return $null }
|
||||||
|
$payload = Read-Exactly $len
|
||||||
|
if ($null -eq $payload) { return $null }
|
||||||
|
$json = [System.Text.Encoding]::UTF8.GetString($payload)
|
||||||
|
return ($json | ConvertFrom-Json)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Message($obj) {
|
||||||
|
$json = $obj | ConvertTo-Json -Compress -Depth 50
|
||||||
|
$bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
|
||||||
|
$lenBytes = [BitConverter]::GetBytes([int]$bytes.Length)
|
||||||
|
$stdout.Write($lenBytes, 0, 4)
|
||||||
|
$stdout.Write($bytes, 0, $bytes.Length)
|
||||||
|
$stdout.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-DateStr([string]$d) { return ($d -match $DATE_RE) }
|
||||||
|
|
||||||
|
function Resolve-Share([string]$path) {
|
||||||
|
if ([string]::IsNullOrWhiteSpace($path)) { throw 'empty_path' }
|
||||||
|
if (-not (Test-Path -LiteralPath $path)) {
|
||||||
|
New-Item -ItemType Directory -Path $path -Force | Out-Null
|
||||||
|
}
|
||||||
|
return (Get-Item -LiteralPath $path).FullName
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-DayDir([string]$share, [string]$date) {
|
||||||
|
$yyyy = $date.Substring(0,4)
|
||||||
|
$mm = $date.Substring(5,2)
|
||||||
|
return (Join-Path (Join-Path $share $yyyy) $mm)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-DayFileWrite([string]$share, [string]$date) {
|
||||||
|
$dir = Get-DayDir $share $date
|
||||||
|
if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
||||||
|
return (Join-Path $dir ($date + '.json'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-DayFileRead([string]$share, [string]$date) {
|
||||||
|
$ym = Join-Path (Get-DayDir $share $date) ($date + '.json')
|
||||||
|
if (Test-Path -LiteralPath $ym) { return $ym }
|
||||||
|
$flat = Join-Path $share ($date + '.json')
|
||||||
|
if (Test-Path -LiteralPath $flat) { return $flat }
|
||||||
|
return $ym
|
||||||
|
}
|
||||||
|
|
||||||
|
function Read-Index([string]$share) {
|
||||||
|
$idx = Join-Path $share $INDEX_NAME
|
||||||
|
if (Test-Path -LiteralPath $idx) {
|
||||||
|
try { return (Get-Content -LiteralPath $idx -Raw -Encoding UTF8 | ConvertFrom-Json) } catch { return $null }
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Update-Index([string]$share, [string]$date, [long]$savedAt) {
|
||||||
|
$idx = Join-Path $share $INDEX_NAME
|
||||||
|
# v2026.1.8 - verrou DEDIE a l'index. Plusieurs hotes (plusieurs onglets
|
||||||
|
# Planification ouverts, ou plusieurs postes) font un read-modify-write
|
||||||
|
# concurrent sur _index.json ; sans verrou, deux Move-Item se recouvrent et la
|
||||||
|
# maj d'un hote est perdue (lost update) -> l'index prend du retard sur les
|
||||||
|
# fichiers-jour -> conflits CAS a repetition ("refusion" en boucle). On
|
||||||
|
# serialise donc la sequence lecture->modif->ecriture. Vol du verrou perime
|
||||||
|
# (> 10 s) comme pour les fichiers-jour. Best-effort : si on n'obtient pas le
|
||||||
|
# verrou, on renonce (la prochaine ecriture reussie remettra l'index a jour).
|
||||||
|
$lock = $idx + '.lock'
|
||||||
|
$haveLock = $false
|
||||||
|
for ($i = 0; $i -lt 20 -and -not $haveLock; $i++) {
|
||||||
|
try { [System.IO.File]::Open($lock, [System.IO.FileMode]::CreateNew, [System.IO.FileAccess]::Write).Close(); $haveLock = $true }
|
||||||
|
catch {
|
||||||
|
try {
|
||||||
|
$age = ((Get-Date) - (Get-Item -LiteralPath $lock).LastWriteTime).TotalSeconds
|
||||||
|
if ($age -gt 10) { Remove-Item -LiteralPath $lock -Force -ErrorAction SilentlyContinue }
|
||||||
|
else { Start-Sleep -Milliseconds 50 }
|
||||||
|
} catch { Start-Sleep -Milliseconds 50 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $haveLock) { Write-Activity 'DEBUG' "Index non mis a jour pour le $(Format-DateFR $date) (verrou index indisponible)"; return }
|
||||||
|
try {
|
||||||
|
# lecture DANS le verrou -> on part toujours de la derniere version.
|
||||||
|
$map = @{}
|
||||||
|
if (Test-Path -LiteralPath $idx) {
|
||||||
|
try {
|
||||||
|
$existing = Get-Content -LiteralPath $idx -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
|
foreach ($p in $existing.PSObject.Properties) { $map[$p.Name] = $p.Value }
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
$map[$date] = $savedAt
|
||||||
|
$tmp = $idx + '.tmp'
|
||||||
|
[System.IO.File]::WriteAllText($tmp, ($map | ConvertTo-Json -Compress -Depth 5), $Utf8NoBom)
|
||||||
|
Move-Item -LiteralPath $tmp -Destination $idx -Force
|
||||||
|
Write-Activity 'DEBUG' "Index du dossier mis a jour pour le $(Format-DateFR $date)"
|
||||||
|
} finally {
|
||||||
|
Remove-Item -LiteralPath $lock -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SavedAt([string]$share, [string]$date, [string]$file) {
|
||||||
|
$ix = Read-Index $share
|
||||||
|
if ($ix -and ($ix.PSObject.Properties.Name -contains $date)) {
|
||||||
|
try { return [long]$ix.$date } catch {}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$o = Get-Content -LiteralPath $file -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
|
return [long]$o.savedAt
|
||||||
|
} catch { return 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
# v2026.1.7 - le host vient d'etre lance par le navigateur (connexion de
|
||||||
|
# l'extension). On (re)lance le tray s'il n'est pas deja la, et on horodate
|
||||||
|
# l'activite (sert aussi de battement de coeur au tray fraichement lance).
|
||||||
|
# Best-effort, tout en try/catch : ne JAMAIS perturber le protocole stdio.
|
||||||
|
try {
|
||||||
|
$trayRunning = $false
|
||||||
|
try { $tm = [System.Threading.Mutex]::OpenExisting('Local\PlanificationTraySingleton'); $tm.Dispose(); $trayRunning = $true } catch { $trayRunning = $false }
|
||||||
|
if (-not $trayRunning -and $PSScriptRoot) {
|
||||||
|
$trayVbs = Join-Path $PSScriptRoot 'tray.vbs'
|
||||||
|
if (Test-Path -LiteralPath $trayVbs) { Start-Process wscript.exe -ArgumentList "`"$trayVbs`"" }
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
Write-Activity 'INFO' "Host demarre (v$HOST_VERSION) - connexion de l'extension."
|
||||||
|
|
||||||
|
while ($true) {
|
||||||
|
$msg = Read-Message
|
||||||
|
# v2026.1.7 - inactivite > 2 h : arret propre (le navigateur relancera le host
|
||||||
|
# a la prochaine activite de l'extension, qui relancera aussi le tray).
|
||||||
|
if ($msg -is [string] -and $msg -eq 'TIMEOUT') {
|
||||||
|
Write-Activity 'INFO' 'Aucune communication depuis 2 h - arret du host (relance auto a la prochaine activite).'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ($null -eq $msg) { break }
|
||||||
|
try {
|
||||||
|
$action = [string]$msg.action
|
||||||
|
switch ($action) {
|
||||||
|
|
||||||
|
'version' {
|
||||||
|
Write-Message @{ ok = $true; version = $HOST_VERSION }
|
||||||
|
}
|
||||||
|
|
||||||
|
'stat' {
|
||||||
|
if (-not (Test-DateStr $msg.date)) { Write-Message @{ ok=$false; error='bad_date' }; break }
|
||||||
|
$share = Resolve-Share $msg.path
|
||||||
|
$f = Get-DayFileRead $share $msg.date
|
||||||
|
$fr = Format-DateFR $msg.date
|
||||||
|
if (Test-Path -LiteralPath $f) {
|
||||||
|
$sa = Get-SavedAt $share $msg.date $f
|
||||||
|
Write-Activity 'DEBUG' "Verification du planning du $fr : present dans le dossier"
|
||||||
|
Write-Message @{ ok=$true; exists=$true; savedAt=$sa }
|
||||||
|
} else {
|
||||||
|
Write-Activity 'DEBUG' "Verification du planning du $fr : absent du dossier"
|
||||||
|
Write-Message @{ ok=$true; exists=$false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
'read' {
|
||||||
|
if (-not (Test-DateStr $msg.date)) { Write-Message @{ ok=$false; error='bad_date' }; break }
|
||||||
|
$share = Resolve-Share $msg.path
|
||||||
|
$f = Get-DayFileRead $share $msg.date
|
||||||
|
$fr = Format-DateFR $msg.date
|
||||||
|
if (-not (Test-Path -LiteralPath $f)) { Write-Message @{ ok=$true; exists=$false }; break }
|
||||||
|
$offset = 0
|
||||||
|
if ($null -ne $msg.offset) { $offset = [int]$msg.offset }
|
||||||
|
$fs = [System.IO.File]::OpenRead($f)
|
||||||
|
try {
|
||||||
|
$total = [int]$fs.Length
|
||||||
|
if ($offset -lt 0) { $offset = 0 }
|
||||||
|
if ($offset -gt $total) { $offset = $total }
|
||||||
|
$len = [Math]::Min($CHUNK_BYTES, $total - $offset)
|
||||||
|
$b64 = ''
|
||||||
|
if ($len -gt 0) {
|
||||||
|
$buf = New-Object byte[] $len
|
||||||
|
$fs.Position = $offset
|
||||||
|
$got = 0
|
||||||
|
while ($got -lt $len) {
|
||||||
|
$r = $fs.Read($buf, $got, $len - $got)
|
||||||
|
if ($r -le 0) { break }
|
||||||
|
$got += $r
|
||||||
|
}
|
||||||
|
$b64 = [Convert]::ToBase64String($buf, 0, $got)
|
||||||
|
}
|
||||||
|
$more = (($offset + $len) -lt $total)
|
||||||
|
if ($offset -eq 0) {
|
||||||
|
if ($msg.note) { Write-Activity 'INFO' ([string]$msg.note) }
|
||||||
|
else { Write-Activity 'INFO' "Lecture du planning du $fr depuis le dossier ($([Math]::Round($total/1024)) Ko)" }
|
||||||
|
} else {
|
||||||
|
Write-Activity 'DEBUG' "Transfert d'une tranche du planning du $fr (position $offset)"
|
||||||
|
}
|
||||||
|
Write-Message @{ ok=$true; exists=$true; total=$total; offset=$offset; chunk=$b64; more=$more }
|
||||||
|
} finally { $fs.Close() }
|
||||||
|
}
|
||||||
|
|
||||||
|
'save' {
|
||||||
|
# v2026.1.6 - FRS : ecriture compare-and-swap + verrou (merge concurrent).
|
||||||
|
# Si expectedSavedAt fourni, on n'ecrit QUE si le savedAt du dossier est
|
||||||
|
# toujours celui-la (sinon conflit -> l'extension refusionne et reessaie).
|
||||||
|
if (-not (Test-DateStr $msg.date)) { Write-Message @{ ok=$false; error='bad_date' }; break }
|
||||||
|
$share = Resolve-Share $msg.path
|
||||||
|
$f = Get-DayFileWrite $share $msg.date
|
||||||
|
$fr = Format-DateFR $msg.date
|
||||||
|
$json = [string]$msg.json
|
||||||
|
$hasExpect = ($null -ne $msg.expectedSavedAt)
|
||||||
|
$expect = if ($hasExpect) { [long]$msg.expectedSavedAt } else { 0 }
|
||||||
|
$lock = $f + '.lock'
|
||||||
|
|
||||||
|
# Verrou inter-machines : creation exclusive ; vol si perime (> 10 s).
|
||||||
|
$haveLock = $false
|
||||||
|
try { [System.IO.File]::Open($lock, [System.IO.FileMode]::CreateNew, [System.IO.FileAccess]::Write).Close(); $haveLock = $true }
|
||||||
|
catch {
|
||||||
|
try {
|
||||||
|
$age = ((Get-Date) - (Get-Item -LiteralPath $lock).LastWriteTime).TotalSeconds
|
||||||
|
if ($age -gt 10) {
|
||||||
|
Remove-Item -LiteralPath $lock -Force -ErrorAction SilentlyContinue
|
||||||
|
[System.IO.File]::Open($lock, [System.IO.FileMode]::CreateNew, [System.IO.FileAccess]::Write).Close(); $haveLock = $true
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
if (-not $haveLock) { Write-Message @{ ok=$false; locked=$true }; break }
|
||||||
|
|
||||||
|
try {
|
||||||
|
$proceed = $true
|
||||||
|
if ($hasExpect) {
|
||||||
|
$cur = 0
|
||||||
|
$existing = Get-DayFileRead $share $msg.date
|
||||||
|
if (Test-Path -LiteralPath $existing) {
|
||||||
|
try { $o = Get-Content -LiteralPath $existing -Raw -Encoding UTF8 | ConvertFrom-Json; $cur = [long]$o.savedAt } catch { $cur = 0 }
|
||||||
|
}
|
||||||
|
if ($cur -ne $expect) {
|
||||||
|
Write-Activity 'DEBUG' "Conflit d'ecriture sur le $fr (attendu $expect, dossier $cur) - refusion cote extension"
|
||||||
|
Write-Message @{ ok=$false; conflict=$true; currentSavedAt=$cur }
|
||||||
|
$proceed = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($proceed) {
|
||||||
|
$tmp = $f + '.tmp'
|
||||||
|
[System.IO.File]::WriteAllText($tmp, $json, $Utf8NoBom)
|
||||||
|
Move-Item -LiteralPath $tmp -Destination $f -Force
|
||||||
|
$savedAt = 0
|
||||||
|
if ($null -ne $msg.savedAt) { $savedAt = [long]$msg.savedAt }
|
||||||
|
try { Update-Index $share $msg.date $savedAt } catch {}
|
||||||
|
if ($msg.note) { Write-Activity 'INFO' ([string]$msg.note) }
|
||||||
|
else { Write-Activity 'INFO' "Sauvegarde du planning du $fr dans le dossier ($([Math]::Round($json.Length/1024)) Ko)" }
|
||||||
|
Write-Message @{ ok=$true; savedAt=$savedAt }
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Remove-Item -LiteralPath $lock -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
'list' {
|
||||||
|
$share = Resolve-Share $msg.path
|
||||||
|
$days = @()
|
||||||
|
$ix = Read-Index $share
|
||||||
|
if ($ix) {
|
||||||
|
foreach ($p in $ix.PSObject.Properties) {
|
||||||
|
if ($p.Name -match $DATE_RE) { $days += @{ date=$p.Name; savedAt=[long]$p.Value } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Activity 'DEBUG' "Liste des plannings disponibles dans le dossier ($($days.Count) jour(s))"
|
||||||
|
Write-Message @{ ok=$true; days=@($days) }
|
||||||
|
}
|
||||||
|
|
||||||
|
'log' {
|
||||||
|
$txt = ''
|
||||||
|
if (Test-Path -LiteralPath $LOG_FILE) {
|
||||||
|
try {
|
||||||
|
$all = Get-Content -LiteralPath $LOG_FILE -Encoding UTF8 -ErrorAction SilentlyContinue
|
||||||
|
if ($all) { $txt = ($all | Select-Object -Last 400) -join "`n" }
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
Write-Message @{ ok=$true; version=$HOST_VERSION; log=$txt }
|
||||||
|
}
|
||||||
|
|
||||||
|
'logmsg' {
|
||||||
|
$lvl = if ($msg.level) { [string]$msg.level } else { 'INFO' }
|
||||||
|
if ($msg.message) { Write-Activity $lvl ([string]$msg.message) }
|
||||||
|
Write-Message @{ ok=$true }
|
||||||
|
}
|
||||||
|
|
||||||
|
default {
|
||||||
|
Write-Message @{ ok=$false; error=('unknown_action:' + $action) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
try { Write-Activity 'ERREUR' "$action : $($_.Exception.Message)" } catch {}
|
||||||
|
Write-Message @{ ok=$false; error=([string]$_.Exception.Message) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
# Planification - gestionnaire graphique du helper de sync. // 2026.1.5 - FRS
|
||||||
|
# Developpe par Quentin (FRS) - DGNSI, Canton de Vaud.
|
||||||
|
# Licence MIT - voir le fichier LICENSE.
|
||||||
|
#
|
||||||
|
# Fenetre avec : Installer / Mettre a jour, Reparer, Desinstaller.
|
||||||
|
# Per-user (HKCU), sans admin, sans signature. Lance par Gerer.cmd.
|
||||||
|
#
|
||||||
|
# Structure du paquet : Gerer.cmd (racine), bin\ (manage/host/tray/.vbs),
|
||||||
|
# assets\ (icon.png). Structure installee (%LOCALAPPDATA%\Planification) :
|
||||||
|
# bin\ (host.ps1, host.bat, tray.ps1, tray.vbs), assets\ (icon.png),
|
||||||
|
# manifests\ (nm-*.json), activity.log (a la racine).
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
Add-Type -AssemblyName System.Windows.Forms
|
||||||
|
Add-Type -AssemblyName System.Drawing
|
||||||
|
|
||||||
|
$HostName = 'ch.netaplaid.planification'
|
||||||
|
$ExtIdChromium = 'hlkepgcngeckdbapafkheondiagjgphc'
|
||||||
|
$ExtIdFirefox = '[email protected]'
|
||||||
|
|
||||||
|
$ScriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Path } # paquet\bin
|
||||||
|
$PkgRoot = Split-Path -Parent $ScriptDir # paquet\
|
||||||
|
|
||||||
|
$InstallDir = Join-Path $env:LOCALAPPDATA 'Planification'
|
||||||
|
$InstallBin = Join-Path $InstallDir 'bin'
|
||||||
|
$InstallAssets = Join-Path $InstallDir 'assets'
|
||||||
|
$InstallManifests = Join-Path $InstallDir 'manifests'
|
||||||
|
|
||||||
|
function Get-HostVersion([string]$psFile) {
|
||||||
|
if (Test-Path -LiteralPath $psFile) {
|
||||||
|
$m = Select-String -LiteralPath $psFile -Pattern "HOST_VERSION\s*=\s*'([^']*)'" -ErrorAction SilentlyContinue
|
||||||
|
if ($m) { return $m.Matches[0].Groups[1].Value }
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Stop-Tray {
|
||||||
|
Get-CimInstance Win32_Process -Filter "Name='wscript.exe'" -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.CommandLine -like '*tray.vbs*' } |
|
||||||
|
ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
||||||
|
Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.CommandLine -like '*tray.ps1*' } |
|
||||||
|
ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-Host {
|
||||||
|
Stop-Tray
|
||||||
|
foreach ($d in @($InstallBin, $InstallAssets, $InstallManifests)) { New-Item -ItemType Directory -Path $d -Force | Out-Null }
|
||||||
|
|
||||||
|
# Scripts -> bin\ (ecriture fraiche = pas de Mark of the Web).
|
||||||
|
# v2026.1.7 - on copie AUSSI manage.ps1 dans l'install pour que le gestionnaire
|
||||||
|
# (Reparer / Desinstaller) reste accessible via le Menu Demarrer meme apres
|
||||||
|
# suppression du dossier du paquet.
|
||||||
|
foreach ($name in @('host.ps1', 'tray.ps1', 'manage.ps1')) {
|
||||||
|
$src = Join-Path $ScriptDir $name
|
||||||
|
if (Test-Path -LiteralPath $src) {
|
||||||
|
Get-Content -LiteralPath $src -Raw | Set-Content -LiteralPath (Join-Path $InstallBin $name) -Encoding UTF8
|
||||||
|
Unblock-File -LiteralPath (Join-Path $InstallBin $name) -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$srcVbs = Join-Path $ScriptDir 'tray.vbs'
|
||||||
|
if (Test-Path -LiteralPath $srcVbs) {
|
||||||
|
Get-Content -LiteralPath $srcVbs -Raw | Set-Content -LiteralPath (Join-Path $InstallBin 'tray.vbs') -Encoding ASCII
|
||||||
|
Unblock-File -LiteralPath (Join-Path $InstallBin 'tray.vbs') -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Icone -> assets\ (binaire : Copy-Item).
|
||||||
|
$srcIcon = Join-Path $PkgRoot 'assets\icon.png'
|
||||||
|
if (Test-Path -LiteralPath $srcIcon) { Copy-Item -LiteralPath $srcIcon -Destination (Join-Path $InstallAssets 'icon.png') -Force }
|
||||||
|
|
||||||
|
# Wrapper .bat dans bin\ (host.ps1 est dans le meme dossier -> %~dp0host.ps1).
|
||||||
|
$dstBat = Join-Path $InstallBin 'host.bat'
|
||||||
|
Set-Content -LiteralPath $dstBat -Encoding ASCII -Value @(
|
||||||
|
'@echo off',
|
||||||
|
'powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0host.ps1" %*'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Manifests natifs -> manifests\ (path = bin\host.bat).
|
||||||
|
$manChromium = Join-Path $InstallManifests 'nm-chromium.json'
|
||||||
|
$manFirefox = Join-Path $InstallManifests 'nm-firefox.json'
|
||||||
|
(([ordered]@{ name=$HostName; description='Planification cache sync host'; path=$dstBat; type='stdio'; allowed_origins=@("chrome-extension://$ExtIdChromium/") }) | ConvertTo-Json -Depth 5) | Set-Content -LiteralPath $manChromium -Encoding UTF8
|
||||||
|
(([ordered]@{ name=$HostName; description='Planification cache sync host'; path=$dstBat; type='stdio'; allowed_extensions=@($ExtIdFirefox) }) | ConvertTo-Json -Depth 5) | Set-Content -LiteralPath $manFirefox -Encoding UTF8
|
||||||
|
|
||||||
|
function Reg([string]$base, [string]$manifest) {
|
||||||
|
$key = Join-Path $base $HostName
|
||||||
|
New-Item -Path $key -Force | Out-Null
|
||||||
|
Set-Item -Path $key -Value $manifest
|
||||||
|
}
|
||||||
|
Reg 'HKCU:\Software\Google\Chrome\NativeMessagingHosts' $manChromium
|
||||||
|
Reg 'HKCU:\Software\Microsoft\Edge\NativeMessagingHosts' $manChromium
|
||||||
|
Reg 'HKCU:\Software\Mozilla\NativeMessagingHosts' $manFirefox
|
||||||
|
|
||||||
|
# v2026.1.7 - le tray ne demarre PLUS au login et n'est PLUS lance a l'install.
|
||||||
|
# Il est demarre A LA DEMANDE par host.ps1 quand l'extension se connecte, et
|
||||||
|
# s'auto-ferme apres 2 h sans activite. On retire l'ancien autostart s'il
|
||||||
|
# existe (installs precedentes) pour que "de base = eteint".
|
||||||
|
Remove-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run' -Name 'PlanificationTray' -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# v2026.1.7 - Menu Demarrer : raccourci vers le gestionnaire installe
|
||||||
|
# (Installer / Reparer / Desinstaller), retrouvable sans le dossier du paquet.
|
||||||
|
# 1) lanceur .vbs (fenetre PowerShell masquee) du manage.ps1 installe.
|
||||||
|
$dstGererVbs = Join-Path $InstallBin 'gerer.vbs'
|
||||||
|
Set-Content -LiteralPath $dstGererVbs -Encoding ASCII -Value @(
|
||||||
|
"' Lance le gestionnaire Planification (fenetre PowerShell masquee).",
|
||||||
|
'Set sh = CreateObject("WScript.Shell")',
|
||||||
|
'Set fso = CreateObject("Scripting.FileSystemObject")',
|
||||||
|
'dir = fso.GetParentFolderName(WScript.ScriptFullName)',
|
||||||
|
'sh.Run "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & dir & "\manage.ps1""", 0, False'
|
||||||
|
)
|
||||||
|
# 2) icone .ico (depuis icon.png) pour le raccourci, best-effort.
|
||||||
|
$icoPath = Join-Path $InstallAssets 'icon.ico'
|
||||||
|
try {
|
||||||
|
$pngForIco = Join-Path $InstallAssets 'icon.png'
|
||||||
|
if (Test-Path -LiteralPath $pngForIco) {
|
||||||
|
Add-Type -AssemblyName System.Drawing
|
||||||
|
$bmp = New-Object System.Drawing.Bitmap($pngForIco)
|
||||||
|
$ico = [System.Drawing.Icon]::FromHandle($bmp.GetHicon())
|
||||||
|
$fs = [System.IO.File]::Create($icoPath)
|
||||||
|
$ico.Save($fs); $fs.Close()
|
||||||
|
}
|
||||||
|
} catch { $icoPath = $null }
|
||||||
|
# 3) raccourcis .lnk dans le Menu Demarrer (deux entrees) :
|
||||||
|
# - "gerer" -> manage.ps1 (installer / reparer / desinstaller)
|
||||||
|
# - "demarrer" -> tray.vbs (relance/affiche la sync a la demande)
|
||||||
|
$dstTrayVbs = Join-Path $InstallBin 'tray.vbs'
|
||||||
|
try {
|
||||||
|
$startDir = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs'
|
||||||
|
New-Item -ItemType Directory -Path $startDir -Force | Out-Null
|
||||||
|
# Nettoyage de l'ancien nom de raccourci (installs precedentes).
|
||||||
|
Remove-Item -LiteralPath (Join-Path $startDir 'Planification - synchronisation.lnk') -Force -ErrorAction SilentlyContinue
|
||||||
|
$ws = New-Object -ComObject WScript.Shell
|
||||||
|
function New-Lnk([string]$name, [string]$targetVbs, [string]$desc) {
|
||||||
|
$sc = $ws.CreateShortcut((Join-Path $startDir $name))
|
||||||
|
$sc.TargetPath = 'wscript.exe'
|
||||||
|
$sc.Arguments = '"' + $targetVbs + '"'
|
||||||
|
$sc.WorkingDirectory = $InstallBin
|
||||||
|
$sc.Description = $desc
|
||||||
|
if ($icoPath -and (Test-Path -LiteralPath $icoPath)) { $sc.IconLocation = "$icoPath,0" }
|
||||||
|
$sc.Save()
|
||||||
|
}
|
||||||
|
New-Lnk 'Planification - gerer.lnk' $dstGererVbs 'Gerer la synchronisation Planification (installer / reparer / desinstaller)'
|
||||||
|
New-Lnk 'Planification - demarrer la synchro.lnk' $dstTrayVbs 'Demarrer / afficher la synchronisation Planification'
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Uninstall-Host {
|
||||||
|
Stop-Tray
|
||||||
|
Remove-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run' -Name 'PlanificationTray' -ErrorAction SilentlyContinue
|
||||||
|
foreach ($base in @(
|
||||||
|
'HKCU:\Software\Google\Chrome\NativeMessagingHosts',
|
||||||
|
'HKCU:\Software\Microsoft\Edge\NativeMessagingHosts',
|
||||||
|
'HKCU:\Software\Mozilla\NativeMessagingHosts')) {
|
||||||
|
Remove-Item -Path (Join-Path $base $HostName) -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
if (Test-Path -LiteralPath $InstallDir) { Remove-Item -LiteralPath $InstallDir -Recurse -Force -ErrorAction SilentlyContinue }
|
||||||
|
# v2026.1.7 - retirer les raccourcis du Menu Demarrer (hors InstallDir).
|
||||||
|
$startDir = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs'
|
||||||
|
foreach ($lnk in @('Planification - synchronisation.lnk', 'Planification - gerer.lnk', 'Planification - demarrer la synchro.lnk')) {
|
||||||
|
Remove-Item -LiteralPath (Join-Path $startDir $lnk) -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------- Interface -----------------------------------
|
||||||
|
$available = Get-HostVersion (Join-Path $ScriptDir 'host.ps1')
|
||||||
|
|
||||||
|
$form = New-Object System.Windows.Forms.Form
|
||||||
|
$form.Text = 'Planification - synchronisation'
|
||||||
|
$form.Size = New-Object System.Drawing.Size(460, 320)
|
||||||
|
$form.StartPosition = 'CenterScreen'
|
||||||
|
$form.FormBorderStyle = 'FixedSingle'
|
||||||
|
$form.MaximizeBox = $false
|
||||||
|
try {
|
||||||
|
$ic = Join-Path $PkgRoot 'assets\icon.png'
|
||||||
|
if (Test-Path -LiteralPath $ic) { $form.Icon = [System.Drawing.Icon]::FromHandle((New-Object System.Drawing.Bitmap($ic)).GetHicon()) }
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
$title = New-Object System.Windows.Forms.Label
|
||||||
|
$title.Text = 'Synchronisation du planning entre postes'
|
||||||
|
$title.Font = New-Object System.Drawing.Font('Segoe UI', 11, [System.Drawing.FontStyle]::Bold)
|
||||||
|
$title.AutoSize = $true
|
||||||
|
$title.Location = New-Object System.Drawing.Point(18, 16)
|
||||||
|
$form.Controls.Add($title)
|
||||||
|
|
||||||
|
$status = New-Object System.Windows.Forms.Label
|
||||||
|
$status.AutoSize = $false
|
||||||
|
$status.Size = New-Object System.Drawing.Size(420, 40)
|
||||||
|
$status.Location = New-Object System.Drawing.Point(18, 48)
|
||||||
|
$form.Controls.Add($status)
|
||||||
|
|
||||||
|
function Refresh-Status {
|
||||||
|
$installed = Get-HostVersion (Join-Path $InstallBin 'host.ps1')
|
||||||
|
if ($installed) { $status.Text = "Installe : version $installed`r`nDisponible (ce dossier) : version $available" }
|
||||||
|
else { $status.Text = "Non installe sur ce poste.`r`nDisponible (ce dossier) : version $available" }
|
||||||
|
}
|
||||||
|
Refresh-Status
|
||||||
|
|
||||||
|
$result = New-Object System.Windows.Forms.Label
|
||||||
|
$result.AutoSize = $false
|
||||||
|
$result.Size = New-Object System.Drawing.Size(420, 30)
|
||||||
|
$result.Location = New-Object System.Drawing.Point(18, 248)
|
||||||
|
$result.ForeColor = [System.Drawing.Color]::DarkGreen
|
||||||
|
|
||||||
|
function Add-Button([string]$text, [int]$y, [scriptblock]$action) {
|
||||||
|
$b = New-Object System.Windows.Forms.Button
|
||||||
|
$b.Text = $text
|
||||||
|
$b.Size = New-Object System.Drawing.Size(420, 38)
|
||||||
|
$b.Location = New-Object System.Drawing.Point(18, $y)
|
||||||
|
$b.Font = New-Object System.Drawing.Font('Segoe UI', 10)
|
||||||
|
$b.add_Click($action)
|
||||||
|
$form.Controls.Add($b)
|
||||||
|
}
|
||||||
|
|
||||||
|
Add-Button 'Installer / Mettre a jour' 100 {
|
||||||
|
try { Install-Host; $result.ForeColor=[System.Drawing.Color]::DarkGreen; $result.Text='Installation / mise a jour terminee.'; Refresh-Status }
|
||||||
|
catch { $result.ForeColor=[System.Drawing.Color]::Firebrick; $result.Text=('Erreur : ' + $_.Exception.Message) }
|
||||||
|
}
|
||||||
|
Add-Button 'Reparer (reinstaller proprement)' 144 {
|
||||||
|
try { Install-Host; $result.ForeColor=[System.Drawing.Color]::DarkGreen; $result.Text='Reparation terminee.'; Refresh-Status }
|
||||||
|
catch { $result.ForeColor=[System.Drawing.Color]::Firebrick; $result.Text=('Erreur : ' + $_.Exception.Message) }
|
||||||
|
}
|
||||||
|
Add-Button 'Desinstaller' 188 {
|
||||||
|
$r = [System.Windows.Forms.MessageBox]::Show('Desinstaller le helper de synchronisation ? (le cache du dossier partage n''est pas touche)', 'Confirmer', 'YesNo', 'Question')
|
||||||
|
if ($r -eq 'Yes') {
|
||||||
|
try { Uninstall-Host; $result.ForeColor=[System.Drawing.Color]::DarkGreen; $result.Text='Desinstalle. Pense a retirer l''extension du navigateur.'; Refresh-Status }
|
||||||
|
catch { $result.ForeColor=[System.Drawing.Color]::Firebrick; $result.Text=('Erreur : ' + $_.Exception.Message) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$form.Controls.Add($result)
|
||||||
|
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||||
|
[void]$form.ShowDialog()
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
# Planification - icone barre des taches (best-effort, optionnel) // 2026.1.4 - FRS
|
||||||
|
# Developpe par Quentin (FRS) - DGNSI, Canton de Vaud.
|
||||||
|
# Licence MIT - voir le fichier LICENSE.
|
||||||
|
#
|
||||||
|
# Indique que le helper de sync est installe. Clic / menu -> fenetre du journal
|
||||||
|
# (aspect application : journal colore par niveau, auto-rafraichi, bouton
|
||||||
|
# "Tout afficher"). Lance par tray.vbs (fenetre masquee), au login.
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'SilentlyContinue'
|
||||||
|
|
||||||
|
# Instance unique.
|
||||||
|
$mutex = New-Object System.Threading.Mutex($false, 'Local\PlanificationTraySingleton')
|
||||||
|
if (-not $mutex.WaitOne(0)) { exit }
|
||||||
|
|
||||||
|
Add-Type -AssemblyName System.Windows.Forms
|
||||||
|
Add-Type -AssemblyName System.Drawing
|
||||||
|
|
||||||
|
# tray.ps1 vit dans <install>\bin -> racine = parent ; assets et log a la racine.
|
||||||
|
$root = if ($PSScriptRoot) { Split-Path -Parent $PSScriptRoot } else { Join-Path $env:LOCALAPPDATA 'Planification' }
|
||||||
|
$logFile = Join-Path $root 'activity.log'
|
||||||
|
$iconPng = Join-Path (Join-Path $root 'assets') 'icon.png'
|
||||||
|
|
||||||
|
function Get-TrayIcon {
|
||||||
|
try {
|
||||||
|
if (Test-Path -LiteralPath $iconPng) {
|
||||||
|
$bmp = New-Object System.Drawing.Bitmap($iconPng)
|
||||||
|
return [System.Drawing.Icon]::FromHandle($bmp.GetHicon())
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return [System.Drawing.SystemIcons]::Information
|
||||||
|
}
|
||||||
|
|
||||||
|
$script:logForm = $null
|
||||||
|
$script:logRtb = $null
|
||||||
|
$script:logChk = $null
|
||||||
|
$script:logSig = ''
|
||||||
|
|
||||||
|
$colInfo = [System.Drawing.Color]::FromArgb(40, 70, 120)
|
||||||
|
$colDebug = [System.Drawing.Color]::FromArgb(150, 150, 150)
|
||||||
|
$colErr = [System.Drawing.Color]::FromArgb(200, 40, 40)
|
||||||
|
$colTs = [System.Drawing.Color]::FromArgb(120, 120, 120)
|
||||||
|
|
||||||
|
function Update-LogText {
|
||||||
|
if (-not $script:logRtb -or $script:logRtb.IsDisposed) { return }
|
||||||
|
$lines = @()
|
||||||
|
if (Test-Path -LiteralPath $logFile) {
|
||||||
|
try {
|
||||||
|
$all = Get-Content -LiteralPath $logFile -Encoding UTF8 -ErrorAction SilentlyContinue
|
||||||
|
if ($all) {
|
||||||
|
if ($script:logChk -and -not $script:logChk.Checked) {
|
||||||
|
$all = $all | Where-Object { $_ -notmatch '\[DEBUG\]' }
|
||||||
|
}
|
||||||
|
$lines = @($all | Select-Object -Last 500)
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
$sig = ($lines -join "`n")
|
||||||
|
if ($sig -eq $script:logSig) { return }
|
||||||
|
$script:logSig = $sig
|
||||||
|
|
||||||
|
$rtb = $script:logRtb
|
||||||
|
$rtb.SuspendLayout()
|
||||||
|
$rtb.Clear()
|
||||||
|
if ($lines.Count -eq 0) {
|
||||||
|
$rtb.SelectionColor = $colDebug
|
||||||
|
$rtb.AppendText("(aucune activite de synchronisation pour le moment)")
|
||||||
|
} else {
|
||||||
|
foreach ($line in $lines) {
|
||||||
|
$col = $colInfo
|
||||||
|
if ($line -match '\[ERREUR\]') { $col = $colErr }
|
||||||
|
elseif ($line -match '\[DEBUG\]') { $col = $colDebug }
|
||||||
|
if ($line -match '^(\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2})(.*)$') {
|
||||||
|
$rtb.SelectionStart = $rtb.TextLength; $rtb.SelectionColor = $colTs
|
||||||
|
$rtb.AppendText($Matches[1])
|
||||||
|
$rtb.SelectionStart = $rtb.TextLength; $rtb.SelectionColor = $col
|
||||||
|
$rtb.AppendText($Matches[2] + "`n")
|
||||||
|
} else {
|
||||||
|
$rtb.SelectionStart = $rtb.TextLength; $rtb.SelectionColor = $col
|
||||||
|
$rtb.AppendText($line + "`n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$rtb.ResumeLayout()
|
||||||
|
$rtb.SelectionStart = $rtb.TextLength; $rtb.ScrollToCaret()
|
||||||
|
}
|
||||||
|
|
||||||
|
function Show-LogWindow {
|
||||||
|
if ($script:logForm -and -not $script:logForm.IsDisposed) {
|
||||||
|
$script:logForm.WindowState = 'Normal'; $script:logForm.Activate(); return
|
||||||
|
}
|
||||||
|
$form = New-Object System.Windows.Forms.Form
|
||||||
|
$form.Text = 'Planification - journal de synchronisation'
|
||||||
|
$form.Size = New-Object System.Drawing.Size(760, 540)
|
||||||
|
$form.StartPosition = 'CenterScreen'
|
||||||
|
$form.BackColor = [System.Drawing.Color]::FromArgb(245, 246, 248)
|
||||||
|
try { $form.Icon = Get-TrayIcon } catch {}
|
||||||
|
|
||||||
|
# 1) zone de texte (Fill) ajoutee en premier
|
||||||
|
$rtb = New-Object System.Windows.Forms.RichTextBox
|
||||||
|
$rtb.Dock = 'Fill'
|
||||||
|
$rtb.ReadOnly = $true
|
||||||
|
$rtb.BorderStyle = 'None'
|
||||||
|
$rtb.BackColor = [System.Drawing.Color]::White
|
||||||
|
$rtb.Font = New-Object System.Drawing.Font('Consolas', 9.5)
|
||||||
|
$rtb.DetectUrls = $false
|
||||||
|
$form.Controls.Add($rtb)
|
||||||
|
|
||||||
|
# 2) barre d'outils claire avec le BOUTON bascule "Tout afficher" bien visible
|
||||||
|
$bar = New-Object System.Windows.Forms.Panel
|
||||||
|
$bar.Dock = 'Top'; $bar.Height = 40
|
||||||
|
$bar.BackColor = [System.Drawing.Color]::FromArgb(238, 240, 243)
|
||||||
|
$chk = New-Object System.Windows.Forms.CheckBox
|
||||||
|
$chk.Appearance = 'Button'
|
||||||
|
$chk.Text = 'Tout afficher (mode detaille)'
|
||||||
|
$chk.TextAlign = 'MiddleCenter'
|
||||||
|
$chk.AutoSize = $false
|
||||||
|
$chk.Size = New-Object System.Drawing.Size(200, 28)
|
||||||
|
$chk.Location = New-Object System.Drawing.Point(10, 6)
|
||||||
|
$chk.FlatStyle = 'System'
|
||||||
|
$chk.add_CheckedChanged({ $script:logSig = '~'; Update-LogText })
|
||||||
|
$bar.Controls.Add($chk)
|
||||||
|
$hint = New-Object System.Windows.Forms.Label
|
||||||
|
$hint.Text = 'Par defaut : l''essentiel. Activez pour voir tout le detail technique.'
|
||||||
|
$hint.ForeColor = [System.Drawing.Color]::FromArgb(90, 90, 90)
|
||||||
|
$hint.AutoSize = $true
|
||||||
|
$hint.Location = New-Object System.Drawing.Point(220, 12)
|
||||||
|
$bar.Controls.Add($hint)
|
||||||
|
$form.Controls.Add($bar)
|
||||||
|
|
||||||
|
# 3) bandeau titre (bleu) ajoute en dernier -> tout en haut
|
||||||
|
$hdrBar = New-Object System.Windows.Forms.Panel
|
||||||
|
$hdrBar.Dock = 'Top'; $hdrBar.Height = 32
|
||||||
|
$hdrBar.BackColor = [System.Drawing.Color]::FromArgb(40, 70, 120)
|
||||||
|
$hdr = New-Object System.Windows.Forms.Label
|
||||||
|
$hdr.Text = 'Activite de synchronisation - Planification'
|
||||||
|
$hdr.ForeColor = [System.Drawing.Color]::White
|
||||||
|
$hdr.Font = New-Object System.Drawing.Font('Segoe UI', 10, [System.Drawing.FontStyle]::Bold)
|
||||||
|
$hdr.AutoSize = $true
|
||||||
|
$hdr.Location = New-Object System.Drawing.Point(10, 7)
|
||||||
|
$hdrBar.Controls.Add($hdr)
|
||||||
|
$form.Controls.Add($hdrBar)
|
||||||
|
|
||||||
|
$script:logForm = $form
|
||||||
|
$script:logRtb = $rtb
|
||||||
|
$script:logChk = $chk
|
||||||
|
$script:logSig = ''
|
||||||
|
Update-LogText
|
||||||
|
|
||||||
|
$timer = New-Object System.Windows.Forms.Timer
|
||||||
|
$timer.Interval = 2000
|
||||||
|
$timer.add_Tick({ Update-LogText })
|
||||||
|
$timer.Start()
|
||||||
|
$form.add_FormClosed({ $timer.Stop(); $timer.Dispose() })
|
||||||
|
|
||||||
|
$form.Show(); $form.Activate()
|
||||||
|
}
|
||||||
|
|
||||||
|
$ni = New-Object System.Windows.Forms.NotifyIcon
|
||||||
|
$ni.Icon = Get-TrayIcon
|
||||||
|
$ni.Text = 'Planification - synchronisation active'
|
||||||
|
$ni.Visible = $true
|
||||||
|
|
||||||
|
# v2026.1.8 - "toujours present si actif" : le NotifyIcon disparait quand
|
||||||
|
# explorer.exe redemarre (Windows rediffuse alors le message "TaskbarCreated").
|
||||||
|
# Sans le traiter, l'icone ne revient JAMAIS tant que le tray tourne. On ecoute
|
||||||
|
# ce message via une petite fenetre native et on re-affiche l'icone.
|
||||||
|
try {
|
||||||
|
Add-Type -ReferencedAssemblies System.Windows.Forms -TypeDefinition @'
|
||||||
|
using System;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class PlanifTrayWatcher : NativeWindow {
|
||||||
|
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||||
|
static extern uint RegisterWindowMessage(string lpString);
|
||||||
|
public static readonly uint TaskbarCreated = RegisterWindowMessage("TaskbarCreated");
|
||||||
|
private NotifyIcon _icon;
|
||||||
|
public PlanifTrayWatcher(NotifyIcon icon) {
|
||||||
|
_icon = icon;
|
||||||
|
this.CreateHandle(new CreateParams());
|
||||||
|
}
|
||||||
|
protected override void WndProc(ref Message m) {
|
||||||
|
if (m.Msg == TaskbarCreated && _icon != null) {
|
||||||
|
try { _icon.Visible = false; _icon.Visible = true; } catch {}
|
||||||
|
}
|
||||||
|
base.WndProc(ref m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'@ -ErrorAction Stop
|
||||||
|
$script:trayWatcher = New-Object PlanifTrayWatcher($ni)
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
$menu = New-Object System.Windows.Forms.ContextMenuStrip
|
||||||
|
$miLog = $menu.Items.Add('Voir le journal de synchronisation')
|
||||||
|
$miLog.add_Click({ Show-LogWindow })
|
||||||
|
$miQuit = $menu.Items.Add('Quitter')
|
||||||
|
$miQuit.add_Click({ $ni.Visible = $false; $ni.Dispose(); [System.Windows.Forms.Application]::Exit() })
|
||||||
|
$ni.ContextMenuStrip = $menu
|
||||||
|
$ni.add_MouseClick({ param($s, $e) if ($e.Button -eq [System.Windows.Forms.MouseButtons]::Left) { Show-LogWindow } })
|
||||||
|
|
||||||
|
# v2026.1.7 - auto-extinction apres 2 h SANS activite de synchronisation. On se
|
||||||
|
# base sur la date de derniere ecriture du journal (le host y ecrit a chaque
|
||||||
|
# operation). Le navigateur relancera le host a la prochaine activite, qui
|
||||||
|
# relancera ce tray. -> "de base eteint, s'active a l'usage, se ferme au repos".
|
||||||
|
$IDLE_LIMIT_MIN = 120
|
||||||
|
$idleTimer = New-Object System.Windows.Forms.Timer
|
||||||
|
$idleTimer.Interval = 300000 # verifie toutes les 5 min
|
||||||
|
$idleTimer.add_Tick({
|
||||||
|
try {
|
||||||
|
$age = $null
|
||||||
|
if (Test-Path -LiteralPath $logFile) {
|
||||||
|
$age = ((Get-Date) - (Get-Item -LiteralPath $logFile).LastWriteTime).TotalMinutes
|
||||||
|
}
|
||||||
|
# Pas de journal, ou journal inactif depuis >= 2 h -> on s'eteint.
|
||||||
|
if (($null -eq $age) -or ($age -ge $IDLE_LIMIT_MIN)) {
|
||||||
|
$ni.Visible = $false; $ni.Dispose()
|
||||||
|
[System.Windows.Forms.Application]::Exit()
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
})
|
||||||
|
$idleTimer.Start()
|
||||||
|
|
||||||
|
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||||
|
[System.Windows.Forms.Application]::Run()
|
||||||
|
$idleTimer.Stop()
|
||||||
|
$ni.Dispose()
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
' Lance tray.ps1 en arriere-plan, fenetre PowerShell masquee (pas de console).
|
||||||
|
Set sh = CreateObject("WScript.Shell")
|
||||||
|
Set fso = CreateObject("Scripting.FileSystemObject")
|
||||||
|
dir = fso.GetParentFolderName(WScript.ScriptFullName)
|
||||||
|
sh.Run "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & dir & "\tray.ps1""", 0, False
|
||||||
|
After Width: | Height: | Size: 444 B |
|
Before Width: | Height: | Size: 118 B After Width: | Height: | Size: 118 B |
|
Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 207 B |
@@ -1,37 +1,32 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Planification",
|
"name": "Planification",
|
||||||
"version": "2026.5.21",
|
"version": "2026.5.50",
|
||||||
"description": "Vue claire et rapide du planning des techniciens EasyVista. Regroupe interventions et réservations par tech, affiche horaires, contact, lieu, catégorie et statut en un coup d'œil.",
|
"description": "Vue claire et rapide du planning des techniciens EasyVista. Développé par Quentin — DGNSI, Canton de Vaud.",
|
||||||
"browser_specific_settings": {
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl/El4I9gxcZkp5bnheg4ZBCKxckRLt9xQ5x+i6phEPYlydl6NapWlkbOyycUaeTZcpoEh/tKQnpMpoEc5jY8zCoRTwh/vfejVBZE6ANDG/dc3iix/1Z8OhRI4zMaRYQ+DQTjS5Q6l6wkcwzbQB85HZNhke/k1qIW6RCuBjeJYmES5WVDwzfCNVvaF6uQvdOCpLjrA3+LI6vUBEwaMg1hM42mC3dU0z9p7qzAby/dH3XpZD9HTFXLJL9/1zryN9rXNDpvZwYWnJ0hLTu5tfmURkvgKOwquX1KJy9vupmhUALRtS08Fplx/e3kUH3LD+coS1ExLwoOZ+1ORBUE9JCB8wIDAQAB",
|
||||||
"gecko": {
|
|
||||||
"id": "[email protected]",
|
|
||||||
"strict_min_version": "140.0",
|
|
||||||
"data_collection_permissions": {
|
|
||||||
"required": [
|
|
||||||
"none"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"activeTab",
|
"activeTab",
|
||||||
"scripting",
|
"scripting",
|
||||||
"storage",
|
"storage",
|
||||||
"tabs",
|
"tabs",
|
||||||
"alarms"
|
"alarms",
|
||||||
|
"nativeMessaging"
|
||||||
|
],
|
||||||
|
"optional_permissions": [
|
||||||
|
"cookies"
|
||||||
],
|
],
|
||||||
"host_permissions": [
|
"host_permissions": [
|
||||||
"https://itsma.etat-de-vaud.ch/*",
|
"https://itsma.etat-de-vaud.ch/*",
|
||||||
"https://itsma.vd.ch/*"
|
"https://itsma.vd.ch/*"
|
||||||
],
|
],
|
||||||
|
"optional_host_permissions": [
|
||||||
|
"https://*/*"
|
||||||
|
],
|
||||||
"action": {
|
"action": {
|
||||||
"default_title": "Ouvrir la Planification"
|
"default_title": "Ouvrir la Planification"
|
||||||
},
|
},
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": [
|
"service_worker": "background.js"
|
||||||
"background.js"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "icons/icon16.png",
|
"16": "icons/icon16.png",
|
||||||
@@ -1,4 +1,13 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
<!--
|
||||||
|
Planification — Extension navigateur EasyVista (Canton de Vaud / DGNSI)
|
||||||
|
|
||||||
|
Copyright (c) 2026 Quentin
|
||||||
|
Licensed under the MIT License — see LICENSE file in the project root.
|
||||||
|
|
||||||
|
@author Quentin
|
||||||
|
@repository https://gitea.netaplaid.ch/FroSteel/Planification
|
||||||
|
-->
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
@@ -9,45 +18,55 @@
|
|||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<div class="topbar-left">
|
<div class="topbar-left">
|
||||||
<!-- v4.2.3 : pastille avec initiales de l'utilisateur connecté, avant
|
<!-- v4.2.3 : pastille avec initiales de l'utilisateur connecté, avant
|
||||||
le titre. Clic → popup fixe avec nom complet juste en dessous. -->
|
le titre. Clic → popup fixe avec nom complet juste en dessous.
|
||||||
<button id="user-badge" class="user-badge hidden"
|
v2026.5.34 : TOUJOURS visible d'office avec "?" (état user inconnu)
|
||||||
|
pour garantir l'accès au menu (⊞ Vue / ⚙ Paramètres) même si
|
||||||
|
la détection user échoue ou est en retard.
|
||||||
|
Le script JS mettra à jour le textContent + classes quand le
|
||||||
|
fetch aboutit. En cas d'échec persistant, reste sur "?". -->
|
||||||
|
<button id="user-badge" class="user-badge user-badge-unknown"
|
||||||
type="button" aria-label="Utilisateur connecté"
|
type="button" aria-label="Utilisateur connecté"
|
||||||
title="Utilisateur connecté"></button>
|
title="Utilisateur — cliquer pour accéder aux paramètres">?</button>
|
||||||
<h1 id="app-title">Planification</h1>
|
<h1 id="app-title">Planification</h1>
|
||||||
<div class="date-nav">
|
<!-- : bloc "Aujourd'hui + horloge" encadré, suivi DIRECTEMENT
|
||||||
<button id="nav-prev" class="btn btn-nav" title="Jour précédent" aria-label="Jour précédent">◀</button>
|
du statut d'actualisation (MAJ + ✓), puis le sélecteur de date
|
||||||
<!-- v2026.5.17 : input date custom qui affiche "Vendredi 24.04.2026" -->
|
du planning. -->
|
||||||
<div class="date-custom-wrapper">
|
<div id="today-block" class="today-block">
|
||||||
<div id="date-custom" class="date-custom" role="button" tabindex="0" title="Choisir une date">
|
<button id="nav-today" class="btn btn-today" title="Revenir au jour courant">Aujourd'hui</button>
|
||||||
<span id="date-custom-label"></span>
|
|
||||||
<span class="date-custom-icon">📅</span>
|
|
||||||
</div>
|
|
||||||
<input type="date" id="date-picker" class="date-input-hidden">
|
|
||||||
</div>
|
|
||||||
<button id="nav-next" class="btn btn-nav" title="Jour suivant" aria-label="Jour suivant">▶</button>
|
|
||||||
<button id="nav-today" class="btn btn-today" title="Aujourd'hui">Auj.</button>
|
|
||||||
</div>
|
|
||||||
<span id="capture-info" class="capture-info"></span>
|
|
||||||
<span id="refresh-check" class="refresh-check hidden" title="Mise à jour terminée">✓</span>
|
|
||||||
</div>
|
|
||||||
<!-- v2026.5.16 : date complète du jour au-dessus de l'heure dans la topbar -->
|
|
||||||
<div id="app-clock" class="app-clock" title="Date et heure actuelles">
|
<div id="app-clock" class="app-clock" title="Date et heure actuelles">
|
||||||
<div id="app-clock-date" class="app-clock-date"></div>
|
<div id="app-clock-date" class="app-clock-date"></div>
|
||||||
<div id="app-clock-time" class="app-clock-time"></div>
|
<div id="app-clock-time" class="app-clock-time"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<span id="capture-info" class="capture-info"></span>
|
||||||
|
<span id="refresh-check" class="refresh-check hidden" title="Mise à jour terminée">✓</span>
|
||||||
|
<div class="date-nav">
|
||||||
|
<button id="nav-prev" class="btn btn-nav" title="Jour précédent" aria-label="Jour précédent">◀</button>
|
||||||
|
<div class="date-custom-wrapper">
|
||||||
|
<div id="date-custom" class="date-custom" role="button" tabindex="0" title="Choisir une date du planning">
|
||||||
|
<span id="date-custom-label"></span>
|
||||||
|
</div>
|
||||||
|
<input type="date" id="date-picker" class="date-input-hidden">
|
||||||
|
</div>
|
||||||
|
<button id="nav-next" class="btn btn-nav" title="Jour suivant" aria-label="Jour suivant">▶</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- v5.0.9 : compteur de session EasyVista (visible < 5 min restantes) -->
|
<!-- v5.0.9 : compteur de session EasyVista (visible < 5 min restantes) -->
|
||||||
<div id="app-session" class="app-session hidden"></div>
|
<div id="app-session" class="app-session hidden"></div>
|
||||||
<div class="topbar-right">
|
<div class="topbar-right">
|
||||||
<!-- v4.2.6 : bouton créer une absence pour un ou plusieurs techs -->
|
<!-- v2026.5.46 — bouton principal "Ajouter" : ouvre un menu déroulant
|
||||||
<button id="absence-btn" class="btn btn-action" title="Créer une absence pour un ou plusieurs techniciens">
|
avec Réservation / Absence / Pompier (remplace les boutons séparés). -->
|
||||||
<svg class="btn-action-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
<div id="add-menu-wrap" class="add-menu-wrap">
|
||||||
<rect x="3" y="5" width="18" height="16" rx="2" stroke="currentColor" stroke-width="1.8"/>
|
<button id="add-menu-btn" class="btn btn-action" title="Ajouter : réservation, absence ou pompier" aria-haspopup="true" aria-expanded="false">
|
||||||
<path d="M3 9h18M8 3v4M16 3v4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
|
<span class="btn-action-emoji">➕</span>
|
||||||
<circle cx="12" cy="15" r="4.2" stroke="currentColor" stroke-width="1.8" fill="none"/>
|
<span class="btn-action-label">Ajouter</span>
|
||||||
<line x1="9" y1="15" x2="15" y2="15" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" transform="rotate(-45 12 15)"/>
|
|
||||||
</svg>
|
|
||||||
<span class="btn-action-label">Absence</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
<div id="add-menu" class="add-menu hidden" role="menu">
|
||||||
|
<button id="add-menu-reservation" class="add-menu-item" type="button" role="menuitem"><span class="add-menu-emoji">📅</span> Réservation</button>
|
||||||
|
<button id="add-menu-absence" class="add-menu-item" type="button" role="menuitem"><span class="add-menu-emoji">🗓️</span> Absence</button>
|
||||||
|
<button id="add-menu-pompier" class="add-menu-item" type="button" role="menuitem"><span class="add-menu-emoji">🚒</span> Pompier</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- v4.2.6 : bouton envoyer la planification sur la douchette -->
|
<!-- v4.2.6 : bouton envoyer la planification sur la douchette -->
|
||||||
<button id="douchette-btn" class="btn btn-action" title="Envoyer la planification sur la douchette des techniciens">
|
<button id="douchette-btn" class="btn btn-action" title="Envoyer la planification sur la douchette des techniciens">
|
||||||
<span class="btn-action-emoji">🎯</span>
|
<span class="btn-action-emoji">🎯</span>
|
||||||
@@ -73,7 +92,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Bannière session expirée (non bloquante, apparaît en haut du contenu) -->
|
<!-- v2026.5.46 — bandeaux pleine largeur en haut (sous la topbar). Le
|
||||||
|
chevauchement avec la ligne stats+recherche est réglé par l'empilement
|
||||||
|
sticky (--stack-top), pas en les déplaçant. Un seul visible à la fois
|
||||||
|
(coordinateur _hideAllSessionBanners / _setSessionBanner). -->
|
||||||
<div id="session-expired-banner" class="session-banner hidden">
|
<div id="session-expired-banner" class="session-banner hidden">
|
||||||
<span class="session-banner-icon">⚠</span>
|
<span class="session-banner-icon">⚠</span>
|
||||||
<span class="session-banner-text">
|
<span class="session-banner-text">
|
||||||
@@ -83,8 +105,6 @@
|
|||||||
<button id="session-banner-reconnect" class="btn btn-primary btn-sm">Ouvrir EasyVista</button>
|
<button id="session-banner-reconnect" class="btn btn-primary btn-sm">Ouvrir EasyVista</button>
|
||||||
<button id="session-banner-close" class="btn btn-icon" title="Masquer">×</button>
|
<button id="session-banner-close" class="btn btn-icon" title="Masquer">×</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- v4.2.5 : Bannière EasyVista inaccessible (non bloquante, avec cache) -->
|
|
||||||
<div id="ev-unreachable-banner" class="session-banner ev-banner hidden">
|
<div id="ev-unreachable-banner" class="session-banner ev-banner hidden">
|
||||||
<span class="session-banner-icon">⚠</span>
|
<span class="session-banner-icon">⚠</span>
|
||||||
<span class="session-banner-text">
|
<span class="session-banner-text">
|
||||||
@@ -96,14 +116,44 @@
|
|||||||
<button id="ev-unreachable-banner-close" class="btn btn-icon" title="Masquer">×</button>
|
<button id="ev-unreachable-banner-close" class="btn btn-icon" title="Masquer">×</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Barre de progression (visible uniquement pendant un refresh actif) -->
|
<main id="main">
|
||||||
|
<div id="error-box" class="error-box hidden"></div>
|
||||||
|
<!-- v2026.5.46 — ligne collante (sticky) EN PREMIER : stats + barre de
|
||||||
|
recherche sur la même hauteur, toujours visibles au scroll. En vue
|
||||||
|
horizontale, les deux enfants sont relocalisés dans la sidebar
|
||||||
|
(cf. ELEMENTS_TO_RELOCATE). -->
|
||||||
|
<div id="topline" class="topline">
|
||||||
|
<div id="stats" class="stats hidden"></div>
|
||||||
|
<div id="search-zone" class="search-zone hidden">
|
||||||
|
<div class="search-bar">
|
||||||
|
<span class="search-ico" aria-hidden="true">🔍</span>
|
||||||
|
<input id="ev-search" class="search-input" type="search" autocomplete="off"
|
||||||
|
spellcheck="false" placeholder="Rechercher : réf, personne, matériel, lieu…">
|
||||||
|
<span id="ev-search-spin" class="search-spin hidden" aria-hidden="true"></span>
|
||||||
|
<button id="ev-search-clear" class="search-clear hidden" type="button" title="Effacer" aria-label="Effacer">×</button>
|
||||||
|
<!-- v2026.5.46 — aide sur la syntaxe de recherche -->
|
||||||
|
<button id="ev-search-help" class="search-help" type="button" title="Aide sur la recherche" aria-label="Aide sur la recherche">❕</button>
|
||||||
|
</div>
|
||||||
|
<div id="ev-search-results" class="search-results hidden" role="listbox"></div>
|
||||||
|
</div>
|
||||||
|
<!-- v2026.5.46 — 3e colonne (droite) : bouton « Mes actions » du mode
|
||||||
|
coordinateur, calé complètement à droite de la ligne. -->
|
||||||
|
<div id="topline-coord" class="topline-coord"></div>
|
||||||
|
</div>
|
||||||
|
<!-- v2026.5.46 — barre de progression du rafraîchissement : SOUS la ligne
|
||||||
|
stats+recherche (#topline), sticky juste en dessous (avant : au-dessus). -->
|
||||||
<div id="progress-bar" class="progress-bar hidden">
|
<div id="progress-bar" class="progress-bar hidden">
|
||||||
<div class="progress-bar-fill" id="progress-bar-fill"></div>
|
<div class="progress-bar-fill" id="progress-bar-fill"></div>
|
||||||
<span class="progress-bar-label" id="progress-bar-label"></span>
|
<span class="progress-bar-label" id="progress-bar-label"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- v2026.5.46 — le « Chargement… » vit DANS la zone des cartes
|
||||||
<main id="main">
|
(sous la ligne stats+recherche), jamais en haut de page. -->
|
||||||
<div id="error-box" class="error-box hidden"></div>
|
<div id="loading" class="loading">Chargement…</div>
|
||||||
|
<!-- v2026.5.49 — les messages « session expirée » / « EV inaccessible »
|
||||||
|
CENTRÉS vivent DANS la zone de contenu (sous la barre de recherche, à
|
||||||
|
la place des cartes), pas en haut de #main : sinon ils s'affichaient
|
||||||
|
au-dessus de la ligne stats+recherche. showSessionNeeded/showEvUnreachable
|
||||||
|
vident #cards et affichent le message ici. -->
|
||||||
<div id="session-needed" class="session-needed hidden">
|
<div id="session-needed" class="session-needed hidden">
|
||||||
<h2>Session EasyVista expirée</h2>
|
<h2>Session EasyVista expirée</h2>
|
||||||
<p>Reconnectez-vous à EasyVista pour continuer.</p>
|
<p>Reconnectez-vous à EasyVista pour continuer.</p>
|
||||||
@@ -115,8 +165,6 @@
|
|||||||
<button id="open-ev-btn-2" class="btn btn-primary">Ouvrir EasyVista</button>
|
<button id="open-ev-btn-2" class="btn btn-primary">Ouvrir EasyVista</button>
|
||||||
<button id="retry-btn" class="btn btn-subtle">Réessayer</button>
|
<button id="retry-btn" class="btn btn-subtle">Réessayer</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="loading" class="loading">Chargement…</div>
|
|
||||||
<div id="stats" class="stats hidden"></div>
|
|
||||||
<div id="cards" class="cards"></div>
|
<div id="cards" class="cards"></div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||