wuic-framework-lib 1.5.0 → 1.7.1
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/fesm2022/{wuic-framework-lib-chart-list.component-0J1ghIrS.mjs → wuic-framework-lib-chart-list.component-Dq9IEKsY.mjs} +50 -38
- package/fesm2022/{wuic-framework-lib-code-editor.component-C21mvjyr.mjs → wuic-framework-lib-code-editor.component-B6IpTSo8.mjs} +174 -274
- package/fesm2022/{wuic-framework-lib-designer.component-x10ZzUEy.mjs → wuic-framework-lib-designer.component-D6cbqNFg.mjs} +499 -581
- package/fesm2022/wuic-framework-lib-first-run-wizard.component-D1ZQUl-q.mjs +1630 -0
- package/fesm2022/{wuic-framework-lib-framework-docs.component-xTGLbEnp.mjs → wuic-framework-lib-framework-docs.component-C_o5r7pg.mjs} +4320 -1255
- package/fesm2022/{wuic-framework-lib-pivot-builder.component-BkFFjPm4.mjs → wuic-framework-lib-pivot-builder.component-DkWGzJpr.mjs} +580 -68
- package/fesm2022/{wuic-framework-lib-prompt-dialog-fallback-host.component-Bi-TrJB6.mjs → wuic-framework-lib-prompt-dialog-fallback-host.component-D29go5yA.mjs} +2 -2
- package/fesm2022/wuic-framework-lib-report-designer.component-DcpCWghx.mjs +380 -0
- package/fesm2022/wuic-framework-lib-report-scaffold-dialog.component-CgUjG7Bi.mjs +431 -0
- package/fesm2022/{wuic-framework-lib-report-viewer.component-BR-BfI2r.mjs → wuic-framework-lib-report-viewer.component-C4Opgl_M.mjs} +8 -13
- package/fesm2022/{wuic-framework-lib-scene3d-designer.component-BQzFxSXP.mjs → wuic-framework-lib-scene3d-designer.component-8l3vsyEb.mjs} +323 -88
- package/fesm2022/{wuic-framework-lib-scene3d-light-baker-gpu-0LjqhSNn.mjs → wuic-framework-lib-scene3d-light-baker-gpu-C1w-8Aai.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-scene3d-shader-effect-CQLWkuh1.mjs → wuic-framework-lib-scene3d-shader-effect-DjHyCAFK.mjs} +612 -32
- package/fesm2022/{wuic-framework-lib-scene3d-viewer.component-imoV3HOs.mjs → wuic-framework-lib-scene3d-viewer.component-nMSl5jbe.mjs} +149 -18
- package/fesm2022/{wuic-framework-lib-scheduler-list.component-xzlTEkoq.mjs → wuic-framework-lib-scheduler-list.component-Cvk3Zg0Q.mjs} +169 -53
- package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-CrS6fOGj.mjs → wuic-framework-lib-spreadsheet-list-sf.component-C1U7JPiq.mjs} +149 -108
- package/fesm2022/{wuic-framework-lib-stimulsoft-license.service-BIyxB4pO.mjs → wuic-framework-lib-stimulsoft-license.service-CsNNSFYs.mjs} +3 -3
- package/fesm2022/wuic-framework-lib-timeline-list.component-cZbPgNh1.mjs +805 -0
- package/fesm2022/{wuic-framework-lib-workflow-designer.component-5xtQ7-6z.mjs → wuic-framework-lib-workflow-designer.component-C_4RQGa0.mjs} +180 -150
- package/fesm2022/{wuic-framework-lib-wuic-framework-lib-3TZ-j-yL.mjs → wuic-framework-lib-wuic-framework-lib-BZZwta8b.mjs} +6785 -2347
- package/fesm2022/wuic-framework-lib.mjs +1 -1
- package/package.json +21 -2
- package/styles/_wuic-tokens.scss +82 -0
- package/styles/styles.scss +160 -0
- package/types/wuic-framework-lib.d.ts +4269 -2691
- package/fesm2022/wuic-framework-lib-first-run-wizard.component-BuSxs_CM.mjs +0 -1316
- package/fesm2022/wuic-framework-lib-report-designer.component-ZvB46-LV.mjs +0 -194
|
@@ -0,0 +1,1630 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { EventEmitter, Output, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import * as i1$2 from '@angular/common';
|
|
4
|
+
import { CommonModule } from '@angular/common';
|
|
5
|
+
import * as i1$1 from '@angular/forms';
|
|
6
|
+
import { FormsModule } from '@angular/forms';
|
|
7
|
+
import * as i1 from '@angular/common/http';
|
|
8
|
+
import { HttpContext } from '@angular/common/http';
|
|
9
|
+
import { G as GlobalHandler, W as WtoolboxService, p as WUIC_SUPPRESS_GLOBAL_ERROR } from './wuic-framework-lib-wuic-framework-lib-BZZwta8b.mjs';
|
|
10
|
+
import { firstValueFrom } from 'rxjs';
|
|
11
|
+
import * as i8 from '@ngx-translate/core';
|
|
12
|
+
import { TranslateModule } from '@ngx-translate/core';
|
|
13
|
+
import * as i4 from 'primeng/button';
|
|
14
|
+
import { ButtonModule } from 'primeng/button';
|
|
15
|
+
import * as i5 from 'primeng/select';
|
|
16
|
+
import { SelectModule } from 'primeng/select';
|
|
17
|
+
import * as i6 from 'primeng/progressbar';
|
|
18
|
+
import { ProgressBarModule } from 'primeng/progressbar';
|
|
19
|
+
import * as i7 from 'primeng/tooltip';
|
|
20
|
+
import { TooltipModule } from 'primeng/tooltip';
|
|
21
|
+
import * as i2 from 'primeng/dialog';
|
|
22
|
+
import { DialogModule } from 'primeng/dialog';
|
|
23
|
+
import * as i3 from 'primeng/api';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Traduzioni INLINE del first-run wizard (5 lingue: it/en/fr/es/de).
|
|
27
|
+
*
|
|
28
|
+
* Perche' inline e non `_wuic_translations` (eccezione documentata alla skill
|
|
29
|
+
* ui-localization): il wizard e' l'unica pagina che gira PRIMA che il DB
|
|
30
|
+
* metadati esista — `MetaDataSQLConnection` e' ancora al placeholder
|
|
31
|
+
* `__SET_CONNECTION_STRING__` e `MetaService.GetTranslation` risponde []. Una
|
|
32
|
+
* chiave registrata in DB verrebbe mostrata grezza. Stesso motivo per cui il
|
|
33
|
+
* disclaimer GDPR del crash-reporting usa un dizionario inline nel componente.
|
|
34
|
+
*
|
|
35
|
+
* La lingua iniziale viene da `navigator.language`; cambiando "Lingua admin
|
|
36
|
+
* iniziale" nel wizard, il wizard stesso passa a quella lingua
|
|
37
|
+
* (`TranslateService.use`).
|
|
38
|
+
*
|
|
39
|
+
* I valori con markup (`*_html`) finiscono in `[innerHTML]`: usare solo
|
|
40
|
+
* <strong>, <code>, <br>.
|
|
41
|
+
*/
|
|
42
|
+
const FIRST_RUN_SUPPORTED_LANGS = ['it-IT', 'en-US', 'fr-FR', 'es-ES', 'de-DE'];
|
|
43
|
+
const FIRST_RUN_UI_BUNDLES = {
|
|
44
|
+
'it-IT': {
|
|
45
|
+
firstrun: {
|
|
46
|
+
ui: {
|
|
47
|
+
title: 'Configurazione iniziale progetto',
|
|
48
|
+
subtitle: 'Seleziona DBMS, connessione dati e nome database metadati.',
|
|
49
|
+
setup_mode: 'Modalità setup',
|
|
50
|
+
setup_mode_existing: 'DB esistente',
|
|
51
|
+
create_tutorial_if_missing: 'Crea tutorial se manca',
|
|
52
|
+
data_connection_placeholder: 'es. data source=localhost\\sqlexpress;User ID=sa;Password=...;initial catalog=WideWorldImporters',
|
|
53
|
+
test_connection: 'Test connessione e carica DB',
|
|
54
|
+
tutorial_data_db: 'Database dati tutorial',
|
|
55
|
+
data_db: 'Database dati',
|
|
56
|
+
data_db_placeholder: 'Seleziona o digita database',
|
|
57
|
+
refresh_db_list: 'Aggiorna elenco DB',
|
|
58
|
+
tutorial_meta_db: 'Nome DB metadati tutorial',
|
|
59
|
+
meta_db: 'Nome DB metadati',
|
|
60
|
+
scaffold_existing: 'Esegui scaffold automatico delle tabelle del DB selezionato',
|
|
61
|
+
scaffold_existing_hint_html: 'Popola <code>_metadati__tabelle</code> / <code>_metadati__colonne</code> al termine dell\'installazione. Operazione potenzialmente lunga su DB con molte tabelle.',
|
|
62
|
+
admin_section: 'Utente admin iniziale',
|
|
63
|
+
admin_username: 'Username admin iniziale',
|
|
64
|
+
admin_password: 'Password admin iniziale',
|
|
65
|
+
admin_password_placeholder: '(min 4 caratteri)',
|
|
66
|
+
admin_language: 'Lingua admin iniziale',
|
|
67
|
+
admin_language_hint: 'Lingua applicata all\'utente admin dopo il primo login e a questa pagina. Puoi cambiarla in seguito dal profilo utente.',
|
|
68
|
+
rag_section: 'RAG Chatbot (opzionale)',
|
|
69
|
+
rag_intro_html: 'Il RAG Chatbot è già incluso e funziona <strong>out-of-the-box</strong>: l\'engine è nativo <strong>.NET/ONNX</strong> (nessun Python, nessun server separato da installare). Al primo uso della chat scarica automaticamente i modelli (~2.3 GB) da <code>wuic-framework.com</code>. Con una GPU NVIDIA (CUDA 12.x + cuDNN 9.x) le risposte sono in ~1s; senza, fallback automatico su CPU. <strong>Nessun setup da fare qui.</strong>',
|
|
70
|
+
anthropic_key: 'Anthropic API Key (opzionale)',
|
|
71
|
+
anthropic_key_hint_html: 'Senza chiave il chatbot funziona solo in modalità retrieval (ricerca snippet). Con chiave abilita la chat con LLM (Claude) e le azioni proposte sul designer. Viene salvata in <code>appsettings.json</code>; puoi cambiarla anche dopo.',
|
|
72
|
+
error_close_aria: 'Chiudi errore',
|
|
73
|
+
error_close: 'Chiudi',
|
|
74
|
+
progress_batch: 'batch',
|
|
75
|
+
submit: 'Conferma e genera metadataDB',
|
|
76
|
+
msg_connection_required: 'Inserisci la DataSQLConnection prima del test.',
|
|
77
|
+
msg_connection_unparsable: 'DataSQLConnection non valida: impossibile derivare la connessione base.',
|
|
78
|
+
msg_connection_ok: 'Connessione valida',
|
|
79
|
+
msg_connection_ok_detail: 'Connessione riuscita. Trovati {{count}} database.',
|
|
80
|
+
msg_connection_fixed: 'Connessione corretta automaticamente',
|
|
81
|
+
msg_connection_fixed_detail: 'Applicato fix certificati SQL Server (Encrypt=False;TrustServerCertificate=True).',
|
|
82
|
+
msg_credentials_required: 'Stringa DataSQLConnection non valida: servono almeno user e password (oppure Integrated Security=true su MSSQL).',
|
|
83
|
+
msg_integrated_security_mssql_only: 'Integrated Security è supportata solo su Microsoft SQL Server: per {{dbms}} servono user e password.',
|
|
84
|
+
msg_data_source_missing: 'Stringa DataSQLConnection non valida: manca data source/server/host.',
|
|
85
|
+
msg_pick_data_db: 'Seleziona un database dati dalla lista.',
|
|
86
|
+
msg_test_connection_first: 'Testa prima la connessione dati per abilitare il database di destinazione.',
|
|
87
|
+
msg_admin_username_required: 'Inserisci un username per l\'utente admin iniziale.',
|
|
88
|
+
msg_admin_password_required: 'Inserisci una password (almeno 4 caratteri) per l\'utente admin iniziale.',
|
|
89
|
+
msg_base_connection_failed: 'Stringa DataSQLConnection non valida: impossibile derivare la connessione base senza database.',
|
|
90
|
+
confirm_recreate_title: 'Conferma ricreazione metadata DB',
|
|
91
|
+
confirm_recreate_question: 'Il database metadati \'{{db}}\' esiste già. Vuoi eliminarlo e ricrearlo?',
|
|
92
|
+
no: 'No',
|
|
93
|
+
yes: 'Sì',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
'en-US': {
|
|
98
|
+
firstrun: {
|
|
99
|
+
ui: {
|
|
100
|
+
title: 'Initial project setup',
|
|
101
|
+
subtitle: 'Choose the DBMS, the data connection and the metadata database name.',
|
|
102
|
+
setup_mode: 'Setup mode',
|
|
103
|
+
setup_mode_existing: 'Existing database',
|
|
104
|
+
create_tutorial_if_missing: 'Create the tutorial database if missing',
|
|
105
|
+
data_connection_placeholder: 'e.g. data source=localhost\\sqlexpress;User ID=sa;Password=...;initial catalog=WideWorldImporters',
|
|
106
|
+
test_connection: 'Test connection and load databases',
|
|
107
|
+
tutorial_data_db: 'Tutorial data database',
|
|
108
|
+
data_db: 'Data database',
|
|
109
|
+
data_db_placeholder: 'Select or type a database',
|
|
110
|
+
refresh_db_list: 'Refresh database list',
|
|
111
|
+
tutorial_meta_db: 'Tutorial metadata database name',
|
|
112
|
+
meta_db: 'Metadata database name',
|
|
113
|
+
scaffold_existing: 'Scaffold the tables of the selected database automatically',
|
|
114
|
+
scaffold_existing_hint_html: 'Fills <code>_metadati__tabelle</code> / <code>_metadati__colonne</code> when the installation ends. It can take a while on databases with many tables.',
|
|
115
|
+
admin_section: 'Initial admin user',
|
|
116
|
+
admin_username: 'Initial admin username',
|
|
117
|
+
admin_password: 'Initial admin password',
|
|
118
|
+
admin_password_placeholder: '(at least 4 characters)',
|
|
119
|
+
admin_language: 'Initial admin language',
|
|
120
|
+
admin_language_hint: 'Language applied to the admin user after the first login, and to this page. You can change it later from the user profile.',
|
|
121
|
+
rag_section: 'RAG Chatbot (optional)',
|
|
122
|
+
rag_intro_html: 'The RAG Chatbot is included and works <strong>out of the box</strong>: the engine is native <strong>.NET/ONNX</strong> (no Python, no separate server to install). The first time you use the chat it downloads the models (~2.3 GB) from <code>wuic-framework.com</code>. With an NVIDIA GPU (CUDA 12.x + cuDNN 9.x) answers take about 1s; without one it falls back to CPU. <strong>Nothing to set up here.</strong>',
|
|
123
|
+
anthropic_key: 'Anthropic API key (optional)',
|
|
124
|
+
anthropic_key_hint_html: 'Without a key the chatbot only works in retrieval mode (snippet search). With a key it enables the LLM chat (Claude) and the actions it proposes in the designer. It is stored in <code>appsettings.json</code> and can be changed later.',
|
|
125
|
+
error_close_aria: 'Dismiss error',
|
|
126
|
+
error_close: 'Dismiss',
|
|
127
|
+
progress_batch: 'batch',
|
|
128
|
+
submit: 'Confirm and create the metadata database',
|
|
129
|
+
msg_connection_required: 'Enter the DataSQLConnection before testing.',
|
|
130
|
+
msg_connection_unparsable: 'Invalid DataSQLConnection: the base connection cannot be derived.',
|
|
131
|
+
msg_connection_ok: 'Connection valid',
|
|
132
|
+
msg_connection_ok_detail: 'Connected. Found {{count}} databases.',
|
|
133
|
+
msg_connection_fixed: 'Connection fixed automatically',
|
|
134
|
+
msg_connection_fixed_detail: 'Applied the SQL Server certificate fix (Encrypt=False;TrustServerCertificate=True).',
|
|
135
|
+
msg_credentials_required: 'Invalid DataSQLConnection: user and password are required (or Integrated Security=true on SQL Server).',
|
|
136
|
+
msg_integrated_security_mssql_only: 'Integrated Security is only supported on Microsoft SQL Server: {{dbms}} needs a user and a password.',
|
|
137
|
+
msg_data_source_missing: 'Invalid DataSQLConnection: data source/server/host is missing.',
|
|
138
|
+
msg_pick_data_db: 'Pick a data database from the list.',
|
|
139
|
+
msg_test_connection_first: 'Test the data connection first to enable the target database.',
|
|
140
|
+
msg_admin_username_required: 'Enter a username for the initial admin user.',
|
|
141
|
+
msg_admin_password_required: 'Enter a password (at least 4 characters) for the initial admin user.',
|
|
142
|
+
msg_base_connection_failed: 'Invalid DataSQLConnection: the base connection without a database cannot be derived.',
|
|
143
|
+
confirm_recreate_title: 'Confirm metadata database recreation',
|
|
144
|
+
confirm_recreate_question: 'The metadata database \'{{db}}\' already exists. Do you want to drop and recreate it?',
|
|
145
|
+
no: 'No',
|
|
146
|
+
yes: 'Yes',
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
'fr-FR': {
|
|
151
|
+
firstrun: {
|
|
152
|
+
ui: {
|
|
153
|
+
title: 'Configuration initiale du projet',
|
|
154
|
+
subtitle: 'Choisissez le SGBD, la connexion aux données et le nom de la base de métadonnées.',
|
|
155
|
+
setup_mode: 'Mode d\'installation',
|
|
156
|
+
setup_mode_existing: 'Base existante',
|
|
157
|
+
create_tutorial_if_missing: 'Créer la base du tutoriel si elle manque',
|
|
158
|
+
data_connection_placeholder: 'ex. data source=localhost\\sqlexpress;User ID=sa;Password=...;initial catalog=WideWorldImporters',
|
|
159
|
+
test_connection: 'Tester la connexion et charger les bases',
|
|
160
|
+
tutorial_data_db: 'Base de données du tutoriel',
|
|
161
|
+
data_db: 'Base de données',
|
|
162
|
+
data_db_placeholder: 'Sélectionnez ou saisissez une base',
|
|
163
|
+
refresh_db_list: 'Actualiser la liste des bases',
|
|
164
|
+
tutorial_meta_db: 'Nom de la base de métadonnées du tutoriel',
|
|
165
|
+
meta_db: 'Nom de la base de métadonnées',
|
|
166
|
+
scaffold_existing: 'Générer automatiquement les métadonnées des tables de la base choisie',
|
|
167
|
+
scaffold_existing_hint_html: 'Remplit <code>_metadati__tabelle</code> / <code>_metadati__colonne</code> à la fin de l\'installation. L\'opération peut être longue sur une base comptant beaucoup de tables.',
|
|
168
|
+
admin_section: 'Utilisateur administrateur initial',
|
|
169
|
+
admin_username: 'Nom d\'utilisateur administrateur',
|
|
170
|
+
admin_password: 'Mot de passe administrateur',
|
|
171
|
+
admin_password_placeholder: '(4 caractères minimum)',
|
|
172
|
+
admin_language: 'Langue de l\'administrateur',
|
|
173
|
+
admin_language_hint: 'Langue appliquée à l\'administrateur après la première connexion, ainsi qu\'à cette page. Vous pourrez la changer depuis le profil utilisateur.',
|
|
174
|
+
rag_section: 'RAG Chatbot (facultatif)',
|
|
175
|
+
rag_intro_html: 'Le RAG Chatbot est inclus et fonctionne <strong>immédiatement</strong> : le moteur est natif <strong>.NET/ONNX</strong> (pas de Python, aucun serveur séparé à installer). Au premier usage du chat, les modèles (~2,3 Go) sont téléchargés depuis <code>wuic-framework.com</code>. Avec un GPU NVIDIA (CUDA 12.x + cuDNN 9.x) les réponses arrivent en ~1 s ; sinon, repli automatique sur le CPU. <strong>Rien à configurer ici.</strong>',
|
|
176
|
+
anthropic_key: 'Clé API Anthropic (facultative)',
|
|
177
|
+
anthropic_key_hint_html: 'Sans clé, le chatbot fonctionne uniquement en mode recherche d\'extraits. Avec une clé, il active le chat LLM (Claude) et les actions proposées dans le designer. Elle est enregistrée dans <code>appsettings.json</code> et reste modifiable ensuite.',
|
|
178
|
+
error_close_aria: 'Fermer l\'erreur',
|
|
179
|
+
error_close: 'Fermer',
|
|
180
|
+
progress_batch: 'lot',
|
|
181
|
+
submit: 'Confirmer et créer la base de métadonnées',
|
|
182
|
+
msg_connection_required: 'Saisissez la DataSQLConnection avant le test.',
|
|
183
|
+
msg_connection_unparsable: 'DataSQLConnection invalide : impossible de dériver la connexion de base.',
|
|
184
|
+
msg_connection_ok: 'Connexion valide',
|
|
185
|
+
msg_connection_ok_detail: 'Connexion réussie. {{count}} bases trouvées.',
|
|
186
|
+
msg_connection_fixed: 'Connexion corrigée automatiquement',
|
|
187
|
+
msg_connection_fixed_detail: 'Correctif de certificat SQL Server appliqué (Encrypt=False;TrustServerCertificate=True).',
|
|
188
|
+
msg_credentials_required: 'DataSQLConnection invalide : utilisateur et mot de passe sont requis (ou Integrated Security=true sur SQL Server).',
|
|
189
|
+
msg_integrated_security_mssql_only: 'Integrated Security n\'est pris en charge que sur Microsoft SQL Server : {{dbms}} exige un utilisateur et un mot de passe.',
|
|
190
|
+
msg_data_source_missing: 'DataSQLConnection invalide : data source/server/host manquant.',
|
|
191
|
+
msg_pick_data_db: 'Choisissez une base de données dans la liste.',
|
|
192
|
+
msg_test_connection_first: 'Testez d\'abord la connexion aux données pour activer la base cible.',
|
|
193
|
+
msg_admin_username_required: 'Saisissez un nom d\'utilisateur pour l\'administrateur initial.',
|
|
194
|
+
msg_admin_password_required: 'Saisissez un mot de passe (4 caractères minimum) pour l\'administrateur initial.',
|
|
195
|
+
msg_base_connection_failed: 'DataSQLConnection invalide : impossible de dériver la connexion de base sans base de données.',
|
|
196
|
+
confirm_recreate_title: 'Confirmer la recréation de la base de métadonnées',
|
|
197
|
+
confirm_recreate_question: 'La base de métadonnées \'{{db}}\' existe déjà. Voulez-vous la supprimer et la recréer ?',
|
|
198
|
+
no: 'Non',
|
|
199
|
+
yes: 'Oui',
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
'es-ES': {
|
|
204
|
+
firstrun: {
|
|
205
|
+
ui: {
|
|
206
|
+
title: 'Configuración inicial del proyecto',
|
|
207
|
+
subtitle: 'Elige el SGBD, la conexión de datos y el nombre de la base de metadatos.',
|
|
208
|
+
setup_mode: 'Modo de instalación',
|
|
209
|
+
setup_mode_existing: 'Base de datos existente',
|
|
210
|
+
create_tutorial_if_missing: 'Crear la base del tutorial si falta',
|
|
211
|
+
data_connection_placeholder: 'p. ej. data source=localhost\\sqlexpress;User ID=sa;Password=...;initial catalog=WideWorldImporters',
|
|
212
|
+
test_connection: 'Probar conexión y cargar bases de datos',
|
|
213
|
+
tutorial_data_db: 'Base de datos del tutorial',
|
|
214
|
+
data_db: 'Base de datos',
|
|
215
|
+
data_db_placeholder: 'Selecciona o escribe una base de datos',
|
|
216
|
+
refresh_db_list: 'Actualizar lista de bases de datos',
|
|
217
|
+
tutorial_meta_db: 'Nombre de la base de metadatos del tutorial',
|
|
218
|
+
meta_db: 'Nombre de la base de metadatos',
|
|
219
|
+
scaffold_existing: 'Generar automáticamente los metadatos de las tablas de la base elegida',
|
|
220
|
+
scaffold_existing_hint_html: 'Rellena <code>_metadati__tabelle</code> / <code>_metadati__colonne</code> al terminar la instalación. Puede tardar en bases con muchas tablas.',
|
|
221
|
+
admin_section: 'Usuario administrador inicial',
|
|
222
|
+
admin_username: 'Usuario administrador inicial',
|
|
223
|
+
admin_password: 'Contraseña del administrador',
|
|
224
|
+
admin_password_placeholder: '(mínimo 4 caracteres)',
|
|
225
|
+
admin_language: 'Idioma del administrador',
|
|
226
|
+
admin_language_hint: 'Idioma aplicado al administrador tras el primer inicio de sesión y a esta página. Puedes cambiarlo después desde el perfil de usuario.',
|
|
227
|
+
rag_section: 'RAG Chatbot (opcional)',
|
|
228
|
+
rag_intro_html: 'El RAG Chatbot ya está incluido y funciona <strong>de inmediato</strong>: el motor es nativo <strong>.NET/ONNX</strong> (sin Python ni servidores aparte). La primera vez que uses el chat descarga los modelos (~2,3 GB) desde <code>wuic-framework.com</code>. Con una GPU NVIDIA (CUDA 12.x + cuDNN 9.x) las respuestas tardan ~1 s; sin ella, recurre automáticamente a la CPU. <strong>Aquí no hay nada que configurar.</strong>',
|
|
229
|
+
anthropic_key: 'Clave API de Anthropic (opcional)',
|
|
230
|
+
anthropic_key_hint_html: 'Sin clave el chatbot solo funciona en modo de recuperación (búsqueda de fragmentos). Con clave habilita el chat con LLM (Claude) y las acciones propuestas en el diseñador. Se guarda en <code>appsettings.json</code> y puedes cambiarla más adelante.',
|
|
231
|
+
error_close_aria: 'Cerrar el error',
|
|
232
|
+
error_close: 'Cerrar',
|
|
233
|
+
progress_batch: 'lote',
|
|
234
|
+
submit: 'Confirmar y crear la base de metadatos',
|
|
235
|
+
msg_connection_required: 'Introduce la DataSQLConnection antes de la prueba.',
|
|
236
|
+
msg_connection_unparsable: 'DataSQLConnection no válida: no se puede derivar la conexión base.',
|
|
237
|
+
msg_connection_ok: 'Conexión válida',
|
|
238
|
+
msg_connection_ok_detail: 'Conexión correcta. Se han encontrado {{count}} bases de datos.',
|
|
239
|
+
msg_connection_fixed: 'Conexión corregida automáticamente',
|
|
240
|
+
msg_connection_fixed_detail: 'Aplicada la corrección de certificados de SQL Server (Encrypt=False;TrustServerCertificate=True).',
|
|
241
|
+
msg_credentials_required: 'DataSQLConnection no válida: se necesitan usuario y contraseña (o Integrated Security=true en SQL Server).',
|
|
242
|
+
msg_integrated_security_mssql_only: 'Integrated Security solo se admite en Microsoft SQL Server: {{dbms}} necesita usuario y contraseña.',
|
|
243
|
+
msg_data_source_missing: 'DataSQLConnection no válida: falta data source/server/host.',
|
|
244
|
+
msg_pick_data_db: 'Selecciona una base de datos de la lista.',
|
|
245
|
+
msg_test_connection_first: 'Prueba primero la conexión de datos para habilitar la base de destino.',
|
|
246
|
+
msg_admin_username_required: 'Introduce un usuario para el administrador inicial.',
|
|
247
|
+
msg_admin_password_required: 'Introduce una contraseña (mínimo 4 caracteres) para el administrador inicial.',
|
|
248
|
+
msg_base_connection_failed: 'DataSQLConnection no válida: no se puede derivar la conexión base sin base de datos.',
|
|
249
|
+
confirm_recreate_title: 'Confirmar la recreación de la base de metadatos',
|
|
250
|
+
confirm_recreate_question: 'La base de metadatos \'{{db}}\' ya existe. ¿Quieres eliminarla y volver a crearla?',
|
|
251
|
+
no: 'No',
|
|
252
|
+
yes: 'Sí',
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
'de-DE': {
|
|
257
|
+
firstrun: {
|
|
258
|
+
ui: {
|
|
259
|
+
title: 'Erstkonfiguration des Projekts',
|
|
260
|
+
subtitle: 'Wähle das DBMS, die Datenverbindung und den Namen der Metadaten-Datenbank.',
|
|
261
|
+
setup_mode: 'Installationsmodus',
|
|
262
|
+
setup_mode_existing: 'Vorhandene Datenbank',
|
|
263
|
+
create_tutorial_if_missing: 'Tutorial-Datenbank anlegen, falls sie fehlt',
|
|
264
|
+
data_connection_placeholder: 'z. B. data source=localhost\\sqlexpress;User ID=sa;Password=...;initial catalog=WideWorldImporters',
|
|
265
|
+
test_connection: 'Verbindung testen und Datenbanken laden',
|
|
266
|
+
tutorial_data_db: 'Tutorial-Datenbank',
|
|
267
|
+
data_db: 'Datenbank',
|
|
268
|
+
data_db_placeholder: 'Datenbank auswählen oder eingeben',
|
|
269
|
+
refresh_db_list: 'Datenbankliste aktualisieren',
|
|
270
|
+
tutorial_meta_db: 'Name der Tutorial-Metadatenbank',
|
|
271
|
+
meta_db: 'Name der Metadaten-Datenbank',
|
|
272
|
+
scaffold_existing: 'Metadaten der Tabellen der gewählten Datenbank automatisch erzeugen',
|
|
273
|
+
scaffold_existing_hint_html: 'Füllt <code>_metadati__tabelle</code> / <code>_metadati__colonne</code> am Ende der Installation. Bei Datenbanken mit vielen Tabellen kann das dauern.',
|
|
274
|
+
admin_section: 'Erster Administrator',
|
|
275
|
+
admin_username: 'Benutzername des Administrators',
|
|
276
|
+
admin_password: 'Passwort des Administrators',
|
|
277
|
+
admin_password_placeholder: '(mindestens 4 Zeichen)',
|
|
278
|
+
admin_language: 'Sprache des Administrators',
|
|
279
|
+
admin_language_hint: 'Sprache, die nach der ersten Anmeldung für den Administrator und für diese Seite gilt. Sie lässt sich später im Benutzerprofil ändern.',
|
|
280
|
+
rag_section: 'RAG Chatbot (optional)',
|
|
281
|
+
rag_intro_html: 'Der RAG Chatbot ist enthalten und funktioniert <strong>sofort</strong>: Die Engine ist nativ <strong>.NET/ONNX</strong> (kein Python, kein separater Server). Beim ersten Chat werden die Modelle (~2,3 GB) von <code>wuic-framework.com</code> geladen. Mit einer NVIDIA-GPU (CUDA 12.x + cuDNN 9.x) dauern Antworten etwa 1 s, sonst wird automatisch die CPU verwendet. <strong>Hier ist nichts einzurichten.</strong>',
|
|
282
|
+
anthropic_key: 'Anthropic-API-Schlüssel (optional)',
|
|
283
|
+
anthropic_key_hint_html: 'Ohne Schlüssel arbeitet der Chatbot nur im Retrieval-Modus (Snippet-Suche). Mit Schlüssel sind der LLM-Chat (Claude) und die im Designer vorgeschlagenen Aktionen aktiv. Er wird in <code>appsettings.json</code> gespeichert und kann später geändert werden.',
|
|
284
|
+
error_close_aria: 'Fehler schließen',
|
|
285
|
+
error_close: 'Schließen',
|
|
286
|
+
progress_batch: 'Stapel',
|
|
287
|
+
submit: 'Bestätigen und Metadaten-Datenbank erzeugen',
|
|
288
|
+
msg_connection_required: 'Gib die DataSQLConnection vor dem Test ein.',
|
|
289
|
+
msg_connection_unparsable: 'Ungültige DataSQLConnection: Die Basisverbindung kann nicht abgeleitet werden.',
|
|
290
|
+
msg_connection_ok: 'Verbindung gültig',
|
|
291
|
+
msg_connection_ok_detail: 'Verbindung hergestellt. {{count}} Datenbanken gefunden.',
|
|
292
|
+
msg_connection_fixed: 'Verbindung automatisch korrigiert',
|
|
293
|
+
msg_connection_fixed_detail: 'SQL-Server-Zertifikatskorrektur angewendet (Encrypt=False;TrustServerCertificate=True).',
|
|
294
|
+
msg_credentials_required: 'Ungültige DataSQLConnection: Benutzer und Passwort sind erforderlich (oder Integrated Security=true auf SQL Server).',
|
|
295
|
+
msg_integrated_security_mssql_only: 'Integrated Security wird nur von Microsoft SQL Server unterstützt: {{dbms}} benötigt Benutzer und Passwort.',
|
|
296
|
+
msg_data_source_missing: 'Ungültige DataSQLConnection: data source/server/host fehlt.',
|
|
297
|
+
msg_pick_data_db: 'Wähle eine Datenbank aus der Liste.',
|
|
298
|
+
msg_test_connection_first: 'Teste zuerst die Datenverbindung, um die Zieldatenbank zu aktivieren.',
|
|
299
|
+
msg_admin_username_required: 'Gib einen Benutzernamen für den ersten Administrator ein.',
|
|
300
|
+
msg_admin_password_required: 'Gib ein Passwort (mindestens 4 Zeichen) für den ersten Administrator ein.',
|
|
301
|
+
msg_base_connection_failed: 'Ungültige DataSQLConnection: Die Basisverbindung ohne Datenbank kann nicht abgeleitet werden.',
|
|
302
|
+
confirm_recreate_title: 'Neuerstellung der Metadaten-Datenbank bestätigen',
|
|
303
|
+
confirm_recreate_question: 'Die Metadaten-Datenbank \'{{db}}\' existiert bereits. Möchtest du sie löschen und neu erstellen?',
|
|
304
|
+
no: 'Nein',
|
|
305
|
+
yes: 'Ja',
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Componente standalone framework che incapsula l'INTERO workflow di firstRun:
|
|
313
|
+
*
|
|
314
|
+
* 1. probe `GET /api/Meta/FirstRunStatus` al boot per decidere visibilita';
|
|
315
|
+
* 2. rendering del form wizard (DBMS, connection string, admin, RAG API key);
|
|
316
|
+
* 3. test connessione + caricamento elenco DB (`MetaService.get_database_names`);
|
|
317
|
+
* 4. submit `MetaService.configure_wuic` con tutti i parametri raccolti;
|
|
318
|
+
* 5. polling progress (`GET /api/Meta/FirstRunProgress`) durante l'install;
|
|
319
|
+
* 6. handling specifico dell'errore `METADATA_DB_EXISTS_CONFIRM_REQUIRED:` con
|
|
320
|
+
* promptDialog di conferma drop+recreate;
|
|
321
|
+
* 7. clear lato client (cookies + sessionStorage + localStorage + indexedDB)
|
|
322
|
+
* al termine dell'install e wait `waitForBackendReady` prima del redirect;
|
|
323
|
+
* 8. registrazione bundle traduzioni `firstrun.*` (5 lingue) PRIMA che il
|
|
324
|
+
* template risolva i pipe `| translate`.
|
|
325
|
+
*
|
|
326
|
+
* **Auto-control**: il componente decide da se' se mostrarsi (firstRun=true) o
|
|
327
|
+
* essere invisibile (firstRun=false). Il consumer si limita a montarlo nel
|
|
328
|
+
* template app shell (`<wuic-first-run-wizard></wuic-first-run-wizard>`) e
|
|
329
|
+
* sottoscrive l'output `(complete)` per refreshare la UI quando il wizard
|
|
330
|
+
* conclude (di solito un full-page reload via location.assign).
|
|
331
|
+
*
|
|
332
|
+
* **Self-contained**: tutti gli helper (parseConnectionString, normalizeDbms,
|
|
333
|
+
* extractErrorMessage, ecc.) sono incapsulati qui dentro. Niente dipendenza
|
|
334
|
+
* dal consumer oltre ai servizi WUIC standard (HttpClient, TranslateService,
|
|
335
|
+
* WtoolboxService per promptDialog + appSettings).
|
|
336
|
+
*/
|
|
337
|
+
class WuicFirstRunWizardComponent {
|
|
338
|
+
http;
|
|
339
|
+
translate;
|
|
340
|
+
messageService;
|
|
341
|
+
injector;
|
|
342
|
+
/**
|
|
343
|
+
* Emessa quando il flusso firstRun viene rilevato come NON necessario
|
|
344
|
+
* (firstRun=false al boot) OPPURE quando l'install e' completato con
|
|
345
|
+
* successo. Il consumer puo' usarla per inizializzare il resto della UI
|
|
346
|
+
* (carico shell, dynamic imports lazy, ecc.) sapendo che non c'e' overlay
|
|
347
|
+
* a coprire la pagina. In caso di success-with-redirect (caso normale post
|
|
348
|
+
* install) la emit avviene PRIMA del location.assign, ma e' essenzialmente
|
|
349
|
+
* fire-and-forget perche' il browser navighera' via subito dopo.
|
|
350
|
+
*/
|
|
351
|
+
complete = new EventEmitter();
|
|
352
|
+
// ── Visibilita' overlay ─────────────────────────────────────────────────
|
|
353
|
+
/** True quando il backend ha confermato firstRun=true e dobbiamo renderizzare il form. */
|
|
354
|
+
showFirstRunInstall = false;
|
|
355
|
+
/** True durante il bootstrap iniziale (probe FirstRunStatus + setup form). */
|
|
356
|
+
firstRunLoading = true;
|
|
357
|
+
/** True durante la POST configure_wuic (disabilita CTA + input). */
|
|
358
|
+
firstRunInstalling = false;
|
|
359
|
+
/** True durante il "test connessione e carica DB" in flight. */
|
|
360
|
+
firstRunDbLoading = false;
|
|
361
|
+
firstRunConnectionTesting = false;
|
|
362
|
+
/** True dopo che il test connessione e' andato a buon fine. Gate del DB select. */
|
|
363
|
+
firstRunConnectionValid = false;
|
|
364
|
+
// ── Banner errore con dismiss ────────────────────────────────────────────
|
|
365
|
+
_firstRunError = '';
|
|
366
|
+
firstRunErrorDismissed = false;
|
|
367
|
+
get firstRunError() { return this._firstRunError; }
|
|
368
|
+
set firstRunError(value) {
|
|
369
|
+
const next = value ?? '';
|
|
370
|
+
if (next !== this._firstRunError) {
|
|
371
|
+
this.firstRunErrorDismissed = false;
|
|
372
|
+
}
|
|
373
|
+
this._firstRunError = next;
|
|
374
|
+
}
|
|
375
|
+
dismissFirstRunError() { this.firstRunErrorDismissed = true; }
|
|
376
|
+
// ── Opzioni form (defaultate, restrette dal probe FirstRunStatus) ───────
|
|
377
|
+
firstRunDbmsOptions = [
|
|
378
|
+
{ label: 'Microsoft SQL Server', value: 'mssql' },
|
|
379
|
+
{ label: 'MySQL', value: 'mysql' },
|
|
380
|
+
{ label: 'Oracle', value: 'oracle' },
|
|
381
|
+
{ label: 'PostgreSQL', value: 'postgres' }
|
|
382
|
+
];
|
|
383
|
+
firstRunAdminLanguageOptions = [
|
|
384
|
+
{ label: 'Italiano', value: 'it-IT' },
|
|
385
|
+
{ label: 'English', value: 'en-US' },
|
|
386
|
+
{ label: 'Français', value: 'fr-FR' },
|
|
387
|
+
{ label: 'Español', value: 'es-ES' },
|
|
388
|
+
{ label: 'Deutsch', value: 'de-DE' }
|
|
389
|
+
];
|
|
390
|
+
/** Rebuilt on every language change: see refreshLocalizedOptions(). */
|
|
391
|
+
firstRunSetupModeOptions = [
|
|
392
|
+
{ label: FIRST_RUN_UI_BUNDLES['en-US'].firstrun.ui['setup_mode_existing'], value: 'existing' },
|
|
393
|
+
{ label: 'Tutorial WideWorldImporters', value: 'tutorial' }
|
|
394
|
+
];
|
|
395
|
+
firstRunTutorialAvailable = true;
|
|
396
|
+
firstRunForm = {
|
|
397
|
+
setupMode: 'existing',
|
|
398
|
+
createTutorialIfMissing: true,
|
|
399
|
+
dbms: 'mssql',
|
|
400
|
+
dataConnectionString: '',
|
|
401
|
+
dataDbName: '',
|
|
402
|
+
tutorialDataDbName: 'WideWorldImporters',
|
|
403
|
+
tutorialMetadataDbName: 'MetadataCRM',
|
|
404
|
+
metadataDbName: 'metadataDB',
|
|
405
|
+
adminUsername: 'admin',
|
|
406
|
+
adminPassword: '',
|
|
407
|
+
adminLanguage: WuicFirstRunWizardComponent.resolveDefaultAdminLanguage(),
|
|
408
|
+
// On by default: whoever points the wizard at an existing database wants its tables to
|
|
409
|
+
// become routes. With this off, accepting the defaults produced an application with no
|
|
410
|
+
// routes at all (journey win-vscode-src, 2026-09-07). Tutorial mode ignores the flag.
|
|
411
|
+
scaffoldExistingDatabase: true,
|
|
412
|
+
anthropicApiKey: '',
|
|
413
|
+
// Crash reporting opt-in (skill crash-reporting Commit 8). OFF by default
|
|
414
|
+
// per GDPR: l'utente lo abilita esplicitamente in fase di first-install
|
|
415
|
+
// dopo aver letto il disclaimer (link/expand inline nel template). Se
|
|
416
|
+
// abilitato qui, il backend writer di appsettings setta
|
|
417
|
+
// `CrashReporting.Enabled=true` e `DisclaimerAcceptedVersion="1.0"` +
|
|
418
|
+
// `DisclaimerAcceptedAt=<now>` (audit trail) — equivalente al flusso
|
|
419
|
+
// appsettings-editor (Commit 9), centralizzato cosi' che il primo
|
|
420
|
+
// boot non richieda un secondo round-trip dialog.
|
|
421
|
+
enableCrashReporting: false,
|
|
422
|
+
};
|
|
423
|
+
/**
|
|
424
|
+
* Visibilita' del popup disclaimer GDPR del crash-reporting (apre al check
|
|
425
|
+
* del checkbox nel form). Vedi `crashReportingLabels` per il razionale dei
|
|
426
|
+
* testi hardcoded.
|
|
427
|
+
*/
|
|
428
|
+
crashReportingDisclaimerVisible = false;
|
|
429
|
+
/**
|
|
430
|
+
* Labels HARDCODED per il disclaimer GDPR del crash-reporting.
|
|
431
|
+
*
|
|
432
|
+
* Razionale: il first-run wizard e' l'UNICA pagina che gira PRIMA che il
|
|
433
|
+
* DB metadati esista (`MetaDataSQLConnection` ancora a placeholder
|
|
434
|
+
* `__SET_CONNECTION_STRING__`). La `wuic_translation` table NON e'
|
|
435
|
+
* interrogabile in quello stato — qualunque chiave registrata li' viene
|
|
436
|
+
* mostrata grezza dal `TranslationManagerService` (fallback "ritorna la
|
|
437
|
+
* chiave"). Le altre label del wizard funzionano via `ngx-translate`
|
|
438
|
+
* con i dictionary statici bundlati nell'app, ma non abbiamo (al
|
|
439
|
+
* momento) un dictionary GDPR multilingua bundlato — quindi niente
|
|
440
|
+
* fallback. Soluzione: traduzioni inline in TS, switch sulla lingua
|
|
441
|
+
* scelta dall'admin nel select "Lingua admin iniziale".
|
|
442
|
+
*
|
|
443
|
+
* Contratto: `getter` ricalcola al cambio di `firstRunForm.adminLanguage`
|
|
444
|
+
* (che lega il select del wizard). Default 'en-US' se sconosciuta.
|
|
445
|
+
*/
|
|
446
|
+
get crashReportingLabels() {
|
|
447
|
+
const dict = this.crashReportingDictionary;
|
|
448
|
+
const key = (this.firstRunForm.adminLanguage || 'en-US');
|
|
449
|
+
return dict[key] || dict['en-US'];
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Dictionary inline italian/english/french/spanish/german. Lingue
|
|
453
|
+
* supportate matchano `getSupportedLanguages` del backend. Bump
|
|
454
|
+
* `disclaimerVersion` (lato backend `CrashReportingOptions.CurrentDisclaimerVersion`)
|
|
455
|
+
* se cambi sostanzialmente i testi qui — invalida i consensi precedenti
|
|
456
|
+
* e forza re-prompt al login successivo.
|
|
457
|
+
*/
|
|
458
|
+
crashReportingDictionary = {
|
|
459
|
+
'it-IT': {
|
|
460
|
+
title: 'Segnalazione errori automatica',
|
|
461
|
+
checkbox_accept: 'Acconsento all\'invio anonimo dei rapporti di errore',
|
|
462
|
+
intro_short: 'WUIC può inviare automaticamente i dettagli tecnici degli errori al server privato del produttore.',
|
|
463
|
+
read_full: 'Leggi tutto',
|
|
464
|
+
intro: 'Per migliorare la stabilità del prodotto, WUIC può inviare automaticamente i dettagli tecnici degli errori che si verificano nella tua applicazione al nostro server privato. Nessun contenuto personale o di business viene trasmesso. La funzione è opzionale e disattivabile in qualsiasi momento.',
|
|
465
|
+
what_we_send: 'Cosa viene inviato:',
|
|
466
|
+
what_we_dont_send: 'Tipo eccezione, messaggio, stack trace, URL della richiesta, versione del framework, fingerprint anonimo della macchina (hash), user-agent del browser.',
|
|
467
|
+
where_it_goes: 'Dove va: server privato del fornitore (errors.wuic-framework.com), accessibile solo dal team di sviluppo del framework.',
|
|
468
|
+
retention: 'Conservazione: massimo 90 giorni, poi cancellazione automatica.',
|
|
469
|
+
legal_basis: 'Base giuridica: consenso esplicito (GDPR art. 6.1.a). Puoi revocarlo in qualsiasi momento.',
|
|
470
|
+
how_to_disable: 'Come disattivare: dal menu Impostazioni → Configurazione applicazione, togli il flag "CrashReporting.Enabled".',
|
|
471
|
+
btn_accept: 'Accetto e attivo',
|
|
472
|
+
btn_decline: 'Rifiuto'
|
|
473
|
+
},
|
|
474
|
+
'en-US': {
|
|
475
|
+
title: 'Automatic error reporting',
|
|
476
|
+
checkbox_accept: 'I consent to anonymous error report transmission',
|
|
477
|
+
intro_short: 'WUIC can automatically send technical error details to the vendor\'s private server.',
|
|
478
|
+
read_full: 'Read full text',
|
|
479
|
+
intro: 'To improve product stability, WUIC can automatically send technical details about errors that occur in your application to our private server. No personal or business content is transmitted. This feature is optional and can be disabled at any time.',
|
|
480
|
+
what_we_send: 'What gets sent:',
|
|
481
|
+
what_we_dont_send: 'Exception type, message, stack trace, request URL, framework version, anonymous machine fingerprint (hash), browser user-agent.',
|
|
482
|
+
where_it_goes: 'Where it goes: vendor\'s private server (errors.wuic-framework.com), accessible only by the framework development team.',
|
|
483
|
+
retention: 'Retention: up to 90 days, then automatic deletion.',
|
|
484
|
+
legal_basis: 'Legal basis: explicit consent (GDPR art. 6.1.a). You can revoke it at any time.',
|
|
485
|
+
how_to_disable: 'How to disable: from Settings → Application configuration, untick the "CrashReporting.Enabled" flag.',
|
|
486
|
+
btn_accept: 'Accept and enable',
|
|
487
|
+
btn_decline: 'Decline'
|
|
488
|
+
},
|
|
489
|
+
'fr-FR': {
|
|
490
|
+
title: 'Signalement automatique des erreurs',
|
|
491
|
+
checkbox_accept: 'J\'accepte la transmission anonyme des rapports d\'erreur',
|
|
492
|
+
intro_short: 'WUIC peut envoyer automatiquement les détails techniques des erreurs au serveur privé du fournisseur.',
|
|
493
|
+
read_full: 'Lire le texte complet',
|
|
494
|
+
intro: 'Pour améliorer la stabilité du produit, WUIC peut envoyer automatiquement les détails techniques des erreurs survenant dans votre application à notre serveur privé. Aucun contenu personnel ou métier n\'est transmis. Cette fonction est optionnelle et désactivable à tout moment.',
|
|
495
|
+
what_we_send: 'Ce qui est envoyé :',
|
|
496
|
+
what_we_dont_send: 'Type d\'exception, message, stack trace, URL de la requête, version du framework, empreinte anonyme de la machine (hash), user-agent du navigateur.',
|
|
497
|
+
where_it_goes: 'Destination : serveur privé du fournisseur (errors.wuic-framework.com), accessible uniquement par l\'équipe de développement.',
|
|
498
|
+
retention: 'Rétention : 90 jours maximum, puis suppression automatique.',
|
|
499
|
+
legal_basis: 'Base juridique : consentement explicite (RGPD art. 6.1.a). Révocable à tout moment.',
|
|
500
|
+
how_to_disable: 'Comment désactiver : Paramètres → Configuration de l\'application, décochez "CrashReporting.Enabled".',
|
|
501
|
+
btn_accept: 'Accepter et activer',
|
|
502
|
+
btn_decline: 'Refuser'
|
|
503
|
+
},
|
|
504
|
+
'es-ES': {
|
|
505
|
+
title: 'Notificación automática de errores',
|
|
506
|
+
checkbox_accept: 'Acepto la transmisión anónima de informes de error',
|
|
507
|
+
intro_short: 'WUIC puede enviar automáticamente los detalles técnicos de los errores al servidor privado del fabricante.',
|
|
508
|
+
read_full: 'Leer texto completo',
|
|
509
|
+
intro: 'Para mejorar la estabilidad del producto, WUIC puede enviar automáticamente los detalles técnicos de los errores que ocurren en su aplicación a nuestro servidor privado. No se transmite ningún contenido personal o de negocio. Esta función es opcional y se puede desactivar en cualquier momento.',
|
|
510
|
+
what_we_send: 'Qué se envía:',
|
|
511
|
+
what_we_dont_send: 'Tipo de excepción, mensaje, stack trace, URL de la solicitud, versión del framework, huella anónima de la máquina (hash), user-agent del navegador.',
|
|
512
|
+
where_it_goes: 'A dónde va: servidor privado del fabricante (errors.wuic-framework.com), accesible solo por el equipo de desarrollo.',
|
|
513
|
+
retention: 'Retención: hasta 90 días, después eliminación automática.',
|
|
514
|
+
legal_basis: 'Base jurídica: consentimiento explícito (RGPD art. 6.1.a). Revocable en cualquier momento.',
|
|
515
|
+
how_to_disable: 'Cómo desactivar: Ajustes → Configuración de la aplicación, desmarque "CrashReporting.Enabled".',
|
|
516
|
+
btn_accept: 'Aceptar y activar',
|
|
517
|
+
btn_decline: 'Rechazar'
|
|
518
|
+
},
|
|
519
|
+
'de-DE': {
|
|
520
|
+
title: 'Automatische Fehlermeldung',
|
|
521
|
+
checkbox_accept: 'Ich willige in die anonyme Übertragung von Fehlerberichten ein',
|
|
522
|
+
intro_short: 'WUIC kann technische Fehlerdetails automatisch an den privaten Server des Anbieters senden.',
|
|
523
|
+
read_full: 'Vollständigen Text lesen',
|
|
524
|
+
intro: 'Zur Verbesserung der Produktstabilität kann WUIC technische Details zu Fehlern in Ihrer Anwendung automatisch an unseren privaten Server senden. Keine persönlichen oder geschäftlichen Inhalte werden übertragen. Diese Funktion ist optional und jederzeit deaktivierbar.',
|
|
525
|
+
what_we_send: 'Was gesendet wird:',
|
|
526
|
+
what_we_dont_send: 'Ausnahmetyp, Nachricht, Stack-Trace, Anfrage-URL, Framework-Version, anonymer Maschinen-Fingerabdruck (Hash), Browser-User-Agent.',
|
|
527
|
+
where_it_goes: 'Wohin: privater Server des Anbieters (errors.wuic-framework.com), nur für das Entwicklungsteam zugänglich.',
|
|
528
|
+
retention: 'Aufbewahrung: max. 90 Tage, dann automatische Löschung.',
|
|
529
|
+
legal_basis: 'Rechtsgrundlage: ausdrückliche Einwilligung (DSGVO Art. 6.1.a). Jederzeit widerrufbar.',
|
|
530
|
+
how_to_disable: 'Deaktivieren: Einstellungen → Anwendungskonfiguration, Flag "CrashReporting.Enabled" deaktivieren.',
|
|
531
|
+
btn_accept: 'Akzeptieren und aktivieren',
|
|
532
|
+
btn_decline: 'Ablehnen'
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
/**
|
|
536
|
+
* Click sul checkbox del consenso. Comportamento:
|
|
537
|
+
* - check (off → on) : apre il popup disclaimer; il flag viene davvero
|
|
538
|
+
* attivato SOLO quando l'utente preme "Accetto" nel dialog. Se chiude
|
|
539
|
+
* o annulla, il flag resta off.
|
|
540
|
+
* - uncheck (on → off): no popup, opt-out immediato.
|
|
541
|
+
*/
|
|
542
|
+
onCrashReportingToggle(newValue) {
|
|
543
|
+
if (newValue) {
|
|
544
|
+
// Tenuto false finche' l'utente non conferma nel dialog. Cosi' il
|
|
545
|
+
// checkbox NON appare gia' flaggato mentre l'utente legge il
|
|
546
|
+
// disclaimer — l'attivazione e' un atto esplicito post-lettura.
|
|
547
|
+
this.firstRunForm.enableCrashReporting = false;
|
|
548
|
+
this.crashReportingDisclaimerVisible = true;
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
this.firstRunForm.enableCrashReporting = false;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
openCrashReportingDisclaimer() {
|
|
555
|
+
this.crashReportingDisclaimerVisible = true;
|
|
556
|
+
}
|
|
557
|
+
acceptCrashReporting() {
|
|
558
|
+
this.firstRunForm.enableCrashReporting = true;
|
|
559
|
+
this.crashReportingDisclaimerVisible = false;
|
|
560
|
+
}
|
|
561
|
+
declineCrashReporting() {
|
|
562
|
+
this.firstRunForm.enableCrashReporting = false;
|
|
563
|
+
this.crashReportingDisclaimerVisible = false;
|
|
564
|
+
}
|
|
565
|
+
firstRunDataDbOptions = [];
|
|
566
|
+
firstRunRealPath = '';
|
|
567
|
+
/** Stato progress bar polled da `GET /api/Meta/FirstRunProgress`. */
|
|
568
|
+
firstRunProgress = {
|
|
569
|
+
active: false,
|
|
570
|
+
phase: 'idle',
|
|
571
|
+
current: 0,
|
|
572
|
+
total: 0,
|
|
573
|
+
percent: 0,
|
|
574
|
+
message: '',
|
|
575
|
+
elapsedMs: 0,
|
|
576
|
+
finished: false,
|
|
577
|
+
failed: false,
|
|
578
|
+
error: null
|
|
579
|
+
};
|
|
580
|
+
firstRunProgressTimer = null;
|
|
581
|
+
constructor(http, translate, messageService, injector) {
|
|
582
|
+
this.http = http;
|
|
583
|
+
this.translate = translate;
|
|
584
|
+
this.messageService = messageService;
|
|
585
|
+
this.injector = injector;
|
|
586
|
+
}
|
|
587
|
+
/** Disposer del delegate registrato sul GlobalHandler — vedi ngOnInit. */
|
|
588
|
+
metadataDbExistsDelegateDisposer;
|
|
589
|
+
/** Anti-loop: il prompt e' fire-and-forget; senza guardia, ricliccare
|
|
590
|
+
* "Sì" ad un secondo trigger lancerebbe re-submit ricorsivi. */
|
|
591
|
+
metadataDbExistsPromptInFlight = false;
|
|
592
|
+
ngOnInit() {
|
|
593
|
+
// Bundle traduzioni firstRun PRIMA che il template renda i pipe `| translate`.
|
|
594
|
+
// Necessario perche' in firstRun il DB metadati e' vuoto e il fetch
|
|
595
|
+
// `MetaService.GetTranslation` ritorna [] (gestito gia' a TranslationManager
|
|
596
|
+
// livello, ma queste sono chiavi dedicate al wizard non presenti nel
|
|
597
|
+
// FIRST_RUN_FALLBACK del manager).
|
|
598
|
+
this.registerFirstRunTranslations();
|
|
599
|
+
// Delegate sul GlobalHandler che intercetta il marker
|
|
600
|
+
// `METADATA_DB_EXISTS_CONFIRM_REQUIRED` lanciato dal server da
|
|
601
|
+
// `MetaService.configure_wuic` quando il metadata DB target esiste gia'.
|
|
602
|
+
// Senza questo, l'introduzione del JsonExceptionFilter (skill exception-
|
|
603
|
+
// handling) wrappa il marker in `args.message` e l'errore arriverebbe
|
|
604
|
+
// all'utente come dialog generico `errors.server.unhandled` invece del
|
|
605
|
+
// prompt di conferma drop+recreate.
|
|
606
|
+
this.metadataDbExistsDelegateDisposer = GlobalHandler.registerDelegate((ctx) => {
|
|
607
|
+
if (!ctx.message?.includes('METADATA_DB_EXISTS_CONFIRM_REQUIRED'))
|
|
608
|
+
return false;
|
|
609
|
+
// Async fire-and-forget: ritorno true (sopprime il dialog default),
|
|
610
|
+
// poi prompt + retry submit.
|
|
611
|
+
void this.handleMetadataDbExistsPrompt(ctx);
|
|
612
|
+
return true;
|
|
613
|
+
}, { priority: 100 });
|
|
614
|
+
// Hook post-login per consumare `logs/rag-setup-pending.json`.
|
|
615
|
+
// Probe FirstRunStatus + bootstrap form.
|
|
616
|
+
void this.bootstrapFirstRun();
|
|
617
|
+
}
|
|
618
|
+
ngOnDestroy() {
|
|
619
|
+
this.stopFirstRunProgressPolling();
|
|
620
|
+
this.metadataDbExistsDelegateDisposer?.();
|
|
621
|
+
}
|
|
622
|
+
/** Mostra il prompt di conferma drop+recreate del metadata DB e, se
|
|
623
|
+
* l'utente conferma, ri-lancia l'install con dropExistingMetadataDb=true.
|
|
624
|
+
* Triggered dal delegate sul GlobalHandler quando il server emette il
|
|
625
|
+
* marker `METADATA_DB_EXISTS_CONFIRM_REQUIRED:<dbName>`. */
|
|
626
|
+
async handleMetadataDbExistsPrompt(ctx) {
|
|
627
|
+
if (this.metadataDbExistsPromptInFlight)
|
|
628
|
+
return;
|
|
629
|
+
this.metadataDbExistsPromptInFlight = true;
|
|
630
|
+
try {
|
|
631
|
+
// dbName: prima cerca in args (envelope tipizzato moderno),
|
|
632
|
+
// poi parsea il marker stringa `METADATA_DB_EXISTS_CONFIRM_REQUIRED:<dbName>`,
|
|
633
|
+
// poi fallback al nome dal form.
|
|
634
|
+
let dbName = (ctx.args && (ctx.args.dbName || ctx.args.metadataDbName)) || '';
|
|
635
|
+
if (!dbName) {
|
|
636
|
+
const m = /METADATA_DB_EXISTS_CONFIRM_REQUIRED:([^\s|"\\]+)/.exec(ctx.message || '');
|
|
637
|
+
if (m)
|
|
638
|
+
dbName = m[1];
|
|
639
|
+
}
|
|
640
|
+
if (!dbName)
|
|
641
|
+
dbName = this.firstRunForm.metadataDbName || 'metadataDB';
|
|
642
|
+
const promptResult = await WtoolboxService.promptDialog(this.translate.instant('firstrun.ui.confirm_recreate_title'), [
|
|
643
|
+
{
|
|
644
|
+
name: 'confirmDrop',
|
|
645
|
+
caption: this.translate.instant('firstrun.ui.confirm_recreate_question', { db: dbName }),
|
|
646
|
+
type: 'dictionary_radio',
|
|
647
|
+
value: 'no',
|
|
648
|
+
required: true,
|
|
649
|
+
dictionaryData: [
|
|
650
|
+
{ label: this.translate.instant('firstrun.ui.no'), value: 'no' },
|
|
651
|
+
{ label: this.translate.instant('firstrun.ui.yes'), value: 'yes' }
|
|
652
|
+
]
|
|
653
|
+
}
|
|
654
|
+
], '620px', '400px');
|
|
655
|
+
const rawConfirm = promptResult?.confirmDrop;
|
|
656
|
+
let confirmDropValue = rawConfirm;
|
|
657
|
+
if (rawConfirm && typeof rawConfirm === 'object' && 'value' in rawConfirm && typeof (rawConfirm).value !== 'function') {
|
|
658
|
+
confirmDropValue = (rawConfirm).value;
|
|
659
|
+
}
|
|
660
|
+
else if (rawConfirm && typeof (rawConfirm).getValue === 'function') {
|
|
661
|
+
confirmDropValue = (rawConfirm).getValue();
|
|
662
|
+
}
|
|
663
|
+
if (confirmDropValue && typeof confirmDropValue === 'object' && 'value' in confirmDropValue) {
|
|
664
|
+
confirmDropValue = (confirmDropValue).value;
|
|
665
|
+
}
|
|
666
|
+
const confirmDrop = String(confirmDropValue ?? 'no').toLowerCase() === 'yes';
|
|
667
|
+
if (confirmDrop) {
|
|
668
|
+
await this.submitFirstRunInstallInternal(true);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
finally {
|
|
672
|
+
this.metadataDbExistsPromptInFlight = false;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
676
|
+
// STATIC HELPERS
|
|
677
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
678
|
+
/**
|
|
679
|
+
* Default della lingua admin basato su `navigator.language`. Mappa su uno
|
|
680
|
+
* dei 5 tag IETF supportati (it/en/fr/es/de). Module-scope statico (chiamato
|
|
681
|
+
* in field-initializer di firstRunForm).
|
|
682
|
+
*/
|
|
683
|
+
static resolveDefaultAdminLanguage() {
|
|
684
|
+
const raw = (typeof navigator !== 'undefined' ? (navigator.language || '') : '').toLowerCase();
|
|
685
|
+
if (raw.startsWith('it'))
|
|
686
|
+
return 'it-IT';
|
|
687
|
+
if (raw.startsWith('en'))
|
|
688
|
+
return 'en-US';
|
|
689
|
+
if (raw.startsWith('fr'))
|
|
690
|
+
return 'fr-FR';
|
|
691
|
+
if (raw.startsWith('es'))
|
|
692
|
+
return 'es-ES';
|
|
693
|
+
if (raw.startsWith('de'))
|
|
694
|
+
return 'de-DE';
|
|
695
|
+
return 'en-US'; // unknown browser language: English, not Italian
|
|
696
|
+
}
|
|
697
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
698
|
+
// BOOTSTRAP / PROBE
|
|
699
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
700
|
+
async bootstrapFirstRun() {
|
|
701
|
+
this.firstRunLoading = true;
|
|
702
|
+
this.firstRunError = '';
|
|
703
|
+
const apiBase = WtoolboxService.appSettings?.api_url || '';
|
|
704
|
+
const globalRoot = WtoolboxService.appSettings?.global_root_url || '';
|
|
705
|
+
try {
|
|
706
|
+
// firstRun VINCE sull'auth: su una prima installazione genuina il (nuovo) DB
|
|
707
|
+
// metadati non ha utenti legittimi, quindi un cookie/sessione residua
|
|
708
|
+
// (`Auth/Me` -> authenticated=true) NON deve dirottare l'app sul login saltando
|
|
709
|
+
// il wizard. La decisione si basa SOLO su FirstRunStatus; l'eventuale stato di
|
|
710
|
+
// auth e' irrilevante (quando firstRun=false il wizard si salta comunque sotto).
|
|
711
|
+
const publicStatus = await firstValueFrom(this.http.get(`${apiBase}Meta/FirstRunStatus`, this.suppressGlobalErrorOpts()));
|
|
712
|
+
const settings = this.parseDictionaryResponse(publicStatus);
|
|
713
|
+
const firstRunSetting = settings['firstRun'] ?? settings['first-run'] ?? settings['firstrun'];
|
|
714
|
+
const isFirstRun = this.toBoolean(firstRunSetting);
|
|
715
|
+
this.firstRunRealPath = String(settings['projectDataFolder'] || settings['project-data-folder'] || '').trim();
|
|
716
|
+
const currentDbms = String(settings['dbms'] || '').trim().toLowerCase();
|
|
717
|
+
if (['mssql', 'mysql', 'oracle', 'postgres', 'postgresql'].includes(currentDbms)) {
|
|
718
|
+
this.firstRunForm.dbms = currentDbms === 'postgresql' ? 'postgres' : currentDbms;
|
|
719
|
+
}
|
|
720
|
+
const tutorialFlag = settings['tutorialAvailable'] ?? settings['tutorialavailable'];
|
|
721
|
+
this.firstRunTutorialAvailable = tutorialFlag === undefined ? true : this.toBoolean(tutorialFlag);
|
|
722
|
+
if (!this.firstRunTutorialAvailable) {
|
|
723
|
+
this.firstRunSetupModeOptions = [{ label: this.translate.instant('firstrun.ui.setup_mode_existing'), value: 'existing' }];
|
|
724
|
+
this.firstRunForm.setupMode = 'existing';
|
|
725
|
+
}
|
|
726
|
+
if (!isFirstRun) {
|
|
727
|
+
this.showFirstRunInstall = false;
|
|
728
|
+
this.complete.emit();
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
// Filter DBMS dropdown to providers actually deployed.
|
|
732
|
+
try {
|
|
733
|
+
const availableResp = await firstValueFrom(this.http.get(`${apiBase}Meta/AvailableDbms`, this.suppressGlobalErrorOpts()));
|
|
734
|
+
const availableList = availableResp?.dbms || [];
|
|
735
|
+
if (Array.isArray(availableList) && availableList.length > 0) {
|
|
736
|
+
const filtered = availableList.map(entry => {
|
|
737
|
+
const id = String(entry?.id || '').trim().toLowerCase();
|
|
738
|
+
const label = String(entry?.label || id).trim();
|
|
739
|
+
const value = id === 'postgresql' ? 'postgres' : id;
|
|
740
|
+
return { label, value };
|
|
741
|
+
}).filter(opt => !!opt.value);
|
|
742
|
+
if (filtered.length > 0) {
|
|
743
|
+
this.firstRunDbmsOptions = filtered;
|
|
744
|
+
if (!filtered.some(opt => opt.value === this.firstRunForm.dbms)) {
|
|
745
|
+
this.firstRunForm.dbms = filtered[0].value;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
catch {
|
|
751
|
+
// older backend without AvailableDbms — leave hardcoded list
|
|
752
|
+
}
|
|
753
|
+
this.showFirstRunInstall = true;
|
|
754
|
+
await this.loadDefaultConnectionString(settings);
|
|
755
|
+
}
|
|
756
|
+
catch {
|
|
757
|
+
// Legacy fallback for environments where Meta/FirstRunStatus is unavailable.
|
|
758
|
+
try {
|
|
759
|
+
const appSettingsResponse = await firstValueFrom(this.http.post(`${globalRoot}MetaService.getAppSettings`, {}, this.suppressGlobalErrorOpts()));
|
|
760
|
+
const settings = this.parseDictionaryResponse(appSettingsResponse);
|
|
761
|
+
const firstRunSetting = settings['firstRun'] ?? settings['first-run'] ?? settings['firstrun'];
|
|
762
|
+
const isFirstRun = this.toBoolean(firstRunSetting);
|
|
763
|
+
this.firstRunRealPath = String(settings['projectDataFolder'] || settings['project-data-folder'] || '').trim();
|
|
764
|
+
const currentDbms = String(settings['dbms'] || '').trim().toLowerCase();
|
|
765
|
+
if (['mssql', 'mysql', 'oracle', 'postgres', 'postgresql'].includes(currentDbms)) {
|
|
766
|
+
this.firstRunForm.dbms = currentDbms === 'postgresql' ? 'postgres' : currentDbms;
|
|
767
|
+
}
|
|
768
|
+
if (!isFirstRun) {
|
|
769
|
+
this.showFirstRunInstall = false;
|
|
770
|
+
this.complete.emit();
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
this.showFirstRunInstall = true;
|
|
774
|
+
await this.loadDefaultConnectionString(settings);
|
|
775
|
+
}
|
|
776
|
+
catch {
|
|
777
|
+
this.showFirstRunInstall = false;
|
|
778
|
+
this.complete.emit();
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
finally {
|
|
782
|
+
this.firstRunLoading = false;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
async loadDefaultConnectionString(settings) {
|
|
786
|
+
const fallbackConn = String(settings['connection'] || '').trim();
|
|
787
|
+
const fallbackDb = String(settings['DataDBName'] || settings['datadbname'] || '').trim();
|
|
788
|
+
const fallbackMetaDb = String(settings['metaDataDBName'] || settings['metadataDbName'] || settings['MetaDataDBName']
|
|
789
|
+
|| this.firstRunForm.metadataDbName || 'MetadataCRM').trim();
|
|
790
|
+
if (fallbackConn && fallbackDb && !/initial\s+catalog\s*=|database\s*=|dbq\s*=/i.test(fallbackConn)) {
|
|
791
|
+
this.firstRunForm.dataConnectionString = `${fallbackConn};initial catalog=${fallbackDb}`;
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
this.firstRunForm.dataConnectionString = fallbackConn;
|
|
795
|
+
}
|
|
796
|
+
const parsed = this.parseConnectionString(this.firstRunForm.dataConnectionString || '');
|
|
797
|
+
this.firstRunForm.dataDbName = parsed.databaseName || fallbackDb || '';
|
|
798
|
+
this.firstRunForm.metadataDbName = fallbackMetaDb || this.firstRunForm.metadataDbName || 'MetadataCRM';
|
|
799
|
+
this.firstRunForm.tutorialDataDbName = this.firstRunForm.dataDbName || 'WideWorldImporters';
|
|
800
|
+
this.firstRunForm.tutorialMetadataDbName = String(this.firstRunForm.metadataDbName || 'MetadataCRM').trim() || 'MetadataCRM';
|
|
801
|
+
}
|
|
802
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
803
|
+
// FORM EVENT HANDLERS
|
|
804
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
805
|
+
onFirstRunConnectionChanged(value) {
|
|
806
|
+
this.firstRunForm.dataConnectionString = value;
|
|
807
|
+
const parsed = this.parseConnectionString(value || '');
|
|
808
|
+
this.firstRunForm.dataDbName = parsed.databaseName || '';
|
|
809
|
+
this.firstRunDataDbOptions = [];
|
|
810
|
+
this.firstRunConnectionValid = false;
|
|
811
|
+
}
|
|
812
|
+
onFirstRunSetupModeChanged(value) {
|
|
813
|
+
const mode = String(value || '').trim().toLowerCase() === 'tutorial' ? 'tutorial' : 'existing';
|
|
814
|
+
this.firstRunForm.setupMode = mode;
|
|
815
|
+
if (mode === 'tutorial') {
|
|
816
|
+
this.firstRunForm.tutorialDataDbName = String(this.firstRunForm.tutorialDataDbName || 'WideWorldImporters').trim();
|
|
817
|
+
this.firstRunForm.tutorialMetadataDbName = String(this.firstRunForm.tutorialMetadataDbName || this.firstRunForm.metadataDbName || 'MetadataCRM').trim();
|
|
818
|
+
this.firstRunForm.dataDbName = this.firstRunForm.tutorialDataDbName;
|
|
819
|
+
this.firstRunForm.metadataDbName = this.firstRunForm.tutorialMetadataDbName;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
onFirstRunDbmsChanged(value) {
|
|
823
|
+
const normalizedDbms = this.normalizeDbms(value);
|
|
824
|
+
this.firstRunForm.dbms = normalizedDbms === 'postgresql' ? 'postgres' : normalizedDbms;
|
|
825
|
+
const parsed = this.parseConnectionString(this.firstRunForm.dataConnectionString || '');
|
|
826
|
+
const preferredDatabaseName = String(this.firstRunForm.dataDbName || parsed.databaseName || '').trim();
|
|
827
|
+
this.firstRunForm.dataConnectionString = this.buildProviderConnectionString(normalizedDbms, {
|
|
828
|
+
dataSource: parsed.dataSource,
|
|
829
|
+
port: parsed.port,
|
|
830
|
+
databaseName: preferredDatabaseName,
|
|
831
|
+
userId: parsed.userId,
|
|
832
|
+
password: parsed.password,
|
|
833
|
+
integratedSecurity: parsed.integratedSecurity && normalizedDbms === 'mssql'
|
|
834
|
+
});
|
|
835
|
+
this.firstRunForm.dataDbName = preferredDatabaseName;
|
|
836
|
+
this.firstRunDataDbOptions = [];
|
|
837
|
+
this.firstRunConnectionValid = false;
|
|
838
|
+
}
|
|
839
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
840
|
+
// TEST CONNECTION
|
|
841
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
842
|
+
async testFirstRunConnectionAndLoadDatabases() {
|
|
843
|
+
this.firstRunError = '';
|
|
844
|
+
this.firstRunConnectionValid = false;
|
|
845
|
+
this.firstRunDataDbOptions = [];
|
|
846
|
+
const dbms = this.normalizeDbms(this.firstRunForm.dbms);
|
|
847
|
+
const rawConnectionString = String(this.firstRunForm.dataConnectionString || '').trim();
|
|
848
|
+
if (!rawConnectionString) {
|
|
849
|
+
this.firstRunError = this.translate.instant('firstrun.ui.msg_connection_required');
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const baseConnection = this.buildConnectionWithoutDatabase(rawConnectionString, dbms);
|
|
853
|
+
if (!baseConnection) {
|
|
854
|
+
this.firstRunError = this.translate.instant('firstrun.ui.msg_connection_unparsable');
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
this.firstRunConnectionTesting = true;
|
|
858
|
+
this.firstRunDbLoading = true;
|
|
859
|
+
try {
|
|
860
|
+
const databases = await this.fetchDatabaseNames(dbms, baseConnection);
|
|
861
|
+
this.firstRunDataDbOptions = databases.map(name => ({ label: name, value: name }));
|
|
862
|
+
this.firstRunConnectionValid = true;
|
|
863
|
+
if (!this.firstRunDataDbOptions.some(x => x.value === this.firstRunForm.dataDbName)) {
|
|
864
|
+
this.firstRunForm.dataDbName = this.firstRunDataDbOptions[0]?.value || this.firstRunForm.dataDbName || '';
|
|
865
|
+
}
|
|
866
|
+
this.messageService.add({
|
|
867
|
+
severity: 'success',
|
|
868
|
+
summary: this.translate.instant('firstrun.ui.msg_connection_ok'),
|
|
869
|
+
detail: this.translate.instant('firstrun.ui.msg_connection_ok_detail', { count: this.firstRunDataDbOptions.length })
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
catch (error) {
|
|
873
|
+
const detail = this.extractErrorMessage(error);
|
|
874
|
+
const canAutoFixCert = dbms === 'mssql' && this.isSqlServerCertificateError(detail);
|
|
875
|
+
if (canAutoFixCert) {
|
|
876
|
+
const fixedDataConnectionString = this.applySqlServerCertificateFix(rawConnectionString);
|
|
877
|
+
const fixedBaseConnection = this.buildConnectionWithoutDatabase(fixedDataConnectionString, dbms);
|
|
878
|
+
if (fixedBaseConnection) {
|
|
879
|
+
try {
|
|
880
|
+
const databases = await this.fetchDatabaseNames(dbms, fixedBaseConnection);
|
|
881
|
+
this.firstRunForm.dataConnectionString = fixedDataConnectionString;
|
|
882
|
+
this.firstRunDataDbOptions = databases.map(name => ({ label: name, value: name }));
|
|
883
|
+
this.firstRunConnectionValid = true;
|
|
884
|
+
if (!this.firstRunDataDbOptions.some(x => x.value === this.firstRunForm.dataDbName)) {
|
|
885
|
+
this.firstRunForm.dataDbName = this.firstRunDataDbOptions[0]?.value || this.firstRunForm.dataDbName || '';
|
|
886
|
+
}
|
|
887
|
+
this.messageService.add({
|
|
888
|
+
severity: 'success',
|
|
889
|
+
summary: this.translate.instant('firstrun.ui.msg_connection_fixed'),
|
|
890
|
+
detail: this.translate.instant('firstrun.ui.msg_connection_fixed_detail')
|
|
891
|
+
});
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
catch {
|
|
895
|
+
// fall through to original error path
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
this.firstRunConnectionValid = false;
|
|
900
|
+
this.firstRunDataDbOptions = [];
|
|
901
|
+
this.firstRunError = detail;
|
|
902
|
+
// Errore mostrato SOLO inline nel form (firstRunError): niente toast ne
|
|
903
|
+
// dialog globale (le chiamate usano suppressGlobalErrorOpts()).
|
|
904
|
+
}
|
|
905
|
+
finally {
|
|
906
|
+
this.firstRunDbLoading = false;
|
|
907
|
+
this.firstRunConnectionTesting = false;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Opzioni HTTP che sopprimono il dialog/handler globale (`GlobalHandler`)
|
|
912
|
+
* per le chiamate del first-run wizard. Gli errori dell'installer vengono
|
|
913
|
+
* mostrati SOLO inline nel form (`firstRunError`) — niente popup dialog
|
|
914
|
+
* (interceptor) ne toast. Vedi WUIC_SUPPRESS_GLOBAL_ERROR.
|
|
915
|
+
*/
|
|
916
|
+
suppressGlobalErrorOpts() {
|
|
917
|
+
return { context: new HttpContext().set(WUIC_SUPPRESS_GLOBAL_ERROR, true) };
|
|
918
|
+
}
|
|
919
|
+
async fetchDatabaseNames(dbms, baseConnection) {
|
|
920
|
+
const globalRoot = WtoolboxService.appSettings?.global_root_url || '';
|
|
921
|
+
const endpoint = `${globalRoot}MetaService.get_database_names`;
|
|
922
|
+
const payload = await firstValueFrom(this.http.post(endpoint, {
|
|
923
|
+
rdbDBMS: dbms,
|
|
924
|
+
connectionString: baseConnection
|
|
925
|
+
}, this.suppressGlobalErrorOpts()));
|
|
926
|
+
return this.parseStringArrayResponse(payload)
|
|
927
|
+
.map(x => String(x || '').trim())
|
|
928
|
+
.filter(x => !!x);
|
|
929
|
+
}
|
|
930
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
931
|
+
// SUBMIT INSTALL
|
|
932
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
933
|
+
async submitFirstRunInstall() {
|
|
934
|
+
await this.submitFirstRunInstallInternal(false);
|
|
935
|
+
}
|
|
936
|
+
async submitFirstRunInstallInternal(confirmDropExistingMetadataDb) {
|
|
937
|
+
this.firstRunError = '';
|
|
938
|
+
const dbms = this.normalizeDbms(this.firstRunForm.dbms);
|
|
939
|
+
const conn = this.parseConnectionString(this.firstRunForm.dataConnectionString || '');
|
|
940
|
+
const isTutorialMode = this.firstRunForm.setupMode === 'tutorial';
|
|
941
|
+
const selectedDataDbName = String(isTutorialMode
|
|
942
|
+
? (this.firstRunForm.tutorialDataDbName || this.firstRunForm.dataDbName || conn.databaseName || 'WideWorldImporters')
|
|
943
|
+
: (this.firstRunForm.dataDbName || conn.databaseName || '')).trim();
|
|
944
|
+
const selectedMetadataDbName = String(isTutorialMode
|
|
945
|
+
? (this.firstRunForm.tutorialMetadataDbName || this.firstRunForm.metadataDbName || 'MetadataCRM')
|
|
946
|
+
: (this.firstRunForm.metadataDbName || 'metadataDB')).trim();
|
|
947
|
+
if (!conn.integratedSecurity && (!conn.userId || !conn.password)) {
|
|
948
|
+
this.firstRunError = this.translate.instant('firstrun.ui.msg_credentials_required');
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
if (conn.integratedSecurity && dbms !== 'mssql') {
|
|
952
|
+
this.firstRunError = this.translate.instant('firstrun.ui.msg_integrated_security_mssql_only', { dbms });
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (!conn.dataSource) {
|
|
956
|
+
this.firstRunError = this.translate.instant('firstrun.ui.msg_data_source_missing');
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
if (!selectedDataDbName) {
|
|
960
|
+
this.firstRunError = this.translate.instant('firstrun.ui.msg_pick_data_db');
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (!this.firstRunConnectionValid) {
|
|
964
|
+
this.firstRunError = this.translate.instant('firstrun.ui.msg_test_connection_first');
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const adminUsername = String(this.firstRunForm.adminUsername || '').trim();
|
|
968
|
+
const adminPassword = String(this.firstRunForm.adminPassword || '');
|
|
969
|
+
if (!adminUsername) {
|
|
970
|
+
this.firstRunError = this.translate.instant('firstrun.ui.msg_admin_username_required');
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
if (!adminPassword || adminPassword.length < 4) {
|
|
974
|
+
this.firstRunError = this.translate.instant('firstrun.ui.msg_admin_password_required');
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
const dataBaseConnection = this.buildConnectionWithoutDatabase(this.firstRunForm.dataConnectionString || '', dbms);
|
|
978
|
+
if (!dataBaseConnection) {
|
|
979
|
+
this.firstRunError = this.translate.instant('firstrun.ui.msg_base_connection_failed');
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
this.firstRunInstalling = true;
|
|
983
|
+
this.startFirstRunProgressPolling();
|
|
984
|
+
try {
|
|
985
|
+
const globalRoot = WtoolboxService.appSettings?.global_root_url || '';
|
|
986
|
+
const endpoint = `${globalRoot}MetaService.configure_wuic`;
|
|
987
|
+
const realPath = (this.firstRunRealPath || '').trim();
|
|
988
|
+
await firstValueFrom(this.http.post(endpoint, {
|
|
989
|
+
license_email: 'first-run@local',
|
|
990
|
+
rdbDBMS: dbms,
|
|
991
|
+
conn_datasource: conn.dataSource,
|
|
992
|
+
port: conn.port,
|
|
993
|
+
conn_database_name: selectedDataDbName,
|
|
994
|
+
conn_user_id: conn.userId,
|
|
995
|
+
conn_password: conn.password,
|
|
996
|
+
conn_integrated_security: conn.integratedSecurity ? 'true' : 'false',
|
|
997
|
+
conn_data_base_connection_string: dataBaseConnection,
|
|
998
|
+
preScaffoldDB: true,
|
|
999
|
+
rdbDBMSMeta: dbms,
|
|
1000
|
+
conn_datasource_meta: conn.dataSource,
|
|
1001
|
+
portMeta: conn.port,
|
|
1002
|
+
conn_metadata_db_name: selectedMetadataDbName,
|
|
1003
|
+
conn_user_id_meta: conn.userId,
|
|
1004
|
+
conn_password_meta: conn.password,
|
|
1005
|
+
conn_integrated_security_meta: conn.integratedSecurity ? 'true' : 'false',
|
|
1006
|
+
psqlPath: '',
|
|
1007
|
+
theme: 'default',
|
|
1008
|
+
site_url: globalThis.location.origin,
|
|
1009
|
+
email_host: '',
|
|
1010
|
+
email_port: '',
|
|
1011
|
+
email_username: '',
|
|
1012
|
+
email_password: '',
|
|
1013
|
+
realPath,
|
|
1014
|
+
confirmDropExistingMetadataDb: confirmDropExistingMetadataDb ? 'true' : 'false',
|
|
1015
|
+
enableTutorialDbProvisioning: (isTutorialMode && this.firstRunForm.createTutorialIfMissing) ? 'true' : 'false',
|
|
1016
|
+
tutorialDataDbName: isTutorialMode ? selectedDataDbName : '',
|
|
1017
|
+
tutorialMetadataDbName: isTutorialMode ? selectedMetadataDbName : '',
|
|
1018
|
+
scaffoldTutorialDatabase: isTutorialMode ? 'true' : 'false',
|
|
1019
|
+
scaffoldExistingDatabase: (!isTutorialMode && this.firstRunForm.scaffoldExistingDatabase) ? 'true' : 'false',
|
|
1020
|
+
adminUsername,
|
|
1021
|
+
adminPassword,
|
|
1022
|
+
adminLanguage: this.firstRunForm.adminLanguage,
|
|
1023
|
+
anthropicApiKey: (this.firstRunForm.anthropicApiKey || '').trim(),
|
|
1024
|
+
// Crash reporting opt-in (skill crash-reporting Commit 8). Quando true,
|
|
1025
|
+
// configure_wuic deve scrivere `CrashReporting.Enabled=true`,
|
|
1026
|
+
// `DisclaimerAcceptedVersion="1.0"` e `DisclaimerAcceptedAt=<now>` in
|
|
1027
|
+
// appsettings.json. Quando false (default), niente da fare — la
|
|
1028
|
+
// sezione resta a Enabled=false e l'utente potra' attivarla in
|
|
1029
|
+
// seguito dall'appsettings-editor (Commit 9).
|
|
1030
|
+
enableCrashReporting: this.firstRunForm.enableCrashReporting ? 'true' : 'false'
|
|
1031
|
+
}, this.suppressGlobalErrorOpts()));
|
|
1032
|
+
// STOP polling FirstRunProgress NOW — `configure_wuic` ha appena riscritto
|
|
1033
|
+
// `appsettings.json` (firstRun=false + nuove ConnectionStrings). IIS rileva
|
|
1034
|
+
// il file change via FileSystemWatcher e recyclia l'app pool. Se il polling
|
|
1035
|
+
// continua a girare durante il recycle riceve `503 Application Shutting Down`
|
|
1036
|
+
// → GlobalHandler `error.http_503.user_message` → dialog modale all'utente
|
|
1037
|
+
// ("Http failure response: 503") che oscura il flow di success.
|
|
1038
|
+
// Spostato qui PRIMA di clearClientState + waitForBackendReady (era nel
|
|
1039
|
+
// `finally` block, troppo tardi: i ~30s di waitForBackendReady erano
|
|
1040
|
+
// esattamente la finestra di shutdown→restart pool).
|
|
1041
|
+
this.stopFirstRunProgressPolling();
|
|
1042
|
+
await this.clearClientStateForFirstRunLogin();
|
|
1043
|
+
// I servizi notifiche (WS `/ws/notifications` + watcher) sono registrati SEMPRE
|
|
1044
|
+
// dal backend (anche durante il first-run) e partono in polling resiliente che
|
|
1045
|
+
// rilegge la connection FRESH -> nessun restart necessario qui: appena
|
|
1046
|
+
// configure_wuic scrive la connection reale + crea lo schema, il push si aggancia.
|
|
1047
|
+
// Vedi Startup.cs (registrazione sempre attiva) e NotificationSqlDependencyWatcher.
|
|
1048
|
+
await this.waitForBackendReady();
|
|
1049
|
+
const scaffoldRoute = '/scaffolding/dialog/1556';
|
|
1050
|
+
const loginRedirectUrl = `/?redirect=${encodeURIComponent(scaffoldRoute)}&firstRunLogin=1`;
|
|
1051
|
+
this.complete.emit();
|
|
1052
|
+
globalThis.location.assign(loginRedirectUrl);
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
catch (error) {
|
|
1056
|
+
this.firstRunError = this.extractErrorMessage(error);
|
|
1057
|
+
// Branch METADATA_DB_EXISTS: il server chiede conferma di drop+recreate del
|
|
1058
|
+
// metadata DB esistente. Le chiamate del wizard sopprimono il GlobalHandler
|
|
1059
|
+
// (errore solo inline), quindi il delegate registrato in ngOnInit NON scatta
|
|
1060
|
+
// piu' -> mostriamo il prompt di conferma DIRETTAMENTE qui. La guard
|
|
1061
|
+
// `metadataDbExistsPromptInFlight` dentro handleMetadataDbExistsPrompt evita
|
|
1062
|
+
// doppi prompt se il delegate dovesse comunque scattare.
|
|
1063
|
+
if (!confirmDropExistingMetadataDb && this.firstRunError.includes('METADATA_DB_EXISTS_CONFIRM_REQUIRED')) {
|
|
1064
|
+
void this.handleMetadataDbExistsPrompt({ message: this.firstRunError });
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
// Errore mostrato SOLO inline nel form (firstRunError): niente toast ne
|
|
1068
|
+
// dialog globale (configure_wuic usa suppressGlobalErrorOpts()).
|
|
1069
|
+
}
|
|
1070
|
+
finally {
|
|
1071
|
+
this.firstRunInstalling = false;
|
|
1072
|
+
this.stopFirstRunProgressPolling();
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1076
|
+
// PROGRESS POLLING
|
|
1077
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1078
|
+
startFirstRunProgressPolling() {
|
|
1079
|
+
this.stopFirstRunProgressPolling();
|
|
1080
|
+
this.firstRunProgress = {
|
|
1081
|
+
active: false,
|
|
1082
|
+
phase: 'starting',
|
|
1083
|
+
current: 0,
|
|
1084
|
+
total: 0,
|
|
1085
|
+
percent: 0,
|
|
1086
|
+
message: 'Inizializzazione...',
|
|
1087
|
+
elapsedMs: 0,
|
|
1088
|
+
finished: false,
|
|
1089
|
+
failed: false,
|
|
1090
|
+
error: null
|
|
1091
|
+
};
|
|
1092
|
+
const apiBase = WtoolboxService.appSettings?.api_url || '';
|
|
1093
|
+
this.firstRunProgressTimer = setInterval(async () => {
|
|
1094
|
+
try {
|
|
1095
|
+
const resp = await firstValueFrom(this.http.get(`${apiBase}Meta/FirstRunProgress`, this.suppressGlobalErrorOpts()));
|
|
1096
|
+
if (resp && typeof resp === 'object') {
|
|
1097
|
+
this.firstRunProgress = {
|
|
1098
|
+
active: !!resp.active,
|
|
1099
|
+
phase: String(resp.phase || 'idle'),
|
|
1100
|
+
current: Number(resp.current || 0),
|
|
1101
|
+
total: Number(resp.total || 0),
|
|
1102
|
+
percent: Number(resp.percent || 0),
|
|
1103
|
+
message: String(resp.message || ''),
|
|
1104
|
+
elapsedMs: Number(resp.elapsedMs || 0),
|
|
1105
|
+
finished: !!resp.finished,
|
|
1106
|
+
failed: !!resp.failed,
|
|
1107
|
+
error: resp.error || null
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
catch {
|
|
1112
|
+
// transient — keep ticking
|
|
1113
|
+
}
|
|
1114
|
+
}, 500);
|
|
1115
|
+
}
|
|
1116
|
+
stopFirstRunProgressPolling() {
|
|
1117
|
+
if (this.firstRunProgressTimer) {
|
|
1118
|
+
clearInterval(this.firstRunProgressTimer);
|
|
1119
|
+
this.firstRunProgressTimer = null;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1123
|
+
// POST-INSTALL CLEANUP + WAIT
|
|
1124
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1125
|
+
async clearClientStateForFirstRunLogin() {
|
|
1126
|
+
try {
|
|
1127
|
+
localStorage.clear();
|
|
1128
|
+
}
|
|
1129
|
+
catch { /* ignore */ }
|
|
1130
|
+
try {
|
|
1131
|
+
sessionStorage.clear();
|
|
1132
|
+
}
|
|
1133
|
+
catch { /* ignore */ }
|
|
1134
|
+
await this.deleteIndexedDbByName('MetaDB');
|
|
1135
|
+
await this.deleteIndexedDbByName('WuicClientSideCrudDB');
|
|
1136
|
+
}
|
|
1137
|
+
async waitForBackendReady() {
|
|
1138
|
+
const apiBase = WtoolboxService.appSettings?.api_url || '';
|
|
1139
|
+
const sentinelUrl = `${apiBase}Meta/FirstRunStatus`;
|
|
1140
|
+
const startedAt = Date.now();
|
|
1141
|
+
const timeoutMs = 30_000;
|
|
1142
|
+
const intervalMs = 500;
|
|
1143
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1144
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
1145
|
+
try {
|
|
1146
|
+
await firstValueFrom(this.http.get(sentinelUrl, this.suppressGlobalErrorOpts()));
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
catch {
|
|
1150
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
deleteIndexedDbByName(dbName) {
|
|
1155
|
+
const normalized = String(dbName || '').trim();
|
|
1156
|
+
if (!normalized || typeof indexedDB === 'undefined') {
|
|
1157
|
+
return Promise.resolve();
|
|
1158
|
+
}
|
|
1159
|
+
return new Promise((resolve) => {
|
|
1160
|
+
try {
|
|
1161
|
+
const request = indexedDB.deleteDatabase(normalized);
|
|
1162
|
+
request.onsuccess = () => resolve();
|
|
1163
|
+
request.onerror = () => resolve();
|
|
1164
|
+
request.onblocked = () => resolve();
|
|
1165
|
+
}
|
|
1166
|
+
catch {
|
|
1167
|
+
resolve();
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1172
|
+
// CONNECTION-STRING UTILITIES
|
|
1173
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1174
|
+
parseConnectionString(connectionString) {
|
|
1175
|
+
const map = new Map();
|
|
1176
|
+
(connectionString || '')
|
|
1177
|
+
.split(';')
|
|
1178
|
+
.map(part => part.trim())
|
|
1179
|
+
.filter(part => !!part && part.includes('='))
|
|
1180
|
+
.forEach(part => {
|
|
1181
|
+
const idx = part.indexOf('=');
|
|
1182
|
+
const key = part.substring(0, idx).trim().toLowerCase();
|
|
1183
|
+
const value = part.substring(idx + 1).trim();
|
|
1184
|
+
map.set(key, value);
|
|
1185
|
+
});
|
|
1186
|
+
const dataSourceRaw = this.getConnValue(map, ['data source', 'server', 'host', 'datasource', 'addr', 'address']);
|
|
1187
|
+
const dbName = this.getConnValue(map, ['initial catalog', 'database', 'dbq', 'service name']);
|
|
1188
|
+
const userId = this.getConnValue(map, ['user id', 'uid', 'user', 'username']);
|
|
1189
|
+
const password = this.getConnValue(map, ['password', 'pwd']);
|
|
1190
|
+
const explicitPort = this.getConnValue(map, ['port']);
|
|
1191
|
+
const integratedSecurityRaw = this.getConnValue(map, ['integrated security', 'trusted_connection']).toLowerCase();
|
|
1192
|
+
const integratedSecurity = integratedSecurityRaw === 'true' || integratedSecurityRaw === 'yes' || integratedSecurityRaw === 'sspi';
|
|
1193
|
+
let dataSource = dataSourceRaw;
|
|
1194
|
+
let port = explicitPort;
|
|
1195
|
+
if (!port && dataSourceRaw.includes(',')) {
|
|
1196
|
+
const chunks = dataSourceRaw.split(',');
|
|
1197
|
+
dataSource = (chunks[0] || '').trim();
|
|
1198
|
+
port = (chunks[1] || '').trim();
|
|
1199
|
+
}
|
|
1200
|
+
return { dataSource, port, databaseName: dbName, userId, password, integratedSecurity };
|
|
1201
|
+
}
|
|
1202
|
+
buildConnectionWithoutDatabase(connectionString, normalizedDbms) {
|
|
1203
|
+
const dbms = this.normalizeDbms(normalizedDbms);
|
|
1204
|
+
try {
|
|
1205
|
+
const parts = (connectionString || '')
|
|
1206
|
+
.split(';')
|
|
1207
|
+
.map(part => part.trim())
|
|
1208
|
+
.filter(part => !!part && part.includes('='));
|
|
1209
|
+
const keep = [];
|
|
1210
|
+
for (const part of parts) {
|
|
1211
|
+
const idx = part.indexOf('=');
|
|
1212
|
+
const key = part.substring(0, idx).trim().toLowerCase();
|
|
1213
|
+
if (['initial catalog', 'database', 'dbq', 'service name'].includes(key))
|
|
1214
|
+
continue;
|
|
1215
|
+
if (dbms === 'mssql' && (key === 'attachdbfilename' || key === 'initial file name'))
|
|
1216
|
+
continue;
|
|
1217
|
+
keep.push(part);
|
|
1218
|
+
}
|
|
1219
|
+
return keep.join(';');
|
|
1220
|
+
}
|
|
1221
|
+
catch {
|
|
1222
|
+
return '';
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
buildProviderConnectionString(normalizedDbms, conn) {
|
|
1226
|
+
const dbms = this.normalizeDbms(normalizedDbms);
|
|
1227
|
+
const dataSource = String(conn.dataSource || '').trim();
|
|
1228
|
+
const port = String(conn.port || '').trim();
|
|
1229
|
+
const databaseName = String(conn.databaseName || '').trim();
|
|
1230
|
+
const userId = String(conn.userId || '').trim();
|
|
1231
|
+
const password = String(conn.password || '').trim();
|
|
1232
|
+
const integratedSecurity = !!conn.integratedSecurity;
|
|
1233
|
+
const parts = [];
|
|
1234
|
+
if (dbms === 'mssql') {
|
|
1235
|
+
let serverValue = dataSource;
|
|
1236
|
+
if (serverValue && port && !serverValue.includes(','))
|
|
1237
|
+
serverValue = `${serverValue},${port}`;
|
|
1238
|
+
if (serverValue)
|
|
1239
|
+
parts.push(`data source=${serverValue}`);
|
|
1240
|
+
if (integratedSecurity) {
|
|
1241
|
+
parts.push('Integrated Security=True');
|
|
1242
|
+
}
|
|
1243
|
+
else {
|
|
1244
|
+
parts.push('integrated security=False');
|
|
1245
|
+
if (userId)
|
|
1246
|
+
parts.push(`User ID=${userId}`);
|
|
1247
|
+
if (password)
|
|
1248
|
+
parts.push(`Password=${password}`);
|
|
1249
|
+
parts.push('Persist Security Info=true');
|
|
1250
|
+
}
|
|
1251
|
+
parts.push('Encrypt=False', 'TrustServerCertificate=True');
|
|
1252
|
+
if (databaseName)
|
|
1253
|
+
parts.push(`initial catalog=${databaseName}`);
|
|
1254
|
+
return parts.join(';');
|
|
1255
|
+
}
|
|
1256
|
+
if (dbms === 'mysql' || dbms === 'postgresql') {
|
|
1257
|
+
if (dataSource)
|
|
1258
|
+
parts.push(`server=${dataSource}`);
|
|
1259
|
+
if (userId)
|
|
1260
|
+
parts.push(`user id=${userId}`);
|
|
1261
|
+
if (password)
|
|
1262
|
+
parts.push(`password=${password}`);
|
|
1263
|
+
parts.push('persist security info=True');
|
|
1264
|
+
if (databaseName)
|
|
1265
|
+
parts.push(`database=${databaseName}`);
|
|
1266
|
+
if (port)
|
|
1267
|
+
parts.push(`Port=${port}`);
|
|
1268
|
+
return parts.join(';');
|
|
1269
|
+
}
|
|
1270
|
+
if (dbms === 'oracle') {
|
|
1271
|
+
if (dataSource)
|
|
1272
|
+
parts.push(`data source=${dataSource}`);
|
|
1273
|
+
if (userId)
|
|
1274
|
+
parts.push(`User ID=${userId}`);
|
|
1275
|
+
if (password)
|
|
1276
|
+
parts.push(`Password=${password}`);
|
|
1277
|
+
if (databaseName)
|
|
1278
|
+
parts.push(`service name=${databaseName}`);
|
|
1279
|
+
if (port)
|
|
1280
|
+
parts.push(`Port=${port}`);
|
|
1281
|
+
return parts.join(';');
|
|
1282
|
+
}
|
|
1283
|
+
return String(this.firstRunForm.dataConnectionString || '').trim();
|
|
1284
|
+
}
|
|
1285
|
+
isSqlServerCertificateError(detail) {
|
|
1286
|
+
const message = String(detail || '').toLowerCase();
|
|
1287
|
+
return message.includes('ssl provider')
|
|
1288
|
+
|| (message.includes('certificate') && message.includes('chain'))
|
|
1289
|
+
|| (message.includes('certific') && message.includes('catena'))
|
|
1290
|
+
|| message.includes('trustservercertificate');
|
|
1291
|
+
}
|
|
1292
|
+
applySqlServerCertificateFix(connectionString) {
|
|
1293
|
+
const segments = (connectionString || '').split(';').map(p => p.trim()).filter(p => !!p);
|
|
1294
|
+
const filtered = [];
|
|
1295
|
+
for (const part of segments) {
|
|
1296
|
+
const idx = part.indexOf('=');
|
|
1297
|
+
if (idx <= 0) {
|
|
1298
|
+
filtered.push(part);
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
const key = part.substring(0, idx).trim().toLowerCase();
|
|
1302
|
+
if (key === 'encrypt' || key === 'trustservercertificate')
|
|
1303
|
+
continue;
|
|
1304
|
+
filtered.push(part);
|
|
1305
|
+
}
|
|
1306
|
+
filtered.push('Encrypt=False', 'TrustServerCertificate=True');
|
|
1307
|
+
return filtered.join(';');
|
|
1308
|
+
}
|
|
1309
|
+
getConnValue(map, keys) {
|
|
1310
|
+
for (const k of keys) {
|
|
1311
|
+
const value = map.get(k.toLowerCase());
|
|
1312
|
+
if (value !== undefined)
|
|
1313
|
+
return value;
|
|
1314
|
+
}
|
|
1315
|
+
return '';
|
|
1316
|
+
}
|
|
1317
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1318
|
+
// GENERIC PARSE / NORMALIZE
|
|
1319
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1320
|
+
parseDictionaryResponse(payload) {
|
|
1321
|
+
const raw = this.unwrapPayload(payload);
|
|
1322
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw))
|
|
1323
|
+
return raw;
|
|
1324
|
+
if (typeof raw === 'string') {
|
|
1325
|
+
try {
|
|
1326
|
+
const parsed = JSON.parse(raw);
|
|
1327
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
1328
|
+
return parsed;
|
|
1329
|
+
}
|
|
1330
|
+
catch {
|
|
1331
|
+
return {};
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return {};
|
|
1335
|
+
}
|
|
1336
|
+
parseStringArrayResponse(payload) {
|
|
1337
|
+
const raw = this.unwrapPayload(payload);
|
|
1338
|
+
if (Array.isArray(raw))
|
|
1339
|
+
return raw.map(x => String(x ?? ''));
|
|
1340
|
+
if (typeof raw === 'string') {
|
|
1341
|
+
try {
|
|
1342
|
+
const parsed = JSON.parse(raw);
|
|
1343
|
+
return Array.isArray(parsed) ? parsed.map(x => String(x ?? '')) : [];
|
|
1344
|
+
}
|
|
1345
|
+
catch {
|
|
1346
|
+
return [];
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
return [];
|
|
1350
|
+
}
|
|
1351
|
+
unwrapPayload(payload) {
|
|
1352
|
+
if (payload && typeof payload === 'object') {
|
|
1353
|
+
if ('d' in payload)
|
|
1354
|
+
return payload.d;
|
|
1355
|
+
if ('value' in payload)
|
|
1356
|
+
return payload.value;
|
|
1357
|
+
}
|
|
1358
|
+
return payload;
|
|
1359
|
+
}
|
|
1360
|
+
normalizeDbms(value) {
|
|
1361
|
+
const v = String(value || '').trim().toLowerCase();
|
|
1362
|
+
if (v === 'postgres')
|
|
1363
|
+
return 'postgresql';
|
|
1364
|
+
return v;
|
|
1365
|
+
}
|
|
1366
|
+
toBoolean(value) {
|
|
1367
|
+
const normalized = String(value ?? '').trim().toLowerCase();
|
|
1368
|
+
return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'si';
|
|
1369
|
+
}
|
|
1370
|
+
extractErrorMessage(error) {
|
|
1371
|
+
const fromBackend = error?.error;
|
|
1372
|
+
if (typeof fromBackend === 'string' && fromBackend.trim())
|
|
1373
|
+
return this.cleanServerErrorMessage(fromBackend);
|
|
1374
|
+
if (fromBackend && typeof fromBackend === 'object') {
|
|
1375
|
+
const parts = [];
|
|
1376
|
+
const detail = fromBackend.message || fromBackend.error || fromBackend.title;
|
|
1377
|
+
if (detail)
|
|
1378
|
+
parts.push(String(detail));
|
|
1379
|
+
const rootMessage = (fromBackend).rootMessage;
|
|
1380
|
+
if (rootMessage && (!detail || !String(detail).includes(String(rootMessage)))) {
|
|
1381
|
+
parts.push(String(rootMessage));
|
|
1382
|
+
}
|
|
1383
|
+
// ── New typed-exception envelope (skill exception-handling) ────────
|
|
1384
|
+
// Server's JsonExceptionFilter wraps unhandled exceptions in:
|
|
1385
|
+
// { ok:false, errorCode, fallbackMessage, args: { type, message, ... }, traceId }
|
|
1386
|
+
// Senza questi pickup, marker grezzi tipo `METADATA_DB_EXISTS_CONFIRM_REQUIRED:`
|
|
1387
|
+
// (lanciati come `throw new InvalidOperationException(marker)` lato server)
|
|
1388
|
+
// restano sepolti in `args.message` → il flow `if (firstRunError.includes(marker))`
|
|
1389
|
+
// del wizard non trova mai il match e perde il prompt di overwrite.
|
|
1390
|
+
const argsMsg = (fromBackend).args?.message;
|
|
1391
|
+
if (argsMsg && !parts.some(p => p.includes(String(argsMsg)))) {
|
|
1392
|
+
parts.push(String(argsMsg));
|
|
1393
|
+
}
|
|
1394
|
+
const fallbackMsg = (fromBackend).fallbackMessage;
|
|
1395
|
+
if (fallbackMsg && !parts.some(p => p.includes(String(fallbackMsg)))) {
|
|
1396
|
+
parts.push(String(fallbackMsg));
|
|
1397
|
+
}
|
|
1398
|
+
if (parts.length > 0)
|
|
1399
|
+
return this.cleanServerErrorMessage(parts.join(' | '));
|
|
1400
|
+
}
|
|
1401
|
+
return this.cleanServerErrorMessage(String(error?.message || 'Errore sconosciuto durante il first-run setup.'));
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Ripulisce il wrapper grezzo dell'AsmxProxy dispatcher mostrando SOLO il
|
|
1405
|
+
* messaggio dell'eccezione interna piu' profonda. Il backend, quando una
|
|
1406
|
+
* invocazione `MetaService.*` fallisce, rilancia un messaggio verboso del
|
|
1407
|
+
* tipo:
|
|
1408
|
+
* AsmxProxy invocation failed. Requested='MetaService.get_database_names',
|
|
1409
|
+
* Current='ok', ... Methods='...', Parameters='...'.
|
|
1410
|
+
* Inner='System.InvalidOperationException: Errore test connessione/lista
|
|
1411
|
+
* database: Errore dell'istanza.'
|
|
1412
|
+
* Di tutto questo l'utente vuole solo la parte utile -> qui estraiamo il
|
|
1413
|
+
* contenuto dell'ultimo `Inner='...'` e togliamo il prefisso del tipo
|
|
1414
|
+
* eccezione .NET (es. `System.InvalidOperationException: `). Se il messaggio
|
|
1415
|
+
* non e' un dump AsmxProxy lo lasciamo invariato. Preserva eventuali marker
|
|
1416
|
+
* (es. METADATA_DB_EXISTS_CONFIRM_REQUIRED) che cosi' diventano anzi piu'
|
|
1417
|
+
* facili da matchare (non piu' sepolti nel dump).
|
|
1418
|
+
*/
|
|
1419
|
+
cleanServerErrorMessage(raw) {
|
|
1420
|
+
const original = String(raw || '').trim();
|
|
1421
|
+
let msg = original;
|
|
1422
|
+
if (!msg)
|
|
1423
|
+
return msg;
|
|
1424
|
+
// Scarta progressivamente i wrapper `Inner='...'` fino all'eccezione piu'
|
|
1425
|
+
// interna (guard anti-loop). Greedy: l'apostrofo dentro al messaggio (es.
|
|
1426
|
+
// "dell'istanza") resta incluso, la cattura arriva fino all'ultima quote.
|
|
1427
|
+
let m;
|
|
1428
|
+
let guard = 0;
|
|
1429
|
+
while ((m = msg.match(/Inner='([\s\S]*)'/)) !== null && guard++ < 6) {
|
|
1430
|
+
msg = m[1].trim();
|
|
1431
|
+
}
|
|
1432
|
+
// Togli il prefisso fully-qualified del tipo eccezione .NET
|
|
1433
|
+
// (es. "System.InvalidOperationException: " / "System.Data.SqlClient.SqlException: ").
|
|
1434
|
+
msg = msg.replace(/^(?:[A-Za-z_]\w*\.)+[A-Za-z_]\w*(?:Exception|Error)\s*:\s*/, '').trim();
|
|
1435
|
+
return msg || original;
|
|
1436
|
+
}
|
|
1437
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1438
|
+
// I18N BUNDLES (firstrun.* keys)
|
|
1439
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1440
|
+
registerFirstRunTranslations() {
|
|
1441
|
+
const bundles = {
|
|
1442
|
+
'it-IT': { firstrun: { rag: {
|
|
1443
|
+
installTooltip: 'PREREQUISITO: Python 3.12 installato e sul PATH del worker IIS.\n\n' +
|
|
1444
|
+
'Se la checkbox è disabilitata: Python non è stato rilevato.\n' +
|
|
1445
|
+
'Per installarlo (ONE-CLICK):\n' +
|
|
1446
|
+
'• Vai nella cartella del deploy (dove c\'è WuicCore.dll)\n' +
|
|
1447
|
+
'• Right-click su rag-setup.ps1 → "Esegui come amministratore"\n' +
|
|
1448
|
+
'• Lo script si auto-eleva via UAC, estrae Python 3.12 embeddable (niente MSI, bypass GPO DisableMSI), installa pip, aggiorna PATH, fa iisreset\n' +
|
|
1449
|
+
'• Ricarica il form (Ctrl+F5) → la checkbox si abilita\n\n' +
|
|
1450
|
+
'Python >= 3.13 accettato ma non testato (wheels torch più stabili su 3.12).\n\n' +
|
|
1451
|
+
'Quando Python è presente, l\'install RAG parte in BACKGROUND appena finita la creazione DB. ' +
|
|
1452
|
+
'Puoi loggarti subito e usare il resto dell\'app; riceverai notifiche nel bell in alto ai 4 step + 1 finale (cliccabile per aprire il chatbot).\n\n' +
|
|
1453
|
+
'Tempi tipici (rete ~50-100 Mbps):\n' +
|
|
1454
|
+
'• First run CPU: 4-8 minuti (download torch ~200 MB)\n' +
|
|
1455
|
+
'• First run GPU CUDA: 18-32 minuti (download torch ~2.5 GB)\n' +
|
|
1456
|
+
'• Rerun con venv già presente: <20 sec\n\n' +
|
|
1457
|
+
'Cold start del server dopo setup: ~13 sec.',
|
|
1458
|
+
cudaTooltip: 'Download: ~2.5 GB (vs ~200 MB versione CPU)\n' +
|
|
1459
|
+
'Tempo aggiuntivo: +15-25 min su rete standard\n' +
|
|
1460
|
+
'Performance runtime: ~10-20× più veloce su embedding search + rerank.\n\n' +
|
|
1461
|
+
'Richiede: GPU NVIDIA + driver CUDA 12.x installati lato sistema.',
|
|
1462
|
+
apiKeyTooltip: 'Senza chiave: modalità retrieval-only (ricerca snippet dal codebase).\n' +
|
|
1463
|
+
'Con chiave: chat LLM con sintesi via Claude.\n\n' +
|
|
1464
|
+
'La chiave viene salvata come env var del processo server (non nel DB).\n' +
|
|
1465
|
+
'Puoi impostarla anche post-install rilanciando: pwsh rag-setup.ps1 -Start -AnthropicApiKey "sk-ant-...".'
|
|
1466
|
+
} } },
|
|
1467
|
+
'en-US': { firstrun: { rag: {
|
|
1468
|
+
installTooltip: 'PREREQUISITE: Python 3.12 installed and on the IIS worker PATH.\n\n' +
|
|
1469
|
+
'If this checkbox is disabled: Python was not detected.\n' +
|
|
1470
|
+
'To install (ONE-CLICK):\n' +
|
|
1471
|
+
'• Go to the deploy folder (where WuicCore.dll is)\n' +
|
|
1472
|
+
'• Right-click on rag-setup.ps1 → "Run as administrator"\n' +
|
|
1473
|
+
'• The script self-elevates via UAC, extracts Python 3.12 embeddable (no MSI, bypasses GPO DisableMSI), installs pip, updates PATH, runs iisreset\n' +
|
|
1474
|
+
'• Reload the form (Ctrl+F5) → the checkbox enables automatically\n\n' +
|
|
1475
|
+
'Python >= 3.13 is accepted but not tested (torch wheels are most stable on 3.12).\n\n' +
|
|
1476
|
+
'When Python is present, RAG install runs in BACKGROUND as soon as DB setup completes. ' +
|
|
1477
|
+
'You can log in right away and use the rest of the app; you will receive notifications in the top bell ' +
|
|
1478
|
+
'for 4 steps + 1 final (clickable to open the chatbot).\n\n' +
|
|
1479
|
+
'Typical timings (network ~50-100 Mbps):\n' +
|
|
1480
|
+
'• First run CPU: 4-8 minutes (torch download ~200 MB)\n' +
|
|
1481
|
+
'• First run GPU CUDA: 18-32 minutes (torch download ~2.5 GB)\n' +
|
|
1482
|
+
'• Rerun with existing venv: <20 sec\n\n' +
|
|
1483
|
+
'Server cold start after setup: ~13 sec.',
|
|
1484
|
+
cudaTooltip: 'Download: ~2.5 GB (vs ~200 MB CPU version)\n' +
|
|
1485
|
+
'Extra time: +15-25 min on standard network\n' +
|
|
1486
|
+
'Runtime performance: ~10-20× faster on embedding search + rerank.\n\n' +
|
|
1487
|
+
'Requires: NVIDIA GPU + CUDA 12.x drivers installed system-wide.',
|
|
1488
|
+
apiKeyTooltip: 'Without key: retrieval-only mode (codebase snippet search).\n' +
|
|
1489
|
+
'With key: LLM chat with synthesis via Claude.\n\n' +
|
|
1490
|
+
'The key is stored as a server process env var (not in DB).\n' +
|
|
1491
|
+
'Can also be set post-install by re-running: pwsh rag-setup.ps1 -Start -AnthropicApiKey "sk-ant-...".'
|
|
1492
|
+
} } },
|
|
1493
|
+
'fr-FR': { firstrun: { rag: {
|
|
1494
|
+
installTooltip: 'PRÉREQUIS : Python 3.12 installé et sur le PATH du worker IIS.\n\n' +
|
|
1495
|
+
'Si la case est désactivée : Python n\'a pas été détecté.\n' +
|
|
1496
|
+
'Pour l\'installer (ONE-CLICK) :\n' +
|
|
1497
|
+
'• Allez dans le dossier du deploy (où se trouve WuicCore.dll)\n' +
|
|
1498
|
+
'• Right-click sur rag-setup.ps1 → "Exécuter en tant qu\'administrateur"\n' +
|
|
1499
|
+
'• Le script s\'auto-élève via UAC, extrait Python 3.12 embeddable (sans MSI, contourne la GPO DisableMSI), installe pip, met à jour PATH, lance iisreset\n' +
|
|
1500
|
+
'• Rechargez le formulaire (Ctrl+F5) → la case s\'active automatiquement\n\n' +
|
|
1501
|
+
'Python >= 3.13 accepté mais non testé (wheels torch plus stables sur 3.12).\n\n' +
|
|
1502
|
+
'Quand Python est présent, l\'install RAG tourne en ARRIÈRE-PLAN dès la fin du setup BDD. ' +
|
|
1503
|
+
'Vous pouvez vous connecter immédiatement ; vous recevrez des notifications ' +
|
|
1504
|
+
'dans la cloche en haut pour 4 étapes + 1 finale (cliquable pour ouvrir le chatbot).\n\n' +
|
|
1505
|
+
'Temps typiques (réseau ~50-100 Mbps) :\n' +
|
|
1506
|
+
'• Premier run CPU : 4-8 minutes (téléchargement torch ~200 Mo)\n' +
|
|
1507
|
+
'• Premier run GPU CUDA : 18-32 minutes (téléchargement torch ~2,5 Go)\n' +
|
|
1508
|
+
'• Rerun avec venv existant : <20 sec\n\n' +
|
|
1509
|
+
'Cold start du serveur après setup : ~13 sec.',
|
|
1510
|
+
cudaTooltip: 'Téléchargement : ~2,5 Go (vs ~200 Mo version CPU)\n' +
|
|
1511
|
+
'Temps supplémentaire : +15-25 min sur réseau standard\n' +
|
|
1512
|
+
'Performance runtime : ~10-20× plus rapide en recherche embedding + rerank.\n\n' +
|
|
1513
|
+
'Requiert : GPU NVIDIA + pilotes CUDA 12.x installés au niveau système.',
|
|
1514
|
+
apiKeyTooltip: 'Sans clé : mode retrieval-only (recherche de snippets dans le codebase).\n' +
|
|
1515
|
+
'Avec clé : chat LLM avec synthèse via Claude.\n\n' +
|
|
1516
|
+
'La clé est stockée comme variable env du processus serveur (pas dans la BDD).\n' +
|
|
1517
|
+
'Peut aussi être définie post-install en relançant : pwsh rag-setup.ps1 -Start -AnthropicApiKey "sk-ant-...".'
|
|
1518
|
+
} } },
|
|
1519
|
+
'es-ES': { firstrun: { rag: {
|
|
1520
|
+
installTooltip: 'PRERREQUISITO: Python 3.12 instalado y en el PATH del worker IIS.\n\n' +
|
|
1521
|
+
'Si la casilla está deshabilitada: Python no fue detectado.\n' +
|
|
1522
|
+
'Para instalarlo (ONE-CLICK):\n' +
|
|
1523
|
+
'• Ve a la carpeta del deploy (donde está WuicCore.dll)\n' +
|
|
1524
|
+
'• Click derecho en rag-setup.ps1 → "Ejecutar como administrador"\n' +
|
|
1525
|
+
'• El script se auto-eleva vía UAC, extrae Python 3.12 embeddable (sin MSI, evita la GPO DisableMSI), instala pip, actualiza PATH, ejecuta iisreset\n' +
|
|
1526
|
+
'• Recarga el formulario (Ctrl+F5) → la casilla se habilita automáticamente\n\n' +
|
|
1527
|
+
'Python >= 3.13 aceptado pero no testado (wheels torch más estables en 3.12).\n\n' +
|
|
1528
|
+
'Con Python presente, la instalación RAG corre en SEGUNDO PLANO apenas termina el setup BD. ' +
|
|
1529
|
+
'Puedes iniciar sesión inmediatamente; recibirás notificaciones ' +
|
|
1530
|
+
'en la campana superior para 4 pasos + 1 final (cliqueable para abrir el chatbot).\n\n' +
|
|
1531
|
+
'Tiempos típicos (red ~50-100 Mbps):\n' +
|
|
1532
|
+
'• First run CPU: 4-8 minutos (descarga torch ~200 MB)\n' +
|
|
1533
|
+
'• First run GPU CUDA: 18-32 minutos (descarga torch ~2,5 GB)\n' +
|
|
1534
|
+
'• Rerun con venv ya presente: <20 seg\n\n' +
|
|
1535
|
+
'Cold start del servidor tras setup: ~13 seg.',
|
|
1536
|
+
cudaTooltip: 'Descarga: ~2,5 GB (vs ~200 MB versión CPU)\n' +
|
|
1537
|
+
'Tiempo adicional: +15-25 min en red estándar\n' +
|
|
1538
|
+
'Rendimiento runtime: ~10-20× más rápido en embedding search + rerank.\n\n' +
|
|
1539
|
+
'Requiere: GPU NVIDIA + drivers CUDA 12.x instalados a nivel sistema.',
|
|
1540
|
+
apiKeyTooltip: 'Sin clave: modo retrieval-only (búsqueda de snippets del codebase).\n' +
|
|
1541
|
+
'Con clave: chat LLM con síntesis vía Claude.\n\n' +
|
|
1542
|
+
'La clave se guarda como env var del proceso servidor (no en BD).\n' +
|
|
1543
|
+
'Puede establecerse también post-install relanzando: pwsh rag-setup.ps1 -Start -AnthropicApiKey "sk-ant-...".'
|
|
1544
|
+
} } },
|
|
1545
|
+
'de-DE': { firstrun: { rag: {
|
|
1546
|
+
installTooltip: 'VORAUSSETZUNG: Python 3.12 installiert und im PATH des IIS-Workers.\n\n' +
|
|
1547
|
+
'Falls die Checkbox deaktiviert ist: Python wurde nicht erkannt.\n' +
|
|
1548
|
+
'Zum Installieren (ONE-CLICK):\n' +
|
|
1549
|
+
'• Gehen Sie in den Deploy-Ordner (wo WuicCore.dll liegt)\n' +
|
|
1550
|
+
'• Rechtsklick auf rag-setup.ps1 → "Als Administrator ausführen"\n' +
|
|
1551
|
+
'• Das Skript hebt sich via UAC selbst an, extrahiert Python 3.12 embeddable (kein MSI, umgeht die GPO DisableMSI), installiert pip, aktualisiert PATH, führt iisreset aus\n' +
|
|
1552
|
+
'• Laden Sie das Formular neu (Strg+F5) → die Checkbox wird automatisch aktiviert\n\n' +
|
|
1553
|
+
'Python >= 3.13 akzeptiert, aber nicht getestet (Torch-Wheels am stabilsten auf 3.12).\n\n' +
|
|
1554
|
+
'Wenn Python vorhanden ist, läuft die RAG-Installation im HINTERGRUND, sobald das DB-Setup fertig ist. ' +
|
|
1555
|
+
'Sie können sich sofort anmelden; Sie erhalten Benachrichtigungen ' +
|
|
1556
|
+
'in der oberen Glocke für 4 Schritte + 1 Abschluss (klickbar zum Öffnen des Chatbots).\n\n' +
|
|
1557
|
+
'Typische Dauer (Netz ~50-100 Mbps):\n' +
|
|
1558
|
+
'• First Run CPU: 4-8 Minuten (Torch-Download ~200 MB)\n' +
|
|
1559
|
+
'• First Run GPU CUDA: 18-32 Minuten (Torch-Download ~2,5 GB)\n' +
|
|
1560
|
+
'• Rerun mit bestehendem venv: <20 Sek\n\n' +
|
|
1561
|
+
'Server-Cold-Start nach Setup: ~13 Sek.',
|
|
1562
|
+
cudaTooltip: 'Download: ~2,5 GB (vs ~200 MB CPU-Version)\n' +
|
|
1563
|
+
'Zusätzliche Zeit: +15-25 Min in Standard-Netz\n' +
|
|
1564
|
+
'Runtime-Performance: ~10-20× schneller bei Embedding-Search + Rerank.\n\n' +
|
|
1565
|
+
'Erfordert: NVIDIA-GPU + systemweit installierte CUDA-12.x-Treiber.',
|
|
1566
|
+
apiKeyTooltip: 'Ohne Schlüssel: Retrieval-only-Modus (Snippet-Suche im Codebase).\n' +
|
|
1567
|
+
'Mit Schlüssel: LLM-Chat mit Synthese via Claude.\n\n' +
|
|
1568
|
+
'Der Schlüssel wird als Env-Var des Server-Prozesses gespeichert (nicht in DB).\n' +
|
|
1569
|
+
'Kann auch nach der Installation gesetzt werden via: pwsh rag-setup.ps1 -Start -AnthropicApiKey "sk-ant-...".'
|
|
1570
|
+
} } }
|
|
1571
|
+
};
|
|
1572
|
+
for (const [lang, strings] of Object.entries(bundles)) {
|
|
1573
|
+
this.translate.setTranslation(lang, strings, true);
|
|
1574
|
+
}
|
|
1575
|
+
// Labels of the wizard itself (inline: no metadata DB yet, see first-run-wizard.i18n.ts).
|
|
1576
|
+
for (const [lang, strings] of Object.entries(FIRST_RUN_UI_BUNDLES)) {
|
|
1577
|
+
this.translate.setTranslation(lang, strings, true);
|
|
1578
|
+
}
|
|
1579
|
+
// The wizard ALWAYS follows the browser language: it runs before any user
|
|
1580
|
+
// exists, so a default language set elsewhere at bootstrap (it-IT) must not
|
|
1581
|
+
// win here. The admin-language select then switches it (see
|
|
1582
|
+
// onFirstRunAdminLanguageChanged).
|
|
1583
|
+
const resolved = WuicFirstRunWizardComponent.resolveDefaultAdminLanguage();
|
|
1584
|
+
if (!this.translate.getDefaultLang())
|
|
1585
|
+
this.translate.setDefaultLang(resolved);
|
|
1586
|
+
this.translate.use(resolved);
|
|
1587
|
+
this.refreshLocalizedOptions();
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* The admin-language select drives the language of the wizard itself as well:
|
|
1591
|
+
* what you pick for the admin is what you immediately read on this page
|
|
1592
|
+
* (the crash-reporting disclaimer already followed this value).
|
|
1593
|
+
*/
|
|
1594
|
+
onFirstRunAdminLanguageChanged(lang) {
|
|
1595
|
+
this.firstRunForm.adminLanguage = lang;
|
|
1596
|
+
if (!lang)
|
|
1597
|
+
return;
|
|
1598
|
+
this.translate.use(lang);
|
|
1599
|
+
this.refreshLocalizedOptions();
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Select options are plain strings, not template bindings: the `translate`
|
|
1603
|
+
* pipe cannot re-render them, so they are rebuilt whenever the language
|
|
1604
|
+
* changes. Product names (DBMS, "Tutorial WideWorldImporters") stay as they are.
|
|
1605
|
+
*/
|
|
1606
|
+
refreshLocalizedOptions() {
|
|
1607
|
+
const existing = this.translate.instant('firstrun.ui.setup_mode_existing');
|
|
1608
|
+
this.firstRunSetupModeOptions = this.firstRunSetupModeOptions.map(o => o.value === 'existing' ? { ...o, label: existing } : o);
|
|
1609
|
+
}
|
|
1610
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicFirstRunWizardComponent, deps: [{ token: i1.HttpClient }, { token: i8.TranslateService }, { token: i3.MessageService }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Component });
|
|
1611
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: WuicFirstRunWizardComponent, isStandalone: true, selector: "wuic-first-run-wizard-impl", outputs: { complete: "complete" }, ngImport: i0, template: "@if (showFirstRunInstall) {\r\n<div class=\"first-run-overlay\">\r\n <div class=\"first-run-card first-run-card-wide\">\r\n <div class=\"first-run-title\">{{ 'firstrun.ui.title' | translate }}</div>\r\n <div class=\"first-run-subtitle\">{{ 'firstrun.ui.subtitle' | translate }}</div>\r\n\r\n <!-- BODY scrollabile: header (title/subtitle) e footer (progress + CTA)\r\n restano sempre visibili, solo questo blocco scrolla quando il form\r\n e' piu' alto della viewport. -->\r\n <div class=\"first-run-body\">\r\n\r\n <!-- Grid 2 colonne: sx = connessione + scaffold / dx = admin + RAG.\r\n Su viewport stretti (< 720px) il grid collassa a 1 colonna. -->\r\n <div class=\"first-run-grid\">\r\n\r\n <!-- \u2500\u2500 COLONNA SINISTRA: Database + Setup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"first-run-col\">\r\n\r\n @if (firstRunSetupModeOptions.length > 1) {\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-mode\">{{ 'firstrun.ui.setup_mode' | translate }}</label>\r\n <p-select [options]=\"firstRunSetupModeOptions\" optionLabel=\"label\" optionValue=\"value\"\r\n [ngModel]=\"firstRunForm.setupMode\" (ngModelChange)=\"onFirstRunSetupModeChanged($event)\"\r\n [disabled]=\"firstRunInstalling\" [style]=\"{ width: '100%' }\" inputId=\"first-run-mode\"></p-select>\r\n </div>\r\n }\r\n\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-dbms\">DBMS</label>\r\n <p-select [options]=\"firstRunDbmsOptions\" optionLabel=\"label\" optionValue=\"value\"\r\n [ngModel]=\"firstRunForm.dbms\" (ngModelChange)=\"onFirstRunDbmsChanged($event)\"\r\n [disabled]=\"firstRunInstalling\" [style]=\"{ width: '100%' }\" inputId=\"first-run-dbms\"></p-select>\r\n </div>\r\n\r\n @if (firstRunForm.setupMode === 'tutorial') {\r\n <div class=\"first-run-field first-run-checkbox-row\">\r\n <label>\r\n <input type=\"checkbox\" [ngModel]=\"firstRunForm.createTutorialIfMissing\"\r\n (ngModelChange)=\"firstRunForm.createTutorialIfMissing = !!$event\" [disabled]=\"firstRunInstalling\" />\r\n {{ 'firstrun.ui.create_tutorial_if_missing' | translate }}\r\n </label>\r\n </div>\r\n }\r\n\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-datasql\">DataSQLConnection</label>\r\n <input id=\"first-run-datasql\" class=\"first-run-input\" type=\"text\"\r\n [ngModel]=\"firstRunForm.dataConnectionString\" (ngModelChange)=\"onFirstRunConnectionChanged($event)\"\r\n [disabled]=\"firstRunInstalling\"\r\n [placeholder]=\"'firstrun.ui.data_connection_placeholder' | translate\" />\r\n </div>\r\n\r\n <div class=\"first-run-field\">\r\n <p-button data-testid=\"first-run-test-connection\" [label]=\"'firstrun.ui.test_connection' | translate\" severity=\"secondary\" [outlined]=\"true\"\r\n [loading]=\"firstRunConnectionTesting\"\r\n [disabled]=\"firstRunInstalling || firstRunLoading || !firstRunForm.dataConnectionString\"\r\n (onClick)=\"testFirstRunConnectionAndLoadDatabases()\"></p-button>\r\n </div>\r\n\r\n @if (firstRunForm.setupMode === 'tutorial') {\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-datadb\">{{ 'firstrun.ui.tutorial_data_db' | translate }}</label>\r\n <input id=\"first-run-datadb\" class=\"first-run-input\" type=\"text\"\r\n [ngModel]=\"firstRunForm.tutorialDataDbName\"\r\n (ngModelChange)=\"firstRunForm.tutorialDataDbName = $event; firstRunForm.dataDbName = $event\"\r\n [disabled]=\"firstRunInstalling || !firstRunConnectionValid\"\r\n placeholder=\"WideWorldImporters\" />\r\n </div>\r\n } @else {\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-datadb\">{{ 'firstrun.ui.data_db' | translate }}</label>\r\n <p-select [options]=\"firstRunDataDbOptions\" optionLabel=\"label\" optionValue=\"value\"\r\n [ngModel]=\"firstRunForm.dataDbName\" (ngModelChange)=\"firstRunForm.dataDbName = $event\"\r\n [disabled]=\"firstRunInstalling || firstRunDbLoading || !firstRunConnectionValid\" [filter]=\"true\"\r\n [editable]=\"true\" [style]=\"{ width: '100%' }\" inputId=\"first-run-datadb\"\r\n [placeholder]=\"'firstrun.ui.data_db_placeholder' | translate\"></p-select>\r\n <div style=\"margin-top: 0.5rem;\">\r\n <p-button data-testid=\"first-run-refresh-dbs\" [label]=\"'firstrun.ui.refresh_db_list' | translate\" severity=\"secondary\" [outlined]=\"true\" size=\"small\"\r\n [loading]=\"firstRunDbLoading\" [disabled]=\"firstRunInstalling || !firstRunConnectionValid\"\r\n (onClick)=\"testFirstRunConnectionAndLoadDatabases()\"></p-button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-metadb\">{{ (firstRunForm.setupMode === 'tutorial' ? 'firstrun.ui.tutorial_meta_db' : 'firstrun.ui.meta_db') | translate }}</label>\r\n <input id=\"first-run-metadb\" class=\"first-run-input\" type=\"text\"\r\n [ngModel]=\"firstRunForm.setupMode === 'tutorial' ? firstRunForm.tutorialMetadataDbName : firstRunForm.metadataDbName\"\r\n (ngModelChange)=\"firstRunForm.metadataDbName = $event; firstRunForm.tutorialMetadataDbName = $event\"\r\n [disabled]=\"firstRunInstalling\"\r\n placeholder=\"metadataDB\" />\r\n </div>\r\n\r\n @if (firstRunForm.setupMode !== 'tutorial') {\r\n <div class=\"first-run-field first-run-checkbox-row\">\r\n <label>\r\n <input type=\"checkbox\" [ngModel]=\"firstRunForm.scaffoldExistingDatabase\"\r\n (ngModelChange)=\"firstRunForm.scaffoldExistingDatabase = !!$event\"\r\n [disabled]=\"firstRunInstalling\" />\r\n {{ 'firstrun.ui.scaffold_existing' | translate }}\r\n </label>\r\n <small style=\"display:block; margin-top:4px; opacity:0.75; font-size:0.8rem; line-height:1.35;\"\r\n [innerHTML]=\"'firstrun.ui.scaffold_existing_hint_html' | translate\"></small>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500 COLONNA DESTRA: Admin + RAG \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"first-run-col\">\r\n\r\n <div class=\"first-run-section-title\">{{ 'firstrun.ui.admin_section' | translate }}</div>\r\n\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-admin-username\">{{ 'firstrun.ui.admin_username' | translate }}</label>\r\n <input id=\"first-run-admin-username\" class=\"first-run-input\" type=\"text\"\r\n [ngModel]=\"firstRunForm.adminUsername\"\r\n (ngModelChange)=\"firstRunForm.adminUsername = $event\"\r\n [disabled]=\"firstRunInstalling\" autocomplete=\"off\"\r\n placeholder=\"admin\" />\r\n </div>\r\n\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-admin-password\">{{ 'firstrun.ui.admin_password' | translate }}</label>\r\n <input id=\"first-run-admin-password\" class=\"first-run-input\" type=\"password\"\r\n [ngModel]=\"firstRunForm.adminPassword\"\r\n (ngModelChange)=\"firstRunForm.adminPassword = $event\"\r\n [disabled]=\"firstRunInstalling\" autocomplete=\"new-password\"\r\n [placeholder]=\"'firstrun.ui.admin_password_placeholder' | translate\" />\r\n </div>\r\n\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-admin-language\">{{ 'firstrun.ui.admin_language' | translate }}</label>\r\n <p-select [options]=\"firstRunAdminLanguageOptions\" optionLabel=\"label\" optionValue=\"value\"\r\n [ngModel]=\"firstRunForm.adminLanguage\"\r\n (ngModelChange)=\"onFirstRunAdminLanguageChanged($event)\"\r\n [disabled]=\"firstRunInstalling\"\r\n [style]=\"{ width: '100%' }\" inputId=\"first-run-admin-language\"></p-select>\r\n <small style=\"display:block; margin-top:4px; opacity:0.75; font-size:0.8rem; line-height:1.35;\">\r\n {{ 'firstrun.ui.admin_language_hint' | translate }}\r\n </small>\r\n </div>\r\n\r\n <div class=\"first-run-section-title first-run-section-title-rag\">{{ 'firstrun.ui.rag_section' | translate }}</div>\r\n\r\n <div class=\"first-run-field\">\r\n <small style=\"display:block; margin-bottom:8px; opacity:0.8; font-size:0.85rem; line-height:1.4;\"\r\n [innerHTML]=\"'firstrun.ui.rag_intro_html' | translate\"></small>\r\n <label for=\"first-run-anthropic-key\">\r\n {{ 'firstrun.ui.anthropic_key' | translate }}\r\n <i class=\"pi pi-info-circle first-run-info-icon\"\r\n [pTooltip]=\"'firstrun.rag.apiKeyTooltip' | translate\"\r\n tooltipPosition=\"right\"\r\n [tooltipStyleClass]=\"'first-run-tooltip'\"\r\n [escape]=\"false\"></i>\r\n </label>\r\n <textarea id=\"first-run-anthropic-key\" class=\"first-run-input first-run-textarea\"\r\n [ngModel]=\"firstRunForm.anthropicApiKey\"\r\n (ngModelChange)=\"firstRunForm.anthropicApiKey = $event\"\r\n [disabled]=\"firstRunInstalling\"\r\n rows=\"2\" autocomplete=\"off\"\r\n placeholder=\"sk-ant-...\"></textarea>\r\n <small style=\"display:block; margin-top:4px; opacity:0.75; font-size:0.8rem; line-height:1.35;\"\r\n [innerHTML]=\"'firstrun.ui.anthropic_key_hint_html' | translate\"></small>\r\n </div>\r\n </div>\r\n\r\n <!-- Crash reporting opt-in (skill crash-reporting Commit 8). Default OFF.\r\n Il consenso GDPR usa labels HARDCODED in `crashReportingLabels`\r\n dentro il componente (NON `wuic_translation`) perche' il\r\n first-run gira PRIMA che il DB metadati esista \u2014 niente DB,\r\n niente translation table. Vedi commento esteso nel componente.\r\n Al check del consenso si apre il popup col disclaimer\r\n dettagliato; l'utente conferma li' per attivare il flag. -->\r\n <div class=\"first-run-section-title first-run-section-title-crash-reporting\">{{ crashReportingLabels.title }}</div>\r\n <div class=\"first-run-field first-run-checkbox-row\">\r\n <label>\r\n <input type=\"checkbox\" [ngModel]=\"firstRunForm.enableCrashReporting\"\r\n (ngModelChange)=\"onCrashReportingToggle($event)\"\r\n [disabled]=\"firstRunInstalling\" />\r\n {{ crashReportingLabels.checkbox_accept }}\r\n </label>\r\n <small style=\"display:block; margin-top:4px; opacity:0.85; font-size:0.85rem; line-height:1.45;\">\r\n {{ crashReportingLabels.intro_short }}\r\n <a href=\"javascript:void(0)\" (click)=\"openCrashReportingDisclaimer()\"\r\n style=\"margin-left:6px;\">{{ crashReportingLabels.read_full }}</a>\r\n </small>\r\n </div>\r\n </div>\r\n\r\n <!-- Disclaimer GDPR popup (apre al check + da link \"leggi tutto\"). -->\r\n <p-dialog [(visible)]=\"crashReportingDisclaimerVisible\"\r\n [modal]=\"true\"\r\n [closable]=\"true\"\r\n [closeOnEscape]=\"true\"\r\n [header]=\"crashReportingLabels.title\"\r\n [style]=\"{ width: '600px', maxWidth: '90vw' }\"\r\n [contentStyle]=\"{ 'max-height': '70vh', 'overflow-y': 'auto' }\"\r\n [appendTo]=\"'body'\">\r\n <div style=\"line-height:1.55;\">\r\n <p>{{ crashReportingLabels.intro }}</p>\r\n <p><strong>{{ crashReportingLabels.what_we_send }}</strong></p>\r\n <ul style=\"margin:0 0 8px 18px; padding:0;\">\r\n <li>{{ crashReportingLabels.what_we_dont_send }}</li>\r\n <li>{{ crashReportingLabels.where_it_goes }}</li>\r\n <li>{{ crashReportingLabels.retention }}</li>\r\n <li>{{ crashReportingLabels.legal_basis }}</li>\r\n <li>{{ crashReportingLabels.how_to_disable }}</li>\r\n </ul>\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"crashReportingLabels.btn_decline\"\r\n severity=\"secondary\"\r\n (onClick)=\"declineCrashReporting()\"></p-button>\r\n <p-button [label]=\"crashReportingLabels.btn_accept\"\r\n (onClick)=\"acceptCrashReporting()\"></p-button>\r\n </ng-template>\r\n </p-dialog>\r\n\r\n </div><!-- /.first-run-body -->\r\n\r\n <!-- FOOTER sticky: error (se presente) + riga progress|CTA sempre visibile. -->\r\n <div class=\"first-run-footer\">\r\n @if (firstRunError && !firstRunErrorDismissed) {\r\n <div class=\"first-run-error\" role=\"alert\">\r\n <button type=\"button\"\r\n class=\"first-run-error__close\"\r\n (click)=\"dismissFirstRunError()\"\r\n [attr.aria-label]=\"'firstrun.ui.error_close_aria' | translate\"\r\n [title]=\"'firstrun.ui.error_close' | translate\">\u00D7</button>\r\n <div class=\"first-run-error__body\">{{ firstRunError }}</div>\r\n </div>\r\n }\r\n\r\n <div class=\"first-run-footer-row\">\r\n @if (firstRunInstalling && firstRunProgress.active) {\r\n <div class=\"first-run-progress\">\r\n <div class=\"first-run-progress-label\">\r\n <strong>{{ firstRunProgress.phase }}</strong>\r\n @if (firstRunProgress.total > 0 && !firstRunProgress.phase.includes('.restore')) {\r\n <span class=\"first-run-progress-count\">\r\n \u2014 {{ 'firstrun.ui.progress_batch' | translate }} {{ firstRunProgress.current }} / {{ firstRunProgress.total }}\r\n </span>\r\n }\r\n @if (firstRunProgress.elapsedMs > 0) {\r\n <span class=\"first-run-progress-count\">\r\n \u00B7 {{ (firstRunProgress.elapsedMs / 1000) | number:'1.0-0' }}s\r\n </span>\r\n }\r\n </div>\r\n @if (firstRunProgress.phase.includes('.restore') && firstRunProgress.percent < 5) {\r\n <p-progressBar mode=\"indeterminate\" [style]=\"{'height': '1rem'}\"></p-progressBar>\r\n } @else {\r\n <p-progressBar [value]=\"firstRunProgress.percent\" [showValue]=\"true\" [style]=\"{'height': '1rem'}\"></p-progressBar>\r\n }\r\n @if (firstRunProgress.message) {\r\n <div class=\"first-run-progress-message\">{{ firstRunProgress.message }}</div>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"first-run-progress-placeholder\"></div>\r\n }\r\n\r\n <div class=\"first-run-actions\">\r\n <p-button data-testid=\"first-run-submit\" [label]=\"'firstrun.ui.submit' | translate\" [loading]=\"firstRunInstalling\"\r\n [disabled]=\"firstRunInstalling || firstRunLoading\" (onClick)=\"submitFirstRunInstall()\"></p-button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n}\r\n", styles: [".first-run-overlay{position:fixed;inset:0;z-index:900;display:flex;align-items:center;justify-content:center;padding:24px;background:linear-gradient(135deg,#081221d9,#16294acc)}.first-run-card-wide{width:min(1180px,100%)!important}.first-run-card{width:min(860px,100%);min-height:min(720px,100vh - 48px);max-height:calc(100vh - 48px);display:flex;flex-direction:column;overflow:hidden;border-radius:14px;padding:22px;background:var(--p-surface-0);box-shadow:0 18px 48px #00000059;border:1px solid var(--p-content-border-color)}.first-run-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding-right:4px;margin-right:-4px}.first-run-footer{flex:0 0 auto;margin-top:12px;padding-top:12px;border-top:1px solid var(--p-content-border-color);display:flex;flex-direction:column;gap:10px}.first-run-footer-row{display:flex;align-items:center;gap:16px}.first-run-title{font-size:1.25rem;font-weight:700}.first-run-subtitle{margin-top:6px;margin-bottom:18px;opacity:.8}.first-run-field{margin-bottom:14px}.first-run-field label{display:block;margin-bottom:6px;font-weight:600}.first-run-checkbox-row label{display:inline-flex;align-items:center;gap:.5rem;margin-bottom:0;font-weight:500}.first-run-input{width:100%;height:40px;border-radius:8px;border:1px solid var(--p-content-border-color);padding:8px 12px;font-size:.95rem;background:var(--p-surface-0);color:var(--p-text-color)}.first-run-textarea{height:auto;min-height:56px;resize:vertical;font-family:var(--p-font-family);line-height:1.4}.first-run-grid{display:grid;grid-template-columns:1fr 1fr;column-gap:28px;row-gap:0}@media(max-width:860px){.first-run-grid{grid-template-columns:1fr}}.first-run-col{display:flex;flex-direction:column;min-width:0}.first-run-section-title{margin-top:4px;margin-bottom:12px;padding-bottom:6px;font-size:.95rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;opacity:.8;border-bottom:1px solid var(--p-content-border-color)}.first-run-section-title-rag{margin-top:18px}.first-run-python-warning{margin-top:8px;padding:10px 12px;background:#fef3c7;border:1px solid #fcd34d;border-radius:6px;color:#78350f;font-size:.85rem;line-height:1.4}.first-run-python-warning strong{color:#78350f}.first-run-python-warning code{background:#00000014;padding:1px 4px;border-radius:3px;font-family:ui-monospace,Cascadia Code,Consolas,monospace;font-size:.82rem}.first-run-python-warning ol li+li{margin-top:4px}.first-run-python-warning-link{color:#92400e;text-decoration:underline;font-weight:600}.first-run-python-warning-link:hover{color:#78350f}.first-run-python-warning-link:visited{color:#92400e}.first-run-python-note{margin-top:8px;padding:10px 12px;background:#dbeafe;border:1px solid #93c5fd;border-radius:6px;color:#1e3a8a;font-size:.85rem;line-height:1.4}.first-run-python-note strong{color:#1e3a8a}.first-run-python-note code{background:#00000014;padding:1px 4px;border-radius:3px;font-family:ui-monospace,Cascadia Code,Consolas,monospace;font-size:.82rem}.first-run-info-icon{margin-left:6px;font-size:.95rem;color:var(--p-primary-color, #3b82f6);cursor:help;vertical-align:middle;opacity:.85}.first-run-info-icon:hover{opacity:1}::ng-deep .p-tooltip.first-run-tooltip{max-width:440px}::ng-deep .p-tooltip.first-run-tooltip .p-tooltip-text{white-space:pre-line;font-size:.85rem;line-height:1.45}.first-run-input:focus{outline:2px solid rgba(59,130,246,.25);border-color:#3b82f6}.first-run-error{position:relative;margin:8px 0 14px;color:#7f1d1d;background:#fee2e2;border:1px solid #fca5a5;border-radius:8px;padding:10px 36px 10px 12px}.first-run-error__body{max-height:200px;overflow-y:auto;word-break:break-word;white-space:pre-wrap;font-family:ui-monospace,Cascadia Code,Consolas,monospace;font-size:.85rem;line-height:1.4}.first-run-error__close{position:absolute;top:6px;right:8px;width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center;padding:0;background:transparent;border:0;border-radius:4px;color:inherit;font-size:1.25rem;line-height:1;cursor:pointer;opacity:.75;transition:background-color .12s ease,opacity .12s ease}.first-run-error__close:hover,.first-run-error__close:focus-visible{opacity:1;background:#7f1d1d1f;outline:none}.first-run-error__close:active{background:#7f1d1d33}.first-run-actions{flex:0 0 auto;display:flex;justify-content:flex-end}.first-run-progress{flex:1 1 auto;min-width:0;padding:8px 12px;background:var(--p-surface-50);border:1px solid var(--p-content-border-color);border-radius:8px;display:flex;flex-direction:column;gap:4px}.first-run-progress-placeholder{flex:1 1 auto;min-width:0}.first-run-progress-label{font-size:.9rem}.first-run-progress-label strong{font-weight:600}.first-run-progress-label .first-run-progress-count{opacity:.7;margin-left:4px}.first-run-progress-message{font-size:.8rem;opacity:.75;font-family:ui-monospace,Cascadia Code,Consolas,monospace}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i6.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "unit", "mode", "color"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i7.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.Eager });
|
|
1612
|
+
}
|
|
1613
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicFirstRunWizardComponent, decorators: [{
|
|
1614
|
+
type: Component,
|
|
1615
|
+
args: [{ selector: 'wuic-first-run-wizard-impl', standalone: true, imports: [
|
|
1616
|
+
CommonModule,
|
|
1617
|
+
FormsModule,
|
|
1618
|
+
TranslateModule,
|
|
1619
|
+
ButtonModule,
|
|
1620
|
+
SelectModule,
|
|
1621
|
+
ProgressBarModule,
|
|
1622
|
+
TooltipModule,
|
|
1623
|
+
DialogModule
|
|
1624
|
+
], changeDetection: ChangeDetectionStrategy.Default, template: "@if (showFirstRunInstall) {\r\n<div class=\"first-run-overlay\">\r\n <div class=\"first-run-card first-run-card-wide\">\r\n <div class=\"first-run-title\">{{ 'firstrun.ui.title' | translate }}</div>\r\n <div class=\"first-run-subtitle\">{{ 'firstrun.ui.subtitle' | translate }}</div>\r\n\r\n <!-- BODY scrollabile: header (title/subtitle) e footer (progress + CTA)\r\n restano sempre visibili, solo questo blocco scrolla quando il form\r\n e' piu' alto della viewport. -->\r\n <div class=\"first-run-body\">\r\n\r\n <!-- Grid 2 colonne: sx = connessione + scaffold / dx = admin + RAG.\r\n Su viewport stretti (< 720px) il grid collassa a 1 colonna. -->\r\n <div class=\"first-run-grid\">\r\n\r\n <!-- \u2500\u2500 COLONNA SINISTRA: Database + Setup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"first-run-col\">\r\n\r\n @if (firstRunSetupModeOptions.length > 1) {\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-mode\">{{ 'firstrun.ui.setup_mode' | translate }}</label>\r\n <p-select [options]=\"firstRunSetupModeOptions\" optionLabel=\"label\" optionValue=\"value\"\r\n [ngModel]=\"firstRunForm.setupMode\" (ngModelChange)=\"onFirstRunSetupModeChanged($event)\"\r\n [disabled]=\"firstRunInstalling\" [style]=\"{ width: '100%' }\" inputId=\"first-run-mode\"></p-select>\r\n </div>\r\n }\r\n\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-dbms\">DBMS</label>\r\n <p-select [options]=\"firstRunDbmsOptions\" optionLabel=\"label\" optionValue=\"value\"\r\n [ngModel]=\"firstRunForm.dbms\" (ngModelChange)=\"onFirstRunDbmsChanged($event)\"\r\n [disabled]=\"firstRunInstalling\" [style]=\"{ width: '100%' }\" inputId=\"first-run-dbms\"></p-select>\r\n </div>\r\n\r\n @if (firstRunForm.setupMode === 'tutorial') {\r\n <div class=\"first-run-field first-run-checkbox-row\">\r\n <label>\r\n <input type=\"checkbox\" [ngModel]=\"firstRunForm.createTutorialIfMissing\"\r\n (ngModelChange)=\"firstRunForm.createTutorialIfMissing = !!$event\" [disabled]=\"firstRunInstalling\" />\r\n {{ 'firstrun.ui.create_tutorial_if_missing' | translate }}\r\n </label>\r\n </div>\r\n }\r\n\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-datasql\">DataSQLConnection</label>\r\n <input id=\"first-run-datasql\" class=\"first-run-input\" type=\"text\"\r\n [ngModel]=\"firstRunForm.dataConnectionString\" (ngModelChange)=\"onFirstRunConnectionChanged($event)\"\r\n [disabled]=\"firstRunInstalling\"\r\n [placeholder]=\"'firstrun.ui.data_connection_placeholder' | translate\" />\r\n </div>\r\n\r\n <div class=\"first-run-field\">\r\n <p-button data-testid=\"first-run-test-connection\" [label]=\"'firstrun.ui.test_connection' | translate\" severity=\"secondary\" [outlined]=\"true\"\r\n [loading]=\"firstRunConnectionTesting\"\r\n [disabled]=\"firstRunInstalling || firstRunLoading || !firstRunForm.dataConnectionString\"\r\n (onClick)=\"testFirstRunConnectionAndLoadDatabases()\"></p-button>\r\n </div>\r\n\r\n @if (firstRunForm.setupMode === 'tutorial') {\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-datadb\">{{ 'firstrun.ui.tutorial_data_db' | translate }}</label>\r\n <input id=\"first-run-datadb\" class=\"first-run-input\" type=\"text\"\r\n [ngModel]=\"firstRunForm.tutorialDataDbName\"\r\n (ngModelChange)=\"firstRunForm.tutorialDataDbName = $event; firstRunForm.dataDbName = $event\"\r\n [disabled]=\"firstRunInstalling || !firstRunConnectionValid\"\r\n placeholder=\"WideWorldImporters\" />\r\n </div>\r\n } @else {\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-datadb\">{{ 'firstrun.ui.data_db' | translate }}</label>\r\n <p-select [options]=\"firstRunDataDbOptions\" optionLabel=\"label\" optionValue=\"value\"\r\n [ngModel]=\"firstRunForm.dataDbName\" (ngModelChange)=\"firstRunForm.dataDbName = $event\"\r\n [disabled]=\"firstRunInstalling || firstRunDbLoading || !firstRunConnectionValid\" [filter]=\"true\"\r\n [editable]=\"true\" [style]=\"{ width: '100%' }\" inputId=\"first-run-datadb\"\r\n [placeholder]=\"'firstrun.ui.data_db_placeholder' | translate\"></p-select>\r\n <div style=\"margin-top: 0.5rem;\">\r\n <p-button data-testid=\"first-run-refresh-dbs\" [label]=\"'firstrun.ui.refresh_db_list' | translate\" severity=\"secondary\" [outlined]=\"true\" size=\"small\"\r\n [loading]=\"firstRunDbLoading\" [disabled]=\"firstRunInstalling || !firstRunConnectionValid\"\r\n (onClick)=\"testFirstRunConnectionAndLoadDatabases()\"></p-button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-metadb\">{{ (firstRunForm.setupMode === 'tutorial' ? 'firstrun.ui.tutorial_meta_db' : 'firstrun.ui.meta_db') | translate }}</label>\r\n <input id=\"first-run-metadb\" class=\"first-run-input\" type=\"text\"\r\n [ngModel]=\"firstRunForm.setupMode === 'tutorial' ? firstRunForm.tutorialMetadataDbName : firstRunForm.metadataDbName\"\r\n (ngModelChange)=\"firstRunForm.metadataDbName = $event; firstRunForm.tutorialMetadataDbName = $event\"\r\n [disabled]=\"firstRunInstalling\"\r\n placeholder=\"metadataDB\" />\r\n </div>\r\n\r\n @if (firstRunForm.setupMode !== 'tutorial') {\r\n <div class=\"first-run-field first-run-checkbox-row\">\r\n <label>\r\n <input type=\"checkbox\" [ngModel]=\"firstRunForm.scaffoldExistingDatabase\"\r\n (ngModelChange)=\"firstRunForm.scaffoldExistingDatabase = !!$event\"\r\n [disabled]=\"firstRunInstalling\" />\r\n {{ 'firstrun.ui.scaffold_existing' | translate }}\r\n </label>\r\n <small style=\"display:block; margin-top:4px; opacity:0.75; font-size:0.8rem; line-height:1.35;\"\r\n [innerHTML]=\"'firstrun.ui.scaffold_existing_hint_html' | translate\"></small>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500 COLONNA DESTRA: Admin + RAG \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"first-run-col\">\r\n\r\n <div class=\"first-run-section-title\">{{ 'firstrun.ui.admin_section' | translate }}</div>\r\n\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-admin-username\">{{ 'firstrun.ui.admin_username' | translate }}</label>\r\n <input id=\"first-run-admin-username\" class=\"first-run-input\" type=\"text\"\r\n [ngModel]=\"firstRunForm.adminUsername\"\r\n (ngModelChange)=\"firstRunForm.adminUsername = $event\"\r\n [disabled]=\"firstRunInstalling\" autocomplete=\"off\"\r\n placeholder=\"admin\" />\r\n </div>\r\n\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-admin-password\">{{ 'firstrun.ui.admin_password' | translate }}</label>\r\n <input id=\"first-run-admin-password\" class=\"first-run-input\" type=\"password\"\r\n [ngModel]=\"firstRunForm.adminPassword\"\r\n (ngModelChange)=\"firstRunForm.adminPassword = $event\"\r\n [disabled]=\"firstRunInstalling\" autocomplete=\"new-password\"\r\n [placeholder]=\"'firstrun.ui.admin_password_placeholder' | translate\" />\r\n </div>\r\n\r\n <div class=\"first-run-field\">\r\n <label for=\"first-run-admin-language\">{{ 'firstrun.ui.admin_language' | translate }}</label>\r\n <p-select [options]=\"firstRunAdminLanguageOptions\" optionLabel=\"label\" optionValue=\"value\"\r\n [ngModel]=\"firstRunForm.adminLanguage\"\r\n (ngModelChange)=\"onFirstRunAdminLanguageChanged($event)\"\r\n [disabled]=\"firstRunInstalling\"\r\n [style]=\"{ width: '100%' }\" inputId=\"first-run-admin-language\"></p-select>\r\n <small style=\"display:block; margin-top:4px; opacity:0.75; font-size:0.8rem; line-height:1.35;\">\r\n {{ 'firstrun.ui.admin_language_hint' | translate }}\r\n </small>\r\n </div>\r\n\r\n <div class=\"first-run-section-title first-run-section-title-rag\">{{ 'firstrun.ui.rag_section' | translate }}</div>\r\n\r\n <div class=\"first-run-field\">\r\n <small style=\"display:block; margin-bottom:8px; opacity:0.8; font-size:0.85rem; line-height:1.4;\"\r\n [innerHTML]=\"'firstrun.ui.rag_intro_html' | translate\"></small>\r\n <label for=\"first-run-anthropic-key\">\r\n {{ 'firstrun.ui.anthropic_key' | translate }}\r\n <i class=\"pi pi-info-circle first-run-info-icon\"\r\n [pTooltip]=\"'firstrun.rag.apiKeyTooltip' | translate\"\r\n tooltipPosition=\"right\"\r\n [tooltipStyleClass]=\"'first-run-tooltip'\"\r\n [escape]=\"false\"></i>\r\n </label>\r\n <textarea id=\"first-run-anthropic-key\" class=\"first-run-input first-run-textarea\"\r\n [ngModel]=\"firstRunForm.anthropicApiKey\"\r\n (ngModelChange)=\"firstRunForm.anthropicApiKey = $event\"\r\n [disabled]=\"firstRunInstalling\"\r\n rows=\"2\" autocomplete=\"off\"\r\n placeholder=\"sk-ant-...\"></textarea>\r\n <small style=\"display:block; margin-top:4px; opacity:0.75; font-size:0.8rem; line-height:1.35;\"\r\n [innerHTML]=\"'firstrun.ui.anthropic_key_hint_html' | translate\"></small>\r\n </div>\r\n </div>\r\n\r\n <!-- Crash reporting opt-in (skill crash-reporting Commit 8). Default OFF.\r\n Il consenso GDPR usa labels HARDCODED in `crashReportingLabels`\r\n dentro il componente (NON `wuic_translation`) perche' il\r\n first-run gira PRIMA che il DB metadati esista \u2014 niente DB,\r\n niente translation table. Vedi commento esteso nel componente.\r\n Al check del consenso si apre il popup col disclaimer\r\n dettagliato; l'utente conferma li' per attivare il flag. -->\r\n <div class=\"first-run-section-title first-run-section-title-crash-reporting\">{{ crashReportingLabels.title }}</div>\r\n <div class=\"first-run-field first-run-checkbox-row\">\r\n <label>\r\n <input type=\"checkbox\" [ngModel]=\"firstRunForm.enableCrashReporting\"\r\n (ngModelChange)=\"onCrashReportingToggle($event)\"\r\n [disabled]=\"firstRunInstalling\" />\r\n {{ crashReportingLabels.checkbox_accept }}\r\n </label>\r\n <small style=\"display:block; margin-top:4px; opacity:0.85; font-size:0.85rem; line-height:1.45;\">\r\n {{ crashReportingLabels.intro_short }}\r\n <a href=\"javascript:void(0)\" (click)=\"openCrashReportingDisclaimer()\"\r\n style=\"margin-left:6px;\">{{ crashReportingLabels.read_full }}</a>\r\n </small>\r\n </div>\r\n </div>\r\n\r\n <!-- Disclaimer GDPR popup (apre al check + da link \"leggi tutto\"). -->\r\n <p-dialog [(visible)]=\"crashReportingDisclaimerVisible\"\r\n [modal]=\"true\"\r\n [closable]=\"true\"\r\n [closeOnEscape]=\"true\"\r\n [header]=\"crashReportingLabels.title\"\r\n [style]=\"{ width: '600px', maxWidth: '90vw' }\"\r\n [contentStyle]=\"{ 'max-height': '70vh', 'overflow-y': 'auto' }\"\r\n [appendTo]=\"'body'\">\r\n <div style=\"line-height:1.55;\">\r\n <p>{{ crashReportingLabels.intro }}</p>\r\n <p><strong>{{ crashReportingLabels.what_we_send }}</strong></p>\r\n <ul style=\"margin:0 0 8px 18px; padding:0;\">\r\n <li>{{ crashReportingLabels.what_we_dont_send }}</li>\r\n <li>{{ crashReportingLabels.where_it_goes }}</li>\r\n <li>{{ crashReportingLabels.retention }}</li>\r\n <li>{{ crashReportingLabels.legal_basis }}</li>\r\n <li>{{ crashReportingLabels.how_to_disable }}</li>\r\n </ul>\r\n </div>\r\n <ng-template pTemplate=\"footer\">\r\n <p-button [label]=\"crashReportingLabels.btn_decline\"\r\n severity=\"secondary\"\r\n (onClick)=\"declineCrashReporting()\"></p-button>\r\n <p-button [label]=\"crashReportingLabels.btn_accept\"\r\n (onClick)=\"acceptCrashReporting()\"></p-button>\r\n </ng-template>\r\n </p-dialog>\r\n\r\n </div><!-- /.first-run-body -->\r\n\r\n <!-- FOOTER sticky: error (se presente) + riga progress|CTA sempre visibile. -->\r\n <div class=\"first-run-footer\">\r\n @if (firstRunError && !firstRunErrorDismissed) {\r\n <div class=\"first-run-error\" role=\"alert\">\r\n <button type=\"button\"\r\n class=\"first-run-error__close\"\r\n (click)=\"dismissFirstRunError()\"\r\n [attr.aria-label]=\"'firstrun.ui.error_close_aria' | translate\"\r\n [title]=\"'firstrun.ui.error_close' | translate\">\u00D7</button>\r\n <div class=\"first-run-error__body\">{{ firstRunError }}</div>\r\n </div>\r\n }\r\n\r\n <div class=\"first-run-footer-row\">\r\n @if (firstRunInstalling && firstRunProgress.active) {\r\n <div class=\"first-run-progress\">\r\n <div class=\"first-run-progress-label\">\r\n <strong>{{ firstRunProgress.phase }}</strong>\r\n @if (firstRunProgress.total > 0 && !firstRunProgress.phase.includes('.restore')) {\r\n <span class=\"first-run-progress-count\">\r\n \u2014 {{ 'firstrun.ui.progress_batch' | translate }} {{ firstRunProgress.current }} / {{ firstRunProgress.total }}\r\n </span>\r\n }\r\n @if (firstRunProgress.elapsedMs > 0) {\r\n <span class=\"first-run-progress-count\">\r\n \u00B7 {{ (firstRunProgress.elapsedMs / 1000) | number:'1.0-0' }}s\r\n </span>\r\n }\r\n </div>\r\n @if (firstRunProgress.phase.includes('.restore') && firstRunProgress.percent < 5) {\r\n <p-progressBar mode=\"indeterminate\" [style]=\"{'height': '1rem'}\"></p-progressBar>\r\n } @else {\r\n <p-progressBar [value]=\"firstRunProgress.percent\" [showValue]=\"true\" [style]=\"{'height': '1rem'}\"></p-progressBar>\r\n }\r\n @if (firstRunProgress.message) {\r\n <div class=\"first-run-progress-message\">{{ firstRunProgress.message }}</div>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"first-run-progress-placeholder\"></div>\r\n }\r\n\r\n <div class=\"first-run-actions\">\r\n <p-button data-testid=\"first-run-submit\" [label]=\"'firstrun.ui.submit' | translate\" [loading]=\"firstRunInstalling\"\r\n [disabled]=\"firstRunInstalling || firstRunLoading\" (onClick)=\"submitFirstRunInstall()\"></p-button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n}\r\n", styles: [".first-run-overlay{position:fixed;inset:0;z-index:900;display:flex;align-items:center;justify-content:center;padding:24px;background:linear-gradient(135deg,#081221d9,#16294acc)}.first-run-card-wide{width:min(1180px,100%)!important}.first-run-card{width:min(860px,100%);min-height:min(720px,100vh - 48px);max-height:calc(100vh - 48px);display:flex;flex-direction:column;overflow:hidden;border-radius:14px;padding:22px;background:var(--p-surface-0);box-shadow:0 18px 48px #00000059;border:1px solid var(--p-content-border-color)}.first-run-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding-right:4px;margin-right:-4px}.first-run-footer{flex:0 0 auto;margin-top:12px;padding-top:12px;border-top:1px solid var(--p-content-border-color);display:flex;flex-direction:column;gap:10px}.first-run-footer-row{display:flex;align-items:center;gap:16px}.first-run-title{font-size:1.25rem;font-weight:700}.first-run-subtitle{margin-top:6px;margin-bottom:18px;opacity:.8}.first-run-field{margin-bottom:14px}.first-run-field label{display:block;margin-bottom:6px;font-weight:600}.first-run-checkbox-row label{display:inline-flex;align-items:center;gap:.5rem;margin-bottom:0;font-weight:500}.first-run-input{width:100%;height:40px;border-radius:8px;border:1px solid var(--p-content-border-color);padding:8px 12px;font-size:.95rem;background:var(--p-surface-0);color:var(--p-text-color)}.first-run-textarea{height:auto;min-height:56px;resize:vertical;font-family:var(--p-font-family);line-height:1.4}.first-run-grid{display:grid;grid-template-columns:1fr 1fr;column-gap:28px;row-gap:0}@media(max-width:860px){.first-run-grid{grid-template-columns:1fr}}.first-run-col{display:flex;flex-direction:column;min-width:0}.first-run-section-title{margin-top:4px;margin-bottom:12px;padding-bottom:6px;font-size:.95rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;opacity:.8;border-bottom:1px solid var(--p-content-border-color)}.first-run-section-title-rag{margin-top:18px}.first-run-python-warning{margin-top:8px;padding:10px 12px;background:#fef3c7;border:1px solid #fcd34d;border-radius:6px;color:#78350f;font-size:.85rem;line-height:1.4}.first-run-python-warning strong{color:#78350f}.first-run-python-warning code{background:#00000014;padding:1px 4px;border-radius:3px;font-family:ui-monospace,Cascadia Code,Consolas,monospace;font-size:.82rem}.first-run-python-warning ol li+li{margin-top:4px}.first-run-python-warning-link{color:#92400e;text-decoration:underline;font-weight:600}.first-run-python-warning-link:hover{color:#78350f}.first-run-python-warning-link:visited{color:#92400e}.first-run-python-note{margin-top:8px;padding:10px 12px;background:#dbeafe;border:1px solid #93c5fd;border-radius:6px;color:#1e3a8a;font-size:.85rem;line-height:1.4}.first-run-python-note strong{color:#1e3a8a}.first-run-python-note code{background:#00000014;padding:1px 4px;border-radius:3px;font-family:ui-monospace,Cascadia Code,Consolas,monospace;font-size:.82rem}.first-run-info-icon{margin-left:6px;font-size:.95rem;color:var(--p-primary-color, #3b82f6);cursor:help;vertical-align:middle;opacity:.85}.first-run-info-icon:hover{opacity:1}::ng-deep .p-tooltip.first-run-tooltip{max-width:440px}::ng-deep .p-tooltip.first-run-tooltip .p-tooltip-text{white-space:pre-line;font-size:.85rem;line-height:1.45}.first-run-input:focus{outline:2px solid rgba(59,130,246,.25);border-color:#3b82f6}.first-run-error{position:relative;margin:8px 0 14px;color:#7f1d1d;background:#fee2e2;border:1px solid #fca5a5;border-radius:8px;padding:10px 36px 10px 12px}.first-run-error__body{max-height:200px;overflow-y:auto;word-break:break-word;white-space:pre-wrap;font-family:ui-monospace,Cascadia Code,Consolas,monospace;font-size:.85rem;line-height:1.4}.first-run-error__close{position:absolute;top:6px;right:8px;width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center;padding:0;background:transparent;border:0;border-radius:4px;color:inherit;font-size:1.25rem;line-height:1;cursor:pointer;opacity:.75;transition:background-color .12s ease,opacity .12s ease}.first-run-error__close:hover,.first-run-error__close:focus-visible{opacity:1;background:#7f1d1d1f;outline:none}.first-run-error__close:active{background:#7f1d1d33}.first-run-actions{flex:0 0 auto;display:flex;justify-content:flex-end}.first-run-progress{flex:1 1 auto;min-width:0;padding:8px 12px;background:var(--p-surface-50);border:1px solid var(--p-content-border-color);border-radius:8px;display:flex;flex-direction:column;gap:4px}.first-run-progress-placeholder{flex:1 1 auto;min-width:0}.first-run-progress-label{font-size:.9rem}.first-run-progress-label strong{font-weight:600}.first-run-progress-label .first-run-progress-count{opacity:.7;margin-left:4px}.first-run-progress-message{font-size:.8rem;opacity:.75;font-family:ui-monospace,Cascadia Code,Consolas,monospace}\n"] }]
|
|
1625
|
+
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i8.TranslateService }, { type: i3.MessageService }, { type: i0.Injector }], propDecorators: { complete: [{
|
|
1626
|
+
type: Output
|
|
1627
|
+
}] } });
|
|
1628
|
+
|
|
1629
|
+
export { WuicFirstRunWizardComponent };
|
|
1630
|
+
//# sourceMappingURL=wuic-framework-lib-first-run-wizard.component-D1ZQUl-q.mjs.map
|