emilsoftware-utilities 2.0.0-dev.32 → 2.0.0-dev.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/accessi-module/Console/console.css +10 -0
  2. package/dist/accessi-module/Console/console.js +102 -19
  3. package/dist/accessi-module/Console/console.js.map +1 -1
  4. package/dist/accessi-module/Console/generated/accessiApi.d.ts +2 -2
  5. package/dist/accessi-module/Console/generated/accessiApi.js.map +1 -1
  6. package/dist/accessi-module/Console/generated/model/createMenuGroupRequest.d.ts +0 -5
  7. package/dist/accessi-module/Console/generated/model/createMenuGroupRequest.js.map +1 -1
  8. package/dist/accessi-module/Console/generated/model/groupWithMenusEntity.d.ts +0 -2
  9. package/dist/accessi-module/Console/generated/model/groupWithMenusEntity.js.map +1 -1
  10. package/dist/accessi-module/Console/generated/model/menuEntity.d.ts +0 -2
  11. package/dist/accessi-module/Console/generated/model/menuEntity.js.map +1 -1
  12. package/dist/accessi-module/Console/generated/model/publicRegisterRequest.d.ts +0 -2
  13. package/dist/accessi-module/Console/generated/model/publicRegisterRequest.js.map +1 -1
  14. package/dist/accessi-module/Console/generated/model/registerRequest.d.ts +0 -2
  15. package/dist/accessi-module/Console/generated/model/registerRequest.js.map +1 -1
  16. package/dist/accessi-module/Console/generated/model/updateMenuGroupRequest.d.ts +0 -5
  17. package/dist/accessi-module/Console/generated/model/updateMenuGroupRequest.js.map +1 -1
  18. package/dist/accessi-module/Console/generated/model/userDto.d.ts +0 -4
  19. package/dist/accessi-module/Console/generated/model/userDto.js.map +1 -1
  20. package/dist/accessi-module/Console/index.html +6 -0
  21. package/dist/accessi-module/Console/wiki.js +7 -4
  22. package/dist/accessi-module/Console/wiki.js.map +1 -1
  23. package/dist/accessi-module/Controllers/ConfiguratorController.d.ts +1 -3
  24. package/dist/accessi-module/Controllers/ConfiguratorController.js +4 -17
  25. package/dist/accessi-module/Controllers/ConfiguratorController.js.map +1 -1
  26. package/dist/accessi-module/Controllers/FiltriController.js +4 -4
  27. package/dist/accessi-module/Controllers/FiltriController.js.map +1 -1
  28. package/dist/accessi-module/Controllers/PermissionController.js +14 -14
  29. package/dist/accessi-module/Controllers/PermissionController.js.map +1 -1
  30. package/dist/accessi-module/Controllers/ServiceTokenController.js +1 -1
  31. package/dist/accessi-module/Controllers/ServiceTokenController.js.map +1 -1
  32. package/dist/accessi-module/Controllers/UserController.js +8 -8
  33. package/dist/accessi-module/Controllers/UserController.js.map +1 -1
  34. package/dist/accessi-module/Dtos/CatalogDtos.d.ts +0 -2
  35. package/dist/accessi-module/Dtos/CatalogDtos.js +0 -14
  36. package/dist/accessi-module/Dtos/CatalogDtos.js.map +1 -1
  37. package/dist/accessi-module/Dtos/GetGroupsWithMenusResponse.d.ts +0 -1
  38. package/dist/accessi-module/Dtos/GetGroupsWithMenusResponse.js +0 -8
  39. package/dist/accessi-module/Dtos/GetGroupsWithMenusResponse.js.map +1 -1
  40. package/dist/accessi-module/Dtos/GetMenusResponse.d.ts +0 -1
  41. package/dist/accessi-module/Dtos/GetMenusResponse.js +0 -8
  42. package/dist/accessi-module/Dtos/GetMenusResponse.js.map +1 -1
  43. package/dist/accessi-module/Dtos/RegisterRequest.d.ts +0 -1
  44. package/dist/accessi-module/Dtos/RegisterRequest.js +0 -9
  45. package/dist/accessi-module/Dtos/RegisterRequest.js.map +1 -1
  46. package/dist/accessi-module/Dtos/UserDto.d.ts +0 -2
  47. package/dist/accessi-module/Dtos/UserDto.js +0 -16
  48. package/dist/accessi-module/Dtos/UserDto.js.map +1 -1
  49. package/dist/accessi-module/Services/AuthService/AuthService.js +0 -2
  50. package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
  51. package/dist/accessi-module/Services/PermissionService/PermissionService.js +5 -13
  52. package/dist/accessi-module/Services/PermissionService/PermissionService.js.map +1 -1
  53. package/dist/accessi-module/Services/UserService/UserService.d.ts +0 -3
  54. package/dist/accessi-module/Services/UserService/UserService.js +2 -41
  55. package/dist/accessi-module/Services/UserService/UserService.js.map +1 -1
  56. package/dist/accessi-module/database-updates/AccessiDatabaseUpdater.js +7 -0
  57. package/dist/accessi-module/database-updates/AccessiDatabaseUpdater.js.map +1 -1
  58. package/dist/accessi-module/database-updates/accessiSchema.d.ts +13 -2
  59. package/dist/accessi-module/database-updates/accessiSchema.js +15 -7
  60. package/dist/accessi-module/database-updates/accessiSchema.js.map +1 -1
  61. package/dist/accessi-module/federated-auth/FederatedAuthController.js +15 -15
  62. package/dist/accessi-module/federated-auth/FederatedAuthController.js.map +1 -1
  63. package/dist/accessi-module/security/accessControl.d.ts +11 -2
  64. package/dist/accessi-module/security/accessControl.js +28 -4
  65. package/dist/accessi-module/security/accessControl.js.map +1 -1
  66. package/dist/emilsoftware-utilities-2.0.0-dev.34.tgz +0 -0
  67. package/package.json +2 -2
  68. package/dist/emilsoftware-utilities-2.0.0-dev.32.tgz +0 -0
