emilsoftware-utilities 2.0.0-dev.52 → 2.0.0-dev.53
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.
- package/dist/accessi-module/Console/console.css +21 -0
- package/dist/accessi-module/Console/console.js +153 -2
- package/dist/accessi-module/Console/console.js.map +1 -1
- package/dist/accessi-module/Console/help.js +1 -0
- package/dist/accessi-module/Console/help.js.map +1 -1
- package/dist/emilsoftware-utilities-2.0.0-dev.53.tgz +0 -0
- package/package.json +1 -1
- package/dist/emilsoftware-utilities-2.0.0-dev.52.tgz +0 -0
|
@@ -358,6 +358,27 @@ table code, .entity-metadata dd, .brand small, .role-code { font-variant-numeric
|
|
|
358
358
|
.account-danger-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); }
|
|
359
359
|
.action-with-help { display: inline-flex; align-items: center; gap: var(--s2); }
|
|
360
360
|
.button-icon { width: 16px; height: 16px; flex: 0 0 16px; }
|
|
361
|
+
|
|
362
|
+
/* Autocomplete della pagina di default: select con ricerca e valore libero. */
|
|
363
|
+
.page-default-field { position: relative; }
|
|
364
|
+
.page-combobox { position: relative; }
|
|
365
|
+
.page-combobox-control { display: flex; align-items: center; gap: var(--s2); min-height: 42px; padding: 0 var(--s2) 0 var(--s3); border: 1px solid var(--line-strong); border-radius: var(--r-md); background: var(--surface); }
|
|
366
|
+
.page-combobox-control:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); }
|
|
367
|
+
.page-combobox-control > .button-icon { color: var(--muted); }
|
|
368
|
+
.page-combobox-control input { flex: 1 1 auto; width: 100%; min-height: 40px; padding: 0; border: 0; border-radius: 0; background: transparent; box-shadow: none; }
|
|
369
|
+
.page-combobox-control input:focus { outline: none; }
|
|
370
|
+
.page-combobox-control input:hover { border-color: transparent; }
|
|
371
|
+
.page-combobox-clear, .page-combobox-toggle { width: 30px; min-width: 30px; min-height: 30px; padding: 0; border: 0; color: var(--muted); background: transparent; }
|
|
372
|
+
.page-combobox-clear:hover, .page-combobox-toggle:hover { color: var(--ink); background: var(--surface-sunken); }
|
|
373
|
+
.page-combobox-clear { font-size: 1.1rem; line-height: 1; }
|
|
374
|
+
.page-combobox-list { position: absolute; z-index: 60; top: calc(100% + var(--s1)); left: 0; right: 0; max-height: 280px; margin: 0; padding: var(--s1); overflow: auto; list-style: none; border: 1px solid var(--line); border-radius: var(--r-md); background: var(--surface); box-shadow: var(--shadow-md); }
|
|
375
|
+
.page-combobox-option { display: grid; gap: 2px; padding: var(--s2) var(--s3); border-radius: var(--r-sm); cursor: pointer; }
|
|
376
|
+
.page-combobox-option:hover, .page-combobox-option.is-active { background: var(--brand-soft); }
|
|
377
|
+
.page-combobox-option[aria-selected="true"] { box-shadow: inset 3px 0 0 var(--brand); }
|
|
378
|
+
.page-combobox-option code { overflow-wrap: anywhere; color: var(--brand-strong); font-family: var(--font-mono); font-size: var(--fs-sm); font-weight: 600; }
|
|
379
|
+
.page-combobox-option small { color: var(--muted); font-size: var(--fs-xs); }
|
|
380
|
+
.page-combobox-option.is-disabled code { color: var(--muted); }
|
|
381
|
+
.page-combobox-empty { padding: var(--s3); color: var(--muted); font-size: var(--fs-sm); }
|
|
361
382
|
.user-tabs { display: flex; gap: var(--s1); margin: 0 0 var(--s4); padding: var(--s1); overflow-x: auto; border: 1px solid var(--line); border-radius: var(--r-md); background: var(--surface-sunken); }
|
|
362
383
|
.user-tabs button { flex: 0 0 auto; min-height: 34px; border: 0; color: var(--ink-2); background: transparent; }
|
|
363
384
|
.user-tabs button:hover { color: var(--ink); background: var(--surface); }
|
|
@@ -1533,7 +1533,7 @@ function buildAiDigest() {
|
|
|
1533
1533
|
}
|
|
1534
1534
|
|
|
1535
1535
|
// src/accessi-module/Console/changelog.md
|
|
1536
|
-
var changelog_default = "# Changelog Accessi\n\nNovit\xE0 e modifiche rilevanti della libreria `emilsoftware-utilities` (modulo Accessi).\n\n## Non rilasciato\n\n### Cambiamenti\n- Console: rimosse le sezioni **Filtri utente** e **Tipi filtro**. I filtri applicativi sono deprecati e non vengono pi\xF9 letti n\xE9 scritti dalla console; le tabelle restano invariate e la gestione torna manuale sul database.\n- Console: le conferme operative (es. \"Ruoli aggiornati\") ora compaiono come notifiche in alto a destra e scompaiono automaticamente.\n- Console: il salvataggio del profilo non invia pi\xF9 i campi della tabella `FILTRI`, evitando payload rifiutati dal backend. `UserService` tocca `FILTRI` solo se il chiamante invia esplicitamente almeno un campo legacy.\n- Console: scheda utente riorganizzata. \"Indietro\" resta nella barra superiore; stato registrazione, disattivazione ed eliminazione definitiva sono in un form coerente \"Stato e sicurezza\", con icone e tooltip allineati.\n- Console: pagina Changelog a larghezza piena, con intestazioni di versione evidenziate e sezioni orizzontali leggibili.\n- Seed `npm run accessi:seed-admin`: crea o aggiorna un admin completo (`FLGSUPER = 1` e `FLGADMIN = 1`) e corregge il nome colonna usato in precedenza (`FLGADMIN`).\n\n## 2.0.0-dev.50 (2026-10-15)\n\n### Cambiamenti\n- ORM: `Row` e `DbValue` sono i tipi predefiniti di `Orm.query` e `Orm.execute`; `transactionQuery` usa `Row[]`. Gli usi generici non devono pi\xF9 dichiarare `Record<string, unknown>`.\n- Build: il contratto OpenAPI generato (`src/accessi-module/openapi/`) \xE8 ignorato da Git e viene rigenerato da `npm run generate:accessi-openapi`.\n\n## 2.0.0-dev.49 (2026-10-15)\n\n### Cambiamenti\n- Documentazione HTTP rinominata da Swagger a **OpenAPI**: il modulo unificato espone `/accessi/openapi` e `/accessi/openapi.json`; `setupSwagger` usa `/openapi` come default.\n- Il path \xE8 configurabile tramite `EmilsoftwareOptions.swagger` (utile per far convivere pi\xF9 documentazioni).\n- Allineati log di avvio, Wiki e dispatch delle rotte del modulo.\n- L'alias storico `<path>-json` resta supportato.\n\n### Note\n- I consumer che aprivano `/swagger` o `/accessi/swagger` devono aggiornare l'URL alla nuova documentazione OpenAPI.\n\n## 2.0.0-dev.48 (2026-10-13)\n\n### Correzioni\n- `RestUtilities.sendErrorMessage` restituisce status HTTP specifici (400/401/403/4xx/5xx) e codici generici (`VALIDATION_ERROR`, `UNAUTHORIZED`, `FORBIDDEN`, `INTERNAL_ERROR`, `REQUEST_ERROR`), senza usare codici `ACCESSI_*` nel business helper.\n- Il messaggio restituito \xE8 quello reale dell'eccezione; gli errori testuali di validazione vengono riconosciuti come 400.\n\n## 2.0.0-dev.47 (2026-10-13)\n\n### Correzioni\n- Errore schema database reso generico: codice `DATABASE_SCHEMA_OUTDATED` e messaggio senza riferimenti specifici ad Accessi.\n\n## 2.0.0-dev.46 (2026-10-12)\n\n### Correzioni\n- `sendBaseResponse` resta retro-compatibile quando il terzo argomento \xE8 un tag stringa invece dello status HTTP.\n- Gestita la serializzazione dei `bigint`; gli errori di risposta vengono registrati con stack tramite `sendErrorMessage`.\n- `AccessiHttpExceptionFilter` registra gli errori con stack per facilitare la diagnosi.\n\n## 2.0.0-dev.45 (2026-10-12)\n\n### Correzioni\n- `set-gdpr` e l'aggiornamento di `flagGdpr` sono idempotenti: `UPDATE OR INSERT` su `UTENTI_GDPR` con `MATCHING (CODUTE, DATACC)`.\n- Evitate violazioni di chiave primaria e risposte 500 quando il consenso GDPR viene registrato pi\xF9 volte nello stesso giorno.\n\n## 2.0.0-dev.44\n\n### Aggiunte\n- Endpoint pubblico `GET /api/accessi/auth/bootstrap-admin/status` (`getAdminBootstrapStatus`): la console mostra la sezione \"Crea utente admin\" **solo** se `adminBootstrap.enabled` \xE8 `true`.\n\n### Correzioni\n- Rimossi i caratteri corrotti (mojibake UTF-8 \u2194 Windows-1252) da DTO, servizi, controller, console, OpenAPI e client generato (es. \"L'email \xE8 gi\xE0 associata a un utente Accessi.\").\n\n## 2.0.0-dev.43\n\n### Cambiamenti\n- Gerarchia dei ruoli formalizzata **ADMIN > SUPER > utente**: un superutente non puo' gestire un admin su `update-user`, `delete-user` (soft e hard), `set-password`, `force-password-reset`, `set-stato`, `assign-roles` e `assign-permissions` (`ensureCanManageTargetUser`, 403 se violato).\n- Guard **ultimo admin attivo**: non puo' essere disattivato, bloccato, eliminato o privato del flag Admin (400), per evitare il lockout della console.\n- Creazione utente (`create-managed-user` e registrazione pubblica) resa **atomica** rispetto all'invio email: fail-fast se SMTP non configurato e rollback dell'utente creato se l'invio fallisce (`ACCESSI_USER_INVITE_EMAIL_FAILED`).\n- Console: form password separato (Invio imposta la password invece di salvare il profilo), scheda utente in sola lettura per target di rango superiore.\n- SSO: anche il superutente puo' creare utenti SSO e gestirne identita/policy (`federated-auth/users/*`) sugli utenti non-admin; il catalogo dei provider SSO resta riservato agli admin.\n- Coerenza: i filtri utente (`saveFiltriUtente`) rispettano la gerarchia; il flag GDPR impostato dal profilo allinea anche `DATGDPR`; la scheda in sola lettura disabilita anche il collegamento SSO.\n- Eliminazione definitiva: se l'utente ha dati collegati (tabelle esterne/FK) risponde 400 con `code: ACCESSI_USER_DELETE_HAS_REFERENCES` e messaggio esplicito, suggerendo il soft delete (la hard delete resta invariata quando non ci sono vincoli).\n- Consenso GDPR completo: `update-user` con `flagGdpr` scrive anche la riga storica `UTENTI_GDPR`; in console il pulsante \"Registra consenso GDPR\" usa `set-gdpr`.\n- Console: aggiunto il pulsante \"Invia reset password legacy\" (super e admin).\n- Console: **auto-ingresso dal frontend** tramite ticket monouso (`POST /api/accessi/console/entry` + apertura di `#entry=<ticket>`), senza JWT nell'URL; il ticket e' legato all'utente, dura 60s, e' monouso e lo scambio e' protetto da rate limit (`publicAuthRateLimit.consoleEntry`).\n\n## 2.0.0-dev.42\n\n### Aggiunte\n- Gestione dei flag di privilegio dalla console Accessi:\n - un admin pu\xF2 assegnare o togliere sia `FLGADMIN` sia `FLGSUPER`;\n - un superutente senza `FLGADMIN` pu\xF2 gestire solo `FLGSUPER`;\n - i flag sono impostabili in creazione (utente locale o SSO) e in modifica profilo.\n- Eliminazione utenti con due modalit\xE0:\n - soft delete (`delete-user`, `STAREG=DELETE`) per admin e superutenti;\n - eliminazione definitiva (`delete-user-permanent`, solo admin) che rimuove in transazione utente e record collegati.\n- Gestione completa dell'utente dalla console (admin): anagrafica estesa (cellulare, lingua, avatar, metadata JSON), GDPR, scadenza password, tutti i filtri (`FILTRI`) e **impostazione diretta della password** (`POST /api/accessi/user/set-password/:codiceUtente`), senza accesso al database.\n- Scritture utente idempotenti: `UTENTI_CONFIG` in `UPDATE OR INSERT ... MATCHING (CODUTE)`; i campi non inviati restano invariati.\n- Policy password moderna quando si imposta una password (reset, set-password admin, bootstrap): minimo 8 caratteri con maiuscola, minuscola, cifra e carattere speciale, senza spazi e non comune. In caso di violazione risposta 400 con `code: ACCESSI_WEAK_PASSWORD` e `details`. La policy **non** si applica al login, cos\xEC le password preesistenti deboli continuano a funzionare.\n\n### Correzioni\n- Il backend verifica i flag in base al ruolo dell'utente connesso (`ensurePrivilegeFlagChanges`): inviare un flag non consentito restituisce 403.\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";
|
|
1536
|
+
var changelog_default = "# Changelog Accessi\n\nNovit\xE0 e modifiche rilevanti della libreria `emilsoftware-utilities` (modulo Accessi).\n\n## Non rilasciato\n\n### Cambiamenti\n- Console: rimosse le sezioni **Filtri utente** e **Tipi filtro**. I filtri applicativi sono deprecati e non vengono pi\xF9 letti n\xE9 scritti dalla console; le tabelle restano invariate e la gestione torna manuale sul database.\n- Console: le conferme operative (es. \"Ruoli aggiornati\") ora compaiono come notifiche in alto a destra e scompaiono automaticamente.\n- Console: il salvataggio del profilo non invia pi\xF9 i campi della tabella `FILTRI`, evitando payload rifiutati dal backend. `UserService` tocca `FILTRI` solo se il chiamante invia esplicitamente almeno un campo legacy.\n- Console: scheda utente riorganizzata. \"Indietro\" resta nella barra superiore; stato registrazione, disattivazione ed eliminazione definitiva sono in un form coerente \"Stato e sicurezza\", con icone e tooltip allineati.\n- Console: pagina Changelog a larghezza piena, con intestazioni di versione evidenziate e sezioni orizzontali leggibili.\n- Seed `npm run accessi:seed-admin`: crea o aggiorna un admin completo (`FLGSUPER = 1` e `FLGADMIN = 1`) e corregge il nome colonna usato in precedenza (`FLGADMIN`).\n- Console: la pagina di default nella scheda utente \xE8 ora un autocomplete con ricerca. Propone tutte le rotte del catalogo menu, si filtra scrivendo ed \xE8 ancora possibile inserire un valore libero.\n\n## 2.0.0-dev.50 (2026-10-15)\n\n### Cambiamenti\n- ORM: `Row` e `DbValue` sono i tipi predefiniti di `Orm.query` e `Orm.execute`; `transactionQuery` usa `Row[]`. Gli usi generici non devono pi\xF9 dichiarare `Record<string, unknown>`.\n- Build: il contratto OpenAPI generato (`src/accessi-module/openapi/`) \xE8 ignorato da Git e viene rigenerato da `npm run generate:accessi-openapi`.\n\n## 2.0.0-dev.49 (2026-10-15)\n\n### Cambiamenti\n- Documentazione HTTP rinominata da Swagger a **OpenAPI**: il modulo unificato espone `/accessi/openapi` e `/accessi/openapi.json`; `setupSwagger` usa `/openapi` come default.\n- Il path \xE8 configurabile tramite `EmilsoftwareOptions.swagger` (utile per far convivere pi\xF9 documentazioni).\n- Allineati log di avvio, Wiki e dispatch delle rotte del modulo.\n- L'alias storico `<path>-json` resta supportato.\n\n### Note\n- I consumer che aprivano `/swagger` o `/accessi/swagger` devono aggiornare l'URL alla nuova documentazione OpenAPI.\n\n## 2.0.0-dev.48 (2026-10-13)\n\n### Correzioni\n- `RestUtilities.sendErrorMessage` restituisce status HTTP specifici (400/401/403/4xx/5xx) e codici generici (`VALIDATION_ERROR`, `UNAUTHORIZED`, `FORBIDDEN`, `INTERNAL_ERROR`, `REQUEST_ERROR`), senza usare codici `ACCESSI_*` nel business helper.\n- Il messaggio restituito \xE8 quello reale dell'eccezione; gli errori testuali di validazione vengono riconosciuti come 400.\n\n## 2.0.0-dev.47 (2026-10-13)\n\n### Correzioni\n- Errore schema database reso generico: codice `DATABASE_SCHEMA_OUTDATED` e messaggio senza riferimenti specifici ad Accessi.\n\n## 2.0.0-dev.46 (2026-10-12)\n\n### Correzioni\n- `sendBaseResponse` resta retro-compatibile quando il terzo argomento \xE8 un tag stringa invece dello status HTTP.\n- Gestita la serializzazione dei `bigint`; gli errori di risposta vengono registrati con stack tramite `sendErrorMessage`.\n- `AccessiHttpExceptionFilter` registra gli errori con stack per facilitare la diagnosi.\n\n## 2.0.0-dev.45 (2026-10-12)\n\n### Correzioni\n- `set-gdpr` e l'aggiornamento di `flagGdpr` sono idempotenti: `UPDATE OR INSERT` su `UTENTI_GDPR` con `MATCHING (CODUTE, DATACC)`.\n- Evitate violazioni di chiave primaria e risposte 500 quando il consenso GDPR viene registrato pi\xF9 volte nello stesso giorno.\n\n## 2.0.0-dev.44\n\n### Aggiunte\n- Endpoint pubblico `GET /api/accessi/auth/bootstrap-admin/status` (`getAdminBootstrapStatus`): la console mostra la sezione \"Crea utente admin\" **solo** se `adminBootstrap.enabled` \xE8 `true`.\n\n### Correzioni\n- Rimossi i caratteri corrotti (mojibake UTF-8 \u2194 Windows-1252) da DTO, servizi, controller, console, OpenAPI e client generato (es. \"L'email \xE8 gi\xE0 associata a un utente Accessi.\").\n\n## 2.0.0-dev.43\n\n### Cambiamenti\n- Gerarchia dei ruoli formalizzata **ADMIN > SUPER > utente**: un superutente non puo' gestire un admin su `update-user`, `delete-user` (soft e hard), `set-password`, `force-password-reset`, `set-stato`, `assign-roles` e `assign-permissions` (`ensureCanManageTargetUser`, 403 se violato).\n- Guard **ultimo admin attivo**: non puo' essere disattivato, bloccato, eliminato o privato del flag Admin (400), per evitare il lockout della console.\n- Creazione utente (`create-managed-user` e registrazione pubblica) resa **atomica** rispetto all'invio email: fail-fast se SMTP non configurato e rollback dell'utente creato se l'invio fallisce (`ACCESSI_USER_INVITE_EMAIL_FAILED`).\n- Console: form password separato (Invio imposta la password invece di salvare il profilo), scheda utente in sola lettura per target di rango superiore.\n- SSO: anche il superutente puo' creare utenti SSO e gestirne identita/policy (`federated-auth/users/*`) sugli utenti non-admin; il catalogo dei provider SSO resta riservato agli admin.\n- Coerenza: i filtri utente (`saveFiltriUtente`) rispettano la gerarchia; il flag GDPR impostato dal profilo allinea anche `DATGDPR`; la scheda in sola lettura disabilita anche il collegamento SSO.\n- Eliminazione definitiva: se l'utente ha dati collegati (tabelle esterne/FK) risponde 400 con `code: ACCESSI_USER_DELETE_HAS_REFERENCES` e messaggio esplicito, suggerendo il soft delete (la hard delete resta invariata quando non ci sono vincoli).\n- Consenso GDPR completo: `update-user` con `flagGdpr` scrive anche la riga storica `UTENTI_GDPR`; in console il pulsante \"Registra consenso GDPR\" usa `set-gdpr`.\n- Console: aggiunto il pulsante \"Invia reset password legacy\" (super e admin).\n- Console: **auto-ingresso dal frontend** tramite ticket monouso (`POST /api/accessi/console/entry` + apertura di `#entry=<ticket>`), senza JWT nell'URL; il ticket e' legato all'utente, dura 60s, e' monouso e lo scambio e' protetto da rate limit (`publicAuthRateLimit.consoleEntry`).\n\n## 2.0.0-dev.42\n\n### Aggiunte\n- Gestione dei flag di privilegio dalla console Accessi:\n - un admin pu\xF2 assegnare o togliere sia `FLGADMIN` sia `FLGSUPER`;\n - un superutente senza `FLGADMIN` pu\xF2 gestire solo `FLGSUPER`;\n - i flag sono impostabili in creazione (utente locale o SSO) e in modifica profilo.\n- Eliminazione utenti con due modalit\xE0:\n - soft delete (`delete-user`, `STAREG=DELETE`) per admin e superutenti;\n - eliminazione definitiva (`delete-user-permanent`, solo admin) che rimuove in transazione utente e record collegati.\n- Gestione completa dell'utente dalla console (admin): anagrafica estesa (cellulare, lingua, avatar, metadata JSON), GDPR, scadenza password, tutti i filtri (`FILTRI`) e **impostazione diretta della password** (`POST /api/accessi/user/set-password/:codiceUtente`), senza accesso al database.\n- Scritture utente idempotenti: `UTENTI_CONFIG` in `UPDATE OR INSERT ... MATCHING (CODUTE)`; i campi non inviati restano invariati.\n- Policy password moderna quando si imposta una password (reset, set-password admin, bootstrap): minimo 8 caratteri con maiuscola, minuscola, cifra e carattere speciale, senza spazi e non comune. In caso di violazione risposta 400 con `code: ACCESSI_WEAK_PASSWORD` e `details`. La policy **non** si applica al login, cos\xEC le password preesistenti deboli continuano a funzionare.\n\n### Correzioni\n- Il backend verifica i flag in base al ruolo dell'utente connesso (`ensurePrivilegeFlagChanges`): inviare un flag non consentito restituisce 403.\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";
|
|
1537
1537
|
|
|
1538
1538
|
// src/accessi-module/Console/help.ts
|
|
1539
1539
|
var p2 = (text) => ({ kind: "p", text });
|
|
@@ -1688,6 +1688,7 @@ var HELP = {
|
|
|
1688
1688
|
title: "Pagina di default",
|
|
1689
1689
|
blocks: [
|
|
1690
1690
|
p2(`Percorso o codice della pagina verso cui indirizzare l'utente dopo il login. Esempio: /dashboard oppure la chiave di una pagina gestita dal frontend.`),
|
|
1691
|
+
p2(`La console propone in autocomplete tutte le rotte presenti nel catalogo dei menu. Puoi filtrare la lista scrivendo oppure inserire un valore libero.`),
|
|
1691
1692
|
p2(`E una stringa libera (massimo 50 caratteri): la libreria la conserva e la restituisce nel profilo; l'instradamento effettivo lo decide il frontend.`)
|
|
1692
1693
|
]
|
|
1693
1694
|
},
|
|
@@ -2574,6 +2575,155 @@ function dateInputValue(value) {
|
|
|
2574
2575
|
const match = /^(\d{4}-\d{2}-\d{2})/.exec(text);
|
|
2575
2576
|
return match?.[1] ?? "";
|
|
2576
2577
|
}
|
|
2578
|
+
function pageRouteSuggestions(groups) {
|
|
2579
|
+
const routes = /* @__PURE__ */ new Map();
|
|
2580
|
+
for (const group of groups) {
|
|
2581
|
+
for (const menu of group.menus) {
|
|
2582
|
+
const value = (menu.pagina ?? "").trim();
|
|
2583
|
+
if (!value) continue;
|
|
2584
|
+
const suggestion = {
|
|
2585
|
+
value,
|
|
2586
|
+
menu: menu.descrizioneMenu || void 0,
|
|
2587
|
+
group: group.descrizioneGruppo || void 0,
|
|
2588
|
+
disabled: menu.enabled === false
|
|
2589
|
+
};
|
|
2590
|
+
const current = routes.get(value);
|
|
2591
|
+
if (!current || current.disabled && !suggestion.disabled) routes.set(value, suggestion);
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
return [...routes.values()].sort((a, b) => a.value.localeCompare(b.value, "it"));
|
|
2595
|
+
}
|
|
2596
|
+
function pageDefaultField(user) {
|
|
2597
|
+
const value = user.paginaDefault ?? "";
|
|
2598
|
+
return `<label class="page-default-field">Pagina di default ${helpDot("user-default-page")}
|
|
2599
|
+
<div class="page-combobox" data-page-combobox>
|
|
2600
|
+
<div class="page-combobox-control">
|
|
2601
|
+
${actionIcon('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>')}
|
|
2602
|
+
<input id="pagina-default" name="paginaDefault" type="text" maxlength="50" value="${escapeHtml(value)}" autocomplete="off" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="pagina-default-listbox" placeholder="Cerca una rotta o scrivi una pagina">
|
|
2603
|
+
<button type="button" class="page-combobox-clear" aria-label="Pulisci la pagina di default" ${value ? "" : "hidden"}>×</button>
|
|
2604
|
+
<button type="button" class="page-combobox-toggle" aria-label="Mostra le pagine disponibili" aria-haspopup="listbox">${actionIcon('<path d="m6 9 6 6 6-6"/>')}</button>
|
|
2605
|
+
</div>
|
|
2606
|
+
<ul id="pagina-default-listbox" class="page-combobox-list" role="listbox" aria-label="Pagine disponibili" hidden></ul>
|
|
2607
|
+
</div>
|
|
2608
|
+
<span class="form-help">Scegli una rotta dal catalogo dei menu oppure scrivi un valore libero.</span>
|
|
2609
|
+
</label>`;
|
|
2610
|
+
}
|
|
2611
|
+
function bindPageDefaultCombobox(routes) {
|
|
2612
|
+
const root = document.querySelector("[data-page-combobox]");
|
|
2613
|
+
if (!root) return;
|
|
2614
|
+
const input = root.querySelector('input[name="paginaDefault"]');
|
|
2615
|
+
const list3 = root.querySelector(".page-combobox-list");
|
|
2616
|
+
const clear = root.querySelector(".page-combobox-clear");
|
|
2617
|
+
const toggle = root.querySelector(".page-combobox-toggle");
|
|
2618
|
+
if (!input || !list3) return;
|
|
2619
|
+
let matches = [];
|
|
2620
|
+
let activeIndex = -1;
|
|
2621
|
+
const optionId = (index) => `pagina-default-option-${index}`;
|
|
2622
|
+
const setOpen = (open) => {
|
|
2623
|
+
list3.hidden = !open;
|
|
2624
|
+
input.setAttribute("aria-expanded", String(open));
|
|
2625
|
+
if (!open) {
|
|
2626
|
+
activeIndex = -1;
|
|
2627
|
+
input.removeAttribute("aria-activedescendant");
|
|
2628
|
+
}
|
|
2629
|
+
};
|
|
2630
|
+
const renderMatches = (query) => {
|
|
2631
|
+
const needle = query.trim().toLocaleLowerCase("it");
|
|
2632
|
+
matches = needle === "" ? routes : routes.filter((route) => [route.value, route.menu ?? "", route.group ?? ""].join(" ").toLocaleLowerCase("it").includes(needle));
|
|
2633
|
+
list3.innerHTML = matches.map((route, index) => `<li id="${optionId(index)}" class="page-combobox-option${route.disabled ? " is-disabled" : ""}" role="option" aria-selected="${route.value === input.value ? "true" : "false"}" data-index="${index}"><code>${escapeHtml(route.value)}</code><small>${escapeHtml([route.menu, route.group].filter(Boolean).join(" \xB7 ") || "Rotta del catalogo menu")}${route.disabled ? " \xB7 menu disabilitato" : ""}</small></li>`).join("") || '<li class="page-combobox-empty" role="presentation">Nessuna rotta corrisponde. Puoi comunque lasciare il valore scritto.</li>';
|
|
2634
|
+
activeIndex = -1;
|
|
2635
|
+
input.removeAttribute("aria-activedescendant");
|
|
2636
|
+
};
|
|
2637
|
+
const highlight = () => {
|
|
2638
|
+
list3.querySelectorAll(".page-combobox-option").forEach((option, index) => {
|
|
2639
|
+
option.classList.toggle("is-active", index === activeIndex);
|
|
2640
|
+
});
|
|
2641
|
+
const current = activeIndex >= 0 ? list3.querySelector(`#${optionId(activeIndex)}`) : null;
|
|
2642
|
+
if (current) {
|
|
2643
|
+
input.setAttribute("aria-activedescendant", optionId(activeIndex));
|
|
2644
|
+
current.scrollIntoView({ block: "nearest" });
|
|
2645
|
+
} else {
|
|
2646
|
+
input.removeAttribute("aria-activedescendant");
|
|
2647
|
+
}
|
|
2648
|
+
};
|
|
2649
|
+
const openList = () => {
|
|
2650
|
+
renderMatches("");
|
|
2651
|
+
setOpen(true);
|
|
2652
|
+
highlight();
|
|
2653
|
+
};
|
|
2654
|
+
const refresh = (query) => {
|
|
2655
|
+
renderMatches(query);
|
|
2656
|
+
setOpen(true);
|
|
2657
|
+
highlight();
|
|
2658
|
+
};
|
|
2659
|
+
const choose = (route) => {
|
|
2660
|
+
input.value = route.value;
|
|
2661
|
+
if (clear) clear.hidden = false;
|
|
2662
|
+
input.focus();
|
|
2663
|
+
setOpen(false);
|
|
2664
|
+
};
|
|
2665
|
+
input.addEventListener("focus", openList);
|
|
2666
|
+
input.addEventListener("input", () => {
|
|
2667
|
+
if (clear) clear.hidden = input.value.length === 0;
|
|
2668
|
+
refresh(input.value);
|
|
2669
|
+
});
|
|
2670
|
+
input.addEventListener("blur", () => {
|
|
2671
|
+
window.setTimeout(() => {
|
|
2672
|
+
if (!root.contains(document.activeElement)) setOpen(false);
|
|
2673
|
+
}, 0);
|
|
2674
|
+
});
|
|
2675
|
+
input.addEventListener("keydown", (event) => {
|
|
2676
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
2677
|
+
event.preventDefault();
|
|
2678
|
+
if (list3.hidden) refresh(input.value);
|
|
2679
|
+
if (matches.length === 0) return;
|
|
2680
|
+
activeIndex = event.key === "ArrowDown" ? (activeIndex + 1) % matches.length : activeIndex <= 0 ? matches.length - 1 : activeIndex - 1;
|
|
2681
|
+
highlight();
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
if (event.key === "Enter" && !list3.hidden && activeIndex >= 0) {
|
|
2685
|
+
const route = matches[activeIndex];
|
|
2686
|
+
if (route) {
|
|
2687
|
+
event.preventDefault();
|
|
2688
|
+
choose(route);
|
|
2689
|
+
}
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
if (event.key === "Escape" && !list3.hidden) {
|
|
2693
|
+
event.preventDefault();
|
|
2694
|
+
setOpen(false);
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
if (event.key === "Tab") setOpen(false);
|
|
2698
|
+
});
|
|
2699
|
+
list3.addEventListener("mousedown", (event) => {
|
|
2700
|
+
if (event.target.closest("[data-index]")) event.preventDefault();
|
|
2701
|
+
});
|
|
2702
|
+
list3.addEventListener("click", (event) => {
|
|
2703
|
+
const option = event.target.closest("[data-index]");
|
|
2704
|
+
if (!option) return;
|
|
2705
|
+
const route = matches[Number(option.dataset.index)];
|
|
2706
|
+
if (route) choose(route);
|
|
2707
|
+
});
|
|
2708
|
+
if (clear) {
|
|
2709
|
+
clear.addEventListener("click", () => {
|
|
2710
|
+
input.value = "";
|
|
2711
|
+
clear.hidden = true;
|
|
2712
|
+
input.focus();
|
|
2713
|
+
openList();
|
|
2714
|
+
});
|
|
2715
|
+
}
|
|
2716
|
+
if (toggle) {
|
|
2717
|
+
toggle.addEventListener("click", () => {
|
|
2718
|
+
if (list3.hidden) {
|
|
2719
|
+
input.focus();
|
|
2720
|
+
openList();
|
|
2721
|
+
} else {
|
|
2722
|
+
setOpen(false);
|
|
2723
|
+
}
|
|
2724
|
+
});
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2577
2727
|
function extendedProfileFields(user) {
|
|
2578
2728
|
return `<fieldset><legend>Anagrafica estesa</legend>
|
|
2579
2729
|
<label>Cellulare ${helpDot("user-cellulare")}<input name="cellulare" maxlength="30" value="${escapeHtml(user.cellulare ?? "")}"></label>
|
|
@@ -2768,11 +2918,12 @@ async function showUser(rawCode) {
|
|
|
2768
2918
|
</form>
|
|
2769
2919
|
${userDetailTabs(federatedAuthenticationAvailable)}<div class="user-editor">
|
|
2770
2920
|
${identitySection}
|
|
2771
|
-
<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
|
|
2921
|
+
<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>${pageDefaultField(user)}${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\xE0 SSO attive.</span>` : ""}${authenticationPolicyFields(user)}${extendedProfileFields(user)}${privilegeFields(user)}<button id="save-profile" type="submit">Salva</button></form>
|
|
2772
2922
|
${passwordFields()}
|
|
2773
2923
|
${roleAssignmentEditor(allRoles, userGrants.ruoli, menuGroups)}
|
|
2774
2924
|
${directGrantEditor(menuGroups, userGrants.abilitazioni)}
|
|
2775
2925
|
</div>`);
|
|
2926
|
+
bindPageDefaultCombobox(pageRouteSuggestions(menuGroups));
|
|
2776
2927
|
byId("back").onclick = () => show("users");
|
|
2777
2928
|
const userTabButtons = Array.from(document.querySelectorAll("[data-user-tab]"));
|
|
2778
2929
|
userTabButtons.forEach((button, index) => {
|