v2026.5.40 — Sélection groupe EV + édition domaines + tri équipe + vue horizontale enrichie

This commit is contained in:
Quentin Rouiller
2026-04-27 00:43:00 +02:00
parent 47a0bca998
commit af85473837
6 changed files with 1341 additions and 288 deletions
+133 -14
View File
@@ -587,17 +587,26 @@ function watchReconnectTabForIamLogin(tabId) {
*/
async function fetchCurrentUser(origin, phpsessid) {
const url = `${origin}/index.php?PHPSESSID=${encodeURIComponent(phpsessid)}`;
const resp = await fetch(url, {
// v2026.5.40 : on passe par evFetch() qui ajoute les headers Referer +
// X-Requested-With attendus par EV. Sans ça, /index.php renvoyait une
// page intermédiaire SSO sans le bloc .profile-info → user_null perpétuel.
const resp = await evFetch(url, origin, {
method: "GET",
credentials: "include",
headers: { "Accept": "text/html,*/*" }
});
// v4.2 : cette fonction est lancée en tâche de fond au démarrage. Si la
// session est expirée ou EV inaccessible, on retourne juste null — le
// planning lui-même déclenchera l'écran d'erreur approprié.
if (!resp.ok) return null;
if (!resp.ok) {
LOG.warn("currentUser", "fetch /index.php non-OK", { status: resp.status });
return null;
}
const html = await resp.text();
if (looksLikeLoginPage(html)) return null;
if (looksLikeLoginPage(html)) {
LOG.warn("currentUser", "page de login detectee — session probablement intermediaire SSO");
return null;
}
LOG.info("currentUser", "HTML index.php recu", { taille: html.length, hasProfileInfo: /class=["']profile-info["']/.test(html) });
// v4.2.2 : patterns spécifiques à la structure EasyVista réelle du Canton
// de Vaud (identifiés à partir du HTML de la page d'accueil). L'user est
@@ -669,7 +678,18 @@ async function fetchCurrentUser(origin, phpsessid) {
}
}
if (!name && !login && !service) return null;
if (!name && !login && !service) {
// v2026.5.40 : log diagnostic pour comprendre pourquoi l'extraction echoue
LOG.warn("currentUser", "aucun nom/service/login extrait du HTML",
{
taille: html.length,
hasH5: /class=["']h5["']/.test(html),
hasProfileInfo: /class=["']profile-info["']/.test(html),
hasEvDropdown: /class=["'][^"']*ev-employee-dropdown/.test(html),
snippet: html.substring(0, 200).replace(/\s+/g, " ")
});
return null;
}
return { name, login, service };
}
@@ -925,9 +945,80 @@ async function deletePlanningItem(origin, phpsessid, actionId, kind) {
}
// ============================================================================
// v5.0.0 : Détection de la liste des techniciens depuis la page planning EV
// v2026.5.41 : Détection des GROUPES EasyVista (SI-CSS, SI-EXT, …)
// ============================================================================
/**
* Fetch le HTML de la page planning et extrait la liste des groupes depuis
* `<select id="plan_group_id">`. C'est la source autoritative : ce sont les
* `group_id` exacts qu'EasyVista lui-même utilise pour le widget de
* changement de groupe sur la page planning. Robuste aux ajouts/renommages :
* si le DGNSI ajoute un 3e groupe ou renomme SI-CSS, ça apparaît tout seul.
*
* Retourne { groups: [{id, name}, ...] }. Liste vide si le fetch échoue ou
* si EV renvoie une page de login / redirection JS.
*
* @author Quentin Rouiller
*/
async function detectGroupsFromEV(origin, phpsessid) {
const url = `${origin}/index.php?eventName=HelpDesk_PlanningItem&PHPSESSID=${encodeURIComponent(phpsessid)}`;
console.log("[bg] detectGroupsFromEV → fetch page planning");
console.log("[bg] URL =", url);
const r = await evFetch(url, origin);
if (!r.ok) {
const err = new Error("HTTP " + r.status);
err.kind = classifyHttpStatus(r.status);
err.status = r.status;
throw err;
}
const html = await r.text();
console.log("[bg] page planning taille =", html.length);
if (looksLikeLoginPage(html)) {
const err = new Error("session_expired");
err.kind = "session_expired";
throw err;
}
// Parser le <select id="plan_group_id">…</select> et ses <option>.
// On accepte plan_group_id avant ou après le name=, et on tolère les
// attributs supplémentaires (onchange, class, etc.).
const groups = [];
const selectMatch = html.match(/<select[^>]*\bid=["']plan_group_id["'][^>]*>([\s\S]*?)<\/select>/i);
if (selectMatch) {
const inner = selectMatch[1];
const rxOpt = /<option[^>]*\bvalue=["'](\d+)["'][^>]*>([^<]+)<\/option>/gi;
let m;
while ((m = rxOpt.exec(inner)) !== null) {
const id = m[1];
const name = (m[2] || "").trim();
if (id && name && !groups.some(g => g.id === id)) {
groups.push({ id, name });
}
}
}
console.log("[bg] " + groups.length + " groupes détectés :", groups);
// Fallback : si le <select> est absent (rendu côté client tardif), on
// tente de lire les favoris menuGlobals.tech4 où les noms sont en base64.
// Ces favoris listent les filtres "SI-CSS"/"SI-EXT" — utile si le HTML
// initial ne contient pas encore le <select>.
if (groups.length === 0) {
const rxFav = /"TITLE"\s*:\s*"([^"]+)"[\s\S]{0,400}?"q2_value_selected"\s*:\s*"([A-Za-z0-9+/=]+)"/g;
let mF;
while ((mF = rxFav.exec(html)) !== null) {
const title = mF[1];
// On n'a pas l'ID dans les favoris : on garde au moins le nom pour
// affichage info, mais sans ID ce groupe n'est pas sélectionnable
// pour fetcher le planning. On ignore donc côté retour.
console.log("[bg] fallback favoris : " + title + " (sans ID, ignoré)");
}
}
return { groups };
}
/**
* v5.0.1 : Détection de la liste complète des membres du groupe EasyVista.
*
@@ -942,14 +1033,19 @@ async function deletePlanningItem(origin, phpsessid, actionId, kind) {
*
* Retourne { ids: [{id, name, alreadyInTeam}], groupId }.
*/
async function detectTeamFromEV(origin, phpsessid) {
async function detectTeamFromEV(origin, phpsessid, groupIdArg, supportIdsArg) {
// v5.0.1 : valeurs par défaut (correspondent au groupe actuel).
// À terme elles devraient venir de la config admin.
// v2026.5.41 : on accepte un groupId en argument (ex: switch SI-CSS → SI-EXT).
// Quand on change de groupe, on n'a pas la liste support_ids du nouveau
// groupe → on passe une chaîne vide, le serveur retourne quand même tous
// les membres, juste sans pré-cochage "alreadyInTeam".
const DEFAULT_GROUP_ID = "191";
const DEFAULT_SUPPORT_IDS = "76272,83725,66635,92235,90070,40944,72485,86874";
const groupId = DEFAULT_GROUP_ID;
const supportIds = DEFAULT_SUPPORT_IDS;
const groupId = groupIdArg || DEFAULT_GROUP_ID;
const supportIds = (typeof supportIdsArg === "string")
? supportIdsArg
: (groupId === DEFAULT_GROUP_ID ? DEFAULT_SUPPORT_IDS : "");
console.log("[bg] detectTeamFromEV : group_id =", groupId, "| support_ids =", supportIds);
// Fetch la popup de sélection des intervenants du groupe
@@ -1228,16 +1324,39 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
return;
}
if (msg.type === "detectTeam") {
// v5.0.0 : détecte la liste des IDs de techniciens depuis le HTML
// v5.0.1 : retourne aussi les noms via la popup group_supports
if (msg.type === "detectGroups") {
// v2026.5.41 : détecte la liste des groupes EV (SI-CSS, SI-EXT, …)
// depuis le <select id="plan_group_id"> de la page planning.
const session = await findEasyVistaSession();
if (!session) {
sendResponse({ ok: false, error: "no_session" });
return;
}
try {
const result = await detectTeamFromEV(session.origin, session.phpsessid);
const result = await detectGroupsFromEV(session.origin, session.phpsessid);
sendResponse({ ok: true, groups: result.groups });
} catch (err) {
sendResponse({
ok: false,
error: err.kind || err.message || String(err)
});
}
return;
}
if (msg.type === "detectTeam") {
// v5.0.0 : détecte la liste des IDs de techniciens depuis le HTML
// v5.0.1 : retourne aussi les noms via la popup group_supports
// v2026.5.41 : accepte msg.groupId pour basculer entre SI-CSS / SI-EXT
const session = await findEasyVistaSession();
if (!session) {
sendResponse({ ok: false, error: "no_session" });
return;
}
try {
const result = await detectTeamFromEV(
session.origin, session.phpsessid, msg.groupId, msg.supportIds
);
// result = { ids: [{id,name,alreadyInTeam}, ...], groupId }
sendResponse({ ok: true, members: result.ids, groupId: result.groupId });
} catch (err) {