@@ -616,3 +616,13 @@ article h3 { margin-bottom: var(--s2); }
616
616
  @media (prefers-reduced-motion: reduce) {
617
617
  *, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
618
618
  }
619
+
620
+ /* Changelog / markdown body */
621
+ .markdown-body h1 { margin: 0 0 var(--s3); font-size: 1.55rem; color: var(--ink); }
622
+ .markdown-body h2 { margin: var(--s5) 0 var(--s2); padding-top: var(--s3); border-top: 1px solid var(--line); font-size: 1.2rem; color: var(--ink); }
623
+ .markdown-body h3 { margin: var(--s4) 0 var(--s2); font-size: 1rem; color: var(--ink-2); }
624
+ .markdown-body h4 { margin: var(--s3) 0 var(--s1); font-size: .95rem; color: var(--ink-2); }
625
+ .markdown-body strong { color: var(--ink); }
626
+ .markdown-body code { padding: 1px 5px; border-radius: var(--r-sm); color: var(--brand-strong); background: var(--brand-soft); font-family: var(--font-mono); font-size: .92em; }
627
+ .markdown-code { margin: 0; padding: var(--s4); overflow-x: auto; border: 1px solid var(--line); border-radius: var(--r-md); background: var(--surface); }
628
+ .markdown-code code { padding: 0; background: transparent; color: var(--ink); white-space: pre; }
@@ -1141,8 +1141,8 @@ POST /api/accessi/auth/confirm-reset-password/{token}
1141
1141
  " body: JSON.stringify({ email, flagSuper: true }), // ignorato o negato",
1142
1142
  "});"
1143
1143
  ].join("\n"), "Privilegi dal client"),
