Table of Contents
- Architecture
- Structure du projet
- Composants principaux
- Flux principal
- Stack technique
- Fonctions clés (viewer.js)
- Fonctions clés (background.js)
- Helpers de lecture de config (v2026.5.41)
- Configuration persistée
- Variables CSS dynamiques (v2026.5.44)
- Helpers d'apparence (v2026.5.44)
- Helpers de verdict ghost (v2026.5.44 — refonte)
- Refresh séquentiel + abort (v2026.5.44)
Architecture
Structure du projet
Planification/
├── .gitignore
├── build.sh # Génère dist/chromium/, dist/firefox/, .zip, .xpi, met à jour firefox-updates.json
├── firefox-updates.json # Manifest auto-update Firefox (servi via update_url)
├── CHANGELOG.md
├── LICENSE # MIT
├── README.md
└── src/ # Sources de l'extension (chargées par le navigateur)
├── manifest.json # Manifest V3 (Chrome) + browser_specific_settings (Firefox)
├── background.js # Service worker (~1 600 lignes)
├── viewer.html # Interface principale
├── viewer.js # Logique UI (~10 700 lignes)
├── viewer.css # Styles + thèmes clair/sombre (~4 800 lignes)
└── icons/ # icon16, icon48, icon128
Composants principaux
background.js — Service worker (Manifest V3)
Worker de fond responsable de :
- Récupération de la session EasyVista (PHPSESSID)
- Fetch du XML
calendar_block(planning du jour pour les techs configurés) - Fetch des fiches détaillées (HTML)
- Fetch du timeline JSON (texte action complet)
- Détection contexte réseau (interne / externe via SSO)
- Détection automatique des groupes EasyVista disponibles (depuis v2026.5.40) en parsant le
<select id="plan_group_id">de la page Planning EV - Détection automatique des membres d'un groupe (paramétrable par
groupIddepuis v2026.5.40) - Gestion du cache local (
chrome.storage.local) - Nettoyage des vieux caches (cron)
viewer.js — Interface principale
- Parsing du XML calendar_block
- Construction des cards / rows par tech
- Timeline visuelle avec segments (depuis v2026.5.40 : barre couleur catégorie + référence + ville en vue horizontale)
- Tooltips détaillés (épinglables, draggables)
- 2 modes d'affichage (classique / horizontale)
- Panel admin (Apparence / Équipe / EasyVista / Diagnostics / À propos) — section "Statuts" retirée en v2026.5.40
- Onglet Équipe : sélecteur de groupe EV (SI-CSS, SI-EXT, …) avec rafraîchissement auto de la liste de membres au changement de groupe (v2026.5.40)
- Onglet EasyVista : édition manuelle des deux domaines interne / externe (v2026.5.40)
- Module LOG unifié (debug toggle)
- Handlers globaux d'erreur
sendMessageavec timeout
viewer.css
- Variables CSS (
:root) pour thème clair / sombre - Layout grille (vue classique) + flex (vue horizontale)
- Animations transitions douces
- Media queries (compact 24" / breakpoint étroit)
Flux principal
┌────────────────┐ clic icône ┌─────────────────────┐
│ Navigateur │ ───────────────> │ background.js │
│ (Chrome / │ │ (service worker) │
│ Firefox) │ <─── viewer.html │ │
└────────────────┘ └──────────┬──────────┘
│ │
│ chrome.runtime.sendMessage │ findEasyVistaSession()
│ {type: "fetchPlanning"} │ ↓
▼ ▼
┌────────────────────────┐ ┌────────────────────┐
│ viewer.js (UI) │ │ EasyVista server │
│ ─ init() │ │ itsma.vd.ch / │
│ ─ loadForDate(iso) │ ◄── XML ── │ etat-de-vaud.ch │
│ ─ parseXML │ fiches │ │
│ ─ buildCard() x N │ │ planning_xhr.php │
│ ─ buildTooltipHTML │ │ index.php (fiches) │
│ ─ renderFromData │ │ etc. │
│ ─ writeCache │ └────────────────────┘
└──────────┬─────────────┘
│
▼
┌────────────────────────────────────────────────┐
│ chrome.storage.local │
│ ─ admin_config (groupe, équipe, domaines, …) │
│ ─ planning_cache_YYYY-MM-DD (par jour) │
│ localStorage │
│ ─ view_mode (classic / horizontal) │
└────────────────────────────────────────────────┘
Étapes clés (de init() à l'affichage) :
init()→ charge le thème + la config admin + les bornes de la journée + l'équipe.refreshSessionAndLoad()→ cherche la session EV viabackground.js.loadForDate(isoDate)→ essaye d'abord le cache local (readCache), puis fetche le XML calendar_block viabackground.fetchPlanningXml.- Pour chaque intervention présente dans le XML : on en
buildCard()immédiatement avec les attributs déjà enrichis (contact/lieu/catégorie). Pour les statuts détaillés, on lance desfetchFicheséquentiels en arrière-plan qui mettent à jour le DOM au fur et à mesure. - À la fin du fetch complet :
writeCache()sauvegarde tout danschrome.storage.local["planning_cache_YYYY-MM-DD"].
Stack technique
| Élément | Détail |
|---|---|
| Manifest | V3 |
| Browser support | Chrome / Edge / Firefox 140+ |
| Pas de framework | JS vanilla |
| Pas de build step navigateur | les fichiers sont chargés tels quels (un script build.sh empaquette les sources en .zip / .xpi) |
| Stockage | chrome.storage.local (cache + admin_config) + localStorage (préfs UI) |
| Réseau | fetch avec credentials (cookies EV) |
| Logs | console (préfixé + timestamp + version) |
| Auto-update Firefox | firefox-updates.json à la racine du repo, .xpi signé Mozilla AMO |
Fonctions clés (viewer.js)
| Fonction | Introduite | Rôle |
|---|---|---|
loadForDate |
v1.0.0 | Fetch + parse planning pour une date |
buildCard |
v1.0.0 | Construit la card HTML d'un technicien |
buildTooltipHTML |
v1.0.0 | HTML détaillé d'une intervention |
pinTooltip |
v4.1.3 | Épingle un tooltip |
bindTimelinePopover |
v4.2.3 | Lie segments timeline → popovers |
_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) |
_applyViewMode |
v2026.5.32 | Toggle vue classique/horizontale |
positionTooltipAnchored |
v2026.5.34 | Positionnement tooltip (4 candidats) |
LOG.error/warn/info |
v2026.5.38 | Logger unifié |
_applyTextZoom |
v2026.5.39 | Zoom global du texte (slider Apparence) |
renderAdminSectionTeam |
v5.0.0 | Onglet admin Équipe (sélecteur de groupe EV depuis v2026.5.40, tri inclus + alpha) |
renderAdminSectionEV |
v5.0.0 | Onglet admin EasyVista (édition domaines depuis v2026.5.40) |
paintGroupOptions / refreshTeamForGroup |
v2026.5.40 | Helpers du sélecteur de groupe (peuplement + refresh auto de la liste équipe) |
_normalizeOrigin |
v2026.5.40 | Normalisation des URLs des domaines EV éditées dans l'admin |
Fonctions clés (background.js)
| Fonction | Rôle |
|---|---|
findEasyVistaSession |
Trouve l'onglet EV ouvert + extrait PHPSESSID |
fetchPlanningXml |
Fetch XML calendar_block du planning |
fetchFicheHtml |
Fetch HTML d'une fiche EV (avec retry SSO) |
fetchTimelineApi |
Fetch JSON timeline d'une fiche (texte action complet) |
fetchCurrentUser |
Identifie l'user EV connecté |
detectNetworkContext |
Interne (etat-de-vaud.ch) ou externe (vd.ch) |
detectGroupsFromEV (v2026.5.40) |
Parse le <select id="plan_group_id"> de la page Planning → liste [{id, name}, …] des groupes disponibles |
detectTeamFromEV (paramétrable depuis v2026.5.40) |
Liste les membres d'un groupe ; accepte un groupId en argument pour basculer entre SI-CSS / SI-EXT |
evFetch |
Wrapper fetch avec headers EV (Referer, X-Requested-With) |
Helpers de lecture de config (v2026.5.41)
Côté background.js, tous les hardcodes runtime ont été remplacés par des helpers qui lisent admin_config à chaque appel :
| Helper | Retourne | Fallback |
|---|---|---|
getAdminConfig() |
objet cfg complet |
{} |
getEvOrigins() |
tableau d'origines à surveiller | DEFAULT_EV_ORIGINS (2 défauts hardcodés) |
getGroupId() |
string group_id | DEFAULT_GROUP_ID = "191" (SI-CSS) |
getSupportIds() |
CSV des clés de cfg.team |
"" (lève no_team_configured côté fetchPlanningXml) |
getDayBounds() |
{ start, end } heures |
{ start: 8, end: 18 } |
fetchPlanningXml() lève Error("no_team_configured") quand getSupportIds() retourne une chaîne vide → le viewer affiche un message invitant à configurer Paramètres → Équipe.
Configuration persistée
chrome.storage.local["admin_config"] :
| Clé | Type | Rôle | Introduite |
|---|---|---|---|
team |
{id: name} |
Liste des techniciens inclus | v5.0.0 |
recurringAbsences |
{id: [days]} |
Jours de la semaine d'absence récurrente | v5.0.0 |
groupId |
string | ID du groupe EV sélectionné | v5.0.0 |
groupName |
string | Nom du groupe sélectionné (cache d'affichage) | v2026.5.40 |
detectedGroups |
[{id, name}] |
Cache de la dernière détection des groupes | v2026.5.40 |
evOrigins |
[interne, externe] |
Domaines EasyVista éditables dans l'admin | v2026.5.40 |
theme |
auto/light/dark |
Préférence thème | v2026.5.39 |
textZoom |
-2..+2 |
Niveau de zoom du texte | v2026.5.39 |
cacheDays |
number | Durée de rétention du cache (défaut 7) | v2026.5.39 |
dayStart / dayEnd |
number | Plage horaire de la timeline (défaut 8-18) | v2026.5.39 |
closedStatus / resolvedStatus / cancelledStatus / suspendedStatus |
[string] |
Statuts EV reconnus pour les verdicts (matching insensible casse/accents/conjugaisons) | v2026.5.44 |
topbarColor |
string #rrggbb |
Couleur de fond de la topbar (vide = défaut thème) | v2026.5.44 |
appFont |
string | Identifiant de police (cf. APP_FONT_OPTIONS) appliquée à toute la page |
v2026.5.44 |
localStorage["view_mode"] : classic ou horizontal (depuis v2026.5.32).
Variables CSS dynamiques (v2026.5.44)
Posées sur :root ou html par JS, lues partout dans le CSS :
| Variable | Source | Rôle |
|---|---|---|
--topbar-bg |
_applyTopbarColor(color) depuis cfg.topbarColor |
Fond de la topbar (et de la sidebar en horizontal). Vide = --bg-elevated du thème |
--topbar-text |
_applyTopbarColor calcule via _readableTextOn(color) (luminance pondérée 0.299·r + 0.587·g + 0.114·b) |
Couleur du texte topbar — blanc si fond foncé, foncé si fond clair |
--app-font |
_applyAppFont(val) depuis cfg.appFont (mappe sur _appFontStack) |
Stack font-family appliquée à body (cascade vers toute la page) |
--topbar-height |
_initTopbarHeightVar() via ResizeObserver sur header.topbar |
Hauteur réelle de la topbar — utilisée par les éléments sticky en dessous (.session-banner, .progress-bar, .session-slide-alert) pour leur top: |
--text-scale / --zoom-factor |
_applyTextZoom(level) depuis cfg.textZoom |
Multiplicateurs du zoom texte (existants depuis v2026.5.39, listés ici pour complétude) |
Classe html.has-topbar-color (toggle JS dans _applyTopbarColor) active les règles color-mix translucides sur les boutons / today-block / date-custom de la topbar et de la sidebar horizontale, pour qu'ils restent lisibles sur n'importe quel fond.
Helpers d'apparence (v2026.5.44)
Fonction (viewer.js) |
Rôle |
|---|---|
APP_FONT_OPTIONS |
Tableau des 28 stacks de police proposés dans le panel admin |
_appFontStack(val) |
Retourne le stack font-family correspondant à un identifiant |
_hexToLuminance(hex) |
Calcule la luminance perçue (0..1) d'une couleur hex (#rgb ou #rrggbb) |
_readableTextOn(hex) |
Retourne #1a1f2b ou #ffffff selon contraste optimal |
_applyTopbarColor(color) |
Pose --topbar-bg, --topbar-text et toggle html.has-topbar-color |
_applyAppFont(val) |
Pose --app-font sur :root |
_initTopbarStyleFromConfig() |
Lit cfg.topbarColor + cfg.appFont (avec fallback cfg.topbarFont) au boot et applique |
_initTopbarHeightVar() |
Démarre le ResizeObserver qui maintient --topbar-height à jour |
Helpers de verdict ghost (v2026.5.44 — refonte)
Dans analyzeOneDisappearedIv() :
Verdict (iv._disappearStatus) |
Décision | Rendu UI |
|---|---|---|
terminated-clos |
KEEP | Carte verte + ✓✓ |
terminated-pending |
KEEP | ✓ gris « Fait » |
terminated-suspended |
KEEP | ✓ jaune « Suspendu » + barre verte (horizontal) |
closed / terminated |
KEEP | Carte verte + ✓✓ |
cancelled / cancelled-reservation / cancelled-absence |
REMOVE | (retiré silencieusement en PROD ; loggé en mode diagnostic) |
error |
KEEP (silent) | Inchangé |
fiche_tronquee |
KEEP (silent) | Inchangé |
Helpers de matching (_normalizeStatus, _stemStatus, _statusMatchesAny) : permettent à l'utilisateur de saisir ses statuts EV en français normal (« Clôturé », « clôture », « Clos », « clôs », « Suspendu : Attente info »…) avec matching tolérant aux variations casse/accents/conjugaisons via lowercase + suppression diacritiques + strip suffixes verbaux courants.
Refresh séquentiel + abort (v2026.5.44)
background.js :
const _evFetchControllers = new Set(); // registre global des AbortController en vol
function _abortAllEvFetches() { // appelé sur message "abortAllFetches"
for (const c of _evFetchControllers) {
try { c.abort(); } catch (e) { /* ignore */ }
}
_evFetchControllers.clear();
}
async function evFetch(url, origin, opts = {}) {
let controller = null;
if (!opts.signal) {
controller = new AbortController();
_evFetchControllers.add(controller);
}
// ... fetch avec signal
try { return await fetch(url, fetchOpts); }
finally { if (controller) _evFetchControllers.delete(controller); }
}
viewer.js envoie {type: "abortAllFetches"} au service worker quand l'utilisateur clique « ✕ Arrêter ». Tous les fetch EV en vol sont avortés instantanément (avant la complétion). Le state state.refreshAborted interrompt aussi la boucle séquentielle au prochain await.