emilsoftware-utilities 2.0.0-dev.40 → 2.0.0-dev.41

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.
@@ -1,3 +1,12 @@
1
+ /** Emesso quando una richiesta autenticata riceve 401: la console deve riportare al login. */
2
+ export declare const ACCESSI_UNAUTHORIZED_EVENT = "accessi-console-unauthorized";
3
+ /** Errore del client console: conserva status, codice stabile Accessi e dettagli di validazione. */
4
+ export declare class AccessiRequestError extends Error {
5
+ readonly status: number;
6
+ readonly code?: string | undefined;
7
+ readonly details?: string[] | undefined;
8
+ constructor(message: string, status: number, code?: string | undefined, details?: string[] | undefined);
9
+ }
1
10
  /** Imposta il JWT usato da tutte le funzioni generate da Orval. */
2
11
  export declare function setAccessiConsoleToken(token: string | null): void;
3
12
  /**
@@ -9,10 +9,24 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AccessiRequestError = exports.ACCESSI_UNAUTHORIZED_EVENT = void 0;
12
13
  exports.setAccessiConsoleToken = setAccessiConsoleToken;
13
14
  exports.accessiFetch = accessiFetch;
14
15
  let accessToken = null;
15
16
  const loadingEventName = 'accessi-console-network';
17
+ /** Emesso quando una richiesta autenticata riceve 401: la console deve riportare al login. */
18
+ exports.ACCESSI_UNAUTHORIZED_EVENT = 'accessi-console-unauthorized';
19
+ /** Errore del client console: conserva status, codice stabile Accessi e dettagli di validazione. */
20
+ class AccessiRequestError extends Error {
21
+ constructor(message, status, code, details) {
22
+ super(message);
23
+ this.status = status;
24
+ this.code = code;
25
+ this.details = details;
26
+ this.name = 'AccessiRequestError';
27
+ }
28
+ }
29
+ exports.AccessiRequestError = AccessiRequestError;
16
30
  /** Notifies the browser shell without coupling generated Orval calls to the UI implementation. */