1144
- note("info", `Campi di dominio`, `UTENTI_CONFIG contiene solo i flag di dominio Accessi: FLGSUPER, FLGADMIN, FLG2FATT (2FA), FLGPWDLESS (passwordless) e FLGPASSWORD (login locale). I flag applicativi legacy (FLGMOP, FLGPIANA, FLGADDETTI, FLGOSPITI, FLGPIANARFID, FLGCONTA, FLGCUBI, FLGCICLPASS, FLGDIPENDENTI, FLGINVENTARI) e le colonne applicative (CAUMOV, NUMMAC, RAGSOCCLI) sono stati rimossi dallo schema: i backend devono gestirli in tabelle proprie. ENABLEIA resta per l'assistente IA.`),
1145
- note("info", `Gruppi menu e NOMECAMPO`, `Ogni gruppo menu puo dichiarare NOMECAMPO. get-groups-with-menus e get-menus espongono nomeCampo. Dopo la pulizia dello schema NOMECAMPO non deve piu puntare a colonne applicative rimosse.`),
1144
+ note("info", `Campi di dominio`, `UTENTI_CONFIG contiene solo i flag di dominio Accessi: FLGSUPER, FLGADMIN, FLG2FATT (2FA), FLGPWDLESS (passwordless) e FLGPASSWORD (login locale). I flag/colonne applicativi (FLGMOP, FLGPIANA, FLGADDETTI, FLGOSPITI, FLGPIANARFID, FLGCONTA, FLGCUBI, FLGCICLPASS, FLGDIPENDENTI, FLGINVENTARI, CAUMOV, NUMMAC, RAGSOCCLI, PAGDEF, ENABLEIA, CELLUTE e NOMECAMPO) sono stati rimossi: i backend devono gestirli in tabelle proprie. Il telefono canonico e UTENTI_CONFIG.CELLULARE.`),
1145
+ note("info", `Autorizzazioni`, `I gruppi/menu si abilitano tramite ABILITAZIONI, RUOLI_MNU e UTENTI_RUOLI. Il vecchio meccanismo NOMECAMPO (flag applicativo su UTENTI_CONFIG) e stato rimosso.`),
1146
1146
  note("info", `Campi estesi`, `extensionFieldsOptions allega colonne di tabelle applicative al profilo (per esempio la ragione sociale). Le colonne sono in whitelist: non costruirle mai da input HTTP.`)
1147
1147
  ]
1148
1148
  },
@@ -1315,16 +1315,19 @@ POST /api/accessi/auth/confirm-reset-password/{token}
1315
1315
  title: "Console amministrativa",
1316
1316
  summary: "Accesso, sezioni e che cosa si pu\xF2 fare.",
1317
1317
  blocks: [
1318
- p(`La console \xE8 servita dal modulo su /api/accessi/console e richiede un superutente Accessi. Non ha stato proprio: ogni azione chiama le API protette con il JWT dell'utente connesso.`),
1318
+ p(`La console \xE8 servita dal modulo su /api/accessi/console. Non ha stato proprio: ogni azione chiama le API protette con il JWT dell'utente connesso.`),
1319
1319
  list([
1320
1320
  `Utenti: elenco, creazione locale o SSO, profilo, ruoli, grant diretti, stato, 2FA.`,
1321
1321
  `Ruoli e grant: CRUD dei ruoli con l'albero delle abilitazioni.`,
1322
1322
  `Menu e gruppi: CRUD di gruppi, menu e tipi menu.`,
1323
1323
  `Filtri: filtri utente e tipi filtro.`,
1324
1324
  `Token di servizio: creazione, rotazione, revoca.`,
1325
- `SSO: provider e identit\xE0.`
1325
+ `SSO: provider e identit\xE0.`,
1326
+ `Wiki e Changelog: documentazione e novit\xE0 della libreria.`
1326
1327
  ]),
1327
- note("info", `Accesso`, `Apri /api/accessi/console e accedi con un utente con flagSuper=true. Le sezioni sono raggiungibili anche tramite deep link (per esempio /api/accessi/console/menu-e-gruppi/menu).`)
1328
+ note("info", `Superutente (FLGSUPER)`, `Riceve TUTTE le abilitazioni al livello massimo (30) dopo il login. In console vede solo la sezione Utenti e gestisce utenti, ruoli e grant.`),
1329
+ note("info", `Admin (FLGADMIN)`, `Accede a tutta la console (catalogo, token, SSO, utenti) ma NON conferisce abilitazioni: i menu restano quelli assegnati via ABILITAZIONI/RUOLI_MNU.`),
1330
+ note("info", `Accesso`, `Apri /api/accessi/console e accedi con un utente con FLGSUPER o FLGADMIN. Le sezioni sono raggiungibili anche tramite deep link (per esempio /api/accessi/console/menu-e-gruppi/menu).`)
1328
1331
  ]
