Compare commits

..

18 Commits

Author SHA1 Message Date
FroSteel a92e3429b2 Version 2026.5.35 — Fix popup épinglé position vue horizontale + stats gauche 2026-04-25 10:00:00 +02:00
FroSteel 1ecc60e160 Version 2026.5.34 — Bouton 📌 restauré + badge user cliquable
- _softUnpinPopup refait, _maybeRetryFetchUser, positionTooltipAnchored unifiée
[code interpolé]
2026-04-24 18:00:00 +02:00
FroSteel a5993c54c9 Version 2026.5.33 — Interactions vue horizontale différenciées (hover / clic)
[code interpolé]
2026-04-24 15:00:00 +02:00
FroSteel b0a8102c29 Version 2026.5.32 — Vue horizontale togglable (VIEW_MODE_KEY, _applyViewMode)
[code interpolé]
2026-04-24 12:00:00 +02:00
FroSteel ecb490c55a Version 2026.5.31 — Sarcelle absence récurrente (REJETÉ par utilisateur)
[code interpolé — version revertée par la suite]
2026-04-24 09:00:00 +02:00
FroSteel 7e497de40e Version 2026.5.30 — Absence récurrente cyan + mode compact 24"
[code interpolé]
2026-04-23 17:00:00 +02:00
FroSteel bbdcb8c7de Version 2026.5.29 — Contraste++ + footer QRO/version
[code interpolé]
2026-04-23 15:00:00 +02:00
FroSteel 5a9e465116 Version 2026.5.28 — Ajustements visuels absences
- Retrait pastille .tech-name-dot, 'Maladie/Accident', popups 520px fixe
[code interpolé]
2026-04-23 13:00:00 +02:00
FroSteel 0511c18b07 Version 2026.5.27 — Classification absences (Maladie/Congé/Pompier)
- Topbar une ligne, fermeture auto popups, contrastes améliorés
- ABSENCE_LABELS, couleurs Maladie/Congé/Pompier, badge + barre gauche
[code interpolé]
2026-04-23 11:00:00 +02:00
FroSteel df623da8f4 Version 2026.5.26 — Badge user inconnu cliquable + retry 60s (max 10 essais)
[code interpolé]
2026-04-23 09:00:00 +02:00
FroSteel 1441b0a7a1 Version 2026.5.25 — Bouton ⚙ Paramètres dans popup user-badge
[code interpolé]
2026-04-22 17:00:00 +02:00
FroSteel 5eae40d38b Version 2026.5.24 — Corrections diverses
[code interpolé]
2026-04-22 15:00:00 +02:00
FroSteel e69482add4 Version 2026.5.23 — Reset bulleState.pinned + iv._reloading
[code interpolé v2026.5.22 → v2026.5.35]
2026-04-22 13:00:00 +02:00
FroSteel a382d8f35f Version 2026.5.22 — Régénération tooltip hover après softUnpin 2026-04-22 11:00:00 +02:00
FroSteel 7824990fba Version 2026.5.21 — Ajustements
[code interpolé]
2026-04-22 09:00:00 +02:00
FroSteel e7c5e281d9 Version 2026.5.20 — Safe area popups (topbar + dock)
[code interpolé]
2026-04-21 17:00:00 +02:00
FroSteel c74d52c40c Version 2026.5.19 — Drag popup épinglé
[code interpolé]
2026-04-21 15:00:00 +02:00
FroSteel 8c76085f03 Version 2026.5.18 — Dock pastilles popups épinglés avec couleur catégorie
[code interpolé]
2026-04-21 13:00:00 +02:00
5 changed files with 3279 additions and 286 deletions
+111 -46
View File
@@ -400,6 +400,67 @@ function originForContext(context) {
: "https://itsma.vd.ch"; : "https://itsma.vd.ch";
} }
/**
* v2026.5.16 : surveille un onglet ouvert pour détecter si le Windows SSO
* a échoué et rediriger vers la bonne page.
*
* Quand la session portail Canton est expirée, EasyVista redirige vers
* https://portail.etat-de-vaud.ch/iamlogin/?spEntityID=...
* (page de login manuel moche). On préfère rediriger vers
* https://portail.etat-de-vaud.ch/iam/accueil/
* qui déclenche le Windows Kerberos SSO automatique.
*
* @param {number} tabId - ID de l'onglet à surveiller
*/
function watchReconnectTabForIamLogin(tabId) {
let redirected = false;
const timeoutMs = 60000; // surveille max 60s
const listener = (updatedTabId, changeInfo, tab) => {
if (updatedTabId !== tabId) return;
if (redirected) return;
const url = changeInfo.url || (tab && tab.url) || "";
if (!url) return;
// Détecter la page de login manuel
// Patterns : portail.etat-de-vaud.ch/iamlogin/ ou www.portail.vd.ch/iamlogin/
if (/\/iamlogin\//i.test(url) && /portail\./i.test(url)) {
redirected = true;
// Choisir le domaine de redirection :
// - si on voit portail.etat-de-vaud.ch → rester sur interne
// - si on voit www.portail.vd.ch → rester sur externe
let targetUrl;
if (/portail\.etat-de-vaud\.ch/i.test(url)) {
targetUrl = "https://portail.etat-de-vaud.ch/iam/accueil/";
} else {
targetUrl = "https://www.portail.vd.ch/iam/accueil/";
}
console.log(`[bg] watchReconnectTab : iamlogin détecté, redirection vers ${targetUrl}`);
chrome.tabs.update(tabId, { url: targetUrl }).catch(e => {
console.warn("[bg] watchReconnectTab : update failed", e);
});
}
};
chrome.tabs.onUpdated.addListener(listener);
// Stop la surveillance après 60s pour ne pas accumuler des listeners morts
setTimeout(() => {
try {
chrome.tabs.onUpdated.removeListener(listener);
} catch (e) {}
}, timeoutMs);
// Si l'onglet est fermé, stop aussi
const closeListener = (closedTabId) => {
if (closedTabId === tabId) {
try { chrome.tabs.onUpdated.removeListener(listener); } catch (e) {}
try { chrome.tabs.onRemoved.removeListener(closeListener); } catch (e) {}
}
};
chrome.tabs.onRemoved.addListener(closeListener);
}
// ============================================================================ // ============================================================================
// v4.2 : récupération de l'utilisateur connecté // v4.2 : récupération de l'utilisateur connecté
// ============================================================================ // ============================================================================
@@ -669,34 +730,19 @@ async function submitDouchette(origin, phpsessid, opts) {
async function deletePlanningItem(origin, phpsessid, actionId, kind) { async function deletePlanningItem(origin, phpsessid, actionId, kind) {
if (!actionId) throw new Error("actionId manquant"); if (!actionId) throw new Error("actionId manquant");
// v5.0.1 : plusieurs function_name à tester dans l'ordre (du plus probable // v5.0.14 : confirmé par capture Network réelle — EasyVista utilise
// au moins probable). Le premier qui renvoie 200 ET non-login est considéré OK. // "Planning_delete_absence" pour TOUS les types d'entrée planning (absences,
const fnNames = kind === "reservation" // réservations, événements, etc.). Réponse XML : <Planning_delete_absence>true</...>
? [ // On met donc ce nom en PREMIER pour tout, et on garde les autres en fallback.
"Planning_delete_reservation", const fnNames = [
"delete_reservation", "Planning_delete_absence", // ← le seul qui marche vraiment côté EV
"fc_delete_reservation", // Fallbacks historiques (au cas où EV change un jour) :
"delete_act_reservation", "Planning_delete_reservation",
"delete_planning_reservation", "delete_absence",
"remove_reservation", "delete_reservation",
// v5.0.2 : réservations sont parfois traitées comme absences côté API "fc_delete_absence",
"Planning_delete_absence", "fc_delete_reservation"
"delete_absence", ];
"fc_delete_absence"
]
: [
// v5.0.2 : élargir la liste, on a essayé 3 sans succès. Les variantes
// plausibles vues dans les API EasyVista :
"Planning_delete_absence", // le plus "officiel"
"delete_absence", // le nom JS dans le onclick
"fc_delete_absence", // pattern fc_*
"delete_act_absence", // parfois "act_" dans les noms
"Planning_delete_holiday", // en anglais
"delete_holiday",
"fc_delete_holiday",
"delete_planning_absence", // variation complète
"remove_absence"
];
let lastErr = null; let lastErr = null;
let lastBody = null; let lastBody = null;
@@ -709,7 +755,11 @@ async function deletePlanningItem(origin, phpsessid, actionId, kind) {
console.log(`[bg] deletePlanningItem → tente function_name=${fn}`, url.substring(0, 180)); console.log(`[bg] deletePlanningItem → tente function_name=${fn}`, url.substring(0, 180));
try { try {
const r = await fetch(url, { method: "GET", credentials: "include" }); // v5.0.13 : utiliser evFetch() au lieu de fetch() brut pour que les
// headers Referer + X-Requested-With soient envoyés — sinon EV renvoie
// un <script> de redirection CSRF qui ne ressemble pas à une erreur et
// notre heuristique le prenait à tort pour un succès.
const r = await evFetch(url, origin, { method: "GET" });
const body = await r.text(); const body = await r.text();
console.log(`[bg] status=${r.status} | body (200 premiers chars) : ${(body || "").substring(0, 200)}`); console.log(`[bg] status=${r.status} | body (200 premiers chars) : ${(body || "").substring(0, 200)}`);
@@ -724,24 +774,34 @@ async function deletePlanningItem(origin, phpsessid, actionId, kind) {
throw new Error("session_expired"); throw new Error("session_expired");
} }
// v5.0.1 : heuristique pour détecter si la suppression a marché. // v5.0.14 : détection explicite du succès XML observé dans les captures
// EasyVista renvoie typiquement : // réseau : <Planning_delete_absence>true</Planning_delete_absence>
// - une chaine vide ou "ok" ou "1" si succès const trimmed = (body || "").trim();
// - un message d'erreur / html d'erreur si function_name inconnu const lower = trimmed.toLowerCase();
// On considère que tout ce qui n'est pas un message d'erreur évident
// est un succès. Si plusieurs fn renvoient 200, on prend le premier. // Succès explicite : réponse XML du type <X>true</X>
const trimmed = (body || "").trim().toLowerCase(); if (/^<\w+>true<\/\w+>\s*$/i.test(trimmed)) {
const looksLikeError = trimmed.includes("error") console.log(`[bg] → SUCCÈS confirmé par XML <...>true</...> avec function_name=${fn}`);
|| trimmed.includes("erreur") return { status: r.status, functionName: fn, body: trimmed };
|| trimmed.includes("unknown function")
|| trimmed.includes("fonction inconnue")
|| trimmed.includes("<html");
if (!looksLikeError) {
console.log(`[bg] → suppression OK avec function_name=${fn}`);
return { status: r.status, functionName: fn, body: body.substring(0, 200) };
} }
console.log(`[bg] → réponse ressemble à une erreur, on tente le prochain nom`);
lastBody = body; // Détection d'échec : <X>false</X>, erreurs, html, redirect, etc.
const looksLikeError = /^<\w+>false<\/\w+>\s*$/i.test(trimmed)
|| lower.includes("error")
|| lower.includes("erreur")
|| lower.includes("unknown function")
|| lower.includes("fonction inconnue")
|| lower.includes("<html")
|| lower.includes("window.location.href");
if (looksLikeError) {
console.log(`[bg] → réponse ressemble à une erreur, on tente le prochain nom`);
lastBody = body;
continue;
}
// Pas d'erreur évidente mais pas de succès explicite non plus
// (ex: réponse vide ou "1" ou "ok"). On considère comme succès.
console.log(`[bg] → suppression probablement OK (body neutre) avec function_name=${fn}`);
return { status: r.status, functionName: fn, body: trimmed.substring(0, 200) };
} catch (err) { } catch (err) {
if (err.message === "session_expired") throw err; if (err.message === "session_expired") throw err;
console.warn(`[bg] erreur avec ${fn}:`, err); console.warn(`[bg] erreur avec ${fn}:`, err);
@@ -1124,6 +1184,11 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
url: `${origin}/`, // racine → EV redirige vers SSO si besoin url: `${origin}/`, // racine → EV redirige vers SSO si besoin
active: true active: true
}); });
// v2026.5.16 : surveiller cet onglet — si on tombe sur la page de
// login manuel portail.etat-de-vaud.ch/iamlogin/, rediriger vers
// portail.etat-de-vaud.ch/iam/accueil/ qui déclenche le Windows
// SSO Kerberos automatiquement.
watchReconnectTabForIamLogin(tab.id);
sendResponse({ ok: true, tabId: tab.id, origin }); sendResponse({ ok: true, tabId: tab.id, origin });
} catch (err) { } catch (err) {
sendResponse({ ok: false, error: err.message || String(err) }); sendResponse({ ok: false, error: err.message || String(err) });
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "Planification", "name": "Planification",
"version": "2026.5.17", "version": "2026.5.35",
"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. Regroupe interventions et réservations par tech, affiche horaires, contact, lieu, catégorie et statut en un coup d'œil.",
"browser_specific_settings": { "browser_specific_settings": {
"gecko": { "gecko": {
+1219 -17
View File
File diff suppressed because it is too large Load Diff
+13 -5
View File
@@ -9,10 +9,15 @@
<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"> <div class="date-nav">
<button id="nav-prev" class="btn btn-nav" title="Jour précédent" aria-label="Jour précédent"></button> <button id="nav-prev" class="btn btn-nav" title="Jour précédent" aria-label="Jour précédent"></button>
@@ -30,8 +35,11 @@
<span id="capture-info" class="capture-info"></span> <span id="capture-info" class="capture-info"></span>
<span id="refresh-check" class="refresh-check hidden" title="Mise à jour terminée"></span> <span id="refresh-check" class="refresh-check hidden" title="Mise à jour terminée"></span>
</div> </div>
<!-- v5.0.0 : horloge au milieu, format HH:MM, mise à jour toutes les min --> <!-- v2026.5.16 : date complète du jour au-dessus de l'heure dans la topbar -->
<div id="app-clock" class="app-clock" title="Heure actuelle"></div> <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-time" class="app-clock-time"></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">
+1935 -217
View File
File diff suppressed because it is too large Load Diff