/**
* Espace DSI (`PTA.modules.dsi`), écran dsi.php — V7.11.
*
* Deux blocs indépendants dans une seule page :
* - les COMPTES D'ACCÈS : création, régénération de jeton, activation,
* suppression, et génération en lot pour les identités qui n'ont pas encore
* de compte ;
* - le JOURNAL D'ACTIVITÉ : recherche filtrée et paginée, statistiques de tête,
* purge des entrées anciennes.
*
* Le premier bloc est rendu par `comptes-acces.js`, partagé avec l'onglet
* Administration : ce fichier ne lui apporte que ce qui est propre à la DSI —
* les rôles privilégiés et leur champ « Nom du titulaire », les badges de mot de
* passe (V7.12), l'alerte du jeton d'installation et le bouton qui filtre le
* journal sur un compte. Le second bloc, le journal, n'existe QUE sur cet écran
* et vit donc entièrement ici.
*
* RÈGLE CENTRALE DE L'ÉCRAN : un jeton n'existe en clair que dans la réponse qui
* vient de le produire. Il est donc affiché dans un panneau dédié, qui reste
* ouvert tant que la DSI ne l'a pas refermé, et n'est jamais rechargé depuis le
* serveur — la base n'en garde qu'une empreinte. Toute la mise en garde de
* l'interface découle de là.
*/
(() => {
'use strict';
const PTA = window.PTA;
// core.js ou comptes-acces.js absent : la page reste lisible, simplement non
// interactive.
if (!PTA?.shared?.ecranComptesAcces) return;
const { apiJson, postJson, showMessageIn, formatDateTime, escapeHtml } = PTA.utils;
const el = (id) => document.getElementById(id);
const message = (id, texte, type = 'info') => showMessageIn(id, texte, type);
/** Page courante du journal, seul état conservé par le module. */
let pageJournal = 1;
const LIBELLES_ROLE = {
DSI: 'DSI',
SERVICE_ENFANCE: 'Service Enfance',
RESPONSABLE_STRUCTURE: 'Responsable de structure',
AGENT: 'Agent',
};
const LIBELLES_CATEGORIE = {
CONNEXION: 'Connexion',
ECRITURE: 'Écriture',
ACCES_REFUSE: 'Refus d’accès',
ADMINISTRATION: 'Administration',
SYSTEME: 'Système',
};
// ----------------------------------------------------------------- comptes
/**
* Badges d'état propres à la DSI, ajoutés à « Actif / Désactivé ».
*
* Second facteur (V7.12) : seuls les comptes Service Enfance et DSI en
* portent un. « À définir » n'est pas une anomalie — c'est l'état normal
* d'un compte neuf ou fraîchement réinitialisé.
*/
function etatsCompte(compte) {
const etats = [];
if (compte.jeton_installation) etats.push('Jeton d’installation');
if (compte.mot_de_passe_requis) {
etats.push(compte.mot_de_passe_defini
? 'Mot de passe défini'
: 'Mot de passe à définir');
}
return etats;
}
/**
* Boutons propres à la DSI en fin de ligne : effacement du mot de passe (la
* DSI peut l'effacer, jamais en poser un) et accès au journal du compte.
*/
function actionsCompte(compte) {
return [
compte.mot_de_passe_requis && compte.mot_de_passe_defini
? ''
: '',
'',
].join('');
}
/**
* Traite les deux boutons que la fabrique ne connaît pas.
* @returns {boolean} Vrai s'il faut recharger la liste des comptes derrière.
*/
async function actionCompte(evenement, { bouton, compteId, titulaire }) {
if (bouton.classList.contains('compte-reset-password')) {
if (!window.confirm(`Effacer le mot de passe de ${titulaire} ? Son jeton reste valable, et il en choisira un nouveau à sa prochaine connexion.`)) return false;
const donnees = await postJson('api/dsi_compte_mot_de_passe.php', { compte_id: compteId });
message('dsi-accounts-message', donnees.message, 'success');
return true;
}
if (bouton.classList.contains('compte-journal')) {
// Filtre le journal sur ce compte, sans quitter la page ni toucher
// à la liste des comptes.
el('dsi-journal-search').value = '';
pageJournal = 1;
await chargerJournal({ id_compte: compteId });
el('dsi-journal-body')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
return false;
}
const ecranComptes = PTA.shared.ecranComptesAcces({
prefixe: 'dsi',
endpoints: {
liste: 'api/dsi_comptes.php',
creer: 'api/dsi_compte_creer.php',
jeton: 'api/dsi_compte_jeton.php',
statut: 'api/dsi_compte_statut.php',
manquants: 'api/dsi_comptes_manquants.php',
},
// Seul l'espace DSI crée des comptes sans identité en base : eux seuls
// ont besoin du champ libre « Nom du titulaire », et de ces deux aides.
avecLibelle: true,
aides: {
SERVICE_ENFANCE: 'Compte d’administration métier : accès à tous les lieux, à tous les agents et à la validation des plannings. Il n’est rattaché à aucun agent en base — indiquez simplement le nom de son titulaire. Son titulaire choisira un mot de passe à sa première connexion, en plus du jeton.',
DSI: 'Compte d’administration technique : comptes d’accès et journal uniquement, aucun accès aux plannings. Prévoyez-en au moins deux, pour ne pas dépendre d’un seul jeton. Son titulaire choisira un mot de passe à sa première connexion, en plus du jeton.',
},
rappelJetons: ' Les comptes Service Enfance et DSI choisiront en plus un mot de passe à leur première connexion.',
etatsSupplementaires: etatsCompte,
actionsSupplementaires: actionsCompte,
actionPersonnalisee: actionCompte,
// Le jeton d'installation est publié en clair dans la migration SQL :
// l'alerte reste affichée tant qu'il n'a pas été régénéré.
apresChargement: (donnees) => {
const alerte = el('dsi-installation-warning');
if (alerte) alerte.hidden = !donnees.jeton_installation_actif;
},
});
// ----------------------------------------------------------------- journal
/** Une ligne du journal. */
function ligneJournal(entree) {
const detail = [
entree.description ? escapeHtml(entree.description) : '',
entree.cible_table
? `
${escapeHtml(entree.cible_table)}${entree.cible_id ? ` #${Number(entree.cible_id)}` : ''}`
: '',
entree.contexte
? `
${escapeHtml(JSON.stringify(entree.contexte))}`
: '',
].join('');
const classeResultat = entree.resultat === 'SUCCES' ? '' : ` dsi-journal-${entree.resultat.toLowerCase()}`;
return `
${escapeHtml(entree.action)}