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.
Files changed (27) hide show
  1. package/fesm2022/{wuic-framework-lib-chart-list.component-0J1ghIrS.mjs → wuic-framework-lib-chart-list.component-Dq9IEKsY.mjs} +50 -38
  2. package/fesm2022/{wuic-framework-lib-code-editor.component-C21mvjyr.mjs → wuic-framework-lib-code-editor.component-B6IpTSo8.mjs} +174 -274
  3. package/fesm2022/{wuic-framework-lib-designer.component-x10ZzUEy.mjs → wuic-framework-lib-designer.component-D6cbqNFg.mjs} +499 -581
  4. package/fesm2022/wuic-framework-lib-first-run-wizard.component-D1ZQUl-q.mjs +1630 -0
  5. package/fesm2022/{wuic-framework-lib-framework-docs.component-xTGLbEnp.mjs → wuic-framework-lib-framework-docs.component-C_o5r7pg.mjs} +4320 -1255
  6. package/fesm2022/{wuic-framework-lib-pivot-builder.component-BkFFjPm4.mjs → wuic-framework-lib-pivot-builder.component-DkWGzJpr.mjs} +580 -68
  7. 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
  8. package/fesm2022/wuic-framework-lib-report-designer.component-DcpCWghx.mjs +380 -0
  9. package/fesm2022/wuic-framework-lib-report-scaffold-dialog.component-CgUjG7Bi.mjs +431 -0
  10. package/fesm2022/{wuic-framework-lib-report-viewer.component-BR-BfI2r.mjs → wuic-framework-lib-report-viewer.component-C4Opgl_M.mjs} +8 -13
  11. package/fesm2022/{wuic-framework-lib-scene3d-designer.component-BQzFxSXP.mjs → wuic-framework-lib-scene3d-designer.component-8l3vsyEb.mjs} +323 -88
  12. package/fesm2022/{wuic-framework-lib-scene3d-light-baker-gpu-0LjqhSNn.mjs → wuic-framework-lib-scene3d-light-baker-gpu-C1w-8Aai.mjs} +2 -2
  13. package/fesm2022/{wuic-framework-lib-scene3d-shader-effect-CQLWkuh1.mjs → wuic-framework-lib-scene3d-shader-effect-DjHyCAFK.mjs} +612 -32
  14. package/fesm2022/{wuic-framework-lib-scene3d-viewer.component-imoV3HOs.mjs → wuic-framework-lib-scene3d-viewer.component-nMSl5jbe.mjs} +149 -18
  15. package/fesm2022/{wuic-framework-lib-scheduler-list.component-xzlTEkoq.mjs → wuic-framework-lib-scheduler-list.component-Cvk3Zg0Q.mjs} +169 -53
  16. package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-CrS6fOGj.mjs → wuic-framework-lib-spreadsheet-list-sf.component-C1U7JPiq.mjs} +149 -108
  17. package/fesm2022/{wuic-framework-lib-stimulsoft-license.service-BIyxB4pO.mjs → wuic-framework-lib-stimulsoft-license.service-CsNNSFYs.mjs} +3 -3
  18. package/fesm2022/wuic-framework-lib-timeline-list.component-cZbPgNh1.mjs +805 -0
  19. package/fesm2022/{wuic-framework-lib-workflow-designer.component-5xtQ7-6z.mjs → wuic-framework-lib-workflow-designer.component-C_4RQGa0.mjs} +180 -150
  20. package/fesm2022/{wuic-framework-lib-wuic-framework-lib-3TZ-j-yL.mjs → wuic-framework-lib-wuic-framework-lib-BZZwta8b.mjs} +6785 -2347
  21. package/fesm2022/wuic-framework-lib.mjs +1 -1
  22. package/package.json +21 -2
  23. package/styles/_wuic-tokens.scss +82 -0
  24. package/styles/styles.scss +160 -0
  25. package/types/wuic-framework-lib.d.ts +4269 -2691
  26. package/fesm2022/wuic-framework-lib-first-run-wizard.component-BuSxs_CM.mjs +0 -1316
  27. package/fesm2022/wuic-framework-lib-report-designer.component-ZvB46-LV.mjs +0 -194