1329
1332
  },
1330
1333
  // -------------------------------------------------------------------------
@@ -1463,6 +1466,9 @@ function buildAiDigest() {
1463
1466
  return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
1464
1467
  }
1465
1468
 
1469
+ // src/accessi-module/Console/changelog.md
1470
+ var changelog_default = "# Changelog Accessi\n\nNovit\xE0 e modifiche rilevanti della libreria `emilsoftware-utilities` (modulo Accessi).\n\n## 2.0.0-dev.33\n\n### Cambiamenti\n- Rimosse le logiche applicative residue dallo schema Accessi:\n - `MENU_GRP.NOMECAMPO`\n - `UTENTI.ENABLEIA`\n - `UTENTI_CONFIG.PAGDEF`\n - `UTENTI.CELLUTE`\n- Telefono canonico: `UTENTI_CONFIG.CELLULARE` (allargato a `VARCHAR(30)`).\n- Migrazione automatica: copia `UTENTI.CELLUTE` \u2192 `UTENTI_CONFIG.CELLULARE`, poi `DROP` delle colonne obsolete.\n- Ruoli chiariti:\n - `FLGSUPER`: superutente, riceve **tutte** le abilitazioni al livello massimo (30) dopo il login; in console vede **solo** la sezione **Utenti**.\n - `FLGADMIN`: accede a **tutta** la console (catalogo, token, SSO, utenti) ma **non** influisce sulle abilitazioni.\n- Aggiunta la sezione **Changelog** in console (sotto la Wiki).\n\n### Note\n- `FILTRI` e `FILTRI_TIPO` restano invariati (piano di migrazione futuro).\n- I backend che usavano i campi rimossi devono gestirli in tabelle proprie.\n\n## 2.0.0-dev.32\n\n### Cambiamenti\n- Rimosso il supporto ai flag applicativi in `UTENTI_CONFIG`: `FLGMOP`, `FLGPIANA`, `FLGADDETTI`, `FLGOSPITI`, `FLGPIANARFID`, `FLGCONTA`, `FLGCUBI`, `FLGCICLPASS`, `FLGDIPENDENTI`, `FLGINVENTARI`, `CAUMOV`, `NUMMAC`, `RAGSOCCLI`.\n- Rinominata la colonna `FLGADMINCONFIG` in `FLGADMIN` (con copia dei valori e `DROP`).\n\n## 2.0.0-dev.31\n\n### Aggiunte\n- Esposizione dei servizi Accessi all'host Express (`getAccessiModuleServices`) per delegare login, SSO e token senza reimplementarli.\n\n### Correzioni\n- `UserService.getUsers`: filtri estesi (`codiceUtente[]`, `numRep[]`, `codDipendente`, `cellulare`, `tipFil`, `flagSuper`).\n\n## 2.0.0-dev.24\n\n### Cambiamenti (breaking)\n- Chiavi JWT derivate per scopo (`access`, `reset`, `2fa`) e claim `typ` obbligatorio: gli access token precedenti non sono pi\xF9 validi.\n- Endpoint Allegati protetti di default.\n- Pool di connessioni attivo di default (`poolSize` 5).\n\n### Aggiunte\n- Service token macchina-a-macchina (gestione, guard, console).\n- CRUD completo dei cataloghi dalla console Accessi.\n- Supporto Firebird 2.5 nel migrator e nei test di integrazione.\n";
1471
+
1466
1472
  // src/accessi-module/Console/help.ts
1467
1473
  var p2 = (text) => ({ kind: "p", text });
1468
1474
  var list2 = (items) => ({ kind: "list", items });