17
31
  function notifyNetworkActivity(active) {
18
32
  if (typeof window !== 'undefined') {
@@ -23,18 +37,26 @@ function notifyNetworkActivity(active) {
23
37
  function setAccessiConsoleToken(token) {
24
38
  accessToken = token;
25
39
  }
40
+ /** Legge una stringa non vuota dal payload di errore Accessi, ignorando tipi inattesi. */
41
+ function payloadString(payload, key) {
42
+ if (!payload || typeof payload !== 'object')
43
+ return undefined;
44
+ const value = payload[key];
45
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
46
+ }
26
47
  /**
27
48
  * Trasporto comune del client Orval: aggiunge il Bearer token e converte le
28
49
  * risposte di errore Accessi in Error JavaScript leggibili dalla console.
29
50
  */
30
51
  function accessiFetch(url, options) {
31
52
  return __awaiter(this, void 0, void 0, function* () {
32
- var _a, _b;
53
+ var _a, _b, _c;
33
54
  const headers = new Headers(options.headers);
34
55
  headers.set('Accept', 'application/json');
35
56
  if (options.body !== undefined && !headers.has('Content-Type')) {
36
57
  headers.set('Content-Type', 'application/json');
37
58
  }
59
+ const authenticatedRequest = accessToken !== null;
38
60
  if (accessToken)
39
61
  headers.set('Authorization', `Bearer ${accessToken}`);
40
62
  notifyNetworkActivity(true);
@@ -42,8 +64,14 @@ function accessiFetch(url, options) {
42
64
  const response = yield fetch(url, Object.assign(Object.assign({}, options), { headers }));
43
65
  const payload = yield response.json().catch(() => undefined);
44
66
  if (!response.ok) {
45
- const message = (_b = (_a = payload === null || payload === void 0 ? void 0 : payload.message) !== null && _a !== void 0 ? _a : payload === null || payload === void 0 ? void 0 : payload.error) !== null && _b !== void 0 ? _b : `Errore HTTP ${response.status}`;
46
- throw new Error(message);
67
+ const message = (_b = (_a = payloadString(payload, 'message')) !== null && _a !== void 0 ? _a : payloadString(payload, 'error')) !== null && _b !== void 0 ? _b : `Errore HTTP ${response.status}`;
68
+ const rawDetails = payload && typeof payload === 'object' ? payload.details : undefined;
69
+ const details = Array.isArray(rawDetails) ? rawDetails.map((item) => String(item)) : undefined;
70
+ // Un 401 su una richiesta autenticata significa sessione scaduta/revocata: il token non e piu valido.
71
+ if (response.status === 401 && authenticatedRequest && typeof window !== 'undefined') {
72
+ window.dispatchEvent(new CustomEvent(exports.ACCESSI_UNAUTHORIZED_EVENT));
73
+ }
74
+ throw new AccessiRequestError(message, response.status, (_c = payloadString(payload, 'code')) !== null && _c !== void 0 ? _c : payloadString(payload, 'error'), details);
47
75
  }
48
76
  return {
49
77
  data: payload,
@@ -1 +1 @@
1
- {"version":3,"file":"accessiFetch.js","sourceRoot":"","sources":["../../../src/accessi-module/Console/accessiFetch.ts"],"names":[],"mappings":";;;;;;;;;;;AAYA,wDAEC;AAMD,oCAwBC;AA5CD,IAAI,WAAW,GAAkB,IAAI,CAAC;AAEtC,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAEnD,kGAAkG;AAClG,SAAS,qBAAqB,CAAC,MAAe;IAC5C,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,MAAM,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,gBAAgB,EAAE,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;IAClF,CAAC;AACH,CAAC;AAED,mEAAmE;AACnE,SAAgB,sBAAsB,CAAC,KAAoB;IACzD,WAAW,GAAG,KAAK,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,SAAsB,YAAY,CAAI,GAAW,EAAE,OAAoB;;;QACrE,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC7C,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;QAC1C,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;YAC/D,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;QAClD,CAAC;QACD,IAAI,WAAW;YAAE,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,WAAW,EAAE,CAAC,CAAC;QAEvE,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,kCAAO,OAAO,KAAE,OAAO,IAAG,CAAC;YAC3D,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC7D,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,OAAO,GAAG,MAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,OAAO,mCAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,KAAK,mCAAI,eAAe,QAAQ,CAAC,MAAM,EAAE,CAAC;gBACvF,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;YAC3B,CAAC;YACD,OAAO;gBACL,IAAI,EAAE,OAAO;gBACb,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;aACrB,CAAC;QACT,CAAC;gBAAS,CAAC;YACT,qBAAqB,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;CAAA","sourcesContent":["let accessToken: string | null = null;\n\nconst loadingEventName = 'accessi-console-network';\n\n/** Notifies the browser shell without coupling generated Orval calls to the UI implementation. */\nfunction notifyNetworkActivity(active: boolean): void {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(loadingEventName, { detail: { active } }));\n }\n}\n\n/** Imposta il JWT usato da tutte le funzioni generate da Orval. */\nexport function setAccessiConsoleToken(token: string | null): void {\n accessToken = token;\n}\n\n/**\n * Trasporto comune del client Orval: aggiunge il Bearer token e converte le\n * risposte di errore Accessi in Error JavaScript leggibili dalla console.\n */\nexport async function accessiFetch<T>(url: string, options: RequestInit): Promise<T> {\n const headers = new Headers(options.headers);\n headers.set('Accept', 'application/json');\n if (options.body !== undefined && !headers.has('Content-Type')) {\n headers.set('Content-Type', 'application/json');\n }\n if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`);\n\n notifyNetworkActivity(true);\n try {\n const response = await fetch(url, { ...options, headers });\n const payload = await response.json().catch(() => undefined);\n if (!response.ok) {\n const message = payload?.message ?? payload?.error ?? `Errore HTTP ${response.status}`;\n throw new Error(message);\n }\n return {\n data: payload,\n status: response.status,\n headers: response.headers,\n } as T;\n } finally {\n notifyNetworkActivity(false);\n }\n}\n"]}
1
+ {"version":3,"file":"accessiFetch.js","sourceRoot":"","sources":["../../../src/accessi-module/Console/accessiFetch.ts"],"names":[],"mappings":"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","sourcesContent":["let accessToken: string | null = null;\n\nconst loadingEventName = 'accessi-console-network';\n/** Emesso quando una richiesta autenticata riceve 401: la console deve riportare al login. */\nexport const ACCESSI_UNAUTHORIZED_EVENT = 'accessi-console-unauthorized';\n\n/** Errore del client console: conserva status, codice stabile Accessi e dettagli di validazione. */\nexport class AccessiRequestError extends Error {\n constructor(message: string, readonly status: number, readonly code?: string, readonly details?: string[]) {\n super(message);\n this.name = 'AccessiRequestError';\n }\n}\n\n/** Notifies the browser shell without coupling generated Orval calls to the UI implementation. */\nfunction notifyNetworkActivity(active: boolean): void {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(loadingEventName, { detail: { active } }));\n }\n}\n\n/** Imposta il JWT usato da tutte le funzioni generate da Orval. */\nexport function setAccessiConsoleToken(token: string | null): void {\n accessToken = token;\n}\n\n/** Legge una stringa non vuota dal payload di errore Accessi, ignorando tipi inattesi. */\nfunction payloadString(payload: unknown, key: string): string | undefined {\n if (!payload || typeof payload !== 'object') return undefined;\n const value = (payload as Record<string, unknown>)[key];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/**\n * Trasporto comune del client Orval: aggiunge il Bearer token e converte le\n * risposte di errore Accessi in Error JavaScript leggibili dalla console.\n */\nexport async function accessiFetch<T>(url: string, options: RequestInit): Promise<T> {\n const headers = new Headers(options.headers);\n headers.set('Accept', 'application/json');\n if (options.body !== undefined && !headers.has('Content-Type')) {\n headers.set('Content-Type', 'application/json');\n }\n const authenticatedRequest = accessToken !== null;\n if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`);\n\n notifyNetworkActivity(true);\n try {\n const response = await fetch(url, { ...options, headers });\n const payload = await response.json().catch(() => undefined);\n if (!response.ok) {\n const message = payloadString(payload, 'message') ?? payloadString(payload, 'error') ?? `Errore HTTP ${response.status}`;\n const rawDetails = payload && typeof payload === 'object' ? (payload as { details?: unknown }).details : undefined;\n const details = Array.isArray(rawDetails) ? rawDetails.map((item) => String(item)) : undefined;\n // Un 401 su una richiesta autenticata significa sessione scaduta/revocata: il token non e piu valido.\n if (response.status === 401 && authenticatedRequest && typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(ACCESSI_UNAUTHORIZED_EVENT));\n }\n throw new AccessiRequestError(message, response.status, payloadString(payload, 'code') ?? payloadString(payload, 'error'), details);\n }\n return {\n data: payload,\n status: response.status,\n headers: response.headers,\n } as T;\n } finally {\n notifyNetworkActivity(false);\n }\n}\n"]}
@@ -263,9 +263,14 @@ legend { padding: 0 var(--s2); color: var(--ink-2); font-weight: 650; font-size:
263
263
  .error { color: var(--danger); }
264
264
 
265
265
  #notice { min-height: 0; }
266
- #notice:not(:empty) { margin: 0; padding: var(--s3) var(--s4); border: 1px solid var(--brand-line); border-radius: var(--r-md); color: var(--brand-strong); background: var(--brand-soft); }
267
- #notice .error { color: var(--danger); }
268
- #notice:has(.error) { border-color: var(--danger-line); color: var(--danger); background: var(--danger-soft); }
266
+ #notice:not(:empty) { margin: 0 0 var(--s4); }
267
+ .notice { display: flex; align-items: flex-start; gap: var(--s3); padding: var(--s3) var(--s4); border: 1px solid var(--brand-line); border-radius: var(--r-md); color: var(--brand-strong); background: var(--brand-soft); }
268
+ .notice-error { border-color: var(--danger-line); color: var(--danger); background: var(--danger-soft); }
269
+ .notice-icon { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 1.3rem; height: 1.3rem; border-radius: 50%; color: #fff; background: var(--brand-strong); font-weight: 700; font-size: .82rem; }
270
+ .notice-error .notice-icon { background: var(--danger); }
271
+ .notice-text { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
272
+ .notice-close { flex: 0 0 auto; padding: 0 .2rem; border: 0; background: none; color: inherit; font-size: 1.15rem; line-height: 1; cursor: pointer; opacity: .7; }
273
+ .notice-close:hover, .notice-close:focus-visible { opacity: 1; }
269
274
 
270
275
  .page-header { display: flex; align-items: end; justify-content: space-between; gap: var(--s4); margin: 0 0 var(--s5); padding-bottom: var(--s4); border-bottom: 1px solid var(--line); }
271
276
  .page-header h2 { margin: 0; }
@@ -1,6 +1,16 @@
1
1
  // src/accessi-module/Console/accessiFetch.ts
2
2
  var accessToken = null;
3
3
  var loadingEventName = "accessi-console-network";
4
+ var ACCESSI_UNAUTHORIZED_EVENT = "accessi-console-unauthorized";
5
+ var AccessiRequestError = class extends Error {
6
+ constructor(message, status, code3, details) {
7
+ super(message);
8
+ this.status = status;
9
+ this.code = code3;
10
+ this.details = details;
11
+ this.name = "AccessiRequestError";
12
+ }
13
+ };
4
14
  function notifyNetworkActivity(active) {
5
15
  if (typeof window !== "undefined") {
6
16
  window.dispatchEvent(new CustomEvent(loadingEventName, { detail: { active } }));
@@ -9,20 +19,31 @@ function notifyNetworkActivity(active) {
9
19
  function setAccessiConsoleToken(token2) {
10
20
  accessToken = token2;
11
21
  }
22
+ function payloadString(payload, key) {
23
+ if (!payload || typeof payload !== "object") return void 0;
24
+ const value = payload[key];
25
+ return typeof value === "string" && value.length > 0 ? value : void 0;
26
+ }
12
27
  async function accessiFetch(url, options) {
13
28
  const headers = new Headers(options.headers);
14
29
  headers.set("Accept", "application/json");
15
30
  if (options.body !== void 0 && !headers.has("Content-Type")) {
16
31
  headers.set("Content-Type", "application/json");
17
32
  }
33
+ const authenticatedRequest = accessToken !== null;
18
34
  if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
19
35
  notifyNetworkActivity(true);
20
36
  try {
21
37
  const response = await fetch(url, { ...options, headers });
22
38
  const payload = await response.json().catch(() => void 0);
23
39
  if (!response.ok) {
24
- const message = payload?.message ?? payload?.error ?? `Errore HTTP ${response.status}`;
25
- throw new Error(message);
40
+ const message = payloadString(payload, "message") ?? payloadString(payload, "error") ?? `Errore HTTP ${response.status}`;
41
+ const rawDetails = payload && typeof payload === "object" ? payload.details : void 0;
42
+ const details = Array.isArray(rawDetails) ? rawDetails.map((item) => String(item)) : void 0;
43
+ if (response.status === 401 && authenticatedRequest && typeof window !== "undefined") {
44
+ window.dispatchEvent(new CustomEvent(ACCESSI_UNAUTHORIZED_EVENT));
45
+ }
46
+ throw new AccessiRequestError(message, response.status, payloadString(payload, "code") ?? payloadString(payload, "error"), details);
26
47
  }
27
48
  return {
28
49
  data: payload,
@@ -959,7 +980,8 @@ var WIKI_SECTIONS = [
959
980
  ]),
960
981
  code("bash", `npm run db:update:accessi # riconcilia lo schema
961
982
  npm run db:check:accessi # verifica senza modificare`, "CLI schema"),
962
- note("warn", `Versioni`, `La libreria richiede uno schema compatibile con la versione corrente (ACCESSI_SCHEMA_VERSION). Se la verifica fallisce, leggi il codice errore ACCESSI_DATABASE_SCHEMA_OUTDATED ed esegui db:update:accessi.`)
983
+ note("warn", `Versioni`, `La libreria richiede uno schema compatibile con la versione corrente (ACCESSI_SCHEMA_VERSION). Se la verifica fallisce, leggi il codice errore ACCESSI_DATABASE_SCHEMA_OUTDATED ed esegui db:update:accessi.`),
984
+ note("info", `Schema esatto`, `La riconciliazione allinea le tabelle Accessi a "niente in piu, niente in meno": le colonne non previste vengono segnalate come incompatibili e rimosse automaticamente (dopo rename e copie dei dati legacy). Gli indici, i trigger e i CHECK extra non previsti non vengono tolti, ma un indice non previsto su una tabella Accessi viene riportato come avviso senza bloccare l'avvio.`)
963
985
  ]
964
986
  },
965
987
  // -------------------------------------------------------------------------
@@ -1950,8 +1972,24 @@ function result(response) {
1950
1972
  if (body.Result === void 0) throw new Error(body.message ?? "Risposta Accessi priva di dati.");
1951
1973
  return body.Result;
1952
1974
  }
1975
+ var noticeTimer;
1953
1976
  function showNotice(message, isError = false) {
1954
- byId("notice").innerHTML = message ? `<span class="${isError ? "error" : ""}">${escapeHtml(message)}</span>` : "";
1977
+ const container = byId("notice");
1978
+ if (noticeTimer !== void 0) {
1979
+ window.clearTimeout(noticeTimer);
1980
+ noticeTimer = void 0;
1981
+ }
1982
+ if (!message) {
1983
+ container.innerHTML = "";
1984
+ return;
1985
+ }
1986
+ const kind = isError ? "error" : "success";
1987
+ const icon = isError ? "!" : "\u2713";
1988
+ container.innerHTML = `<div class="notice notice-${kind}${isError ? " error" : ""}" role="alert"><span class="notice-icon" aria-hidden="true">${icon}</span><span class="notice-text">${escapeHtml(message)}</span><button type="button" class="notice-close" aria-label="Chiudi il messaggio">&times;</button></div>`;
1989
+ container.querySelector(".notice-close")?.addEventListener("click", () => showNotice(""));
1990
+ if (!isError) {
1991
+ noticeTimer = window.setTimeout(() => showNotice(""), 6e3);
1992
+ }
1955
1993
  }
1956
1994
  function menuMetadata(menu) {
1957
1995
  const details = [
@@ -2074,7 +2112,13 @@ function finishBoot(view) {
2074
2112
  byId("console").hidden = view !== "console";
2075
2113
  }
2076
2114
  function errorMessage(error) {
2077
- return error instanceof Error ? error.message : "Operazione non riuscita.";
2115
+ if (!(error instanceof Error)) return "Operazione non riuscita.";
2116
+ const extra = error;
2117
+ const code3 = typeof extra.code === "string" ? extra.code : void 0;
2118
+ const details = Array.isArray(extra.details) ? extra.details.map((item) => String(item)) : [];
2119
+ const detailSuffix = details.length ? ` (${details.join("; ")})` : "";
2120
+ const message = code3 && !error.message.startsWith(`${code3}:`) ? `${code3}: ${error.message}` : error.message;
2121
+ return `${message}${detailSuffix}`;
2078
2122
  }
2079
2123
  function handleAction(action) {
2080
2124
  void action().catch((error) => showNotice(errorMessage(error), true));
@@ -2612,12 +2656,14 @@ async function showUser(rawCode) {
2612
2656
  handleAction(async () => {
2613
2657
  const values = formValues(eventForm(event));
2614
2658
  await linkFederatedIdentity(codiceUtente, { provider: values.provider ?? "", subject: values.subject ?? "", note: values.note || void 0 });
2659
+ showNotice("Collegamento SSO registrato.");
2615
2660
  await showUser(String(codiceUtente));
2616
2661
  });
2617
2662
  };
2618
2663
  document.querySelectorAll("[data-identity-toggle]").forEach((button) => {
2619
2664
  button.onclick = () => handleAction(async () => {
2620
2665
  await updateFederatedIdentity(button.dataset.identityToggle ?? "", { active: button.dataset.active === "true" });
2666
+ showNotice("Collegamento SSO aggiornato.");
2621
2667
  await showUser(String(codiceUtente));
2622
2668
  });
2623
2669
  });
@@ -2659,14 +2705,17 @@ async function showRoles() {
2659
2705
  };
2660
2706
  });
2661
2707
  const roleFormElement = document.getElementById("role-form");
2662
- if (roleFormElement) roleFormElement.onsubmit = async (event) => {
2708
+ if (roleFormElement) roleFormElement.onsubmit = (event) => {
2663
2709
  event.preventDefault();
2664
- const form = eventForm(event);
2665
- const data = new FormData(form);
2666
- const request = { descrizioneRuolo: formValues(form).descrizione ?? "", menu: Array.from(data.getAll("menu"), (codiceMenu) => ({ codiceMenu: String(codiceMenu), tipoAbilitazione: Number(data.get(`menu-level:${codiceMenu}`) ?? 10) })) };
2667
- if (role?.codiceRuolo) await updateRole(role.codiceRuolo, request);
2668
- else await createRole(request);
2669
- await show("roles");
2710
+ handleAction(async () => {
2711
+ const form = eventForm(event);
2712
+ const data = new FormData(form);
2713
+ const request = { descrizioneRuolo: formValues(form).descrizione ?? "", menu: Array.from(data.getAll("menu"), (codiceMenu) => ({ codiceMenu: String(codiceMenu), tipoAbilitazione: Number(data.get(`menu-level:${codiceMenu}`) ?? 10) })) };
2714
+ if (role?.codiceRuolo) await updateRole(role.codiceRuolo, request);
2715
+ else await createRole(request);
2716
+ showNotice(role?.codiceRuolo ? "Ruolo aggiornato." : "Ruolo creato.");
2717
+ await show("roles");
2718
+ });
2670
2719
  };
2671
2720
  };
2672
2721
  byId("new-role").onclick = () => roleForm();
@@ -2879,19 +2928,22 @@ async function showFilters() {
2879
2928
  const users = await loadUsers();
2880
2929
  render(`<h2>Filtri utente</h2><p class="form-help">Filtri applicativi salvati per singolo utente.</p><form id="filters"><label>Utente ${helpDot("filters-user")}<select name="codUte">${users.map(({ utente }) => `<option value="${utente.codiceUtente}">${escapeHtml(utente.email)}</option>`).join("")}</select><span class="form-help">Salvati per l'utente selezionato.</span></label><label>Filtro JSON ${helpDot("filters-json")}<textarea name="json" rows="12">{}</textarea><span class="form-help">Usa Carica per partire dalla struttura esistente.</span></label><button name="action" value="load">Carica</button><button name="action" value="save">Salva</button></form>`);
2881
2930
  const filtersForm = document.getElementById("filters");
2882
- if (filtersForm) filtersForm.onsubmit = async (event) => {
2931
+ if (filtersForm) filtersForm.onsubmit = (event) => {
2883
2932
  event.preventDefault();
2884
2933
  const form = eventForm(event);
2885
2934
  const action = event.submitter?.value;
2886
2935
  const code3 = Number(formValues(form).codUte);
2887
- if (action === "load") {
2888
- const filters = result(await getFiltriUtente({ codUte: code3 }));
2889
- form.elements.namedItem("json").value = JSON.stringify(filters[0] ?? { codUte: code3 }, null, 2);
2890
- } else {
2891
- const parsed = JSON.parse(formValues(form).json ?? "{}");
2892
- await saveFiltriUtente({ ...parsed, codUte: code3 });
2893
- showNotice("Filtri salvati.");
2894
- }
2936
+ handleAction(async () => {
2937
+ if (action === "load") {
2938
+ const filters = result(await getFiltriUtente({ codUte: code3 }));
2939
+ form.elements.namedItem("json").value = JSON.stringify(filters[0] ?? { codUte: code3 }, null, 2);
2940
+ showNotice("Filtri caricati.");
2941
+ } else {
2942
+ const parsed = JSON.parse(formValues(form).json ?? "{}");
2943
+ await saveFiltriUtente({ ...parsed, codUte: code3 });
2944
+ showNotice("Filtri salvati.");
2945
+ }
2946
+ });
2895
2947
  };
2896
2948
  }
2897
2949
  async function showFilterTypes() {
@@ -2972,6 +3024,7 @@ async function showServiceTokens(includeRevoked = false) {
2972
3024
  document.querySelectorAll("[data-rotate]").forEach((button) => {
2973
3025
  button.onclick = () => handleAction(async () => {
2974
3026
  const issued = result(await rotateServiceToken(button.dataset.rotate ?? ""));
3027
+ showNotice("Token di servizio ruotato.");
2975
3028
  showIssuedServiceToken(issued);
2976
3029
  });
2977
3030
  });
@@ -2991,6 +3044,7 @@ function showServiceTokenForm() {
2991
3044
  scopes: scopes.length ? scopes : void 0,
2992
3045
  ttlDays: Number.isFinite(ttlDays) ? ttlDays : void 0
2993
3046
  }));
3047
+ showNotice("Token di servizio creato.");
2994
3048
  showIssuedServiceToken(issued);
2995
3049
  });
2996
3050
  };
@@ -3219,7 +3273,6 @@ async function showSql() {
3219
3273
  }
3220
3274
  async function show(view) {
3221
3275
  try {
3222
- showNotice("");
3223
3276
  const views = {
3224
3277
  users: showUsers,
3225
3278
  roles: showRoles,
@@ -3310,6 +3363,16 @@ byId("nav").onclick = (event) => {
3310
3363
  window.addEventListener("popstate", () => {
3311
3364
  if (token && currentUser) handleAction(() => show(viewFromLocation()));
3312
3365
  });
3366
+ window.addEventListener(ACCESSI_UNAUTHORIZED_EVENT, () => {
3367
+ if (!token) return;
3368
+ token = null;
3369
+ currentUser = null;
3370
+ setAccessiConsoleToken(null);
3371
+ sessionStorage.removeItem("accessi-console-token");
3372
+ finishBoot("login");
3373
+ resetLogin();
3374
+ byId("login-error").textContent = "Sessione scaduta o non piu valida. Accedi di nuovo.";
3375
+ });
3313
3376
  window.addEventListener("accessi-console-network", (event) => {
3314
3377
  updateLoadingState(event.detail.active);
3315
3378
  });