@@ -1,1316 +0,0 @@
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, n as WUIC_SUPPRESS_GLOBAL_ERROR } from './wuic-framework-lib-wuic-framework-lib-3TZ-j-yL.mjs';
10
- import { firstValueFrom } from 'rxjs';
11
- import * as i2 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 i6 from 'primeng/select';
16
- import { SelectModule } from 'primeng/select';
17
- import * as i11 from 'primeng/progressbar';
18
- import { ProgressBarModule } from 'primeng/progressbar';
19
- import * as i6$1 from 'primeng/tooltip';
20
- import { TooltipModule } from 'primeng/tooltip';
21
- import * as i2$1 from 'primeng/dialog';
22
- import { DialogModule } from 'primeng/dialog';
23
- import * as i3 from 'primeng/api';
24
-
25
- /**
26
- * Componente standalone framework che incapsula l'INTERO workflow di firstRun:
27
- *
28
- * 1. probe `GET /api/Meta/FirstRunStatus` al boot per decidere visibilita';
29
- * 2. rendering del form wizard (DBMS, connection string, admin, RAG API key);
30
- * 3. test connessione + caricamento elenco DB (`MetaService.get_database_names`);
31
- * 4. submit `MetaService.configure_wuic` con tutti i parametri raccolti;
32
- * 5. polling progress (`GET /api/Meta/FirstRunProgress`) durante l'install;
33
- * 6. handling specifico dell'errore `METADATA_DB_EXISTS_CONFIRM_REQUIRED:` con
34
- * promptDialog di conferma drop+recreate;
35
- * 7. clear lato client (cookies + sessionStorage + localStorage + indexedDB)
36
- * al termine dell'install e wait `waitForBackendReady` prima del redirect;
37
- * 8. registrazione bundle traduzioni `firstrun.*` (5 lingue) PRIMA che il
38
- * template risolva i pipe `| translate`.
39
- *
40
- * **Auto-control**: il componente decide da se' se mostrarsi (firstRun=true) o
41
- * essere invisibile (firstRun=false). Il consumer si limita a montarlo nel
42
- * template app shell (`<wuic-first-run-wizard></wuic-first-run-wizard>`) e
43
- * sottoscrive l'output `(complete)` per refreshare la UI quando il wizard
44
- * conclude (di solito un full-page reload via location.assign).
45
- *
46
- * **Self-contained**: tutti gli helper (parseConnectionString, normalizeDbms,
47
- * extractErrorMessage, ecc.) sono incapsulati qui dentro. Niente dipendenza
48
- * dal consumer oltre ai servizi WUIC standard (HttpClient, TranslateService,
49
- * WtoolboxService per promptDialog + appSettings).
50
- */
51
- class WuicFirstRunWizardComponent {
52
- http;
53
- translate;
54
- messageService;
55
- injector;
56
- /**
57
- * Emessa quando il flusso firstRun viene rilevato come NON necessario
58
- * (firstRun=false al boot) OPPURE quando l'install e' completato con
59
- * successo. Il consumer puo' usarla per inizializzare il resto della UI
60
- * (carico shell, dynamic imports lazy, ecc.) sapendo che non c'e' overlay
61
- * a coprire la pagina. In caso di success-with-redirect (caso normale post
62
- * install) la emit avviene PRIMA del location.assign, ma e' essenzialmente
63
- * fire-and-forget perche' il browser navighera' via subito dopo.
64
- */
65
- complete = new EventEmitter();
66
- // ── Visibilita' overlay ─────────────────────────────────────────────────
67
- /** True quando il backend ha confermato firstRun=true e dobbiamo renderizzare il form. */
68
- showFirstRunInstall = false;
69
- /** True durante il bootstrap iniziale (probe FirstRunStatus + setup form). */
70
- firstRunLoading = true;
71
- /** True durante la POST configure_wuic (disabilita CTA + input). */
72
- firstRunInstalling = false;
73
- /** True durante il "test connessione e carica DB" in flight. */
74
- firstRunDbLoading = false;
75
- firstRunConnectionTesting = false;
76
- /** True dopo che il test connessione e' andato a buon fine. Gate del DB select. */
77
- firstRunConnectionValid = false;
78
- // ── Banner errore con dismiss ────────────────────────────────────────────
79
- _firstRunError = '';
80
- firstRunErrorDismissed = false;
81
- get firstRunError() { return this._firstRunError; }
82
- set firstRunError(value) {
83
- const next = value ?? '';
84
- if (next !== this._firstRunError) {
85
- this.firstRunErrorDismissed = false;
86
- }
87
- this._firstRunError = next;
88
- }
89
- dismissFirstRunError() { this.firstRunErrorDismissed = true; }
90
- // ── Opzioni form (defaultate, restrette dal probe FirstRunStatus) ───────
91
- firstRunDbmsOptions = [
92
- { label: 'Microsoft SQL Server', value: 'mssql' },
93
- { label: 'MySQL', value: 'mysql' },
94
- { label: 'Oracle', value: 'oracle' },
95
- { label: 'PostgreSQL', value: 'postgres' }
96
- ];
97
- firstRunAdminLanguageOptions = [
98
- { label: 'Italiano', value: 'it-IT' },
99
- { label: 'English', value: 'en-US' },
100
- { label: 'Français', value: 'fr-FR' },
101
- { label: 'Español', value: 'es-ES' },
102
- { label: 'Deutsch', value: 'de-DE' }
103
- ];
104
- firstRunSetupModeOptions = [
105
- { label: 'DB esistente', value: 'existing' },
106
- { label: 'Tutorial WideWorldImporters', value: 'tutorial' }
107
- ];
108
- firstRunTutorialAvailable = true;
109
- firstRunForm = {
110
- setupMode: 'existing',
111
- createTutorialIfMissing: true,
112
- dbms: 'mssql',
113
- dataConnectionString: '',
114
- dataDbName: '',
115
- tutorialDataDbName: 'WideWorldImporters',
116
- tutorialMetadataDbName: 'MetadataCRM',
117
- metadataDbName: 'metadataDB',
118
- adminUsername: 'admin',
119
- adminPassword: '',
120
- adminLanguage: WuicFirstRunWizardComponent.resolveDefaultAdminLanguage(),
121
- scaffoldExistingDatabase: false,
122
- anthropicApiKey: '',
123
- // Crash reporting opt-in (skill crash-reporting Commit 8). OFF by default
124
- // per GDPR: l'utente lo abilita esplicitamente in fase di first-install
125
- // dopo aver letto il disclaimer (link/expand inline nel template). Se
126
- // abilitato qui, il backend writer di appsettings setta
127
- // `CrashReporting.Enabled=true` e `DisclaimerAcceptedVersion="1.0"` +
128
- // `DisclaimerAcceptedAt=<now>` (audit trail) — equivalente al flusso
129
- // appsettings-editor (Commit 9), centralizzato cosi' che il primo
130
- // boot non richieda un secondo round-trip dialog.
131
- enableCrashReporting: false,
132
- };
133
- /**
134
- * Visibilita' del popup disclaimer GDPR del crash-reporting (apre al check
135
- * del checkbox nel form). Vedi `crashReportingLabels` per il razionale dei
136
- * testi hardcoded.
137
- */
138
- crashReportingDisclaimerVisible = false;
139
- /**
140
- * Labels HARDCODED per il disclaimer GDPR del crash-reporting.
141
- *
142
- * Razionale: il first-run wizard e' l'UNICA pagina che gira PRIMA che il
143
- * DB metadati esista (`MetaDataSQLConnection` ancora a placeholder
144
- * `__SET_CONNECTION_STRING__`). La `wuic_translation` table NON e'
145
- * interrogabile in quello stato — qualunque chiave registrata li' viene
146
- * mostrata grezza dal `TranslationManagerService` (fallback "ritorna la
147
- * chiave"). Le altre label del wizard funzionano via `ngx-translate`
148
- * con i dictionary statici bundlati nell'app, ma non abbiamo (al
149
- * momento) un dictionary GDPR multilingua bundlato — quindi niente
150
- * fallback. Soluzione: traduzioni inline in TS, switch sulla lingua
151
- * scelta dall'admin nel select "Lingua admin iniziale".
152
- *
153
- * Contratto: `getter` ricalcola al cambio di `firstRunForm.adminLanguage`
154
- * (che lega il select del wizard). Default 'en-US' se sconosciuta.
155
- */
156
- get crashReportingLabels() {
157
- const dict = this.crashReportingDictionary;
158
- const key = (this.firstRunForm.adminLanguage || 'en-US');
159
- return dict[key] || dict['en-US'];
160
- }
161
- /**
162
- * Dictionary inline italian/english/french/spanish/german. Lingue
163
- * supportate matchano `getSupportedLanguages` del backend. Bump
164
- * `disclaimerVersion` (lato backend `CrashReportingOptions.CurrentDisclaimerVersion`)
165
- * se cambi sostanzialmente i testi qui — invalida i consensi precedenti
166
- * e forza re-prompt al login successivo.
167
- */
168
- crashReportingDictionary = {
169
- 'it-IT': {
170
- title: 'Segnalazione errori automatica',
171
- checkbox_accept: 'Acconsento all\'invio anonimo dei rapporti di errore',
172
- intro_short: 'WUIC può inviare automaticamente i dettagli tecnici degli errori al server privato del produttore.',
173
- read_full: 'Leggi tutto',
174
- 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.',
175
- what_we_send: 'Cosa viene inviato:',
176
- what_we_dont_send: 'Tipo eccezione, messaggio, stack trace, URL della richiesta, versione del framework, fingerprint anonimo della macchina (hash), user-agent del browser.',
177
- where_it_goes: 'Dove va: server privato del fornitore (errors.wuic-framework.com), accessibile solo dal team di sviluppo del framework.',
178
- retention: 'Conservazione: massimo 90 giorni, poi cancellazione automatica.',
179
- legal_basis: 'Base giuridica: consenso esplicito (GDPR art. 6.1.a). Puoi revocarlo in qualsiasi momento.',
180
- how_to_disable: 'Come disattivare: dal menu Impostazioni → Configurazione applicazione, togli il flag "CrashReporting.Enabled".',
181
- btn_accept: 'Accetto e attivo',
182
- btn_decline: 'Rifiuto'
183
- },
184
- 'en-US': {
185
- title: 'Automatic error reporting',
186
- checkbox_accept: 'I consent to anonymous error report transmission',
187
- intro_short: 'WUIC can automatically send technical error details to the vendor\'s private server.',
188
- read_full: 'Read full text',
189
- 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.',
190
- what_we_send: 'What gets sent:',
191
- what_we_dont_send: 'Exception type, message, stack trace, request URL, framework version, anonymous machine fingerprint (hash), browser user-agent.',
192
- where_it_goes: 'Where it goes: vendor\'s private server (errors.wuic-framework.com), accessible only by the framework development team.',
193
- retention: 'Retention: up to 90 days, then automatic deletion.',
194
- legal_basis: 'Legal basis: explicit consent (GDPR art. 6.1.a). You can revoke it at any time.',
195
- how_to_disable: 'How to disable: from Settings → Application configuration, untick the "CrashReporting.Enabled" flag.',
196
- btn_accept: 'Accept and enable',
197
- btn_decline: 'Decline'
198
- },
199
- 'fr-FR': {
200
- title: 'Signalement automatique des erreurs',
201
- checkbox_accept: 'J\'accepte la transmission anonyme des rapports d\'erreur',
202
- intro_short: 'WUIC peut envoyer automatiquement les détails techniques des erreurs au serveur privé du fournisseur.',
203
- read_full: 'Lire le texte complet',
204
- 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.',
205
- what_we_send: 'Ce qui est envoyé :',
206
- 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.',
207
- where_it_goes: 'Destination : serveur privé du fournisseur (errors.wuic-framework.com), accessible uniquement par l\'équipe de développement.',
208
- retention: 'Rétention : 90 jours maximum, puis suppression automatique.',
209
- legal_basis: 'Base juridique : consentement explicite (RGPD art. 6.1.a). Révocable à tout moment.',
210
- how_to_disable: 'Comment désactiver : Paramètres → Configuration de l\'application, décochez "CrashReporting.Enabled".',
211
- btn_accept: 'Accepter et activer',
212
- btn_decline: 'Refuser'
213
- },
214
- 'es-ES': {
215
- title: 'Notificación automática de errores',
216
- checkbox_accept: 'Acepto la transmisión anónima de informes de error',
217
- intro_short: 'WUIC puede enviar automáticamente los detalles técnicos de los errores al servidor privado del fabricante.',
218
- read_full: 'Leer texto completo',
219
- 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.',
220
- what_we_send: 'Qué se envía:',
221
- 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.',
222
- where_it_goes: 'A dónde va: servidor privado del fabricante (errors.wuic-framework.com), accesible solo por el equipo de desarrollo.',
223
- retention: 'Retención: hasta 90 días, después eliminación automática.',
224
- legal_basis: 'Base jurídica: consentimiento explícito (RGPD art. 6.1.a). Revocable en cualquier momento.',
225
- how_to_disable: 'Cómo desactivar: Ajustes → Configuración de la aplicación, desmarque "CrashReporting.Enabled".',
226
- btn_accept: 'Aceptar y activar',
227
- btn_decline: 'Rechazar'
228
- },
229
- 'de-DE': {
230
- title: 'Automatische Fehlermeldung',
231
- checkbox_accept: 'Ich willige in die anonyme Übertragung von Fehlerberichten ein',
232
- intro_short: 'WUIC kann technische Fehlerdetails automatisch an den privaten Server des Anbieters senden.',
233
- read_full: 'Vollständigen Text lesen',
234
- 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.',
235
- what_we_send: 'Was gesendet wird:',
236
- what_we_dont_send: 'Ausnahmetyp, Nachricht, Stack-Trace, Anfrage-URL, Framework-Version, anonymer Maschinen-Fingerabdruck (Hash), Browser-User-Agent.',
237
- where_it_goes: 'Wohin: privater Server des Anbieters (errors.wuic-framework.com), nur für das Entwicklungsteam zugänglich.',
238
- retention: 'Aufbewahrung: max. 90 Tage, dann automatische Löschung.',
239
- legal_basis: 'Rechtsgrundlage: ausdrückliche Einwilligung (DSGVO Art. 6.1.a). Jederzeit widerrufbar.',
240
- how_to_disable: 'Deaktivieren: Einstellungen → Anwendungskonfiguration, Flag "CrashReporting.Enabled" deaktivieren.',
241
- btn_accept: 'Akzeptieren und aktivieren',
242
- btn_decline: 'Ablehnen'
243
- }
244
- };
245
- /**
246
- * Click sul checkbox del consenso. Comportamento:
247
- * - check (off → on) : apre il popup disclaimer; il flag viene davvero
248
- * attivato SOLO quando l'utente preme "Accetto" nel dialog. Se chiude
249
- * o annulla, il flag resta off.
250
- * - uncheck (on → off): no popup, opt-out immediato.
251
- */
252
- onCrashReportingToggle(newValue) {
253
- if (newValue) {
254
- // Tenuto false finche' l'utente non conferma nel dialog. Cosi' il
255
- // checkbox NON appare gia' flaggato mentre l'utente legge il
256
- // disclaimer — l'attivazione e' un atto esplicito post-lettura.
257
- this.firstRunForm.enableCrashReporting = false;
258
- this.crashReportingDisclaimerVisible = true;
259
- }
260
- else {
261
- this.firstRunForm.enableCrashReporting = false;
262
- }
263
- }
264
- openCrashReportingDisclaimer() {
265
- this.crashReportingDisclaimerVisible = true;
266
- }
267
- acceptCrashReporting() {
268
- this.firstRunForm.enableCrashReporting = true;
269
- this.crashReportingDisclaimerVisible = false;
270
- }
271
- declineCrashReporting() {
272
- this.firstRunForm.enableCrashReporting = false;
273
- this.crashReportingDisclaimerVisible = false;
274
- }
275
- firstRunDataDbOptions = [];
276
- firstRunRealPath = '';
277
- /** Stato progress bar polled da `GET /api/Meta/FirstRunProgress`. */
278
- firstRunProgress = {
279
- active: false,
280
- phase: 'idle',
281
- current: 0,
282
- total: 0,
283
- percent: 0,
284
- message: '',
285
- elapsedMs: 0,
286
- finished: false,
287
- failed: false,
288
- error: null
289
- };
290
- firstRunProgressTimer = null;
291
- constructor(http, translate, messageService, injector) {
292
- this.http = http;
293
- this.translate = translate;
294
- this.messageService = messageService;
295
- this.injector = injector;
296
- }
297
- /** Disposer del delegate registrato sul GlobalHandler — vedi ngOnInit. */
298
- metadataDbExistsDelegateDisposer;
299
- /** Anti-loop: il prompt e' fire-and-forget; senza guardia, ricliccare
300
- * "Sì" ad un secondo trigger lancerebbe re-submit ricorsivi. */
301
- metadataDbExistsPromptInFlight = false;
302
- ngOnInit() {
303
- // Bundle traduzioni firstRun PRIMA che il template renda i pipe `| translate`.
304
- // Necessario perche' in firstRun il DB metadati e' vuoto e il fetch
305
- // `MetaService.GetTranslation` ritorna [] (gestito gia' a TranslationManager
306
- // livello, ma queste sono chiavi dedicate al wizard non presenti nel
307
- // FIRST_RUN_FALLBACK del manager).
308
- this.registerFirstRunTranslations();
309
- // Delegate sul GlobalHandler che intercetta il marker
310
- // `METADATA_DB_EXISTS_CONFIRM_REQUIRED` lanciato dal server da
311
- // `MetaService.configure_wuic` quando il metadata DB target esiste gia'.
312
- // Senza questo, l'introduzione del JsonExceptionFilter (skill exception-
313
- // handling) wrappa il marker in `args.message` e l'errore arriverebbe
314
- // all'utente come dialog generico `errors.server.unhandled` invece del
315
- // prompt di conferma drop+recreate.
316
- this.metadataDbExistsDelegateDisposer = GlobalHandler.registerDelegate((ctx) => {
317
- if (!ctx.message?.includes('METADATA_DB_EXISTS_CONFIRM_REQUIRED'))
318
- return false;
319
- // Async fire-and-forget: ritorno true (sopprime il dialog default),
320
- // poi prompt + retry submit.
321
- void this.handleMetadataDbExistsPrompt(ctx);
322
- return true;
323
- }, { priority: 100 });
324
- // Hook post-login per consumare `logs/rag-setup-pending.json`.
325
- // Probe FirstRunStatus + bootstrap form.
326
- void this.bootstrapFirstRun();
327
- }
328
- ngOnDestroy() {
329
- this.stopFirstRunProgressPolling();
330
- this.metadataDbExistsDelegateDisposer?.();
331
- }
332
- /** Mostra il prompt di conferma drop+recreate del metadata DB e, se
333
- * l'utente conferma, ri-lancia l'install con dropExistingMetadataDb=true.
334
- * Triggered dal delegate sul GlobalHandler quando il server emette il
335
- * marker `METADATA_DB_EXISTS_CONFIRM_REQUIRED:<dbName>`. */
336
- async handleMetadataDbExistsPrompt(ctx) {
337
- if (this.metadataDbExistsPromptInFlight)
338
- return;
339
- this.metadataDbExistsPromptInFlight = true;
340
- try {
341
- // dbName: prima cerca in args (envelope tipizzato moderno),
342
- // poi parsea il marker stringa `METADATA_DB_EXISTS_CONFIRM_REQUIRED:<dbName>`,
343
- // poi fallback al nome dal form.
344
- let dbName = (ctx.args && (ctx.args.dbName || ctx.args.metadataDbName)) || '';
345
- if (!dbName) {
346
- const m = /METADATA_DB_EXISTS_CONFIRM_REQUIRED:([^\s|"\\]+)/.exec(ctx.message || '');
347
- if (m)
348
- dbName = m[1];
349
- }
350
- if (!dbName)
351
- dbName = this.firstRunForm.metadataDbName || 'metadataDB';
352
- const promptResult = await WtoolboxService.promptDialog('Conferma ricreazione metadata DB', [
353
- {
354
- name: 'confirmDrop',
355
- caption: `Il database metadati '${dbName}' esiste già. Vuoi eliminarlo e ricrearlo?`,
356
- type: 'dictionary_radio',
357
- value: 'no',
358
- required: true,
359
- dictionaryData: [
360
- { label: 'No', value: 'no' },
361
- { label: 'Sì', value: 'yes' }
362
- ]
363
- }
364
- ], '620px', '400px');
365
- const rawConfirm = promptResult?.confirmDrop;
366
- let confirmDropValue = rawConfirm;
367
- if (rawConfirm && typeof rawConfirm === 'object' && 'value' in rawConfirm && typeof rawConfirm.value !== 'function') {
368
- confirmDropValue = rawConfirm.value;
369
- }
370
- else if (rawConfirm && typeof rawConfirm.getValue === 'function') {
371
- confirmDropValue = rawConfirm.getValue();
372
- }
373
- if (confirmDropValue && typeof confirmDropValue === 'object' && 'value' in confirmDropValue) {
374
- confirmDropValue = confirmDropValue.value;
375
- }
376
- const confirmDrop = String(confirmDropValue ?? 'no').toLowerCase() === 'yes';
377
- if (confirmDrop) {
378
- await this.submitFirstRunInstallInternal(true);
379
- }
380
- }
381
- finally {
382
- this.metadataDbExistsPromptInFlight = false;
383
- }
384
- }
385
- // ════════════════════════════════════════════════════════════════════════
386
- // STATIC HELPERS
387
- // ════════════════════════════════════════════════════════════════════════
388
- /**
389
- * Default della lingua admin basato su `navigator.language`. Mappa su uno
390
- * dei 5 tag IETF supportati (it/en/fr/es/de). Module-scope statico (chiamato
391
- * in field-initializer di firstRunForm).
392
- */
393
- static resolveDefaultAdminLanguage() {
394
- const raw = (typeof navigator !== 'undefined' ? (navigator.language || '') : '').toLowerCase();
395
- if (raw.startsWith('it'))
396
- return 'it-IT';
397
- if (raw.startsWith('en'))
398
- return 'en-US';
399
- if (raw.startsWith('fr'))
400
- return 'fr-FR';
401
- if (raw.startsWith('es'))
402
- return 'es-ES';
403
- if (raw.startsWith('de'))
404
- return 'de-DE';
405
- return 'it-IT';
406
- }
407
- // ════════════════════════════════════════════════════════════════════════
408
- // BOOTSTRAP / PROBE
409
- // ════════════════════════════════════════════════════════════════════════
410
- async bootstrapFirstRun() {
411
- this.firstRunLoading = true;
412
- this.firstRunError = '';
413
- const apiBase = WtoolboxService.appSettings?.api_url || '';
414
- const globalRoot = WtoolboxService.appSettings?.global_root_url || '';
415
- try {
416
- // firstRun VINCE sull'auth: su una prima installazione genuina il (nuovo) DB
417
- // metadati non ha utenti legittimi, quindi un cookie/sessione residua
418
- // (`Auth/Me` -> authenticated=true) NON deve dirottare l'app sul login saltando
419
- // il wizard. La decisione si basa SOLO su FirstRunStatus; l'eventuale stato di
420
- // auth e' irrilevante (quando firstRun=false il wizard si salta comunque sotto).
421
- const publicStatus = await firstValueFrom(this.http.get(`${apiBase}Meta/FirstRunStatus`, this.suppressGlobalErrorOpts()));
422
- const settings = this.parseDictionaryResponse(publicStatus);
423
- const firstRunSetting = settings['firstRun'] ?? settings['first-run'] ?? settings['firstrun'];
424
- const isFirstRun = this.toBoolean(firstRunSetting);
425
- this.firstRunRealPath = String(settings['projectDataFolder'] || settings['project-data-folder'] || '').trim();
426
- const currentDbms = String(settings['dbms'] || '').trim().toLowerCase();
427
- if (['mssql', 'mysql', 'oracle', 'postgres', 'postgresql'].includes(currentDbms)) {
428
- this.firstRunForm.dbms = currentDbms === 'postgresql' ? 'postgres' : currentDbms;
429
- }
430
- const tutorialFlag = settings['tutorialAvailable'] ?? settings['tutorialavailable'];
431
- this.firstRunTutorialAvailable = tutorialFlag === undefined ? true : this.toBoolean(tutorialFlag);
432
- if (!this.firstRunTutorialAvailable) {
433
- this.firstRunSetupModeOptions = [{ label: 'DB esistente', value: 'existing' }];
434
- this.firstRunForm.setupMode = 'existing';
435
- }
436
- if (!isFirstRun) {
437
- this.showFirstRunInstall = false;
438
- this.complete.emit();
439
- return;
440
- }
441
- // Filter DBMS dropdown to providers actually deployed.
442
- try {
443
- const availableResp = await firstValueFrom(this.http.get(`${apiBase}Meta/AvailableDbms`, this.suppressGlobalErrorOpts()));
444
- const availableList = availableResp?.dbms || [];
445
- if (Array.isArray(availableList) && availableList.length > 0) {
446
- const filtered = availableList.map(entry => {
447
- const id = String(entry?.id || '').trim().toLowerCase();
448
- const label = String(entry?.label || id).trim();
449
- const value = id === 'postgresql' ? 'postgres' : id;
450
- return { label, value };
451
- }).filter(opt => !!opt.value);
452
- if (filtered.length > 0) {
453
- this.firstRunDbmsOptions = filtered;
454
- if (!filtered.some(opt => opt.value === this.firstRunForm.dbms)) {
455
- this.firstRunForm.dbms = filtered[0].value;
456
- }
457
- }
458
- }
459
- }
460
- catch {
461
- // older backend without AvailableDbms — leave hardcoded list
462
- }
463
- this.showFirstRunInstall = true;
464
- await this.loadDefaultConnectionString(settings);
465
- }
466
- catch {
467
- // Legacy fallback for environments where Meta/FirstRunStatus is unavailable.
468
- try {
469
- const appSettingsResponse = await firstValueFrom(this.http.post(`${globalRoot}MetaService.getAppSettings`, {}, this.suppressGlobalErrorOpts()));
470
- const settings = this.parseDictionaryResponse(appSettingsResponse);
471
- const firstRunSetting = settings['firstRun'] ?? settings['first-run'] ?? settings['firstrun'];
472
- const isFirstRun = this.toBoolean(firstRunSetting);
473
- this.firstRunRealPath = String(settings['projectDataFolder'] || settings['project-data-folder'] || '').trim();
474
- const currentDbms = String(settings['dbms'] || '').trim().toLowerCase();
475
- if (['mssql', 'mysql', 'oracle', 'postgres', 'postgresql'].includes(currentDbms)) {
476
- this.firstRunForm.dbms = currentDbms === 'postgresql' ? 'postgres' : currentDbms;
477
- }
478
- if (!isFirstRun) {
479
- this.showFirstRunInstall = false;
480
- this.complete.emit();
481
- return;
482
- }
483
- this.showFirstRunInstall = true;
484
- await this.loadDefaultConnectionString(settings);
485
- }
486
- catch {
487
- this.showFirstRunInstall = false;
488
- this.complete.emit();
489
- }
490
- }
491
- finally {
492
- this.firstRunLoading = false;
493
- }
494
- }
495
- async loadDefaultConnectionString(settings) {
496
- const fallbackConn = String(settings['connection'] || '').trim();
497
- const fallbackDb = String(settings['DataDBName'] || settings['datadbname'] || '').trim();
498
- const fallbackMetaDb = String(settings['metaDataDBName'] || settings['metadataDbName'] || settings['MetaDataDBName']
499
- || this.firstRunForm.metadataDbName || 'MetadataCRM').trim();
500
- if (fallbackConn && fallbackDb && !/initial\s+catalog\s*=|database\s*=|dbq\s*=/i.test(fallbackConn)) {
501
- this.firstRunForm.dataConnectionString = `${fallbackConn};initial catalog=${fallbackDb}`;
502
- }
503
- else {
504
- this.firstRunForm.dataConnectionString = fallbackConn;
505
- }
506
- const parsed = this.parseConnectionString(this.firstRunForm.dataConnectionString || '');
507
- this.firstRunForm.dataDbName = parsed.databaseName || fallbackDb || '';
508
- this.firstRunForm.metadataDbName = fallbackMetaDb || this.firstRunForm.metadataDbName || 'MetadataCRM';
509
- this.firstRunForm.tutorialDataDbName = this.firstRunForm.dataDbName || 'WideWorldImporters';
510
- this.firstRunForm.tutorialMetadataDbName = String(this.firstRunForm.metadataDbName || 'MetadataCRM').trim() || 'MetadataCRM';
511
- }
512
- // ════════════════════════════════════════════════════════════════════════
513
- // FORM EVENT HANDLERS
514
- // ════════════════════════════════════════════════════════════════════════
515
- onFirstRunConnectionChanged(value) {
516
- this.firstRunForm.dataConnectionString = value;
517
- const parsed = this.parseConnectionString(value || '');
518
- this.firstRunForm.dataDbName = parsed.databaseName || '';
519
- this.firstRunDataDbOptions = [];
520
- this.firstRunConnectionValid = false;
521
- }
522
- onFirstRunSetupModeChanged(value) {
523
- const mode = String(value || '').trim().toLowerCase() === 'tutorial' ? 'tutorial' : 'existing';
524
- this.firstRunForm.setupMode = mode;
525
- if (mode === 'tutorial') {
526
- this.firstRunForm.tutorialDataDbName = String(this.firstRunForm.tutorialDataDbName || 'WideWorldImporters').trim();
527
- this.firstRunForm.tutorialMetadataDbName = String(this.firstRunForm.tutorialMetadataDbName || this.firstRunForm.metadataDbName || 'MetadataCRM').trim();
528
- this.firstRunForm.dataDbName = this.firstRunForm.tutorialDataDbName;
529
- this.firstRunForm.metadataDbName = this.firstRunForm.tutorialMetadataDbName;
530
- }
531
- }
532
- onFirstRunDbmsChanged(value) {
533
- const normalizedDbms = this.normalizeDbms(value);
534
- this.firstRunForm.dbms = normalizedDbms === 'postgresql' ? 'postgres' : normalizedDbms;
535
- const parsed = this.parseConnectionString(this.firstRunForm.dataConnectionString || '');
536
- const preferredDatabaseName = String(this.firstRunForm.dataDbName || parsed.databaseName || '').trim();
537
- this.firstRunForm.dataConnectionString = this.buildProviderConnectionString(normalizedDbms, {
538
- dataSource: parsed.dataSource,
539
- port: parsed.port,
540
- databaseName: preferredDatabaseName,
541
- userId: parsed.userId,
542
- password: parsed.password,
543
- integratedSecurity: parsed.integratedSecurity && normalizedDbms === 'mssql'
544
- });
545
- this.firstRunForm.dataDbName = preferredDatabaseName;
546
- this.firstRunDataDbOptions = [];
547
- this.firstRunConnectionValid = false;
548
- }
549
- // ════════════════════════════════════════════════════════════════════════
550
- // TEST CONNECTION
551
- // ════════════════════════════════════════════════════════════════════════
552
- async testFirstRunConnectionAndLoadDatabases() {
553
- this.firstRunError = '';
554
- this.firstRunConnectionValid = false;
555
- this.firstRunDataDbOptions = [];
556
- const dbms = this.normalizeDbms(this.firstRunForm.dbms);
557
- const rawConnectionString = String(this.firstRunForm.dataConnectionString || '').trim();
558
- if (!rawConnectionString) {
559
- this.firstRunError = 'Inserisci la DataSQLConnection prima del test.';
560
- return;
561
- }
562
- const baseConnection = this.buildConnectionWithoutDatabase(rawConnectionString, dbms);
563
- if (!baseConnection) {
564
- this.firstRunError = 'DataSQLConnection non valida: impossibile derivare la connessione base.';
565
- return;
566
- }
567
- this.firstRunConnectionTesting = true;
568
- this.firstRunDbLoading = true;
569
- try {
570
- const databases = await this.fetchDatabaseNames(dbms, baseConnection);
571
- this.firstRunDataDbOptions = databases.map(name => ({ label: name, value: name }));
572
- this.firstRunConnectionValid = true;
573
- if (!this.firstRunDataDbOptions.some(x => x.value === this.firstRunForm.dataDbName)) {
574
- this.firstRunForm.dataDbName = this.firstRunDataDbOptions[0]?.value || this.firstRunForm.dataDbName || '';
575
- }
576
- this.messageService.add({
577
- severity: 'success',
578
- summary: 'Connessione valida',
579
- detail: `Connessione riuscita. Trovati ${this.firstRunDataDbOptions.length} database.`
580
- });
581
- }
582
- catch (error) {
583
- const detail = this.extractErrorMessage(error);
584
- const canAutoFixCert = dbms === 'mssql' && this.isSqlServerCertificateError(detail);
585
- if (canAutoFixCert) {
586
- const fixedDataConnectionString = this.applySqlServerCertificateFix(rawConnectionString);
587
- const fixedBaseConnection = this.buildConnectionWithoutDatabase(fixedDataConnectionString, dbms);
588
- if (fixedBaseConnection) {
589
- try {
590
- const databases = await this.fetchDatabaseNames(dbms, fixedBaseConnection);
591
- this.firstRunForm.dataConnectionString = fixedDataConnectionString;
592
- this.firstRunDataDbOptions = databases.map(name => ({ label: name, value: name }));
593
- this.firstRunConnectionValid = true;
594
- if (!this.firstRunDataDbOptions.some(x => x.value === this.firstRunForm.dataDbName)) {
595
- this.firstRunForm.dataDbName = this.firstRunDataDbOptions[0]?.value || this.firstRunForm.dataDbName || '';
596
- }
597
- this.messageService.add({
598
- severity: 'success',
599
- summary: 'Connessione corretta automaticamente',
600
- detail: 'Applicato fix certificati SQL Server (Encrypt=False;TrustServerCertificate=True).'
601
- });
602
- return;
603
- }
604
- catch {
605
- // fall through to original error path
606
- }
607
- }
608
- }
609
- this.firstRunConnectionValid = false;
610
- this.firstRunDataDbOptions = [];
611
- this.firstRunError = detail;
612
- // Errore mostrato SOLO inline nel form (firstRunError): niente toast ne
613
- // dialog globale (le chiamate usano suppressGlobalErrorOpts()).
614
- }
615
- finally {
616
- this.firstRunDbLoading = false;
617
- this.firstRunConnectionTesting = false;
618
- }
619
- }
620
- /**
621
- * Opzioni HTTP che sopprimono il dialog/handler globale (`GlobalHandler`)
622
- * per le chiamate del first-run wizard. Gli errori dell'installer vengono
623
- * mostrati SOLO inline nel form (`firstRunError`) — niente popup dialog
624
- * (interceptor) ne toast. Vedi WUIC_SUPPRESS_GLOBAL_ERROR.
625
- */
626
- suppressGlobalErrorOpts() {
627
- return { context: new HttpContext().set(WUIC_SUPPRESS_GLOBAL_ERROR, true) };
628
- }
629
- async fetchDatabaseNames(dbms, baseConnection) {
630
- const globalRoot = WtoolboxService.appSettings?.global_root_url || '';
631
- const endpoint = `${globalRoot}MetaService.get_database_names`;
632
- const payload = await firstValueFrom(this.http.post(endpoint, {
633
- rdbDBMS: dbms,
634
- connectionString: baseConnection
635
- }, this.suppressGlobalErrorOpts()));
636
- return this.parseStringArrayResponse(payload)
637
- .map(x => String(x || '').trim())
638
- .filter(x => !!x);
639
- }
640
- // ════════════════════════════════════════════════════════════════════════
641
- // SUBMIT INSTALL
642
- // ════════════════════════════════════════════════════════════════════════
643
- async submitFirstRunInstall() {
644
- await this.submitFirstRunInstallInternal(false);
645
- }
646
- async submitFirstRunInstallInternal(confirmDropExistingMetadataDb) {
647
- this.firstRunError = '';
648
- const dbms = this.normalizeDbms(this.firstRunForm.dbms);
649
- const conn = this.parseConnectionString(this.firstRunForm.dataConnectionString || '');
650
- const isTutorialMode = this.firstRunForm.setupMode === 'tutorial';
651
- const selectedDataDbName = String(isTutorialMode
652
- ? (this.firstRunForm.tutorialDataDbName || this.firstRunForm.dataDbName || conn.databaseName || 'WideWorldImporters')
653
- : (this.firstRunForm.dataDbName || conn.databaseName || '')).trim();
654
- const selectedMetadataDbName = String(isTutorialMode
655
- ? (this.firstRunForm.tutorialMetadataDbName || this.firstRunForm.metadataDbName || 'MetadataCRM')
656
- : (this.firstRunForm.metadataDbName || 'metadataDB')).trim();
657
- if (!conn.integratedSecurity && (!conn.userId || !conn.password)) {
658
- this.firstRunError = 'Stringa DataSQLConnection non valida: servono almeno user e password (oppure Integrated Security=true su MSSQL).';
659
- return;
660
- }
661
- if (conn.integratedSecurity && dbms !== 'mssql') {
662
- this.firstRunError = 'Integrated Security è supportata solo su Microsoft SQL Server: per ' + dbms + ' servono user e password.';
663
- return;
664
- }
665
- if (!conn.dataSource) {
666
- this.firstRunError = 'Stringa DataSQLConnection non valida: manca data source/server/host.';
667
- return;
668
- }
669
- if (!selectedDataDbName) {
670
- this.firstRunError = 'Seleziona un database dati dalla lista.';
671
- return;
672
- }
673
- if (!this.firstRunConnectionValid) {
674
- this.firstRunError = 'Testa prima la connessione dati per abilitare il database di destinazione.';
675
- return;
676
- }
677
- const adminUsername = String(this.firstRunForm.adminUsername || '').trim();
678
- const adminPassword = String(this.firstRunForm.adminPassword || '');
679
- if (!adminUsername) {
680
- this.firstRunError = 'Inserisci un username per l\'utente admin iniziale.';
681
- return;
682
- }
683
- if (!adminPassword || adminPassword.length < 4) {
684
- this.firstRunError = 'Inserisci una password (almeno 4 caratteri) per l\'utente admin iniziale.';
685
- return;
686
- }
687
- const dataBaseConnection = this.buildConnectionWithoutDatabase(this.firstRunForm.dataConnectionString || '', dbms);
688
- if (!dataBaseConnection) {
689
- this.firstRunError = 'Stringa DataSQLConnection non valida: impossibile derivare la connessione base senza database.';
690
- return;
691
- }
692
- this.firstRunInstalling = true;
693
- this.startFirstRunProgressPolling();
694
- try {
695
- const globalRoot = WtoolboxService.appSettings?.global_root_url || '';
696
- const endpoint = `${globalRoot}MetaService.configure_wuic`;
697
- const realPath = (this.firstRunRealPath || '').trim();
698
- await firstValueFrom(this.http.post(endpoint, {
699
- license_email: 'first-run@local',
700
- rdbDBMS: dbms,
701
- conn_datasource: conn.dataSource,
702
- port: conn.port,
703
- conn_database_name: selectedDataDbName,
704
- conn_user_id: conn.userId,
705
- conn_password: conn.password,
706
- conn_integrated_security: conn.integratedSecurity ? 'true' : 'false',
707
- conn_data_base_connection_string: dataBaseConnection,
708
- preScaffoldDB: true,
709
- rdbDBMSMeta: dbms,
710
- conn_datasource_meta: conn.dataSource,
711
- portMeta: conn.port,
712
- conn_metadata_db_name: selectedMetadataDbName,
713
- conn_user_id_meta: conn.userId,
714
- conn_password_meta: conn.password,
715
- conn_integrated_security_meta: conn.integratedSecurity ? 'true' : 'false',
716
- psqlPath: '',
717
- theme: 'default',
718
- site_url: globalThis.location.origin,
719
- email_host: '',
720
- email_port: '',
721
- email_username: '',
722
- email_password: '',
723
- realPath,
724
- confirmDropExistingMetadataDb: confirmDropExistingMetadataDb ? 'true' : 'false',
725
- enableTutorialDbProvisioning: (isTutorialMode && this.firstRunForm.createTutorialIfMissing) ? 'true' : 'false',
726
- tutorialDataDbName: isTutorialMode ? selectedDataDbName : '',
727
- tutorialMetadataDbName: isTutorialMode ? selectedMetadataDbName : '',
728
- scaffoldTutorialDatabase: isTutorialMode ? 'true' : 'false',
729
- scaffoldExistingDatabase: (!isTutorialMode && this.firstRunForm.scaffoldExistingDatabase) ? 'true' : 'false',
730
- adminUsername,
731
- adminPassword,
732
- adminLanguage: this.firstRunForm.adminLanguage,
733
- anthropicApiKey: (this.firstRunForm.anthropicApiKey || '').trim(),
734
- // Crash reporting opt-in (skill crash-reporting Commit 8). Quando true,
735
- // configure_wuic deve scrivere `CrashReporting.Enabled=true`,
736
- // `DisclaimerAcceptedVersion="1.0"` e `DisclaimerAcceptedAt=<now>` in
737
- // appsettings.json. Quando false (default), niente da fare — la
738
- // sezione resta a Enabled=false e l'utente potra' attivarla in
739
- // seguito dall'appsettings-editor (Commit 9).
740
- enableCrashReporting: this.firstRunForm.enableCrashReporting ? 'true' : 'false'
741
- }, this.suppressGlobalErrorOpts()));
742
- // STOP polling FirstRunProgress NOW — `configure_wuic` ha appena riscritto
743
- // `appsettings.json` (firstRun=false + nuove ConnectionStrings). IIS rileva
744
- // il file change via FileSystemWatcher e recyclia l'app pool. Se il polling
745
- // continua a girare durante il recycle riceve `503 Application Shutting Down`
746
- // → GlobalHandler `error.http_503.user_message` → dialog modale all'utente
747
- // ("Http failure response: 503") che oscura il flow di success.
748
- // Spostato qui PRIMA di clearClientState + waitForBackendReady (era nel
749
- // `finally` block, troppo tardi: i ~30s di waitForBackendReady erano
750
- // esattamente la finestra di shutdown→restart pool).
751
- this.stopFirstRunProgressPolling();
752
- await this.clearClientStateForFirstRunLogin();
753
- // I servizi notifiche (WS `/ws/notifications` + watcher) sono registrati SEMPRE
754
- // dal backend (anche durante il first-run) e partono in polling resiliente che
755
- // rilegge la connection FRESH -> nessun restart necessario qui: appena
756
- // configure_wuic scrive la connection reale + crea lo schema, il push si aggancia.
757
- // Vedi Startup.cs (registrazione sempre attiva) e NotificationSqlDependencyWatcher.
758
- await this.waitForBackendReady();
759
- const scaffoldRoute = '/scaffolding/dialog/1556';
760
- const loginRedirectUrl = `/?redirect=${encodeURIComponent(scaffoldRoute)}&firstRunLogin=1`;
761
- this.complete.emit();
762
- globalThis.location.assign(loginRedirectUrl);
763
- return;
764
- }
765
- catch (error) {
766
- this.firstRunError = this.extractErrorMessage(error);
767
- // Branch METADATA_DB_EXISTS: il server chiede conferma di drop+recreate del
768
- // metadata DB esistente. Le chiamate del wizard sopprimono il GlobalHandler
769
- // (errore solo inline), quindi il delegate registrato in ngOnInit NON scatta
770
- // piu' -> mostriamo il prompt di conferma DIRETTAMENTE qui. La guard
771
- // `metadataDbExistsPromptInFlight` dentro handleMetadataDbExistsPrompt evita
772
- // doppi prompt se il delegate dovesse comunque scattare.
773
- if (!confirmDropExistingMetadataDb && this.firstRunError.includes('METADATA_DB_EXISTS_CONFIRM_REQUIRED')) {
774
- void this.handleMetadataDbExistsPrompt({ message: this.firstRunError });
775
- return;
776
- }
777
- // Errore mostrato SOLO inline nel form (firstRunError): niente toast ne
778
- // dialog globale (configure_wuic usa suppressGlobalErrorOpts()).
779
- }
780
- finally {
781
- this.firstRunInstalling = false;
782
- this.stopFirstRunProgressPolling();
783
- }
784
- }
785
- // ════════════════════════════════════════════════════════════════════════
786
- // PROGRESS POLLING
787
- // ════════════════════════════════════════════════════════════════════════
788
- startFirstRunProgressPolling() {
789
- this.stopFirstRunProgressPolling();
790
- this.firstRunProgress = {
791
- active: false,
792
- phase: 'starting',
793
- current: 0,
794
- total: 0,
795
- percent: 0,
796
- message: 'Inizializzazione...',
797
- elapsedMs: 0,
798
- finished: false,
799
- failed: false,
800
- error: null
801
- };
802
- const apiBase = WtoolboxService.appSettings?.api_url || '';
803
- this.firstRunProgressTimer = setInterval(async () => {
804
- try {
805
- const resp = await firstValueFrom(this.http.get(`${apiBase}Meta/FirstRunProgress`, this.suppressGlobalErrorOpts()));
806
- if (resp && typeof resp === 'object') {
807
- this.firstRunProgress = {
808
- active: !!resp.active,
809
- phase: String(resp.phase || 'idle'),
810
- current: Number(resp.current || 0),
811
- total: Number(resp.total || 0),
812
- percent: Number(resp.percent || 0),
813
- message: String(resp.message || ''),
814
- elapsedMs: Number(resp.elapsedMs || 0),
815
- finished: !!resp.finished,
816
- failed: !!resp.failed,
817
- error: resp.error || null
818
- };
819
- }
820
- }
821
- catch {
822
- // transient — keep ticking
823
- }
824
- }, 500);
825
- }
826
- stopFirstRunProgressPolling() {
827
- if (this.firstRunProgressTimer) {
828
- clearInterval(this.firstRunProgressTimer);
829
- this.firstRunProgressTimer = null;
830
- }
831
- }
832
- // ════════════════════════════════════════════════════════════════════════
833
- // POST-INSTALL CLEANUP + WAIT
834
- // ════════════════════════════════════════════════════════════════════════
835
- async clearClientStateForFirstRunLogin() {
836
- try {
837
- localStorage.clear();
838
- }
839
- catch { /* ignore */ }
840
- try {
841
- sessionStorage.clear();
842
- }
843
- catch { /* ignore */ }
844
- await this.deleteIndexedDbByName('MetaDB');
845
- await this.deleteIndexedDbByName('WuicClientSideCrudDB');
846
- }
847
- async waitForBackendReady() {
848
- const apiBase = WtoolboxService.appSettings?.api_url || '';
849
- const sentinelUrl = `${apiBase}Meta/FirstRunStatus`;
850
- const startedAt = Date.now();
851
- const timeoutMs = 30_000;
852
- const intervalMs = 500;
853
- await new Promise((resolve) => setTimeout(resolve, 500));
854
- while (Date.now() - startedAt < timeoutMs) {
855
- try {
856
- await firstValueFrom(this.http.get(sentinelUrl, this.suppressGlobalErrorOpts()));
857
- return;
858
- }
859
- catch {
860
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
861
- }
862
- }
863
- }
864
- deleteIndexedDbByName(dbName) {
865
- const normalized = String(dbName || '').trim();
866
- if (!normalized || typeof indexedDB === 'undefined') {
867
- return Promise.resolve();
868
- }
869
- return new Promise((resolve) => {
870
- try {
871
- const request = indexedDB.deleteDatabase(normalized);
872
- request.onsuccess = () => resolve();
873
- request.onerror = () => resolve();
874
- request.onblocked = () => resolve();
875
- }
876
- catch {
877
- resolve();
878
- }
879
- });
880
- }
881
- // ════════════════════════════════════════════════════════════════════════
882
- // CONNECTION-STRING UTILITIES
883
- // ════════════════════════════════════════════════════════════════════════
884
- parseConnectionString(connectionString) {
885
- const map = new Map();
886
- (connectionString || '')
887
- .split(';')
888
- .map(part => part.trim())
889
- .filter(part => !!part && part.includes('='))
890
- .forEach(part => {
891
- const idx = part.indexOf('=');
892
- const key = part.substring(0, idx).trim().toLowerCase();
893
- const value = part.substring(idx + 1).trim();
894
- map.set(key, value);
895
- });
896
- const dataSourceRaw = this.getConnValue(map, ['data source', 'server', 'host', 'datasource', 'addr', 'address']);
897
- const dbName = this.getConnValue(map, ['initial catalog', 'database', 'dbq', 'service name']);
898
- const userId = this.getConnValue(map, ['user id', 'uid', 'user', 'username']);
899
- const password = this.getConnValue(map, ['password', 'pwd']);
900
- const explicitPort = this.getConnValue(map, ['port']);
901
- const integratedSecurityRaw = this.getConnValue(map, ['integrated security', 'trusted_connection']).toLowerCase();
902
- const integratedSecurity = integratedSecurityRaw === 'true' || integratedSecurityRaw === 'yes' || integratedSecurityRaw === 'sspi';
903
- let dataSource = dataSourceRaw;
904
- let port = explicitPort;
905
- if (!port && dataSourceRaw.includes(',')) {
906
- const chunks = dataSourceRaw.split(',');
907
- dataSource = (chunks[0] || '').trim();
908
- port = (chunks[1] || '').trim();
909
- }
910
- return { dataSource, port, databaseName: dbName, userId, password, integratedSecurity };
911
- }
912
- buildConnectionWithoutDatabase(connectionString, normalizedDbms) {
913
- const dbms = this.normalizeDbms(normalizedDbms);
914
- try {
915
- const parts = (connectionString || '')
916
- .split(';')
917
- .map(part => part.trim())
918
- .filter(part => !!part && part.includes('='));
919
- const keep = [];
920
- for (const part of parts) {
921
- const idx = part.indexOf('=');
922
- const key = part.substring(0, idx).trim().toLowerCase();
923
- if (['initial catalog', 'database', 'dbq', 'service name'].includes(key))
924
- continue;
925
- if (dbms === 'mssql' && (key === 'attachdbfilename' || key === 'initial file name'))
926
- continue;
927
- keep.push(part);
928
- }
929
- return keep.join(';');
930
- }
931
- catch {
932
- return '';
933
- }
934
- }
935
- buildProviderConnectionString(normalizedDbms, conn) {
936
- const dbms = this.normalizeDbms(normalizedDbms);
937
- const dataSource = String(conn.dataSource || '').trim();
938
- const port = String(conn.port || '').trim();
939
- const databaseName = String(conn.databaseName || '').trim();
940
- const userId = String(conn.userId || '').trim();
941
- const password = String(conn.password || '').trim();
942
- const integratedSecurity = !!conn.integratedSecurity;
943
- const parts = [];
944
- if (dbms === 'mssql') {
945
- let serverValue = dataSource;
946
- if (serverValue && port && !serverValue.includes(','))
947
- serverValue = `${serverValue},${port}`;
948
- if (serverValue)
949
- parts.push(`data source=${serverValue}`);
950
- if (integratedSecurity) {
951
- parts.push('Integrated Security=True');
952
- }
953
- else {
954
- parts.push('integrated security=False');
955
- if (userId)
956
- parts.push(`User ID=${userId}`);
957
- if (password)
958
- parts.push(`Password=${password}`);
959
- parts.push('Persist Security Info=true');
960
- }
961
- parts.push('Encrypt=False');
962
- parts.push('TrustServerCertificate=True');
963
- if (databaseName)
964
- parts.push(`initial catalog=${databaseName}`);
965
- return parts.join(';');
966
- }
967
- if (dbms === 'mysql' || dbms === 'postgresql') {
968
- if (dataSource)
969
- parts.push(`server=${dataSource}`);
970
- if (userId)
971
- parts.push(`user id=${userId}`);
972
- if (password)
973
- parts.push(`password=${password}`);
974
- parts.push('persist security info=True');
975
- if (databaseName)
976
- parts.push(`database=${databaseName}`);
977
- if (port)
978
- parts.push(`Port=${port}`);
979
- return parts.join(';');
980
- }
981
- if (dbms === 'oracle') {
982
- if (dataSource)
983
- parts.push(`data source=${dataSource}`);
984
- if (userId)
985
- parts.push(`User ID=${userId}`);
986
- if (password)
987
- parts.push(`Password=${password}`);
988
- if (databaseName)
989
- parts.push(`service name=${databaseName}`);
990
- if (port)
991
- parts.push(`Port=${port}`);
992
- return parts.join(';');
993
- }
994
- return String(this.firstRunForm.dataConnectionString || '').trim();
995
- }
996
- isSqlServerCertificateError(detail) {
997
- const message = String(detail || '').toLowerCase();
998
- return message.includes('ssl provider')
999
- || (message.includes('certificate') && message.includes('chain'))
1000
- || (message.includes('certific') && message.includes('catena'))
1001
- || message.includes('trustservercertificate');
1002
- }
1003
- applySqlServerCertificateFix(connectionString) {
1004
- const segments = (connectionString || '').split(';').map(p => p.trim()).filter(p => !!p);
1005
- const filtered = [];
1006
- for (const part of segments) {
1007
- const idx = part.indexOf('=');
1008
- if (idx <= 0) {
1009
- filtered.push(part);
1010
- continue;
1011
- }
1012
- const key = part.substring(0, idx).trim().toLowerCase();
1013
- if (key === 'encrypt' || key === 'trustservercertificate')
1014
- continue;
1015
- filtered.push(part);
1016
- }
1017
- filtered.push('Encrypt=False');
1018
- filtered.push('TrustServerCertificate=True');
1019
- return filtered.join(';');
1020
- }
1021
- getConnValue(map, keys) {
1022
- for (const k of keys) {
1023
- const value = map.get(k.toLowerCase());
1024
- if (value !== undefined)
1025
- return value;
1026
- }
1027
- return '';
1028
- }
1029
- // ════════════════════════════════════════════════════════════════════════
1030
- // GENERIC PARSE / NORMALIZE
1031
- // ════════════════════════════════════════════════════════════════════════
1032
- parseDictionaryResponse(payload) {
1033
- const raw = this.unwrapPayload(payload);
1034
- if (raw && typeof raw === 'object' && !Array.isArray(raw))
1035
- return raw;
1036
- if (typeof raw === 'string') {
1037
- try {
1038
- const parsed = JSON.parse(raw);
1039
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
1040
- return parsed;
1041
- }
1042
- catch {
1043
- return {};
1044
- }
1045
- }
1046
- return {};
1047
- }
1048
- parseStringArrayResponse(payload) {
1049
- const raw = this.unwrapPayload(payload);
1050
- if (Array.isArray(raw))
1051
- return raw.map(x => String(x ?? ''));
1052
- if (typeof raw === 'string') {
1053
- try {
1054
- const parsed = JSON.parse(raw);
1055
- return Array.isArray(parsed) ? parsed.map(x => String(x ?? '')) : [];
1056
- }
1057
- catch {
1058
- return [];
1059
- }
1060
- }
1061
- return [];
1062
- }
1063
- unwrapPayload(payload) {
1064
- if (payload && typeof payload === 'object') {
1065
- if ('d' in payload)
1066
- return payload.d;
1067
- if ('value' in payload)
1068
- return payload.value;
1069
- }
1070
- return payload;
1071
- }
1072
- normalizeDbms(value) {
1073
- const v = String(value || '').trim().toLowerCase();
1074
- if (v === 'postgres')
1075
- return 'postgresql';
1076
- return v;
1077
- }
1078
- toBoolean(value) {
1079
- const normalized = String(value ?? '').trim().toLowerCase();
1080
- return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'si';
1081
- }
1082
- extractErrorMessage(error) {
1083
- const fromBackend = error?.error;
1084
- if (typeof fromBackend === 'string' && fromBackend.trim())
1085
- return this.cleanServerErrorMessage(fromBackend);
1086
- if (fromBackend && typeof fromBackend === 'object') {
1087
- const parts = [];
1088
- const detail = fromBackend.message || fromBackend.error || fromBackend.title;
1089
- if (detail)
1090
- parts.push(String(detail));
1091
- const rootMessage = fromBackend.rootMessage;
1092
- if (rootMessage && (!detail || !String(detail).includes(String(rootMessage)))) {
1093
- parts.push(String(rootMessage));
1094
- }
1095
- // ── New typed-exception envelope (skill exception-handling) ────────
1096
- // Server's JsonExceptionFilter wraps unhandled exceptions in:
1097
- // { ok:false, errorCode, fallbackMessage, args: { type, message, ... }, traceId }
1098
- // Senza questi pickup, marker grezzi tipo `METADATA_DB_EXISTS_CONFIRM_REQUIRED:`
1099
- // (lanciati come `throw new InvalidOperationException(marker)` lato server)
1100
- // restano sepolti in `args.message` → il flow `if (firstRunError.includes(marker))`
1101
- // del wizard non trova mai il match e perde il prompt di overwrite.
1102
- const argsMsg = fromBackend.args?.message;
1103
- if (argsMsg && !parts.some(p => p.includes(String(argsMsg)))) {
1104
- parts.push(String(argsMsg));
1105
- }
1106
- const fallbackMsg = fromBackend.fallbackMessage;
1107
- if (fallbackMsg && !parts.some(p => p.includes(String(fallbackMsg)))) {
1108
- parts.push(String(fallbackMsg));
1109
- }
1110
- if (parts.length > 0)
1111
- return this.cleanServerErrorMessage(parts.join(' | '));
1112
- }
1113
- return this.cleanServerErrorMessage(String(error?.message || 'Errore sconosciuto durante il first-run setup.'));
1114
- }
1115
- /**
1116
- * Ripulisce il wrapper grezzo dell'AsmxProxy dispatcher mostrando SOLO il
1117
- * messaggio dell'eccezione interna piu' profonda. Il backend, quando una
1118
- * invocazione `MetaService.*` fallisce, rilancia un messaggio verboso del
1119
- * tipo:
1120
- * AsmxProxy invocation failed. Requested='MetaService.get_database_names',
1121
- * Current='ok', ... Methods='...', Parameters='...'.
1122
- * Inner='System.InvalidOperationException: Errore test connessione/lista
1123
- * database: Errore dell'istanza.'
1124
- * Di tutto questo l'utente vuole solo la parte utile -> qui estraiamo il
1125
- * contenuto dell'ultimo `Inner='...'` e togliamo il prefisso del tipo
1126
- * eccezione .NET (es. `System.InvalidOperationException: `). Se il messaggio
1127
- * non e' un dump AsmxProxy lo lasciamo invariato. Preserva eventuali marker
1128
- * (es. METADATA_DB_EXISTS_CONFIRM_REQUIRED) che cosi' diventano anzi piu'
1129
- * facili da matchare (non piu' sepolti nel dump).
1130
- */
1131
- cleanServerErrorMessage(raw) {
1132
- const original = String(raw || '').trim();
1133
- let msg = original;
1134
- if (!msg)
1135
- return msg;
1136
- // Scarta progressivamente i wrapper `Inner='...'` fino all'eccezione piu'
1137
- // interna (guard anti-loop). Greedy: l'apostrofo dentro al messaggio (es.
1138
- // "dell'istanza") resta incluso, la cattura arriva fino all'ultima quote.
1139
- let m;
1140
- let guard = 0;
1141
- while ((m = msg.match(/Inner='([\s\S]*)'/)) !== null && guard++ < 6) {
1142
- msg = m[1].trim();
1143
- }
1144
- // Togli il prefisso fully-qualified del tipo eccezione .NET
1145
- // (es. "System.InvalidOperationException: " / "System.Data.SqlClient.SqlException: ").
1146
- msg = msg.replace(/^(?:[A-Za-z_][\w]*\.)+[A-Za-z_]\w*(?:Exception|Error)\s*:\s*/, '').trim();
1147
- return msg || original;
1148
- }
1149
- // ════════════════════════════════════════════════════════════════════════
1150
- // I18N BUNDLES (firstrun.* keys)
1151
- // ════════════════════════════════════════════════════════════════════════
1152
- registerFirstRunTranslations() {
1153
- const bundles = {
1154
- 'it-IT': { firstrun: { rag: {
1155
- installTooltip: 'PREREQUISITO: Python 3.12 installato e sul PATH del worker IIS.\n\n' +
1156
- 'Se la checkbox è disabilitata: Python non è stato rilevato.\n' +
1157
- 'Per installarlo (ONE-CLICK):\n' +
1158
- '• Vai nella cartella del deploy (dove c\'è WuicCore.dll)\n' +
1159
- '• Right-click su rag-setup.ps1 → "Esegui come amministratore"\n' +
1160
- '• Lo script si auto-eleva via UAC, estrae Python 3.12 embeddable (niente MSI, bypass GPO DisableMSI), installa pip, aggiorna PATH, fa iisreset\n' +
1161
- '• Ricarica il form (Ctrl+F5) → la checkbox si abilita\n\n' +
1162
- 'Python >= 3.13 accettato ma non testato (wheels torch più stabili su 3.12).\n\n' +
1163
- 'Quando Python è presente, l\'install RAG parte in BACKGROUND appena finita la creazione DB. ' +
1164
- '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' +
1165
- 'Tempi tipici (rete ~50-100 Mbps):\n' +
1166
- '• First run CPU: 4-8 minuti (download torch ~200 MB)\n' +
1167
- '• First run GPU CUDA: 18-32 minuti (download torch ~2.5 GB)\n' +
1168
- '• Rerun con venv già presente: <20 sec\n\n' +
1169
- 'Cold start del server dopo setup: ~13 sec.',
1170
- cudaTooltip: 'Download: ~2.5 GB (vs ~200 MB versione CPU)\n' +
1171
- 'Tempo aggiuntivo: +15-25 min su rete standard\n' +
1172
- 'Performance runtime: ~10-20× più veloce su embedding search + rerank.\n\n' +
1173
- 'Richiede: GPU NVIDIA + driver CUDA 12.x installati lato sistema.',
1174
- apiKeyTooltip: 'Senza chiave: modalità retrieval-only (ricerca snippet dal codebase).\n' +
1175
- 'Con chiave: chat LLM con sintesi via Claude.\n\n' +
1176
- 'La chiave viene salvata come env var del processo server (non nel DB).\n' +
1177
- 'Puoi impostarla anche post-install rilanciando: pwsh rag-setup.ps1 -Start -AnthropicApiKey "sk-ant-...".'
1178
- } } },
1179
- 'en-US': { firstrun: { rag: {
1180
- installTooltip: 'PREREQUISITE: Python 3.12 installed and on the IIS worker PATH.\n\n' +
1181
- 'If this checkbox is disabled: Python was not detected.\n' +
1182
- 'To install (ONE-CLICK):\n' +
1183
- '• Go to the deploy folder (where WuicCore.dll is)\n' +
1184
- '• Right-click on rag-setup.ps1 → "Run as administrator"\n' +
1185
- '• The script self-elevates via UAC, extracts Python 3.12 embeddable (no MSI, bypasses GPO DisableMSI), installs pip, updates PATH, runs iisreset\n' +
1186
- '• Reload the form (Ctrl+F5) → the checkbox enables automatically\n\n' +
1187
- 'Python >= 3.13 is accepted but not tested (torch wheels are most stable on 3.12).\n\n' +
1188
- 'When Python is present, RAG install runs in BACKGROUND as soon as DB setup completes. ' +
1189
- 'You can log in right away and use the rest of the app; you will receive notifications in the top bell ' +
1190
- 'for 4 steps + 1 final (clickable to open the chatbot).\n\n' +
1191
- 'Typical timings (network ~50-100 Mbps):\n' +
1192
- '• First run CPU: 4-8 minutes (torch download ~200 MB)\n' +
1193
- '• First run GPU CUDA: 18-32 minutes (torch download ~2.5 GB)\n' +
1194
- '• Rerun with existing venv: <20 sec\n\n' +
1195
- 'Server cold start after setup: ~13 sec.',
1196
- cudaTooltip: 'Download: ~2.5 GB (vs ~200 MB CPU version)\n' +
1197
- 'Extra time: +15-25 min on standard network\n' +
1198
- 'Runtime performance: ~10-20× faster on embedding search + rerank.\n\n' +
1199
- 'Requires: NVIDIA GPU + CUDA 12.x drivers installed system-wide.',
1200
- apiKeyTooltip: 'Without key: retrieval-only mode (codebase snippet search).\n' +
1201
- 'With key: LLM chat with synthesis via Claude.\n\n' +
1202
- 'The key is stored as a server process env var (not in DB).\n' +
1203
- 'Can also be set post-install by re-running: pwsh rag-setup.ps1 -Start -AnthropicApiKey "sk-ant-...".'
1204
- } } },
1205
- 'fr-FR': { firstrun: { rag: {
1206
- installTooltip: 'PRÉREQUIS : Python 3.12 installé et sur le PATH du worker IIS.\n\n' +
1207
- 'Si la case est désactivée : Python n\'a pas été détecté.\n' +
1208
- 'Pour l\'installer (ONE-CLICK) :\n' +
1209
- '• Allez dans le dossier du deploy (où se trouve WuicCore.dll)\n' +
1210
- '• Right-click sur rag-setup.ps1 → "Exécuter en tant qu\'administrateur"\n' +
1211
- '• 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' +
1212
- '• Rechargez le formulaire (Ctrl+F5) → la case s\'active automatiquement\n\n' +
1213
- 'Python >= 3.13 accepté mais non testé (wheels torch plus stables sur 3.12).\n\n' +
1214
- 'Quand Python est présent, l\'install RAG tourne en ARRIÈRE-PLAN dès la fin du setup BDD. ' +
1215
- 'Vous pouvez vous connecter immédiatement ; vous recevrez des notifications ' +
1216
- 'dans la cloche en haut pour 4 étapes + 1 finale (cliquable pour ouvrir le chatbot).\n\n' +
1217
- 'Temps typiques (réseau ~50-100 Mbps) :\n' +
1218
- '• Premier run CPU : 4-8 minutes (téléchargement torch ~200 Mo)\n' +
1219
- '• Premier run GPU CUDA : 18-32 minutes (téléchargement torch ~2,5 Go)\n' +
1220
- '• Rerun avec venv existant : <20 sec\n\n' +
1221
- 'Cold start du serveur après setup : ~13 sec.',
1222
- cudaTooltip: 'Téléchargement : ~2,5 Go (vs ~200 Mo version CPU)\n' +
1223
- 'Temps supplémentaire : +15-25 min sur réseau standard\n' +
1224
- 'Performance runtime : ~10-20× plus rapide en recherche embedding + rerank.\n\n' +
1225
- 'Requiert : GPU NVIDIA + pilotes CUDA 12.x installés au niveau système.',
1226
- apiKeyTooltip: 'Sans clé : mode retrieval-only (recherche de snippets dans le codebase).\n' +
1227
- 'Avec clé : chat LLM avec synthèse via Claude.\n\n' +
1228
- 'La clé est stockée comme variable env du processus serveur (pas dans la BDD).\n' +
1229
- 'Peut aussi être définie post-install en relançant : pwsh rag-setup.ps1 -Start -AnthropicApiKey "sk-ant-...".'
1230
- } } },
1231
- 'es-ES': { firstrun: { rag: {
1232
- installTooltip: 'PRERREQUISITO: Python 3.12 instalado y en el PATH del worker IIS.\n\n' +
1233
- 'Si la casilla está deshabilitada: Python no fue detectado.\n' +
1234
- 'Para instalarlo (ONE-CLICK):\n' +
1235
- '• Ve a la carpeta del deploy (donde está WuicCore.dll)\n' +
1236
- '• Click derecho en rag-setup.ps1 → "Ejecutar como administrador"\n' +
1237
- '• 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' +
1238
- '• Recarga el formulario (Ctrl+F5) → la casilla se habilita automáticamente\n\n' +
1239
- 'Python >= 3.13 aceptado pero no testado (wheels torch más estables en 3.12).\n\n' +
1240
- 'Con Python presente, la instalación RAG corre en SEGUNDO PLANO apenas termina el setup BD. ' +
1241
- 'Puedes iniciar sesión inmediatamente; recibirás notificaciones ' +
1242
- 'en la campana superior para 4 pasos + 1 final (cliqueable para abrir el chatbot).\n\n' +
1243
- 'Tiempos típicos (red ~50-100 Mbps):\n' +
1244
- '• First run CPU: 4-8 minutos (descarga torch ~200 MB)\n' +
1245
- '• First run GPU CUDA: 18-32 minutos (descarga torch ~2,5 GB)\n' +
1246
- '• Rerun con venv ya presente: <20 seg\n\n' +
1247
- 'Cold start del servidor tras setup: ~13 seg.',
1248
- cudaTooltip: 'Descarga: ~2,5 GB (vs ~200 MB versión CPU)\n' +
1249
- 'Tiempo adicional: +15-25 min en red estándar\n' +
1250
- 'Rendimiento runtime: ~10-20× más rápido en embedding search + rerank.\n\n' +
1251
- 'Requiere: GPU NVIDIA + drivers CUDA 12.x instalados a nivel sistema.',
1252
- apiKeyTooltip: 'Sin clave: modo retrieval-only (búsqueda de snippets del codebase).\n' +
1253
- 'Con clave: chat LLM con síntesis vía Claude.\n\n' +
1254
- 'La clave se guarda como env var del proceso servidor (no en BD).\n' +
1255
- 'Puede establecerse también post-install relanzando: pwsh rag-setup.ps1 -Start -AnthropicApiKey "sk-ant-...".'
1256
- } } },
1257
- 'de-DE': { firstrun: { rag: {
1258
- installTooltip: 'VORAUSSETZUNG: Python 3.12 installiert und im PATH des IIS-Workers.\n\n' +
1259
- 'Falls die Checkbox deaktiviert ist: Python wurde nicht erkannt.\n' +
1260
- 'Zum Installieren (ONE-CLICK):\n' +
1261
- '• Gehen Sie in den Deploy-Ordner (wo WuicCore.dll liegt)\n' +
1262
- '• Rechtsklick auf rag-setup.ps1 → "Als Administrator ausführen"\n' +
1263
- '• 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' +
1264
- '• Laden Sie das Formular neu (Strg+F5) → die Checkbox wird automatisch aktiviert\n\n' +
1265
- 'Python >= 3.13 akzeptiert, aber nicht getestet (Torch-Wheels am stabilsten auf 3.12).\n\n' +
1266
- 'Wenn Python vorhanden ist, läuft die RAG-Installation im HINTERGRUND, sobald das DB-Setup fertig ist. ' +
1267
- 'Sie können sich sofort anmelden; Sie erhalten Benachrichtigungen ' +
1268
- 'in der oberen Glocke für 4 Schritte + 1 Abschluss (klickbar zum Öffnen des Chatbots).\n\n' +
1269
- 'Typische Dauer (Netz ~50-100 Mbps):\n' +
1270
- '• First Run CPU: 4-8 Minuten (Torch-Download ~200 MB)\n' +
1271
- '• First Run GPU CUDA: 18-32 Minuten (Torch-Download ~2,5 GB)\n' +
1272
- '• Rerun mit bestehendem venv: <20 Sek\n\n' +
1273
- 'Server-Cold-Start nach Setup: ~13 Sek.',
1274
- cudaTooltip: 'Download: ~2,5 GB (vs ~200 MB CPU-Version)\n' +
1275
- 'Zusätzliche Zeit: +15-25 Min in Standard-Netz\n' +
1276
- 'Runtime-Performance: ~10-20× schneller bei Embedding-Search + Rerank.\n\n' +
1277
- 'Erfordert: NVIDIA-GPU + systemweit installierte CUDA-12.x-Treiber.',
1278
- apiKeyTooltip: 'Ohne Schlüssel: Retrieval-only-Modus (Snippet-Suche im Codebase).\n' +
1279
- 'Mit Schlüssel: LLM-Chat mit Synthese via Claude.\n\n' +
1280
- 'Der Schlüssel wird als Env-Var des Server-Prozesses gespeichert (nicht in DB).\n' +
1281
- 'Kann auch nach der Installation gesetzt werden via: pwsh rag-setup.ps1 -Start -AnthropicApiKey "sk-ant-...".'
1282
- } } }
1283
- };
1284
- for (const [lang, strings] of Object.entries(bundles)) {
1285
- this.translate.setTranslation(lang, strings, true);
1286
- }
1287
- if (!this.translate.getDefaultLang()) {
1288
- const browserLang = (navigator.language || 'it-IT');
1289
- const resolvedLang = Object.keys(bundles).find(l => l.toLowerCase() === browserLang.toLowerCase())
1290
- || Object.keys(bundles).find(l => l.startsWith(browserLang.substring(0, 2)))
1291
- || 'it-IT';
1292
- this.translate.setDefaultLang(resolvedLang);
1293
- void this.translate.use(resolvedLang);
1294
- }
1295
- }
1296
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicFirstRunWizardComponent, deps: [{ token: i1.HttpClient }, { token: i2.TranslateService }, { token: i3.MessageService }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Component });
1297
- 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) {\n<div class=\"first-run-overlay\">\n <div class=\"first-run-card first-run-card-wide\">\n <div class=\"first-run-title\">Configurazione iniziale progetto</div>\n <div class=\"first-run-subtitle\">Seleziona DBMS, connessione dati e nome database metadati.</div>\n\n <!-- BODY scrollabile: header (title/subtitle) e footer (progress + CTA)\n restano sempre visibili, solo questo blocco scrolla quando il form\n e' piu' alto della viewport. -->\n <div class=\"first-run-body\">\n\n <!-- Grid 2 colonne: sx = connessione + scaffold / dx = admin + RAG.\n Su viewport stretti (< 720px) il grid collassa a 1 colonna. -->\n <div class=\"first-run-grid\">\n\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 -->\n <div class=\"first-run-col\">\n\n @if (firstRunSetupModeOptions.length > 1) {\n <div class=\"first-run-field\">\n <label for=\"first-run-mode\">Modalit\u00E0 setup</label>\n <p-select [options]=\"firstRunSetupModeOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"firstRunForm.setupMode\" (ngModelChange)=\"onFirstRunSetupModeChanged($event)\"\n [disabled]=\"firstRunInstalling\" [style]=\"{ width: '100%' }\" inputId=\"first-run-mode\"></p-select>\n </div>\n }\n\n <div class=\"first-run-field\">\n <label for=\"first-run-dbms\">DBMS</label>\n <p-select [options]=\"firstRunDbmsOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"firstRunForm.dbms\" (ngModelChange)=\"onFirstRunDbmsChanged($event)\"\n [disabled]=\"firstRunInstalling\" [style]=\"{ width: '100%' }\" inputId=\"first-run-dbms\"></p-select>\n </div>\n\n @if (firstRunForm.setupMode === 'tutorial') {\n <div class=\"first-run-field first-run-checkbox-row\">\n <label>\n <input type=\"checkbox\" [ngModel]=\"firstRunForm.createTutorialIfMissing\"\n (ngModelChange)=\"firstRunForm.createTutorialIfMissing = !!$event\" [disabled]=\"firstRunInstalling\" />\n Crea tutorial se manca\n </label>\n </div>\n }\n\n <div class=\"first-run-field\">\n <label for=\"first-run-datasql\">DataSQLConnection</label>\n <input id=\"first-run-datasql\" class=\"first-run-input\" type=\"text\"\n [ngModel]=\"firstRunForm.dataConnectionString\" (ngModelChange)=\"onFirstRunConnectionChanged($event)\"\n [disabled]=\"firstRunInstalling\"\n placeholder=\"es. data source=localhost\\sqlexpress;User ID=sa;Password=...;initial catalog=WideWorldImporters\" />\n </div>\n\n <div class=\"first-run-field\">\n <p-button label=\"Test connessione e carica DB\" severity=\"secondary\" [outlined]=\"true\"\n [loading]=\"firstRunConnectionTesting\"\n [disabled]=\"firstRunInstalling || firstRunLoading || !firstRunForm.dataConnectionString\"\n (onClick)=\"testFirstRunConnectionAndLoadDatabases()\"></p-button>\n </div>\n\n @if (firstRunForm.setupMode === 'tutorial') {\n <div class=\"first-run-field\">\n <label for=\"first-run-datadb\">Database dati tutorial</label>\n <input id=\"first-run-datadb\" class=\"first-run-input\" type=\"text\"\n [ngModel]=\"firstRunForm.tutorialDataDbName\"\n (ngModelChange)=\"firstRunForm.tutorialDataDbName = $event; firstRunForm.dataDbName = $event\"\n [disabled]=\"firstRunInstalling || !firstRunConnectionValid\"\n placeholder=\"WideWorldImporters\" />\n </div>\n } @else {\n <div class=\"first-run-field\">\n <label for=\"first-run-datadb\">Database dati</label>\n <p-select [options]=\"firstRunDataDbOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"firstRunForm.dataDbName\" (ngModelChange)=\"firstRunForm.dataDbName = $event\"\n [disabled]=\"firstRunInstalling || firstRunDbLoading || !firstRunConnectionValid\" [filter]=\"true\"\n [editable]=\"true\" [style]=\"{ width: '100%' }\" inputId=\"first-run-datadb\"\n placeholder=\"Seleziona o digita database\"></p-select>\n <div style=\"margin-top: 0.5rem;\">\n <p-button label=\"Aggiorna elenco DB\" severity=\"secondary\" [outlined]=\"true\" size=\"small\"\n [loading]=\"firstRunDbLoading\" [disabled]=\"firstRunInstalling || !firstRunConnectionValid\"\n (onClick)=\"testFirstRunConnectionAndLoadDatabases()\"></p-button>\n </div>\n </div>\n }\n\n <div class=\"first-run-field\">\n <label for=\"first-run-metadb\">@if (firstRunForm.setupMode === 'tutorial') {Nome DB metadati tutorial} @else {Nome DB metadati}</label>\n <input id=\"first-run-metadb\" class=\"first-run-input\" type=\"text\"\n [ngModel]=\"firstRunForm.setupMode === 'tutorial' ? firstRunForm.tutorialMetadataDbName : firstRunForm.metadataDbName\"\n (ngModelChange)=\"firstRunForm.metadataDbName = $event; firstRunForm.tutorialMetadataDbName = $event\"\n [disabled]=\"firstRunInstalling\"\n placeholder=\"metadataDB\" />\n </div>\n\n @if (firstRunForm.setupMode !== 'tutorial') {\n <div class=\"first-run-field first-run-checkbox-row\">\n <label>\n <input type=\"checkbox\" [ngModel]=\"firstRunForm.scaffoldExistingDatabase\"\n (ngModelChange)=\"firstRunForm.scaffoldExistingDatabase = !!$event\"\n [disabled]=\"firstRunInstalling\" />\n Esegui scaffold automatico delle tabelle del DB selezionato\n </label>\n <small style=\"display:block; margin-top:4px; opacity:0.75; font-size:0.8rem; line-height:1.35;\">\n Popola <code>_metadati__tabelle</code> / <code>_metadati__colonne</code> al termine dell'installazione.\n Operazione potenzialmente lunga su DB con molte tabelle.\n </small>\n </div>\n }\n </div>\n\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 -->\n <div class=\"first-run-col\">\n\n <div class=\"first-run-section-title\">Utente admin iniziale</div>\n\n <div class=\"first-run-field\">\n <label for=\"first-run-admin-username\">Username admin iniziale</label>\n <input id=\"first-run-admin-username\" class=\"first-run-input\" type=\"text\"\n [ngModel]=\"firstRunForm.adminUsername\"\n (ngModelChange)=\"firstRunForm.adminUsername = $event\"\n [disabled]=\"firstRunInstalling\" autocomplete=\"off\"\n placeholder=\"admin\" />\n </div>\n\n <div class=\"first-run-field\">\n <label for=\"first-run-admin-password\">Password admin iniziale</label>\n <input id=\"first-run-admin-password\" class=\"first-run-input\" type=\"password\"\n [ngModel]=\"firstRunForm.adminPassword\"\n (ngModelChange)=\"firstRunForm.adminPassword = $event\"\n [disabled]=\"firstRunInstalling\" autocomplete=\"new-password\"\n placeholder=\"(min 4 caratteri)\" />\n </div>\n\n <div class=\"first-run-field\">\n <label for=\"first-run-admin-language\">Lingua admin iniziale</label>\n <p-select [options]=\"firstRunAdminLanguageOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"firstRunForm.adminLanguage\"\n (ngModelChange)=\"firstRunForm.adminLanguage = $event\"\n [disabled]=\"firstRunInstalling\"\n [style]=\"{ width: '100%' }\" inputId=\"first-run-admin-language\"></p-select>\n <small style=\"display:block; margin-top:4px; opacity:0.75; font-size:0.8rem; line-height:1.35;\">\n Lingua applicata all'utente admin dopo il primo login. Puoi cambiarla in seguito dal profilo utente.\n </small>\n </div>\n\n <div class=\"first-run-section-title first-run-section-title-rag\">RAG Chatbot (opzionale)</div>\n\n <div class=\"first-run-field\">\n <small style=\"display:block; margin-bottom:8px; opacity:0.8; font-size:0.85rem; line-height:1.4;\">\n Il RAG Chatbot \u00E8 gi\u00E0 incluso e funziona <strong>out-of-the-box</strong>: l'engine \u00E8 nativo\n <strong>.NET/ONNX</strong> (nessun Python, nessun server separato da installare). Al primo\n uso della chat scarica automaticamente i modelli (~2.3 GB) da <code>wuic-framework.com</code>.\n Con una GPU NVIDIA (CUDA 12.x + cuDNN 9.x) le risposte sono in ~1s; senza, fallback automatico\n su CPU. <strong>Nessun setup da fare qui.</strong>\n </small>\n <label for=\"first-run-anthropic-key\">\n Anthropic API Key (opzionale)\n <i class=\"pi pi-info-circle first-run-info-icon\"\n [pTooltip]=\"'firstrun.rag.apiKeyTooltip' | translate\"\n tooltipPosition=\"right\"\n [tooltipStyleClass]=\"'first-run-tooltip'\"\n [escape]=\"false\"></i>\n </label>\n <textarea id=\"first-run-anthropic-key\" class=\"first-run-input first-run-textarea\"\n [ngModel]=\"firstRunForm.anthropicApiKey\"\n (ngModelChange)=\"firstRunForm.anthropicApiKey = $event\"\n [disabled]=\"firstRunInstalling\"\n rows=\"2\" autocomplete=\"off\"\n placeholder=\"sk-ant-...\"></textarea>\n <small style=\"display:block; margin-top:4px; opacity:0.75; font-size:0.8rem; line-height:1.35;\">\n Senza chiave il chatbot funziona solo in modalit\u00E0 retrieval (ricerca snippet).\n Con chiave abilita la chat con LLM (Claude) e le azioni proposte sul designer.\n Viene salvata in <code>appsettings.json</code>; puoi cambiarla anche dopo.\n </small>\n </div>\n </div>\n\n <!-- Crash reporting opt-in (skill crash-reporting Commit 8). Default OFF.\n Il consenso GDPR usa labels HARDCODED in `crashReportingLabels`\n dentro il componente (NON `wuic_translation`) perche' il\n first-run gira PRIMA che il DB metadati esista \u2014 niente DB,\n niente translation table. Vedi commento esteso nel componente.\n Al check del consenso si apre il popup col disclaimer\n dettagliato; l'utente conferma li' per attivare il flag. -->\n <div class=\"first-run-section-title first-run-section-title-crash-reporting\">{{ crashReportingLabels.title }}</div>\n <div class=\"first-run-field first-run-checkbox-row\">\n <label>\n <input type=\"checkbox\" [ngModel]=\"firstRunForm.enableCrashReporting\"\n (ngModelChange)=\"onCrashReportingToggle($event)\"\n [disabled]=\"firstRunInstalling\" />\n {{ crashReportingLabels.checkbox_accept }}\n </label>\n <small style=\"display:block; margin-top:4px; opacity:0.85; font-size:0.85rem; line-height:1.45;\">\n {{ crashReportingLabels.intro_short }}\n <a href=\"javascript:void(0)\" (click)=\"openCrashReportingDisclaimer()\"\n style=\"margin-left:6px;\">{{ crashReportingLabels.read_full }}</a>\n </small>\n </div>\n </div>\n\n <!-- Disclaimer GDPR popup (apre al check + da link \"leggi tutto\"). -->\n <p-dialog [(visible)]=\"crashReportingDisclaimerVisible\"\n [modal]=\"true\"\n [closable]=\"true\"\n [closeOnEscape]=\"true\"\n [header]=\"crashReportingLabels.title\"\n [style]=\"{ width: '600px', maxWidth: '90vw' }\"\n [contentStyle]=\"{ 'max-height': '70vh', 'overflow-y': 'auto' }\"\n [appendTo]=\"'body'\">\n <div style=\"line-height:1.55;\">\n <p>{{ crashReportingLabels.intro }}</p>\n <p><strong>{{ crashReportingLabels.what_we_send }}</strong></p>\n <ul style=\"margin:0 0 8px 18px; padding:0;\">\n <li>{{ crashReportingLabels.what_we_dont_send }}</li>\n <li>{{ crashReportingLabels.where_it_goes }}</li>\n <li>{{ crashReportingLabels.retention }}</li>\n <li>{{ crashReportingLabels.legal_basis }}</li>\n <li>{{ crashReportingLabels.how_to_disable }}</li>\n </ul>\n </div>\n <ng-template pTemplate=\"footer\">\n <p-button [label]=\"crashReportingLabels.btn_decline\"\n severity=\"secondary\"\n (onClick)=\"declineCrashReporting()\"></p-button>\n <p-button [label]=\"crashReportingLabels.btn_accept\"\n (onClick)=\"acceptCrashReporting()\"></p-button>\n </ng-template>\n </p-dialog>\n\n </div><!-- /.first-run-body -->\n\n <!-- FOOTER sticky: error (se presente) + riga progress|CTA sempre visibile. -->\n <div class=\"first-run-footer\">\n @if (firstRunError && !firstRunErrorDismissed) {\n <div class=\"first-run-error\" role=\"alert\">\n <button type=\"button\"\n class=\"first-run-error__close\"\n (click)=\"dismissFirstRunError()\"\n aria-label=\"Chiudi errore\"\n title=\"Chiudi\">\u00D7</button>\n <div class=\"first-run-error__body\">{{ firstRunError }}</div>\n </div>\n }\n\n <div class=\"first-run-footer-row\">\n @if (firstRunInstalling && firstRunProgress.active) {\n <div class=\"first-run-progress\">\n <div class=\"first-run-progress-label\">\n <strong>{{ firstRunProgress.phase }}</strong>\n @if (firstRunProgress.total > 0 && !firstRunProgress.phase.includes('.restore')) {\n <span class=\"first-run-progress-count\">\n \u2014 batch {{ firstRunProgress.current }} / {{ firstRunProgress.total }}\n </span>\n }\n @if (firstRunProgress.elapsedMs > 0) {\n <span class=\"first-run-progress-count\">\n \u00B7 {{ (firstRunProgress.elapsedMs / 1000) | number:'1.0-0' }}s\n </span>\n }\n </div>\n @if (firstRunProgress.phase.includes('.restore') && firstRunProgress.percent < 5) {\n <p-progressBar mode=\"indeterminate\" [style]=\"{'height': '1rem'}\"></p-progressBar>\n } @else {\n <p-progressBar [value]=\"firstRunProgress.percent\" [showValue]=\"true\" [style]=\"{'height': '1rem'}\"></p-progressBar>\n }\n @if (firstRunProgress.message) {\n <div class=\"first-run-progress-message\">{{ firstRunProgress.message }}</div>\n }\n </div>\n } @else {\n <div class=\"first-run-progress-placeholder\"></div>\n }\n\n <div class=\"first-run-actions\">\n <p-button label=\"Conferma e genera metadataDB\" [loading]=\"firstRunInstalling\"\n [disabled]=\"firstRunInstalling || firstRunLoading\" (onClick)=\"submitFirstRunInstall()\"></p-button>\n </div>\n </div>\n </div>\n </div>\n</div>\n}\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: i6.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: i11.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "unit", "mode", "color"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6$1.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$1.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: i2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.Eager });
1298
- }
1299
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WuicFirstRunWizardComponent, decorators: [{
1300
- type: Component,
1301
- args: [{ selector: 'wuic-first-run-wizard-impl', standalone: true, imports: [
1302
- CommonModule,
1303
- FormsModule,
1304
- TranslateModule,
1305
- ButtonModule,
1306
- SelectModule,
1307
- ProgressBarModule,
1308
- TooltipModule,
1309
- DialogModule
1310
- ], changeDetection: ChangeDetectionStrategy.Default, template: "@if (showFirstRunInstall) {\n<div class=\"first-run-overlay\">\n <div class=\"first-run-card first-run-card-wide\">\n <div class=\"first-run-title\">Configurazione iniziale progetto</div>\n <div class=\"first-run-subtitle\">Seleziona DBMS, connessione dati e nome database metadati.</div>\n\n <!-- BODY scrollabile: header (title/subtitle) e footer (progress + CTA)\n restano sempre visibili, solo questo blocco scrolla quando il form\n e' piu' alto della viewport. -->\n <div class=\"first-run-body\">\n\n <!-- Grid 2 colonne: sx = connessione + scaffold / dx = admin + RAG.\n Su viewport stretti (< 720px) il grid collassa a 1 colonna. -->\n <div class=\"first-run-grid\">\n\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 -->\n <div class=\"first-run-col\">\n\n @if (firstRunSetupModeOptions.length > 1) {\n <div class=\"first-run-field\">\n <label for=\"first-run-mode\">Modalit\u00E0 setup</label>\n <p-select [options]=\"firstRunSetupModeOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"firstRunForm.setupMode\" (ngModelChange)=\"onFirstRunSetupModeChanged($event)\"\n [disabled]=\"firstRunInstalling\" [style]=\"{ width: '100%' }\" inputId=\"first-run-mode\"></p-select>\n </div>\n }\n\n <div class=\"first-run-field\">\n <label for=\"first-run-dbms\">DBMS</label>\n <p-select [options]=\"firstRunDbmsOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"firstRunForm.dbms\" (ngModelChange)=\"onFirstRunDbmsChanged($event)\"\n [disabled]=\"firstRunInstalling\" [style]=\"{ width: '100%' }\" inputId=\"first-run-dbms\"></p-select>\n </div>\n\n @if (firstRunForm.setupMode === 'tutorial') {\n <div class=\"first-run-field first-run-checkbox-row\">\n <label>\n <input type=\"checkbox\" [ngModel]=\"firstRunForm.createTutorialIfMissing\"\n (ngModelChange)=\"firstRunForm.createTutorialIfMissing = !!$event\" [disabled]=\"firstRunInstalling\" />\n Crea tutorial se manca\n </label>\n </div>\n }\n\n <div class=\"first-run-field\">\n <label for=\"first-run-datasql\">DataSQLConnection</label>\n <input id=\"first-run-datasql\" class=\"first-run-input\" type=\"text\"\n [ngModel]=\"firstRunForm.dataConnectionString\" (ngModelChange)=\"onFirstRunConnectionChanged($event)\"\n [disabled]=\"firstRunInstalling\"\n placeholder=\"es. data source=localhost\\sqlexpress;User ID=sa;Password=...;initial catalog=WideWorldImporters\" />\n </div>\n\n <div class=\"first-run-field\">\n <p-button label=\"Test connessione e carica DB\" severity=\"secondary\" [outlined]=\"true\"\n [loading]=\"firstRunConnectionTesting\"\n [disabled]=\"firstRunInstalling || firstRunLoading || !firstRunForm.dataConnectionString\"\n (onClick)=\"testFirstRunConnectionAndLoadDatabases()\"></p-button>\n </div>\n\n @if (firstRunForm.setupMode === 'tutorial') {\n <div class=\"first-run-field\">\n <label for=\"first-run-datadb\">Database dati tutorial</label>\n <input id=\"first-run-datadb\" class=\"first-run-input\" type=\"text\"\n [ngModel]=\"firstRunForm.tutorialDataDbName\"\n (ngModelChange)=\"firstRunForm.tutorialDataDbName = $event; firstRunForm.dataDbName = $event\"\n [disabled]=\"firstRunInstalling || !firstRunConnectionValid\"\n placeholder=\"WideWorldImporters\" />\n </div>\n } @else {\n <div class=\"first-run-field\">\n <label for=\"first-run-datadb\">Database dati</label>\n <p-select [options]=\"firstRunDataDbOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"firstRunForm.dataDbName\" (ngModelChange)=\"firstRunForm.dataDbName = $event\"\n [disabled]=\"firstRunInstalling || firstRunDbLoading || !firstRunConnectionValid\" [filter]=\"true\"\n [editable]=\"true\" [style]=\"{ width: '100%' }\" inputId=\"first-run-datadb\"\n placeholder=\"Seleziona o digita database\"></p-select>\n <div style=\"margin-top: 0.5rem;\">\n <p-button label=\"Aggiorna elenco DB\" severity=\"secondary\" [outlined]=\"true\" size=\"small\"\n [loading]=\"firstRunDbLoading\" [disabled]=\"firstRunInstalling || !firstRunConnectionValid\"\n (onClick)=\"testFirstRunConnectionAndLoadDatabases()\"></p-button>\n </div>\n </div>\n }\n\n <div class=\"first-run-field\">\n <label for=\"first-run-metadb\">@if (firstRunForm.setupMode === 'tutorial') {Nome DB metadati tutorial} @else {Nome DB metadati}</label>\n <input id=\"first-run-metadb\" class=\"first-run-input\" type=\"text\"\n [ngModel]=\"firstRunForm.setupMode === 'tutorial' ? firstRunForm.tutorialMetadataDbName : firstRunForm.metadataDbName\"\n (ngModelChange)=\"firstRunForm.metadataDbName = $event; firstRunForm.tutorialMetadataDbName = $event\"\n [disabled]=\"firstRunInstalling\"\n placeholder=\"metadataDB\" />\n </div>\n\n @if (firstRunForm.setupMode !== 'tutorial') {\n <div class=\"first-run-field first-run-checkbox-row\">\n <label>\n <input type=\"checkbox\" [ngModel]=\"firstRunForm.scaffoldExistingDatabase\"\n (ngModelChange)=\"firstRunForm.scaffoldExistingDatabase = !!$event\"\n [disabled]=\"firstRunInstalling\" />\n Esegui scaffold automatico delle tabelle del DB selezionato\n </label>\n <small style=\"display:block; margin-top:4px; opacity:0.75; font-size:0.8rem; line-height:1.35;\">\n Popola <code>_metadati__tabelle</code> / <code>_metadati__colonne</code> al termine dell'installazione.\n Operazione potenzialmente lunga su DB con molte tabelle.\n </small>\n </div>\n }\n </div>\n\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 -->\n <div class=\"first-run-col\">\n\n <div class=\"first-run-section-title\">Utente admin iniziale</div>\n\n <div class=\"first-run-field\">\n <label for=\"first-run-admin-username\">Username admin iniziale</label>\n <input id=\"first-run-admin-username\" class=\"first-run-input\" type=\"text\"\n [ngModel]=\"firstRunForm.adminUsername\"\n (ngModelChange)=\"firstRunForm.adminUsername = $event\"\n [disabled]=\"firstRunInstalling\" autocomplete=\"off\"\n placeholder=\"admin\" />\n </div>\n\n <div class=\"first-run-field\">\n <label for=\"first-run-admin-password\">Password admin iniziale</label>\n <input id=\"first-run-admin-password\" class=\"first-run-input\" type=\"password\"\n [ngModel]=\"firstRunForm.adminPassword\"\n (ngModelChange)=\"firstRunForm.adminPassword = $event\"\n [disabled]=\"firstRunInstalling\" autocomplete=\"new-password\"\n placeholder=\"(min 4 caratteri)\" />\n </div>\n\n <div class=\"first-run-field\">\n <label for=\"first-run-admin-language\">Lingua admin iniziale</label>\n <p-select [options]=\"firstRunAdminLanguageOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"firstRunForm.adminLanguage\"\n (ngModelChange)=\"firstRunForm.adminLanguage = $event\"\n [disabled]=\"firstRunInstalling\"\n [style]=\"{ width: '100%' }\" inputId=\"first-run-admin-language\"></p-select>\n <small style=\"display:block; margin-top:4px; opacity:0.75; font-size:0.8rem; line-height:1.35;\">\n Lingua applicata all'utente admin dopo il primo login. Puoi cambiarla in seguito dal profilo utente.\n </small>\n </div>\n\n <div class=\"first-run-section-title first-run-section-title-rag\">RAG Chatbot (opzionale)</div>\n\n <div class=\"first-run-field\">\n <small style=\"display:block; margin-bottom:8px; opacity:0.8; font-size:0.85rem; line-height:1.4;\">\n Il RAG Chatbot \u00E8 gi\u00E0 incluso e funziona <strong>out-of-the-box</strong>: l'engine \u00E8 nativo\n <strong>.NET/ONNX</strong> (nessun Python, nessun server separato da installare). Al primo\n uso della chat scarica automaticamente i modelli (~2.3 GB) da <code>wuic-framework.com</code>.\n Con una GPU NVIDIA (CUDA 12.x + cuDNN 9.x) le risposte sono in ~1s; senza, fallback automatico\n su CPU. <strong>Nessun setup da fare qui.</strong>\n </small>\n <label for=\"first-run-anthropic-key\">\n Anthropic API Key (opzionale)\n <i class=\"pi pi-info-circle first-run-info-icon\"\n [pTooltip]=\"'firstrun.rag.apiKeyTooltip' | translate\"\n tooltipPosition=\"right\"\n [tooltipStyleClass]=\"'first-run-tooltip'\"\n [escape]=\"false\"></i>\n </label>\n <textarea id=\"first-run-anthropic-key\" class=\"first-run-input first-run-textarea\"\n [ngModel]=\"firstRunForm.anthropicApiKey\"\n (ngModelChange)=\"firstRunForm.anthropicApiKey = $event\"\n [disabled]=\"firstRunInstalling\"\n rows=\"2\" autocomplete=\"off\"\n placeholder=\"sk-ant-...\"></textarea>\n <small style=\"display:block; margin-top:4px; opacity:0.75; font-size:0.8rem; line-height:1.35;\">\n Senza chiave il chatbot funziona solo in modalit\u00E0 retrieval (ricerca snippet).\n Con chiave abilita la chat con LLM (Claude) e le azioni proposte sul designer.\n Viene salvata in <code>appsettings.json</code>; puoi cambiarla anche dopo.\n </small>\n </div>\n </div>\n\n <!-- Crash reporting opt-in (skill crash-reporting Commit 8). Default OFF.\n Il consenso GDPR usa labels HARDCODED in `crashReportingLabels`\n dentro il componente (NON `wuic_translation`) perche' il\n first-run gira PRIMA che il DB metadati esista \u2014 niente DB,\n niente translation table. Vedi commento esteso nel componente.\n Al check del consenso si apre il popup col disclaimer\n dettagliato; l'utente conferma li' per attivare il flag. -->\n <div class=\"first-run-section-title first-run-section-title-crash-reporting\">{{ crashReportingLabels.title }}</div>\n <div class=\"first-run-field first-run-checkbox-row\">\n <label>\n <input type=\"checkbox\" [ngModel]=\"firstRunForm.enableCrashReporting\"\n (ngModelChange)=\"onCrashReportingToggle($event)\"\n [disabled]=\"firstRunInstalling\" />\n {{ crashReportingLabels.checkbox_accept }}\n </label>\n <small style=\"display:block; margin-top:4px; opacity:0.85; font-size:0.85rem; line-height:1.45;\">\n {{ crashReportingLabels.intro_short }}\n <a href=\"javascript:void(0)\" (click)=\"openCrashReportingDisclaimer()\"\n style=\"margin-left:6px;\">{{ crashReportingLabels.read_full }}</a>\n </small>\n </div>\n </div>\n\n <!-- Disclaimer GDPR popup (apre al check + da link \"leggi tutto\"). -->\n <p-dialog [(visible)]=\"crashReportingDisclaimerVisible\"\n [modal]=\"true\"\n [closable]=\"true\"\n [closeOnEscape]=\"true\"\n [header]=\"crashReportingLabels.title\"\n [style]=\"{ width: '600px', maxWidth: '90vw' }\"\n [contentStyle]=\"{ 'max-height': '70vh', 'overflow-y': 'auto' }\"\n [appendTo]=\"'body'\">\n <div style=\"line-height:1.55;\">\n <p>{{ crashReportingLabels.intro }}</p>\n <p><strong>{{ crashReportingLabels.what_we_send }}</strong></p>\n <ul style=\"margin:0 0 8px 18px; padding:0;\">\n <li>{{ crashReportingLabels.what_we_dont_send }}</li>\n <li>{{ crashReportingLabels.where_it_goes }}</li>\n <li>{{ crashReportingLabels.retention }}</li>\n <li>{{ crashReportingLabels.legal_basis }}</li>\n <li>{{ crashReportingLabels.how_to_disable }}</li>\n </ul>\n </div>\n <ng-template pTemplate=\"footer\">\n <p-button [label]=\"crashReportingLabels.btn_decline\"\n severity=\"secondary\"\n (onClick)=\"declineCrashReporting()\"></p-button>\n <p-button [label]=\"crashReportingLabels.btn_accept\"\n (onClick)=\"acceptCrashReporting()\"></p-button>\n </ng-template>\n </p-dialog>\n\n </div><!-- /.first-run-body -->\n\n <!-- FOOTER sticky: error (se presente) + riga progress|CTA sempre visibile. -->\n <div class=\"first-run-footer\">\n @if (firstRunError && !firstRunErrorDismissed) {\n <div class=\"first-run-error\" role=\"alert\">\n <button type=\"button\"\n class=\"first-run-error__close\"\n (click)=\"dismissFirstRunError()\"\n aria-label=\"Chiudi errore\"\n title=\"Chiudi\">\u00D7</button>\n <div class=\"first-run-error__body\">{{ firstRunError }}</div>\n </div>\n }\n\n <div class=\"first-run-footer-row\">\n @if (firstRunInstalling && firstRunProgress.active) {\n <div class=\"first-run-progress\">\n <div class=\"first-run-progress-label\">\n <strong>{{ firstRunProgress.phase }}</strong>\n @if (firstRunProgress.total > 0 && !firstRunProgress.phase.includes('.restore')) {\n <span class=\"first-run-progress-count\">\n \u2014 batch {{ firstRunProgress.current }} / {{ firstRunProgress.total }}\n </span>\n }\n @if (firstRunProgress.elapsedMs > 0) {\n <span class=\"first-run-progress-count\">\n \u00B7 {{ (firstRunProgress.elapsedMs / 1000) | number:'1.0-0' }}s\n </span>\n }\n </div>\n @if (firstRunProgress.phase.includes('.restore') && firstRunProgress.percent < 5) {\n <p-progressBar mode=\"indeterminate\" [style]=\"{'height': '1rem'}\"></p-progressBar>\n } @else {\n <p-progressBar [value]=\"firstRunProgress.percent\" [showValue]=\"true\" [style]=\"{'height': '1rem'}\"></p-progressBar>\n }\n @if (firstRunProgress.message) {\n <div class=\"first-run-progress-message\">{{ firstRunProgress.message }}</div>\n }\n </div>\n } @else {\n <div class=\"first-run-progress-placeholder\"></div>\n }\n\n <div class=\"first-run-actions\">\n <p-button label=\"Conferma e genera metadataDB\" [loading]=\"firstRunInstalling\"\n [disabled]=\"firstRunInstalling || firstRunLoading\" (onClick)=\"submitFirstRunInstall()\"></p-button>\n </div>\n </div>\n </div>\n </div>\n</div>\n}\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"] }]
1311
- }], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.TranslateService }, { type: i3.MessageService }, { type: i0.Injector }], propDecorators: { complete: [{
1312
- type: Output
1313
- }] } });
1314
-
1315
- export { WuicFirstRunWizardComponent };
1316
- //# sourceMappingURL=wuic-framework-lib-first-run-wizard.component-BuSxs_CM.mjs.map