@@ -1833,7 +1839,8 @@ var routeSegment = {
1833
1839
  ssoProviders: "sso/provider",
1834
1840
  ssoUsers: "sso/utenti",
1835
1841
  tokens: "token-di-servizio",
1836
- wiki: "wiki"
1842
+ wiki: "wiki",
1843
+ changelog: "changelog"
1837
1844
  };
1838
1845
  var routeView = Object.fromEntries(Object.entries(routeSegment).map(([view, segment]) => [segment, view]));
1839
1846
  var parentDefaultView = {
@@ -2022,6 +2029,10 @@ function viewFromLocation() {
2022
2029
  return routeView[decoded] ?? parentDefaultView[decoded] ?? "users";
2023
2030
  }
2024
2031
  async function navigate(view, replace = false) {
2032
+ if (!isViewAllowed(view)) {
2033
+ view = "users";
2034
+ replace = true;
2035
+ }
2025
2036
  const destination = routePath(view);
2026
2037
  if (window.location.pathname !== destination) {
2027
2038
  window.history[replace ? "replaceState" : "pushState"]({ view }, "", destination);
@@ -2029,8 +2040,26 @@ async function navigate(view, replace = false) {
2029
2040
  if (view === "users") usersPage = 0;
2030
2041
  await show(view);
2031
2042
  }
2032
- function requireAdmin() {
2033
- if (!currentUser?.flagSuper) throw new Error("La console richiede un superutente Accessi.");
2043
+ function requireConsoleAccess() {
2044
+ if (!currentUser?.flagSuper && !currentUser?.flagAdmin) {
2045
+ throw new Error("La console richiede un superutente o un admin Accessi.");
2046
+ }
2047
+ }
2048
+ function isViewAllowed(view) {
2049
+ if (!currentUser) return false;
2050
+ if (currentUser.flagAdmin) return true;
2051
+ return view === "users";
2052
+ }
2053
+ function applyRoleNavigation() {
2054
+ document.querySelectorAll("#nav [data-view]").forEach((link) => {
2055
+ const view = link.dataset.view;
2056
+ const item = link.closest("li") ?? link;
2057
+ item.hidden = !isViewAllowed(view);
2058
+ });
2059
+ document.querySelectorAll("#nav details[data-nav-group]").forEach((branch) => {
2060
+ const hasVisible = Array.from(branch.querySelectorAll("[data-view]")).some((link) => !link.closest("li").hidden);
2061
+ branch.hidden = !hasVisible;
2062
+ });
2034
2063
  }
2035
2064
  function formValues(form) {
2036
2065
  return Object.fromEntries(new FormData(form).entries());
@@ -2234,9 +2263,11 @@ async function bootstrap() {
2234
2263
  if (!token) throw new Error("Inserisci le credenziali amministrative.");
2235
2264
  const payload = result(await getUserByToken({ token }));
2236
2265
  currentUser = payload.userData;
2237
- requireAdmin();
2266
+ requireConsoleAccess();
2267
+ applyRoleNavigation();
2238
2268
  await detectFederatedAuthentication();
2239
- byId("who").textContent = `${currentUser.email} - superutente`;
2269
+ const roleLabel = currentUser.flagSuper && currentUser.flagAdmin ? "super + admin" : currentUser.flagAdmin ? "admin" : "superutente";
2270
+ byId("who").textContent = `${currentUser.email} - ${roleLabel}`;
2240
2271
  byId("sso-navigation").hidden = !federatedAuthenticationAvailable;
2241
2272
  finishBoot("console");
2242
2273
  await navigate(viewFromLocation(), window.location.pathname === consoleBasePath);
@@ -2285,12 +2316,6 @@ function authenticationPolicyFields(user) {
2285
2316
  <p class="form-help">Accesso senza password (richiede il codice email).</p>
2286
2317
  </fieldset>`;
2287
2318
  }
2288
- function applicationFlagFields(user) {
2289
- const record = user;
2290
- return `<fieldset><legend>Abilitazioni applicative</legend>
2291
- <label class="check"><input name="enableIa" type="checkbox" ${record.enableIa ? "checked" : ""}> Assistente IA (ENABLEIA)</label>
2292
- </fieldset>`;
2293
- }
2294
2319
  function showLocalUserForm() {
2295
2320
  render(`<button id="back">Indietro</button><h2>Nuovo utente locale</h2><p class="muted">Viene inviata l'e-mail per impostare la password.</p>
2296
2321
  <form id="local-user"><label>Email ${helpDot("user-email")}<input name="email" type="email" required><span class="form-help">Identificativo di accesso; ricever\xC3\xA0 l'email per la password.</span></label><label>Nome ${helpDot("user-name")}<input name="nome"></label><label>Cognome ${helpDot("user-name")}<input name="cognome"></label><button>Crea utente</button></form>`);
@@ -2428,7 +2453,7 @@ async function showUser(rawCode) {
2428
2453
  const defaultUserTab = federatedAuthenticationAvailable ? "sso" : "profile";
2429
2454
  render(`<div class="user-action-bar"><button id="back" type="button" class="secondary">Indietro</button><label class="inline-field">Stato registrazione ${helpDot("user-state")}<select id="user-state">${registrationStateOptions(user.statoRegistrazione)}</select></label><button id="save-state" type="button" class="secondary">Aggiorna stato</button><button id="disable-user" type="button" class="danger-button">Imposta stato eliminato</button>${helpDot("user-delete")}</div><div class="page-header"><div><p class="eyebrow">Utente ${codiceUtente}</p><h2>${escapeHtml(user.email)}</h2></div><span class="muted">Gestione profilo, ruoli e autorizzazioni</span></div>${userDetailTabs(federatedAuthenticationAvailable)}<div class="user-editor">
2430
2455
  ${identitySection}
2431
- <form id="profile" data-user-panel="profile" role="tabpanel" aria-labelledby="user-tab-profile"><h3>Profilo e accesso</h3><label>Nome ${helpDot("user-name")}<input name="nome" value="${escapeHtml(user.nome)}"></label><label>Cognome ${helpDot("user-name")}<input name="cognome" value="${escapeHtml(user.cognome)}"></label><label>Email ${helpDot("user-email")}<input name="email" type="email" value="${escapeHtml(user.email)}" required><span class="form-help">Identificativo di accesso e recapito.</span></label>${federatedAuthenticationAvailable ? `<label class="check"><input name="passwordLoginEnabled" type="checkbox" ${user.passwordLoginEnabled !== false ? "checked" : ""}> Login con password ${helpDot("user-password-login")}</label><span class="form-help">Se disabilitato, il login con email e password restituisce un errore esplicito; restano valide le identit\xC3\xA0 SSO attive.</span>` : ""}${authenticationPolicyFields(user)}${applicationFlagFields(user)}<button>Salva</button></form>
2456
+ <form id="profile" data-user-panel="profile" role="tabpanel" aria-labelledby="user-tab-profile"><h3>Profilo e accesso</h3><label>Nome ${helpDot("user-name")}<input name="nome" value="${escapeHtml(user.nome)}"></label><label>Cognome ${helpDot("user-name")}<input name="cognome" value="${escapeHtml(user.cognome)}"></label><label>Email ${helpDot("user-email")}<input name="email" type="email" value="${escapeHtml(user.email)}" required><span class="form-help">Identificativo di accesso e recapito.</span></label>${federatedAuthenticationAvailable ? `<label class="check"><input name="passwordLoginEnabled" type="checkbox" ${user.passwordLoginEnabled !== false ? "checked" : ""}> Login con password ${helpDot("user-password-login")}</label><span class="form-help">Se disabilitato, il login con email e password restituisce un errore esplicito; restano valide le identit\xC3\xA0 SSO attive.</span>` : ""}${authenticationPolicyFields(user)}<button>Salva</button></form>
2432
2457
  ${roleAssignmentEditor(allRoles, userGrants.ruoli, menuGroups)}
2433
2458
  ${directGrantEditor(menuGroups, userGrants.abilitazioni)}
2434
2459
  </div>`);
@@ -2467,7 +2492,6 @@ async function showUser(rawCode) {
2467
2492
  cognome: values.cognome || void 0,
2468
2493
  flagDueFattori: fields.has("flagDueFattori"),
2469
2494
  passwordlessLoginEnabled: fields.has("passwordlessLoginEnabled"),
2470
- enableIa: fields.has("enableIa"),
2471
2495
  ...federatedAuthenticationAvailable ? { passwordLoginEnabled: fields.has("passwordLoginEnabled") } : {}
2472
2496
  });
2473
2497
  if (codiceUtente === currentUser?.codiceUtente && (fields.has("flagDueFattori") !== Boolean(user.flagDueFattori) || fields.has("passwordlessLoginEnabled") !== Boolean(user.passwordlessLoginEnabled))) {
@@ -3007,6 +3031,64 @@ async function showWiki(sectionId) {
3007
3031
  showNotice("Documentazione per AI copiata negli appunti.");
3008
3032
  });
3009
3033
  }
3034
+ function inlineMarkdown(text) {
3035
+ return escapeHtml(text).replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/`([^`]+)`/g, "<code>$1</code>");
3036
+ }
3037
+ function renderMarkdown(markdown) {
3038
+ const lines = markdown.replace(/\r\n/g, "\n").split("\n");
3039
+ const html = [];
3040
+ let inList = false;
3041
+ let inCode = false;
3042
+ const closeList = () => {
3043
+ if (inList) {
3044
+ html.push("</ul>");
3045
+ inList = false;
3046
+ }
3047
+ };
3048
+ for (const line of lines) {
3049
+ if (line.startsWith("```")) {
3050
+ closeList();
3051
+ html.push(inCode ? "</code></pre>" : '<pre class="markdown-code"><code>');
3052
+ inCode = !inCode;
3053
+ continue;
3054
+ }
3055
+ if (inCode) {
3056
+ html.push(escapeHtml(line));
3057
+ continue;
3058
+ }
3059
+ const heading = /^(#{1,4})\s+(.*)$/.exec(line);
3060
+ if (heading) {
3061
+ closeList();
3062
+ const level = (heading[1] ?? "").length;
3063
+ html.push(`<h${level}>${inlineMarkdown(heading[2] ?? "")}</h${level}>`);
3064
+ continue;
3065
+ }
3066
+ const bullet = /^\s*[-*]\s+(.*)$/.exec(line);
3067
+ if (bullet) {
3068
+ if (!inList) {
3069
+ html.push("<ul>");
3070
+ inList = true;
3071
+ }
3072
+ html.push(`<li>${inlineMarkdown(bullet[1] ?? "")}</li>`);
3073
+ continue;
3074
+ }
3075
+ if (line.trim() === "") {
3076
+ closeList();
3077
+ continue;
3078
+ }
3079
+ closeList();
3080
+ html.push(`<p>${inlineMarkdown(line)}</p>`);
3081
+ }
3082
+ if (inCode) html.push("</code></pre>");
3083
+ closeList();
3084
+ return html.join("");
3085
+ }
3086
+ async function showChangelog() {
3087
+ render(`<div class="wiki"><article class="wiki-content markdown-body">
3088
+ <header class="wiki-section-head"><h2>Changelog</h2><p>Novita e modifiche rilevanti del modulo Accessi.</p></header>
3089
+ ${renderMarkdown(changelog_default)}
3090
+ </article></div>`);
3091
+ }
3010
3092
  async function show(view) {
3011
3093
  try {
3012
3094
  showNotice("");
@@ -3021,7 +3103,8 @@ async function show(view) {
3021
3103
  ssoProviders: showSsoProviders,
3022
3104
  ssoUsers: showSsoUsers,
3023
3105
  tokens: showServiceTokens,
3024
- wiki: showWiki
3106
+ wiki: showWiki,
3107
+ changelog: showChangelog
3025
3108
  };
3026
3109
  setActiveNavigation(view);
3027
3110
  const renderView = views[view];