@esfaenza/admin-module 20.3.20 → 20.3.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  import * as i5 from '@angular/common';
2
- import { CommonModule } from '@angular/common';
2
+ import { formatNumber, CommonModule } from '@angular/common';
3
3
  import * as i7 from '@angular/forms';
4
4
  import { FormsModule } from '@angular/forms';
5
5
  import * as i0 from '@angular/core';
6
- import { InjectionToken, Injectable, Inject, Component, ViewChild, EventEmitter, Output, Input, Pipe, inject, NgModule } from '@angular/core';
6
+ import { InjectionToken, Injectable, Inject, Component, ViewChild, EventEmitter, Output, Input, Optional, Pipe, inject, NgModule } from '@angular/core';
7
7
  import * as i4 from '@angular/router';
8
8
  import { RouterModule } from '@angular/router';
9
9
  import * as i8$1 from 'ngx-bootstrap/modal';
@@ -25,11 +25,12 @@ import { ContextMenuModule } from '@esfaenza/ngx-contextmenu';
25
25
  import * as i3 from '@esfaenza/preferences';
26
26
  import { HttpParams } from '@angular/common/http';
27
27
  import { HTTPService } from '@esfaenza/httpservice';
28
- import { firstValueFrom, catchError, of, ReplaySubject } from 'rxjs';
28
+ import { firstValueFrom, catchError, throwError, of, ReplaySubject } from 'rxjs';
29
29
  import * as i10 from '@esfaenza/core/directives';
30
30
  import { DirectivesModule } from '@esfaenza/core/directives';
31
31
  import { GenericItem, KeyValue as KeyValue$1 } from '@esfaenza/core/domain';
32
32
  import { __decorate } from 'tslib';
33
+ import * as i5$1 from '@angular/platform-browser';
33
34
  import * as am5 from '@amcharts/amcharts5';
34
35
  import { ComponentsModule } from '@esfaenza/core/components';
35
36
 
@@ -44,6 +45,9 @@ class AuditsView extends AppSearch {
44
45
  const ADM_MODULE_PATH = new InjectionToken('ADM_MODULE_PATH');
45
46
  const ADM_CONVERSATIONS = new InjectionToken('ADM_CONVERSATIONS');
46
47
  const ADM_TOPACT_V2 = new InjectionToken('ADM_TOPACT_V2');
48
+ const ADM_PROFILER_V2 = new InjectionToken('ADM_PROFILER_V2');
49
+ const ADM_WORKLOAD_V2 = new InjectionToken('ADM_WORKLOAD_V2');
50
+ const ADM_METRICS_V2 = new InjectionToken('ADM_METRICS_V2');
47
51
 
48
52
  class AdmAuditLoc extends LocalizationService {
49
53
  constructor(injector) {
@@ -81,6 +85,10 @@ const GET_Director_GetInfos = "/api/Director/GetInfos";
81
85
  const GET_Director_GetMetrics = "/api/Director/GetMetrics";
82
86
  const POST_Director_GetMetrics = "/api/Director/GetMetrics";
83
87
  const POST_Director_GetProfilerDataNodes = "/api/Director/GetProfilerDataNodes";
88
+ const POST_Director_GetProfilerNode = "/api/Director/GetProfilerNode";
89
+ const POST_Director_GetProfilerFlat = "/api/Director/GetProfilerFlat";
90
+ const GET_Director_GetInstanceId = "/api/Director/GetInstanceId";
91
+ const GET_Director_GetSnapshots = "/api/Director/GetSnapshots";
84
92
  const GET_Director_GetWorkloadReport = "/api/Director/GetWorkloadReport";
85
93
  const POST_Director_GetServerAudits = "/api/Director/GetServerAudits";
86
94
  const POST_Director_GetDynamicViewData = "/api/Director/GetDynamicViewData";
@@ -157,9 +165,51 @@ class AdminModuleBaseService {
157
165
  // Stesso URL di getTopActivities: con DirectorFeature.TopActivityV2 attivo il backend-for-frontend risponde con il contratto v2
158
166
  async getTopActivitiesV2(be, req) {
159
167
  var pars = new HttpParams().set("be", be ?? "");
160
- let ret = await firstValueFrom(this.http.post(POST_Director_GetTopActivities, req, pars));
168
+ let ret = await this.v2Call(this.http.post(POST_Director_GetTopActivities, req, pars, "", false));
161
169
  return ret;
162
170
  }
171
+ // Stesso URL di getProfilerDataNodes: con DirectorFeature.ProfilerV2 attivo il backend-for-frontend risponde con l'albero del profiler v2
172
+ async getProfilerTreeV2(be, req) {
173
+ var pars = new HttpParams().set("be", be ?? "");
174
+ let ret = await this.v2Call(this.http.post(POST_Director_GetProfilerDataNodes, req, pars, "", false));
175
+ return ret;
176
+ }
177
+ // null se il nodo non c'è nelle istanze o nella finestra scelte
178
+ async getProfilerNodeV2(be, req) {
179
+ var pars = new HttpParams().set("be", be ?? "");
180
+ let ret = await this.v2Call(this.http.post(POST_Director_GetProfilerNode, req, pars, "", false));
181
+ return ret;
182
+ }
183
+ async getProfilerFlatV2(be, req) {
184
+ var pars = new HttpParams().set("be", be ?? "");
185
+ let ret = await this.v2Call(this.http.post(POST_Director_GetProfilerFlat, req, pars, "", false));
186
+ return ret;
187
+ }
188
+ // Le chiamate delle pagine v2 non usano la gestione errori di HTTPService, che in caso di errore restituisce un Observable che non termina mai:
189
+ // l'await resterebbe appeso e la pagina bloccata in caricamento. L'errore arriva al chiamante (vedi errorText), tranne la sessione scaduta:
190
+ // per quella ripeto una chiamata qualsiasi con la gestione standard, così il redirect al login lo fa HTTPService come sulle altre pagine
191
+ v2(call) {
192
+ return call.pipe(catchError(err => {
193
+ if (err?.status == 401)
194
+ this.http.get(GET_Director_GetInstances, new HttpParams()).subscribe();
195
+ return throwError(() => err);
196
+ }));
197
+ }
198
+ v2Call(call) {
199
+ return firstValueFrom(this.v2(call));
200
+ }
201
+ // Istanza che risponde alla chiamata, facoltativa: se il backend non la dà la pagina va avanti senza
202
+ async getInstanceId(be) {
203
+ var pars = new HttpParams().set("be", be ?? "");
204
+ let ret = await firstValueFrom(this.http.get(GET_Director_GetInstanceId, pars, null, false).pipe(catchError(_ => of(null))));
205
+ return ret;
206
+ }
207
+ // Testo di un errore delle chiamate v2: il backend-for-frontend manda il messaggio come stringa JSON (542 per i 400 del backend).
208
+ // Solo la prima riga, perché i 500 portano lo stack
209
+ errorText(err) {
210
+ let message = typeof err?.error === "string" ? err.error : err?.error?.message ?? err?.message ?? "";
211
+ return String(message).split(/\r?\n/)[0];
212
+ }
163
213
  async getInfos(be) {
164
214
  var pars = new HttpParams().set("be", be ?? "");
165
215
  let ret = await firstValueFrom(this.http.get(GET_Director_GetInfos, pars, null, false).pipe(catchError(_ => of(null))));
@@ -184,6 +234,45 @@ class AdminModuleBaseService {
184
234
  let pars = new HttpParams().set("from", from ?? "").set("to", to ?? "").set("be", be ?? "").set("global", global);
185
235
  this.http.getFileNative(GET_Director_GetWorkloadReport, pars);
186
236
  }
237
+ // Stesso URL di downloadWorkloadReport: con DirectorFeature.WorkloadReportV2 attivo il backend-for-frontend non manda più un file
238
+ // ma l'HTML del report come stringa JSON. Restituisce l'Observable perché la generazione dura qualche secondo e si può annullare
239
+ getWorkloadReportV2(be, req) {
240
+ let pars = new HttpParams().set("be", be ?? "");
241
+ if (req.snapFrom != null)
242
+ pars = pars.set("snapFrom", req.snapFrom).set("snapTo", req.snapTo);
243
+ if (req.global != null)
244
+ pars = pars.set("global", req.global);
245
+ for (let id of req.instanceIds ?? [])
246
+ pars = pars.append("instanceIds", id);
247
+ return this.v2(this.http.get(GET_Director_GetWorkloadReport, pars, null, false));
248
+ }
249
+ // Stesso URL di getMetricsList: con DirectorFeature.MetricsV2 attivo il backend-for-frontend risponde con il catalogo (nome, tipo, unità, istanze)
250
+ async getMetricsCatalogV2(be, req) {
251
+ let pars = new HttpParams().set("be", be ?? "");
252
+ if (req.global != null)
253
+ pars = pars.set("global", req.global);
254
+ for (let id of req.instanceIds ?? [])
255
+ pars = pars.append("instanceIds", id);
256
+ let ret = await this.v2Call(this.http.get(GET_Director_GetMetrics, pars, null, false));
257
+ return ret;
258
+ }
259
+ // Stesso URL di getMetrics: con DirectorFeature.MetricsV2 attivo il backend-for-frontend risponde con i campioni del backend così come sono
260
+ async getMetricsV2(be, req) {
261
+ var pars = new HttpParams().set("be", be ?? "");
262
+ let ret = await this.v2Call(this.http.post(POST_Director_GetMetrics, req, pars, "", false));
263
+ return ret;
264
+ }
265
+ async getWorkloadSnapshots(be, req) {
266
+ let pars = new HttpParams().set("be", be ?? "");
267
+ if (req.from)
268
+ pars = pars.set("from", req.from);
269
+ if (req.to)
270
+ pars = pars.set("to", req.to);
271
+ for (let id of req.instanceIds ?? [])
272
+ pars = pars.append("instanceIds", id);
273
+ let ret = await this.v2Call(this.http.get(GET_Director_GetSnapshots, pars, null, false));
274
+ return ret;
275
+ }
187
276
  async getMetrics(be, sv) {
188
277
  var pars = new HttpParams().set("be", be ?? "");
189
278
  let ret = await firstValueFrom(this.http.post(POST_Director_GetMetrics, sv, pars));
@@ -753,14 +842,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
753
842
 
754
843
  // Angular
755
844
  class AdmProfilerComponent {
756
- constructor(lc, localStorage, location, intercom, route, adms, mod) {
845
+ constructor(lc, localStorage, location, intercom, route, adms, router, mod, workloadV2) {
757
846
  this.lc = lc;
758
847
  this.localStorage = localStorage;
759
848
  this.location = location;
760
849
  this.intercom = intercom;
761
850
  this.route = route;
762
851
  this.adms = adms;
852
+ this.router = router;
763
853
  this.mod = mod;
854
+ this.workloadV2 = workloadV2;
764
855
  this.searchView = new ProfilerDataNodesView();
765
856
  this.be = route.snapshot.params["be"] || "";
766
857
  this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
@@ -805,7 +896,12 @@ class AdmProfilerComponent {
805
896
  this.searchView.page = 1;
806
897
  this.POSTGetItems(this.searchView);
807
898
  }
899
+ // Con DirectorFeature.WorkloadReportV2 lo stesso URL non dà più un file: il report ha la sua pagina
808
900
  downloadWorkloadReport() {
901
+ if (this.workloadV2) {
902
+ this.router.navigate([`/pages/${this.mod}/workload`, ...(this.be ? [this.be] : [])]);
903
+ return;
904
+ }
809
905
  this.adms.downloadWorkloadReport(this.be, this.searchView.filterfrom, this.searchView.filterto, this.searchView.filterglobal);
810
906
  }
811
907
  async POSTGetItems(data) {
@@ -826,219 +922,3090 @@ class AdmProfilerComponent {
826
922
  this.NodeDetails = await this.adms.getNodeStats(this.be, nodeId);
827
923
  });
828
924
  }
829
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerComponent, deps: [{ token: i1.LocalizationService }, { token: i3.PreferencesService }, { token: i5.Location }, { token: i1$1.InterComService }, { token: i4.ActivatedRoute }, { token: AdminModuleBaseService }, { token: ADM_MODULE_PATH }], target: i0.ɵɵFactoryTarget.Component }); }
830
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: AdmProfilerComponent, isStandalone: false, selector: "adm_profiler", providers: [{ provide: LocalizationService, useClass: AdmProfilerComponentLoc }], ngImport: i0, template: "<div class=\"row\">\n <div class=\"col-12\">\n <div class=\"card app-overflow-visible\" widget>\n <div class=\"card-header\">\n <span class=\"text-uppercase\">{{'Profiler del Server' | localize: lc}}</span>&nbsp;\n </div>\n <div class=\"card-body widget-body\">\n <div class=\"col-md-12 bottom-15\">\n <form role=\"form\">\n <div class=\"row app-first-filter-line\">\n <div class=\"col-xl-2 col-lg-2 col-md-12 app-filter-wrapper\">\n <form-date [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"searchView.filterfrom\" name=\"filterfrom\" [Placeholder]=\"'Da' | localize: lc\"></form-date>\n </div>\n <div class=\"col-xl-2 col-lg-2 col-md-12 app-filter-wrapper\">\n <form-date [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"searchView.filterto\" name=\"filterto\" [Placeholder]=\"'A' | localize: lc\"></form-date>\n </div>\n <div class=\"col-xl-2 col-lg-2 col-md-12 app-filter-wrapper\" style=\"display: flex; height: 38px;\">\n <form-checkbox [FormLayout]=\"false\" [SliderMode]=\"true\" [Validation]=\"false\" [(ngModel)]=\"searchView.filterglobal\" name=\"filterglobal\"></form-checkbox>&nbsp;\n <span class=\"app-pointer app-padding-top-5 app-padding-left-5\" (click)=\"searchView.filterglobal = !searchView.filterglobal\">{{'Globale' | localize: lc}}</span>\n </div>\n\n <button [disabled]=\"searchView.searchinprogress\" class=\"input-group-addon btn app-btn-search app-margin-top-20 app-margin-right-5\" (click)=\"sendSearch()\" type=\"submit\"><i class=\"fa fa-search\"></i></button>\n <button class=\"input-group-addon btn app-btn-search app-margin-top-20\" (click)=\"downloadWorkloadReport()\" type=\"submit\"><i class=\"fa fa-download\"></i></button>\n </div>\n </form>\n </div>\n <div class=\"row\">\n <div class=\"col-md-12\">\n @if (!searchView) {\n <p><em>{{'Carico i Dati' | localize: lc}}</em></p>\n }\n @if (searchView) {\n <div>\n <es-table [(ngModel)]=\"searchView\" [Hierarchy]=\"true\" [OrderByColumn]=\"false\" [ParentKey]=\"'parentid'\"\n [OwnKey]=\"'id'\" [StartsExpanded]=\"false\" [ExportFunction]=\"prepareExport.bind(this)\" [Export]=\"true\"\n [ExportFileName]=\"'Profiler Export.csv'\" [HidePaging]=\"true\" (onSearchRequest)=\"sendSearch($event);\">\n <span *th=\"'1'\">{{'ID' | localize : lc}}</span>\n <span *th=\"'2'\">{{'Descrizione' | localize: lc}}</span>\n <span *th=\"'3'\">{{'Chiamate' | localize: lc}}</span>\n <span *th=\"'4'\">{{'Tempo' | localize: lc}}</span>\n <span *th=\"'5'\">{{'Tempo Max' | localize: lc}}</span>\n <span *th=\"'6'\">{{'Tempo Min' | localize: lc}}</span>\n <span *th=\"'7'\">{{'Tempo Medio' | localize: lc}}</span>\n <span *th=\"'8'\">{{'Tempo Specifico' | localize: lc}}</span>\n <span *th=\"'9'\">{{'Percentuale' | localize: lc}}</span>\n <span *th=\"'10'\">{{'Chiamate Periodo' | localize: lc}}</span>\n <span *th=\"'11'\">{{'Tempo Periodo' | localize: lc}}</span>\n <span *th=\"'12'\">{{'Tempo Max Periodo' | localize: lc}}</span>\n <span *th=\"'13'\">{{'Tempo Min Periodo' | localize: lc}}</span>\n <span *th=\"'14'\">{{'Tempo Medio Periodo' | localize: lc}}</span>\n <span *th=\"'15'\">{{'Tempo Specifico Periodo' | localize: lc}}</span>\n <span *th=\"'16'\">{{'Percentuale Periodo' | localize: lc}}</span>\n <span *th=\"'17'\">{{'Nome Tipologia Evento' | localize: lc}}</span>\n <span *th=\"'18'\">{{'Nome Classe Attesa' | localize: lc}}</span>\n <span *th=\"'19'\">{{'Numero Istanze' | localize: lc}}</span>\n <span *td=\"'1'; let item\"><span class=\"app-link app-inline\" (click)=\"loadNodeDetails(item.id); dlgNodeDetails.show();\">{{item.id}}</span></span>\n <span *td=\"'2'; let item\" title=\"{{item.description}}\">{{item.description.length <= 50 ? item.description : item.description.substr(0,50) + \"[...]\" }}</span>\n <span *td=\"'3'; let item\">{{item.calls}}</span>\n <span *td=\"'4'; let item\">{{item.time}}</span>\n <span *td=\"'5'; let item\">{{item.peaktime}}</span>\n <span *td=\"'6'; let item\">{{item.mintime}}</span>\n <span *td=\"'7'; let item\">{{item.avgtime}}</span>\n <span *td=\"'8'; let item\">{{item.timewithoutchildren }}</span>\n <span *td=\"'9'; let item\">{{item.percentage}}</span>\n <span *td=\"'10' let item\">{{item.snapdeltacalls}}</span>\n <span *td=\"'11' let item\">{{item.snapdeltatime}}</span>\n <span *td=\"'12' let item\">{{item.snappeaktime}}</span>\n <span *td=\"'13' let item\">{{item.snapmintime}}</span>\n <span *td=\"'14' let item\">{{item.snapavgtime}}</span>\n <span *td=\"'15' let item\">{{item.snaptimewithoutchildren}}</span>\n <span *td=\"'16' let item\">{{item.snappercentage}}</span>\n <span *td=\"'17' let item\">{{item.eventtypename}}</span>\n <span *td=\"'18' let item\">{{item.waitclassname}}</span>\n <span *td=\"'19' let item\">{{item.numinstances}}</span>\n </es-table>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n\n<div bsModal #dlgNodeDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\n @if (NodeDetails) {\n <node-detail [NodeDetails]=\"NodeDetails\" (onClose)=\"dlgNodeDetails.hide()\"></node-detail>\n }\n</div>", dependencies: [{ kind: "directive", type: i7.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i7.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i7.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i8$1.ModalDirective, selector: "[bsModal]", inputs: ["config", "closeInterceptor"], outputs: ["onShow", "onShown", "onHide", "onHidden"], exportAs: ["bs-modal"] }, { kind: "directive", type: i8.EsTdDirective, selector: "[td]", inputs: ["tdVisible", "td", "tdForProperty", "tdIf", "tdClass", "tdPropertyAccessor", "tdAlignment", "tdFormat", "tdIconClass", "tdConditionField", "tdConditionValue", "tdTitle", "tdContent"] }, { kind: "directive", type: i8.EsThDirective, selector: "[th]", inputs: ["th", "thPinned", "thPinnedRight", "thPinnable", "thPinnedWidth", "thVisible", "thFixed", "thParent", "thOrder", "thOrderable", "thWrap", "thResizable", "thClass", "thShrink", "thGroupClass", "thIf", "thEmpty", "thGroup", "thAggregation", "thProperty", "thType", "thSource", "thMulti", "thStaticContent", "thBackgroundColor", "thAlignment", "thFormat", "thEditorName", "thEditorOptions", "thRendererName", "thRoutePath", "thRouteParameterProperties"] }, { kind: "component", type: i8.EsTableComponent, selector: "es-table", inputs: ["globalCheck", "autoUpdate", "seconds", "researchInProgress", "globalColVisCheck", "EsThTdProvider", "CornerMenuOptions", "ColumnsResizable", "ColumnsPinnable", "AutoUpdate", "Height", "EmptySpaceBackgroundColor", "EsTableHandledSearch", "MaxHeight", "ContainerClass", "XLSXExport", "CSVExport", "AllSearch", "HighCellDensity", "Selection", "ShowLoadingOnBootstrap", "Removal", "RemovalCondition", "Export", "ShiftClick", "CountLabel", "HidePaging", "HidePagingCount", "HidePagingButtons", "ContextMenu", "ExportFileName", "TableClass", "ExportFunction", "ExportOnlyVisibleColumns", "HasHeaderGroup", "HasSecondaryHeaderGroup", "HeaderHidden", "BodyHidden", "SelectionDisabled", "SingleSelection", "RowClassAssigner", "OrderByColumn", "MultipleOrderingDirectives", "HiddenColumns", "ColumnsOrdering", "SearchView", "SelectAll", "UseArrayModePaging", "DynamicRowColumnsDefinition", "DynamicOperations", "Hierarchy", "ParentKey", "OwnKey", "AutoSortHierarchy", "StartsExpanded", "SavePreferences", "DefaultAlignment", "PagingStyle", "RowGroupingPagingStyle", "ItemSourceProperty", "UseSelectionCache", "ShowItemGroupsColumns", "Editable", "RangeSelection", "SearchThrottle", "ArraymodeItemsPerPage"], outputs: ["globalCheckChange", "autoUpdateChange", "secondsChange", "researchInProgressChange", "globalColVisCheckChange", "onOrderChanged", "onSearchRequest", "onSelectionChanged", "onRemoval", "onAbortRemoval", "onModelChange", "onOpenContextMenu", "onCornerAction", "onDynamicOperation"] }, { kind: "component", type: i9.EsFormDateComponent, selector: "form-date", inputs: ["InOutFormat", "FloatingLabel", "DateFormat"] }, { kind: "component", type: i9.EsFormCheckboxComponent, selector: "form-checkbox", inputs: ["SliderMode"] }, { kind: "directive", type: i10.Widget, selector: "[widget]" }, { kind: "component", type: NodeDetailComponent, selector: "node-detail", inputs: ["NodeDetails"], outputs: ["onClose"] }, { kind: "pipe", type: i1.LocalizePipe, name: "localize" }] }); }
925
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerComponent, deps: [{ token: i1.LocalizationService }, { token: i3.PreferencesService }, { token: i5.Location }, { token: i1$1.InterComService }, { token: i4.ActivatedRoute }, { token: AdminModuleBaseService }, { token: i4.Router }, { token: ADM_MODULE_PATH }, { token: ADM_WORKLOAD_V2, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
926
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: AdmProfilerComponent, isStandalone: false, selector: "adm_profiler", providers: [{ provide: LocalizationService, useClass: AdmProfilerComponentLoc }], ngImport: i0, template: "<div class=\"row\">\n <div class=\"col-12\">\n <div class=\"card app-overflow-visible\" widget>\n <div class=\"card-header\">\n <span class=\"text-uppercase\">{{'Profiler del Server' | localize: lc}}</span>&nbsp;\n </div>\n <div class=\"card-body widget-body\">\n <div class=\"col-md-12 bottom-15\">\n <form role=\"form\">\n <div class=\"row app-first-filter-line\">\n <div class=\"col-xl-2 col-lg-2 col-md-12 app-filter-wrapper\">\n <form-date [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"searchView.filterfrom\" name=\"filterfrom\" [Placeholder]=\"'Da' | localize: lc\"></form-date>\n </div>\n <div class=\"col-xl-2 col-lg-2 col-md-12 app-filter-wrapper\">\n <form-date [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"searchView.filterto\" name=\"filterto\" [Placeholder]=\"'A' | localize: lc\"></form-date>\n </div>\n <div class=\"col-xl-2 col-lg-2 col-md-12 app-filter-wrapper\" style=\"display: flex; height: 38px;\">\n <form-checkbox [FormLayout]=\"false\" [SliderMode]=\"true\" [Validation]=\"false\" [(ngModel)]=\"searchView.filterglobal\" name=\"filterglobal\"></form-checkbox>&nbsp;\n <span class=\"app-pointer app-padding-top-5 app-padding-left-5\" (click)=\"searchView.filterglobal = !searchView.filterglobal\">{{'Globale' | localize: lc}}</span>\n </div>\n\n <button [disabled]=\"searchView.searchinprogress\" class=\"input-group-addon btn app-btn-search app-margin-top-20 app-margin-right-5\" (click)=\"sendSearch()\" type=\"submit\"><i class=\"fa fa-search\"></i></button>\n <button class=\"input-group-addon btn app-btn-search app-margin-top-20\" (click)=\"downloadWorkloadReport()\" type=\"submit\" [title]=\"workloadV2 ? 'Workload report' : ''\"><i class=\"fa\" [class.fa-download]=\"!workloadV2\" [class.fa-file-text]=\"workloadV2\"></i></button>\n </div>\n </form>\n </div>\n <div class=\"row\">\n <div class=\"col-md-12\">\n @if (!searchView) {\n <p><em>{{'Carico i Dati' | localize: lc}}</em></p>\n }\n @if (searchView) {\n <div>\n <es-table [(ngModel)]=\"searchView\" [Hierarchy]=\"true\" [OrderByColumn]=\"false\" [ParentKey]=\"'parentid'\"\n [OwnKey]=\"'id'\" [StartsExpanded]=\"false\" [ExportFunction]=\"prepareExport.bind(this)\" [Export]=\"true\"\n [ExportFileName]=\"'Profiler Export.csv'\" [HidePaging]=\"true\" (onSearchRequest)=\"sendSearch($event);\">\n <span *th=\"'1'\">{{'ID' | localize : lc}}</span>\n <span *th=\"'2'\">{{'Descrizione' | localize: lc}}</span>\n <span *th=\"'3'\">{{'Chiamate' | localize: lc}}</span>\n <span *th=\"'4'\">{{'Tempo' | localize: lc}}</span>\n <span *th=\"'5'\">{{'Tempo Max' | localize: lc}}</span>\n <span *th=\"'6'\">{{'Tempo Min' | localize: lc}}</span>\n <span *th=\"'7'\">{{'Tempo Medio' | localize: lc}}</span>\n <span *th=\"'8'\">{{'Tempo Specifico' | localize: lc}}</span>\n <span *th=\"'9'\">{{'Percentuale' | localize: lc}}</span>\n <span *th=\"'10'\">{{'Chiamate Periodo' | localize: lc}}</span>\n <span *th=\"'11'\">{{'Tempo Periodo' | localize: lc}}</span>\n <span *th=\"'12'\">{{'Tempo Max Periodo' | localize: lc}}</span>\n <span *th=\"'13'\">{{'Tempo Min Periodo' | localize: lc}}</span>\n <span *th=\"'14'\">{{'Tempo Medio Periodo' | localize: lc}}</span>\n <span *th=\"'15'\">{{'Tempo Specifico Periodo' | localize: lc}}</span>\n <span *th=\"'16'\">{{'Percentuale Periodo' | localize: lc}}</span>\n <span *th=\"'17'\">{{'Nome Tipologia Evento' | localize: lc}}</span>\n <span *th=\"'18'\">{{'Nome Classe Attesa' | localize: lc}}</span>\n <span *th=\"'19'\">{{'Numero Istanze' | localize: lc}}</span>\n <span *td=\"'1'; let item\"><span class=\"app-link app-inline\" (click)=\"loadNodeDetails(item.id); dlgNodeDetails.show();\">{{item.id}}</span></span>\n <span *td=\"'2'; let item\" title=\"{{item.description}}\">{{item.description.length <= 50 ? item.description : item.description.substr(0,50) + \"[...]\" }}</span>\n <span *td=\"'3'; let item\">{{item.calls}}</span>\n <span *td=\"'4'; let item\">{{item.time}}</span>\n <span *td=\"'5'; let item\">{{item.peaktime}}</span>\n <span *td=\"'6'; let item\">{{item.mintime}}</span>\n <span *td=\"'7'; let item\">{{item.avgtime}}</span>\n <span *td=\"'8'; let item\">{{item.timewithoutchildren }}</span>\n <span *td=\"'9'; let item\">{{item.percentage}}</span>\n <span *td=\"'10' let item\">{{item.snapdeltacalls}}</span>\n <span *td=\"'11' let item\">{{item.snapdeltatime}}</span>\n <span *td=\"'12' let item\">{{item.snappeaktime}}</span>\n <span *td=\"'13' let item\">{{item.snapmintime}}</span>\n <span *td=\"'14' let item\">{{item.snapavgtime}}</span>\n <span *td=\"'15' let item\">{{item.snaptimewithoutchildren}}</span>\n <span *td=\"'16' let item\">{{item.snappercentage}}</span>\n <span *td=\"'17' let item\">{{item.eventtypename}}</span>\n <span *td=\"'18' let item\">{{item.waitclassname}}</span>\n <span *td=\"'19' let item\">{{item.numinstances}}</span>\n </es-table>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n\n<div bsModal #dlgNodeDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\n @if (NodeDetails) {\n <node-detail [NodeDetails]=\"NodeDetails\" (onClose)=\"dlgNodeDetails.hide()\"></node-detail>\n }\n</div>", dependencies: [{ kind: "directive", type: i7.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i7.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i7.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i8$1.ModalDirective, selector: "[bsModal]", inputs: ["config", "closeInterceptor"], outputs: ["onShow", "onShown", "onHide", "onHidden"], exportAs: ["bs-modal"] }, { kind: "directive", type: i8.EsTdDirective, selector: "[td]", inputs: ["tdVisible", "td", "tdForProperty", "tdIf", "tdClass", "tdPropertyAccessor", "tdAlignment", "tdFormat", "tdIconClass", "tdConditionField", "tdConditionValue", "tdTitle", "tdContent"] }, { kind: "directive", type: i8.EsThDirective, selector: "[th]", inputs: ["th", "thPinned", "thPinnedRight", "thPinnable", "thPinnedWidth", "thVisible", "thFixed", "thParent", "thOrder", "thOrderable", "thWrap", "thResizable", "thClass", "thShrink", "thGroupClass", "thIf", "thEmpty", "thGroup", "thAggregation", "thProperty", "thType", "thSource", "thMulti", "thStaticContent", "thBackgroundColor", "thAlignment", "thFormat", "thEditorName", "thEditorOptions", "thRendererName", "thRoutePath", "thRouteParameterProperties"] }, { kind: "component", type: i8.EsTableComponent, selector: "es-table", inputs: ["globalCheck", "autoUpdate", "seconds", "researchInProgress", "globalColVisCheck", "EsThTdProvider", "CornerMenuOptions", "ColumnsResizable", "ColumnsPinnable", "AutoUpdate", "Height", "EmptySpaceBackgroundColor", "EsTableHandledSearch", "MaxHeight", "ContainerClass", "XLSXExport", "CSVExport", "AllSearch", "HighCellDensity", "Selection", "ShowLoadingOnBootstrap", "Removal", "RemovalCondition", "Export", "ShiftClick", "CountLabel", "HidePaging", "HidePagingCount", "HidePagingButtons", "ContextMenu", "ExportFileName", "TableClass", "ExportFunction", "ExportOnlyVisibleColumns", "HasHeaderGroup", "HasSecondaryHeaderGroup", "HeaderHidden", "BodyHidden", "SelectionDisabled", "SingleSelection", "RowClassAssigner", "OrderByColumn", "MultipleOrderingDirectives", "HiddenColumns", "ColumnsOrdering", "SearchView", "SelectAll", "UseArrayModePaging", "DynamicRowColumnsDefinition", "DynamicOperations", "Hierarchy", "ParentKey", "OwnKey", "AutoSortHierarchy", "StartsExpanded", "SavePreferences", "DefaultAlignment", "PagingStyle", "RowGroupingPagingStyle", "ItemSourceProperty", "UseSelectionCache", "ShowItemGroupsColumns", "Editable", "RangeSelection", "SearchThrottle", "ArraymodeItemsPerPage"], outputs: ["globalCheckChange", "autoUpdateChange", "secondsChange", "researchInProgressChange", "globalColVisCheckChange", "onOrderChanged", "onSearchRequest", "onSelectionChanged", "onRemoval", "onAbortRemoval", "onModelChange", "onOpenContextMenu", "onCornerAction", "onDynamicOperation"] }, { kind: "component", type: i9.EsFormDateComponent, selector: "form-date", inputs: ["InOutFormat", "FloatingLabel", "DateFormat"] }, { kind: "component", type: i9.EsFormCheckboxComponent, selector: "form-checkbox", inputs: ["SliderMode"] }, { kind: "directive", type: i10.Widget, selector: "[widget]" }, { kind: "component", type: NodeDetailComponent, selector: "node-detail", inputs: ["NodeDetails"], outputs: ["onClose"] }, { kind: "pipe", type: i1.LocalizePipe, name: "localize" }] }); }
831
927
  }
832
928
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerComponent, decorators: [{
833
929
  type: Component,
834
- args: [{ selector: "adm_profiler", providers: [{ provide: LocalizationService, useClass: AdmProfilerComponentLoc }], standalone: false, template: "<div class=\"row\">\n <div class=\"col-12\">\n <div class=\"card app-overflow-visible\" widget>\n <div class=\"card-header\">\n <span class=\"text-uppercase\">{{'Profiler del Server' | localize: lc}}</span>&nbsp;\n </div>\n <div class=\"card-body widget-body\">\n <div class=\"col-md-12 bottom-15\">\n <form role=\"form\">\n <div class=\"row app-first-filter-line\">\n <div class=\"col-xl-2 col-lg-2 col-md-12 app-filter-wrapper\">\n <form-date [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"searchView.filterfrom\" name=\"filterfrom\" [Placeholder]=\"'Da' | localize: lc\"></form-date>\n </div>\n <div class=\"col-xl-2 col-lg-2 col-md-12 app-filter-wrapper\">\n <form-date [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"searchView.filterto\" name=\"filterto\" [Placeholder]=\"'A' | localize: lc\"></form-date>\n </div>\n <div class=\"col-xl-2 col-lg-2 col-md-12 app-filter-wrapper\" style=\"display: flex; height: 38px;\">\n <form-checkbox [FormLayout]=\"false\" [SliderMode]=\"true\" [Validation]=\"false\" [(ngModel)]=\"searchView.filterglobal\" name=\"filterglobal\"></form-checkbox>&nbsp;\n <span class=\"app-pointer app-padding-top-5 app-padding-left-5\" (click)=\"searchView.filterglobal = !searchView.filterglobal\">{{'Globale' | localize: lc}}</span>\n </div>\n\n <button [disabled]=\"searchView.searchinprogress\" class=\"input-group-addon btn app-btn-search app-margin-top-20 app-margin-right-5\" (click)=\"sendSearch()\" type=\"submit\"><i class=\"fa fa-search\"></i></button>\n <button class=\"input-group-addon btn app-btn-search app-margin-top-20\" (click)=\"downloadWorkloadReport()\" type=\"submit\"><i class=\"fa fa-download\"></i></button>\n </div>\n </form>\n </div>\n <div class=\"row\">\n <div class=\"col-md-12\">\n @if (!searchView) {\n <p><em>{{'Carico i Dati' | localize: lc}}</em></p>\n }\n @if (searchView) {\n <div>\n <es-table [(ngModel)]=\"searchView\" [Hierarchy]=\"true\" [OrderByColumn]=\"false\" [ParentKey]=\"'parentid'\"\n [OwnKey]=\"'id'\" [StartsExpanded]=\"false\" [ExportFunction]=\"prepareExport.bind(this)\" [Export]=\"true\"\n [ExportFileName]=\"'Profiler Export.csv'\" [HidePaging]=\"true\" (onSearchRequest)=\"sendSearch($event);\">\n <span *th=\"'1'\">{{'ID' | localize : lc}}</span>\n <span *th=\"'2'\">{{'Descrizione' | localize: lc}}</span>\n <span *th=\"'3'\">{{'Chiamate' | localize: lc}}</span>\n <span *th=\"'4'\">{{'Tempo' | localize: lc}}</span>\n <span *th=\"'5'\">{{'Tempo Max' | localize: lc}}</span>\n <span *th=\"'6'\">{{'Tempo Min' | localize: lc}}</span>\n <span *th=\"'7'\">{{'Tempo Medio' | localize: lc}}</span>\n <span *th=\"'8'\">{{'Tempo Specifico' | localize: lc}}</span>\n <span *th=\"'9'\">{{'Percentuale' | localize: lc}}</span>\n <span *th=\"'10'\">{{'Chiamate Periodo' | localize: lc}}</span>\n <span *th=\"'11'\">{{'Tempo Periodo' | localize: lc}}</span>\n <span *th=\"'12'\">{{'Tempo Max Periodo' | localize: lc}}</span>\n <span *th=\"'13'\">{{'Tempo Min Periodo' | localize: lc}}</span>\n <span *th=\"'14'\">{{'Tempo Medio Periodo' | localize: lc}}</span>\n <span *th=\"'15'\">{{'Tempo Specifico Periodo' | localize: lc}}</span>\n <span *th=\"'16'\">{{'Percentuale Periodo' | localize: lc}}</span>\n <span *th=\"'17'\">{{'Nome Tipologia Evento' | localize: lc}}</span>\n <span *th=\"'18'\">{{'Nome Classe Attesa' | localize: lc}}</span>\n <span *th=\"'19'\">{{'Numero Istanze' | localize: lc}}</span>\n <span *td=\"'1'; let item\"><span class=\"app-link app-inline\" (click)=\"loadNodeDetails(item.id); dlgNodeDetails.show();\">{{item.id}}</span></span>\n <span *td=\"'2'; let item\" title=\"{{item.description}}\">{{item.description.length <= 50 ? item.description : item.description.substr(0,50) + \"[...]\" }}</span>\n <span *td=\"'3'; let item\">{{item.calls}}</span>\n <span *td=\"'4'; let item\">{{item.time}}</span>\n <span *td=\"'5'; let item\">{{item.peaktime}}</span>\n <span *td=\"'6'; let item\">{{item.mintime}}</span>\n <span *td=\"'7'; let item\">{{item.avgtime}}</span>\n <span *td=\"'8'; let item\">{{item.timewithoutchildren }}</span>\n <span *td=\"'9'; let item\">{{item.percentage}}</span>\n <span *td=\"'10' let item\">{{item.snapdeltacalls}}</span>\n <span *td=\"'11' let item\">{{item.snapdeltatime}}</span>\n <span *td=\"'12' let item\">{{item.snappeaktime}}</span>\n <span *td=\"'13' let item\">{{item.snapmintime}}</span>\n <span *td=\"'14' let item\">{{item.snapavgtime}}</span>\n <span *td=\"'15' let item\">{{item.snaptimewithoutchildren}}</span>\n <span *td=\"'16' let item\">{{item.snappercentage}}</span>\n <span *td=\"'17' let item\">{{item.eventtypename}}</span>\n <span *td=\"'18' let item\">{{item.waitclassname}}</span>\n <span *td=\"'19' let item\">{{item.numinstances}}</span>\n </es-table>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n\n<div bsModal #dlgNodeDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\n @if (NodeDetails) {\n <node-detail [NodeDetails]=\"NodeDetails\" (onClose)=\"dlgNodeDetails.hide()\"></node-detail>\n }\n</div>" }]
835
- }], ctorParameters: () => [{ type: i1.LocalizationService }, { type: i3.PreferencesService }, { type: i5.Location }, { type: i1$1.InterComService }, { type: i4.ActivatedRoute }, { type: AdminModuleBaseService }, { type: undefined, decorators: [{
930
+ args: [{ selector: "adm_profiler", providers: [{ provide: LocalizationService, useClass: AdmProfilerComponentLoc }], standalone: false, template: "<div class=\"row\">\n <div class=\"col-12\">\n <div class=\"card app-overflow-visible\" widget>\n <div class=\"card-header\">\n <span class=\"text-uppercase\">{{'Profiler del Server' | localize: lc}}</span>&nbsp;\n </div>\n <div class=\"card-body widget-body\">\n <div class=\"col-md-12 bottom-15\">\n <form role=\"form\">\n <div class=\"row app-first-filter-line\">\n <div class=\"col-xl-2 col-lg-2 col-md-12 app-filter-wrapper\">\n <form-date [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"searchView.filterfrom\" name=\"filterfrom\" [Placeholder]=\"'Da' | localize: lc\"></form-date>\n </div>\n <div class=\"col-xl-2 col-lg-2 col-md-12 app-filter-wrapper\">\n <form-date [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"searchView.filterto\" name=\"filterto\" [Placeholder]=\"'A' | localize: lc\"></form-date>\n </div>\n <div class=\"col-xl-2 col-lg-2 col-md-12 app-filter-wrapper\" style=\"display: flex; height: 38px;\">\n <form-checkbox [FormLayout]=\"false\" [SliderMode]=\"true\" [Validation]=\"false\" [(ngModel)]=\"searchView.filterglobal\" name=\"filterglobal\"></form-checkbox>&nbsp;\n <span class=\"app-pointer app-padding-top-5 app-padding-left-5\" (click)=\"searchView.filterglobal = !searchView.filterglobal\">{{'Globale' | localize: lc}}</span>\n </div>\n\n <button [disabled]=\"searchView.searchinprogress\" class=\"input-group-addon btn app-btn-search app-margin-top-20 app-margin-right-5\" (click)=\"sendSearch()\" type=\"submit\"><i class=\"fa fa-search\"></i></button>\n <button class=\"input-group-addon btn app-btn-search app-margin-top-20\" (click)=\"downloadWorkloadReport()\" type=\"submit\" [title]=\"workloadV2 ? 'Workload report' : ''\"><i class=\"fa\" [class.fa-download]=\"!workloadV2\" [class.fa-file-text]=\"workloadV2\"></i></button>\n </div>\n </form>\n </div>\n <div class=\"row\">\n <div class=\"col-md-12\">\n @if (!searchView) {\n <p><em>{{'Carico i Dati' | localize: lc}}</em></p>\n }\n @if (searchView) {\n <div>\n <es-table [(ngModel)]=\"searchView\" [Hierarchy]=\"true\" [OrderByColumn]=\"false\" [ParentKey]=\"'parentid'\"\n [OwnKey]=\"'id'\" [StartsExpanded]=\"false\" [ExportFunction]=\"prepareExport.bind(this)\" [Export]=\"true\"\n [ExportFileName]=\"'Profiler Export.csv'\" [HidePaging]=\"true\" (onSearchRequest)=\"sendSearch($event);\">\n <span *th=\"'1'\">{{'ID' | localize : lc}}</span>\n <span *th=\"'2'\">{{'Descrizione' | localize: lc}}</span>\n <span *th=\"'3'\">{{'Chiamate' | localize: lc}}</span>\n <span *th=\"'4'\">{{'Tempo' | localize: lc}}</span>\n <span *th=\"'5'\">{{'Tempo Max' | localize: lc}}</span>\n <span *th=\"'6'\">{{'Tempo Min' | localize: lc}}</span>\n <span *th=\"'7'\">{{'Tempo Medio' | localize: lc}}</span>\n <span *th=\"'8'\">{{'Tempo Specifico' | localize: lc}}</span>\n <span *th=\"'9'\">{{'Percentuale' | localize: lc}}</span>\n <span *th=\"'10'\">{{'Chiamate Periodo' | localize: lc}}</span>\n <span *th=\"'11'\">{{'Tempo Periodo' | localize: lc}}</span>\n <span *th=\"'12'\">{{'Tempo Max Periodo' | localize: lc}}</span>\n <span *th=\"'13'\">{{'Tempo Min Periodo' | localize: lc}}</span>\n <span *th=\"'14'\">{{'Tempo Medio Periodo' | localize: lc}}</span>\n <span *th=\"'15'\">{{'Tempo Specifico Periodo' | localize: lc}}</span>\n <span *th=\"'16'\">{{'Percentuale Periodo' | localize: lc}}</span>\n <span *th=\"'17'\">{{'Nome Tipologia Evento' | localize: lc}}</span>\n <span *th=\"'18'\">{{'Nome Classe Attesa' | localize: lc}}</span>\n <span *th=\"'19'\">{{'Numero Istanze' | localize: lc}}</span>\n <span *td=\"'1'; let item\"><span class=\"app-link app-inline\" (click)=\"loadNodeDetails(item.id); dlgNodeDetails.show();\">{{item.id}}</span></span>\n <span *td=\"'2'; let item\" title=\"{{item.description}}\">{{item.description.length <= 50 ? item.description : item.description.substr(0,50) + \"[...]\" }}</span>\n <span *td=\"'3'; let item\">{{item.calls}}</span>\n <span *td=\"'4'; let item\">{{item.time}}</span>\n <span *td=\"'5'; let item\">{{item.peaktime}}</span>\n <span *td=\"'6'; let item\">{{item.mintime}}</span>\n <span *td=\"'7'; let item\">{{item.avgtime}}</span>\n <span *td=\"'8'; let item\">{{item.timewithoutchildren }}</span>\n <span *td=\"'9'; let item\">{{item.percentage}}</span>\n <span *td=\"'10' let item\">{{item.snapdeltacalls}}</span>\n <span *td=\"'11' let item\">{{item.snapdeltatime}}</span>\n <span *td=\"'12' let item\">{{item.snappeaktime}}</span>\n <span *td=\"'13' let item\">{{item.snapmintime}}</span>\n <span *td=\"'14' let item\">{{item.snapavgtime}}</span>\n <span *td=\"'15' let item\">{{item.snaptimewithoutchildren}}</span>\n <span *td=\"'16' let item\">{{item.snappercentage}}</span>\n <span *td=\"'17' let item\">{{item.eventtypename}}</span>\n <span *td=\"'18' let item\">{{item.waitclassname}}</span>\n <span *td=\"'19' let item\">{{item.numinstances}}</span>\n </es-table>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n\n<div bsModal #dlgNodeDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\n @if (NodeDetails) {\n <node-detail [NodeDetails]=\"NodeDetails\" (onClose)=\"dlgNodeDetails.hide()\"></node-detail>\n }\n</div>" }]
931
+ }], ctorParameters: () => [{ type: i1.LocalizationService }, { type: i3.PreferencesService }, { type: i5.Location }, { type: i1$1.InterComService }, { type: i4.ActivatedRoute }, { type: AdminModuleBaseService }, { type: i4.Router }, { type: undefined, decorators: [{
836
932
  type: Inject,
837
933
  args: [ADM_MODULE_PATH]
934
+ }] }, { type: undefined, decorators: [{
935
+ type: Optional
936
+ }, {
937
+ type: Inject,
938
+ args: [ADM_WORKLOAD_V2]
838
939
  }] }] });
839
940
 
840
- class TopActivitiesDataView extends AppSearch {
841
- constructor() { super(); }
941
+ // Ordinamento decrescente del profiler v2 (figli nell'albero, righe nella vista per nome). I numeri sono quelli del backend
942
+ var ProfilerOrderBy;
943
+ (function (ProfilerOrderBy) {
944
+ ProfilerOrderBy[ProfilerOrderBy["Time"] = 0] = "Time";
945
+ ProfilerOrderBy[ProfilerOrderBy["TimeLessChildren"] = 1] = "TimeLessChildren";
946
+ ProfilerOrderBy[ProfilerOrderBy["Calls"] = 2] = "Calls";
947
+ ProfilerOrderBy[ProfilerOrderBy["AvgTime"] = 3] = "AvgTime";
948
+ ProfilerOrderBy[ProfilerOrderBy["PeakTime"] = 4] = "PeakTime";
949
+ ProfilerOrderBy[ProfilerOrderBy["SnapDeltaTime"] = 5] = "SnapDeltaTime";
950
+ ProfilerOrderBy[ProfilerOrderBy["SnapDeltaTimeLessChildren"] = 6] = "SnapDeltaTimeLessChildren";
951
+ ProfilerOrderBy[ProfilerOrderBy["SnapDeltaCalls"] = 7] = "SnapDeltaCalls";
952
+ ProfilerOrderBy[ProfilerOrderBy["ActiveThreads"] = 8] = "ActiveThreads";
953
+ })(ProfilerOrderBy || (ProfilerOrderBy = {}));
954
+ // Parametri comuni del profiler v2 (specifica "Profiler gerarchico"): date in ISO UTC (…Z), campi non valorizzati = default del backend.
955
+ // Il backend-for-frontend legge i nomi senza badare a maiuscole e minuscole e li inoltra in query string
956
+ class ProfilerV2Request {
957
+ }
958
+ // Albero: con search il backend ignora maxDepth, maxChildren e minPercentage
959
+ class ProfilerV2TreeRequest extends ProfilerV2Request {
960
+ }
961
+ class ProfilerV2NodeRequest extends ProfilerV2Request {
962
+ }
963
+ // Eventi per nome: un evento per riga sommato su tutti i percorsi
964
+ class ProfilerV2FlatRequest extends ProfilerV2Request {
842
965
  }
843
966
 
844
- class AdmTopActivitieLoc extends LocalizationService {
967
+ // Angular
968
+ // Formati del profiler v2 (specifica "Profiler gerarchico"): durate nell'unità più leggibile, interi con il separatore
969
+ // delle migliaia italiano, percentuali con un decimale sotto 10. Un dato mancante è "—"
970
+ class ProfilerFormatPipe {
971
+ transform(value, kind) {
972
+ if (value == null || isNaN(value))
973
+ return "—";
974
+ switch (kind) {
975
+ case "dur": return formatDuration(value);
976
+ case "pct": return formatPercentage(value);
977
+ default: return formatNumber(value, "it", "1.0-0");
978
+ }
979
+ }
980
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: ProfilerFormatPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
981
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.34", ngImport: i0, type: ProfilerFormatPipe, isStandalone: true, name: "pv2fmt" }); }
982
+ }
983
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: ProfilerFormatPipe, decorators: [{
984
+ type: Pipe,
985
+ args: [{ name: "pv2fmt" }]
986
+ }] });
987
+ // Secondi: "850 µs", "4,27 ms", "312,5 ms", "8,41 s", "4 min 12 s", "3 h 20 min", "76 h"
988
+ function formatDuration(seconds) {
989
+ if (seconds == null || isNaN(seconds))
990
+ return "—";
991
+ let abs = Math.abs(seconds);
992
+ if (abs < 0.001)
993
+ return `${formatNumber(seconds * 1000000, "it", "1.0-0")} µs`;
994
+ if (abs < 1)
995
+ return `${formatNumber(seconds * 1000, "it", abs < 0.01 ? "1.2-2" : "1.1-1")} ms`;
996
+ if (abs < 60)
997
+ return `${formatNumber(seconds, "it", abs < 10 ? "1.2-2" : "1.1-1")} s`;
998
+ if (abs < 3600)
999
+ return `${Math.floor(abs / 60)} min ${Math.floor(abs % 60)} s`;
1000
+ if (abs < 48 * 3600)
1001
+ return `${Math.floor(abs / 3600)} h ${Math.floor(abs % 3600 / 60)} min`;
1002
+ return `${formatNumber(Math.floor(abs / 3600), "it", "1.0-0")} h`;
1003
+ }
1004
+ // Da 0 a 100: "4,2%", "34%"
1005
+ function formatPercentage(value) {
1006
+ if (value == null || isNaN(value))
1007
+ return "—";
1008
+ return `${formatNumber(value, "it", Math.abs(value) < 10 ? "1.1-1" : "1.0-0")}%`;
1009
+ }
1010
+
1011
+ class AdmProfilerV2Loc extends LocalizationService {
845
1012
  constructor(injector) {
846
1013
  super(injector);
847
- super.set("it->en", "Sessioni Attive", ["Active Sessions"]);
848
- super.set("it->en", "Prime Istruzioni", ["Top Nodes"]);
849
- super.set("it->en", "Prime Sessioni", ["Top Sessions"]);
850
- super.set("it->en", "Carico i Dati", ["Loading Data"]);
851
- super.set("it->en", "Attività Principali", ["Top Activities"]);
852
- super.set("it->en", "Descrizione Nodo", ["Node Description"]);
1014
+ super.set("it->en", "Profiler del Server", ["Server Profiler"]);
1015
+ super.set("it->en", "Errore", ["Error"]);
1016
+ // Barra dei comandi e riga della finestra
1017
+ super.set("it->en", "Sorgente", ["Source"]);
1018
+ super.set("it->en", "Storico", ["History"]);
1019
+ super.set("it->en", "Istanze", ["Instances"]);
1020
+ super.set("it->en", "Questa istanza", ["This instance"]);
1021
+ super.set("it->en", "Tutto il cluster", ["Whole cluster"]);
1022
+ super.set("it->en", "istanze", ["instances"]);
1023
+ super.set("it->en", "istanza", ["instance"]);
1024
+ super.set("it->en", "Solo", ["Only"]);
1025
+ super.set("it->en", "Dal", ["From"]);
1026
+ super.set("it->en", "Al", ["To"]);
1027
+ super.set("it->en", "Valori", ["Values"]);
1028
+ super.set("it->en", "Dall'avvio", ["Since start"]);
1029
+ super.set("it->en", "Dall'ultimo snapshot", ["Since last snapshot"]);
1030
+ super.set("it->en", "Aggiorna", ["Refresh"]);
1031
+ super.set("it->en", "Scarica il workload report", ["Download the workload report"]);
1032
+ super.set("it->en", "Workload report", ["Workload report"]);
1033
+ super.set("it->en", "Delta dall'ultimo snapshot delle", ["Delta since the last snapshot at"]);
1034
+ super.set("it->en", "min fa", ["min ago"]);
1035
+ super.set("it->en", "s fa", ["s ago"]);
1036
+ super.set("it->en", "mai", ["never"]);
1037
+ super.set("it->en", "Snapshot dalle", ["Snapshots from"]);
1038
+ super.set("it->en", "alle", ["to"]);
1039
+ super.set("it->en", "Nessuno snapshot termina nella finestra scelta", ["No snapshot ends in the chosen window"]);
1040
+ super.set("it->en", "nodi nell'albero", ["nodes in the tree"]);
1041
+ super.set("it->en", "nella risposta", ["in the response"]);
1042
+ super.set("it->en", "eventi distinti", ["distinct events"]);
1043
+ super.set("it->en", "Caricamento…", ["Loading…"]);
1044
+ // Vista e ordinamenti
1045
+ super.set("it->en", "Albero", ["Tree"]);
1046
+ super.set("it->en", "Per nome", ["By name"]);
1047
+ super.set("it->en", "Ordina per", ["Sort by"]);
1048
+ super.set("it->en", "Tempo", ["Time"]);
1049
+ super.set("it->en", "Tempo proprio", ["Self time"]);
1050
+ super.set("it->en", "Chiamate", ["Calls"]);
1051
+ super.set("it->en", "Tempo medio", ["Average time"]);
1052
+ super.set("it->en", "Picco", ["Peak"]);
1053
+ super.set("it->en", "Δ Tempo", ["Δ Time"]);
1054
+ super.set("it->en", "Δ Tempo proprio", ["Δ Self time"]);
1055
+ super.set("it->en", "Δ Chiamate", ["Δ Calls"]);
1056
+ super.set("it->en", "Thread attivi", ["Active threads"]);
1057
+ super.set("it->en", "Tutti", ["All"]);
1058
+ super.set("it->en", "Nessuna", ["None"]);
1059
+ // Albero
1060
+ super.set("it->en", "Cerca nei nomi o per id", ["Search names or by id"]);
1061
+ super.set("it->en", "Per esempio MRD_METERREADINGS, ENQUEUE, ProfilesMgr", ["For example MRD_METERREADINGS, ENQUEUE, ProfilesMgr"]);
1062
+ super.set("it->en", "Figli per nodo", ["Children per node"]);
1063
+ super.set("it->en", "Soglia", ["Threshold"]);
1064
+ super.set("it->en", "Chiudi", ["Close"]);
1065
+ super.set("it->en", "Il nodo non c'è nelle istanze scelte: l'albero riparte da Idle.", ["The node is not in the chosen instances: the tree starts again from Idle."]);
1066
+ super.set("it->en", "nodi trovati, con i loro antenati.", ["nodes found, with their ancestors."]);
1067
+ super.set("it->en", "I nodi trovati sono più di 1.000: sono mostrati i primi per l'ordinamento scelto.", ["More than 1,000 nodes found: the first ones by the chosen sort order are shown."]);
1068
+ super.set("it->en", "Nessun nodo contiene", ["No node contains"]);
853
1069
  super.set("it->en", "Nodo", ["Node"]);
854
- super.set("it->en", "Attività", ["Activity"]);
855
- super.set("it->en", "Limite", ["Limit"]);
856
- super.set("it->en", "Tipo Nodo", ["Node Type"]);
857
- super.set("it->en", "Utente", ["User"]);
858
- super.set("it->en", "Tenant", ["Tenant"]);
859
- super.set("it->en", "Processo", ["Process"]);
860
- super.set("it->en", "Aggiorna ogni", ["Update every"]);
861
- super.set("it->en", "secondo/i", ["second/s"]);
862
- super.set("it->en", "Thread Massimi", ["Maximum Threads"]);
863
- super.set("it->en", "CPU", ["CPUs"]);
864
- super.set("it->en", "Classe Attesa", ["Wait Class Name"]);
865
- super.set("it->en", "Seriale", ["Serial"]);
866
- super.set("it->en", "Stato", ["Status"]);
1070
+ super.set("it->en", "% del padre", ["% of parent"]);
1071
+ super.set("it->en", "Δ Proprio", ["Δ Self"]);
1072
+ super.set("it->en", "Medio", ["Average"]);
1073
+ super.set("it->en", "Thread ora", ["Threads now"]);
1074
+ super.set("it->en", "del padre", ["of parent"]);
1075
+ super.set("it->en", "del nodo di primo livello", ["of the first-level node"]);
1076
+ super.set("it->en", "Esecuzione in corso da", ["Execution running for"]);
1077
+ super.set("it->en", "forse bloccata", ["possibly stuck"]);
1078
+ super.set("it->en", "Caricamento dei figli…", ["Loading children…"]);
1079
+ super.set("it->en", "figli non mostrati", ["children not shown"]);
1080
+ super.set("it->en", "chiamate", ["calls"]);
1081
+ super.set("it->en", "Mostra tutti", ["Show all"]);
1082
+ super.set("it->en", "Nessuno snapshot termina nella finestra scelta: allargala o spostala indietro.", ["No snapshot ends in the chosen window: widen it or move it back."]);
1083
+ super.set("it->en", "Il nodo non c'è nelle istanze scelte.", ["The node is not in the chosen instances."]);
1084
+ // Eventi per nome
1085
+ super.set("it->en", "Cerca nei nomi", ["Search names"]);
867
1086
  super.set("it->en", "Tipo", ["Type"]);
868
- super.set("it->en", "Utente", ["User"]);
1087
+ super.set("it->en", "Righe", ["Rows"]);
1088
+ super.set("it->en", "Anche gli eventi idle", ["Include idle events"]);
1089
+ super.set("it->en", "Evento", ["Event"]);
1090
+ super.set("it->en", "Percorsi", ["Paths"]);
1091
+ super.set("it->en", "% proprio", ["% self"]);
1092
+ super.set("it->en", "Picco dall'avvio", ["Peak since start"]);
1093
+ super.set("it->en", "Thread", ["Threads"]);
1094
+ super.set("it->en", "Nodi con più tempo", ["Nodes with the most time"]);
1095
+ super.set("it->en", "Dettaglio", ["Details"]);
1096
+ super.set("it->en", "Apri nell'albero", ["Open in tree"]);
1097
+ super.set("it->en", "altri", ["more"]);
1098
+ super.set("it->en", "Nessun evento con questi filtri.", ["No events match these filters."]);
1099
+ super.set("it->en", "mostrati i primi", ["showing the first"]);
1100
+ super.set("it->en", "La percentuale è sul tempo proprio di tutti gli eventi non idle.", ["The percentage is of the self time of all non-idle events."]);
1101
+ // Dettaglio
1102
+ super.set("it->en", "Seleziona un nodo per vederne il dettaglio.", ["Select a node to see its details."]);
1103
+ super.set("it->en", "Il nodo non c'è nelle istanze o nella finestra scelte.", ["The node is not in the chosen instances or window."]);
1104
+ super.set("it->en", "Copia", ["Copy"]);
1105
+ super.set("it->en", "Copiato", ["Copied"]);
1106
+ super.set("it->en", "Percorso", ["Path"]);
1107
+ super.set("it->en", "Nella finestra", ["In window"]);
1108
+ super.set("it->en", "Esecuzioni", ["Executions"]);
1109
+ super.set("it->en", "Tempo totale", ["Total time"]);
1110
+ super.set("it->en", "Minimo", ["Minimum"]);
1111
+ super.set("it->en", "Adesso", ["Now"]);
1112
+ super.set("it->en", "Thread dentro", ["Threads inside"]);
1113
+ super.set("it->en", "Esecuzione più lunga", ["Longest execution"]);
1114
+ super.set("it->en", "Ultima uscita", ["Last exit"]);
1115
+ super.set("it->en", "Tempo proprio del sottoalbero per tipo", ["Subtree self time by type"]);
1116
+ super.set("it->en", "Sessioni attive sul nodo e sui discendenti", ["Active sessions on node and descendants"]);
1117
+ super.set("it->en", "Media", ["Average"]);
1118
+ super.set("it->en", "picco", ["peak"]);
1119
+ super.set("it->en", "sessioni", ["sessions"]);
1120
+ super.set("it->en", "punti dell'API", ["API points"]);
1121
+ super.set("it->en", "uno al secondo", ["one per second"]);
1122
+ super.set("it->en", "uno ogni", ["one every"]);
1123
+ super.set("it->en", "raggruppati a", ["grouped by"]);
1124
+ super.set("it->en", "Nessun campione in memoria per questa finestra: il grafico copre solo le ultime due ore circa.", ["No in-memory samples for this window: the chart only covers about the last two hours."]);
1125
+ super.set("it->en", "Per istanza", ["By instance"]);
1126
+ super.set("it->en", "Istanza", ["Instance"]);
1127
+ super.set("it->en", "Proprio", ["Self"]);
1128
+ super.set("it->en", "figli diretti", ["direct children"]);
1129
+ super.set("it->en", "discendenti", ["descendants"]);
1130
+ super.set("it->en", "Apri come radice", ["Open as root"]);
869
1131
  }
870
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivitieLoc, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
871
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivitieLoc }); }
1132
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerV2Loc, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
1133
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerV2Loc }); }
872
1134
  }
873
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivitieLoc, decorators: [{
1135
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerV2Loc, decorators: [{
874
1136
  type: Injectable
875
1137
  }], ctorParameters: () => [{ type: i0.Injector }] });
876
1138
 
877
1139
  // Angular
878
- class AdmTopActivitiesComponent {
879
- constructor(dateExts, lc, localStorage, location, intercom, route, adms, mod) {
1140
+ const STORAGE_KEY$3 = "HotSearchProfilerV2";
1141
+ const INSTANCE_THIS = "@this";
1142
+ const INSTANCE_ALL$1 = "@all";
1143
+ // All'apertura la radice con due livelli (R6), poi i figli si chiedono un livello alla volta
1144
+ const TREE_DEPTH = 2;
1145
+ const SEARCH_MIN_CHARS = 2;
1146
+ const SEARCH_DELAY_MS = 400;
1147
+ const DATES_DELAY_MS$1 = 600;
1148
+ // Muovendosi con la tastiera il dettaglio si chiede dopo una breve pausa, non a ogni tasto
1149
+ const DETAIL_DELAY_MS = 180;
1150
+ // Esecuzione in corso da più di così: forse bloccata (R15)
1151
+ const LONG_RUNNING_SECONDS = 60;
1152
+ const CHART_POINTS = 160;
1153
+ // Ogni tipo di evento ha un colore fisso, gli stessi della Top Activity v2. Wait e sleep di classe Idle usano quello di Idle
1154
+ const TYPE_COLORS = {
1155
+ Idle: "#bdbdbd", Cpu: "#43a047", Database: "#1e88e5", Wait: "#fdd835", Script: "#8e24aa", Network: "#8d6e63", IO: "#00acc1", Sleep: "#90a4ae", Throttle: "#fb8c00"
1156
+ };
1157
+ const TYPE_LABELS = {
1158
+ Idle: "Idle", Cpu: "CPU", Database: "DB", Wait: "Wait", Script: "Script", Network: "Net", IO: "I/O", Sleep: "Sleep", Throttle: "Throttle"
1159
+ };
1160
+ const EVENT_TYPES = ["Cpu", "Database", "Wait", "Script", "Network", "IO", "Sleep", "Throttle"];
1161
+ // Passando ai valori dall'ultimo snapshot l'ordinamento passa alla chiave corrispondente, e viceversa (R4)
1162
+ const TO_SNAP = {
1163
+ [ProfilerOrderBy.Time]: ProfilerOrderBy.SnapDeltaTime,
1164
+ [ProfilerOrderBy.TimeLessChildren]: ProfilerOrderBy.SnapDeltaTimeLessChildren,
1165
+ [ProfilerOrderBy.Calls]: ProfilerOrderBy.SnapDeltaCalls
1166
+ };
1167
+ const TO_BASE = {
1168
+ [ProfilerOrderBy.SnapDeltaTime]: ProfilerOrderBy.Time,
1169
+ [ProfilerOrderBy.SnapDeltaTimeLessChildren]: ProfilerOrderBy.TimeLessChildren,
1170
+ [ProfilerOrderBy.SnapDeltaCalls]: ProfilerOrderBy.Calls
1171
+ };
1172
+ class ProfilerV2Row {
1173
+ }
1174
+ class ProfilerV2TypePart {
1175
+ }
1176
+ class ProfilerV2Chart {
1177
+ }
1178
+ // Preferenze ricordate fra una visita e l'altra (sorgente, istanze, finestra e radice ripartono dai default)
1179
+ class ProfilerV2Settings {
1180
+ }
1181
+ class AdmProfilerV2Component {
1182
+ constructor(dateExts, lc, localStorage, intercom, route, router, location, adms, mod, workloadV2) {
880
1183
  this.dateExts = dateExts;
881
1184
  this.lc = lc;
882
1185
  this.localStorage = localStorage;
883
- this.location = location;
884
1186
  this.intercom = intercom;
885
1187
  this.route = route;
1188
+ this.router = router;
1189
+ this.location = location;
886
1190
  this.adms = adms;
887
1191
  this.mod = mod;
888
- this.searchView = new TopActivitiesDataView();
889
- this.autoUpdate = false;
890
- this.seconds = "5";
891
- this.rtWindow = null;
892
- this.destroyed$ = new ReplaySubject(1);
893
- this.seriesPropertiesMapping = {};
894
- this.propertiesSeriesMapping = {
895
- ["cputime"]: "CPU",
896
- ["waittime"]: "Wait",
897
- ["scripttime"]: "Scripting",
898
- ["dbtime"]: "DB",
899
- ["nettime"]: "Network",
900
- ["throttletime"]: "Throttling",
901
- ["sleeptime"]: "Sleep",
902
- ["iotime"]: "IO"
903
- };
904
- this.seriesPropertiesMapping2 = {};
1192
+ this.workloadV2 = workloadV2;
1193
+ this.EventTypes = EVENT_TYPES;
1194
+ // Barra dei comandi
1195
+ this.live = true;
1196
+ // Di default tutto il cluster, come nella Top Activity v2
1197
+ this.instance = INSTANCE_ALL$1;
1198
+ this.filterfrom = "";
1199
+ this.filterto = "";
1200
+ // Valori dall'ultimo snapshot invece che dall'avvio (solo live)
1201
+ this.snapValues = false;
1202
+ this.view = "tree";
1203
+ // Albero
1204
+ this.orderBy = String(ProfilerOrderBy.Time);
1205
+ // "0" = tutti, nessuna soglia
1206
+ this.maxChildren = "25";
1207
+ this.minPercentage = "0";
1208
+ this.searchText = "";
1209
+ this.activeSearch = "";
1210
+ this.rows = [];
1211
+ this.treeLoading = false;
1212
+ this.matchCount = 0;
1213
+ // Eventi per nome
1214
+ this.flatSearch = "";
1215
+ // "*" = tutti i tipi (con un id vuoto la select mostrerebbe il segnaposto invece di "Tutti")
1216
+ this.flatType = "*";
1217
+ this.flatOrderBy = String(ProfilerOrderBy.TimeLessChildren);
1218
+ this.flatTop = "100";
1219
+ this.flatIncludeIdle = false;
1220
+ this.flatLoading = false;
1221
+ this.openFlat = new Set();
1222
+ this.detailLoading = false;
1223
+ this.detailMissing = false;
1224
+ this.typeParts = [];
1225
+ this.copied = false;
1226
+ // Sorgenti di select e pulsanti
1227
+ this.Instances = [];
1228
+ this.TreeOrders = [];
1229
+ this.FlatOrders = [];
1230
+ this.TreeOrderOptions = [];
1231
+ this.FlatOrderOptions = [];
1232
+ this.ChildrenOptions = [];
1233
+ this.ThresholdOptions = [];
1234
+ this.FlatTypes = [];
1235
+ this.FlatRows = [];
1236
+ this.instanceIds = [];
1237
+ this.rootId = null;
1238
+ this.nodes = new Map();
1239
+ this.parents = new Map();
1240
+ this.expanded = new Set();
1241
+ // Nodi di cui children è completo per i parametri correnti, anche se vuoto (per esempio tutti sotto la soglia)
1242
+ this.loaded = new Set();
1243
+ this.loadingChildren = new Set();
1244
+ // Una risposta arrivata dopo un ricaricamento successivo si scarta
1245
+ this.treeGeneration = 0;
1246
+ this.flatGeneration = 0;
1247
+ this.detailGeneration = 0;
905
1248
  this.be = route.snapshot.params["be"] || "";
906
1249
  this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
907
1250
  }
908
- ngOnInit() {
909
- // Ogni minuto calcolo qual è la finestra del realTime
910
- // interval(60 * 1000).pipe(takeUntil(this.destroyed$)).subscribe(_ => {
911
- // this.updateRealTimeWindow();
912
- // });
913
- this.storageSub = this.route.queryParams.subscribe(params => {
914
- this.Init(params["ignoreStorage"]);
915
- });
1251
+ async ngOnInit() {
1252
+ this.buildSources();
1253
+ let saved = this.route.snapshot.queryParams["ignoreStorage"] ? null : await firstValueFrom(this.localStorage.getItem(STORAGE_KEY$3), { defaultValue: null });
1254
+ if (saved)
1255
+ this.applySettings(saved);
1256
+ // Gli id sono stabili: la radice può stare nell'indirizzo della pagina
1257
+ this.rootId = this.route.snapshot.queryParams["root"] || null;
1258
+ this.loadInstances();
1259
+ this.loadTree(false);
916
1260
  }
917
- async updateRealTimeWindow() {
918
- let data = new TopActivitiesDataView();
919
- data.filterbe = this.be;
920
- data = await this.adms.getTopActivities(this.be, data);
921
- let reference = data.items[0];
922
- for (let i = 0; i < reference.actsessions.length; i++) {
923
- let itemref = reference.actsessions[i];
924
- let theD = this.dateExts.getDateConvertion(itemref.sampletime);
925
- if (!this.rtWindow)
926
- this.rtWindow = { from: theD, to: theD };
927
- else {
928
- if (theD.isBefore(this.rtWindow.from))
929
- this.rtWindow.from = theD;
930
- if (theD.isAfter(this.rtWindow.to))
931
- this.rtWindow.to = theD;
1261
+ ngOnDestroy() {
1262
+ clearTimeout(this.searchTimer);
1263
+ clearTimeout(this.flatSearchTimer);
1264
+ clearTimeout(this.datesTimer);
1265
+ clearTimeout(this.detailTimer);
1266
+ }
1267
+ // #region Barra dei comandi
1268
+ setLive(live) {
1269
+ if (this.live == live)
1270
+ return;
1271
+ this.live = live;
1272
+ if (!live) {
1273
+ // Sullo storico non ci sono i valori dall'ultimo snapshot né i thread attivi (R30)
1274
+ this.snapValues = false;
1275
+ this.orderBy = String(+this.orderBy == ProfilerOrderBy.ActiveThreads ? ProfilerOrderBy.Time : TO_BASE[+this.orderBy] ?? +this.orderBy);
1276
+ this.flatOrderBy = String(+this.flatOrderBy == ProfilerOrderBy.ActiveThreads ? ProfilerOrderBy.TimeLessChildren : TO_BASE[+this.flatOrderBy] ?? +this.flatOrderBy);
1277
+ // Default: le quattro ore che finiscono all'ultimo snapshot
1278
+ if (!this.filterfrom || !this.filterto) {
1279
+ let lastSnapshot = this.parseUtc((this.view == "tree" ? this.tree?.snapFrom : this.flat?.snapFrom) ?? null) ?? this.floorHour(new Date());
1280
+ this.filterto = this.fmt(lastSnapshot);
1281
+ this.filterfrom = this.fmt(new Date(lastSnapshot.getTime() - 4 * 3600 * 1000));
932
1282
  }
933
1283
  }
1284
+ this.updateOrderOptions();
1285
+ this.reloadAll();
1286
+ }
1287
+ setInstance(id) {
1288
+ this.instance = id;
1289
+ this.reloadAll();
1290
+ }
1291
+ datesChanged() {
1292
+ clearTimeout(this.datesTimer);
1293
+ this.datesTimer = setTimeout(() => {
1294
+ if (this.toDate(this.filterfrom) && this.toDate(this.filterto))
1295
+ this.reloadAll();
1296
+ }, DATES_DELAY_MS$1);
1297
+ }
1298
+ // Cambia colonne e intestazioni; richiama il server solo se cambia anche l'ordinamento
1299
+ setSnapValues(on) {
1300
+ if (this.snapValues == on)
1301
+ return;
1302
+ this.snapValues = on;
1303
+ let map = on ? TO_SNAP : TO_BASE;
1304
+ let treeOrder = String(map[+this.orderBy] ?? +this.orderBy);
1305
+ // La vista per nome non propone Δ Tempo: lì Tempo resta Tempo
1306
+ let flatMapped = map[+this.flatOrderBy];
1307
+ let flatOrder = String(flatMapped != null && flatMapped != ProfilerOrderBy.SnapDeltaTime ? flatMapped : +this.flatOrderBy);
1308
+ let reload = this.view == "tree" ? treeOrder != this.orderBy : flatOrder != this.flatOrderBy;
1309
+ this.orderBy = treeOrder;
1310
+ this.flatOrderBy = flatOrder;
1311
+ this.saveSettings();
1312
+ if (reload)
1313
+ this.reloadView();
1314
+ }
1315
+ setView(view) {
1316
+ if (this.view == view)
1317
+ return;
1318
+ this.view = view;
1319
+ this.reloadView();
1320
+ }
1321
+ // Aggiorna: la vista corrente e il dettaglio aperto (niente aggiornamento automatico, R5)
1322
+ reloadAll() {
1323
+ this.reloadView();
1324
+ if (this.selectedId)
1325
+ this.loadDetail(this.selectedId);
1326
+ }
1327
+ reloadView() {
1328
+ if (this.view == "tree")
1329
+ this.loadTree(true);
1330
+ else
1331
+ this.loadFlat();
934
1332
  }
935
- Init(ignoreStore) {
936
- this.doInit(ignoreStore);
937
- }
938
- async doInit(ignoreStorage) {
939
- let item = await this.localStorage.getItem("HotSearchTopAct").toPromise();
940
- if (!ignoreStorage && item) {
941
- this.init(item);
1333
+ // Con DirectorFeature.WorkloadReportV2 il report non è più un file da scaricare: ha la sua pagina
1334
+ downloadWorkloadReport() {
1335
+ if (this.workloadV2) {
1336
+ this.router.navigate([`/pages/${this.mod}/workload`, ...(this.be ? [this.be] : [])]);
1337
+ return;
942
1338
  }
943
- else {
944
- this.init(null);
945
- this.location.replaceState(`/pages/${this.mod}/topact`);
946
- this.route.snapshot.queryParams = [];
947
- await this.localStorage.removeItem("HotSearchTopAct").toPromise();
1339
+ let from = this.live ? "" : this.filterfrom;
1340
+ let to = this.live ? "" : this.filterto;
1341
+ this.adms.downloadWorkloadReport(this.be, from, to, this.instance != INSTANCE_THIS);
1342
+ }
1343
+ get instanceLabel() {
1344
+ return this.Instances.find(t => t.id == this.instance)?.description ?? "";
1345
+ }
1346
+ get loading() {
1347
+ return this.view == "tree" ? this.treeLoading : this.flatLoading;
1348
+ }
1349
+ // Vista corrente, per la riga della finestra
1350
+ get current() {
1351
+ return this.view == "tree" ? this.tree : this.flat;
1352
+ }
1353
+ baseRequest(req) {
1354
+ req.realTime = this.live;
1355
+ // Dietro un bilanciatore "questa istanza" cambia da una chiamata all'altra: si manda sempre l'istanza esplicita o global (R3)
1356
+ if (this.instance == INSTANCE_ALL$1)
1357
+ req.global = true;
1358
+ else if (this.instance != INSTANCE_THIS)
1359
+ req.instanceId = this.instance;
1360
+ else if (this.instanceId)
1361
+ req.instanceId = this.instanceId;
1362
+ if (!this.live) {
1363
+ req.snapFrom = this.toDate(this.filterfrom)?.toISOString();
1364
+ req.snapTo = this.toDate(this.filterto)?.toISOString();
948
1365
  }
1366
+ return req;
949
1367
  }
950
- init(HotPotato) {
951
- if (HotPotato) {
952
- this.searchView = HotPotato;
953
- this.POSTGetItems(this.searchView, false);
954
- }
1368
+ // #endregion
1369
+ // #region Albero
1370
+ setOrderBy(id) {
1371
+ this.orderBy = id;
1372
+ this.saveSettings();
1373
+ this.loadTree(true);
1374
+ }
1375
+ setMaxChildren(id) {
1376
+ this.maxChildren = id;
1377
+ this.saveSettings();
1378
+ this.loadTree(true);
1379
+ }
1380
+ setMinPercentage(id) {
1381
+ this.minPercentage = id;
1382
+ this.saveSettings();
1383
+ this.loadTree(true);
1384
+ }
1385
+ searchChanged() {
1386
+ clearTimeout(this.searchTimer);
1387
+ this.searchTimer = setTimeout(() => {
1388
+ let text = (this.searchText ?? "").trim();
1389
+ let next = text.length >= SEARCH_MIN_CHARS ? text : "";
1390
+ if (next == this.activeSearch)
1391
+ return;
1392
+ // Svuotando la ricerca si torna all'albero di prima, con gli stessi nodi aperti
1393
+ if (!this.activeSearch)
1394
+ this.expandedBeforeSearch = new Set(this.expanded);
1395
+ else if (!next)
1396
+ this.expanded = this.expandedBeforeSearch ?? new Set();
1397
+ this.activeSearch = next;
1398
+ this.loadTree(!next);
1399
+ }, SEARCH_DELAY_MS);
1400
+ }
1401
+ clearSearch() {
1402
+ this.searchText = "";
1403
+ this.searchChanged();
1404
+ }
1405
+ // Nodo come radice (R13); Idle si chiede senza rootNodeId
1406
+ setRoot(id) {
1407
+ this.rootId = id && id != this.rootPathIdle ? id : null;
1408
+ this.searchText = "";
1409
+ this.activeSearch = "";
1410
+ this.expanded.clear();
1411
+ this.treeNotice = null;
1412
+ this.view = "tree";
1413
+ this.updateUrl();
1414
+ this.loadTree(false);
1415
+ }
1416
+ get rootPathIdle() {
1417
+ let root = this.tree?.idleNode;
1418
+ return root?.level == 0 ? root.nodeId : this.tree?.rootPath?.[0]?.nodeId;
1419
+ }
1420
+ async loadTree(keepExpanded) {
1421
+ let generation = ++this.treeGeneration;
1422
+ let searching = !!this.activeSearch;
1423
+ let previous = keepExpanded ? [...this.expanded] : [];
1424
+ let req = this.baseRequest(new ProfilerV2TreeRequest());
1425
+ req.orderBy = +this.orderBy;
1426
+ if (this.rootId)
1427
+ req.rootNodeId = this.rootId;
1428
+ if (searching)
1429
+ req.search = this.activeSearch;
955
1430
  else {
956
- this.searchView = new TopActivitiesDataView();
957
- this.sendSearch();
1431
+ req.maxDepth = TREE_DEPTH;
1432
+ if (+this.maxChildren > 0)
1433
+ req.maxChildren = +this.maxChildren;
1434
+ if (+this.minPercentage > 0)
1435
+ req.minPercentage = +this.minPercentage;
958
1436
  }
959
- }
960
- ngOnDestroy() {
961
- this.destroyed$.next(true);
962
- this.destroyed$.complete();
963
- if (this.storageSub)
964
- this.storageSub.unsubscribe();
965
- }
966
- async sendSearch(resetPage = true, update = false, searchViewOverride = null) {
967
- let sv = searchViewOverride ?? this.searchView;
968
- if ((sv.filterfrom || sv.filterto) && !this.rtWindow)
969
- await this.updateRealTimeWindow();
970
- let dateRequestOnRealtimeWindow = sv.filterfrom && this.dateExts.getDateConvertion(sv.filterfrom).isAfter(this.rtWindow.from) &&
971
- sv.filterto && this.dateExts.getDateConvertion(sv.filterto).isBefore(this.rtWindow.to);
972
- sv.filterrealtime = dateRequestOnRealtimeWindow;
973
- sv.searchinprogress = true;
974
- this.POSTGetItems(sv, update);
975
- }
976
- async POSTGetItems(data, update) {
977
- data.filterbe = this.be;
978
- let res = await this.adms.getTopActivities(this.be, data);
979
- //Se sono in modalità di update non devo toccare la searchView attuale se non per il searchinprogress che viene messo dall'es table
980
- if (!update) {
981
- this.searchView = res;
982
- this.localStorage.setItem("HotSearchTopAct", this.searchView).subscribe();
1437
+ this.treeLoading = true;
1438
+ this.treeError = null;
1439
+ try {
1440
+ let res = await this.adms.getProfilerTreeV2(this.be, req);
1441
+ if (generation != this.treeGeneration)
1442
+ return;
1443
+ // Radice non trovata (R23): lo si dice e si torna a Idle
1444
+ if (res && !res.idleNode && res.snapFrom && this.rootId) {
1445
+ this.rootId = null;
1446
+ this.updateUrl();
1447
+ this.treeNotice = this.lc.loc("Il nodo non c'è nelle istanze scelte: l'albero riparte da Idle.");
1448
+ this.loadTree(false);
1449
+ return;
1450
+ }
1451
+ this.tree = res;
1452
+ this.nodes.clear();
1453
+ this.parents.clear();
1454
+ this.loaded.clear();
1455
+ this.loadingChildren.clear();
1456
+ // I nodi che erano aperti restano aperti: quelli che non sono ancora arrivati si riaprono quando arrivano i figli del padre
1457
+ this.expanded = new Set(searching ? [] : previous);
1458
+ let root = res?.idleNode;
1459
+ if (root) {
1460
+ this.ingest(root, null, searching ? null : TREE_DEPTH, searching);
1461
+ this.expanded.add(root.nodeId);
1462
+ }
1463
+ if (searching) {
1464
+ // L'albero della ricerca contiene solo i percorsi dei nodi trovati e si mostra tutto aperto
1465
+ this.nodes.forEach(t => { if (t.children?.length)
1466
+ this.expanded.add(t.nodeId); });
1467
+ this.matchCount = [...this.nodes.values()].filter(t => t.searchMatch).length;
1468
+ }
1469
+ this.buildRows();
1470
+ this.nodes.forEach(t => this.reopen(t, generation));
1471
+ }
1472
+ catch (err) {
1473
+ if (generation == this.treeGeneration)
1474
+ this.treeError = this.errorMessage(err);
1475
+ }
1476
+ finally {
1477
+ if (generation == this.treeGeneration)
1478
+ this.treeLoading = false;
983
1479
  }
984
- // DirectorInfos anche se per qualche motivo crasha di brutto è comunque non obbligatorio, evito di non caricare la pagina solo perché lui scoppia
985
- let dirinfos = await this.adms.getInfos(this.be);
986
- this.searchView.searchinprogress = false;
987
- this.reviewBindings(update, res, dirinfos);
988
- }
989
- loadNodeDetails(nodeId) {
990
- this.NodeDetails = null;
991
- requestAnimationFrame(async () => {
992
- this.NodeDetails = await this.adms.getNodeStats(this.be, nodeId, this.searchView.filterglobal);
993
- });
994
1480
  }
995
- reviewBindings(update, searchViewOverride, dirInfos) {
996
- let reference = (searchViewOverride ?? this.searchView).items[0];
997
- this.TopNodes = reference.topnodes;
998
- this.TopSessions = reference.topsessions;
999
- this.DirInfos = dirInfos?.cluster ? dirInfos : null;
1000
- this.DirInfosGlobal = !dirInfos?.cluster ? dirInfos : null;
1001
- if (!reference.actsessions || reference.actsessions.length == 0) {
1002
- this.TopActiveSessions = null;
1481
+ ingest(node, parent, depthLeft, partial) {
1482
+ this.nodes.set(node.nodeId, node);
1483
+ if (parent)
1484
+ this.parents.set(node.nodeId, parent);
1485
+ // Oltre il limite di profondità, o nella ricerca, i figli non sono tutti: si chiedono quando il nodo si apre
1486
+ if (!partial && (depthLeft == null || depthLeft > 0))
1487
+ this.loaded.add(node.nodeId);
1488
+ for (let child of node.children ?? [])
1489
+ this.ingest(child, node, depthLeft == null ? null : depthLeft - 1, partial);
1490
+ }
1491
+ // Un nodo aperto senza figli caricati (dopo un ricaricamento o "Mostra tutti" del padre) li chiede
1492
+ reopen(node, generation) {
1493
+ if (this.expanded.has(node.nodeId) && this.needsChildren(node) && !this.loadingChildren.has(node.nodeId))
1494
+ this.loadChildren(node, false, generation);
1495
+ }
1496
+ hasChildren(node) {
1497
+ return node.numChildren > 0;
1498
+ }
1499
+ isExpanded(node) {
1500
+ return this.expanded.has(node.nodeId);
1501
+ }
1502
+ needsChildren(node) {
1503
+ return node.numChildren > 0 && !node.children?.length && !this.loaded.has(node.nodeId);
1504
+ }
1505
+ toggle(node, event) {
1506
+ event?.stopPropagation();
1507
+ if (!this.hasChildren(node))
1508
+ return;
1509
+ if (this.expanded.has(node.nodeId)) {
1510
+ this.expanded.delete(node.nodeId);
1511
+ this.buildRows();
1003
1512
  return;
1004
1513
  }
1005
- let series = [];
1006
- let props = Object.keys(reference.actsessions[0]);
1007
- for (let i = 0; i < props.length; i++) {
1008
- if (props[i] != 'sampletime') {
1009
- let s = new LineSerie(this.propertiesSeriesMapping[props[i]], [], null, null, "sessions");
1010
- series.push(s);
1011
- this.seriesPropertiesMapping[props[i]] = s;
1012
- }
1514
+ this.expanded.add(node.nodeId);
1515
+ if (this.needsChildren(node))
1516
+ this.loadChildren(node, false);
1517
+ else
1518
+ this.buildRows();
1519
+ }
1520
+ // R9: la stessa chiamata dei figli senza maxChildren e minPercentage
1521
+ showAll(node, event) {
1522
+ event.stopPropagation();
1523
+ this.loadChildren(node, true);
1524
+ }
1525
+ // R8: i figli di un nodo, che sostituiscono children e omitted* del nodo
1526
+ async loadChildren(node, all, generation = this.treeGeneration) {
1527
+ let req = this.baseRequest(new ProfilerV2TreeRequest());
1528
+ req.rootNodeId = node.nodeId;
1529
+ req.maxDepth = 1;
1530
+ req.orderBy = +this.orderBy;
1531
+ if (!all && +this.maxChildren > 0)
1532
+ req.maxChildren = +this.maxChildren;
1533
+ if (!all && +this.minPercentage > 0)
1534
+ req.minPercentage = +this.minPercentage;
1535
+ this.loadingChildren.add(node.nodeId);
1536
+ this.buildRows();
1537
+ try {
1538
+ let res = await this.adms.getProfilerTreeV2(this.be, req);
1539
+ if (generation != this.treeGeneration)
1540
+ return;
1541
+ let fresh = res?.idleNode;
1542
+ node.children = fresh?.children ?? [];
1543
+ node.numChildren = fresh?.numChildren ?? node.children.length;
1544
+ node.omittedChildren = fresh?.omittedChildren ?? 0;
1545
+ node.omittedCalls = fresh?.omittedCalls ?? 0;
1546
+ node.omittedTime = fresh?.omittedTime ?? 0;
1547
+ node.omittedSnapDeltaTime = fresh?.omittedSnapDeltaTime ?? 0;
1548
+ for (let child of node.children)
1549
+ this.ingest(child, node, 0, false);
1550
+ this.loaded.add(node.nodeId);
1551
+ node.children.forEach(t => this.reopen(t, generation));
1013
1552
  }
1014
- for (let i = 0; i < reference.actsessions.length; i++) {
1015
- let itemref = reference.actsessions[i];
1016
- let theD = new Date(itemref.sampletime);
1017
- for (let i = 0; i < props.length; i++)
1018
- if (props[i] != 'sampletime' && (!update || this.dateExts.getDateConvertion(theD).isAfter(this.LastDataPoint)))
1019
- this.seriesPropertiesMapping[props[i]].values.push(new DateValue(theD, itemref[props[i]]));
1553
+ catch (err) {
1554
+ if (generation == this.treeGeneration) {
1555
+ this.expanded.delete(node.nodeId);
1556
+ this.treeError = this.errorMessage(err);
1557
+ }
1020
1558
  }
1021
- if (update && this.ChartInstance) {
1022
- let data = new LineChartData(series, new Limit(dirInfos?.threads, this.lc.loc("Thread Massimi")));
1023
- if (series.some(t => t.values.length > 0)) {
1024
- this.ChartInstance.addData(data);
1025
- this.LastDataPoint = series.map(t => dayjs.max(t.values.map(v => this.dateExts.getDateConvertion(v.d))))[0];
1559
+ finally {
1560
+ if (generation == this.treeGeneration) {
1561
+ this.loadingChildren.delete(node.nodeId);
1562
+ this.buildRows();
1026
1563
  }
1027
1564
  }
1028
- else {
1029
- this.TopActiveSessions = new LineChartData(series, new Limit(dirInfos?.threads, this.lc.loc("Thread Massimi")));
1030
- this.LastDataPoint = series.map(t => dayjs.max(t.values.map(v => this.dateExts.getDateConvertion(v.d))))[0];
1565
+ }
1566
+ buildRows() {
1567
+ let rows = [];
1568
+ if (this.tree?.idleNode)
1569
+ this.addRows(rows, this.tree.idleNode, 0);
1570
+ this.rows = rows;
1571
+ }
1572
+ addRows(rows, node, depth) {
1573
+ rows.push({ kind: "node", node: node, depth: depth, key: node.nodeId });
1574
+ if (!this.expanded.has(node.nodeId))
1575
+ return;
1576
+ let loading = this.loadingChildren.has(node.nodeId);
1577
+ if (loading)
1578
+ rows.push({ kind: "loading", node: node, depth: depth + 1, key: node.nodeId + "#loading" });
1579
+ for (let child of node.children ?? [])
1580
+ this.addRows(rows, child, depth + 1);
1581
+ if (!loading && node.omittedChildren > 0 && !this.needsChildren(node))
1582
+ rows.push({ kind: "omitted", node: node, depth: depth + 1, key: node.nodeId + "#omitted" });
1583
+ }
1584
+ // Clic: selezione e dettaglio. Doppio clic: apre o chiude
1585
+ rowClicked(node) {
1586
+ this.select(node.nodeId);
1587
+ }
1588
+ // Su e giù cambiano nodo, destra apre (o scende al primo figlio), sinistra chiude (o sale al padre)
1589
+ treeKeydown(event) {
1590
+ let nodeRows = this.rows.filter(t => t.kind == "node");
1591
+ let index = nodeRows.findIndex(t => t.node.nodeId == this.selectedId);
1592
+ let current = index >= 0 ? nodeRows[index].node : null;
1593
+ let target = null;
1594
+ switch (event.key) {
1595
+ case "ArrowDown":
1596
+ target = nodeRows[Math.min(nodeRows.length - 1, index + 1)]?.node;
1597
+ break;
1598
+ case "ArrowUp":
1599
+ target = nodeRows[Math.max(0, index - 1)]?.node;
1600
+ break;
1601
+ case "ArrowRight":
1602
+ if (current && this.hasChildren(current) && !this.isExpanded(current))
1603
+ this.toggle(current);
1604
+ else if (current && this.isExpanded(current))
1605
+ target = current.children?.[0];
1606
+ break;
1607
+ case "ArrowLeft":
1608
+ if (current && this.isExpanded(current) && this.hasChildren(current))
1609
+ this.toggle(current);
1610
+ else if (current)
1611
+ target = this.parents.get(current.nodeId);
1612
+ break;
1613
+ default:
1614
+ return;
1615
+ }
1616
+ event.preventDefault();
1617
+ if (target) {
1618
+ this.select(target.nodeId, true);
1619
+ setTimeout(() => document.getElementById("pv2-row-" + target.nodeId)?.scrollIntoView({ block: "nearest" }));
1031
1620
  }
1032
1621
  }
1033
- autoUpdateChanged() {
1034
- if (!/^[1-9]\d*$/.test(this.seconds))
1035
- this.seconds = '10';
1036
- const secondsInMs = parseInt(this.seconds, null) * 1000;
1037
- if (this.autoUpdate) {
1038
- this.sendSearch(false, true);
1039
- this.updateTimer = setInterval(() => { this.sendSearch(false, true); }, secondsInMs);
1622
+ isIdle(type, waitClass) {
1623
+ return type == "Idle" || ((type == "Wait" || type == "Sleep") && waitClass == "Idle");
1624
+ }
1625
+ // Esecuzione in corso da più di un minuto su un nodo non idle (solo live)
1626
+ isLongRunning(type, waitClass, activeMaxTime) {
1627
+ return this.live && !this.isIdle(type, waitClass) && (activeMaxTime ?? 0) >= LONG_RUNNING_SECONDS;
1628
+ }
1629
+ // Con la ricerca la parte del nome che corrisponde è evidenziata
1630
+ nameParts(name, search) {
1631
+ if (!search || !name)
1632
+ return [{ text: name ?? "", hit: false }];
1633
+ let parts = [];
1634
+ let lower = name.toLowerCase();
1635
+ let needle = search.toLowerCase();
1636
+ let from = 0;
1637
+ let at = lower.indexOf(needle);
1638
+ while (at >= 0) {
1639
+ if (at > from)
1640
+ parts.push({ text: name.substring(from, at), hit: false });
1641
+ parts.push({ text: name.substring(at, at + needle.length), hit: true });
1642
+ from = at + needle.length;
1643
+ at = lower.indexOf(needle, from);
1040
1644
  }
1041
- else
1645
+ if (from < name.length)
1646
+ parts.push({ text: name.substring(from), hit: false });
1647
+ return parts;
1648
+ }
1649
+ // #endregion
1650
+ // #region Valori secondo la modalità (dall'avvio o dall'ultimo snapshot)
1651
+ get snapMode() {
1652
+ return this.live && this.snapValues;
1653
+ }
1654
+ calls(node) { return this.snapMode ? node.snapDeltaCalls : node.calls; }
1655
+ time(node) { return this.snapMode ? node.snapDeltaTime : node.time; }
1656
+ self(node) { return this.snapMode ? node.snapDeltaTimeLessChildren : node.timeLessChildren; }
1657
+ avg(node) { return this.snapMode ? node.snapDeltaAvgTime : node.avgTime; }
1658
+ peak(node) { return this.snapMode ? node.snapPeakTime : node.peakTime; }
1659
+ parentPerc(node) { return this.snapMode ? node.snapParentPercentage : node.parentPercentage; }
1660
+ firstLevelPerc(node) { return this.snapMode ? node.snapPercentage : node.percentage; }
1661
+ omittedTime(node) { return this.snapMode ? node.omittedSnapDeltaTime : node.omittedTime; }
1662
+ flatCalls(item) { return this.snapMode ? item.snapDeltaCalls : item.calls; }
1663
+ flatSelf(item) { return this.snapMode ? item.snapDeltaTimeLessChildren : item.timeLessChildren; }
1664
+ flatPerc(item) { return this.snapMode ? item.snapPercentage : item.percentage; }
1665
+ flatTime(item) { return this.snapMode ? item.snapDeltaTime : item.time; }
1666
+ // La vista per nome non ha il medio dei delta: si calcola
1667
+ flatAvg(item) { return this.snapMode ? (item.snapDeltaCalls > 0 ? item.snapDeltaTime / item.snapDeltaCalls : null) : item.avgTime; }
1668
+ // #endregion
1669
+ // #region Eventi per nome
1670
+ flatChanged() {
1671
+ this.saveSettings();
1672
+ this.loadFlat();
1673
+ }
1674
+ setFlatTop(id) {
1675
+ this.flatTop = id;
1676
+ this.flatChanged();
1677
+ }
1678
+ flatSearchChanged() {
1679
+ clearTimeout(this.flatSearchTimer);
1680
+ this.flatSearchTimer = setTimeout(() => this.loadFlat(), SEARCH_DELAY_MS);
1681
+ }
1682
+ async loadFlat() {
1683
+ let generation = ++this.flatGeneration;
1684
+ let req = this.baseRequest(new ProfilerV2FlatRequest());
1685
+ req.orderBy = +this.flatOrderBy;
1686
+ req.top = +this.flatTop;
1687
+ let search = (this.flatSearch ?? "").trim();
1688
+ if (search)
1689
+ req.search = search;
1690
+ if (this.flatType && this.flatType != "*")
1691
+ req.eventType = this.flatType;
1692
+ if (this.flatIncludeIdle)
1693
+ req.includeIdle = true;
1694
+ this.flatLoading = true;
1695
+ this.flatError = null;
1696
+ try {
1697
+ let res = await this.adms.getProfilerFlatV2(this.be, req);
1698
+ if (generation != this.flatGeneration)
1699
+ return;
1700
+ this.flat = res;
1701
+ this.openFlat.clear();
1702
+ }
1703
+ catch (err) {
1704
+ if (generation == this.flatGeneration)
1705
+ this.flatError = this.errorMessage(err);
1706
+ }
1707
+ finally {
1708
+ if (generation == this.flatGeneration)
1709
+ this.flatLoading = false;
1710
+ }
1711
+ }
1712
+ flatKey(item) {
1713
+ return `${item.eventTypeName}|${item.waitClassName}|${item.nodeName}`;
1714
+ }
1715
+ toggleFlat(item) {
1716
+ let key = this.flatKey(item);
1717
+ if (this.openFlat.has(key))
1718
+ this.openFlat.delete(key);
1719
+ else
1720
+ this.openFlat.add(key);
1721
+ }
1722
+ // R20: dall'elenco dei nodi di un evento, apri nell'albero
1723
+ openInTree(nodeId, event) {
1724
+ event.stopPropagation();
1725
+ this.setRoot(nodeId);
1726
+ this.select(nodeId);
1727
+ }
1728
+ showDetail(nodeId, event) {
1729
+ event.stopPropagation();
1730
+ this.select(nodeId);
1731
+ }
1732
+ // #endregion
1733
+ // #region Dettaglio
1734
+ select(nodeId, delayed = false) {
1735
+ clearTimeout(this.detailTimer);
1736
+ if (nodeId == this.selectedId && (this.detailLoading || this.detail?.nodeId == nodeId))
1737
+ return;
1738
+ this.selectedId = nodeId;
1739
+ if (delayed)
1740
+ this.detailTimer = setTimeout(() => this.loadDetail(nodeId), DETAIL_DELAY_MS);
1741
+ else
1742
+ this.loadDetail(nodeId);
1743
+ }
1744
+ async loadDetail(nodeId) {
1745
+ // Se l'utente seleziona un altro nodo prima della risposta, quella vecchia si scarta (R18)
1746
+ let generation = ++this.detailGeneration;
1747
+ let req = this.baseRequest(new ProfilerV2NodeRequest());
1748
+ req.nodeId = nodeId;
1749
+ this.detailLoading = true;
1750
+ this.detailError = null;
1751
+ this.detailMissing = false;
1752
+ try {
1753
+ let res = await this.adms.getProfilerNodeV2(this.be, req);
1754
+ if (generation != this.detailGeneration)
1755
+ return;
1756
+ this.detail = res;
1757
+ this.detailMissing = !res;
1758
+ this.typeParts = res ? this.buildTypeParts(res) : [];
1759
+ this.chart = res ? this.buildChart(res) : null;
1760
+ }
1761
+ catch (err) {
1762
+ if (generation == this.detailGeneration) {
1763
+ this.detail = null;
1764
+ this.detailError = `${this.lc.loc("Errore")} ${err?.status ?? ""}. ${this.adms.errorText(err)}`;
1765
+ }
1766
+ }
1767
+ finally {
1768
+ if (generation == this.detailGeneration)
1769
+ this.detailLoading = false;
1770
+ }
1771
+ }
1772
+ // Mentre arriva il dettaglio di un altro nodo quello di prima resta visibile, attenuato, così il pannello non salta
1773
+ get detailStale() {
1774
+ return !!this.detail && this.detail.nodeId != this.selectedId;
1775
+ }
1776
+ async copyId(element) {
1777
+ try {
1778
+ await navigator.clipboard.writeText(this.detail.nodeId);
1779
+ this.copied = true;
1780
+ setTimeout(() => this.copied = false, 1500);
1781
+ }
1782
+ catch {
1783
+ // Senza appunti (pagina non sicura o permesso negato) il testo dell'id resta selezionato, pronto per Ctrl+C
1784
+ let range = document.createRange();
1785
+ range.selectNodeContents(element);
1786
+ let selection = window.getSelection();
1787
+ selection.removeAllRanges();
1788
+ selection.addRange(range);
1789
+ }
1790
+ }
1791
+ // Ripartizione del tempo proprio di nodo e discendenti: solo i tipi maggiori di zero, in ordine decrescente
1792
+ buildTypeParts(stats) {
1793
+ let parts = [
1794
+ ["Cpu", stats.cpuPerc], ["Database", stats.dbPerc], ["Wait", stats.waitPerc], ["Script", stats.scriptPerc], ["Network", stats.networkPerc],
1795
+ ["IO", stats.ioPerc], ["Sleep", stats.sleepPerc], ["Throttle", stats.throttlePerc], ["Idle", stats.idlePerc]
1796
+ ];
1797
+ return parts.filter(t => t[1] > 0).sort((a, b) => b[1] - a[1]).map(t => ({ type: t[0], label: TYPE_LABELS[t[0]], color: TYPE_COLORS[t[0]], percentage: t[1] }));
1798
+ }
1799
+ // activeSessions ha un punto al secondo: si raggruppano in circa 160 punti con la media (R27)
1800
+ buildChart(stats) {
1801
+ let points = stats.activeSessions ?? [];
1802
+ if (points.length == 0)
1803
+ return null;
1804
+ let times = points.map(t => this.parseUtc(t.timestamp).getTime());
1805
+ let values = points.map(t => t.activity ?? 0);
1806
+ let interval = points.length > 1 ? Math.max(1, Math.round((times[times.length - 1] - times[0]) / (points.length - 1) / 1000)) : 1;
1807
+ let size = Math.max(1, Math.ceil(points.length / CHART_POINTS));
1808
+ let grouped = [];
1809
+ for (let i = 0; i < points.length; i += size) {
1810
+ let slice = values.slice(i, i + size);
1811
+ grouped.push({ t: times[i], v: slice.reduce((a, b) => a + b, 0) / slice.length });
1812
+ }
1813
+ let max = this.niceMax(Math.max(...grouped.map(t => t.v)));
1814
+ let n = grouped.length;
1815
+ let x = (i) => n > 1 ? i / (n - 1) * 100 : 50;
1816
+ let y = (v) => 100 - v / max * 100;
1817
+ let line = grouped.map((t, i) => `${i ? "L" : "M"}${x(i).toFixed(2)},${y(t.v).toFixed(2)}`).join(" ");
1818
+ return {
1819
+ line: line,
1820
+ area: `${line} L${x(n - 1).toFixed(2)},100 L${x(0).toFixed(2)},100 Z`,
1821
+ color: this.typeColor(stats.eventTypeName, stats.waitClassName),
1822
+ max: max,
1823
+ lastX: x(n - 1),
1824
+ lastY: y(grouped[n - 1].v),
1825
+ start: this.hhmm(new Date(times[0])),
1826
+ middle: this.hhmm(new Date(times[Math.floor(times.length / 2)])),
1827
+ end: this.hhmm(new Date(times[times.length - 1])),
1828
+ average: values.reduce((a, b) => a + b, 0) / values.length,
1829
+ peak: Math.max(...values),
1830
+ points: points.length,
1831
+ interval: interval,
1832
+ group: size * interval
1833
+ };
1834
+ }
1835
+ // Massimo dell'asse arrotondato a un valore tondo: 1, 2, 2,5 o 5 per una potenza di 10
1836
+ niceMax(value) {
1837
+ if (!(value > 0))
1838
+ return 1;
1839
+ let power = Math.pow(10, Math.floor(Math.log10(value)));
1840
+ let fraction = value / power;
1841
+ return [1, 2, 2.5, 5, 10].find(t => fraction <= t + 1e-9) * power;
1842
+ }
1843
+ // #endregion
1844
+ // #region Presentazione
1845
+ typeColor(type, waitClass) {
1846
+ return TYPE_COLORS[this.isIdle(type, waitClass) ? "Idle" : type] ?? TYPE_COLORS.Idle;
1847
+ }
1848
+ typeLabel(type, waitClass) {
1849
+ let label = TYPE_LABELS[type] ?? type;
1850
+ return waitClass && waitClass != "None" ? `${label} · ${waitClass}` : label;
1851
+ }
1852
+ axisValue(value) {
1853
+ return formatNumber(value, "it", "1.0-3");
1854
+ }
1855
+ duration(seconds) {
1856
+ return formatDuration(seconds);
1857
+ }
1858
+ // Le date delle risposte sono UTC "dd/MM/yyyy HH:mm:ss" senza fuso: si leggono come UTC e si mostrano in ora locale
1859
+ parseUtc(value) {
1860
+ let m = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})$/.exec(value ?? "");
1861
+ return m ? new Date(Date.UTC(+m[3], +m[2] - 1, +m[1], +m[4], +m[5], +m[6])) : value ? new Date(value) : null;
1862
+ }
1863
+ hhmm(value) {
1864
+ let date = typeof value == "string" ? this.parseUtc(value) : value;
1865
+ return date ? dayjs(date).format("HH:mm") : "—";
1866
+ }
1867
+ ddmmhhmm(value) {
1868
+ let date = this.parseUtc(value);
1869
+ return date ? dayjs(date).format("DD/MM HH:mm") : "—";
1870
+ }
1871
+ minutesAgo(value) {
1872
+ let date = this.parseUtc(value);
1873
+ return date ? Math.max(0, Math.floor((Date.now() - date.getTime()) / 60000)) : 0;
1874
+ }
1875
+ // "{x} s fa", "{x} min fa", "{x} h {y} min fa"; "mai" se nessuno è ancora uscito
1876
+ relative(value) {
1877
+ let date = this.parseUtc(value);
1878
+ if (!date)
1879
+ return this.lc.loc("mai");
1880
+ let seconds = Math.max(0, Math.round((Date.now() - date.getTime()) / 1000));
1881
+ if (seconds < 60)
1882
+ return `${seconds} ${this.lc.loc("s fa")}`;
1883
+ let minutes = Math.floor(seconds / 60);
1884
+ if (minutes < 60)
1885
+ return `${minutes} ${this.lc.loc("min fa")}`;
1886
+ return `${Math.floor(minutes / 60)} h ${minutes % 60} ${this.lc.loc("min fa")}`;
1887
+ }
1888
+ errorMessage(err) {
1889
+ return `${this.lc.loc("Errore")} ${err?.status ?? ""}: ${this.adms.errorText(err)}`;
1890
+ }
1891
+ // #endregion
1892
+ // #region Supporto
1893
+ buildSources() {
1894
+ let orders = [
1895
+ [ProfilerOrderBy.Time, "Tempo"], [ProfilerOrderBy.TimeLessChildren, "Tempo proprio"], [ProfilerOrderBy.Calls, "Chiamate"],
1896
+ [ProfilerOrderBy.AvgTime, "Tempo medio"], [ProfilerOrderBy.PeakTime, "Picco"], [ProfilerOrderBy.SnapDeltaTime, "Δ Tempo"],
1897
+ [ProfilerOrderBy.SnapDeltaTimeLessChildren, "Δ Tempo proprio"], [ProfilerOrderBy.SnapDeltaCalls, "Δ Chiamate"], [ProfilerOrderBy.ActiveThreads, "Thread attivi"]
1898
+ ];
1899
+ this.TreeOrders = orders.map(t => new KeyValue(String(t[0]), this.lc.loc(t[1])));
1900
+ // La vista per nome parte dal tempo proprio e non ha il Δ del tempo totale
1901
+ this.FlatOrders = [1, 0, 2, 3, 4, 6, 7, 8].map(t => this.TreeOrders.find(k => k.id == String(t)));
1902
+ this.ChildrenOptions = ["10", "25", "50", "100"].map(t => new KeyValue(t, t)).concat([new KeyValue("0", this.lc.loc("Tutti"))]);
1903
+ this.ThresholdOptions = [new KeyValue("0", this.lc.loc("Nessuna")), new KeyValue("0.1", "≥ 0,1%"), new KeyValue("1", "≥ 1%"), new KeyValue("5", "≥ 5%")];
1904
+ this.FlatTypes = [new KeyValue("*", this.lc.loc("Tutti"))].concat(EVENT_TYPES.map(t => new KeyValue(t, t)));
1905
+ this.FlatRows = [new KeyValue("100", "100"), new KeyValue("500", "500"), new KeyValue("1000", "1.000")];
1906
+ this.updateOrderOptions();
1907
+ this.buildInstances();
1908
+ }
1909
+ // Le chiavi dei delta e i thread attivi si propongono solo in live (R10)
1910
+ updateOrderOptions() {
1911
+ this.TreeOrderOptions = this.live ? this.TreeOrders : this.TreeOrders.slice(0, 5);
1912
+ this.FlatOrderOptions = this.live ? this.FlatOrders : this.FlatOrders.slice(0, 5);
1913
+ }
1914
+ // Indipendenti: l'istanza che risponde è facoltativa, l'elenco usa la gestione errori standard
1915
+ loadInstances() {
1916
+ this.adms.getInstanceId(this.be).then(t => {
1917
+ this.instanceId = t;
1918
+ this.buildInstances();
1919
+ });
1920
+ this.adms.getInstances(this.be).then(t => {
1921
+ this.instanceIds = (t ?? []).map(k => k.id);
1922
+ this.buildInstances();
1923
+ });
1924
+ }
1925
+ buildInstances() {
1926
+ this.Instances = [
1927
+ new KeyValue(INSTANCE_THIS, this.instanceId ? `${this.lc.loc("Questa istanza")} (${this.instanceId})` : this.lc.loc("Questa istanza")),
1928
+ new KeyValue(INSTANCE_ALL$1, this.instanceIds.length ? `${this.lc.loc("Tutto il cluster")} (${this.instanceIds.length} ${this.instanceIds.length == 1 ? this.lc.loc("istanza") : this.lc.loc("istanze")})` : this.lc.loc("Tutto il cluster")),
1929
+ ...this.instanceIds.map(t => new KeyValue(t, `${this.lc.loc("Solo")} ${t}`))
1930
+ ];
1931
+ }
1932
+ updateUrl() {
1933
+ let [path, query] = this.location.path().split("?");
1934
+ let params = new URLSearchParams(query ?? "");
1935
+ if (this.rootId)
1936
+ params.set("root", this.rootId);
1937
+ else
1938
+ params.delete("root");
1939
+ this.location.replaceState(path, params.toString());
1940
+ }
1941
+ applySettings(saved) {
1942
+ this.orderBy = saved.orderBy ?? this.orderBy;
1943
+ this.maxChildren = saved.maxChildren ?? this.maxChildren;
1944
+ this.minPercentage = saved.minPercentage ?? this.minPercentage;
1945
+ this.snapValues = saved.snapValues ?? this.snapValues;
1946
+ this.flatOrderBy = saved.flatOrderBy ?? this.flatOrderBy;
1947
+ this.flatTop = saved.flatTop ?? this.flatTop;
1948
+ }
1949
+ saveSettings() {
1950
+ let settings = {
1951
+ orderBy: this.orderBy,
1952
+ maxChildren: this.maxChildren,
1953
+ minPercentage: this.minPercentage,
1954
+ snapValues: this.snapValues,
1955
+ flatOrderBy: this.flatOrderBy,
1956
+ flatTop: this.flatTop
1957
+ };
1958
+ this.localStorage.setItem(STORAGE_KEY$3, settings).subscribe();
1959
+ }
1960
+ toDate(value) {
1961
+ return value ? this.dateExts.getDateConvertion(value).toDate() : null;
1962
+ }
1963
+ fmt(date) {
1964
+ return dayjs(date).format(this.lc.token("getFullDateDisplayFormat"));
1965
+ }
1966
+ floorHour(date) {
1967
+ let ret = new Date(date);
1968
+ ret.setMinutes(0, 0, 0);
1969
+ return ret;
1970
+ }
1971
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerV2Component, deps: [{ token: i1$1.DateService }, { token: i1.LocalizationService }, { token: i3.PreferencesService }, { token: i1$1.InterComService }, { token: i4.ActivatedRoute }, { token: i4.Router }, { token: i5.Location }, { token: AdminModuleBaseService }, { token: ADM_MODULE_PATH }, { token: ADM_WORKLOAD_V2, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
1972
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: AdmProfilerV2Component, isStandalone: false, selector: "profiler_v2", providers: [{ provide: LocalizationService, useClass: AdmProfilerV2Loc }], ngImport: i0, template: "<div class=\"row\">\r\n <div class=\"col-12\">\r\n <div class=\"card app-overflow-visible\" widget>\r\n <div class=\"card-header\">\r\n <span class=\"text-uppercase\">{{'Profiler del Server' | localize: lc}}</span>\r\n </div>\r\n <div class=\"card-body widget-body pv2-body\">\r\n <!-- Barra dei comandi: quali dati caricare. Ogni cambio ricarica la vista e il dettaglio aperto -->\r\n <div class=\"pv2-toolbar\">\r\n <div class=\"pv2-cluster\">\r\n <div class=\"pv2-group\">\r\n <span class=\"pv2-group-label\">{{'Sorgente' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n <button type=\"button\" [class.pv2-on]=\"live\" (click)=\"setLive(true)\"><span class=\"pv2-live-dot\"></span>Live</button>\r\n <button type=\"button\" [class.pv2-on]=\"!live\" (click)=\"setLive(false)\">{{'Storico' | localize: lc}}</button>\r\n </div>\r\n </div>\r\n <div class=\"pv2-field pv2-field-wide pv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"instance\" (ngModelChange)=\"setInstance($event)\" name=\"instance\" [Source]=\"Instances\" [Placeholder]=\"'Istanze' | localize: lc\"></form-select>\r\n </div>\r\n </div>\r\n\r\n @if (!live) {\r\n <div class=\"pv2-cluster\">\r\n <div class=\"pv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterfrom\" (ngModelChange)=\"filterfrom = $event; datesChanged()\" name=\"filterfrom\" [Placeholder]=\"'Dal' | localize: lc\"></form-datetime>\r\n </div>\r\n <div class=\"pv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterto\" (ngModelChange)=\"filterto = $event; datesChanged()\" name=\"filterto\" [Placeholder]=\"'Al' | localize: lc\"></form-datetime>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"pv2-group\">\r\n <span class=\"pv2-group-label\">{{'Valori' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n <button type=\"button\" [class.pv2-on]=\"!snapValues\" (click)=\"setSnapValues(false)\">{{\"Dall'avvio\" | localize: lc}}</button>\r\n <button type=\"button\" [class.pv2-on]=\"snapValues\" (click)=\"setSnapValues(true)\">{{\"Dall'ultimo snapshot\" | localize: lc}}</button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"pv2-cluster pv2-cluster-end\">\r\n <button type=\"button\" class=\"btn app-btn-search\" [disabled]=\"loading\" (click)=\"reloadAll()\" [title]=\"'Aggiorna' | localize: lc\">\r\n <i class=\"fa fa-refresh\" [class.fa-spin]=\"loading\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn app-btn-search\" (click)=\"downloadWorkloadReport()\" [title]=\"(workloadV2 ? 'Workload report' : 'Scarica il workload report') | localize: lc\">\r\n <i class=\"fa\" [class.fa-download]=\"!workloadV2\" [class.fa-file-text]=\"workloadV2\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Riga della finestra: sempre visibile sopra la vista -->\r\n <div class=\"pv2-window\">\r\n <span><strong>{{live ? 'Live' : ('Storico' | localize: lc)}}</strong> &middot; {{instanceLabel}}</span>\r\n @if (current?.snapFrom; as snapFrom) {\r\n @if (live) {\r\n <span>{{\"Delta dall'ultimo snapshot delle\" | localize: lc}} {{hhmm(snapFrom)}} ({{minutesAgo(snapFrom)}} {{'min fa' | localize: lc}})</span>\r\n } @else {\r\n <span>{{'Snapshot dalle' | localize: lc}} {{ddmmhhmm(snapFrom)}} {{'alle' | localize: lc}} {{ddmmhhmm(current?.snapTo)}}</span>\r\n }\r\n } @else if (current && !live) {\r\n <span class=\"pv2-warn-text\">{{'Nessuno snapshot termina nella finestra scelta' | localize: lc}}</span>\r\n }\r\n @if (view == 'tree' && tree?.idleNode) {\r\n <span>{{tree.numNodes | pv2fmt: 'int'}} {{\"nodi nell'albero\" | localize: lc}} &middot; {{tree.numReturnedNodes | pv2fmt: 'int'}} {{'nella risposta' | localize: lc}}</span>\r\n }\r\n @if (view == 'flat' && flat) {\r\n <span>{{flat.numItems | pv2fmt: 'int'}} {{'eventi distinti' | localize: lc}}</span>\r\n }\r\n @if (loading) {\r\n <span class=\"pv2-muted\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</span>\r\n }\r\n </div>\r\n\r\n <div class=\"pv2-main\">\r\n <!-- Vista: albero o eventi per nome -->\r\n <div class=\"pv2-panel pv2-view\">\r\n <div class=\"pv2-view-bar\">\r\n <div class=\"pv2-seg\">\r\n <button type=\"button\" [class.pv2-on]=\"view == 'tree'\" (click)=\"setView('tree')\"><i class=\"fa fa-sitemap\"></i> {{'Albero' | localize: lc}}</button>\r\n <button type=\"button\" [class.pv2-on]=\"view == 'flat'\" (click)=\"setView('flat')\"><i class=\"fa fa-list\"></i> {{'Per nome' | localize: lc}}</button>\r\n </div>\r\n\r\n @if (view == 'tree') {\r\n <div class=\"pv2-field pv2-field-search\" [title]=\"'Per esempio MRD_METERREADINGS, ENQUEUE, ProfilesMgr' | localize: lc\">\r\n <form-input [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"searchText\" (ngModelChange)=\"searchText = $event; searchChanged()\" name=\"search\" [Placeholder]=\"'Cerca nei nomi o per id' | localize: lc\"></form-input>\r\n </div>\r\n <div class=\"pv2-field pv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"orderBy\" (ngModelChange)=\"setOrderBy($event)\" name=\"orderBy\" [Source]=\"TreeOrderOptions\" [Placeholder]=\"'Ordina per' | localize: lc\"></form-select>\r\n </div>\r\n <div class=\"pv2-group\" [class.pv2-disabled]=\"!!activeSearch\">\r\n <span class=\"pv2-group-label\">{{'Figli per nodo' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n @for (o of ChildrenOptions; track o.id) {\r\n <button type=\"button\" [disabled]=\"!!activeSearch\" [class.pv2-on]=\"maxChildren == o.id\" (click)=\"setMaxChildren(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"pv2-group\" [class.pv2-disabled]=\"!!activeSearch\">\r\n <span class=\"pv2-group-label\">{{'Soglia' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n @for (o of ThresholdOptions; track o.id) {\r\n <button type=\"button\" [disabled]=\"!!activeSearch\" [class.pv2-on]=\"minPercentage == o.id\" (click)=\"setMinPercentage(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"pv2-field pv2-field-search\">\r\n <form-input [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"flatSearch\" (ngModelChange)=\"flatSearch = $event; flatSearchChanged()\" name=\"flatSearch\" [Placeholder]=\"'Cerca nei nomi' | localize: lc\"></form-input>\r\n </div>\r\n <div class=\"pv2-field pv2-field-narrow pv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"flatType\" (ngModelChange)=\"flatType = $event; flatChanged()\" name=\"flatType\" [Source]=\"FlatTypes\" [Placeholder]=\"'Tipo' | localize: lc\"></form-select>\r\n </div>\r\n <div class=\"pv2-field pv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"flatOrderBy\" (ngModelChange)=\"flatOrderBy = $event; flatChanged()\" name=\"flatOrderBy\" [Source]=\"FlatOrderOptions\" [Placeholder]=\"'Ordina per' | localize: lc\"></form-select>\r\n </div>\r\n <div class=\"pv2-group\">\r\n <span class=\"pv2-group-label\">{{'Righe' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n @for (o of FlatRows; track o.id) {\r\n <button type=\"button\" [class.pv2-on]=\"flatTop == o.id\" (click)=\"setFlatTop(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"pv2-toggle\">\r\n <form-checkbox [FormLayout]=\"false\" [SliderMode]=\"true\" [Validation]=\"false\" [ngModel]=\"flatIncludeIdle\" (ngModelChange)=\"flatIncludeIdle = $event; flatChanged()\" name=\"flatIncludeIdle\"></form-checkbox>\r\n <span class=\"pv2-pointer\" (click)=\"flatIncludeIdle = !flatIncludeIdle; flatChanged()\">{{'Anche gli eventi idle' | localize: lc}}</span>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (view == 'tree') {\r\n <!-- Percorso della radice: ogni antenato riporta l'albero a quel nodo -->\r\n @if (tree?.idleNode && tree?.rootPath?.length) {\r\n <nav class=\"pv2-crumbs\">\r\n @for (item of tree.rootPath; track item.nodeId) {\r\n <button type=\"button\" class=\"pv2-crumb\" [title]=\"item.nodeName\" (click)=\"setRoot(item.nodeId)\">{{item.nodeName}}</button>\r\n <span class=\"pv2-crumb-sep\">&rsaquo;</span>\r\n }\r\n <span class=\"pv2-crumb pv2-crumb-current\" [title]=\"tree.idleNode.nodeName\">{{tree.idleNode.nodeName}}</span>\r\n </nav>\r\n }\r\n\r\n @if (treeNotice) {\r\n <div class=\"pv2-alert pv2-alert-warn\">\r\n {{treeNotice}}\r\n <button type=\"button\" class=\"pv2-alert-close\" [title]=\"'Chiudi' | localize: lc\" (click)=\"treeNotice = null\">&times;</button>\r\n </div>\r\n }\r\n @if (activeSearch && tree) {\r\n @if (matchCount > 0) {\r\n <div class=\"pv2-alert\" [class.pv2-alert-warn]=\"tree.truncated\">\r\n {{matchCount | pv2fmt: 'int'}} {{'nodi trovati, con i loro antenati.' | localize: lc}}\r\n @if (tree.truncated) {\r\n <strong>{{'I nodi trovati sono pi\u00F9 di 1.000: sono mostrati i primi per l\\'ordinamento scelto.' | localize: lc}}</strong>\r\n }\r\n </div>\r\n } @else if (!treeLoading) {\r\n <div class=\"pv2-alert\">{{'Nessun nodo contiene' | localize: lc}} &laquo;{{activeSearch}}&raquo;.</div>\r\n }\r\n }\r\n @if (treeError) {\r\n <div class=\"pv2-alert pv2-alert-error\">{{treeError}}</div>\r\n }\r\n\r\n <div class=\"pv2-table-wrap\" tabindex=\"0\" (keydown)=\"treeKeydown($event)\">\r\n <table class=\"pv2-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"pv2-col-node\">{{'Nodo' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Chiamate' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Tempo' | localize: lc}}</th>\r\n <th>{{snapMode ? '\u0394 ' : ''}}{{'% del padre' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{(snapMode ? '\u0394 Proprio' : 'Tempo proprio') | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Medio' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Picco' | localize: lc}}</th>\r\n @if (live) {\r\n <th class=\"pv2-num\">{{'Thread ora' | localize: lc}}</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (row of rows; track row.key) {\r\n @if (row.kind == 'node') {\r\n <tr [id]=\"'pv2-row-' + row.node.nodeId\" class=\"pv2-row\"\r\n [class.pv2-selected]=\"row.node.nodeId == selectedId\"\r\n [class.pv2-idle]=\"isIdle(row.node.eventTypeName, row.node.waitClassName)\"\r\n (click)=\"rowClicked(row.node)\" (dblclick)=\"toggle(row.node)\">\r\n <td class=\"pv2-col-node\">\r\n <div class=\"pv2-node\" [style.padding-left.px]=\"row.depth * 16\">\r\n @if (hasChildren(row.node)) {\r\n <button type=\"button\" class=\"pv2-caret\" tabindex=\"-1\" [attr.aria-expanded]=\"isExpanded(row.node)\" (click)=\"toggle(row.node, $event)\">\r\n <i class=\"fa\" [class.fa-caret-down]=\"isExpanded(row.node)\" [class.fa-caret-right]=\"!isExpanded(row.node)\"></i>\r\n </button>\r\n } @else {\r\n <span class=\"pv2-caret\"></span>\r\n }\r\n <span class=\"pv2-type\"><span class=\"pv2-dot\" [style.background]=\"typeColor(row.node.eventTypeName, row.node.waitClassName)\"></span>{{typeLabel(row.node.eventTypeName, row.node.waitClassName)}}</span>\r\n <span class=\"pv2-name\" [title]=\"row.node.nodeName\">@if (row.node.searchMatch && activeSearch) {@for (part of nameParts(row.node.nodeName, activeSearch); track $index) {@if (part.hit) {<mark>{{part.text}}</mark>} @else {<span>{{part.text}}</span>}}} @else {<span>{{row.node.nodeName}}</span>}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{calls(row.node) | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{time(row.node) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-perc\" [title]=\"(parentPerc(row.node) | pv2fmt: 'pct') + ' ' + ('del padre' | localize: lc) + ' \u00B7 ' + (firstLevelPerc(row.node) | pv2fmt: 'pct') + ' ' + ('del nodo di primo livello' | localize: lc)\">\r\n <div class=\"pv2-perc-cell\">\r\n <div class=\"pv2-bar\"><div [style.width.%]=\"parentPerc(row.node)\" [style.background]=\"typeColor(row.node.eventTypeName, row.node.waitClassName)\"></div></div>\r\n <span>{{parentPerc(row.node) | pv2fmt: 'pct'}}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{self(row.node) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{avg(row.node) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{peak(row.node) | pv2fmt: 'dur'}}</td>\r\n @if (live) {\r\n <td class=\"pv2-num pv2-threads\">\r\n @if (isLongRunning(row.node.eventTypeName, row.node.waitClassName, row.node.activeMaxTime)) {\r\n <span class=\"pv2-long\" [title]=\"('Esecuzione in corso da' | localize: lc) + ' ' + duration(row.node.activeMaxTime) + ': ' + ('forse bloccata' | localize: lc)\"><i class=\"fa fa-clock\"></i> {{duration(row.node.activeMaxTime)}}</span>\r\n }\r\n @if (row.node.activeThreads > 0) {\r\n <span class=\"pv2-active\"><span class=\"pv2-active-dot\"></span>{{row.node.activeThreads}}</span>\r\n } @else {\r\n <span class=\"pv2-muted\">{{row.node.activeThreads ?? '\u2014'}}</span>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n } @else if (row.kind == 'loading') {\r\n <tr class=\"pv2-service\">\r\n <td [attr.colspan]=\"live ? 8 : 7\">\r\n <div [style.padding-left.px]=\"row.depth * 16 + 24\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento dei figli\u2026' | localize: lc}}</div>\r\n </td>\r\n </tr>\r\n } @else {\r\n <tr class=\"pv2-service\">\r\n <td [attr.colspan]=\"live ? 8 : 7\">\r\n <div [style.padding-left.px]=\"row.depth * 16 + 24\">\r\n {{row.node.omittedChildren | pv2fmt: 'int'}} {{'figli non mostrati' | localize: lc}} &middot; {{omittedTime(row.node) | pv2fmt: 'dur'}}\r\n @if (!snapMode) {\r\n &middot; {{row.node.omittedCalls | pv2fmt: 'int'}} {{'chiamate' | localize: lc}}\r\n }\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"showAll(row.node, $event)\">{{'Mostra tutti' | localize: lc}}</button>\r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n @if (rows.length == 0 && !treeError) {\r\n <div class=\"pv2-empty\">\r\n @if (!tree) {\r\n {{'Caricamento\u2026' | localize: lc}}\r\n } @else if (!tree.snapFrom && !live) {\r\n {{'Nessuno snapshot termina nella finestra scelta: allargala o spostala indietro.' | localize: lc}}\r\n } @else {\r\n {{\"Il nodo non c'\u00E8 nelle istanze scelte.\" | localize: lc}}\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n @if (flatError) {\r\n <div class=\"pv2-alert pv2-alert-error\">{{flatError}}</div>\r\n }\r\n <div class=\"pv2-table-wrap\">\r\n <table class=\"pv2-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"pv2-col-node\">{{'Evento' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{'Percorsi' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Chiamate' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{(snapMode ? '\u0394 Proprio' : 'Tempo proprio') | localize: lc}}</th>\r\n <th>{{snapMode ? '\u0394 ' : ''}}{{'% proprio' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Tempo' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Medio' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{(snapMode ? \"Picco dall'avvio\" : 'Picco') | localize: lc}}</th>\r\n @if (live) {\r\n <th class=\"pv2-num\">{{'Thread' | localize: lc}}</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (item of flat?.items ?? []; track flatKey(item)) {\r\n <tr class=\"pv2-row\" [class.pv2-idle]=\"isIdle(item.eventTypeName, item.waitClassName)\" [class.pv2-selected]=\"openFlat.has(flatKey(item))\" (click)=\"toggleFlat(item)\">\r\n <td class=\"pv2-col-node\">\r\n <div class=\"pv2-node\">\r\n <span class=\"pv2-caret\"><i class=\"fa\" [class.fa-caret-down]=\"openFlat.has(flatKey(item))\" [class.fa-caret-right]=\"!openFlat.has(flatKey(item))\"></i></span>\r\n <span class=\"pv2-type\"><span class=\"pv2-dot\" [style.background]=\"typeColor(item.eventTypeName, item.waitClassName)\"></span>{{typeLabel(item.eventTypeName, item.waitClassName)}}</span>\r\n <span class=\"pv2-name\" [title]=\"item.nodeName\">@for (part of nameParts(item.nodeName, (flatSearch ?? '').trim()); track $index) {@if (part.hit) {<mark>{{part.text}}</mark>} @else {<span>{{part.text}}</span>}}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{item.numPaths | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{flatCalls(item) | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{flatSelf(item) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-perc\">\r\n <div class=\"pv2-perc-cell\">\r\n <div class=\"pv2-bar\"><div [style.width.%]=\"flatPerc(item)\" [style.background]=\"typeColor(item.eventTypeName, item.waitClassName)\"></div></div>\r\n <span>{{flatPerc(item) | pv2fmt: 'pct'}}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{flatTime(item) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{flatAvg(item) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{item.peakTime | pv2fmt: 'dur'}}</td>\r\n @if (live) {\r\n <td class=\"pv2-num pv2-threads\">\r\n @if (item.activeThreads > 0) {\r\n <span class=\"pv2-active\"><span class=\"pv2-active-dot\"></span>{{item.activeThreads}}</span>\r\n } @else {\r\n <span class=\"pv2-muted\">{{item.activeThreads ?? '\u2014'}}</span>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n @if (openFlat.has(flatKey(item))) {\r\n <tr class=\"pv2-sub\">\r\n <td [attr.colspan]=\"live ? 9 : 8\">\r\n <div class=\"pv2-sub-title\">{{'Nodi con pi\u00F9 tempo' | localize: lc}}</div>\r\n @for (id of item.nodeIds ?? []; track id) {\r\n <div class=\"pv2-sub-item\">\r\n <code>{{id}}</code>\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"showDetail(id, $event)\">{{'Dettaglio' | localize: lc}}</button>\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"openInTree(id, $event)\">{{\"Apri nell'albero\" | localize: lc}}</button>\r\n </div>\r\n }\r\n @if (item.numPaths > (item.nodeIds?.length ?? 0)) {\r\n <div class=\"pv2-muted\">+{{item.numPaths - (item.nodeIds?.length ?? 0) | pv2fmt: 'int'}} {{'altri' | localize: lc}}</div>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n @if (!flatError && (!flat || !flat.items?.length)) {\r\n <div class=\"pv2-empty\">{{(flat ? 'Nessun evento con questi filtri.' : 'Caricamento\u2026') | localize: lc}}</div>\r\n }\r\n </div>\r\n @if (flat?.items?.length) {\r\n <div class=\"pv2-footer\">\r\n {{flat.numItems | pv2fmt: 'int'}} {{'eventi distinti' | localize: lc}}@if (flat.numItems > flat.items.length) {, {{'mostrati i primi' | localize: lc}} {{flat.items.length | pv2fmt: 'int'}}}.\r\n {{'La percentuale \u00E8 sul tempo proprio di tutti gli eventi non idle.' | localize: lc}}\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Dettaglio del nodo selezionato, lo stesso per le due viste -->\r\n <aside class=\"pv2-panel pv2-detail\" [class.pv2-stale]=\"detailStale\">\r\n @if (!selectedId) {\r\n <div class=\"pv2-placeholder\">{{'Seleziona un nodo per vederne il dettaglio.' | localize: lc}}</div>\r\n } @else if (detailMissing && !detailLoading) {\r\n <div class=\"pv2-placeholder\">{{\"Il nodo non c'\u00E8 nelle istanze o nella finestra scelte.\" | localize: lc}}</div>\r\n } @else if (detailError && !detailLoading) {\r\n <div class=\"pv2-placeholder pv2-error-text\">{{detailError}}</div>\r\n } @else if (!detail) {\r\n <div class=\"pv2-placeholder\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</div>\r\n } @else {\r\n <div class=\"pv2-d-head\">\r\n <span class=\"pv2-type\"><span class=\"pv2-dot\" [style.background]=\"typeColor(detail.eventTypeName, detail.waitClassName)\"></span>{{typeLabel(detail.eventTypeName, detail.waitClassName)}}</span>\r\n @if (detailLoading) {\r\n <i class=\"fa fa-spinner fa-spin pv2-muted\"></i>\r\n }\r\n </div>\r\n <div class=\"pv2-fullname\">{{detail.nodeName}}</div>\r\n <div class=\"pv2-id\">\r\n Id <code #idText>{{detail.nodeId}}</code>\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"copyId(idText)\">{{(copied ? 'Copiato' : 'Copia') | localize: lc}}</button>\r\n </div>\r\n\r\n <div class=\"pv2-d-title\">{{'Percorso' | localize: lc}}</div>\r\n <div class=\"pv2-path\">\r\n @for (item of detail.fullWaitStack ?? []; track item.nodeId; let i = $index) {\r\n <button type=\"button\" class=\"pv2-path-item\" [style.padding-left.px]=\"i * 10\" [title]=\"item.nodeName\" (click)=\"select(item.nodeId)\">\r\n <span class=\"pv2-dot\" [style.background]=\"typeColor(item.eventTypeName, item.waitClassName)\"></span>{{item.nodeName}}\r\n </button>\r\n }\r\n <div class=\"pv2-path-item pv2-path-current\" [style.padding-left.px]=\"(detail.fullWaitStack?.length ?? 0) * 10\" [title]=\"detail.nodeName\">\r\n <span class=\"pv2-dot\" [style.background]=\"typeColor(detail.eventTypeName, detail.waitClassName)\"></span>{{detail.nodeName}}\r\n </div>\r\n </div>\r\n\r\n <div class=\"pv2-d-title\">{{(live ? \"Dall'avvio\" : 'Nella finestra') | localize: lc}}</div>\r\n <dl class=\"pv2-kv\">\r\n <dt>{{'Esecuzioni' | localize: lc}}</dt><dd>{{detail.executions | pv2fmt: 'int'}}</dd>\r\n <dt>{{'Tempo totale' | localize: lc}}</dt><dd>{{detail.elapsedTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Tempo proprio' | localize: lc}}</dt><dd>{{detail.elapsedTimeLessChildren | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Medio' | localize: lc}}</dt><dd>{{detail.avgTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Picco' | localize: lc}}</dt><dd>{{detail.peakTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Minimo' | localize: lc}}</dt><dd>{{detail.minTime ? (detail.minTime | pv2fmt: 'dur') : '\u2014'}}</dd>\r\n </dl>\r\n\r\n @if (live) {\r\n <div class=\"pv2-d-title\">{{\"Dall'ultimo snapshot\" | localize: lc}}, {{hhmm(detail.snapFrom)}}</div>\r\n <dl class=\"pv2-kv\">\r\n <dt>{{'Esecuzioni' | localize: lc}}</dt><dd>{{detail.snapDeltaExecutions | pv2fmt: 'int'}}</dd>\r\n <dt>{{'Tempo totale' | localize: lc}}</dt><dd>{{detail.snapDeltaElapsedTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Tempo proprio' | localize: lc}}</dt><dd>{{detail.snapDeltaElapsedTimeLessChildren | pv2fmt: 'dur'}}</dd>\r\n </dl>\r\n\r\n <div class=\"pv2-d-title\">{{'Adesso' | localize: lc}}</div>\r\n <dl class=\"pv2-kv\">\r\n <dt>{{'Thread dentro' | localize: lc}}</dt>\r\n <dd>\r\n @if (detail.activeThreads > 0) {\r\n <span class=\"pv2-active\"><span class=\"pv2-active-dot\"></span>{{detail.activeThreads}}</span>\r\n } @else {\r\n {{detail.activeThreads ?? '\u2014'}}\r\n }\r\n </dd>\r\n <dt>{{'Esecuzione pi\u00F9 lunga' | localize: lc}}</dt>\r\n <dd>\r\n @if (detail.activeThreads > 0) {\r\n {{detail.activeMaxTime | pv2fmt: 'dur'}}\r\n @if (isLongRunning(detail.eventTypeName, detail.waitClassName, detail.activeMaxTime)) {\r\n <span class=\"pv2-long\"><i class=\"fa fa-clock\"></i> {{'forse bloccata' | localize: lc}}</span>\r\n }\r\n } @else {\r\n \u2014\r\n }\r\n </dd>\r\n <dt>{{'Ultima uscita' | localize: lc}}</dt><dd>{{relative(detail.lastExitTime)}}</dd>\r\n </dl>\r\n }\r\n\r\n @if (typeParts.length > 0) {\r\n <div class=\"pv2-d-title\">{{'Tempo proprio del sottoalbero per tipo' | localize: lc}}</div>\r\n <div class=\"pv2-stack\">\r\n @for (part of typeParts; track part.type) {\r\n <div [style.width.%]=\"part.percentage\" [style.background]=\"part.color\" [title]=\"part.label + ' ' + (part.percentage | pv2fmt: 'pct')\"></div>\r\n }\r\n </div>\r\n <div class=\"pv2-legend\">\r\n @for (part of typeParts; track part.type) {\r\n <span class=\"pv2-legend-item\"><span class=\"pv2-swatch\" [style.background]=\"part.color\"></span>{{part.label}} <strong>{{part.percentage | pv2fmt: 'pct'}}</strong></span>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"pv2-d-title\">{{'Sessioni attive sul nodo e sui discendenti' | localize: lc}}</div>\r\n @if (chart) {\r\n <div class=\"pv2-chart\">\r\n <div class=\"pv2-chart-y\">\r\n <span>{{axisValue(chart.max)}}</span>\r\n <span>{{axisValue(chart.max / 2)}}</span>\r\n <span>0</span>\r\n </div>\r\n <div class=\"pv2-chart-plot\">\r\n <svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\r\n <line x1=\"0\" y1=\"50\" x2=\"100\" y2=\"50\" class=\"pv2-chart-grid\" vector-effect=\"non-scaling-stroke\" />\r\n <path [attr.d]=\"chart.area\" [attr.fill]=\"chart.color\" fill-opacity=\"0.25\" />\r\n <path [attr.d]=\"chart.line\" [attr.stroke]=\"chart.color\" fill=\"none\" stroke-width=\"1.5\" vector-effect=\"non-scaling-stroke\" />\r\n </svg>\r\n <span class=\"pv2-chart-last\" [style.left.%]=\"chart.lastX\" [style.top.%]=\"chart.lastY\" [style.background]=\"chart.color\"></span>\r\n </div>\r\n <span></span>\r\n <div class=\"pv2-chart-x\">\r\n <span>{{chart.start}}</span>\r\n <span>{{chart.middle}}</span>\r\n <span>{{chart.end}}</span>\r\n </div>\r\n </div>\r\n <div class=\"pv2-caption\">\r\n {{'Media' | localize: lc}} {{axisValue(chart.average)}} &middot; {{'picco' | localize: lc}} {{axisValue(chart.peak)}} {{'sessioni' | localize: lc}}.\r\n {{chart.points | pv2fmt: 'int'}} {{\"punti dell'API\" | localize: lc}},\r\n @if (chart.interval == 1) {\r\n {{'uno al secondo' | localize: lc}},\r\n } @else {\r\n {{'uno ogni' | localize: lc}} {{chart.interval}} s,\r\n }\r\n {{'raggruppati a' | localize: lc}} {{chart.group}} s\r\n </div>\r\n } @else {\r\n <div class=\"pv2-muted pv2-small\">{{'Nessun campione in memoria per questa finestra: il grafico copre solo le ultime due ore circa.' | localize: lc}}</div>\r\n }\r\n\r\n @if (detail.instances?.length) {\r\n <div class=\"pv2-d-title\">{{'Per istanza' | localize: lc}}</div>\r\n <table class=\"pv2-mini\">\r\n <thead>\r\n <tr>\r\n <th>{{'Istanza' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{'Esecuzioni' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{'Tempo' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{'Proprio' | localize: lc}}</th>\r\n @if (live) {\r\n <th class=\"pv2-num\">{{'Thread' | localize: lc}}</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (inst of detail.instances; track inst.instanceId) {\r\n <tr>\r\n <td><span class=\"pv2-ellipsis\" [title]=\"inst.instanceId\">{{inst.instanceId}}</span></td>\r\n <td class=\"pv2-num\">{{inst.executions | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{inst.elapsedTime | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{inst.elapsedTimeLessChildren | pv2fmt: 'dur'}}</td>\r\n @if (live) {\r\n <td class=\"pv2-num\">{{inst.activeThreads ?? '\u2014'}}</td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n\r\n <div class=\"pv2-d-foot\">\r\n <span class=\"pv2-muted\">{{detail.numChildren | pv2fmt: 'int'}} {{'figli diretti' | localize: lc}} &middot; {{detail.numDescendants | pv2fmt: 'int'}} {{'discendenti' | localize: lc}}</span>\r\n <button type=\"button\" class=\"pv2-btn\" (click)=\"setRoot(detail.nodeId)\"><i class=\"fa fa-sitemap\"></i> {{'Apri come radice' | localize: lc}}</button>\r\n </div>\r\n }\r\n </aside>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: ["@charset \"UTF-8\";:host{display:block}:host ::ng-deep .pv2-noclear .p-select-clear-icon{display:none}.pv2-body{container-type:inline-size}.pv2-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 22px;padding-top:6px}.pv2-cluster{display:flex;flex-wrap:nowrap;align-items:center;gap:10px}.pv2-cluster-end{margin-left:auto}.pv2-field{width:190px}.pv2-field-wide{width:290px}.pv2-field-narrow{width:140px}.pv2-field-search{flex:1 1 220px;min-width:200px;max-width:340px}.pv2-toggle{display:flex;align-items:center;gap:8px;height:39px;white-space:nowrap}.pv2-pointer{cursor:pointer}.pv2-group{position:relative;display:inline-flex}.pv2-group.pv2-disabled{opacity:.5}.pv2-group-label{position:absolute;top:-8px;left:8px;z-index:1;padding:0 4px;background:var(--p-content-background, #fff);color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.pv2-seg{display:inline-flex;height:39px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);overflow:hidden;background:var(--p-content-background, #fff)}.pv2-seg button{display:inline-flex;align-items:center;gap:6px;padding:0 12px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.pv2-seg button:first-child{border-left:0}.pv2-seg button:hover:not(.pv2-on):not(:disabled){background:var(--p-content-hover-background, #f8fafc)}.pv2-seg button:disabled{cursor:default}.pv2-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.pv2-seg button.pv2-on{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca);font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.pv2-live-dot{width:8px;height:8px;border-radius:50%;background:#16a34a}.pv2-window{display:flex;flex-wrap:wrap;align-items:center;row-gap:4px;margin-top:12px;padding:7px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155);font-size:13px}.pv2-window>span+span:before{content:\"\\b7\";margin:0 10px;color:var(--p-text-muted-color, #64748b)}.pv2-warn-text{color:#92400e}.pv2-main{display:grid;grid-template-columns:minmax(0,1fr) 380px;gap:14px;align-items:start;margin-top:14px}@container (max-width: 1000px){.pv2-main{grid-template-columns:minmax(0,1fr)}.pv2-detail{position:static;max-height:none}}.pv2-panel{padding:12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0}.pv2-view-bar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 10px;padding-top:6px;margin-bottom:10px}.pv2-crumbs{display:flex;flex-wrap:wrap;align-items:center;gap:2px 4px;margin-bottom:8px;font-size:12px}.pv2-crumb{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:2px 6px;border:0;border-radius:4px;background:transparent;color:var(--p-primary-color, #6366f1);cursor:pointer}.pv2-crumb:hover{background:var(--p-highlight-background, #eef2ff)}.pv2-crumb-current{color:var(--p-text-color, #334155);font-weight:500;cursor:default}.pv2-crumb-current:hover{background:transparent}.pv2-crumb-sep{color:var(--p-text-muted-color, #64748b)}.pv2-alert{position:relative;margin-bottom:8px;padding:6px 30px 6px 10px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);font-size:13px}.pv2-alert.pv2-alert-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.pv2-alert.pv2-alert-error{border-color:#fca5a5;background:#fef2f2;color:#991b1b}.pv2-alert-close{position:absolute;top:3px;right:6px;border:0;background:transparent;color:inherit;font-size:16px;cursor:pointer}.pv2-table-wrap{max-height:68vh;overflow:auto;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px)}.pv2-table-wrap:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:1px}.pv2-table{width:100%;min-width:740px;border-collapse:separate;border-spacing:0;font-size:13px;color:var(--p-text-color, #334155)}.pv2-table th{position:sticky;top:0;z-index:1;padding:7px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-muted-color, #64748b);font-size:12px;font-weight:500;white-space:nowrap;text-align:left}.pv2-table td{padding:4px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);white-space:nowrap;vertical-align:middle}.pv2-col-node{width:100%;max-width:0}.pv2-num{text-align:right!important;font-variant-numeric:tabular-nums}.pv2-row{cursor:pointer}.pv2-row:hover td{background:var(--p-content-hover-background, #f8fafc)}.pv2-row.pv2-selected td{background:var(--p-highlight-background, #eef2ff)}.pv2-row.pv2-idle td{color:var(--p-text-muted-color, #64748b)}.pv2-row.pv2-idle td .pv2-name{opacity:.75}.pv2-node{display:flex;align-items:center;gap:6px;min-width:0}.pv2-caret{flex:none;width:18px;height:20px;padding:0;border:0;background:transparent;color:var(--p-text-muted-color, #64748b);text-align:center;cursor:pointer}.pv2-type{flex:none;display:inline-flex;align-items:center;gap:5px;padding:0 6px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:10px;color:var(--p-text-muted-color, #64748b);font-size:11px;line-height:18px;white-space:nowrap}.pv2-dot{flex:none;display:inline-block;width:8px;height:8px;border-radius:50%}.pv2-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pv2-name mark{padding:0 1px;border-radius:2px;background:#fde68a;color:#1f2937}.pv2-perc{width:1%}.pv2-perc-cell{display:flex;align-items:center;gap:8px}.pv2-bar{flex:none;width:64px;height:7px;border-radius:2px;overflow:hidden;background:var(--p-content-hover-background, #f8fafc);box-shadow:inset 0 0 0 1px var(--p-content-border-color, #e2e8f0)}.pv2-bar>div{height:100%}.pv2-threads>*+*{margin-left:8px}.pv2-active{display:inline-flex;align-items:center;gap:5px;font-weight:500}.pv2-active-dot{width:7px;height:7px;border-radius:50%;background:#16a34a;box-shadow:0 0 0 3px #16a34a2e}.pv2-long{display:inline-flex;align-items:center;gap:4px;padding:0 6px;border:1px solid #fcd34d;border-radius:10px;background:#fffbeb;color:#92400e;font-size:11px;line-height:18px;white-space:nowrap}.pv2-service td{padding:5px 10px;color:var(--p-text-muted-color, #64748b);font-size:12px;background:var(--p-content-background, #fff)}.pv2-sub td{padding:8px 10px 10px 34px;background:var(--p-content-hover-background, #f8fafc);white-space:normal}.pv2-sub-title{margin-bottom:4px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-sub-item{display:flex;align-items:center;gap:10px;padding:2px 0}.pv2-sub-item code{font-size:12px}.pv2-link{margin-left:6px;padding:0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;cursor:pointer}.pv2-link:hover{text-decoration:underline}.pv2-empty{padding:28px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.pv2-footer{margin-top:8px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-muted{color:var(--p-text-muted-color, #64748b)}.pv2-small{font-size:12px}.pv2-detail{position:sticky;top:10px;max-height:calc(100vh - 20px);overflow:auto;font-size:13px;color:var(--p-text-color, #334155);transition:opacity .15s}.pv2-detail.pv2-stale{opacity:.55}.pv2-placeholder{padding:36px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.pv2-error-text{color:#dc2626}.pv2-d-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.pv2-fullname{max-height:140px;overflow:auto;padding:8px 10px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);font-family:ui-monospace,Cascadia Mono,Consolas,monospace;font-size:12px;white-space:pre-wrap;word-break:break-word}.pv2-id{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-id code{font-size:12px}.pv2-d-title{margin:16px 0 6px;padding-top:10px;border-top:1px solid var(--p-content-border-color, #e2e8f0);color:var(--p-text-muted-color, #64748b);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.pv2-path{display:flex;flex-direction:column}.pv2-path-item{display:flex;align-items:center;gap:6px;max-width:100%;padding:2px 0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.pv2-path-item:hover{text-decoration:underline}.pv2-path-current{color:var(--p-text-color, #334155);font-weight:600;cursor:default}.pv2-path-current:hover{text-decoration:none}.pv2-kv{display:grid;grid-template-columns:auto 1fr;gap:3px 14px;margin:0}.pv2-kv dt{color:var(--p-text-muted-color, #64748b);font-weight:400}.pv2-kv dd{margin:0;text-align:right;font-variant-numeric:tabular-nums}.pv2-stack{display:flex;height:12px;border-radius:3px;overflow:hidden;background:var(--p-content-hover-background, #f8fafc)}.pv2-legend{display:flex;flex-wrap:wrap;gap:4px 12px;margin-top:6px;font-size:12px}.pv2-legend-item{display:inline-flex;align-items:center;gap:5px}.pv2-swatch{width:10px;height:10px;border-radius:2px}.pv2-chart{display:grid;grid-template-columns:auto 1fr;gap:2px 6px}.pv2-chart-y{display:flex;flex-direction:column;justify-content:space-between;height:110px;color:var(--p-text-muted-color, #64748b);font-size:11px;text-align:right;line-height:1}.pv2-chart-plot{position:relative;height:110px;border-left:1px solid var(--p-content-border-color, #e2e8f0);border-bottom:1px solid var(--p-content-border-color, #e2e8f0)}.pv2-chart-plot svg{display:block;width:100%;height:100%}.pv2-chart-grid{stroke:var(--p-content-border-color, #e2e8f0);stroke-dasharray:3 3}.pv2-chart-last{position:absolute;width:8px;height:8px;border:2px solid var(--p-content-background, #fff);border-radius:50%;transform:translate(-50%,-50%)}.pv2-chart-x{display:flex;justify-content:space-between;color:var(--p-text-muted-color, #64748b);font-size:11px}.pv2-caption{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-mini{width:100%;font-size:12px}.pv2-mini th{padding:3px 6px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);color:var(--p-text-muted-color, #64748b);font-weight:500}.pv2-mini td{padding:3px 6px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0)}.pv2-ellipsis{display:inline-block;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.pv2-d-foot{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;margin-top:16px;padding-top:10px;border-top:1px solid var(--p-content-border-color, #e2e8f0)}.pv2-btn{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:13px;cursor:pointer}.pv2-btn:hover{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}\n"], dependencies: [{ kind: "directive", type: i7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i9.EsFormInputComponent, selector: "form-input", inputs: ["minlength", "maxlength", "pattern", "Password", "Type"] }, { kind: "component", type: i9.EsFormSelectComponent, selector: "form-select", inputs: ["OptionsTemplate"] }, { kind: "component", type: i9.EsFormDateTimeComponent, selector: "form-datetime" }, { kind: "component", type: i9.EsFormCheckboxComponent, selector: "form-checkbox", inputs: ["SliderMode"] }, { kind: "directive", type: i10.Widget, selector: "[widget]" }, { kind: "pipe", type: i1.LocalizePipe, name: "localize" }, { kind: "pipe", type: ProfilerFormatPipe, name: "pv2fmt" }] }); }
1973
+ }
1974
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerV2Component, decorators: [{
1975
+ type: Component,
1976
+ args: [{ selector: "profiler_v2", providers: [{ provide: LocalizationService, useClass: AdmProfilerV2Loc }], standalone: false, template: "<div class=\"row\">\r\n <div class=\"col-12\">\r\n <div class=\"card app-overflow-visible\" widget>\r\n <div class=\"card-header\">\r\n <span class=\"text-uppercase\">{{'Profiler del Server' | localize: lc}}</span>\r\n </div>\r\n <div class=\"card-body widget-body pv2-body\">\r\n <!-- Barra dei comandi: quali dati caricare. Ogni cambio ricarica la vista e il dettaglio aperto -->\r\n <div class=\"pv2-toolbar\">\r\n <div class=\"pv2-cluster\">\r\n <div class=\"pv2-group\">\r\n <span class=\"pv2-group-label\">{{'Sorgente' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n <button type=\"button\" [class.pv2-on]=\"live\" (click)=\"setLive(true)\"><span class=\"pv2-live-dot\"></span>Live</button>\r\n <button type=\"button\" [class.pv2-on]=\"!live\" (click)=\"setLive(false)\">{{'Storico' | localize: lc}}</button>\r\n </div>\r\n </div>\r\n <div class=\"pv2-field pv2-field-wide pv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"instance\" (ngModelChange)=\"setInstance($event)\" name=\"instance\" [Source]=\"Instances\" [Placeholder]=\"'Istanze' | localize: lc\"></form-select>\r\n </div>\r\n </div>\r\n\r\n @if (!live) {\r\n <div class=\"pv2-cluster\">\r\n <div class=\"pv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterfrom\" (ngModelChange)=\"filterfrom = $event; datesChanged()\" name=\"filterfrom\" [Placeholder]=\"'Dal' | localize: lc\"></form-datetime>\r\n </div>\r\n <div class=\"pv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterto\" (ngModelChange)=\"filterto = $event; datesChanged()\" name=\"filterto\" [Placeholder]=\"'Al' | localize: lc\"></form-datetime>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"pv2-group\">\r\n <span class=\"pv2-group-label\">{{'Valori' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n <button type=\"button\" [class.pv2-on]=\"!snapValues\" (click)=\"setSnapValues(false)\">{{\"Dall'avvio\" | localize: lc}}</button>\r\n <button type=\"button\" [class.pv2-on]=\"snapValues\" (click)=\"setSnapValues(true)\">{{\"Dall'ultimo snapshot\" | localize: lc}}</button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"pv2-cluster pv2-cluster-end\">\r\n <button type=\"button\" class=\"btn app-btn-search\" [disabled]=\"loading\" (click)=\"reloadAll()\" [title]=\"'Aggiorna' | localize: lc\">\r\n <i class=\"fa fa-refresh\" [class.fa-spin]=\"loading\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn app-btn-search\" (click)=\"downloadWorkloadReport()\" [title]=\"(workloadV2 ? 'Workload report' : 'Scarica il workload report') | localize: lc\">\r\n <i class=\"fa\" [class.fa-download]=\"!workloadV2\" [class.fa-file-text]=\"workloadV2\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Riga della finestra: sempre visibile sopra la vista -->\r\n <div class=\"pv2-window\">\r\n <span><strong>{{live ? 'Live' : ('Storico' | localize: lc)}}</strong> &middot; {{instanceLabel}}</span>\r\n @if (current?.snapFrom; as snapFrom) {\r\n @if (live) {\r\n <span>{{\"Delta dall'ultimo snapshot delle\" | localize: lc}} {{hhmm(snapFrom)}} ({{minutesAgo(snapFrom)}} {{'min fa' | localize: lc}})</span>\r\n } @else {\r\n <span>{{'Snapshot dalle' | localize: lc}} {{ddmmhhmm(snapFrom)}} {{'alle' | localize: lc}} {{ddmmhhmm(current?.snapTo)}}</span>\r\n }\r\n } @else if (current && !live) {\r\n <span class=\"pv2-warn-text\">{{'Nessuno snapshot termina nella finestra scelta' | localize: lc}}</span>\r\n }\r\n @if (view == 'tree' && tree?.idleNode) {\r\n <span>{{tree.numNodes | pv2fmt: 'int'}} {{\"nodi nell'albero\" | localize: lc}} &middot; {{tree.numReturnedNodes | pv2fmt: 'int'}} {{'nella risposta' | localize: lc}}</span>\r\n }\r\n @if (view == 'flat' && flat) {\r\n <span>{{flat.numItems | pv2fmt: 'int'}} {{'eventi distinti' | localize: lc}}</span>\r\n }\r\n @if (loading) {\r\n <span class=\"pv2-muted\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</span>\r\n }\r\n </div>\r\n\r\n <div class=\"pv2-main\">\r\n <!-- Vista: albero o eventi per nome -->\r\n <div class=\"pv2-panel pv2-view\">\r\n <div class=\"pv2-view-bar\">\r\n <div class=\"pv2-seg\">\r\n <button type=\"button\" [class.pv2-on]=\"view == 'tree'\" (click)=\"setView('tree')\"><i class=\"fa fa-sitemap\"></i> {{'Albero' | localize: lc}}</button>\r\n <button type=\"button\" [class.pv2-on]=\"view == 'flat'\" (click)=\"setView('flat')\"><i class=\"fa fa-list\"></i> {{'Per nome' | localize: lc}}</button>\r\n </div>\r\n\r\n @if (view == 'tree') {\r\n <div class=\"pv2-field pv2-field-search\" [title]=\"'Per esempio MRD_METERREADINGS, ENQUEUE, ProfilesMgr' | localize: lc\">\r\n <form-input [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"searchText\" (ngModelChange)=\"searchText = $event; searchChanged()\" name=\"search\" [Placeholder]=\"'Cerca nei nomi o per id' | localize: lc\"></form-input>\r\n </div>\r\n <div class=\"pv2-field pv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"orderBy\" (ngModelChange)=\"setOrderBy($event)\" name=\"orderBy\" [Source]=\"TreeOrderOptions\" [Placeholder]=\"'Ordina per' | localize: lc\"></form-select>\r\n </div>\r\n <div class=\"pv2-group\" [class.pv2-disabled]=\"!!activeSearch\">\r\n <span class=\"pv2-group-label\">{{'Figli per nodo' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n @for (o of ChildrenOptions; track o.id) {\r\n <button type=\"button\" [disabled]=\"!!activeSearch\" [class.pv2-on]=\"maxChildren == o.id\" (click)=\"setMaxChildren(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"pv2-group\" [class.pv2-disabled]=\"!!activeSearch\">\r\n <span class=\"pv2-group-label\">{{'Soglia' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n @for (o of ThresholdOptions; track o.id) {\r\n <button type=\"button\" [disabled]=\"!!activeSearch\" [class.pv2-on]=\"minPercentage == o.id\" (click)=\"setMinPercentage(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"pv2-field pv2-field-search\">\r\n <form-input [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"flatSearch\" (ngModelChange)=\"flatSearch = $event; flatSearchChanged()\" name=\"flatSearch\" [Placeholder]=\"'Cerca nei nomi' | localize: lc\"></form-input>\r\n </div>\r\n <div class=\"pv2-field pv2-field-narrow pv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"flatType\" (ngModelChange)=\"flatType = $event; flatChanged()\" name=\"flatType\" [Source]=\"FlatTypes\" [Placeholder]=\"'Tipo' | localize: lc\"></form-select>\r\n </div>\r\n <div class=\"pv2-field pv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"flatOrderBy\" (ngModelChange)=\"flatOrderBy = $event; flatChanged()\" name=\"flatOrderBy\" [Source]=\"FlatOrderOptions\" [Placeholder]=\"'Ordina per' | localize: lc\"></form-select>\r\n </div>\r\n <div class=\"pv2-group\">\r\n <span class=\"pv2-group-label\">{{'Righe' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n @for (o of FlatRows; track o.id) {\r\n <button type=\"button\" [class.pv2-on]=\"flatTop == o.id\" (click)=\"setFlatTop(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"pv2-toggle\">\r\n <form-checkbox [FormLayout]=\"false\" [SliderMode]=\"true\" [Validation]=\"false\" [ngModel]=\"flatIncludeIdle\" (ngModelChange)=\"flatIncludeIdle = $event; flatChanged()\" name=\"flatIncludeIdle\"></form-checkbox>\r\n <span class=\"pv2-pointer\" (click)=\"flatIncludeIdle = !flatIncludeIdle; flatChanged()\">{{'Anche gli eventi idle' | localize: lc}}</span>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (view == 'tree') {\r\n <!-- Percorso della radice: ogni antenato riporta l'albero a quel nodo -->\r\n @if (tree?.idleNode && tree?.rootPath?.length) {\r\n <nav class=\"pv2-crumbs\">\r\n @for (item of tree.rootPath; track item.nodeId) {\r\n <button type=\"button\" class=\"pv2-crumb\" [title]=\"item.nodeName\" (click)=\"setRoot(item.nodeId)\">{{item.nodeName}}</button>\r\n <span class=\"pv2-crumb-sep\">&rsaquo;</span>\r\n }\r\n <span class=\"pv2-crumb pv2-crumb-current\" [title]=\"tree.idleNode.nodeName\">{{tree.idleNode.nodeName}}</span>\r\n </nav>\r\n }\r\n\r\n @if (treeNotice) {\r\n <div class=\"pv2-alert pv2-alert-warn\">\r\n {{treeNotice}}\r\n <button type=\"button\" class=\"pv2-alert-close\" [title]=\"'Chiudi' | localize: lc\" (click)=\"treeNotice = null\">&times;</button>\r\n </div>\r\n }\r\n @if (activeSearch && tree) {\r\n @if (matchCount > 0) {\r\n <div class=\"pv2-alert\" [class.pv2-alert-warn]=\"tree.truncated\">\r\n {{matchCount | pv2fmt: 'int'}} {{'nodi trovati, con i loro antenati.' | localize: lc}}\r\n @if (tree.truncated) {\r\n <strong>{{'I nodi trovati sono pi\u00F9 di 1.000: sono mostrati i primi per l\\'ordinamento scelto.' | localize: lc}}</strong>\r\n }\r\n </div>\r\n } @else if (!treeLoading) {\r\n <div class=\"pv2-alert\">{{'Nessun nodo contiene' | localize: lc}} &laquo;{{activeSearch}}&raquo;.</div>\r\n }\r\n }\r\n @if (treeError) {\r\n <div class=\"pv2-alert pv2-alert-error\">{{treeError}}</div>\r\n }\r\n\r\n <div class=\"pv2-table-wrap\" tabindex=\"0\" (keydown)=\"treeKeydown($event)\">\r\n <table class=\"pv2-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"pv2-col-node\">{{'Nodo' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Chiamate' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Tempo' | localize: lc}}</th>\r\n <th>{{snapMode ? '\u0394 ' : ''}}{{'% del padre' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{(snapMode ? '\u0394 Proprio' : 'Tempo proprio') | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Medio' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Picco' | localize: lc}}</th>\r\n @if (live) {\r\n <th class=\"pv2-num\">{{'Thread ora' | localize: lc}}</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (row of rows; track row.key) {\r\n @if (row.kind == 'node') {\r\n <tr [id]=\"'pv2-row-' + row.node.nodeId\" class=\"pv2-row\"\r\n [class.pv2-selected]=\"row.node.nodeId == selectedId\"\r\n [class.pv2-idle]=\"isIdle(row.node.eventTypeName, row.node.waitClassName)\"\r\n (click)=\"rowClicked(row.node)\" (dblclick)=\"toggle(row.node)\">\r\n <td class=\"pv2-col-node\">\r\n <div class=\"pv2-node\" [style.padding-left.px]=\"row.depth * 16\">\r\n @if (hasChildren(row.node)) {\r\n <button type=\"button\" class=\"pv2-caret\" tabindex=\"-1\" [attr.aria-expanded]=\"isExpanded(row.node)\" (click)=\"toggle(row.node, $event)\">\r\n <i class=\"fa\" [class.fa-caret-down]=\"isExpanded(row.node)\" [class.fa-caret-right]=\"!isExpanded(row.node)\"></i>\r\n </button>\r\n } @else {\r\n <span class=\"pv2-caret\"></span>\r\n }\r\n <span class=\"pv2-type\"><span class=\"pv2-dot\" [style.background]=\"typeColor(row.node.eventTypeName, row.node.waitClassName)\"></span>{{typeLabel(row.node.eventTypeName, row.node.waitClassName)}}</span>\r\n <span class=\"pv2-name\" [title]=\"row.node.nodeName\">@if (row.node.searchMatch && activeSearch) {@for (part of nameParts(row.node.nodeName, activeSearch); track $index) {@if (part.hit) {<mark>{{part.text}}</mark>} @else {<span>{{part.text}}</span>}}} @else {<span>{{row.node.nodeName}}</span>}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{calls(row.node) | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{time(row.node) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-perc\" [title]=\"(parentPerc(row.node) | pv2fmt: 'pct') + ' ' + ('del padre' | localize: lc) + ' \u00B7 ' + (firstLevelPerc(row.node) | pv2fmt: 'pct') + ' ' + ('del nodo di primo livello' | localize: lc)\">\r\n <div class=\"pv2-perc-cell\">\r\n <div class=\"pv2-bar\"><div [style.width.%]=\"parentPerc(row.node)\" [style.background]=\"typeColor(row.node.eventTypeName, row.node.waitClassName)\"></div></div>\r\n <span>{{parentPerc(row.node) | pv2fmt: 'pct'}}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{self(row.node) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{avg(row.node) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{peak(row.node) | pv2fmt: 'dur'}}</td>\r\n @if (live) {\r\n <td class=\"pv2-num pv2-threads\">\r\n @if (isLongRunning(row.node.eventTypeName, row.node.waitClassName, row.node.activeMaxTime)) {\r\n <span class=\"pv2-long\" [title]=\"('Esecuzione in corso da' | localize: lc) + ' ' + duration(row.node.activeMaxTime) + ': ' + ('forse bloccata' | localize: lc)\"><i class=\"fa fa-clock\"></i> {{duration(row.node.activeMaxTime)}}</span>\r\n }\r\n @if (row.node.activeThreads > 0) {\r\n <span class=\"pv2-active\"><span class=\"pv2-active-dot\"></span>{{row.node.activeThreads}}</span>\r\n } @else {\r\n <span class=\"pv2-muted\">{{row.node.activeThreads ?? '\u2014'}}</span>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n } @else if (row.kind == 'loading') {\r\n <tr class=\"pv2-service\">\r\n <td [attr.colspan]=\"live ? 8 : 7\">\r\n <div [style.padding-left.px]=\"row.depth * 16 + 24\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento dei figli\u2026' | localize: lc}}</div>\r\n </td>\r\n </tr>\r\n } @else {\r\n <tr class=\"pv2-service\">\r\n <td [attr.colspan]=\"live ? 8 : 7\">\r\n <div [style.padding-left.px]=\"row.depth * 16 + 24\">\r\n {{row.node.omittedChildren | pv2fmt: 'int'}} {{'figli non mostrati' | localize: lc}} &middot; {{omittedTime(row.node) | pv2fmt: 'dur'}}\r\n @if (!snapMode) {\r\n &middot; {{row.node.omittedCalls | pv2fmt: 'int'}} {{'chiamate' | localize: lc}}\r\n }\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"showAll(row.node, $event)\">{{'Mostra tutti' | localize: lc}}</button>\r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n @if (rows.length == 0 && !treeError) {\r\n <div class=\"pv2-empty\">\r\n @if (!tree) {\r\n {{'Caricamento\u2026' | localize: lc}}\r\n } @else if (!tree.snapFrom && !live) {\r\n {{'Nessuno snapshot termina nella finestra scelta: allargala o spostala indietro.' | localize: lc}}\r\n } @else {\r\n {{\"Il nodo non c'\u00E8 nelle istanze scelte.\" | localize: lc}}\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n @if (flatError) {\r\n <div class=\"pv2-alert pv2-alert-error\">{{flatError}}</div>\r\n }\r\n <div class=\"pv2-table-wrap\">\r\n <table class=\"pv2-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"pv2-col-node\">{{'Evento' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{'Percorsi' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Chiamate' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{(snapMode ? '\u0394 Proprio' : 'Tempo proprio') | localize: lc}}</th>\r\n <th>{{snapMode ? '\u0394 ' : ''}}{{'% proprio' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Tempo' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Medio' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{(snapMode ? \"Picco dall'avvio\" : 'Picco') | localize: lc}}</th>\r\n @if (live) {\r\n <th class=\"pv2-num\">{{'Thread' | localize: lc}}</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (item of flat?.items ?? []; track flatKey(item)) {\r\n <tr class=\"pv2-row\" [class.pv2-idle]=\"isIdle(item.eventTypeName, item.waitClassName)\" [class.pv2-selected]=\"openFlat.has(flatKey(item))\" (click)=\"toggleFlat(item)\">\r\n <td class=\"pv2-col-node\">\r\n <div class=\"pv2-node\">\r\n <span class=\"pv2-caret\"><i class=\"fa\" [class.fa-caret-down]=\"openFlat.has(flatKey(item))\" [class.fa-caret-right]=\"!openFlat.has(flatKey(item))\"></i></span>\r\n <span class=\"pv2-type\"><span class=\"pv2-dot\" [style.background]=\"typeColor(item.eventTypeName, item.waitClassName)\"></span>{{typeLabel(item.eventTypeName, item.waitClassName)}}</span>\r\n <span class=\"pv2-name\" [title]=\"item.nodeName\">@for (part of nameParts(item.nodeName, (flatSearch ?? '').trim()); track $index) {@if (part.hit) {<mark>{{part.text}}</mark>} @else {<span>{{part.text}}</span>}}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{item.numPaths | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{flatCalls(item) | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{flatSelf(item) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-perc\">\r\n <div class=\"pv2-perc-cell\">\r\n <div class=\"pv2-bar\"><div [style.width.%]=\"flatPerc(item)\" [style.background]=\"typeColor(item.eventTypeName, item.waitClassName)\"></div></div>\r\n <span>{{flatPerc(item) | pv2fmt: 'pct'}}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{flatTime(item) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{flatAvg(item) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{item.peakTime | pv2fmt: 'dur'}}</td>\r\n @if (live) {\r\n <td class=\"pv2-num pv2-threads\">\r\n @if (item.activeThreads > 0) {\r\n <span class=\"pv2-active\"><span class=\"pv2-active-dot\"></span>{{item.activeThreads}}</span>\r\n } @else {\r\n <span class=\"pv2-muted\">{{item.activeThreads ?? '\u2014'}}</span>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n @if (openFlat.has(flatKey(item))) {\r\n <tr class=\"pv2-sub\">\r\n <td [attr.colspan]=\"live ? 9 : 8\">\r\n <div class=\"pv2-sub-title\">{{'Nodi con pi\u00F9 tempo' | localize: lc}}</div>\r\n @for (id of item.nodeIds ?? []; track id) {\r\n <div class=\"pv2-sub-item\">\r\n <code>{{id}}</code>\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"showDetail(id, $event)\">{{'Dettaglio' | localize: lc}}</button>\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"openInTree(id, $event)\">{{\"Apri nell'albero\" | localize: lc}}</button>\r\n </div>\r\n }\r\n @if (item.numPaths > (item.nodeIds?.length ?? 0)) {\r\n <div class=\"pv2-muted\">+{{item.numPaths - (item.nodeIds?.length ?? 0) | pv2fmt: 'int'}} {{'altri' | localize: lc}}</div>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n @if (!flatError && (!flat || !flat.items?.length)) {\r\n <div class=\"pv2-empty\">{{(flat ? 'Nessun evento con questi filtri.' : 'Caricamento\u2026') | localize: lc}}</div>\r\n }\r\n </div>\r\n @if (flat?.items?.length) {\r\n <div class=\"pv2-footer\">\r\n {{flat.numItems | pv2fmt: 'int'}} {{'eventi distinti' | localize: lc}}@if (flat.numItems > flat.items.length) {, {{'mostrati i primi' | localize: lc}} {{flat.items.length | pv2fmt: 'int'}}}.\r\n {{'La percentuale \u00E8 sul tempo proprio di tutti gli eventi non idle.' | localize: lc}}\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Dettaglio del nodo selezionato, lo stesso per le due viste -->\r\n <aside class=\"pv2-panel pv2-detail\" [class.pv2-stale]=\"detailStale\">\r\n @if (!selectedId) {\r\n <div class=\"pv2-placeholder\">{{'Seleziona un nodo per vederne il dettaglio.' | localize: lc}}</div>\r\n } @else if (detailMissing && !detailLoading) {\r\n <div class=\"pv2-placeholder\">{{\"Il nodo non c'\u00E8 nelle istanze o nella finestra scelte.\" | localize: lc}}</div>\r\n } @else if (detailError && !detailLoading) {\r\n <div class=\"pv2-placeholder pv2-error-text\">{{detailError}}</div>\r\n } @else if (!detail) {\r\n <div class=\"pv2-placeholder\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</div>\r\n } @else {\r\n <div class=\"pv2-d-head\">\r\n <span class=\"pv2-type\"><span class=\"pv2-dot\" [style.background]=\"typeColor(detail.eventTypeName, detail.waitClassName)\"></span>{{typeLabel(detail.eventTypeName, detail.waitClassName)}}</span>\r\n @if (detailLoading) {\r\n <i class=\"fa fa-spinner fa-spin pv2-muted\"></i>\r\n }\r\n </div>\r\n <div class=\"pv2-fullname\">{{detail.nodeName}}</div>\r\n <div class=\"pv2-id\">\r\n Id <code #idText>{{detail.nodeId}}</code>\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"copyId(idText)\">{{(copied ? 'Copiato' : 'Copia') | localize: lc}}</button>\r\n </div>\r\n\r\n <div class=\"pv2-d-title\">{{'Percorso' | localize: lc}}</div>\r\n <div class=\"pv2-path\">\r\n @for (item of detail.fullWaitStack ?? []; track item.nodeId; let i = $index) {\r\n <button type=\"button\" class=\"pv2-path-item\" [style.padding-left.px]=\"i * 10\" [title]=\"item.nodeName\" (click)=\"select(item.nodeId)\">\r\n <span class=\"pv2-dot\" [style.background]=\"typeColor(item.eventTypeName, item.waitClassName)\"></span>{{item.nodeName}}\r\n </button>\r\n }\r\n <div class=\"pv2-path-item pv2-path-current\" [style.padding-left.px]=\"(detail.fullWaitStack?.length ?? 0) * 10\" [title]=\"detail.nodeName\">\r\n <span class=\"pv2-dot\" [style.background]=\"typeColor(detail.eventTypeName, detail.waitClassName)\"></span>{{detail.nodeName}}\r\n </div>\r\n </div>\r\n\r\n <div class=\"pv2-d-title\">{{(live ? \"Dall'avvio\" : 'Nella finestra') | localize: lc}}</div>\r\n <dl class=\"pv2-kv\">\r\n <dt>{{'Esecuzioni' | localize: lc}}</dt><dd>{{detail.executions | pv2fmt: 'int'}}</dd>\r\n <dt>{{'Tempo totale' | localize: lc}}</dt><dd>{{detail.elapsedTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Tempo proprio' | localize: lc}}</dt><dd>{{detail.elapsedTimeLessChildren | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Medio' | localize: lc}}</dt><dd>{{detail.avgTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Picco' | localize: lc}}</dt><dd>{{detail.peakTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Minimo' | localize: lc}}</dt><dd>{{detail.minTime ? (detail.minTime | pv2fmt: 'dur') : '\u2014'}}</dd>\r\n </dl>\r\n\r\n @if (live) {\r\n <div class=\"pv2-d-title\">{{\"Dall'ultimo snapshot\" | localize: lc}}, {{hhmm(detail.snapFrom)}}</div>\r\n <dl class=\"pv2-kv\">\r\n <dt>{{'Esecuzioni' | localize: lc}}</dt><dd>{{detail.snapDeltaExecutions | pv2fmt: 'int'}}</dd>\r\n <dt>{{'Tempo totale' | localize: lc}}</dt><dd>{{detail.snapDeltaElapsedTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Tempo proprio' | localize: lc}}</dt><dd>{{detail.snapDeltaElapsedTimeLessChildren | pv2fmt: 'dur'}}</dd>\r\n </dl>\r\n\r\n <div class=\"pv2-d-title\">{{'Adesso' | localize: lc}}</div>\r\n <dl class=\"pv2-kv\">\r\n <dt>{{'Thread dentro' | localize: lc}}</dt>\r\n <dd>\r\n @if (detail.activeThreads > 0) {\r\n <span class=\"pv2-active\"><span class=\"pv2-active-dot\"></span>{{detail.activeThreads}}</span>\r\n } @else {\r\n {{detail.activeThreads ?? '\u2014'}}\r\n }\r\n </dd>\r\n <dt>{{'Esecuzione pi\u00F9 lunga' | localize: lc}}</dt>\r\n <dd>\r\n @if (detail.activeThreads > 0) {\r\n {{detail.activeMaxTime | pv2fmt: 'dur'}}\r\n @if (isLongRunning(detail.eventTypeName, detail.waitClassName, detail.activeMaxTime)) {\r\n <span class=\"pv2-long\"><i class=\"fa fa-clock\"></i> {{'forse bloccata' | localize: lc}}</span>\r\n }\r\n } @else {\r\n \u2014\r\n }\r\n </dd>\r\n <dt>{{'Ultima uscita' | localize: lc}}</dt><dd>{{relative(detail.lastExitTime)}}</dd>\r\n </dl>\r\n }\r\n\r\n @if (typeParts.length > 0) {\r\n <div class=\"pv2-d-title\">{{'Tempo proprio del sottoalbero per tipo' | localize: lc}}</div>\r\n <div class=\"pv2-stack\">\r\n @for (part of typeParts; track part.type) {\r\n <div [style.width.%]=\"part.percentage\" [style.background]=\"part.color\" [title]=\"part.label + ' ' + (part.percentage | pv2fmt: 'pct')\"></div>\r\n }\r\n </div>\r\n <div class=\"pv2-legend\">\r\n @for (part of typeParts; track part.type) {\r\n <span class=\"pv2-legend-item\"><span class=\"pv2-swatch\" [style.background]=\"part.color\"></span>{{part.label}} <strong>{{part.percentage | pv2fmt: 'pct'}}</strong></span>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"pv2-d-title\">{{'Sessioni attive sul nodo e sui discendenti' | localize: lc}}</div>\r\n @if (chart) {\r\n <div class=\"pv2-chart\">\r\n <div class=\"pv2-chart-y\">\r\n <span>{{axisValue(chart.max)}}</span>\r\n <span>{{axisValue(chart.max / 2)}}</span>\r\n <span>0</span>\r\n </div>\r\n <div class=\"pv2-chart-plot\">\r\n <svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\r\n <line x1=\"0\" y1=\"50\" x2=\"100\" y2=\"50\" class=\"pv2-chart-grid\" vector-effect=\"non-scaling-stroke\" />\r\n <path [attr.d]=\"chart.area\" [attr.fill]=\"chart.color\" fill-opacity=\"0.25\" />\r\n <path [attr.d]=\"chart.line\" [attr.stroke]=\"chart.color\" fill=\"none\" stroke-width=\"1.5\" vector-effect=\"non-scaling-stroke\" />\r\n </svg>\r\n <span class=\"pv2-chart-last\" [style.left.%]=\"chart.lastX\" [style.top.%]=\"chart.lastY\" [style.background]=\"chart.color\"></span>\r\n </div>\r\n <span></span>\r\n <div class=\"pv2-chart-x\">\r\n <span>{{chart.start}}</span>\r\n <span>{{chart.middle}}</span>\r\n <span>{{chart.end}}</span>\r\n </div>\r\n </div>\r\n <div class=\"pv2-caption\">\r\n {{'Media' | localize: lc}} {{axisValue(chart.average)}} &middot; {{'picco' | localize: lc}} {{axisValue(chart.peak)}} {{'sessioni' | localize: lc}}.\r\n {{chart.points | pv2fmt: 'int'}} {{\"punti dell'API\" | localize: lc}},\r\n @if (chart.interval == 1) {\r\n {{'uno al secondo' | localize: lc}},\r\n } @else {\r\n {{'uno ogni' | localize: lc}} {{chart.interval}} s,\r\n }\r\n {{'raggruppati a' | localize: lc}} {{chart.group}} s\r\n </div>\r\n } @else {\r\n <div class=\"pv2-muted pv2-small\">{{'Nessun campione in memoria per questa finestra: il grafico copre solo le ultime due ore circa.' | localize: lc}}</div>\r\n }\r\n\r\n @if (detail.instances?.length) {\r\n <div class=\"pv2-d-title\">{{'Per istanza' | localize: lc}}</div>\r\n <table class=\"pv2-mini\">\r\n <thead>\r\n <tr>\r\n <th>{{'Istanza' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{'Esecuzioni' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{'Tempo' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{'Proprio' | localize: lc}}</th>\r\n @if (live) {\r\n <th class=\"pv2-num\">{{'Thread' | localize: lc}}</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (inst of detail.instances; track inst.instanceId) {\r\n <tr>\r\n <td><span class=\"pv2-ellipsis\" [title]=\"inst.instanceId\">{{inst.instanceId}}</span></td>\r\n <td class=\"pv2-num\">{{inst.executions | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{inst.elapsedTime | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{inst.elapsedTimeLessChildren | pv2fmt: 'dur'}}</td>\r\n @if (live) {\r\n <td class=\"pv2-num\">{{inst.activeThreads ?? '\u2014'}}</td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n\r\n <div class=\"pv2-d-foot\">\r\n <span class=\"pv2-muted\">{{detail.numChildren | pv2fmt: 'int'}} {{'figli diretti' | localize: lc}} &middot; {{detail.numDescendants | pv2fmt: 'int'}} {{'discendenti' | localize: lc}}</span>\r\n <button type=\"button\" class=\"pv2-btn\" (click)=\"setRoot(detail.nodeId)\"><i class=\"fa fa-sitemap\"></i> {{'Apri come radice' | localize: lc}}</button>\r\n </div>\r\n }\r\n </aside>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: ["@charset \"UTF-8\";:host{display:block}:host ::ng-deep .pv2-noclear .p-select-clear-icon{display:none}.pv2-body{container-type:inline-size}.pv2-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 22px;padding-top:6px}.pv2-cluster{display:flex;flex-wrap:nowrap;align-items:center;gap:10px}.pv2-cluster-end{margin-left:auto}.pv2-field{width:190px}.pv2-field-wide{width:290px}.pv2-field-narrow{width:140px}.pv2-field-search{flex:1 1 220px;min-width:200px;max-width:340px}.pv2-toggle{display:flex;align-items:center;gap:8px;height:39px;white-space:nowrap}.pv2-pointer{cursor:pointer}.pv2-group{position:relative;display:inline-flex}.pv2-group.pv2-disabled{opacity:.5}.pv2-group-label{position:absolute;top:-8px;left:8px;z-index:1;padding:0 4px;background:var(--p-content-background, #fff);color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.pv2-seg{display:inline-flex;height:39px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);overflow:hidden;background:var(--p-content-background, #fff)}.pv2-seg button{display:inline-flex;align-items:center;gap:6px;padding:0 12px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.pv2-seg button:first-child{border-left:0}.pv2-seg button:hover:not(.pv2-on):not(:disabled){background:var(--p-content-hover-background, #f8fafc)}.pv2-seg button:disabled{cursor:default}.pv2-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.pv2-seg button.pv2-on{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca);font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.pv2-live-dot{width:8px;height:8px;border-radius:50%;background:#16a34a}.pv2-window{display:flex;flex-wrap:wrap;align-items:center;row-gap:4px;margin-top:12px;padding:7px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155);font-size:13px}.pv2-window>span+span:before{content:\"\\b7\";margin:0 10px;color:var(--p-text-muted-color, #64748b)}.pv2-warn-text{color:#92400e}.pv2-main{display:grid;grid-template-columns:minmax(0,1fr) 380px;gap:14px;align-items:start;margin-top:14px}@container (max-width: 1000px){.pv2-main{grid-template-columns:minmax(0,1fr)}.pv2-detail{position:static;max-height:none}}.pv2-panel{padding:12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0}.pv2-view-bar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 10px;padding-top:6px;margin-bottom:10px}.pv2-crumbs{display:flex;flex-wrap:wrap;align-items:center;gap:2px 4px;margin-bottom:8px;font-size:12px}.pv2-crumb{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:2px 6px;border:0;border-radius:4px;background:transparent;color:var(--p-primary-color, #6366f1);cursor:pointer}.pv2-crumb:hover{background:var(--p-highlight-background, #eef2ff)}.pv2-crumb-current{color:var(--p-text-color, #334155);font-weight:500;cursor:default}.pv2-crumb-current:hover{background:transparent}.pv2-crumb-sep{color:var(--p-text-muted-color, #64748b)}.pv2-alert{position:relative;margin-bottom:8px;padding:6px 30px 6px 10px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);font-size:13px}.pv2-alert.pv2-alert-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.pv2-alert.pv2-alert-error{border-color:#fca5a5;background:#fef2f2;color:#991b1b}.pv2-alert-close{position:absolute;top:3px;right:6px;border:0;background:transparent;color:inherit;font-size:16px;cursor:pointer}.pv2-table-wrap{max-height:68vh;overflow:auto;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px)}.pv2-table-wrap:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:1px}.pv2-table{width:100%;min-width:740px;border-collapse:separate;border-spacing:0;font-size:13px;color:var(--p-text-color, #334155)}.pv2-table th{position:sticky;top:0;z-index:1;padding:7px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-muted-color, #64748b);font-size:12px;font-weight:500;white-space:nowrap;text-align:left}.pv2-table td{padding:4px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);white-space:nowrap;vertical-align:middle}.pv2-col-node{width:100%;max-width:0}.pv2-num{text-align:right!important;font-variant-numeric:tabular-nums}.pv2-row{cursor:pointer}.pv2-row:hover td{background:var(--p-content-hover-background, #f8fafc)}.pv2-row.pv2-selected td{background:var(--p-highlight-background, #eef2ff)}.pv2-row.pv2-idle td{color:var(--p-text-muted-color, #64748b)}.pv2-row.pv2-idle td .pv2-name{opacity:.75}.pv2-node{display:flex;align-items:center;gap:6px;min-width:0}.pv2-caret{flex:none;width:18px;height:20px;padding:0;border:0;background:transparent;color:var(--p-text-muted-color, #64748b);text-align:center;cursor:pointer}.pv2-type{flex:none;display:inline-flex;align-items:center;gap:5px;padding:0 6px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:10px;color:var(--p-text-muted-color, #64748b);font-size:11px;line-height:18px;white-space:nowrap}.pv2-dot{flex:none;display:inline-block;width:8px;height:8px;border-radius:50%}.pv2-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pv2-name mark{padding:0 1px;border-radius:2px;background:#fde68a;color:#1f2937}.pv2-perc{width:1%}.pv2-perc-cell{display:flex;align-items:center;gap:8px}.pv2-bar{flex:none;width:64px;height:7px;border-radius:2px;overflow:hidden;background:var(--p-content-hover-background, #f8fafc);box-shadow:inset 0 0 0 1px var(--p-content-border-color, #e2e8f0)}.pv2-bar>div{height:100%}.pv2-threads>*+*{margin-left:8px}.pv2-active{display:inline-flex;align-items:center;gap:5px;font-weight:500}.pv2-active-dot{width:7px;height:7px;border-radius:50%;background:#16a34a;box-shadow:0 0 0 3px #16a34a2e}.pv2-long{display:inline-flex;align-items:center;gap:4px;padding:0 6px;border:1px solid #fcd34d;border-radius:10px;background:#fffbeb;color:#92400e;font-size:11px;line-height:18px;white-space:nowrap}.pv2-service td{padding:5px 10px;color:var(--p-text-muted-color, #64748b);font-size:12px;background:var(--p-content-background, #fff)}.pv2-sub td{padding:8px 10px 10px 34px;background:var(--p-content-hover-background, #f8fafc);white-space:normal}.pv2-sub-title{margin-bottom:4px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-sub-item{display:flex;align-items:center;gap:10px;padding:2px 0}.pv2-sub-item code{font-size:12px}.pv2-link{margin-left:6px;padding:0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;cursor:pointer}.pv2-link:hover{text-decoration:underline}.pv2-empty{padding:28px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.pv2-footer{margin-top:8px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-muted{color:var(--p-text-muted-color, #64748b)}.pv2-small{font-size:12px}.pv2-detail{position:sticky;top:10px;max-height:calc(100vh - 20px);overflow:auto;font-size:13px;color:var(--p-text-color, #334155);transition:opacity .15s}.pv2-detail.pv2-stale{opacity:.55}.pv2-placeholder{padding:36px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.pv2-error-text{color:#dc2626}.pv2-d-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.pv2-fullname{max-height:140px;overflow:auto;padding:8px 10px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);font-family:ui-monospace,Cascadia Mono,Consolas,monospace;font-size:12px;white-space:pre-wrap;word-break:break-word}.pv2-id{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-id code{font-size:12px}.pv2-d-title{margin:16px 0 6px;padding-top:10px;border-top:1px solid var(--p-content-border-color, #e2e8f0);color:var(--p-text-muted-color, #64748b);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.pv2-path{display:flex;flex-direction:column}.pv2-path-item{display:flex;align-items:center;gap:6px;max-width:100%;padding:2px 0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.pv2-path-item:hover{text-decoration:underline}.pv2-path-current{color:var(--p-text-color, #334155);font-weight:600;cursor:default}.pv2-path-current:hover{text-decoration:none}.pv2-kv{display:grid;grid-template-columns:auto 1fr;gap:3px 14px;margin:0}.pv2-kv dt{color:var(--p-text-muted-color, #64748b);font-weight:400}.pv2-kv dd{margin:0;text-align:right;font-variant-numeric:tabular-nums}.pv2-stack{display:flex;height:12px;border-radius:3px;overflow:hidden;background:var(--p-content-hover-background, #f8fafc)}.pv2-legend{display:flex;flex-wrap:wrap;gap:4px 12px;margin-top:6px;font-size:12px}.pv2-legend-item{display:inline-flex;align-items:center;gap:5px}.pv2-swatch{width:10px;height:10px;border-radius:2px}.pv2-chart{display:grid;grid-template-columns:auto 1fr;gap:2px 6px}.pv2-chart-y{display:flex;flex-direction:column;justify-content:space-between;height:110px;color:var(--p-text-muted-color, #64748b);font-size:11px;text-align:right;line-height:1}.pv2-chart-plot{position:relative;height:110px;border-left:1px solid var(--p-content-border-color, #e2e8f0);border-bottom:1px solid var(--p-content-border-color, #e2e8f0)}.pv2-chart-plot svg{display:block;width:100%;height:100%}.pv2-chart-grid{stroke:var(--p-content-border-color, #e2e8f0);stroke-dasharray:3 3}.pv2-chart-last{position:absolute;width:8px;height:8px;border:2px solid var(--p-content-background, #fff);border-radius:50%;transform:translate(-50%,-50%)}.pv2-chart-x{display:flex;justify-content:space-between;color:var(--p-text-muted-color, #64748b);font-size:11px}.pv2-caption{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-mini{width:100%;font-size:12px}.pv2-mini th{padding:3px 6px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);color:var(--p-text-muted-color, #64748b);font-weight:500}.pv2-mini td{padding:3px 6px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0)}.pv2-ellipsis{display:inline-block;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.pv2-d-foot{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;margin-top:16px;padding-top:10px;border-top:1px solid var(--p-content-border-color, #e2e8f0)}.pv2-btn{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:13px;cursor:pointer}.pv2-btn:hover{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}\n"] }]
1977
+ }], ctorParameters: () => [{ type: i1$1.DateService }, { type: i1.LocalizationService }, { type: i3.PreferencesService }, { type: i1$1.InterComService }, { type: i4.ActivatedRoute }, { type: i4.Router }, { type: i5.Location }, { type: AdminModuleBaseService }, { type: undefined, decorators: [{
1978
+ type: Inject,
1979
+ args: [ADM_MODULE_PATH]
1980
+ }] }, { type: undefined, decorators: [{
1981
+ type: Optional
1982
+ }, {
1983
+ type: Inject,
1984
+ args: [ADM_WORKLOAD_V2]
1985
+ }] }] });
1986
+
1987
+ // Workload report v2 (specifica "Workload report"): richieste e risposte come le vuole e le manda il backend (camelCase).
1988
+ // Le date delle risposte sono UTC "dd/MM/yyyy HH:mm:ss" senza fuso
1989
+ // Senza snapshot il report è live; senza istanze e con global è dell'intero cluster
1990
+ class WorkloadReportV2Request {
1991
+ }
1992
+ // Snapshot che terminano fra from e to (ISO UTC)
1993
+ class WorkloadSnapshotsRequest {
1994
+ }
1995
+ class WorkloadSnapshotInstance {
1996
+ }
1997
+ class WorkloadSnapshot {
1998
+ }
1999
+ class WorkloadSnapshotList {
2000
+ }
2001
+
2002
+ class AdmWorkloadV2Loc extends LocalizationService {
2003
+ constructor(injector) {
2004
+ super(injector);
2005
+ super.set("it->en", "Workload report", ["Workload report"]);
2006
+ super.set("it->en", "Errore", ["Error"]);
2007
+ // Barra dei comandi e avvisi
2008
+ super.set("it->en", "Tipo", ["Type"]);
2009
+ super.set("it->en", "Fra snapshot", ["Snapshot range"]);
2010
+ super.set("it->en", "Istanze", ["Instances"]);
2011
+ super.set("it->en", "Tutto il cluster", ["Whole cluster"]);
2012
+ super.set("it->en", "tutto il cluster", ["whole cluster"]);
2013
+ super.set("it->en", "istanze", ["instances"]);
2014
+ super.set("it->en", "istanza", ["instance"]);
2015
+ super.set("it->en", "Genera report", ["Generate report"]);
2016
+ super.set("it->en", "Annulla", ["Cancel"]);
2017
+ super.set("it->en", "Scegli il primo e l'ultimo snapshot", ["Choose the first and last snapshot"]);
2018
+ super.set("it->en", "Lo storico non è attivo su questo ambiente: il report si può produrre solo live.", ["History is not active on this environment: only a live report can be produced."]);
2019
+ super.set("it->en", "Lo snapshot {id} segue un riavvio di {istanza}: counter e meter della finestra non sono attendibili.", ["Snapshot {id} follows a restart of {istanza}: counters and meters in the window are not reliable."]);
2020
+ super.set("it->en", "La finestra supera 24 ore: l'attività campionata ne copre solo le ultime 24.", ["The window is longer than 24 hours: sampled activity only covers the last 24."]);
2021
+ // Snapshot
2022
+ super.set("it->en", "Snapshot", ["Snapshot"]);
2023
+ super.set("it->en", "Ultime 24 ore", ["Last 24 hours"]);
2024
+ super.set("it->en", "Ultimi 7 giorni", ["Last 7 days"]);
2025
+ super.set("it->en", "Tutto", ["All"]);
2026
+ super.set("it->en", "Caricamento…", ["Loading…"]);
2027
+ super.set("it->en", "Dalle", ["From"]);
2028
+ super.set("it->en", "alle", ["to"]);
2029
+ super.set("it->en", "dalle", ["from"]);
2030
+ super.set("it->en", "scegli l'ultimo snapshot", ["choose the last snapshot"]);
2031
+ super.set("it->en", "Azzera", ["Clear"]);
2032
+ super.set("it->en", "Un clic sceglie il primo snapshot, un secondo clic l'ultimo.", ["One click chooses the first snapshot, a second click the last."]);
2033
+ super.set("it->en", "Inizio", ["Start"]);
2034
+ super.set("it->en", "Fine", ["End"]);
2035
+ super.set("it->en", "Durata", ["Duration"]);
2036
+ super.set("it->en", "Manuale", ["Manual"]);
2037
+ super.set("it->en", "Riavvio", ["Restart"]);
2038
+ super.set("it->en", "Un'istanza è ripartita: counter e meter non sono attendibili", ["An instance restarted: counters and meters are not reliable"]);
2039
+ super.set("it->en", "Nessuno snapshot nel periodo scelto: prova con un periodo più lungo.", ["No snapshots in the chosen period: try a longer one."]);
2040
+ super.set("it->en", "Orari in ora locale", ["Times in local time"]);
2041
+ super.set("it->en", "g", ["d"]);
2042
+ // Report
2043
+ super.set("it->en", "Generazione del report…", ["Generating the report…"]);
2044
+ super.set("it->en", "Il report non si può produrre", ["The report cannot be produced"]);
2045
+ super.set("it->en", "generato alle", ["generated at"]);
2046
+ super.set("it->en", "Vai a…", ["Go to…"]);
2047
+ super.set("it->en", "Scarica", ["Download"]);
2048
+ super.set("it->en", "Apri in una scheda", ["Open in a tab"]);
2049
+ super.set("it->en", "Gli orari del report sono in UTC.", ["Report times are in UTC."]);
2050
+ super.set("it->en", "Scegli tipo e istanze, poi genera il report.", ["Choose type and instances, then generate the report."]);
2051
+ }
2052
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmWorkloadV2Loc, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
2053
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmWorkloadV2Loc }); }
2054
+ }
2055
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmWorkloadV2Loc, decorators: [{
2056
+ type: Injectable
2057
+ }], ctorParameters: () => [{ type: i0.Injector }] });
2058
+
2059
+ // Angular
2060
+ const STORAGE_KEY$2 = "HotSearchWorkloadV2";
2061
+ const HOUR_MS = 3600 * 1000;
2062
+ // Sezioni principali del report con le ancore stabili della specifica: nel salto compaiono solo quelle presenti nel documento.
2063
+ // I titoli sono in inglese come nel report
2064
+ const SECTIONS = [
2065
+ ["summary", "Report Summary"], ["loadprofile", "Load profile"], ["efficiency", "Instance efficiency"], ["topevents", "Top 10 events by time"],
2066
+ ["waitclasses", "Wait classes"], ["actsumtype", "Activity summary by event type"], ["actsumclass", "Activity summary by wait class"],
2067
+ ["aas", "Average active sessions"], ["topsess", "Top sessions"], ["topnodes", "Top profiler nodes"], ["topapis", "Top APIs"], ["topjobs", "Top jobs"],
2068
+ ["toptenants", "Top tenants"], ["protime", "Profiler nodes by time"], ["protimelc", "Profiler nodes by self time"], ["procalls", "Profiler nodes by calls"],
2069
+ ["wprotime", "Profiler waits"], ["iprotime", "Profiler idle"], ["sqltime", "SQL by elapsed time"], ["sqlexec", "SQL by executions"], ["sqlpeak", "SQL by peak"],
2070
+ ["sqltext", "Complete list of SQL text"], ["netstat", "Network"], ["iostat", "IO"], ["contwaits", "Waits on latches and locks"], ["latch", "Latch activity"],
2071
+ ["mgauges", "Metrics: gauges"], ["mcounters", "Metrics: counters"], ["mmeters", "Metrics: meters"], ["mhistograms", "Metrics: histograms"],
2072
+ ["mtimers", "Metrics: timers"], ["parameters", "Modified parameters"]
2073
+ ];
2074
+ // Ricordati fra una visita e l'altra (W12)
2075
+ class WorkloadV2Settings {
2076
+ }
2077
+ // Il report mostrato, con i parametri con cui è stato chiesto: resta quello anche se poi si cambiano i comandi
2078
+ class WorkloadV2Report {
2079
+ }
2080
+ class AdmWorkloadV2Component {
2081
+ constructor(lc, localStorage, intercom, route, sanitizer, adms) {
2082
+ this.lc = lc;
2083
+ this.localStorage = localStorage;
2084
+ this.intercom = intercom;
2085
+ this.route = route;
2086
+ this.sanitizer = sanitizer;
2087
+ this.adms = adms;
2088
+ // Comandi
2089
+ this.live = true;
2090
+ // Vuoto = tutto il cluster
2091
+ this.instances = [];
2092
+ // Giorni dell'elenco degli snapshot; "0" = tutto
2093
+ this.period = "7";
2094
+ // Elenco degli snapshot
2095
+ this.snapshots = [];
2096
+ this.historyAvailable = true;
2097
+ this.snapshotsLoading = false;
2098
+ this.snapshotsLoaded = false;
2099
+ // Primo e ultimo snapshot scelti, compresi (selTo null dopo il primo clic)
2100
+ this.selFrom = null;
2101
+ this.selTo = null;
2102
+ this.warnings = [];
2103
+ // Report
2104
+ this.generating = false;
2105
+ this.section = "";
2106
+ this.Instances = [];
2107
+ this.Periods = [];
2108
+ this.snapshotsGeneration = 0;
2109
+ this.be = route.snapshot.params["be"] || "";
2110
+ this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
2111
+ }
2112
+ async ngOnInit() {
2113
+ this.Periods = [
2114
+ new KeyValue("1", this.lc.loc("Ultime 24 ore")),
2115
+ new KeyValue("7", this.lc.loc("Ultimi 7 giorni")),
2116
+ new KeyValue("0", this.lc.loc("Tutto"))
2117
+ ];
2118
+ let saved = this.route.snapshot.queryParams["ignoreStorage"] ? null : await firstValueFrom(this.localStorage.getItem(STORAGE_KEY$2), { defaultValue: null });
2119
+ if (saved) {
2120
+ this.live = saved.live ?? this.live;
2121
+ this.instances = saved.instances ?? this.instances;
2122
+ this.period = saved.period ?? this.period;
2123
+ }
2124
+ this.adms.getInstances(this.be).then(t => this.Instances = t ?? []);
2125
+ this.loadSnapshots();
2126
+ }
2127
+ ngOnDestroy() {
2128
+ this.reportSub?.unsubscribe();
2129
+ }
2130
+ // #region Comandi
2131
+ get clusterLabel() {
2132
+ return this.Instances.length ? `${this.lc.loc("Tutto il cluster")} (${this.Instances.length} ${this.Instances.length == 1 ? this.lc.loc("istanza") : this.lc.loc("istanze")})` : this.lc.loc("Tutto il cluster");
2133
+ }
2134
+ setLive(live) {
2135
+ if (!live && !this.historyAvailable)
2136
+ return;
2137
+ this.live = live;
2138
+ this.updateWarnings();
2139
+ this.saveSettings();
2140
+ }
2141
+ // W6: cambiando istanze l'elenco degli snapshot si ricarica
2142
+ instancesChanged(ids) {
2143
+ this.instances = ids ?? [];
2144
+ this.saveSettings();
2145
+ this.loadSnapshots();
2146
+ }
2147
+ setPeriod(id) {
2148
+ this.period = id;
2149
+ this.saveSettings();
2150
+ this.loadSnapshots();
2151
+ }
2152
+ // In "Fra snapshot" servono il primo e l'ultimo
2153
+ get canGenerate() {
2154
+ return this.live || (this.selFrom != null && this.selTo != null);
2155
+ }
2156
+ // #endregion
2157
+ // #region Snapshot
2158
+ async loadSnapshots() {
2159
+ let generation = ++this.snapshotsGeneration;
2160
+ let req = new WorkloadSnapshotsRequest();
2161
+ if (+this.period > 0)
2162
+ req.from = new Date(Date.now() - +this.period * 24 * HOUR_MS).toISOString();
2163
+ if (this.instances.length)
2164
+ req.instanceIds = this.instances;
2165
+ this.snapshotsLoading = true;
2166
+ this.snapshotsError = null;
2167
+ try {
2168
+ let res = await this.adms.getWorkloadSnapshots(this.be, req);
2169
+ if (generation != this.snapshotsGeneration)
2170
+ return;
2171
+ // Senza risposta (per esempio DefaultOnFail) vale come storico spento
2172
+ this.historyAvailable = res?.historyAvailable ?? false;
2173
+ this.snapshots = res?.snapshots ?? [];
2174
+ this.snapshotsLoaded = true;
2175
+ if (!this.historyAvailable)
2176
+ this.live = true;
2177
+ // Se gli snapshot scelti non ci sono più, la scelta si azzera (W6)
2178
+ let ids = new Set(this.snapshots.map(t => t.snapId));
2179
+ if ((this.selFrom != null && !ids.has(this.selFrom)) || (this.selTo != null && !ids.has(this.selTo)))
2180
+ this.clearSelection();
2181
+ this.updateWarnings();
2182
+ }
2183
+ catch (err) {
2184
+ if (generation == this.snapshotsGeneration)
2185
+ this.snapshotsError = `${this.lc.loc("Errore")} ${err?.status ?? ""}: ${this.adms.errorText(err)}`;
2186
+ }
2187
+ finally {
2188
+ if (generation == this.snapshotsGeneration)
2189
+ this.snapshotsLoading = false;
2190
+ }
2191
+ }
2192
+ // Un clic fissa il primo snapshot, un secondo l'ultimo (in qualsiasi ordine); un terzo ricomincia
2193
+ pick(snapshot) {
2194
+ if (this.selFrom == null || this.selTo != null) {
2195
+ this.selFrom = snapshot.snapId;
2196
+ this.selTo = null;
2197
+ }
2198
+ else {
2199
+ let first = this.selFrom;
2200
+ this.selFrom = Math.min(first, snapshot.snapId);
2201
+ this.selTo = Math.max(first, snapshot.snapId);
2202
+ }
2203
+ this.updateWarnings();
2204
+ }
2205
+ clearSelection() {
2206
+ this.selFrom = null;
2207
+ this.selTo = null;
2208
+ this.updateWarnings();
2209
+ }
2210
+ inRange(snapshot) {
2211
+ if (this.selFrom == null)
2212
+ return false;
2213
+ return this.selTo == null ? snapshot.snapId == this.selFrom : snapshot.snapId >= this.selFrom && snapshot.snapId <= this.selTo;
2214
+ }
2215
+ isEdge(snapshot) {
2216
+ return snapshot.snapId == this.selFrom || snapshot.snapId == this.selTo;
2217
+ }
2218
+ // Finestra scelta: dall'inizio del primo snapshot alla fine dell'ultimo (ognuno copre l'intervallo dal precedente)
2219
+ get selectionWindow() {
2220
+ let first = this.snapshots.find(t => t.snapId == this.selFrom);
2221
+ let last = this.snapshots.find(t => t.snapId == (this.selTo ?? this.selFrom));
2222
+ return first && last ? { from: this.parseUtc(first.beginIntervalTime), to: this.parseUtc(last.endIntervalTime) } : null;
2223
+ }
2224
+ instancesTitle(snapshot) {
2225
+ return (snapshot.instances ?? []).map(t => t.instanceId).join("\n");
2226
+ }
2227
+ // W4: avvisi sulla finestra prima di generare, non bloccanti
2228
+ updateWarnings() {
2229
+ let warnings = [];
2230
+ if (!this.live && this.selFrom != null && this.selTo != null) {
2231
+ this.snapshots.forEach((s, i) => {
2232
+ if (s.restart && s.snapId >= this.selFrom && s.snapId <= this.selTo)
2233
+ for (let instance of this.restartedInstances(i))
2234
+ warnings.push(this.lc.loc("Lo snapshot {id} segue un riavvio di {istanza}: counter e meter della finestra non sono attendibili.").replace("{id}", String(s.snapId)).replace("{istanza}", instance));
2235
+ });
2236
+ let window = this.selectionWindow;
2237
+ if (window && window.to.getTime() - window.from.getTime() > 24 * HOUR_MS)
2238
+ warnings.push(this.lc.loc("La finestra supera 24 ore: l'attività campionata ne copre solo le ultime 24."));
2239
+ }
2240
+ this.warnings = warnings;
2241
+ }
2242
+ // L'istanza ripartita è quella con un avvio diverso dallo snapshot precedente (il successivo nell'elenco, che va dal più recente)
2243
+ restartedInstances(index) {
2244
+ let snapshot = this.snapshots[index];
2245
+ let previous = this.snapshots[index + 1];
2246
+ let ids = (snapshot.instances ?? []).filter(t => {
2247
+ let before = previous?.instances?.find(k => k.instanceId == t.instanceId);
2248
+ return before ? before.startupTime != t.startupTime : this.parseUtc(t.startupTime) > this.parseUtc(snapshot.beginIntervalTime);
2249
+ }).map(t => t.instanceId);
2250
+ return ids.length ? ids : (snapshot.instances ?? []).map(t => t.instanceId);
2251
+ }
2252
+ // #endregion
2253
+ // #region Report
2254
+ // Il report si chiede solo su comando (W7); durante la richiesta lo stesso comando la annulla
2255
+ generateOrCancel() {
2256
+ if (this.generating)
2257
+ this.cancel();
2258
+ else
2259
+ this.generate();
2260
+ }
2261
+ generate() {
2262
+ if (!this.canGenerate)
2263
+ return;
2264
+ let req = new WorkloadReportV2Request();
2265
+ // Mai global=false senza istanze: dietro un bilanciatore darebbe il report dell'istanza che risponde (W2)
2266
+ if (this.instances.length)
2267
+ req.instanceIds = [...this.instances];
2268
+ else
2269
+ req.global = true;
2270
+ let window = this.live ? null : this.selectionWindow;
2271
+ if (!this.live) {
2272
+ req.snapFrom = this.selFrom;
2273
+ req.snapTo = this.selTo;
2274
+ }
2275
+ this.generating = true;
2276
+ this.reportError = null;
2277
+ this.reportSub?.unsubscribe();
2278
+ this.reportSub = this.adms.getWorkloadReportV2(this.be, req).subscribe({
2279
+ next: html => {
2280
+ this.generating = false;
2281
+ this.showReport(html ?? "", req, window);
2282
+ },
2283
+ error: err => {
2284
+ this.generating = false;
2285
+ // La sessione scaduta la gestisce il servizio
2286
+ if (err?.status != 401)
2287
+ this.reportError = this.adms.errorText(err) || `${this.lc.loc("Errore")} ${err?.status ?? ""}`;
2288
+ }
2289
+ });
2290
+ }
2291
+ cancel() {
2292
+ this.reportSub?.unsubscribe();
2293
+ this.reportSub = null;
2294
+ this.generating = false;
2295
+ }
2296
+ showReport(html, req, window) {
2297
+ // Le sezioni del salto sono quelle che il documento ha davvero
2298
+ let parsed = new DOMParser().parseFromString(html, "text/html");
2299
+ let sections = SECTIONS.filter(t => parsed.getElementById(t[0])).map(t => new KeyValue(t[0], t[1]));
2300
+ this.report = {
2301
+ html: html,
2302
+ // Il report va in un iframe con sandbox senza script: lo stile e le ancore restano dentro il riquadro.
2303
+ // Il sanificatore di Angular toglierebbe gli stili inline del documento, per questo lo si marca come sicuro
2304
+ doc: this.sanitizer.bypassSecurityTrustHtml(html),
2305
+ live: req.snapFrom == null,
2306
+ instances: req.instanceIds ?? [],
2307
+ snapFrom: req.snapFrom,
2308
+ snapTo: req.snapTo,
2309
+ windowFrom: window?.from,
2310
+ windowTo: window?.to,
2311
+ generatedAt: new Date(),
2312
+ size: new Blob([html]).size,
2313
+ sections: sections
2314
+ };
2315
+ this.section = "";
2316
+ }
2317
+ // W11: porta il riquadro all'ancora scelta, senza far scorrere la pagina
2318
+ goToSection(anchor) {
2319
+ this.section = anchor;
2320
+ let doc = this.reportFrame?.nativeElement?.contentDocument;
2321
+ let target = doc?.getElementById(anchor);
2322
+ if (target)
2323
+ doc.defaultView.scrollTo({ top: target.getBoundingClientRect().top + doc.defaultView.scrollY - 8, behavior: "smooth" });
2324
+ }
2325
+ // W10: l'HTML in un file UTF-8 con un nome che dice cosa contiene
2326
+ download() {
2327
+ let url = URL.createObjectURL(this.reportBlob());
2328
+ let link = document.createElement("a");
2329
+ link.href = url;
2330
+ link.download = this.fileName();
2331
+ document.body.appendChild(link);
2332
+ link.click();
2333
+ link.remove();
2334
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
2335
+ }
2336
+ openInTab() {
2337
+ let url = URL.createObjectURL(this.reportBlob());
2338
+ window.open(url, "_blank");
2339
+ // La scheda nuova ha già caricato il documento quando l'URL viene revocato
2340
+ setTimeout(() => URL.revokeObjectURL(url), 60000);
2341
+ }
2342
+ reportBlob() {
2343
+ return new Blob([this.report.html], { type: "text/html;charset=utf-8" });
2344
+ }
2345
+ // workload-report_{live|snap-1201-1206}_{cluster|istanze}_{yyyyMMdd-HHmm}Z.html
2346
+ fileName() {
2347
+ let r = this.report;
2348
+ let kind = r.live ? "live" : `snap-${r.snapFrom}-${r.snapTo}`;
2349
+ let scope = r.instances.length ? r.instances.map(t => t.replace(/[^\w.-]+/g, "-")).join("+") : "cluster";
2350
+ return `workload-report_${kind}_${scope}_${this.utcStamp(r.generatedAt)}Z.html`;
2351
+ }
2352
+ // yyyyMMdd-HHmm in UTC (il plugin utc di dayjs non è detto che ci sia)
2353
+ utcStamp(date) {
2354
+ let pad = (n) => String(n).padStart(2, "0");
2355
+ return `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(date.getUTCHours())}${pad(date.getUTCMinutes())}`;
2356
+ }
2357
+ // #endregion
2358
+ // #region Presentazione
2359
+ // Le date delle risposte sono UTC "dd/MM/yyyy HH:mm:ss" senza fuso: si leggono come UTC e si mostrano in ora locale (W16)
2360
+ parseUtc(value) {
2361
+ let m = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})$/.exec(value ?? "");
2362
+ return m ? new Date(Date.UTC(+m[3], +m[2] - 1, +m[1], +m[4], +m[5], +m[6])) : value ? new Date(value) : null;
2363
+ }
2364
+ local(value) {
2365
+ let date = typeof value == "string" ? this.parseUtc(value) : value;
2366
+ return date ? dayjs(date).format("DD/MM HH:mm") : "—";
2367
+ }
2368
+ hhmm(date) {
2369
+ return date ? dayjs(date).format("HH:mm") : "—";
2370
+ }
2371
+ // Fuso dell'ora locale, per dire in che ora sono gli orari della pagina
2372
+ get zone() {
2373
+ return `UTC${dayjs().format("Z")}`;
2374
+ }
2375
+ span(from, to) {
2376
+ let a = typeof from == "string" ? this.parseUtc(from) : from;
2377
+ let b = typeof to == "string" ? this.parseUtc(to) : to;
2378
+ if (!a || !b)
2379
+ return "—";
2380
+ let minutes = Math.max(0, Math.round((b.getTime() - a.getTime()) / 60000));
2381
+ if (minutes < 60)
2382
+ return `${minutes} min`;
2383
+ let hours = Math.floor(minutes / 60);
2384
+ if (hours < 48)
2385
+ return minutes % 60 ? `${hours} h ${minutes % 60} min` : `${hours} h`;
2386
+ let days = Math.floor(hours / 24);
2387
+ return hours % 24 ? `${days} ${this.lc.loc("g")} ${hours % 24} h` : `${days} ${this.lc.loc("g")}`;
2388
+ }
2389
+ kilobytes(size) {
2390
+ return `${Math.max(1, Math.round(size / 1024))} kB`;
2391
+ }
2392
+ scopeLabel(instances) {
2393
+ return instances?.length ? instances.join(", ") : this.lc.loc("tutto il cluster");
2394
+ }
2395
+ saveSettings() {
2396
+ let settings = { live: this.live, instances: this.instances, period: this.period };
2397
+ this.localStorage.setItem(STORAGE_KEY$2, settings).subscribe();
2398
+ }
2399
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmWorkloadV2Component, deps: [{ token: i1.LocalizationService }, { token: i3.PreferencesService }, { token: i1$1.InterComService }, { token: i4.ActivatedRoute }, { token: i5$1.DomSanitizer }, { token: AdminModuleBaseService }], target: i0.ɵɵFactoryTarget.Component }); }
2400
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: AdmWorkloadV2Component, isStandalone: false, selector: "workload_v2", providers: [{ provide: LocalizationService, useClass: AdmWorkloadV2Loc }], viewQueries: [{ propertyName: "reportFrame", first: true, predicate: ["reportFrame"], descendants: true }], ngImport: i0, template: "<div class=\"row\">\r\n <div class=\"col-12\">\r\n <div class=\"card app-overflow-visible\" widget>\r\n <div class=\"card-header\">\r\n <span class=\"text-uppercase\">{{'Workload report' | localize: lc}}</span>\r\n </div>\r\n <div class=\"card-body widget-body\">\r\n <!-- Barra dei comandi: tipo, istanze, genera. Il report si chiede solo con il comando -->\r\n <div class=\"wr-toolbar\">\r\n <div class=\"wr-group\">\r\n <span class=\"wr-group-label\">{{'Tipo' | localize: lc}}</span>\r\n <div class=\"wr-seg\">\r\n <button type=\"button\" [class.wr-on]=\"live\" (click)=\"setLive(true)\"><span class=\"wr-live-dot\"></span>Live</button>\r\n <button type=\"button\" [class.wr-on]=\"!live\" [disabled]=\"!historyAvailable\" (click)=\"setLive(false)\"\r\n [title]=\"historyAvailable ? '' : ('Lo storico non \u00E8 attivo su questo ambiente: il report si pu\u00F2 produrre solo live.' | localize: lc)\">\r\n {{'Fra snapshot' | localize: lc}}\r\n </button>\r\n </div>\r\n </div>\r\n <div class=\"wr-group wr-field-wide\">\r\n <span class=\"wr-group-label\">{{'Istanze' | localize: lc}}</span>\r\n <form-multiselect class=\"wr-fill\" [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"instances\" (ngModelChange)=\"instancesChanged($event)\" name=\"instances\" [Source]=\"Instances\" [Placeholder]=\"clusterLabel\" [MaxShownElements]=\"2\"></form-multiselect>\r\n </div>\r\n <div class=\"wr-toolbar-end\">\r\n <button type=\"button\" class=\"wr-btn wr-btn-primary\" [disabled]=\"!generating && !canGenerate\" (click)=\"generateOrCancel()\"\r\n [title]=\"!generating && !canGenerate ? ('Scegli il primo e l\\'ultimo snapshot' | localize: lc) : ''\">\r\n @if (generating) {\r\n <i class=\"fa fa-times\"></i> {{'Annulla' | localize: lc}}\r\n } @else {\r\n <i class=\"fa fa-file-text\"></i> {{'Genera report' | localize: lc}}\r\n }\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Avvisi sulla finestra (W4): non bloccano la generazione -->\r\n @for (warning of warnings; track $index) {\r\n <div class=\"wr-alert wr-alert-warn\"><i class=\"fa fa-exclamation-triangle\"></i> {{warning}}</div>\r\n }\r\n @if (snapshotsLoaded && !historyAvailable) {\r\n <div class=\"wr-alert\"><i class=\"fa fa-info-circle\"></i> {{'Lo storico non \u00E8 attivo su questo ambiente: il report si pu\u00F2 produrre solo live.' | localize: lc}}</div>\r\n }\r\n\r\n <!-- Elenco degli snapshot, solo per il report fra snapshot -->\r\n @if (!live) {\r\n <div class=\"wr-panel\">\r\n <div class=\"wr-panel-header\">\r\n <span class=\"wr-title\">{{'Snapshot' | localize: lc}}</span>\r\n <div class=\"wr-seg wr-seg-small\">\r\n @for (p of Periods; track p.id) {\r\n <button type=\"button\" [class.wr-on]=\"period == p.id\" (click)=\"setPeriod(p.id)\">{{p.description}}</button>\r\n }\r\n </div>\r\n @if (snapshotsLoading) {\r\n <span class=\"wr-muted\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</span>\r\n }\r\n <span class=\"wr-spacer\"></span>\r\n @if (selectionWindow; as w) {\r\n <span class=\"wr-window\">\r\n {{'Dalle' | localize: lc}} <strong>{{local(w.from)}}</strong> {{'alle' | localize: lc}} <strong>{{local(w.to)}}</strong>\r\n ({{span(w.from, w.to)}})\r\n @if (selTo == null) {\r\n <span class=\"wr-muted\">&middot; {{\"scegli l'ultimo snapshot\" | localize: lc}}</span>\r\n }\r\n </span>\r\n <button type=\"button\" class=\"wr-link\" (click)=\"clearSelection()\">{{'Azzera' | localize: lc}}</button>\r\n } @else {\r\n <span class=\"wr-muted\">{{\"Un clic sceglie il primo snapshot, un secondo clic l'ultimo.\" | localize: lc}}</span>\r\n }\r\n </div>\r\n\r\n @if (snapshotsError) {\r\n <div class=\"wr-alert wr-alert-error\">{{snapshotsError}}</div>\r\n }\r\n\r\n <div class=\"wr-table-wrap\">\r\n <table class=\"wr-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"wr-num\">{{'Snapshot' | localize: lc}}</th>\r\n <th>{{'Inizio' | localize: lc}}</th>\r\n <th>{{'Fine' | localize: lc}}</th>\r\n <th class=\"wr-num\">{{'Durata' | localize: lc}}</th>\r\n <th class=\"wr-num\">{{'Istanze' | localize: lc}}</th>\r\n <th>{{'Manuale' | localize: lc}}</th>\r\n <th>{{'Riavvio' | localize: lc}}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (s of snapshots; track s.snapId) {\r\n <tr class=\"wr-row\" [class.wr-in-range]=\"inRange(s)\" [class.wr-edge]=\"isEdge(s)\" [class.wr-restart]=\"s.restart\" (click)=\"pick(s)\">\r\n <td class=\"wr-num\"><strong>{{s.snapId}}</strong></td>\r\n <td>{{local(s.beginIntervalTime)}}</td>\r\n <td>{{local(s.endIntervalTime)}}</td>\r\n <td class=\"wr-num\">{{span(s.beginIntervalTime, s.endIntervalTime)}}</td>\r\n <td class=\"wr-num\" [title]=\"instancesTitle(s)\">{{s.instances?.length ?? 0}}</td>\r\n <td>\r\n @if (s.manual) {\r\n <span class=\"wr-badge\"><i class=\"fa fa-hand-pointer\"></i> {{'Manuale' | localize: lc}}</span>\r\n }\r\n </td>\r\n <td>\r\n @if (s.restart) {\r\n <span class=\"wr-badge wr-badge-warn\" [title]=\"'Un\\'istanza \u00E8 ripartita: counter e meter non sono attendibili' | localize: lc\"><i class=\"fa fa-power-off\"></i> {{'Riavvio' | localize: lc}}</span>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (snapshotsLoaded && !snapshotsLoading && !snapshotsError && snapshots.length == 0 && historyAvailable) {\r\n <div class=\"wr-empty\">{{'Nessuno snapshot nel periodo scelto: prova con un periodo pi\u00F9 lungo.' | localize: lc}}</div>\r\n }\r\n </div>\r\n <div class=\"wr-footnote\">{{'Orari in ora locale' | localize: lc}} ({{zone}})</div>\r\n </div>\r\n }\r\n\r\n <!-- Report -->\r\n <div class=\"wr-panel wr-report\">\r\n @if (generating) {\r\n <div class=\"wr-alert wr-alert-busy\">\r\n <i class=\"fa fa-spinner fa-spin\"></i> {{'Generazione del report\u2026' | localize: lc}}\r\n <button type=\"button\" class=\"wr-link\" (click)=\"cancel()\">{{'Annulla' | localize: lc}}</button>\r\n </div>\r\n }\r\n @if (reportError) {\r\n <div class=\"wr-alert wr-alert-error\">{{'Il report non si pu\u00F2 produrre' | localize: lc}}: {{reportError}}</div>\r\n }\r\n\r\n @if (report) {\r\n <div class=\"wr-report-bar\">\r\n <div class=\"wr-summary\">\r\n @if (report.live) {\r\n <strong>Live</strong>\r\n } @else {\r\n <strong>{{'Snapshot' | localize: lc}} {{report.snapFrom}}\u2013{{report.snapTo}}</strong>\r\n }\r\n &middot; {{scopeLabel(report.instances)}}\r\n @if (!report.live && report.windowFrom) {\r\n &middot; {{'dalle' | localize: lc}} {{local(report.windowFrom)}} {{'alle' | localize: lc}} {{local(report.windowTo)}}\r\n }\r\n &middot; {{'generato alle' | localize: lc}} {{hhmm(report.generatedAt)}} ({{kilobytes(report.size)}})\r\n </div>\r\n <div class=\"wr-actions\">\r\n @if (report.sections.length > 0) {\r\n <div class=\"wr-field wr-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"section\" (ngModelChange)=\"goToSection($event)\" name=\"section\" [Source]=\"report.sections\" [Placeholder]=\"'Vai a\u2026' | localize: lc\"></form-select>\r\n </div>\r\n }\r\n <button type=\"button\" class=\"wr-btn\" (click)=\"download()\"><i class=\"fa fa-download\"></i> {{'Scarica' | localize: lc}}</button>\r\n <button type=\"button\" class=\"wr-btn\" (click)=\"openInTab()\"><i class=\"fa fa-external-link\"></i> {{'Apri in una scheda' | localize: lc}}</button>\r\n </div>\r\n </div>\r\n <div class=\"wr-footnote\"><i class=\"fa fa-clock\"></i> {{'Gli orari del report sono in UTC.' | localize: lc}}</div>\r\n <!-- Riquadro isolato e senza script: gli stili del report non toccano la pagina e le ancore dell'indice restano dentro (W8) -->\r\n <iframe #reportFrame class=\"wr-frame\" [class.wr-stale]=\"generating\" [srcdoc]=\"report.doc\" sandbox=\"allow-same-origin allow-popups\" title=\"Workload report\"></iframe>\r\n } @else if (!generating && !reportError) {\r\n <div class=\"wr-placeholder\">\r\n <i class=\"fa fa-file-text\"></i>\r\n {{'Scegli tipo e istanze, poi genera il report.' | localize: lc}}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .wr-noclear .p-select-clear-icon{display:none}:host ::ng-deep .wr-fill{display:block;width:100%}.wr-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 18px;padding-top:6px}.wr-toolbar-end{margin-left:auto}.wr-field{width:240px}.wr-field-wide{width:380px;max-width:100%}.wr-group{position:relative;display:inline-flex}.wr-group-label{position:absolute;top:-8px;left:8px;z-index:2;padding:0 4px;background:var(--p-content-background, #fff);color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.wr-seg{display:inline-flex;height:39px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);overflow:hidden;background:var(--p-content-background, #fff)}.wr-seg button{display:inline-flex;align-items:center;gap:6px;padding:0 14px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.wr-seg button:first-child{border-left:0}.wr-seg button:hover:not(.wr-on):not(:disabled){background:var(--p-content-hover-background, #f8fafc)}.wr-seg button:disabled{color:var(--p-text-muted-color, #64748b);opacity:.6;cursor:not-allowed}.wr-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.wr-seg button.wr-on{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca);font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.wr-seg.wr-seg-small{height:30px}.wr-seg.wr-seg-small button{padding:0 10px;font-size:12px}.wr-live-dot{width:8px;height:8px;border-radius:50%;background:#16a34a}.wr-btn{display:inline-flex;align-items:center;gap:7px;height:34px;padding:0 14px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.wr-btn:hover:not(:disabled){background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.wr-btn:disabled{opacity:.55;cursor:not-allowed}.wr-btn.wr-btn-primary{height:39px;padding:0 18px;border-color:var(--p-primary-color, #6366f1);background:var(--p-primary-color, #6366f1);color:var(--p-primary-contrast-color, #fff);font-weight:500}.wr-btn.wr-btn-primary:hover:not(:disabled){background:var(--p-primary-color, #6366f1);color:var(--p-primary-contrast-color, #fff);filter:brightness(1.08)}.wr-link{margin-left:8px;padding:0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;cursor:pointer}.wr-link:hover{text-decoration:underline}.wr-alert{margin-top:10px;padding:7px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155);font-size:13px}.wr-alert.wr-alert-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.wr-alert.wr-alert-error{border-color:#fca5a5;background:#fef2f2;color:#991b1b}.wr-alert.wr-alert-busy{margin-top:0;margin-bottom:10px;border-color:var(--p-primary-color, #6366f1);background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.wr-panel{margin-top:14px;padding:12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0}.wr-panel-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin-bottom:10px}.wr-title{font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--p-text-muted-color, #64748b)}.wr-spacer{flex:1 1 auto}.wr-window{color:var(--p-text-color, #334155);font-size:13px}.wr-muted{color:var(--p-text-muted-color, #64748b);font-size:12px}.wr-table-wrap{max-height:320px;overflow:auto;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px)}.wr-table{width:100%;border-collapse:separate;border-spacing:0;font-size:13px;color:var(--p-text-color, #334155)}.wr-table th{position:sticky;top:0;z-index:1;padding:6px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-muted-color, #64748b);font-size:12px;font-weight:500;white-space:nowrap;text-align:left}.wr-table td{padding:4px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);white-space:nowrap}.wr-num{text-align:right!important;font-variant-numeric:tabular-nums}.wr-row{cursor:pointer}.wr-row:hover td{background:var(--p-content-hover-background, #f8fafc)}.wr-row.wr-restart td:first-child{box-shadow:inset 3px 0 #f59e0b}.wr-row.wr-in-range td{background:var(--p-highlight-background, #eef2ff)}.wr-row.wr-edge td{color:var(--p-highlight-color, #4338ca);font-weight:600}.wr-badge{display:inline-flex;align-items:center;gap:4px;padding:0 7px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:10px;color:var(--p-text-muted-color, #64748b);font-size:11px;line-height:18px}.wr-badge.wr-badge-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.wr-empty{padding:22px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.wr-footnote{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.wr-report-bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px 14px;padding-top:6px}.wr-summary{color:var(--p-text-color, #334155);font-size:13px}.wr-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.wr-frame{display:block;width:100%;height:calc(100vh - 240px);min-height:520px;margin-top:8px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:#fff;transition:opacity .15s}.wr-frame.wr-stale{opacity:.5}.wr-placeholder{display:flex;flex-direction:column;align-items:center;gap:10px;padding:70px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.wr-placeholder .fa{font-size:28px;opacity:.5}\n"], dependencies: [{ kind: "directive", type: i7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i9.EsFormSelectComponent, selector: "form-select", inputs: ["OptionsTemplate"] }, { kind: "component", type: i9.EsFormMultiselectComponent, selector: "form-multiselect", inputs: ["display", "InOutFormat", "FloatingLabel", "MaxShownElements"] }, { kind: "directive", type: i10.Widget, selector: "[widget]" }, { kind: "pipe", type: i1.LocalizePipe, name: "localize" }] }); }
2401
+ }
2402
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmWorkloadV2Component, decorators: [{
2403
+ type: Component,
2404
+ args: [{ selector: "workload_v2", providers: [{ provide: LocalizationService, useClass: AdmWorkloadV2Loc }], standalone: false, template: "<div class=\"row\">\r\n <div class=\"col-12\">\r\n <div class=\"card app-overflow-visible\" widget>\r\n <div class=\"card-header\">\r\n <span class=\"text-uppercase\">{{'Workload report' | localize: lc}}</span>\r\n </div>\r\n <div class=\"card-body widget-body\">\r\n <!-- Barra dei comandi: tipo, istanze, genera. Il report si chiede solo con il comando -->\r\n <div class=\"wr-toolbar\">\r\n <div class=\"wr-group\">\r\n <span class=\"wr-group-label\">{{'Tipo' | localize: lc}}</span>\r\n <div class=\"wr-seg\">\r\n <button type=\"button\" [class.wr-on]=\"live\" (click)=\"setLive(true)\"><span class=\"wr-live-dot\"></span>Live</button>\r\n <button type=\"button\" [class.wr-on]=\"!live\" [disabled]=\"!historyAvailable\" (click)=\"setLive(false)\"\r\n [title]=\"historyAvailable ? '' : ('Lo storico non \u00E8 attivo su questo ambiente: il report si pu\u00F2 produrre solo live.' | localize: lc)\">\r\n {{'Fra snapshot' | localize: lc}}\r\n </button>\r\n </div>\r\n </div>\r\n <div class=\"wr-group wr-field-wide\">\r\n <span class=\"wr-group-label\">{{'Istanze' | localize: lc}}</span>\r\n <form-multiselect class=\"wr-fill\" [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"instances\" (ngModelChange)=\"instancesChanged($event)\" name=\"instances\" [Source]=\"Instances\" [Placeholder]=\"clusterLabel\" [MaxShownElements]=\"2\"></form-multiselect>\r\n </div>\r\n <div class=\"wr-toolbar-end\">\r\n <button type=\"button\" class=\"wr-btn wr-btn-primary\" [disabled]=\"!generating && !canGenerate\" (click)=\"generateOrCancel()\"\r\n [title]=\"!generating && !canGenerate ? ('Scegli il primo e l\\'ultimo snapshot' | localize: lc) : ''\">\r\n @if (generating) {\r\n <i class=\"fa fa-times\"></i> {{'Annulla' | localize: lc}}\r\n } @else {\r\n <i class=\"fa fa-file-text\"></i> {{'Genera report' | localize: lc}}\r\n }\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Avvisi sulla finestra (W4): non bloccano la generazione -->\r\n @for (warning of warnings; track $index) {\r\n <div class=\"wr-alert wr-alert-warn\"><i class=\"fa fa-exclamation-triangle\"></i> {{warning}}</div>\r\n }\r\n @if (snapshotsLoaded && !historyAvailable) {\r\n <div class=\"wr-alert\"><i class=\"fa fa-info-circle\"></i> {{'Lo storico non \u00E8 attivo su questo ambiente: il report si pu\u00F2 produrre solo live.' | localize: lc}}</div>\r\n }\r\n\r\n <!-- Elenco degli snapshot, solo per il report fra snapshot -->\r\n @if (!live) {\r\n <div class=\"wr-panel\">\r\n <div class=\"wr-panel-header\">\r\n <span class=\"wr-title\">{{'Snapshot' | localize: lc}}</span>\r\n <div class=\"wr-seg wr-seg-small\">\r\n @for (p of Periods; track p.id) {\r\n <button type=\"button\" [class.wr-on]=\"period == p.id\" (click)=\"setPeriod(p.id)\">{{p.description}}</button>\r\n }\r\n </div>\r\n @if (snapshotsLoading) {\r\n <span class=\"wr-muted\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</span>\r\n }\r\n <span class=\"wr-spacer\"></span>\r\n @if (selectionWindow; as w) {\r\n <span class=\"wr-window\">\r\n {{'Dalle' | localize: lc}} <strong>{{local(w.from)}}</strong> {{'alle' | localize: lc}} <strong>{{local(w.to)}}</strong>\r\n ({{span(w.from, w.to)}})\r\n @if (selTo == null) {\r\n <span class=\"wr-muted\">&middot; {{\"scegli l'ultimo snapshot\" | localize: lc}}</span>\r\n }\r\n </span>\r\n <button type=\"button\" class=\"wr-link\" (click)=\"clearSelection()\">{{'Azzera' | localize: lc}}</button>\r\n } @else {\r\n <span class=\"wr-muted\">{{\"Un clic sceglie il primo snapshot, un secondo clic l'ultimo.\" | localize: lc}}</span>\r\n }\r\n </div>\r\n\r\n @if (snapshotsError) {\r\n <div class=\"wr-alert wr-alert-error\">{{snapshotsError}}</div>\r\n }\r\n\r\n <div class=\"wr-table-wrap\">\r\n <table class=\"wr-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"wr-num\">{{'Snapshot' | localize: lc}}</th>\r\n <th>{{'Inizio' | localize: lc}}</th>\r\n <th>{{'Fine' | localize: lc}}</th>\r\n <th class=\"wr-num\">{{'Durata' | localize: lc}}</th>\r\n <th class=\"wr-num\">{{'Istanze' | localize: lc}}</th>\r\n <th>{{'Manuale' | localize: lc}}</th>\r\n <th>{{'Riavvio' | localize: lc}}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (s of snapshots; track s.snapId) {\r\n <tr class=\"wr-row\" [class.wr-in-range]=\"inRange(s)\" [class.wr-edge]=\"isEdge(s)\" [class.wr-restart]=\"s.restart\" (click)=\"pick(s)\">\r\n <td class=\"wr-num\"><strong>{{s.snapId}}</strong></td>\r\n <td>{{local(s.beginIntervalTime)}}</td>\r\n <td>{{local(s.endIntervalTime)}}</td>\r\n <td class=\"wr-num\">{{span(s.beginIntervalTime, s.endIntervalTime)}}</td>\r\n <td class=\"wr-num\" [title]=\"instancesTitle(s)\">{{s.instances?.length ?? 0}}</td>\r\n <td>\r\n @if (s.manual) {\r\n <span class=\"wr-badge\"><i class=\"fa fa-hand-pointer\"></i> {{'Manuale' | localize: lc}}</span>\r\n }\r\n </td>\r\n <td>\r\n @if (s.restart) {\r\n <span class=\"wr-badge wr-badge-warn\" [title]=\"'Un\\'istanza \u00E8 ripartita: counter e meter non sono attendibili' | localize: lc\"><i class=\"fa fa-power-off\"></i> {{'Riavvio' | localize: lc}}</span>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (snapshotsLoaded && !snapshotsLoading && !snapshotsError && snapshots.length == 0 && historyAvailable) {\r\n <div class=\"wr-empty\">{{'Nessuno snapshot nel periodo scelto: prova con un periodo pi\u00F9 lungo.' | localize: lc}}</div>\r\n }\r\n </div>\r\n <div class=\"wr-footnote\">{{'Orari in ora locale' | localize: lc}} ({{zone}})</div>\r\n </div>\r\n }\r\n\r\n <!-- Report -->\r\n <div class=\"wr-panel wr-report\">\r\n @if (generating) {\r\n <div class=\"wr-alert wr-alert-busy\">\r\n <i class=\"fa fa-spinner fa-spin\"></i> {{'Generazione del report\u2026' | localize: lc}}\r\n <button type=\"button\" class=\"wr-link\" (click)=\"cancel()\">{{'Annulla' | localize: lc}}</button>\r\n </div>\r\n }\r\n @if (reportError) {\r\n <div class=\"wr-alert wr-alert-error\">{{'Il report non si pu\u00F2 produrre' | localize: lc}}: {{reportError}}</div>\r\n }\r\n\r\n @if (report) {\r\n <div class=\"wr-report-bar\">\r\n <div class=\"wr-summary\">\r\n @if (report.live) {\r\n <strong>Live</strong>\r\n } @else {\r\n <strong>{{'Snapshot' | localize: lc}} {{report.snapFrom}}\u2013{{report.snapTo}}</strong>\r\n }\r\n &middot; {{scopeLabel(report.instances)}}\r\n @if (!report.live && report.windowFrom) {\r\n &middot; {{'dalle' | localize: lc}} {{local(report.windowFrom)}} {{'alle' | localize: lc}} {{local(report.windowTo)}}\r\n }\r\n &middot; {{'generato alle' | localize: lc}} {{hhmm(report.generatedAt)}} ({{kilobytes(report.size)}})\r\n </div>\r\n <div class=\"wr-actions\">\r\n @if (report.sections.length > 0) {\r\n <div class=\"wr-field wr-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"section\" (ngModelChange)=\"goToSection($event)\" name=\"section\" [Source]=\"report.sections\" [Placeholder]=\"'Vai a\u2026' | localize: lc\"></form-select>\r\n </div>\r\n }\r\n <button type=\"button\" class=\"wr-btn\" (click)=\"download()\"><i class=\"fa fa-download\"></i> {{'Scarica' | localize: lc}}</button>\r\n <button type=\"button\" class=\"wr-btn\" (click)=\"openInTab()\"><i class=\"fa fa-external-link\"></i> {{'Apri in una scheda' | localize: lc}}</button>\r\n </div>\r\n </div>\r\n <div class=\"wr-footnote\"><i class=\"fa fa-clock\"></i> {{'Gli orari del report sono in UTC.' | localize: lc}}</div>\r\n <!-- Riquadro isolato e senza script: gli stili del report non toccano la pagina e le ancore dell'indice restano dentro (W8) -->\r\n <iframe #reportFrame class=\"wr-frame\" [class.wr-stale]=\"generating\" [srcdoc]=\"report.doc\" sandbox=\"allow-same-origin allow-popups\" title=\"Workload report\"></iframe>\r\n } @else if (!generating && !reportError) {\r\n <div class=\"wr-placeholder\">\r\n <i class=\"fa fa-file-text\"></i>\r\n {{'Scegli tipo e istanze, poi genera il report.' | localize: lc}}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .wr-noclear .p-select-clear-icon{display:none}:host ::ng-deep .wr-fill{display:block;width:100%}.wr-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 18px;padding-top:6px}.wr-toolbar-end{margin-left:auto}.wr-field{width:240px}.wr-field-wide{width:380px;max-width:100%}.wr-group{position:relative;display:inline-flex}.wr-group-label{position:absolute;top:-8px;left:8px;z-index:2;padding:0 4px;background:var(--p-content-background, #fff);color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.wr-seg{display:inline-flex;height:39px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);overflow:hidden;background:var(--p-content-background, #fff)}.wr-seg button{display:inline-flex;align-items:center;gap:6px;padding:0 14px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.wr-seg button:first-child{border-left:0}.wr-seg button:hover:not(.wr-on):not(:disabled){background:var(--p-content-hover-background, #f8fafc)}.wr-seg button:disabled{color:var(--p-text-muted-color, #64748b);opacity:.6;cursor:not-allowed}.wr-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.wr-seg button.wr-on{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca);font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.wr-seg.wr-seg-small{height:30px}.wr-seg.wr-seg-small button{padding:0 10px;font-size:12px}.wr-live-dot{width:8px;height:8px;border-radius:50%;background:#16a34a}.wr-btn{display:inline-flex;align-items:center;gap:7px;height:34px;padding:0 14px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.wr-btn:hover:not(:disabled){background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.wr-btn:disabled{opacity:.55;cursor:not-allowed}.wr-btn.wr-btn-primary{height:39px;padding:0 18px;border-color:var(--p-primary-color, #6366f1);background:var(--p-primary-color, #6366f1);color:var(--p-primary-contrast-color, #fff);font-weight:500}.wr-btn.wr-btn-primary:hover:not(:disabled){background:var(--p-primary-color, #6366f1);color:var(--p-primary-contrast-color, #fff);filter:brightness(1.08)}.wr-link{margin-left:8px;padding:0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;cursor:pointer}.wr-link:hover{text-decoration:underline}.wr-alert{margin-top:10px;padding:7px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155);font-size:13px}.wr-alert.wr-alert-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.wr-alert.wr-alert-error{border-color:#fca5a5;background:#fef2f2;color:#991b1b}.wr-alert.wr-alert-busy{margin-top:0;margin-bottom:10px;border-color:var(--p-primary-color, #6366f1);background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.wr-panel{margin-top:14px;padding:12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0}.wr-panel-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin-bottom:10px}.wr-title{font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--p-text-muted-color, #64748b)}.wr-spacer{flex:1 1 auto}.wr-window{color:var(--p-text-color, #334155);font-size:13px}.wr-muted{color:var(--p-text-muted-color, #64748b);font-size:12px}.wr-table-wrap{max-height:320px;overflow:auto;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px)}.wr-table{width:100%;border-collapse:separate;border-spacing:0;font-size:13px;color:var(--p-text-color, #334155)}.wr-table th{position:sticky;top:0;z-index:1;padding:6px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-muted-color, #64748b);font-size:12px;font-weight:500;white-space:nowrap;text-align:left}.wr-table td{padding:4px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);white-space:nowrap}.wr-num{text-align:right!important;font-variant-numeric:tabular-nums}.wr-row{cursor:pointer}.wr-row:hover td{background:var(--p-content-hover-background, #f8fafc)}.wr-row.wr-restart td:first-child{box-shadow:inset 3px 0 #f59e0b}.wr-row.wr-in-range td{background:var(--p-highlight-background, #eef2ff)}.wr-row.wr-edge td{color:var(--p-highlight-color, #4338ca);font-weight:600}.wr-badge{display:inline-flex;align-items:center;gap:4px;padding:0 7px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:10px;color:var(--p-text-muted-color, #64748b);font-size:11px;line-height:18px}.wr-badge.wr-badge-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.wr-empty{padding:22px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.wr-footnote{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.wr-report-bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px 14px;padding-top:6px}.wr-summary{color:var(--p-text-color, #334155);font-size:13px}.wr-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.wr-frame{display:block;width:100%;height:calc(100vh - 240px);min-height:520px;margin-top:8px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:#fff;transition:opacity .15s}.wr-frame.wr-stale{opacity:.5}.wr-placeholder{display:flex;flex-direction:column;align-items:center;gap:10px;padding:70px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.wr-placeholder .fa{font-size:28px;opacity:.5}\n"] }]
2405
+ }], ctorParameters: () => [{ type: i1.LocalizationService }, { type: i3.PreferencesService }, { type: i1$1.InterComService }, { type: i4.ActivatedRoute }, { type: i5$1.DomSanitizer }, { type: AdminModuleBaseService }], propDecorators: { reportFrame: [{
2406
+ type: ViewChild,
2407
+ args: ["reportFrame"]
2408
+ }] } });
2409
+
2410
+ // Metriche v2 (specifica "Metriche del Director"): richieste e risposte come le vuole e le manda il backend (camelCase).
2411
+ // Le date delle risposte sono UTC "dd/MM/yyyy HH:mm:ss" senza fuso, al secondo
2412
+ var MetricV2Type;
2413
+ (function (MetricV2Type) {
2414
+ MetricV2Type[MetricV2Type["Counter"] = 0] = "Counter";
2415
+ MetricV2Type[MetricV2Type["Gauge"] = 1] = "Gauge";
2416
+ MetricV2Type[MetricV2Type["Histogram"] = 2] = "Histogram";
2417
+ MetricV2Type[MetricV2Type["Timer"] = 3] = "Timer";
2418
+ MetricV2Type[MetricV2Type["Meter"] = 4] = "Meter";
2419
+ })(MetricV2Type || (MetricV2Type = {}));
2420
+ // Senza istanze e con global, tutto il cluster
2421
+ class MetricsV2CatalogRequest {
2422
+ }
2423
+ // Live (realTime con snapFrom), storico per periodo (snapFrom/snapTo) o storico fra due snapshot (snapIdFrom/snapIdTo)
2424
+ class MetricsV2Request extends MetricsV2CatalogRequest {
2425
+ }
2426
+ class MetricsV2Info {
2427
+ }
2428
+ // Una metrica di un'istanza in un istante: i campi che non riguardano il tipo valgono 0 o null
2429
+ class MetricsV2Sample {
2430
+ }
2431
+
2432
+ class AdmMetricsV2Loc extends LocalizationService {
2433
+ constructor(injector) {
2434
+ super(injector);
2435
+ super.set("it->en", "Metriche di Sistema", ["System Metrics"]);
2436
+ super.set("it->en", "Errore", ["Error"]);
2437
+ // Barra dei comandi
2438
+ super.set("it->en", "Sorgente", ["Source"]);
2439
+ super.set("it->en", "Storico per periodo", ["History by period"]);
2440
+ super.set("it->en", "Storico per snapshot", ["History by snapshot"]);
2441
+ super.set("it->en", "Lo storico non è attivo su questo ambiente: ci sono solo i dati live.", ["History is not active on this environment: only live data is available."]);
2442
+ super.set("it->en", "Finestra", ["Window"]);
2443
+ super.set("it->en", "1 ora", ["1 hour"]);
2444
+ super.set("it->en", "Dal", ["From"]);
2445
+ super.set("it->en", "Al", ["To"]);
2446
+ super.set("it->en", "Dallo snapshot", ["From snapshot"]);
2447
+ super.set("it->en", "Allo snapshot", ["To snapshot"]);
2448
+ super.set("it->en", "Dalle", ["From"]);
2449
+ super.set("it->en", "alle", ["at"]);
2450
+ super.set("it->en", "Istanze", ["Instances"]);
2451
+ super.set("it->en", "Tutto il cluster", ["Whole cluster"]);
2452
+ super.set("it->en", "istanze", ["instances"]);
2453
+ super.set("it->en", "istanza", ["instance"]);
2454
+ super.set("it->en", "Aggiornato alle", ["Updated at"]);
2455
+ super.set("it->en", "Pausa", ["Pause"]);
2456
+ super.set("it->en", "Riprendi", ["Resume"]);
2457
+ super.set("it->en", "Caricamento…", ["Loading…"]);
2458
+ super.set("it->en", "Grafici", ["Charts"]);
2459
+ super.set("it->en", "Tabella", ["Table"]);
2460
+ super.set("it->en", "Lo snapshot {id} segue un riavvio di {istanza}: lì le serie si interrompono.", ["Snapshot {id} follows a restart of {istanza}: series break there."]);
2461
+ // Catalogo
2462
+ super.set("it->en", "Cerca una metrica…", ["Search a metric…"]);
2463
+ super.set("it->en", "Processo", ["Process"]);
2464
+ super.set("it->en", "API", ["API"]);
2465
+ super.set("it->en", "Code asincrone", ["Async queues"]);
2466
+ super.set("it->en", "Attività", ["Activity"]);
2467
+ super.set("it->en", "di", ["of"]);
2468
+ super.set("it->en", "metriche scelte", ["metrics chosen"]);
2469
+ super.set("it->en", "Togli tutte", ["Remove all"]);
2470
+ super.set("it->en", "Voci", ["Items"]);
2471
+ super.set("it->en", "Aggiungi le {N} voci", ["Add the {N} items"]);
2472
+ // Grafici e tessere
2473
+ super.set("it->en", "Scegli una o più metriche dal catalogo.", ["Choose one or more metrics from the catalog."]);
2474
+ super.set("it->en", "dall'avvio", ["since start"]);
2475
+ super.set("it->en", "Orari in ora locale", ["Times in local time"]);
2476
+ super.set("it->en", "trascina su un grafico per lo zoom", ["drag on a chart to zoom"]);
2477
+ super.set("it->en", "Finestra intera", ["Whole window"]);
2478
+ super.set("it->en", "Togli", ["Remove"]);
2479
+ super.set("it->en", "Nessun campione di {metrica} nella finestra scelta", ["No samples of {metrica} in the chosen window"]);
2480
+ super.set("it->en", "riavvio", ["restart"]);
2481
+ super.set("it->en", "thread", ["threads"]);
2482
+ super.set("it->en", "Livello", ["Level"]);
2483
+ super.set("it->en", "Variazione", ["Change"]);
2484
+ super.set("it->en", "Quote", ["Shares"]);
2485
+ super.set("it->en", "Al secondo", ["Per second"]);
2486
+ super.set("it->en", "Rate mobili", ["Moving rates"]);
2487
+ super.set("it->en", "Percentili", ["Percentiles"]);
2488
+ super.set("it->en", "Frequenza", ["Frequency"]);
2489
+ super.set("it->en", "Chiamate e durata", ["Calls and duration"]);
2490
+ super.set("it->en", "In corso", ["In progress"]);
2491
+ super.set("it->en", "in corso", ["in progress"]);
2492
+ super.set("it->en", "mediana", ["median"]);
2493
+ super.set("it->en", "media nell'intervallo", ["interval mean"]);
2494
+ // Tabella
2495
+ super.set("it->en", "Istanza", ["Instance"]);
2496
+ super.set("it->en", "Istante", ["Time"]);
2497
+ super.set("it->en", "Intervallo (s)", ["Interval (s)"]);
2498
+ super.set("it->en", "Snapshot", ["Snapshot"]);
2499
+ super.set("it->en", "Valore", ["Value"]);
2500
+ super.set("it->en", "Conteggio", ["Count"]);
2501
+ super.set("it->en", "Quota", ["Share"]);
2502
+ super.set("it->en", "Mediana", ["Median"]);
2503
+ super.set("it->en", "Massimo", ["Maximum"]);
2504
+ super.set("it->en", "Chiamate", ["Calls"]);
2505
+ super.set("it->en", "Durata media nell'intervallo", ["Mean duration in the interval"]);
2506
+ super.set("it->en", "Esporta CSV", ["Export CSV"]);
2507
+ super.set("it->en", "Mostrate le prime 300 righe di", ["Showing the first 300 rows of"]);
2508
+ super.set("it->en", "l'esportazione le comprende tutte.", ["the export includes all of them."]);
2509
+ }
2510
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmMetricsV2Loc, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
2511
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmMetricsV2Loc }); }
2512
+ }
2513
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmMetricsV2Loc, decorators: [{
2514
+ type: Injectable
2515
+ }], ctorParameters: () => [{ type: i0.Injector }] });
2516
+
2517
+ // Angular
2518
+ // Un punto di una serie: gap = non unirlo al precedente (riavvio o buco nei campioni), v2 = estremo superiore di una fascia
2519
+ class Mv2Point {
2520
+ }
2521
+ class Mv2Series {
2522
+ }
2523
+ class Mv2Mark {
2524
+ }
2525
+ class Mv2Path {
2526
+ }
2527
+ class Mv2Rect {
2528
+ }
2529
+ class Mv2Dot {
2530
+ }
2531
+ class Mv2Tick {
2532
+ }
2533
+ class Mv2MarkTick {
2534
+ }
2535
+ class Mv2TipRow {
2536
+ }
2537
+ const LEFT = 56;
2538
+ const RIGHT = 10;
2539
+ // Spazio in alto per l'unità, che sta sopra la prima tacca
2540
+ const TOP = 20;
2541
+ const BOTTOM = 20;
2542
+ const STEPS_MS = [60, 120, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400, 172800, 604800].map(t => t * 1000);
2543
+ // Grafico SVG delle metriche v2: lo stesso asse dei tempi per tutti i pannelli, cursore e zoom condivisi tramite la pagina.
2544
+ // Le serie arrivano già pronte (punti con le interruzioni segnate): qui solo geometria e interazione
2545
+ class MetricsV2ChartComponent {
2546
+ constructor(host, zone) {
2547
+ this.host = host;
2548
+ this.zone = zone;
2549
+ this.series = [];
2550
+ this.height = 170;
2551
+ this.marks = [];
2552
+ this.cursor = null;
2553
+ this.format = t => String(t);
2554
+ this.cursorChange = new EventEmitter();
2555
+ this.zoom = new EventEmitter();
2556
+ this.width = 600;
2557
+ this.left = LEFT;
2558
+ this.right = RIGHT;
2559
+ this.top = TOP;
2560
+ this.bottom = BOTTOM;
2561
+ this.paths = [];
2562
+ this.rects = [];
2563
+ this.dots = [];
2564
+ this.yTicks = [];
2565
+ this.xTicks = [];
2566
+ this.markTicks = [];
2567
+ // Il riquadro dei valori compare solo sul grafico sotto il mouse, la linea del cursore su tutti
2568
+ this.hovered = false;
2569
+ this.cursorX = null;
2570
+ this.tipTime = "";
2571
+ this.tipRows = [];
2572
+ this.dragFrom = null;
2573
+ this.dragTo = null;
2574
+ this.yMin = 0;
2575
+ this.yMax = 1;
2576
+ }
2577
+ ngOnInit() {
2578
+ this.observer = new ResizeObserver(entries => {
2579
+ let width = Math.floor(entries[0]?.contentRect?.width ?? 0);
2580
+ if (width > 0 && width != this.width)
2581
+ this.zone.run(() => {
2582
+ this.width = width;
2583
+ this.layout();
2584
+ });
2585
+ });
2586
+ this.observer.observe(this.host.nativeElement);
2587
+ }
2588
+ ngOnDestroy() {
2589
+ this.observer?.disconnect();
2590
+ }
2591
+ ngOnChanges(changes) {
2592
+ if (changes.series || changes.from || changes.to || changes.height || changes.marks)
2593
+ this.layout();
2594
+ else if (changes.cursor)
2595
+ this.updateCursor();
2596
+ }
2597
+ min(a, b) { return Math.min(a, b); }
2598
+ abs(a) { return Math.abs(a); }
2599
+ // #region Geometria
2600
+ get plotWidth() {
2601
+ return Math.max(10, this.width - LEFT - RIGHT);
2602
+ }
2603
+ x(t) {
2604
+ return LEFT + (t - this.from) / Math.max(1, this.to - this.from) * this.plotWidth;
2605
+ }
2606
+ y(v) {
2607
+ return TOP + (1 - (v - this.yMin) / Math.max(1e-12, this.yMax - this.yMin)) * (this.height - TOP - BOTTOM);
2608
+ }
2609
+ timeAt(x) {
2610
+ return this.from + (x - LEFT) / this.plotWidth * (this.to - this.from);
2611
+ }
2612
+ layout() {
2613
+ if (this.from == null || this.to == null)
2614
+ return;
2615
+ // Scala verticale sui punti visibili, con lo zero se i valori sono positivi
2616
+ let values = [];
2617
+ for (let s of this.series ?? [])
2618
+ for (let p of s.points)
2619
+ if (p.t >= this.from && p.t <= this.to) {
2620
+ values.push(p.v);
2621
+ if (p.v2 != null)
2622
+ values.push(p.v2);
2623
+ }
2624
+ let lo = values.length ? Math.min(0, ...values) : 0;
2625
+ let hi = values.length ? Math.max(...values) : 1;
2626
+ if (hi <= lo)
2627
+ hi = lo + 1;
2628
+ let step = this.niceStep((hi - lo) / 4);
2629
+ this.yMin = Math.floor(lo / step) * step;
2630
+ this.yMax = Math.ceil(hi / step) * step;
2631
+ this.yTicks = [];
2632
+ for (let v = this.yMin; v <= this.yMax + step / 2; v += step)
2633
+ this.yTicks.push({ key: String(v), pos: this.y(v), label: this.format(Math.abs(v) < step / 1e6 ? 0 : v) });
2634
+ // Asse dei tempi: tacche a passi tondi, al massimo una ogni 90 pixel circa
2635
+ let span = this.to - this.from;
2636
+ let stepMs = STEPS_MS.find(t => span / t <= this.plotWidth / 90) ?? STEPS_MS[STEPS_MS.length - 1];
2637
+ let longRange = span > 36 * 3600 * 1000;
2638
+ let offset = new Date().getTimezoneOffset() * 60000;
2639
+ this.xTicks = [];
2640
+ for (let t = Math.ceil((this.from - offset) / stepMs) * stepMs + offset; t <= this.to; t += stepMs)
2641
+ this.xTicks.push({ key: String(t), pos: this.x(t), label: dayjs(t).format(longRange || stepMs >= 86400000 ? "DD/MM HH:mm" : "HH:mm") });
2642
+ // Segni: riavvii sempre, id degli snapshot solo se c'è spazio (altrimenti uno ogni tanto)
2643
+ let visible = (this.marks ?? []).filter(t => t.t >= this.from && t.t <= this.to);
2644
+ let snaps = visible.filter(t => t.kind == "snapshot");
2645
+ let every = Math.max(1, Math.ceil(snaps.length / Math.max(1, this.plotWidth / 45)));
2646
+ this.markTicks = visible.filter(t => t.kind == "restart" || snaps.indexOf(t) % every == 0)
2647
+ .map((t, i) => ({ key: `${t.kind}${t.t}${i}`, pos: this.x(t.t), label: t.label, kind: t.kind }));
2648
+ let paths = [];
2649
+ let rects = [];
2650
+ let dots = [];
2651
+ let bars = (this.series ?? []).filter(t => t.kind == "bars");
2652
+ for (let s of this.series ?? []) {
2653
+ let points = s.points.filter(p => p.t >= this.from && p.t <= this.to);
2654
+ if (points.length == 0)
2655
+ continue;
2656
+ switch (s.kind) {
2657
+ case "line":
2658
+ case "step":
2659
+ paths.push({ key: s.key, d: this.linePath(points, s.kind == "step"), color: s.color, fill: false, dash: s.dash ?? null, opacity: s.opacity ?? 1 });
2660
+ break;
2661
+ case "band":
2662
+ paths.push({ key: s.key, d: this.bandPath(points), color: s.color, fill: true, dash: null, opacity: s.opacity ?? 0.2 });
2663
+ break;
2664
+ case "dots":
2665
+ points.forEach((p, i) => dots.push({ key: `${s.key}${i}`, x: this.x(p.t), y: this.y(p.v), color: s.color }));
2666
+ break;
2667
+ case "bars":
2668
+ let slot = this.plotWidth / Math.max(1, points.length) * 0.8;
2669
+ let w = Math.max(1, slot / bars.length);
2670
+ let index = bars.indexOf(s);
2671
+ let zero = this.y(Math.max(this.yMin, 0));
2672
+ points.forEach((p, i) => {
2673
+ let y = this.y(p.v);
2674
+ rects.push({ key: `${s.key}${i}`, x: this.x(p.t) - slot / 2 + index * w, y: Math.min(y, zero), w: w, h: Math.max(1, Math.abs(zero - y)), color: s.color });
2675
+ });
2676
+ break;
2677
+ }
2678
+ }
2679
+ this.paths = paths;
2680
+ this.rects = rects;
2681
+ this.dots = dots;
2682
+ this.updateCursor();
2683
+ }
2684
+ // Le linee si spezzano dove il punto ha gap: niente linea fra due campioni separati da un riavvio o da un buco
2685
+ linePath(points, step) {
2686
+ let d = "";
2687
+ points.forEach((p, i) => {
2688
+ let x = this.x(p.t).toFixed(1);
2689
+ let y = this.y(p.v).toFixed(1);
2690
+ if (i == 0 || p.gap)
2691
+ d += `M${x},${y}`;
2692
+ else if (step)
2693
+ d += `H${x}V${y}`;
2694
+ else
2695
+ d += `L${x},${y}`;
2696
+ });
2697
+ return d;
2698
+ }
2699
+ // Fascia fra v e v2, un poligono per ogni tratto senza interruzioni
2700
+ bandPath(points) {
2701
+ let segments = [];
2702
+ for (let p of points) {
2703
+ if (p.v2 == null)
2704
+ continue;
2705
+ if (segments.length == 0 || p.gap)
2706
+ segments.push([]);
2707
+ segments[segments.length - 1].push(p);
2708
+ }
2709
+ return segments.filter(t => t.length > 0).map(seg => {
2710
+ let upper = seg.map((p, i) => `${i ? "L" : "M"}${this.x(p.t).toFixed(1)},${this.y(p.v2).toFixed(1)}`).join("");
2711
+ let lower = [...seg].reverse().map(p => `L${this.x(p.t).toFixed(1)},${this.y(p.v).toFixed(1)}`).join("");
2712
+ return upper + lower + "Z";
2713
+ }).join("");
2714
+ }
2715
+ niceStep(raw) {
2716
+ if (!(raw > 0))
2717
+ return 1;
2718
+ let power = Math.pow(10, Math.floor(Math.log10(raw)));
2719
+ return [1, 2, 2.5, 5, 10].find(t => raw / power <= t + 1e-9) * power;
2720
+ }
2721
+ // #endregion
2722
+ // #region Cursore e zoom
2723
+ updateCursor() {
2724
+ if (this.cursor == null || this.cursor < this.from || this.cursor > this.to) {
2725
+ this.cursorX = null;
2726
+ this.tipRows = [];
2727
+ return;
2728
+ }
2729
+ this.cursorX = this.x(this.cursor);
2730
+ this.tipTime = dayjs(this.cursor).format("DD/MM HH:mm:ss");
2731
+ let rows = [];
2732
+ for (let s of this.series ?? []) {
2733
+ if (s.tooltip === false || s.points.length == 0)
2734
+ continue;
2735
+ let nearest = this.nearest(s.points, this.cursor);
2736
+ if (nearest)
2737
+ rows.push({ color: s.color, label: s.label, value: this.format(nearest.v) });
2738
+ }
2739
+ this.tipRows = rows;
2740
+ }
2741
+ // Il campione più vicino, se non è più lontano di un intervallo tipico della serie
2742
+ nearest(points, t) {
2743
+ let best = null;
2744
+ for (let p of points)
2745
+ if (!best || Math.abs(p.t - t) < Math.abs(best.t - t))
2746
+ best = p;
2747
+ let typical = points.length > 1 ? (points[points.length - 1].t - points[0].t) / (points.length - 1) : Infinity;
2748
+ return best && Math.abs(best.t - t) <= Math.max(typical, 1000) ? best : null;
2749
+ }
2750
+ plotX(event) {
2751
+ let rect = this.host.nativeElement.getBoundingClientRect();
2752
+ return Math.min(this.width - RIGHT, Math.max(LEFT, event.clientX - rect.left));
2753
+ }
2754
+ move(event) {
2755
+ let x = this.plotX(event);
2756
+ this.hovered = true;
2757
+ if (this.dragFrom != null)
2758
+ this.dragTo = x;
2759
+ this.cursorChange.emit(this.timeAt(x));
2760
+ }
2761
+ leave() {
2762
+ this.hovered = false;
2763
+ this.dragFrom = null;
2764
+ this.dragTo = null;
2765
+ this.cursorChange.emit(null);
2766
+ }
2767
+ // Trascinando sull'area si sceglie la finestra dello zoom, per tutti i pannelli
2768
+ down(event) {
2769
+ if (event.button != 0)
2770
+ return;
2771
+ event.preventDefault();
2772
+ this.dragFrom = this.plotX(event);
2773
+ this.dragTo = null;
2774
+ }
2775
+ up(event) {
2776
+ let from = this.dragFrom;
2777
+ let to = this.plotX(event);
2778
+ this.dragFrom = null;
2779
+ this.dragTo = null;
2780
+ if (from != null && Math.abs(to - from) > 6)
2781
+ this.zoom.emit({ from: this.timeAt(Math.min(from, to)), to: this.timeAt(Math.max(from, to)) });
2782
+ }
2783
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: MetricsV2ChartComponent, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
2784
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: MetricsV2ChartComponent, isStandalone: false, selector: "mv2-chart", inputs: { series: "series", from: "from", to: "to", height: "height", unit: "unit", marks: "marks", cursor: "cursor", format: "format" }, outputs: { cursorChange: "cursorChange", zoom: "zoom" }, usesOnChanges: true, ngImport: i0, template: `
2785
+ <div class="mv2c" [style.height.px]="height" (mousemove)="move($event)" (mouseleave)="leave()" (mousedown)="down($event)" (mouseup)="up($event)">
2786
+ <svg [attr.width]="width" [attr.height]="height" [attr.viewBox]="'0 0 ' + width + ' ' + height">
2787
+ @for (tick of yTicks; track tick.key) {
2788
+ <line class="mv2c-grid" [attr.x1]="left" [attr.x2]="width - right" [attr.y1]="tick.pos" [attr.y2]="tick.pos" />
2789
+ <text class="mv2c-ylabel" [attr.x]="left - 6" [attr.y]="tick.pos + 3">{{tick.label}}</text>
2790
+ }
2791
+ @for (tick of xTicks; track tick.key) {
2792
+ <text class="mv2c-xlabel" [attr.x]="tick.pos" [attr.y]="height - 5">{{tick.label}}</text>
2793
+ }
2794
+ @if (unit) {
2795
+ <text class="mv2c-unit" [attr.x]="4" [attr.y]="11">{{unit}}</text>
2796
+ }
2797
+ @for (mark of markTicks; track mark.key) {
2798
+ <line class="mv2c-mark" [class.mv2c-mark-restart]="mark.kind == 'restart'" [class.mv2c-mark-snapshot]="mark.kind == 'snapshot'" [attr.x1]="mark.pos" [attr.x2]="mark.pos" [attr.y1]="top" [attr.y2]="height - bottom" />
2799
+ <text class="mv2c-marklabel" [attr.x]="mark.pos + 3" [attr.y]="top + 9">{{mark.label}}</text>
2800
+ }
2801
+ @for (p of paths; track p.key) {
2802
+ <path [attr.d]="p.d" [attr.fill]="p.fill ? p.color : 'none'" [attr.fill-opacity]="p.fill ? p.opacity : null"
2803
+ [attr.stroke]="p.fill ? 'none' : p.color" [attr.stroke-opacity]="p.fill ? null : p.opacity" [attr.stroke-dasharray]="p.dash" stroke-width="1.6" stroke-linejoin="round" />
2804
+ }
2805
+ @for (r of rects; track r.key) {
2806
+ <rect [attr.x]="r.x" [attr.y]="r.y" [attr.width]="r.w" [attr.height]="r.h" [attr.fill]="r.color" fill-opacity="0.75" />
2807
+ }
2808
+ @for (d of dots; track d.key) {
2809
+ <circle [attr.cx]="d.x" [attr.cy]="d.y" r="2" [attr.fill]="d.color" />
2810
+ }
2811
+ <line class="mv2c-axis" [attr.x1]="left" [attr.x2]="width - right" [attr.y1]="height - bottom" [attr.y2]="height - bottom" />
2812
+ @if (cursorX != null) {
2813
+ <line class="mv2c-cursor" [attr.x1]="cursorX" [attr.x2]="cursorX" [attr.y1]="top" [attr.y2]="height - bottom" />
2814
+ }
2815
+ @if (dragFrom != null && dragTo != null) {
2816
+ <rect class="mv2c-drag" [attr.x]="min(dragFrom, dragTo)" [attr.y]="top" [attr.width]="abs(dragTo - dragFrom)" [attr.height]="height - top - bottom" />
2817
+ }
2818
+ </svg>
2819
+ @if (cursorX != null && tipRows.length > 0 && hovered) {
2820
+ <div class="mv2c-tip" [style.left.px]="cursorX > width / 2 ? null : cursorX + 10" [style.right.px]="cursorX > width / 2 ? width - cursorX + 10 : null">
2821
+ <div class="mv2c-tip-time">{{tipTime}}</div>
2822
+ @for (row of tipRows; track $index) {
2823
+ <div class="mv2c-tip-row"><span class="mv2c-tip-dot" [style.background]="row.color"></span>{{row.label}} <strong>{{row.value}}</strong></div>
2824
+ }
2825
+ </div>
2826
+ }
2827
+ </div>
2828
+ `, isInline: true, styles: [":host{display:block;position:relative;-webkit-user-select:none;user-select:none}.mv2c{position:relative;cursor:crosshair}svg{display:block;overflow:visible}.mv2c-grid{stroke:var(--p-content-border-color, #e2e8f0);stroke-dasharray:2 3}.mv2c-axis{stroke:var(--p-content-border-color, #cbd5e1)}.mv2c-ylabel{fill:var(--p-text-muted-color, #64748b);font-size:10px;text-anchor:end}.mv2c-xlabel{fill:var(--p-text-muted-color, #64748b);font-size:10px;text-anchor:middle}.mv2c-unit{fill:var(--p-text-muted-color, #64748b);font-size:10px}.mv2c-mark{stroke-width:1}.mv2c-mark-restart{stroke:#f59e0b;stroke-dasharray:4 3}.mv2c-mark-snapshot{stroke:var(--p-content-border-color, #e2e8f0)}.mv2c-marklabel{fill:var(--p-text-muted-color, #64748b);font-size:9px}.mv2c-cursor{stroke:var(--p-text-muted-color, #64748b);stroke-width:1}.mv2c-drag{fill:var(--p-primary-color, #6366f1);fill-opacity:.12}.mv2c-tip{position:absolute;top:4px;z-index:3;min-width:140px;max-width:320px;padding:6px 8px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:6px;background:var(--p-content-background, #fff);box-shadow:0 4px 14px #0f172a1f;font-size:11px;pointer-events:none}.mv2c-tip-time{margin-bottom:3px;color:var(--p-text-muted-color, #64748b)}.mv2c-tip-row{display:flex;align-items:center;gap:5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mv2c-tip-row strong{margin-left:auto;padding-left:8px}.mv2c-tip-dot{flex:none;width:8px;height:8px;border-radius:50%}\n"] }); }
2829
+ }
2830
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: MetricsV2ChartComponent, decorators: [{
2831
+ type: Component,
2832
+ args: [{ selector: "mv2-chart", template: `
2833
+ <div class="mv2c" [style.height.px]="height" (mousemove)="move($event)" (mouseleave)="leave()" (mousedown)="down($event)" (mouseup)="up($event)">
2834
+ <svg [attr.width]="width" [attr.height]="height" [attr.viewBox]="'0 0 ' + width + ' ' + height">
2835
+ @for (tick of yTicks; track tick.key) {
2836
+ <line class="mv2c-grid" [attr.x1]="left" [attr.x2]="width - right" [attr.y1]="tick.pos" [attr.y2]="tick.pos" />
2837
+ <text class="mv2c-ylabel" [attr.x]="left - 6" [attr.y]="tick.pos + 3">{{tick.label}}</text>
2838
+ }
2839
+ @for (tick of xTicks; track tick.key) {
2840
+ <text class="mv2c-xlabel" [attr.x]="tick.pos" [attr.y]="height - 5">{{tick.label}}</text>
2841
+ }
2842
+ @if (unit) {
2843
+ <text class="mv2c-unit" [attr.x]="4" [attr.y]="11">{{unit}}</text>
2844
+ }
2845
+ @for (mark of markTicks; track mark.key) {
2846
+ <line class="mv2c-mark" [class.mv2c-mark-restart]="mark.kind == 'restart'" [class.mv2c-mark-snapshot]="mark.kind == 'snapshot'" [attr.x1]="mark.pos" [attr.x2]="mark.pos" [attr.y1]="top" [attr.y2]="height - bottom" />
2847
+ <text class="mv2c-marklabel" [attr.x]="mark.pos + 3" [attr.y]="top + 9">{{mark.label}}</text>
2848
+ }
2849
+ @for (p of paths; track p.key) {
2850
+ <path [attr.d]="p.d" [attr.fill]="p.fill ? p.color : 'none'" [attr.fill-opacity]="p.fill ? p.opacity : null"
2851
+ [attr.stroke]="p.fill ? 'none' : p.color" [attr.stroke-opacity]="p.fill ? null : p.opacity" [attr.stroke-dasharray]="p.dash" stroke-width="1.6" stroke-linejoin="round" />
2852
+ }
2853
+ @for (r of rects; track r.key) {
2854
+ <rect [attr.x]="r.x" [attr.y]="r.y" [attr.width]="r.w" [attr.height]="r.h" [attr.fill]="r.color" fill-opacity="0.75" />
2855
+ }
2856
+ @for (d of dots; track d.key) {
2857
+ <circle [attr.cx]="d.x" [attr.cy]="d.y" r="2" [attr.fill]="d.color" />
2858
+ }
2859
+ <line class="mv2c-axis" [attr.x1]="left" [attr.x2]="width - right" [attr.y1]="height - bottom" [attr.y2]="height - bottom" />
2860
+ @if (cursorX != null) {
2861
+ <line class="mv2c-cursor" [attr.x1]="cursorX" [attr.x2]="cursorX" [attr.y1]="top" [attr.y2]="height - bottom" />
2862
+ }
2863
+ @if (dragFrom != null && dragTo != null) {
2864
+ <rect class="mv2c-drag" [attr.x]="min(dragFrom, dragTo)" [attr.y]="top" [attr.width]="abs(dragTo - dragFrom)" [attr.height]="height - top - bottom" />
2865
+ }
2866
+ </svg>
2867
+ @if (cursorX != null && tipRows.length > 0 && hovered) {
2868
+ <div class="mv2c-tip" [style.left.px]="cursorX > width / 2 ? null : cursorX + 10" [style.right.px]="cursorX > width / 2 ? width - cursorX + 10 : null">
2869
+ <div class="mv2c-tip-time">{{tipTime}}</div>
2870
+ @for (row of tipRows; track $index) {
2871
+ <div class="mv2c-tip-row"><span class="mv2c-tip-dot" [style.background]="row.color"></span>{{row.label}} <strong>{{row.value}}</strong></div>
2872
+ }
2873
+ </div>
2874
+ }
2875
+ </div>
2876
+ `, standalone: false, styles: [":host{display:block;position:relative;-webkit-user-select:none;user-select:none}.mv2c{position:relative;cursor:crosshair}svg{display:block;overflow:visible}.mv2c-grid{stroke:var(--p-content-border-color, #e2e8f0);stroke-dasharray:2 3}.mv2c-axis{stroke:var(--p-content-border-color, #cbd5e1)}.mv2c-ylabel{fill:var(--p-text-muted-color, #64748b);font-size:10px;text-anchor:end}.mv2c-xlabel{fill:var(--p-text-muted-color, #64748b);font-size:10px;text-anchor:middle}.mv2c-unit{fill:var(--p-text-muted-color, #64748b);font-size:10px}.mv2c-mark{stroke-width:1}.mv2c-mark-restart{stroke:#f59e0b;stroke-dasharray:4 3}.mv2c-mark-snapshot{stroke:var(--p-content-border-color, #e2e8f0)}.mv2c-marklabel{fill:var(--p-text-muted-color, #64748b);font-size:9px}.mv2c-cursor{stroke:var(--p-text-muted-color, #64748b);stroke-width:1}.mv2c-drag{fill:var(--p-primary-color, #6366f1);fill-opacity:.12}.mv2c-tip{position:absolute;top:4px;z-index:3;min-width:140px;max-width:320px;padding:6px 8px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:6px;background:var(--p-content-background, #fff);box-shadow:0 4px 14px #0f172a1f;font-size:11px;pointer-events:none}.mv2c-tip-time{margin-bottom:3px;color:var(--p-text-muted-color, #64748b)}.mv2c-tip-row{display:flex;align-items:center;gap:5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mv2c-tip-row strong{margin-left:auto;padding-left:8px}.mv2c-tip-dot{flex:none;width:8px;height:8px;border-radius:50%}\n"] }]
2877
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.NgZone }], propDecorators: { series: [{
2878
+ type: Input
2879
+ }], from: [{
2880
+ type: Input
2881
+ }], to: [{
2882
+ type: Input
2883
+ }], height: [{
2884
+ type: Input
2885
+ }], unit: [{
2886
+ type: Input
2887
+ }], marks: [{
2888
+ type: Input
2889
+ }], cursor: [{
2890
+ type: Input
2891
+ }], format: [{
2892
+ type: Input
2893
+ }], cursorChange: [{
2894
+ type: Output
2895
+ }], zoom: [{
2896
+ type: Output
2897
+ }] } });
2898
+
2899
+ // Angular
2900
+ const STORAGE_KEY$1 = "HotSearchMetricsV2";
2901
+ const MAX_METRICS = 12;
2902
+ // Ogni istanza campiona ogni 15 secondi: più spesso i campioni non cambiano (M19)
2903
+ const REFRESH_MS = 15000;
2904
+ const SELECTION_DELAY_MS = 350;
2905
+ const DATES_DELAY_MS = 600;
2906
+ const TABLE_ROWS = 300;
2907
+ const INSTANCE_COLORS = ["#1e88e5", "#e53935", "#43a047", "#fb8c00", "#8e24aa", "#00897b", "#6d4c41", "#3949ab", "#c0ca33", "#d81b60"];
2908
+ const ITEM_COLORS = ["#3949ab", "#00897b", "#f4511e", "#8e24aa", "#7cb342", "#fdd835", "#6d4c41", "#039be5", "#d81b60", "#546e7a"];
2909
+ // "Active threads time" è un meter in microsecondi di thread attivi: il suo rate al secondo diviso un milione è il numero medio di thread attivi
2910
+ const ACTIVE_THREADS_TIME = "Active threads time";
2911
+ const CPU_USAGE = "CPU Usage";
2912
+ // flatMap non c'è nel lib di compilazione della libreria
2913
+ const flatten = (list, map) => list.reduce((all, t) => all.concat(map(t)), []);
2914
+ const PRESETS = [
2915
+ ["Processo", ["CPU Usage", "Working set", "Private memory", "Thread Pool Queue Length"]],
2916
+ ["API", ["API Call", "Requests", "400 Errors", "500 Errors"]],
2917
+ ["Code asincrone", null],
2918
+ ["Attività", ["Active sessions", "Active threads", ACTIVE_THREADS_TIME]]
2919
+ ];
2920
+ class Mv2CatalogEntry {
2921
+ }
2922
+ class Mv2CatalogGroup {
2923
+ }
2924
+ class Mv2Chart {
2925
+ }
2926
+ class Mv2Panel {
2927
+ }
2928
+ class Mv2TileRow {
2929
+ }
2930
+ class Mv2Tile {
2931
+ }
2932
+ class Mv2Column {
2933
+ }
2934
+ class Mv2TableSection {
2935
+ }
2936
+ // Ricordate fra una visita e l'altra (M11)
2937
+ class MetricsV2Settings {
2938
+ }
2939
+ class AdmMetricsV2Component {
2940
+ constructor(dateExts, lc, localStorage, intercom, route, adms) {
2941
+ this.dateExts = dateExts;
2942
+ this.lc = lc;
2943
+ this.localStorage = localStorage;
2944
+ this.intercom = intercom;
2945
+ this.route = route;
2946
+ this.adms = adms;
2947
+ this.Max = MAX_METRICS;
2948
+ this.Type = MetricV2Type;
2949
+ // Comandi
2950
+ this.source = "live";
2951
+ this.liveMinutes = "30";
2952
+ this.filterfrom = "";
2953
+ this.filterto = "";
2954
+ this.snapFromId = "";
2955
+ this.snapToId = "";
2956
+ // Vuoto = tutto il cluster
2957
+ this.instances = [];
2958
+ this.view = "charts";
2959
+ this.paused = false;
2960
+ // Catalogo e selezione
2961
+ this.catalog = [];
2962
+ this.groups = [];
2963
+ this.search = "";
2964
+ this.selected = [];
2965
+ this.catalogLoading = false;
2966
+ // Snapshot
2967
+ this.snapshots = [];
2968
+ this.historyAvailable = true;
2969
+ this.snapshotsLoaded = false;
2970
+ this.SnapshotOptions = [];
2971
+ this.warnings = [];
2972
+ // Dati
2973
+ this.dataLoading = false;
2974
+ this.panels = [];
2975
+ this.tiles = [];
2976
+ this.tables = [];
2977
+ this.marks = [];
2978
+ this.legend = [];
2979
+ this.viewFrom = null;
2980
+ this.viewTo = null;
2981
+ this.cursor = null;
2982
+ this.Sources = [];
2983
+ this.LiveWindows = [];
2984
+ this.Views = [];
2985
+ this.Instances = [];
2986
+ this.Presets = PRESETS.map(t => t[0]);
2987
+ this.data = new Map();
2988
+ this.seen = new Set();
2989
+ this.hidden = new Set();
2990
+ this.alts = {};
2991
+ this.dataGeneration = 0;
2992
+ this.catalogGeneration = 0;
2993
+ this.visibilityHandler = () => this.visibilityChanged();
2994
+ this.format = (value) => this.number(value);
2995
+ this.be = route.snapshot.params["be"] || "";
2996
+ this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
2997
+ }
2998
+ async ngOnInit() {
2999
+ this.Sources = [new KeyValue("live", "Live"), new KeyValue("period", this.lc.loc("Storico per periodo")), new KeyValue("snap", this.lc.loc("Storico per snapshot"))];
3000
+ this.LiveWindows = [new KeyValue("15", "15 min"), new KeyValue("30", "30 min"), new KeyValue("60", this.lc.loc("1 ora"))];
3001
+ this.Views = [new KeyValue("charts", this.lc.loc("Grafici")), new KeyValue("table", this.lc.loc("Tabella"))];
3002
+ let saved = this.route.snapshot.queryParams["ignoreStorage"] ? null : await firstValueFrom(this.localStorage.getItem(STORAGE_KEY$1), { defaultValue: null });
3003
+ if (saved) {
3004
+ this.selected = (saved.selected ?? []).slice(0, MAX_METRICS);
3005
+ this.source = saved.source ?? this.source;
3006
+ this.instances = saved.instances ?? this.instances;
3007
+ this.liveMinutes = saved.liveMinutes ?? this.liveMinutes;
3008
+ this.view = saved.view ?? this.view;
3009
+ }
3010
+ this.setLastDay();
3011
+ this.adms.getInstances(this.be).then(t => {
3012
+ this.Instances = t ?? [];
3013
+ this.rebuild();
3014
+ });
3015
+ this.loadSnapshots();
3016
+ await this.loadCatalog();
3017
+ this.loadData();
3018
+ this.refreshTimer = setInterval(() => this.refresh(), REFRESH_MS);
3019
+ document.addEventListener("visibilitychange", this.visibilityHandler);
3020
+ }
3021
+ ngOnDestroy() {
3022
+ clearInterval(this.refreshTimer);
3023
+ clearTimeout(this.selectionTimer);
3024
+ clearTimeout(this.datesTimer);
3025
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
3026
+ }
3027
+ // #region Comandi
3028
+ get clusterLabel() {
3029
+ return this.Instances.length ? `${this.lc.loc("Tutto il cluster")} (${this.Instances.length} ${this.Instances.length == 1 ? this.lc.loc("istanza") : this.lc.loc("istanze")})` : this.lc.loc("Tutto il cluster");
3030
+ }
3031
+ get zone() {
3032
+ return `UTC${dayjs().format("Z")}`;
3033
+ }
3034
+ setSource(id) {
3035
+ if (id != "live" && !this.historyAvailable)
3036
+ return;
3037
+ this.source = id;
3038
+ if (this.source == "snap")
3039
+ this.defaultSnapshots();
3040
+ this.updateWarnings();
3041
+ this.saveSettings();
3042
+ this.loadData();
3043
+ }
3044
+ setLiveMinutes(id) {
3045
+ this.liveMinutes = id;
3046
+ this.saveSettings();
3047
+ this.loadData();
3048
+ }
3049
+ datesChanged() {
3050
+ clearTimeout(this.datesTimer);
3051
+ this.datesTimer = setTimeout(() => {
3052
+ if (this.toDate(this.filterfrom) && this.toDate(this.filterto))
3053
+ this.loadData();
3054
+ }, DATES_DELAY_MS);
3055
+ }
3056
+ snapshotsChanged() {
3057
+ this.updateWarnings();
3058
+ this.loadData();
3059
+ }
3060
+ // Cambiando istanze si ricaricano catalogo, snapshot e dati; le metriche che il catalogo non ha più si tolgono
3061
+ async instancesChanged(ids) {
3062
+ this.instances = ids ?? [];
3063
+ this.saveSettings();
3064
+ this.loadSnapshots();
3065
+ await this.loadCatalog();
3066
+ this.loadData();
3067
+ }
3068
+ setView(id) {
3069
+ this.view = id;
3070
+ this.saveSettings();
3071
+ }
3072
+ togglePause() {
3073
+ this.paused = !this.paused;
3074
+ if (!this.paused)
3075
+ this.refresh();
3076
+ }
3077
+ // #endregion
3078
+ // #region Catalogo e selezione
3079
+ async loadCatalog() {
3080
+ let generation = ++this.catalogGeneration;
3081
+ this.catalogLoading = true;
3082
+ this.catalogError = null;
3083
+ try {
3084
+ let req = this.instances.length ? { instanceIds: this.instances } : { global: true };
3085
+ let res = await this.adms.getMetricsCatalogV2(this.be, req);
3086
+ if (generation != this.catalogGeneration)
3087
+ return;
3088
+ this.catalog = res ?? [];
3089
+ let names = new Set(this.catalog.map(t => t.name));
3090
+ let kept = this.selected.filter(t => names.has(t));
3091
+ if (kept.length != this.selected.length) {
3092
+ this.selected = kept;
3093
+ this.saveSettings();
3094
+ }
3095
+ this.buildGroups();
3096
+ }
3097
+ catch (err) {
3098
+ if (generation == this.catalogGeneration)
3099
+ this.catalogError = this.errorMessage(err);
3100
+ }
3101
+ finally {
3102
+ if (generation == this.catalogGeneration)
3103
+ this.catalogLoading = false;
3104
+ }
3105
+ }
3106
+ // Gruppi per tipo; le voci di un totale stanno sotto il totale, chiuse (M10)
3107
+ buildGroups() {
3108
+ let text = (this.search ?? "").trim().toLowerCase();
3109
+ let matches = (info) => !text || info.name.toLowerCase().includes(text);
3110
+ let parentOf = (info) => this.catalog.filter(t => t != info && info.name.startsWith(t.name + " - ")).sort((a, b) => b.name.length - a.name.length)[0];
3111
+ let opened = new Set(flatten(this.groups, g => g.entries.filter(e => e.open).map(e => e.info.name)));
3112
+ let types = [MetricV2Type.Gauge, MetricV2Type.Counter, MetricV2Type.Meter, MetricV2Type.Histogram, MetricV2Type.Timer];
3113
+ this.groups = types.map(type => {
3114
+ let infos = this.catalog.filter(t => t.metricType == type);
3115
+ let entries = infos.filter(t => !parentOf(t)).map(info => {
3116
+ let items = infos.filter(t => parentOf(t) == info);
3117
+ return { info: info, items: items.filter(t => matches(t) || matches(info)), open: opened.has(info.name) || (!!text && items.some(matches)) };
3118
+ }).filter(t => matches(t.info) || t.items.length > 0);
3119
+ return { type: type, label: MetricV2Type[type], entries: entries, count: infos.length };
3120
+ }).filter(t => t.entries.length > 0);
3121
+ }
3122
+ isSelected(name) {
3123
+ return this.selected.includes(name);
3124
+ }
3125
+ canSelect(name) {
3126
+ return this.isSelected(name) || this.selected.length < MAX_METRICS;
3127
+ }
3128
+ toggleMetric(name) {
3129
+ if (this.isSelected(name))
3130
+ this.selected = this.selected.filter(t => t != name);
3131
+ else if (this.selected.length < MAX_METRICS)
3132
+ this.selected = [...this.selected, name];
3133
+ this.selectionChanged();
3134
+ }
3135
+ addItems(entry) {
3136
+ let missing = [entry.info, ...entry.items].map(t => t.name).filter(t => !this.isSelected(t));
3137
+ this.selected = [...this.selected, ...missing].slice(0, MAX_METRICS);
3138
+ entry.open = true;
3139
+ this.selectionChanged();
3140
+ }
3141
+ clearSelection() {
3142
+ this.selected = [];
3143
+ this.selectionChanged();
3144
+ }
3145
+ // Insiemi pronti (M9): le metriche che il catalogo non ha si ignorano
3146
+ applyPreset(name) {
3147
+ let preset = PRESETS.find(t => t[0] == name);
3148
+ let names = preset[1] ?? this.catalog.filter(t => t.name.startsWith("AQ - ") && !this.catalog.some(k => k != t && t.name.startsWith(k.name + " - "))).map(t => t.name);
3149
+ let available = new Set(this.catalog.map(t => t.name));
3150
+ this.selected = names.filter(t => available.has(t)).slice(0, MAX_METRICS);
3151
+ this.selectionChanged();
3152
+ }
3153
+ removeMetric(name) {
3154
+ this.selected = this.selected.filter(t => t != name);
3155
+ this.selectionChanged();
3156
+ }
3157
+ // Più clic di seguito diventano una sola richiesta
3158
+ selectionChanged() {
3159
+ this.saveSettings();
3160
+ clearTimeout(this.selectionTimer);
3161
+ this.selectionTimer = setTimeout(() => this.loadData(), SELECTION_DELAY_MS);
3162
+ }
3163
+ catalogUnit(info) {
3164
+ if (info.name == CPU_USAGE)
3165
+ return "CPU";
3166
+ let unit = info.unit || "";
3167
+ if (info.metricType == MetricV2Type.Timer && info.durationUnit)
3168
+ return [unit, this.durationLabel(info.durationUnit)].filter(t => t).join(" · ");
3169
+ return unit;
3170
+ }
3171
+ addItemsLabel(entry) {
3172
+ return this.lc.loc("Aggiungi le {N} voci").replace("{N}", String(entry.items.length));
3173
+ }
3174
+ emptyLabel(name) {
3175
+ return this.lc.loc("Nessun campione di {metrica} nella finestra scelta").replace("{metrica}", name);
3176
+ }
3177
+ // "2 di 3" se la metrica non c'è su tutte le istanze
3178
+ instancesLabel(info) {
3179
+ let total = this.instances.length || this.Instances.length;
3180
+ return total && info.instances?.length < total ? `${info.instances.length} ${this.lc.loc("di")} ${total}` : "";
3181
+ }
3182
+ // #endregion
3183
+ // #region Snapshot
3184
+ async loadSnapshots() {
3185
+ try {
3186
+ let req = new WorkloadSnapshotsRequest();
3187
+ req.from = new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString();
3188
+ if (this.instances.length)
3189
+ req.instanceIds = this.instances;
3190
+ let res = await this.adms.getWorkloadSnapshots(this.be, req);
3191
+ this.historyAvailable = res?.historyAvailable ?? false;
3192
+ this.snapshots = res?.snapshots ?? [];
3193
+ }
3194
+ catch {
3195
+ // Senza elenco degli snapshot resta il live: le sorgenti storiche restano disponibili per periodo
3196
+ this.snapshots = [];
3197
+ }
3198
+ this.snapshotsLoaded = true;
3199
+ this.SnapshotOptions = this.snapshots.map(t => new KeyValue(String(t.snapId), `${t.snapId} · ${this.local(t.endIntervalTime)}${t.restart ? " ⟲" : ""}${t.manual ? " ✋" : ""}`));
3200
+ if (!this.historyAvailable && this.source != "live") {
3201
+ this.source = "live";
3202
+ this.loadData();
3203
+ }
3204
+ let ids = new Set(this.snapshots.map(t => String(t.snapId)));
3205
+ if (!ids.has(this.snapFromId) || !ids.has(this.snapToId))
3206
+ this.defaultSnapshots();
3207
+ this.updateWarnings();
3208
+ }
3209
+ // Di default le ultime 12 ore di snapshot
3210
+ defaultSnapshots() {
3211
+ if (this.snapshots.length == 0 || (this.snapFromId && this.snapToId && this.snapshots.some(t => String(t.snapId) == this.snapFromId)))
3212
+ return;
3213
+ this.snapToId = String(this.snapshots[0].snapId);
3214
+ this.snapFromId = String(this.snapshots[Math.min(11, this.snapshots.length - 1)].snapId);
3215
+ }
3216
+ get snapshotWindow() {
3217
+ let ids = [+this.snapFromId, +this.snapToId];
3218
+ let first = this.snapshots.find(t => t.snapId == Math.min(...ids));
3219
+ let last = this.snapshots.find(t => t.snapId == Math.max(...ids));
3220
+ return first && last ? { from: this.parseUtc(first.beginIntervalTime), to: this.parseUtc(last.endIntervalTime) } : null;
3221
+ }
3222
+ // M4: un riavvio fra i due snapshot interrompe le serie
3223
+ updateWarnings() {
3224
+ let warnings = [];
3225
+ if (this.source == "snap" && this.snapFromId && this.snapToId) {
3226
+ let lo = Math.min(+this.snapFromId, +this.snapToId);
3227
+ let hi = Math.max(+this.snapFromId, +this.snapToId);
3228
+ this.snapshots.forEach((s, i) => {
3229
+ if (s.restart && s.snapId > lo && s.snapId <= hi) {
3230
+ let previous = this.snapshots[i + 1];
3231
+ let restarted = (s.instances ?? []).filter(t => {
3232
+ let before = previous?.instances?.find(k => k.instanceId == t.instanceId);
3233
+ return before ? before.startupTime != t.startupTime : true;
3234
+ }).map(t => t.instanceId);
3235
+ for (let instance of restarted)
3236
+ warnings.push(this.lc.loc("Lo snapshot {id} segue un riavvio di {istanza}: lì le serie si interrompono.").replace("{id}", String(s.snapId)).replace("{istanza}", instance));
3237
+ }
3238
+ });
3239
+ }
3240
+ this.warnings = warnings;
3241
+ }
3242
+ // #endregion
3243
+ // #region Dati
3244
+ request() {
3245
+ let req = new MetricsV2Request();
3246
+ req.metricNames = [...this.selected];
3247
+ // Mai global=false senza istanze (M6)
3248
+ if (this.instances.length)
3249
+ req.instanceIds = [...this.instances];
3250
+ else
3251
+ req.global = true;
3252
+ if (this.source == "live") {
3253
+ req.realTime = true;
3254
+ req.snapFrom = new Date(Date.now() - +this.liveMinutes * 60000).toISOString();
3255
+ }
3256
+ else if (this.source == "period") {
3257
+ req.realTime = false;
3258
+ req.snapFrom = this.toDate(this.filterfrom)?.toISOString();
3259
+ req.snapTo = this.toDate(this.filterto)?.toISOString();
3260
+ }
3261
+ else {
3262
+ req.realTime = false;
3263
+ req.snapIdFrom = Math.min(+this.snapFromId, +this.snapToId);
3264
+ req.snapIdTo = Math.max(+this.snapFromId, +this.snapToId);
3265
+ }
3266
+ return req;
3267
+ }
3268
+ // Caricamento completo della finestra scelta; senza metriche scelte non parte nessuna chiamata (M8)
3269
+ async loadData() {
3270
+ let generation = ++this.dataGeneration;
3271
+ clearTimeout(this.selectionTimer);
3272
+ this.viewFrom = null;
3273
+ this.viewTo = null;
3274
+ this.dataError = null;
3275
+ if (this.selected.length == 0 || (this.source == "snap" && (!this.snapFromId || !this.snapToId))) {
3276
+ this.data = new Map();
3277
+ this.seen = new Set();
3278
+ this.rebuild();
3279
+ return;
3280
+ }
3281
+ let req = this.request();
3282
+ this.setDomain(req);
3283
+ this.dataLoading = true;
3284
+ try {
3285
+ let res = await this.adms.getMetricsV2(this.be, req);
3286
+ if (generation != this.dataGeneration)
3287
+ return;
3288
+ this.data = new Map();
3289
+ this.seen = new Set();
3290
+ this.merge(res ?? []);
3291
+ this.lastUpdate = new Date();
3292
+ this.rebuild();
3293
+ }
3294
+ catch (err) {
3295
+ if (generation == this.dataGeneration) {
3296
+ this.dataError = this.errorMessage(err);
3297
+ this.data = new Map();
3298
+ this.rebuild();
3299
+ }
3300
+ }
3301
+ finally {
3302
+ if (generation == this.dataGeneration)
3303
+ this.dataLoading = false;
3304
+ }
3305
+ }
3306
+ // M19: solo i campioni nuovi, da quello più vecchio fra gli ultimi delle istanze in pagina; sullo storico niente aggiornamento
3307
+ async refresh() {
3308
+ if (this.source != "live" || this.paused || document.hidden || this.dataLoading || this.selected.length == 0)
3309
+ return;
3310
+ let lastByInstance = new Map();
3311
+ this.data.forEach(byInstance => byInstance.forEach((list, instance) => {
3312
+ let last = list.length ? this.time(list[list.length - 1]) : null;
3313
+ if (last != null)
3314
+ lastByInstance.set(instance, Math.max(lastByInstance.get(instance) ?? 0, last));
3315
+ }));
3316
+ if (lastByInstance.size == 0)
3317
+ return this.loadData();
3318
+ let generation = this.dataGeneration;
3319
+ let req = this.request();
3320
+ req.snapFrom = new Date(Math.min(...lastByInstance.values())).toISOString();
3321
+ try {
3322
+ let res = await this.adms.getMetricsV2(this.be, req);
3323
+ if (generation != this.dataGeneration)
3324
+ return;
3325
+ this.merge(res ?? []);
3326
+ this.setDomain(this.request());
3327
+ // Fuori dalla finestra live si scartano
3328
+ this.data.forEach(byInstance => byInstance.forEach((list, instance) => byInstance.set(instance, list.filter(t => this.time(t) >= this.domainFrom))));
3329
+ this.lastUpdate = new Date();
3330
+ this.rebuild();
3331
+ }
3332
+ catch {
3333
+ // Un aggiornamento mancato si recupera al giro dopo
3334
+ }
3335
+ }
3336
+ // Nella fusione si tiene la versione già in pagina dei campioni ripetuti, che ha le differenze (chiave istanza, metrica, istante)
3337
+ merge(samples) {
3338
+ for (let s of samples) {
3339
+ let key = `${s.instanceId}|${s.name}|${s.sampleTime}`;
3340
+ if (this.seen.has(key))
3341
+ continue;
3342
+ this.seen.add(key);
3343
+ if (!this.data.has(s.name))
3344
+ this.data.set(s.name, new Map());
3345
+ let byInstance = this.data.get(s.name);
3346
+ let instance = s.instanceId ?? "";
3347
+ if (!byInstance.has(instance))
3348
+ byInstance.set(instance, []);
3349
+ byInstance.get(instance).push(s);
3350
+ }
3351
+ this.data.forEach(byInstance => byInstance.forEach(list => list.sort((a, b) => this.time(a) - this.time(b))));
3352
+ }
3353
+ setDomain(req) {
3354
+ if (this.source == "live") {
3355
+ this.domainTo = Date.now();
3356
+ this.domainFrom = this.domainTo - +this.liveMinutes * 60000;
3357
+ }
3358
+ else if (this.source == "period") {
3359
+ this.domainFrom = new Date(req.snapFrom ?? Date.now() - 24 * 3600 * 1000).getTime();
3360
+ this.domainTo = new Date(req.snapTo ?? Date.now()).getTime();
3361
+ }
3362
+ else {
3363
+ let window = this.snapshotWindow;
3364
+ this.domainFrom = window?.from?.getTime() ?? Date.now() - 12 * 3600 * 1000;
3365
+ // I campioni sono alla fine degli snapshot (ogni istanza con i suoi secondi)
3366
+ this.domainTo = (window?.to?.getTime() ?? Date.now()) + 60000;
3367
+ }
3368
+ }
3369
+ // #endregion
3370
+ // #region Grafici, tessere e tabella
3371
+ get chartFrom() { return this.viewFrom ?? this.domainFrom; }
3372
+ get chartTo() { return this.viewTo ?? this.domainTo; }
3373
+ zoomTo(range) {
3374
+ this.viewFrom = range.from;
3375
+ this.viewTo = range.to;
3376
+ }
3377
+ resetZoom() {
3378
+ this.viewFrom = null;
3379
+ this.viewTo = null;
3380
+ }
3381
+ toggleInstance(instance) {
3382
+ if (this.hidden.has(instance))
3383
+ this.hidden.delete(instance);
3384
+ else
3385
+ this.hidden.add(instance);
3386
+ this.rebuild();
3387
+ }
3388
+ setAlt(panel, alt) {
3389
+ this.alts[panel.info.name] = alt;
3390
+ this.rebuild();
3391
+ }
3392
+ info(name) {
3393
+ return this.catalog.find(t => t.name == name) ?? { name: name, metricType: MetricV2Type.Gauge, unit: "", rateUnit: null, durationUnit: null, instances: [] };
3394
+ }
3395
+ // Colore fisso per istanza, lo stesso in tutti i pannelli
3396
+ color(instance) {
3397
+ let all = [...new Set([...this.Instances.map(t => t.id), ...flatten(this.catalog, t => t.instances ?? [])])].sort();
3398
+ let index = all.indexOf(instance);
3399
+ return INSTANCE_COLORS[(index >= 0 ? index : all.length) % INSTANCE_COLORS.length];
3400
+ }
3401
+ rebuild() {
3402
+ let instances = new Set();
3403
+ this.data.forEach(byInstance => byInstance.forEach((_, instance) => instances.add(instance)));
3404
+ this.legend = [...instances].sort().map(t => ({ instance: t, color: this.color(t), hidden: this.hidden.has(t) }));
3405
+ this.marks = this.buildMarks();
3406
+ this.panels = this.selected.map(name => this.buildPanel(this.info(name)));
3407
+ this.tiles = this.selected.map(name => this.buildTile(this.info(name)));
3408
+ this.tables = this.selected.map(name => this.buildTable(this.info(name), this.tables.find(t => t.info.name == name)));
3409
+ }
3410
+ visibleSeries(name) {
3411
+ let byInstance = this.data.get(name);
3412
+ return byInstance ? [...byInstance.entries()].filter(t => !this.hidden.has(t[0]) && t[1].length > 0).sort((a, b) => a[0].localeCompare(b[0])) : [];
3413
+ }
3414
+ // M14: riavvii (un campione senza intervallo che non è il primo della serie) e, sullo storico per snapshot, gli id degli snapshot
3415
+ buildMarks() {
3416
+ let marks = [];
3417
+ let seen = new Set();
3418
+ this.data.forEach(byInstance => byInstance.forEach((list, instance) => list.forEach((s, i) => {
3419
+ if (i > 0 && s.intervalSeconds == null) {
3420
+ let t = this.time(s);
3421
+ let key = `${instance}|${Math.round(t / 60000)}`;
3422
+ if (!seen.has(key)) {
3423
+ seen.add(key);
3424
+ marks.push({ t: t, label: this.lc.loc("riavvio"), kind: "restart" });
3425
+ }
3426
+ }
3427
+ })));
3428
+ if (this.source == "snap") {
3429
+ let bySnap = new Map();
3430
+ this.data.forEach(byInstance => byInstance.forEach(list => list.forEach(s => {
3431
+ if (s.snapId != null && !bySnap.has(s.snapId))
3432
+ bySnap.set(s.snapId, this.time(s));
3433
+ })));
3434
+ [...bySnap.entries()].sort((a, b) => a[1] - b[1]).forEach(([id, t]) => marks.push({ t: t, label: String(id), kind: "snapshot" }));
3435
+ }
3436
+ return marks;
3437
+ }
3438
+ // Punti di una serie: si salta dove il valore manca e si interrompe la linea a un riavvio o se fra due campioni passa più del doppio dell'intervallo tipico
3439
+ points(list, value, upper) {
3440
+ let times = list.map(t => this.time(t));
3441
+ let gaps = times.slice(1).map((t, i) => t - times[i]).sort((a, b) => a - b);
3442
+ let typical = gaps.length ? gaps[Math.floor(gaps.length / 2)] : 0;
3443
+ let out = [];
3444
+ let pendingGap = false;
3445
+ list.forEach((s, i) => {
3446
+ let v = value(s);
3447
+ let gap = i > 0 && (s.intervalSeconds == null || (typical > 0 && times[i] - times[i - 1] > 2 * typical));
3448
+ if (v == null || isNaN(v)) {
3449
+ pendingGap = true;
3450
+ return;
3451
+ }
3452
+ out.push({ t: times[i], v: v, v2: upper ? upper(s) : undefined, gap: gap || pendingGap });
3453
+ pendingGap = false;
3454
+ });
3455
+ return out;
3456
+ }
3457
+ // Valore principale per tipo (tessere e grafico di default)
3458
+ main(info, s) {
3459
+ switch (info.metricType) {
3460
+ case MetricV2Type.Gauge: return s.value;
3461
+ case MetricV2Type.Counter: return s.count;
3462
+ case MetricV2Type.Meter: return info.name == ACTIVE_THREADS_TIME ? (s.ratePerSecond == null ? null : s.ratePerSecond / 1000000) : s.ratePerSecond;
3463
+ case MetricV2Type.Histogram: return s.median;
3464
+ case MetricV2Type.Timer: return s.intervalMean;
3465
+ }
3466
+ return s.value;
3467
+ }
3468
+ mainUnit(info) {
3469
+ switch (info.metricType) {
3470
+ case MetricV2Type.Gauge: return info.name == CPU_USAGE ? "CPU" : info.unit || "";
3471
+ case MetricV2Type.Counter: return info.unit || "";
3472
+ case MetricV2Type.Meter: return info.name == ACTIVE_THREADS_TIME ? this.lc.loc("thread") : `${info.unit || ""}/s`;
3473
+ case MetricV2Type.Histogram: return info.unit || "";
3474
+ case MetricV2Type.Timer: return this.durationLabel(info.durationUnit);
3475
+ }
3476
+ return info.unit || "";
3477
+ }
3478
+ buildPanel(info) {
3479
+ let series = this.visibleSeries(info.name);
3480
+ let items = this.itemsOf(info);
3481
+ let alts = [];
3482
+ switch (info.metricType) {
3483
+ case MetricV2Type.Counter:
3484
+ alts = [new KeyValue("level", this.lc.loc("Livello")), new KeyValue("delta", this.lc.loc("Variazione"))];
3485
+ if (items.length) {
3486
+ alts.push(new KeyValue("items", this.lc.loc("Voci")));
3487
+ if (this.source == "live")
3488
+ alts.push(new KeyValue("share", this.lc.loc("Quote")));
3489
+ }
3490
+ break;
3491
+ case MetricV2Type.Meter:
3492
+ alts = [new KeyValue("rate", this.lc.loc("Al secondo")), new KeyValue("moving", this.lc.loc("Rate mobili"))];
3493
+ if (items.length)
3494
+ alts.push(new KeyValue("items", this.lc.loc("Voci")));
3495
+ break;
3496
+ case MetricV2Type.Histogram:
3497
+ alts = [new KeyValue("percentiles", this.lc.loc("Percentili")), new KeyValue("rate", this.lc.loc("Frequenza"))];
3498
+ break;
3499
+ case MetricV2Type.Timer:
3500
+ alts = [new KeyValue("duration", this.lc.loc("Chiamate e durata")), new KeyValue("active", this.lc.loc("In corso"))];
3501
+ break;
3502
+ }
3503
+ let alt = alts.find(t => t.id == this.alts[info.name])?.id ?? alts[0]?.id ?? "";
3504
+ return { info: info, alts: alts, alt: alt, charts: series.length ? this.buildCharts(info, alt, series, items) : [], empty: series.length == 0 };
3505
+ }
3506
+ // Le voci scelte di un totale, per l'area impilata e le quote
3507
+ itemsOf(info) {
3508
+ if (info.metricType != MetricV2Type.Counter && info.metricType != MetricV2Type.Meter)
3509
+ return [];
3510
+ return this.selected.filter(t => t.startsWith(info.name + " - ") && !this.selected.some(k => k != info.name && t.startsWith(k + " - ") && k.startsWith(info.name + " - "))).map(t => this.info(t));
3511
+ }
3512
+ buildCharts(info, alt, series, items) {
3513
+ let line = (key, instance, points, extra = {}) => ({ key: `${key}|${instance}`, label: instance, color: this.color(instance), kind: "line", points: points, ...extra });
3514
+ let unit = info.unit || "";
3515
+ let rateUnit = `${unit}${this.rateLabel(info.rateUnit)}`;
3516
+ switch (info.metricType) {
3517
+ case MetricV2Type.Gauge:
3518
+ return [{ key: "value", unit: this.mainUnit(info), height: 170, series: series.map(([i, l]) => line("value", i, this.points(l, s => s.value))) }];
3519
+ case MetricV2Type.Counter:
3520
+ if (alt == "delta")
3521
+ return [{ key: "delta", unit: unit, height: 170, series: series.map(([i, l]) => line("delta", i, this.points(l, s => s.deltaCount), { kind: "bars" })) }];
3522
+ if (alt == "items")
3523
+ return [{ key: "items", unit: unit, height: 190, series: this.stacked(items, s => s.count) }];
3524
+ if (alt == "share")
3525
+ return [{ key: "share", unit: "%", height: 170, series: this.itemLines(items, s => s.percent) }];
3526
+ return [{ key: "level", unit: unit, height: 170, series: series.map(([i, l]) => line("level", i, this.points(l, s => s.count))) }];
3527
+ case MetricV2Type.Meter:
3528
+ if (alt == "moving")
3529
+ return [{
3530
+ key: "moving", unit: rateUnit, height: 170,
3531
+ series: flatten(series, ([i, l]) => [
3532
+ line("m1", i, this.points(l, s => s.oneMinuteRate), { label: `${i} · 1 min` }),
3533
+ line("m5", i, this.points(l, s => s.fiveMinuteRate), { label: `${i} · 5 min`, dash: "5 3" }),
3534
+ line("m15", i, this.points(l, s => s.fifteenMinuteRate), { label: `${i} · 15 min`, dash: "1 3" })
3535
+ ])
3536
+ }];
3537
+ if (alt == "items")
3538
+ return [{ key: "items", unit: `${unit}/s`, height: 190, series: this.stacked(items, s => s.ratePerSecond) }];
3539
+ return [{ key: "rate", unit: this.mainUnit(info), height: 170, series: series.map(([i, l]) => line("rate", i, this.points(l, s => this.main(info, s)))) }];
3540
+ case MetricV2Type.Histogram:
3541
+ if (alt == "rate")
3542
+ return [{ key: "rate", unit: `${unit}/s`, height: 170, series: series.map(([i, l]) => line("rate", i, this.points(l, s => s.ratePerSecond))) }];
3543
+ return [{ key: "percentiles", unit: unit, height: 190, series: flatten(series, ([i, l]) => this.percentileBand(i, l, s => s.median, `${i} · ${this.lc.loc("mediana")}`)) }];
3544
+ case MetricV2Type.Timer:
3545
+ if (alt == "active")
3546
+ return [{ key: "active", unit: this.lc.loc("in corso"), height: 170, series: series.map(([i, l]) => line("active", i, this.points(l, s => s.activeSessions), { kind: "step" })) }];
3547
+ return [
3548
+ { key: "calls", unit: `${unit}/s`, height: 120, series: series.map(([i, l]) => line("calls", i, this.points(l, s => s.ratePerSecond))) },
3549
+ { key: "duration", unit: this.durationLabel(info.durationUnit), height: 170, series: flatten(series, ([i, l]) => this.percentileBand(i, l, s => s.intervalMean, `${i} · ${this.lc.loc("media nell'intervallo")}`, true)) }
3550
+ ];
3551
+ }
3552
+ return [];
3553
+ }
3554
+ // Fascia dei percentili del reservoir per istanza: per l'histogram mediana con max a punti, per il timer la durata media dell'intervallo
3555
+ percentileBand(instance, list, center, label, timer = false) {
3556
+ let color = this.color(instance);
3557
+ let ret = [];
3558
+ if (!timer)
3559
+ ret.push({ key: `b1|${instance}`, label: instance, color: color, kind: "band", points: this.points(list, s => s.median, s => s.percentile75), opacity: 0.26, tooltip: false });
3560
+ ret.push({ key: `b2|${instance}`, label: instance, color: color, kind: "band", points: this.points(list, s => timer ? s.median : s.percentile75, s => s.percentile95), opacity: 0.15, tooltip: false });
3561
+ ret.push({ key: `b3|${instance}`, label: instance, color: color, kind: "band", points: this.points(list, s => s.percentile95, s => s.percentile99), opacity: 0.07, tooltip: false });
3562
+ ret.push({ key: `c|${instance}`, label: label, color: color, kind: "line", points: this.points(list, center) });
3563
+ if (!timer)
3564
+ ret.push({ key: `max|${instance}`, label: `${instance} · max`, color: color, kind: "dots", points: this.points(list, s => s.max) });
3565
+ return ret;
3566
+ }
3567
+ // Le voci di un totale sommate sulle istanze visibili, campione per campione (live: a 15 secondi; storico: per snapshot)
3568
+ bucketed(item, value, average) {
3569
+ let buckets = new Map();
3570
+ for (let [, list] of this.visibleSeries(item.name))
3571
+ for (let s of list) {
3572
+ let v = value(s);
3573
+ if (v == null)
3574
+ continue;
3575
+ let key = s.snapId ?? Math.round(this.time(s) / REFRESH_MS);
3576
+ let b = buckets.get(key) ?? { t: this.time(s), v: 0, n: 0 };
3577
+ b.v += v;
3578
+ b.n++;
3579
+ buckets.set(key, b);
3580
+ }
3581
+ return new Map([...buckets.entries()].map(([k, b]) => [k, { t: b.t, v: average ? b.v / b.n : b.v }]));
3582
+ }
3583
+ stacked(items, value) {
3584
+ let perItem = items.map(t => this.bucketed(t, value, false));
3585
+ let keys = [...new Set(flatten(perItem, t => [...t.keys()]))].sort((a, b) => a - b);
3586
+ let base = new Map(keys.map(k => [k, 0]));
3587
+ let bands = items.map((item, i) => {
3588
+ let points = keys.filter(k => perItem[i].has(k)).map(k => {
3589
+ let b = perItem[i].get(k);
3590
+ let lo = base.get(k);
3591
+ base.set(k, lo + b.v);
3592
+ return { t: b.t, v: lo, v2: lo + b.v };
3593
+ });
3594
+ let label = item.name.substring(item.name.lastIndexOf(" - ") + 3);
3595
+ return { key: `stack|${item.name}`, label: label, color: ITEM_COLORS[i % ITEM_COLORS.length], kind: "band", points: points, opacity: 0.55, tooltip: false };
3596
+ });
3597
+ // Linee invisibili solo per il riquadro dei valori: il valore della voce, non la somma impilata
3598
+ let tips = items.map((item, i) => ({
3599
+ key: `stacktip|${item.name}`, label: item.name.substring(item.name.lastIndexOf(" - ") + 3), color: ITEM_COLORS[i % ITEM_COLORS.length], kind: "line",
3600
+ points: [...perItem[i].values()].sort((a, b) => a.t - b.t).map(b => ({ t: b.t, v: b.v })), opacity: 0
3601
+ }));
3602
+ return bands.reverse().concat(tips);
3603
+ }
3604
+ itemLines(items, value) {
3605
+ return items.map((item, i) => ({
3606
+ key: `share|${item.name}`, label: item.name.substring(item.name.lastIndexOf(" - ") + 3), color: ITEM_COLORS[i % ITEM_COLORS.length], kind: "line",
3607
+ points: [...this.bucketed(item, value, true).values()].sort((a, b) => a.t - b.t).map(b => ({ t: b.t, v: b.v }))
3608
+ }));
3609
+ }
3610
+ // M17: valore principale dell'ultimo campione di ogni istanza, mini grafico, istante del campione
3611
+ buildTile(info) {
3612
+ let rows = [];
3613
+ let sparks = [];
3614
+ let latest = 0;
3615
+ for (let [instance, list] of this.visibleSeries(info.name)) {
3616
+ let last = [...list].reverse().find(t => this.main(info, t) != null);
3617
+ if (!last)
3618
+ continue;
3619
+ latest = Math.max(latest, this.time(last));
3620
+ rows.push({
3621
+ instance: instance, color: this.color(instance), value: `${this.number(this.main(info, last))} ${this.mainUnit(info)}`.trim(),
3622
+ extra: info.metricType == MetricV2Type.Meter ? `${this.number(last.count)} ${this.lc.loc("dall'avvio")}` : ""
3623
+ });
3624
+ let points = this.points(list.slice(-80), s => this.main(info, s));
3625
+ if (points.length > 1) {
3626
+ let lo = Math.min(...points.map(t => t.v));
3627
+ let hi = Math.max(...points.map(t => t.v));
3628
+ let t0 = points[0].t;
3629
+ let t1 = points[points.length - 1].t;
3630
+ let d = points.map((p, i) => `${i == 0 || p.gap ? "M" : "L"}${((p.t - t0) / Math.max(1, t1 - t0) * 120).toFixed(1)},${(26 - (hi > lo ? (p.v - lo) / (hi - lo) : 0.5) * 24).toFixed(1)}`).join("");
3631
+ sparks.push({ d: d, color: this.color(instance) });
3632
+ }
3633
+ }
3634
+ return { info: info, rows: rows, sparks: sparks, time: latest ? dayjs(latest).format("HH:mm:ss") : "" };
3635
+ }
3636
+ // M18: colonne comuni e del tipo; ordinabile; esportabile
3637
+ buildTable(info, previous) {
3638
+ let unit = info.unit ? ` (${info.unit})` : "";
3639
+ let dur = info.durationUnit ? ` (${this.durationLabel(info.durationUnit)})` : "";
3640
+ let columns = [
3641
+ { key: "instance", label: this.lc.loc("Istanza"), num: false },
3642
+ { key: "time", label: `${this.lc.loc("Istante")} (${this.zone})`, num: false },
3643
+ { key: "interval", label: this.lc.loc("Intervallo (s)"), num: true }
3644
+ ];
3645
+ if (this.source != "live")
3646
+ columns.push({ key: "snapId", label: this.lc.loc("Snapshot"), num: true });
3647
+ let typed = [];
3648
+ switch (info.metricType) {
3649
+ case MetricV2Type.Gauge:
3650
+ typed = [["value", `${this.lc.loc("Valore")}${info.name == CPU_USAGE ? " (CPU)" : unit}`, s => s.value]];
3651
+ break;
3652
+ case MetricV2Type.Counter:
3653
+ typed = [["count", `${this.lc.loc("Conteggio")}${unit}`, s => s.count], ["delta", `${this.lc.loc("Variazione")}${unit}`, s => s.deltaCount], ["percent", `${this.lc.loc("Quota")} (%)`, s => s.percent]];
3654
+ break;
3655
+ case MetricV2Type.Meter:
3656
+ typed = [["count", `${this.lc.loc("Conteggio")}${unit}`, s => s.count], ["rate", `${this.lc.loc("Al secondo")} (${this.mainUnit(info)})`, s => this.main(info, s)],
3657
+ ["m1", `Rate 1 min (${info.unit || ""}${this.rateLabel(info.rateUnit)})`, s => s.oneMinuteRate], ["m5", "Rate 5 min", s => s.fiveMinuteRate], ["m15", "Rate 15 min", s => s.fifteenMinuteRate]];
3658
+ break;
3659
+ case MetricV2Type.Histogram:
3660
+ typed = [["count", this.lc.loc("Conteggio"), s => s.count], ["median", `${this.lc.loc("Mediana")}${unit}`, s => s.median], ["p95", "p95", s => s.percentile95], ["p99", "p99", s => s.percentile99], ["max", this.lc.loc("Massimo"), s => s.max]];
3661
+ break;
3662
+ case MetricV2Type.Timer:
3663
+ typed = [["count", this.lc.loc("Chiamate"), s => s.count], ["rate", `${this.lc.loc("Al secondo")} (/s)`, s => s.ratePerSecond], ["intervalMean", `${this.lc.loc("Durata media nell'intervallo")}${dur}`, s => s.intervalMean],
3664
+ ["median", `${this.lc.loc("Mediana")}${dur}`, s => s.median], ["p95", "p95", s => s.percentile95], ["p99", "p99", s => s.percentile99], ["active", this.lc.loc("In corso"), s => s.activeSessions]];
3665
+ break;
3666
+ }
3667
+ columns.push(...typed.map(([key, label]) => ({ key: key, label: label, num: true })));
3668
+ let rows = flatten(this.visibleSeries(info.name), ([instance, list]) => list.map(s => {
3669
+ let row = { instance: instance, time: this.time(s), utc: s.sampleTime, interval: s.intervalSeconds, snapId: s.snapId };
3670
+ typed.forEach(([key, , value]) => row[key] = value(s));
3671
+ return row;
3672
+ }));
3673
+ let section = { info: info, columns: columns, rows: [], total: rows.length, sortKey: previous?.sortKey ?? "time", sortDesc: previous?.sortDesc ?? true };
3674
+ section.rows = this.sortRows(rows, section);
3675
+ return section;
3676
+ }
3677
+ sortBy(section, column) {
3678
+ section.sortDesc = section.sortKey == column.key ? !section.sortDesc : column.num || column.key == "time";
3679
+ section.sortKey = column.key;
3680
+ let rebuilt = this.buildTable(section.info, section);
3681
+ section.rows = rebuilt.rows;
3682
+ }
3683
+ sortRows(rows, section) {
3684
+ let key = section.sortKey;
3685
+ let sign = section.sortDesc ? -1 : 1;
3686
+ return rows.sort((a, b) => {
3687
+ let x = a[key];
3688
+ let y = b[key];
3689
+ if (x == null && y == null)
3690
+ return 0;
3691
+ if (x == null)
3692
+ return 1;
3693
+ if (y == null)
3694
+ return -1;
3695
+ return (typeof x == "string" ? x.localeCompare(y) : x - y) * sign;
3696
+ });
3697
+ }
3698
+ shownRows(section) {
3699
+ return section.rows.slice(0, TABLE_ROWS);
3700
+ }
3701
+ cell(row, column) {
3702
+ let v = row[column.key];
3703
+ if (column.key == "time")
3704
+ return dayjs(v).format("DD/MM HH:mm:ss");
3705
+ if (column.key == "instance" || column.key == "snapId")
3706
+ return v ?? "—";
3707
+ return this.number(v);
3708
+ }
3709
+ // CSV delle righe della metrica, con gli orari in UTC e le unità nelle intestazioni
3710
+ exportCsv(section) {
3711
+ let quote = (v) => {
3712
+ let s = v == null ? "" : String(v);
3713
+ return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
3714
+ };
3715
+ let columns = section.columns.map(t => t.key == "time" ? { ...t, label: `${this.lc.loc("Istante")} (UTC)` } : t);
3716
+ let lines = [columns.map(t => quote(t.label)).join(",")];
3717
+ for (let row of section.rows)
3718
+ lines.push(columns.map(t => quote(t.key == "time" ? this.isoUtc(row["utc"]) : row[t.key])).join(","));
3719
+ let blob = new Blob(["" + lines.join("\r\n")], { type: "text/csv;charset=utf-8" });
3720
+ let url = URL.createObjectURL(blob);
3721
+ let link = document.createElement("a");
3722
+ link.href = url;
3723
+ link.download = `metriche_${section.info.name.replace(/[^\w.-]+/g, "-")}.csv`;
3724
+ document.body.appendChild(link);
3725
+ link.click();
3726
+ link.remove();
3727
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
3728
+ }
3729
+ // #endregion
3730
+ // #region Supporto
3731
+ visibilityChanged() {
3732
+ // Tornando sulla pagina si recupera subito invece di aspettare il giro dopo
3733
+ if (!document.hidden)
3734
+ this.refresh();
3735
+ }
3736
+ errorMessage(err) {
3737
+ return err?.status == 542 ? this.adms.errorText(err) : `${this.lc.loc("Errore")} ${err?.status ?? ""}: ${this.adms.errorText(err)}`;
3738
+ }
3739
+ number(value) {
3740
+ if (value == null || isNaN(value))
3741
+ return "—";
3742
+ let abs = Math.abs(value);
3743
+ let digits = abs >= 1000 ? "1.0-0" : abs >= 100 ? "1.0-1" : abs >= 1 ? "1.0-2" : "1.0-3";
3744
+ return formatNumber(value, "it", digits);
3745
+ }
3746
+ durationLabel(unit) {
3747
+ switch (unit) {
3748
+ case "Nanoseconds": return "ns";
3749
+ case "Microseconds": return "µs";
3750
+ case "Milliseconds": return "ms";
3751
+ case "Seconds": return "s";
3752
+ case "Minutes": return "min";
3753
+ case "Hours": return "h";
3754
+ }
3755
+ return unit ?? "";
3756
+ }
3757
+ rateLabel(unit) {
3758
+ switch (unit) {
3759
+ case "Seconds": return "/s";
3760
+ case "Minutes": return "/min";
3761
+ case "Hours": return "/h";
3762
+ case "Days": return "/g";
3763
+ }
3764
+ return unit ? `/${unit}` : "";
3765
+ }
3766
+ // Le date delle risposte sono UTC "dd/MM/yyyy HH:mm:ss" senza fuso: si leggono come UTC e si mostrano in ora locale (M24)
3767
+ parseUtc(value) {
3768
+ let m = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})$/.exec(value ?? "");
3769
+ return m ? new Date(Date.UTC(+m[3], +m[2] - 1, +m[1], +m[4], +m[5], +m[6])) : value ? new Date(value) : null;
3770
+ }
3771
+ time(s) {
3772
+ return this.parseUtc(s.sampleTime)?.getTime() ?? 0;
3773
+ }
3774
+ isoUtc(value) {
3775
+ return this.parseUtc(value)?.toISOString().replace(".000Z", "Z") ?? "";
3776
+ }
3777
+ local(value) {
3778
+ let date = typeof value == "string" ? this.parseUtc(value) : value;
3779
+ return date ? dayjs(date).format("DD/MM HH:mm") : "—";
3780
+ }
3781
+ hhmmss(date) {
3782
+ return date ? dayjs(date).format("HH:mm:ss") : "";
3783
+ }
3784
+ setLastDay() {
3785
+ let now = new Date();
3786
+ this.filterto = this.fmt(now);
3787
+ this.filterfrom = this.fmt(new Date(now.getTime() - 24 * 3600 * 1000));
3788
+ }
3789
+ toDate(value) {
3790
+ return value ? this.dateExts.getDateConvertion(value).toDate() : null;
3791
+ }
3792
+ fmt(date) {
3793
+ return dayjs(date).format(this.lc.token("getFullDateDisplayFormat"));
3794
+ }
3795
+ saveSettings() {
3796
+ let settings = { selected: this.selected, source: this.source, instances: this.instances, liveMinutes: this.liveMinutes, view: this.view };
3797
+ this.localStorage.setItem(STORAGE_KEY$1, settings).subscribe();
3798
+ }
3799
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmMetricsV2Component, deps: [{ token: i1$1.DateService }, { token: i1.LocalizationService }, { token: i3.PreferencesService }, { token: i1$1.InterComService }, { token: i4.ActivatedRoute }, { token: AdminModuleBaseService }], target: i0.ɵɵFactoryTarget.Component }); }
3800
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: AdmMetricsV2Component, isStandalone: false, selector: "metrics_v2", providers: [{ provide: LocalizationService, useClass: AdmMetricsV2Loc }], ngImport: i0, template: "<div class=\"row\">\r\n <div class=\"col-12\">\r\n <div class=\"card app-overflow-visible\" widget>\r\n <div class=\"card-header\">\r\n <span class=\"text-uppercase\">{{'Metriche di Sistema' | localize: lc}}</span>\r\n </div>\r\n <div class=\"card-body widget-body mv2-body\">\r\n <!-- Barra dei comandi: sorgente, finestra, istanze, vista, aggiornamento -->\r\n <div class=\"mv2-toolbar\">\r\n <div class=\"mv2-group\">\r\n <span class=\"mv2-group-label\">{{'Sorgente' | localize: lc}}</span>\r\n <div class=\"mv2-seg\">\r\n @for (o of Sources; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"source == o.id\" [disabled]=\"o.id != 'live' && !historyAvailable\" (click)=\"setSource(o.id)\"\r\n [title]=\"o.id != 'live' && !historyAvailable ? ('Lo storico non \u00E8 attivo su questo ambiente: ci sono solo i dati live.' | localize: lc) : ''\">\r\n @if (o.id == 'live') {\r\n <span class=\"mv2-live-dot\" [class.mv2-live-paused]=\"paused\"></span>\r\n }\r\n {{o.description}}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (source == 'live') {\r\n <div class=\"mv2-group\">\r\n <span class=\"mv2-group-label\">{{'Finestra' | localize: lc}}</span>\r\n <div class=\"mv2-seg\">\r\n @for (o of LiveWindows; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"liveMinutes == o.id\" (click)=\"setLiveMinutes(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n } @else if (source == 'period') {\r\n <div class=\"mv2-cluster\">\r\n <div class=\"mv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterfrom\" (ngModelChange)=\"filterfrom = $event; datesChanged()\" name=\"filterfrom\" [Placeholder]=\"'Dal' | localize: lc\"></form-datetime>\r\n </div>\r\n <div class=\"mv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterto\" (ngModelChange)=\"filterto = $event; datesChanged()\" name=\"filterto\" [Placeholder]=\"'Al' | localize: lc\"></form-datetime>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"mv2-cluster\">\r\n <div class=\"mv2-field mv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"snapFromId\" (ngModelChange)=\"snapFromId = $event; snapshotsChanged()\" name=\"snapFrom\" [Source]=\"SnapshotOptions\" [Placeholder]=\"'Dallo snapshot' | localize: lc\"></form-select>\r\n </div>\r\n <div class=\"mv2-field mv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"snapToId\" (ngModelChange)=\"snapToId = $event; snapshotsChanged()\" name=\"snapTo\" [Source]=\"SnapshotOptions\" [Placeholder]=\"'Allo snapshot' | localize: lc\"></form-select>\r\n </div>\r\n @if (snapshotWindow; as w) {\r\n <span class=\"mv2-muted\">{{'Dalle' | localize: lc}} {{local(w.from)}} {{'alle' | localize: lc}} {{local(w.to)}}</span>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"mv2-group mv2-field-wide\">\r\n <span class=\"mv2-group-label\">{{'Istanze' | localize: lc}}</span>\r\n <form-multiselect class=\"mv2-fill\" [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"instances\" (ngModelChange)=\"instancesChanged($event)\" name=\"instances\" [Source]=\"Instances\" [Placeholder]=\"clusterLabel\" [MaxShownElements]=\"2\"></form-multiselect>\r\n </div>\r\n\r\n <div class=\"mv2-cluster mv2-cluster-end\">\r\n @if (source == 'live') {\r\n <span class=\"mv2-muted\">\r\n @if (dataLoading) {\r\n <i class=\"fa fa-spinner fa-spin\"></i>\r\n }\r\n @if (lastUpdate) {\r\n {{'Aggiornato alle' | localize: lc}} {{hhmmss(lastUpdate)}}\r\n }\r\n </span>\r\n <button type=\"button\" class=\"mv2-btn\" (click)=\"togglePause()\">\r\n <i class=\"fa\" [class.fa-pause]=\"!paused\" [class.fa-play]=\"paused\"></i> {{(paused ? 'Riprendi' : 'Pausa') | localize: lc}}\r\n </button>\r\n } @else if (dataLoading) {\r\n <span class=\"mv2-muted\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</span>\r\n }\r\n <div class=\"mv2-seg\">\r\n @for (o of Views; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"view == o.id\" (click)=\"setView(o.id)\">\r\n <i class=\"fa\" [class.fa-line-chart]=\"o.id == 'charts'\" [class.fa-table]=\"o.id == 'table'\"></i> {{o.description}}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Avvisi -->\r\n @for (warning of warnings; track $index) {\r\n <div class=\"mv2-alert mv2-alert-warn\"><i class=\"fa fa-exclamation-triangle\"></i> {{warning}}</div>\r\n }\r\n @if (snapshotsLoaded && !historyAvailable) {\r\n <div class=\"mv2-alert\"><i class=\"fa fa-info-circle\"></i> {{'Lo storico non \u00E8 attivo su questo ambiente: ci sono solo i dati live.' | localize: lc}}</div>\r\n }\r\n @if (dataError) {\r\n <div class=\"mv2-alert mv2-alert-error\">{{dataError}}</div>\r\n }\r\n\r\n <div class=\"mv2-main\">\r\n <!-- Catalogo -->\r\n <aside class=\"mv2-panel mv2-catalog\">\r\n <div class=\"mv2-field-full\">\r\n <form-input [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"search\" (ngModelChange)=\"search = $event; buildGroups()\" name=\"search\" [Placeholder]=\"'Cerca una metrica\u2026' | localize: lc\"></form-input>\r\n </div>\r\n <div class=\"mv2-presets\">\r\n @for (p of Presets; track p) {\r\n <button type=\"button\" class=\"mv2-chip\" (click)=\"applyPreset(p)\">{{p | localize: lc}}</button>\r\n }\r\n </div>\r\n <div class=\"mv2-counter\">\r\n <span>{{selected.length}} {{'di' | localize: lc}} {{Max}} {{'metriche scelte' | localize: lc}}</span>\r\n @if (selected.length > 0) {\r\n <button type=\"button\" class=\"mv2-link\" (click)=\"clearSelection()\">{{'Togli tutte' | localize: lc}}</button>\r\n }\r\n </div>\r\n\r\n @if (catalogLoading) {\r\n <div class=\"mv2-muted mv2-pad\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</div>\r\n }\r\n @if (catalogError) {\r\n <div class=\"mv2-alert mv2-alert-error\">{{catalogError}}</div>\r\n }\r\n @for (group of groups; track group.type) {\r\n <div class=\"mv2-cat-group\">\r\n <div class=\"mv2-cat-title\">{{group.label}} <span class=\"mv2-muted\">{{group.count}}</span></div>\r\n @for (entry of group.entries; track entry.info.name) {\r\n <div class=\"mv2-cat-item\" [class.mv2-cat-disabled]=\"!canSelect(entry.info.name)\">\r\n <label class=\"mv2-check\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(entry.info.name)\" [disabled]=\"!canSelect(entry.info.name)\" (change)=\"toggleMetric(entry.info.name)\" />\r\n <span class=\"mv2-cat-name\" [title]=\"entry.info.name\">{{entry.info.name}}</span>\r\n </label>\r\n <span class=\"mv2-cat-meta\">\r\n {{catalogUnit(entry.info)}}\r\n @if (instancesLabel(entry.info)) {\r\n <span class=\"mv2-cat-inst\" [title]=\"entry.info.instances.join('\\n')\">{{instancesLabel(entry.info)}}</span>\r\n }\r\n </span>\r\n @if (entry.items.length > 0) {\r\n <button type=\"button\" class=\"mv2-caret\" (click)=\"entry.open = !entry.open\" [title]=\"'Voci' | localize: lc\">\r\n <i class=\"fa\" [class.fa-caret-down]=\"entry.open\" [class.fa-caret-right]=\"!entry.open\"></i> {{entry.items.length}}\r\n </button>\r\n }\r\n </div>\r\n @if (entry.open && entry.items.length > 0) {\r\n <div class=\"mv2-cat-items\">\r\n <button type=\"button\" class=\"mv2-link\" (click)=\"addItems(entry)\">{{addItemsLabel(entry)}}</button>\r\n @for (item of entry.items; track item.name) {\r\n <div class=\"mv2-cat-item mv2-cat-sub\" [class.mv2-cat-disabled]=\"!canSelect(item.name)\">\r\n <label class=\"mv2-check\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(item.name)\" [disabled]=\"!canSelect(item.name)\" (change)=\"toggleMetric(item.name)\" />\r\n <span class=\"mv2-cat-name\" [title]=\"item.name\">{{item.name.substring(entry.info.name.length + 3)}}</span>\r\n </label>\r\n @if (instancesLabel(item)) {\r\n <span class=\"mv2-cat-inst\" [title]=\"item.instances.join('\\n')\">{{instancesLabel(item)}}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n </aside>\r\n\r\n <!-- Grafici, tessere e tabella -->\r\n <section class=\"mv2-area\">\r\n @if (selected.length == 0) {\r\n <div class=\"mv2-panel mv2-placeholder\">\r\n <i class=\"fa fa-line-chart\"></i>\r\n {{'Scegli una o pi\u00F9 metriche dal catalogo.' | localize: lc}}\r\n </div>\r\n } @else {\r\n <!-- Tessere dei valori correnti -->\r\n <div class=\"mv2-tiles\">\r\n @for (tile of tiles; track tile.info.name) {\r\n <div class=\"mv2-tile\">\r\n <div class=\"mv2-tile-name\" [title]=\"tile.info.name\">{{tile.info.name}}</div>\r\n @for (row of tile.rows; track row.instance) {\r\n <div class=\"mv2-tile-row\">\r\n <span class=\"mv2-dot\" [style.background]=\"row.color\" [title]=\"row.instance\"></span>\r\n <strong>{{row.value}}</strong>\r\n @if (row.extra) {\r\n <span class=\"mv2-muted\">{{row.extra}}</span>\r\n }\r\n </div>\r\n } @empty {\r\n <div class=\"mv2-muted\">\u2014</div>\r\n }\r\n <svg class=\"mv2-spark\" viewBox=\"0 0 120 28\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\r\n @for (spark of tile.sparks; track $index) {\r\n <path [attr.d]=\"spark.d\" [attr.stroke]=\"spark.color\" fill=\"none\" stroke-width=\"1.3\" vector-effect=\"non-scaling-stroke\" />\r\n }\r\n </svg>\r\n @if (tile.time) {\r\n <div class=\"mv2-muted mv2-small\">{{'alle' | localize: lc}} {{tile.time}}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Legenda comune: un clic nasconde o mostra l'istanza ovunque -->\r\n <div class=\"mv2-legend-bar\">\r\n <div class=\"mv2-legend\">\r\n @for (item of legend; track item.instance) {\r\n <button type=\"button\" class=\"mv2-legend-item\" [class.mv2-legend-off]=\"item.hidden\" (click)=\"toggleInstance(item.instance)\">\r\n <span class=\"mv2-dot\" [style.background]=\"item.color\"></span>{{item.instance}}\r\n </button>\r\n }\r\n </div>\r\n <span class=\"mv2-muted mv2-small\">{{'Orari in ora locale' | localize: lc}} ({{zone}}) &middot; {{'trascina su un grafico per lo zoom' | localize: lc}}</span>\r\n @if (viewFrom != null) {\r\n <button type=\"button\" class=\"mv2-btn\" (click)=\"resetZoom()\"><i class=\"fa fa-arrows-h\"></i> {{'Finestra intera' | localize: lc}}</button>\r\n }\r\n </div>\r\n\r\n @if (view == 'charts') {\r\n @for (panel of panels; track panel.info.name) {\r\n <div class=\"mv2-panel mv2-chart-panel\">\r\n <div class=\"mv2-panel-header\">\r\n <span class=\"mv2-panel-title\">{{panel.info.name}}</span>\r\n <span class=\"mv2-type\">{{Type[panel.info.metricType]}}</span>\r\n <span class=\"mv2-muted\">{{mainUnit(panel.info)}}</span>\r\n <span class=\"mv2-spacer\"></span>\r\n @if (panel.alts.length > 1) {\r\n <div class=\"mv2-seg mv2-seg-small\">\r\n @for (o of panel.alts; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"panel.alt == o.id\" (click)=\"setAlt(panel, o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n }\r\n <button type=\"button\" class=\"mv2-icon-btn\" [title]=\"'Togli' | localize: lc\" (click)=\"removeMetric(panel.info.name)\">&times;</button>\r\n </div>\r\n @if (panel.empty) {\r\n <div class=\"mv2-empty\">\r\n @if (dataLoading) {\r\n <i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}\r\n } @else {\r\n {{emptyLabel(panel.info.name)}}\r\n }\r\n </div>\r\n } @else {\r\n @for (chart of panel.charts; track chart.key) {\r\n <mv2-chart [series]=\"chart.series\" [from]=\"chartFrom\" [to]=\"chartTo\" [height]=\"chart.height\" [unit]=\"chart.unit\" [marks]=\"marks\"\r\n [cursor]=\"cursor\" [format]=\"format\" (cursorChange)=\"cursor = $event\" (zoom)=\"zoomTo($event)\"></mv2-chart>\r\n }\r\n }\r\n </div>\r\n }\r\n } @else {\r\n @for (section of tables; track section.info.name) {\r\n <div class=\"mv2-panel\">\r\n <div class=\"mv2-panel-header\">\r\n <span class=\"mv2-panel-title\">{{section.info.name}}</span>\r\n <span class=\"mv2-type\">{{Type[section.info.metricType]}}</span>\r\n <span class=\"mv2-spacer\"></span>\r\n @if (section.total > 0) {\r\n <button type=\"button\" class=\"mv2-btn\" (click)=\"exportCsv(section)\"><i class=\"fa fa-download\"></i> {{'Esporta CSV' | localize: lc}}</button>\r\n }\r\n </div>\r\n @if (section.total == 0) {\r\n <div class=\"mv2-empty\">{{emptyLabel(section.info.name)}}</div>\r\n } @else {\r\n <div class=\"mv2-table-wrap\">\r\n <table class=\"mv2-table\">\r\n <thead>\r\n <tr>\r\n @for (column of section.columns; track column.key) {\r\n <th [class.mv2-num]=\"column.num\" (click)=\"sortBy(section, column)\">\r\n {{column.label}}\r\n @if (section.sortKey == column.key) {\r\n <i class=\"fa\" [class.fa-caret-down]=\"section.sortDesc\" [class.fa-caret-up]=\"!section.sortDesc\"></i>\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (row of shownRows(section); track $index) {\r\n <tr>\r\n @for (column of section.columns; track column.key) {\r\n <td [class.mv2-num]=\"column.num\">\r\n @if (column.key == 'instance') {\r\n <span class=\"mv2-dot\" [style.background]=\"color(row['instance'])\"></span>\r\n }\r\n {{cell(row, column)}}\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n @if (section.total > section.rows.length || section.total > 300) {\r\n <div class=\"mv2-muted mv2-small mv2-pad\">{{'Mostrate le prime 300 righe di' | localize: lc}} {{section.total}}; {{\"l'esportazione le comprende tutte.\" | localize: lc}}</div>\r\n }\r\n }\r\n </div>\r\n }\r\n }\r\n }\r\n </section>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .mv2-noclear .p-select-clear-icon{display:none}:host ::ng-deep .mv2-fill{display:block;width:100%}.mv2-body{container-type:inline-size}.mv2-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 18px;padding-top:6px}.mv2-cluster{display:flex;flex-wrap:wrap;align-items:center;gap:10px}.mv2-cluster-end{margin-left:auto}.mv2-field{width:200px}.mv2-field-wide{width:340px;max-width:100%}.mv2-field-full{width:100%}.mv2-group{position:relative;display:inline-flex}.mv2-group-label{position:absolute;top:-8px;left:8px;z-index:2;padding:0 4px;background:var(--p-content-background, #fff);color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.mv2-seg{display:inline-flex;height:39px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);overflow:hidden;background:var(--p-content-background, #fff)}.mv2-seg button{display:inline-flex;align-items:center;gap:6px;padding:0 12px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.mv2-seg button:first-child{border-left:0}.mv2-seg button:hover:not(.mv2-on):not(:disabled){background:var(--p-content-hover-background, #f8fafc)}.mv2-seg button:disabled{color:var(--p-text-muted-color, #64748b);opacity:.6;cursor:not-allowed}.mv2-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.mv2-seg button.mv2-on{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca);font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.mv2-seg.mv2-seg-small{height:28px}.mv2-seg.mv2-seg-small button{padding:0 9px;font-size:12px}.mv2-live-dot{width:8px;height:8px;border-radius:50%;background:#16a34a}.mv2-live-dot.mv2-live-paused{background:var(--p-text-muted-color, #64748b)}.mv2-btn{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.mv2-btn:hover{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.mv2-icon-btn{width:26px;height:26px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--p-text-muted-color, #64748b);font-size:18px;line-height:1;cursor:pointer}.mv2-icon-btn:hover{background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155)}.mv2-link{padding:0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;cursor:pointer}.mv2-link:hover{text-decoration:underline}.mv2-muted{color:var(--p-text-muted-color, #64748b);font-size:12px}.mv2-small{font-size:11px}.mv2-pad{padding:6px 2px}.mv2-spacer{flex:1 1 auto}.mv2-alert{margin-top:10px;padding:7px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155);font-size:13px}.mv2-alert.mv2-alert-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.mv2-alert.mv2-alert-error{border-color:#fca5a5;background:#fef2f2;color:#991b1b}.mv2-main{display:grid;grid-template-columns:300px minmax(0,1fr);gap:14px;align-items:start;margin-top:14px}@container (max-width: 1000px){.mv2-main{grid-template-columns:minmax(0,1fr)}.mv2-catalog{position:static;max-height:360px}}.mv2-panel{padding:12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0}.mv2-catalog{position:sticky;top:10px;max-height:calc(100vh - 20px);overflow:auto;font-size:13px;color:var(--p-text-color, #334155)}.mv2-presets{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 8px}.mv2-chip{padding:3px 10px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:12px;background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:12px;cursor:pointer}.mv2-chip:hover{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.mv2-counter{display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);color:var(--p-text-muted-color, #64748b);font-size:12px}.mv2-cat-group{margin-top:10px}.mv2-cat-title{margin-bottom:4px;color:var(--p-text-muted-color, #64748b);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.mv2-cat-item{display:flex;align-items:center;gap:6px;padding:2px 0;min-width:0}.mv2-cat-item.mv2-cat-disabled{opacity:.5}.mv2-cat-sub{padding-left:18px}.mv2-check{display:flex;align-items:center;gap:6px;flex:1 1 auto;min-width:0;margin:0;cursor:pointer}.mv2-check input{flex:none;display:inline-block;width:auto;height:auto;margin:0;accent-color:var(--p-primary-color, #6366f1)}.mv2-cat-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mv2-cat-meta{flex:none;color:var(--p-text-muted-color, #64748b);font-size:11px;white-space:nowrap}.mv2-cat-inst{margin-left:4px;padding:0 5px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:8px;color:var(--p-text-muted-color, #64748b);font-size:10px;cursor:help}.mv2-caret{flex:none;padding:0 4px;border:0;background:transparent;color:var(--p-text-muted-color, #64748b);font-size:11px;cursor:pointer}.mv2-cat-items{padding:2px 0 4px 18px}.mv2-area{display:flex;flex-direction:column;gap:12px;min-width:0}.mv2-placeholder{display:flex;flex-direction:column;align-items:center;gap:10px;padding:70px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.mv2-placeholder .fa{font-size:28px;opacity:.5}.mv2-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}.mv2-tile{display:flex;flex-direction:column;gap:3px;padding:10px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0;font-size:13px}.mv2-tile-name{overflow:hidden;color:var(--p-text-muted-color, #64748b);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.mv2-tile-row{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}.mv2-spark{display:block;width:100%;height:28px;margin-top:2px}.mv2-dot{display:inline-block;flex:none;width:9px;height:9px;margin-right:4px;border-radius:50%}.mv2-legend-bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px}.mv2-legend{display:flex;flex-wrap:wrap;gap:4px 6px}.mv2-legend-item{display:inline-flex;align-items:center;padding:2px 8px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:12px;background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:12px;cursor:pointer}.mv2-legend-item.mv2-legend-off{opacity:.45;text-decoration:line-through}.mv2-chart-panel{padding-bottom:6px}.mv2-panel-header{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;margin-bottom:6px}.mv2-panel-title{font-size:13px;font-weight:600;color:var(--p-text-color, #334155)}.mv2-type{padding:0 7px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:10px;color:var(--p-text-muted-color, #64748b);font-size:11px;line-height:18px}.mv2-empty{padding:26px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.mv2-table-wrap{max-height:420px;overflow:auto;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px)}.mv2-table{width:100%;border-collapse:separate;border-spacing:0;font-size:12px;color:var(--p-text-color, #334155)}.mv2-table th{position:sticky;top:0;z-index:1;padding:6px 8px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-muted-color, #64748b);font-weight:500;white-space:nowrap;cursor:pointer}.mv2-table td{padding:3px 8px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);white-space:nowrap}.mv2-num{text-align:right!important;font-variant-numeric:tabular-nums}\n"], dependencies: [{ kind: "directive", type: i7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i9.EsFormInputComponent, selector: "form-input", inputs: ["minlength", "maxlength", "pattern", "Password", "Type"] }, { kind: "component", type: i9.EsFormSelectComponent, selector: "form-select", inputs: ["OptionsTemplate"] }, { kind: "component", type: i9.EsFormDateTimeComponent, selector: "form-datetime" }, { kind: "component", type: i9.EsFormMultiselectComponent, selector: "form-multiselect", inputs: ["display", "InOutFormat", "FloatingLabel", "MaxShownElements"] }, { kind: "directive", type: i10.Widget, selector: "[widget]" }, { kind: "component", type: MetricsV2ChartComponent, selector: "mv2-chart", inputs: ["series", "from", "to", "height", "unit", "marks", "cursor", "format"], outputs: ["cursorChange", "zoom"] }, { kind: "pipe", type: i1.LocalizePipe, name: "localize" }] }); }
3801
+ }
3802
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmMetricsV2Component, decorators: [{
3803
+ type: Component,
3804
+ args: [{ selector: "metrics_v2", providers: [{ provide: LocalizationService, useClass: AdmMetricsV2Loc }], standalone: false, template: "<div class=\"row\">\r\n <div class=\"col-12\">\r\n <div class=\"card app-overflow-visible\" widget>\r\n <div class=\"card-header\">\r\n <span class=\"text-uppercase\">{{'Metriche di Sistema' | localize: lc}}</span>\r\n </div>\r\n <div class=\"card-body widget-body mv2-body\">\r\n <!-- Barra dei comandi: sorgente, finestra, istanze, vista, aggiornamento -->\r\n <div class=\"mv2-toolbar\">\r\n <div class=\"mv2-group\">\r\n <span class=\"mv2-group-label\">{{'Sorgente' | localize: lc}}</span>\r\n <div class=\"mv2-seg\">\r\n @for (o of Sources; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"source == o.id\" [disabled]=\"o.id != 'live' && !historyAvailable\" (click)=\"setSource(o.id)\"\r\n [title]=\"o.id != 'live' && !historyAvailable ? ('Lo storico non \u00E8 attivo su questo ambiente: ci sono solo i dati live.' | localize: lc) : ''\">\r\n @if (o.id == 'live') {\r\n <span class=\"mv2-live-dot\" [class.mv2-live-paused]=\"paused\"></span>\r\n }\r\n {{o.description}}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (source == 'live') {\r\n <div class=\"mv2-group\">\r\n <span class=\"mv2-group-label\">{{'Finestra' | localize: lc}}</span>\r\n <div class=\"mv2-seg\">\r\n @for (o of LiveWindows; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"liveMinutes == o.id\" (click)=\"setLiveMinutes(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n } @else if (source == 'period') {\r\n <div class=\"mv2-cluster\">\r\n <div class=\"mv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterfrom\" (ngModelChange)=\"filterfrom = $event; datesChanged()\" name=\"filterfrom\" [Placeholder]=\"'Dal' | localize: lc\"></form-datetime>\r\n </div>\r\n <div class=\"mv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterto\" (ngModelChange)=\"filterto = $event; datesChanged()\" name=\"filterto\" [Placeholder]=\"'Al' | localize: lc\"></form-datetime>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"mv2-cluster\">\r\n <div class=\"mv2-field mv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"snapFromId\" (ngModelChange)=\"snapFromId = $event; snapshotsChanged()\" name=\"snapFrom\" [Source]=\"SnapshotOptions\" [Placeholder]=\"'Dallo snapshot' | localize: lc\"></form-select>\r\n </div>\r\n <div class=\"mv2-field mv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"snapToId\" (ngModelChange)=\"snapToId = $event; snapshotsChanged()\" name=\"snapTo\" [Source]=\"SnapshotOptions\" [Placeholder]=\"'Allo snapshot' | localize: lc\"></form-select>\r\n </div>\r\n @if (snapshotWindow; as w) {\r\n <span class=\"mv2-muted\">{{'Dalle' | localize: lc}} {{local(w.from)}} {{'alle' | localize: lc}} {{local(w.to)}}</span>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"mv2-group mv2-field-wide\">\r\n <span class=\"mv2-group-label\">{{'Istanze' | localize: lc}}</span>\r\n <form-multiselect class=\"mv2-fill\" [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"instances\" (ngModelChange)=\"instancesChanged($event)\" name=\"instances\" [Source]=\"Instances\" [Placeholder]=\"clusterLabel\" [MaxShownElements]=\"2\"></form-multiselect>\r\n </div>\r\n\r\n <div class=\"mv2-cluster mv2-cluster-end\">\r\n @if (source == 'live') {\r\n <span class=\"mv2-muted\">\r\n @if (dataLoading) {\r\n <i class=\"fa fa-spinner fa-spin\"></i>\r\n }\r\n @if (lastUpdate) {\r\n {{'Aggiornato alle' | localize: lc}} {{hhmmss(lastUpdate)}}\r\n }\r\n </span>\r\n <button type=\"button\" class=\"mv2-btn\" (click)=\"togglePause()\">\r\n <i class=\"fa\" [class.fa-pause]=\"!paused\" [class.fa-play]=\"paused\"></i> {{(paused ? 'Riprendi' : 'Pausa') | localize: lc}}\r\n </button>\r\n } @else if (dataLoading) {\r\n <span class=\"mv2-muted\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</span>\r\n }\r\n <div class=\"mv2-seg\">\r\n @for (o of Views; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"view == o.id\" (click)=\"setView(o.id)\">\r\n <i class=\"fa\" [class.fa-line-chart]=\"o.id == 'charts'\" [class.fa-table]=\"o.id == 'table'\"></i> {{o.description}}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Avvisi -->\r\n @for (warning of warnings; track $index) {\r\n <div class=\"mv2-alert mv2-alert-warn\"><i class=\"fa fa-exclamation-triangle\"></i> {{warning}}</div>\r\n }\r\n @if (snapshotsLoaded && !historyAvailable) {\r\n <div class=\"mv2-alert\"><i class=\"fa fa-info-circle\"></i> {{'Lo storico non \u00E8 attivo su questo ambiente: ci sono solo i dati live.' | localize: lc}}</div>\r\n }\r\n @if (dataError) {\r\n <div class=\"mv2-alert mv2-alert-error\">{{dataError}}</div>\r\n }\r\n\r\n <div class=\"mv2-main\">\r\n <!-- Catalogo -->\r\n <aside class=\"mv2-panel mv2-catalog\">\r\n <div class=\"mv2-field-full\">\r\n <form-input [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"search\" (ngModelChange)=\"search = $event; buildGroups()\" name=\"search\" [Placeholder]=\"'Cerca una metrica\u2026' | localize: lc\"></form-input>\r\n </div>\r\n <div class=\"mv2-presets\">\r\n @for (p of Presets; track p) {\r\n <button type=\"button\" class=\"mv2-chip\" (click)=\"applyPreset(p)\">{{p | localize: lc}}</button>\r\n }\r\n </div>\r\n <div class=\"mv2-counter\">\r\n <span>{{selected.length}} {{'di' | localize: lc}} {{Max}} {{'metriche scelte' | localize: lc}}</span>\r\n @if (selected.length > 0) {\r\n <button type=\"button\" class=\"mv2-link\" (click)=\"clearSelection()\">{{'Togli tutte' | localize: lc}}</button>\r\n }\r\n </div>\r\n\r\n @if (catalogLoading) {\r\n <div class=\"mv2-muted mv2-pad\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</div>\r\n }\r\n @if (catalogError) {\r\n <div class=\"mv2-alert mv2-alert-error\">{{catalogError}}</div>\r\n }\r\n @for (group of groups; track group.type) {\r\n <div class=\"mv2-cat-group\">\r\n <div class=\"mv2-cat-title\">{{group.label}} <span class=\"mv2-muted\">{{group.count}}</span></div>\r\n @for (entry of group.entries; track entry.info.name) {\r\n <div class=\"mv2-cat-item\" [class.mv2-cat-disabled]=\"!canSelect(entry.info.name)\">\r\n <label class=\"mv2-check\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(entry.info.name)\" [disabled]=\"!canSelect(entry.info.name)\" (change)=\"toggleMetric(entry.info.name)\" />\r\n <span class=\"mv2-cat-name\" [title]=\"entry.info.name\">{{entry.info.name}}</span>\r\n </label>\r\n <span class=\"mv2-cat-meta\">\r\n {{catalogUnit(entry.info)}}\r\n @if (instancesLabel(entry.info)) {\r\n <span class=\"mv2-cat-inst\" [title]=\"entry.info.instances.join('\\n')\">{{instancesLabel(entry.info)}}</span>\r\n }\r\n </span>\r\n @if (entry.items.length > 0) {\r\n <button type=\"button\" class=\"mv2-caret\" (click)=\"entry.open = !entry.open\" [title]=\"'Voci' | localize: lc\">\r\n <i class=\"fa\" [class.fa-caret-down]=\"entry.open\" [class.fa-caret-right]=\"!entry.open\"></i> {{entry.items.length}}\r\n </button>\r\n }\r\n </div>\r\n @if (entry.open && entry.items.length > 0) {\r\n <div class=\"mv2-cat-items\">\r\n <button type=\"button\" class=\"mv2-link\" (click)=\"addItems(entry)\">{{addItemsLabel(entry)}}</button>\r\n @for (item of entry.items; track item.name) {\r\n <div class=\"mv2-cat-item mv2-cat-sub\" [class.mv2-cat-disabled]=\"!canSelect(item.name)\">\r\n <label class=\"mv2-check\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(item.name)\" [disabled]=\"!canSelect(item.name)\" (change)=\"toggleMetric(item.name)\" />\r\n <span class=\"mv2-cat-name\" [title]=\"item.name\">{{item.name.substring(entry.info.name.length + 3)}}</span>\r\n </label>\r\n @if (instancesLabel(item)) {\r\n <span class=\"mv2-cat-inst\" [title]=\"item.instances.join('\\n')\">{{instancesLabel(item)}}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n </aside>\r\n\r\n <!-- Grafici, tessere e tabella -->\r\n <section class=\"mv2-area\">\r\n @if (selected.length == 0) {\r\n <div class=\"mv2-panel mv2-placeholder\">\r\n <i class=\"fa fa-line-chart\"></i>\r\n {{'Scegli una o pi\u00F9 metriche dal catalogo.' | localize: lc}}\r\n </div>\r\n } @else {\r\n <!-- Tessere dei valori correnti -->\r\n <div class=\"mv2-tiles\">\r\n @for (tile of tiles; track tile.info.name) {\r\n <div class=\"mv2-tile\">\r\n <div class=\"mv2-tile-name\" [title]=\"tile.info.name\">{{tile.info.name}}</div>\r\n @for (row of tile.rows; track row.instance) {\r\n <div class=\"mv2-tile-row\">\r\n <span class=\"mv2-dot\" [style.background]=\"row.color\" [title]=\"row.instance\"></span>\r\n <strong>{{row.value}}</strong>\r\n @if (row.extra) {\r\n <span class=\"mv2-muted\">{{row.extra}}</span>\r\n }\r\n </div>\r\n } @empty {\r\n <div class=\"mv2-muted\">\u2014</div>\r\n }\r\n <svg class=\"mv2-spark\" viewBox=\"0 0 120 28\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\r\n @for (spark of tile.sparks; track $index) {\r\n <path [attr.d]=\"spark.d\" [attr.stroke]=\"spark.color\" fill=\"none\" stroke-width=\"1.3\" vector-effect=\"non-scaling-stroke\" />\r\n }\r\n </svg>\r\n @if (tile.time) {\r\n <div class=\"mv2-muted mv2-small\">{{'alle' | localize: lc}} {{tile.time}}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Legenda comune: un clic nasconde o mostra l'istanza ovunque -->\r\n <div class=\"mv2-legend-bar\">\r\n <div class=\"mv2-legend\">\r\n @for (item of legend; track item.instance) {\r\n <button type=\"button\" class=\"mv2-legend-item\" [class.mv2-legend-off]=\"item.hidden\" (click)=\"toggleInstance(item.instance)\">\r\n <span class=\"mv2-dot\" [style.background]=\"item.color\"></span>{{item.instance}}\r\n </button>\r\n }\r\n </div>\r\n <span class=\"mv2-muted mv2-small\">{{'Orari in ora locale' | localize: lc}} ({{zone}}) &middot; {{'trascina su un grafico per lo zoom' | localize: lc}}</span>\r\n @if (viewFrom != null) {\r\n <button type=\"button\" class=\"mv2-btn\" (click)=\"resetZoom()\"><i class=\"fa fa-arrows-h\"></i> {{'Finestra intera' | localize: lc}}</button>\r\n }\r\n </div>\r\n\r\n @if (view == 'charts') {\r\n @for (panel of panels; track panel.info.name) {\r\n <div class=\"mv2-panel mv2-chart-panel\">\r\n <div class=\"mv2-panel-header\">\r\n <span class=\"mv2-panel-title\">{{panel.info.name}}</span>\r\n <span class=\"mv2-type\">{{Type[panel.info.metricType]}}</span>\r\n <span class=\"mv2-muted\">{{mainUnit(panel.info)}}</span>\r\n <span class=\"mv2-spacer\"></span>\r\n @if (panel.alts.length > 1) {\r\n <div class=\"mv2-seg mv2-seg-small\">\r\n @for (o of panel.alts; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"panel.alt == o.id\" (click)=\"setAlt(panel, o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n }\r\n <button type=\"button\" class=\"mv2-icon-btn\" [title]=\"'Togli' | localize: lc\" (click)=\"removeMetric(panel.info.name)\">&times;</button>\r\n </div>\r\n @if (panel.empty) {\r\n <div class=\"mv2-empty\">\r\n @if (dataLoading) {\r\n <i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}\r\n } @else {\r\n {{emptyLabel(panel.info.name)}}\r\n }\r\n </div>\r\n } @else {\r\n @for (chart of panel.charts; track chart.key) {\r\n <mv2-chart [series]=\"chart.series\" [from]=\"chartFrom\" [to]=\"chartTo\" [height]=\"chart.height\" [unit]=\"chart.unit\" [marks]=\"marks\"\r\n [cursor]=\"cursor\" [format]=\"format\" (cursorChange)=\"cursor = $event\" (zoom)=\"zoomTo($event)\"></mv2-chart>\r\n }\r\n }\r\n </div>\r\n }\r\n } @else {\r\n @for (section of tables; track section.info.name) {\r\n <div class=\"mv2-panel\">\r\n <div class=\"mv2-panel-header\">\r\n <span class=\"mv2-panel-title\">{{section.info.name}}</span>\r\n <span class=\"mv2-type\">{{Type[section.info.metricType]}}</span>\r\n <span class=\"mv2-spacer\"></span>\r\n @if (section.total > 0) {\r\n <button type=\"button\" class=\"mv2-btn\" (click)=\"exportCsv(section)\"><i class=\"fa fa-download\"></i> {{'Esporta CSV' | localize: lc}}</button>\r\n }\r\n </div>\r\n @if (section.total == 0) {\r\n <div class=\"mv2-empty\">{{emptyLabel(section.info.name)}}</div>\r\n } @else {\r\n <div class=\"mv2-table-wrap\">\r\n <table class=\"mv2-table\">\r\n <thead>\r\n <tr>\r\n @for (column of section.columns; track column.key) {\r\n <th [class.mv2-num]=\"column.num\" (click)=\"sortBy(section, column)\">\r\n {{column.label}}\r\n @if (section.sortKey == column.key) {\r\n <i class=\"fa\" [class.fa-caret-down]=\"section.sortDesc\" [class.fa-caret-up]=\"!section.sortDesc\"></i>\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (row of shownRows(section); track $index) {\r\n <tr>\r\n @for (column of section.columns; track column.key) {\r\n <td [class.mv2-num]=\"column.num\">\r\n @if (column.key == 'instance') {\r\n <span class=\"mv2-dot\" [style.background]=\"color(row['instance'])\"></span>\r\n }\r\n {{cell(row, column)}}\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n @if (section.total > section.rows.length || section.total > 300) {\r\n <div class=\"mv2-muted mv2-small mv2-pad\">{{'Mostrate le prime 300 righe di' | localize: lc}} {{section.total}}; {{\"l'esportazione le comprende tutte.\" | localize: lc}}</div>\r\n }\r\n }\r\n </div>\r\n }\r\n }\r\n }\r\n </section>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .mv2-noclear .p-select-clear-icon{display:none}:host ::ng-deep .mv2-fill{display:block;width:100%}.mv2-body{container-type:inline-size}.mv2-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 18px;padding-top:6px}.mv2-cluster{display:flex;flex-wrap:wrap;align-items:center;gap:10px}.mv2-cluster-end{margin-left:auto}.mv2-field{width:200px}.mv2-field-wide{width:340px;max-width:100%}.mv2-field-full{width:100%}.mv2-group{position:relative;display:inline-flex}.mv2-group-label{position:absolute;top:-8px;left:8px;z-index:2;padding:0 4px;background:var(--p-content-background, #fff);color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.mv2-seg{display:inline-flex;height:39px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);overflow:hidden;background:var(--p-content-background, #fff)}.mv2-seg button{display:inline-flex;align-items:center;gap:6px;padding:0 12px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.mv2-seg button:first-child{border-left:0}.mv2-seg button:hover:not(.mv2-on):not(:disabled){background:var(--p-content-hover-background, #f8fafc)}.mv2-seg button:disabled{color:var(--p-text-muted-color, #64748b);opacity:.6;cursor:not-allowed}.mv2-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.mv2-seg button.mv2-on{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca);font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.mv2-seg.mv2-seg-small{height:28px}.mv2-seg.mv2-seg-small button{padding:0 9px;font-size:12px}.mv2-live-dot{width:8px;height:8px;border-radius:50%;background:#16a34a}.mv2-live-dot.mv2-live-paused{background:var(--p-text-muted-color, #64748b)}.mv2-btn{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.mv2-btn:hover{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.mv2-icon-btn{width:26px;height:26px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--p-text-muted-color, #64748b);font-size:18px;line-height:1;cursor:pointer}.mv2-icon-btn:hover{background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155)}.mv2-link{padding:0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;cursor:pointer}.mv2-link:hover{text-decoration:underline}.mv2-muted{color:var(--p-text-muted-color, #64748b);font-size:12px}.mv2-small{font-size:11px}.mv2-pad{padding:6px 2px}.mv2-spacer{flex:1 1 auto}.mv2-alert{margin-top:10px;padding:7px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155);font-size:13px}.mv2-alert.mv2-alert-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.mv2-alert.mv2-alert-error{border-color:#fca5a5;background:#fef2f2;color:#991b1b}.mv2-main{display:grid;grid-template-columns:300px minmax(0,1fr);gap:14px;align-items:start;margin-top:14px}@container (max-width: 1000px){.mv2-main{grid-template-columns:minmax(0,1fr)}.mv2-catalog{position:static;max-height:360px}}.mv2-panel{padding:12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0}.mv2-catalog{position:sticky;top:10px;max-height:calc(100vh - 20px);overflow:auto;font-size:13px;color:var(--p-text-color, #334155)}.mv2-presets{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 8px}.mv2-chip{padding:3px 10px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:12px;background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:12px;cursor:pointer}.mv2-chip:hover{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.mv2-counter{display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);color:var(--p-text-muted-color, #64748b);font-size:12px}.mv2-cat-group{margin-top:10px}.mv2-cat-title{margin-bottom:4px;color:var(--p-text-muted-color, #64748b);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.mv2-cat-item{display:flex;align-items:center;gap:6px;padding:2px 0;min-width:0}.mv2-cat-item.mv2-cat-disabled{opacity:.5}.mv2-cat-sub{padding-left:18px}.mv2-check{display:flex;align-items:center;gap:6px;flex:1 1 auto;min-width:0;margin:0;cursor:pointer}.mv2-check input{flex:none;display:inline-block;width:auto;height:auto;margin:0;accent-color:var(--p-primary-color, #6366f1)}.mv2-cat-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mv2-cat-meta{flex:none;color:var(--p-text-muted-color, #64748b);font-size:11px;white-space:nowrap}.mv2-cat-inst{margin-left:4px;padding:0 5px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:8px;color:var(--p-text-muted-color, #64748b);font-size:10px;cursor:help}.mv2-caret{flex:none;padding:0 4px;border:0;background:transparent;color:var(--p-text-muted-color, #64748b);font-size:11px;cursor:pointer}.mv2-cat-items{padding:2px 0 4px 18px}.mv2-area{display:flex;flex-direction:column;gap:12px;min-width:0}.mv2-placeholder{display:flex;flex-direction:column;align-items:center;gap:10px;padding:70px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.mv2-placeholder .fa{font-size:28px;opacity:.5}.mv2-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}.mv2-tile{display:flex;flex-direction:column;gap:3px;padding:10px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0;font-size:13px}.mv2-tile-name{overflow:hidden;color:var(--p-text-muted-color, #64748b);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.mv2-tile-row{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}.mv2-spark{display:block;width:100%;height:28px;margin-top:2px}.mv2-dot{display:inline-block;flex:none;width:9px;height:9px;margin-right:4px;border-radius:50%}.mv2-legend-bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px}.mv2-legend{display:flex;flex-wrap:wrap;gap:4px 6px}.mv2-legend-item{display:inline-flex;align-items:center;padding:2px 8px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:12px;background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:12px;cursor:pointer}.mv2-legend-item.mv2-legend-off{opacity:.45;text-decoration:line-through}.mv2-chart-panel{padding-bottom:6px}.mv2-panel-header{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;margin-bottom:6px}.mv2-panel-title{font-size:13px;font-weight:600;color:var(--p-text-color, #334155)}.mv2-type{padding:0 7px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:10px;color:var(--p-text-muted-color, #64748b);font-size:11px;line-height:18px}.mv2-empty{padding:26px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.mv2-table-wrap{max-height:420px;overflow:auto;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px)}.mv2-table{width:100%;border-collapse:separate;border-spacing:0;font-size:12px;color:var(--p-text-color, #334155)}.mv2-table th{position:sticky;top:0;z-index:1;padding:6px 8px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-muted-color, #64748b);font-weight:500;white-space:nowrap;cursor:pointer}.mv2-table td{padding:3px 8px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);white-space:nowrap}.mv2-num{text-align:right!important;font-variant-numeric:tabular-nums}\n"] }]
3805
+ }], ctorParameters: () => [{ type: i1$1.DateService }, { type: i1.LocalizationService }, { type: i3.PreferencesService }, { type: i1$1.InterComService }, { type: i4.ActivatedRoute }, { type: AdminModuleBaseService }] });
3806
+
3807
+ class TopActivitiesDataView extends AppSearch {
3808
+ constructor() { super(); }
3809
+ }
3810
+
3811
+ class AdmTopActivitieLoc extends LocalizationService {
3812
+ constructor(injector) {
3813
+ super(injector);
3814
+ super.set("it->en", "Sessioni Attive", ["Active Sessions"]);
3815
+ super.set("it->en", "Prime Istruzioni", ["Top Nodes"]);
3816
+ super.set("it->en", "Prime Sessioni", ["Top Sessions"]);
3817
+ super.set("it->en", "Carico i Dati", ["Loading Data"]);
3818
+ super.set("it->en", "Attività Principali", ["Top Activities"]);
3819
+ super.set("it->en", "Descrizione Nodo", ["Node Description"]);
3820
+ super.set("it->en", "Nodo", ["Node"]);
3821
+ super.set("it->en", "Attività", ["Activity"]);
3822
+ super.set("it->en", "Limite", ["Limit"]);
3823
+ super.set("it->en", "Tipo Nodo", ["Node Type"]);
3824
+ super.set("it->en", "Utente", ["User"]);
3825
+ super.set("it->en", "Tenant", ["Tenant"]);
3826
+ super.set("it->en", "Processo", ["Process"]);
3827
+ super.set("it->en", "Aggiorna ogni", ["Update every"]);
3828
+ super.set("it->en", "secondo/i", ["second/s"]);
3829
+ super.set("it->en", "Thread Massimi", ["Maximum Threads"]);
3830
+ super.set("it->en", "CPU", ["CPUs"]);
3831
+ super.set("it->en", "Classe Attesa", ["Wait Class Name"]);
3832
+ super.set("it->en", "Seriale", ["Serial"]);
3833
+ super.set("it->en", "Stato", ["Status"]);
3834
+ super.set("it->en", "Tipo", ["Type"]);
3835
+ super.set("it->en", "Utente", ["User"]);
3836
+ }
3837
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivitieLoc, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
3838
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivitieLoc }); }
3839
+ }
3840
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivitieLoc, decorators: [{
3841
+ type: Injectable
3842
+ }], ctorParameters: () => [{ type: i0.Injector }] });
3843
+
3844
+ // Angular
3845
+ class AdmTopActivitiesComponent {
3846
+ constructor(dateExts, lc, localStorage, location, intercom, route, adms, mod) {
3847
+ this.dateExts = dateExts;
3848
+ this.lc = lc;
3849
+ this.localStorage = localStorage;
3850
+ this.location = location;
3851
+ this.intercom = intercom;
3852
+ this.route = route;
3853
+ this.adms = adms;
3854
+ this.mod = mod;
3855
+ this.searchView = new TopActivitiesDataView();
3856
+ this.autoUpdate = false;
3857
+ this.seconds = "5";
3858
+ this.rtWindow = null;
3859
+ this.destroyed$ = new ReplaySubject(1);
3860
+ this.seriesPropertiesMapping = {};
3861
+ this.propertiesSeriesMapping = {
3862
+ ["cputime"]: "CPU",
3863
+ ["waittime"]: "Wait",
3864
+ ["scripttime"]: "Scripting",
3865
+ ["dbtime"]: "DB",
3866
+ ["nettime"]: "Network",
3867
+ ["throttletime"]: "Throttling",
3868
+ ["sleeptime"]: "Sleep",
3869
+ ["iotime"]: "IO"
3870
+ };
3871
+ this.seriesPropertiesMapping2 = {};
3872
+ this.be = route.snapshot.params["be"] || "";
3873
+ this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
3874
+ }
3875
+ ngOnInit() {
3876
+ // Ogni minuto calcolo qual è la finestra del realTime
3877
+ // interval(60 * 1000).pipe(takeUntil(this.destroyed$)).subscribe(_ => {
3878
+ // this.updateRealTimeWindow();
3879
+ // });
3880
+ this.storageSub = this.route.queryParams.subscribe(params => {
3881
+ this.Init(params["ignoreStorage"]);
3882
+ });
3883
+ }
3884
+ async updateRealTimeWindow() {
3885
+ let data = new TopActivitiesDataView();
3886
+ data.filterbe = this.be;
3887
+ data = await this.adms.getTopActivities(this.be, data);
3888
+ let reference = data.items[0];
3889
+ for (let i = 0; i < reference.actsessions.length; i++) {
3890
+ let itemref = reference.actsessions[i];
3891
+ let theD = this.dateExts.getDateConvertion(itemref.sampletime);
3892
+ if (!this.rtWindow)
3893
+ this.rtWindow = { from: theD, to: theD };
3894
+ else {
3895
+ if (theD.isBefore(this.rtWindow.from))
3896
+ this.rtWindow.from = theD;
3897
+ if (theD.isAfter(this.rtWindow.to))
3898
+ this.rtWindow.to = theD;
3899
+ }
3900
+ }
3901
+ }
3902
+ Init(ignoreStore) {
3903
+ this.doInit(ignoreStore);
3904
+ }
3905
+ async doInit(ignoreStorage) {
3906
+ let item = await this.localStorage.getItem("HotSearchTopAct").toPromise();
3907
+ if (!ignoreStorage && item) {
3908
+ this.init(item);
3909
+ }
3910
+ else {
3911
+ this.init(null);
3912
+ this.location.replaceState(`/pages/${this.mod}/topact`);
3913
+ this.route.snapshot.queryParams = [];
3914
+ await this.localStorage.removeItem("HotSearchTopAct").toPromise();
3915
+ }
3916
+ }
3917
+ init(HotPotato) {
3918
+ if (HotPotato) {
3919
+ this.searchView = HotPotato;
3920
+ this.POSTGetItems(this.searchView, false);
3921
+ }
3922
+ else {
3923
+ this.searchView = new TopActivitiesDataView();
3924
+ this.sendSearch();
3925
+ }
3926
+ }
3927
+ ngOnDestroy() {
3928
+ this.destroyed$.next(true);
3929
+ this.destroyed$.complete();
3930
+ if (this.storageSub)
3931
+ this.storageSub.unsubscribe();
3932
+ }
3933
+ async sendSearch(resetPage = true, update = false, searchViewOverride = null) {
3934
+ let sv = searchViewOverride ?? this.searchView;
3935
+ if ((sv.filterfrom || sv.filterto) && !this.rtWindow)
3936
+ await this.updateRealTimeWindow();
3937
+ let dateRequestOnRealtimeWindow = sv.filterfrom && this.dateExts.getDateConvertion(sv.filterfrom).isAfter(this.rtWindow.from) &&
3938
+ sv.filterto && this.dateExts.getDateConvertion(sv.filterto).isBefore(this.rtWindow.to);
3939
+ sv.filterrealtime = dateRequestOnRealtimeWindow;
3940
+ sv.searchinprogress = true;
3941
+ this.POSTGetItems(sv, update);
3942
+ }
3943
+ async POSTGetItems(data, update) {
3944
+ data.filterbe = this.be;
3945
+ let res = await this.adms.getTopActivities(this.be, data);
3946
+ //Se sono in modalità di update non devo toccare la searchView attuale se non per il searchinprogress che viene messo dall'es table
3947
+ if (!update) {
3948
+ this.searchView = res;
3949
+ this.localStorage.setItem("HotSearchTopAct", this.searchView).subscribe();
3950
+ }
3951
+ // DirectorInfos anche se per qualche motivo crasha di brutto è comunque non obbligatorio, evito di non caricare la pagina solo perché lui scoppia
3952
+ let dirinfos = await this.adms.getInfos(this.be);
3953
+ this.searchView.searchinprogress = false;
3954
+ this.reviewBindings(update, res, dirinfos);
3955
+ }
3956
+ loadNodeDetails(nodeId) {
3957
+ this.NodeDetails = null;
3958
+ requestAnimationFrame(async () => {
3959
+ this.NodeDetails = await this.adms.getNodeStats(this.be, nodeId, this.searchView.filterglobal);
3960
+ });
3961
+ }
3962
+ reviewBindings(update, searchViewOverride, dirInfos) {
3963
+ let reference = (searchViewOverride ?? this.searchView).items[0];
3964
+ this.TopNodes = reference.topnodes;
3965
+ this.TopSessions = reference.topsessions;
3966
+ this.DirInfos = dirInfos?.cluster ? dirInfos : null;
3967
+ this.DirInfosGlobal = !dirInfos?.cluster ? dirInfos : null;
3968
+ if (!reference.actsessions || reference.actsessions.length == 0) {
3969
+ this.TopActiveSessions = null;
3970
+ return;
3971
+ }
3972
+ let series = [];
3973
+ let props = Object.keys(reference.actsessions[0]);
3974
+ for (let i = 0; i < props.length; i++) {
3975
+ if (props[i] != 'sampletime') {
3976
+ let s = new LineSerie(this.propertiesSeriesMapping[props[i]], [], null, null, "sessions");
3977
+ series.push(s);
3978
+ this.seriesPropertiesMapping[props[i]] = s;
3979
+ }
3980
+ }
3981
+ for (let i = 0; i < reference.actsessions.length; i++) {
3982
+ let itemref = reference.actsessions[i];
3983
+ let theD = new Date(itemref.sampletime);
3984
+ for (let i = 0; i < props.length; i++)
3985
+ if (props[i] != 'sampletime' && (!update || this.dateExts.getDateConvertion(theD).isAfter(this.LastDataPoint)))
3986
+ this.seriesPropertiesMapping[props[i]].values.push(new DateValue(theD, itemref[props[i]]));
3987
+ }
3988
+ if (update && this.ChartInstance) {
3989
+ let data = new LineChartData(series, new Limit(dirInfos?.threads, this.lc.loc("Thread Massimi")));
3990
+ if (series.some(t => t.values.length > 0)) {
3991
+ this.ChartInstance.addData(data);
3992
+ this.LastDataPoint = series.map(t => dayjs.max(t.values.map(v => this.dateExts.getDateConvertion(v.d))))[0];
3993
+ }
3994
+ }
3995
+ else {
3996
+ this.TopActiveSessions = new LineChartData(series, new Limit(dirInfos?.threads, this.lc.loc("Thread Massimi")));
3997
+ this.LastDataPoint = series.map(t => dayjs.max(t.values.map(v => this.dateExts.getDateConvertion(v.d))))[0];
3998
+ }
3999
+ }
4000
+ autoUpdateChanged() {
4001
+ if (!/^[1-9]\d*$/.test(this.seconds))
4002
+ this.seconds = '10';
4003
+ const secondsInMs = parseInt(this.seconds, null) * 1000;
4004
+ if (this.autoUpdate) {
4005
+ this.sendSearch(false, true);
4006
+ this.updateTimer = setInterval(() => { this.sendSearch(false, true); }, secondsInMs);
4007
+ }
4008
+ else
1042
4009
  clearInterval(this.updateTimer);
1043
4010
  }
1044
4011
  dataSelectionChanged(event) {
@@ -1208,6 +4175,7 @@ class AdmTopActivityV2Loc extends LocalizationService {
1208
4175
  super.set("it->en", "Macchina", ["Machine"]);
1209
4176
  super.set("it->en", "Tipo", ["Type"]);
1210
4177
  super.set("it->en", "Processo", ["Process"]);
4178
+ super.set("it->en", "Errore", ["Error"]);
1211
4179
  }
1212
4180
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivityV2Loc, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
1213
4181
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivityV2Loc }); }
@@ -1451,13 +4419,22 @@ class AdmTopActivityV2Component {
1451
4419
  this.applyResult(req, res);
1452
4420
  this.saveSettings();
1453
4421
  }
1454
- catch {
1455
- // L'errore (finestra non valida, backend non raggiungibile...) lo mostra già HTTPService
4422
+ catch (err) {
4423
+ // Finestra non valida e simili arrivano come 542 (avviso), il resto come errore. La sessione scaduta la gestisce il servizio
4424
+ if (err?.status != 401)
4425
+ this.showError(err);
1456
4426
  }
1457
4427
  finally {
1458
4428
  this.loading = false;
1459
4429
  }
1460
4430
  }
4431
+ showError(err) {
4432
+ let text = this.adms.errorText(err);
4433
+ if (err?.status == 542)
4434
+ this.adms.msgExts.simpleWarning(text);
4435
+ else
4436
+ this.adms.msgExts.simpleError(`${this.lc.loc("Errore")} ${err?.status ?? ""}: ${text}`);
4437
+ }
1461
4438
  buildRequest() {
1462
4439
  let to = this.toDate(this.filterto) ?? new Date();
1463
4440
  let from = this.toDate(this.filterfrom) ?? new Date(to.getTime() - 3600 * 1000);
@@ -2491,13 +5468,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
2491
5468
  // La Top Activity v2 prende la rotta "topact" solo se DirectorFeature.TopActivityV2 è attivo nella configurazione del modulo,
2492
5469
  // altrimenti la guardia fallisce e la rotta successiva apre la pagina di sempre
2493
5470
  const topActivityV2Enabled = () => inject(ADM_TOPACT_V2, { optional: true }) ?? false;
5471
+ // Lo stesso per il profiler con DirectorFeature.ProfilerV2
5472
+ const profilerV2Enabled = () => inject(ADM_PROFILER_V2, { optional: true }) ?? false;
5473
+ // La pagina del workload report c'è solo con DirectorFeature.WorkloadReportV2: prima il report era solo un file da scaricare dal profiler
5474
+ const workloadReportV2Enabled = () => inject(ADM_WORKLOAD_V2, { optional: true }) ?? false;
5475
+ // Le metriche v2 prendono la rotta "metrics" solo con DirectorFeature.MetricsV2, come la Top Activity
5476
+ const metricsV2Enabled = () => inject(ADM_METRICS_V2, { optional: true }) ?? false;
2494
5477
  const routeConfig = [
2495
5478
  {
2496
5479
  path: "", children: [
2497
5480
  { path: "serverworkers/:be", component: AdmWrkComponent, data: { title: "Ricerca Processi Applicativi", internalNavigation: false } },
5481
+ { path: "profiler/:be", component: AdmProfilerV2Component, canMatch: [profilerV2Enabled], data: { title: "Profiler del Server", internalNavigation: false } },
2498
5482
  { path: "profiler/:be", component: AdmProfilerComponent, data: { title: "Profiler del Server", internalNavigation: false } },
5483
+ { path: "workload/:be", component: AdmWorkloadV2Component, canMatch: [workloadReportV2Enabled], data: { title: "Workload report", internalNavigation: false } },
2499
5484
  { path: "topact/:be", component: AdmTopActivityV2Component, canMatch: [topActivityV2Enabled], data: { title: "Attività Principali", internalNavigation: false } },
2500
5485
  { path: "topact/:be", component: AdmTopActivitiesComponent, data: { title: "Attività Principali", internalNavigation: false } },
5486
+ { path: "metrics/:be", component: AdmMetricsV2Component, canMatch: [metricsV2Enabled], data: { title: "Metriche di Sistema", internalNavigation: false } },
2501
5487
  { path: "metrics/:be", component: AdmMetricsComponent, data: { title: "Metriche di Sistema", internalNavigation: false } },
2502
5488
  { path: "serverlogs/:be", component: AdmLogsComponent, data: { title: "Log del Server", internalNavigation: false } },
2503
5489
  { path: "serveraudits/:be", component: AdmAuditsComponent, data: { title: "Ricerca Audit Applicativi", internalNavigation: false } },
@@ -2505,9 +5491,12 @@ const routeConfig = [
2505
5491
  { path: "serverviews/:be", component: AdmDynamicViewsComponent, data: { title: "Ricerca Viste Dinamiche Applicative", internalNavigation: false } },
2506
5492
  { path: "metadataquery/:be", component: MtdQueryComponent, data: { title: "Ricerca Viste Dinamiche Applicative", internalNavigation: false } },
2507
5493
  { path: "serverworkers", component: AdmWrkComponent, data: { title: "Ricerca Processi Applicativi", internalNavigation: false } },
5494
+ { path: "profiler", component: AdmProfilerV2Component, canMatch: [profilerV2Enabled], data: { title: "Profiler del Server", internalNavigation: false } },
2508
5495
  { path: "profiler", component: AdmProfilerComponent, data: { title: "Profiler del Server", internalNavigation: false } },
5496
+ { path: "workload", component: AdmWorkloadV2Component, canMatch: [workloadReportV2Enabled], data: { title: "Workload report", internalNavigation: false } },
2509
5497
  { path: "topact", component: AdmTopActivityV2Component, canMatch: [topActivityV2Enabled], data: { title: "Attività Principali", internalNavigation: false } },
2510
5498
  { path: "topact", component: AdmTopActivitiesComponent, data: { title: "Attività Principali", internalNavigation: false } },
5499
+ { path: "metrics", component: AdmMetricsV2Component, canMatch: [metricsV2Enabled], data: { title: "Metriche di Sistema", internalNavigation: false } },
2511
5500
  { path: "metrics", component: AdmMetricsComponent, data: { title: "Metriche di Sistema", internalNavigation: false } },
2512
5501
  { path: "serverlogs", component: AdmLogsComponent, data: { title: "Log del Server", internalNavigation: false } },
2513
5502
  { path: "serveraudits", component: AdmAuditsComponent, data: { title: "Ricerca Audit Applicativi", internalNavigation: false } },
@@ -2541,6 +5530,10 @@ const MODULES = [
2541
5530
  const COMPONENTS = [
2542
5531
  AdmWrkComponent,
2543
5532
  AdmProfilerComponent,
5533
+ AdmProfilerV2Component,
5534
+ AdmWorkloadV2Component,
5535
+ AdmMetricsV2Component,
5536
+ MetricsV2ChartComponent,
2544
5537
  AdmTopActivitiesComponent,
2545
5538
  AdmTopActivityV2Component,
2546
5539
  SessionDetailComponent,
@@ -2554,7 +5547,8 @@ const COMPONENTS = [
2554
5547
  AdmSystemParametersComponent
2555
5548
  ];
2556
5549
  const PIPES = [
2557
- FormatTimePipe
5550
+ FormatTimePipe,
5551
+ ProfilerFormatPipe
2558
5552
  ];
2559
5553
  class AdmModule {
2560
5554
  constructor(commService) {
@@ -2565,6 +5559,10 @@ class AdmModule {
2565
5559
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmModule, deps: [{ token: AdminModuleCommService }], target: i0.ɵɵFactoryTarget.NgModule }); }
2566
5560
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.34", ngImport: i0, type: AdmModule, declarations: [AdmWrkComponent,
2567
5561
  AdmProfilerComponent,
5562
+ AdmProfilerV2Component,
5563
+ AdmWorkloadV2Component,
5564
+ AdmMetricsV2Component,
5565
+ MetricsV2ChartComponent,
2568
5566
  AdmTopActivitiesComponent,
2569
5567
  AdmTopActivityV2Component,
2570
5568
  SessionDetailComponent,
@@ -2592,11 +5590,15 @@ class AdmModule {
2592
5590
  EsGertrudeModule,
2593
5591
  EsChartsModule,
2594
5592
  ComponentsModule,
2595
- DirectivesModule, i4.RouterModule, FormatTimePipe] }); }
5593
+ DirectivesModule, i4.RouterModule, FormatTimePipe,
5594
+ ProfilerFormatPipe] }); }
2596
5595
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmModule, providers: [
2597
5596
  AdminModuleCommService,
2598
5597
  { provide: ADM_CONVERSATIONS, useFactory: () => !!myconfig.TransactionTimeoutSeconds },
2599
- { provide: ADM_TOPACT_V2, useFactory: () => !!myconfig?.DirectorFeature?.TopActivityV2 }
5598
+ { provide: ADM_TOPACT_V2, useFactory: () => !!myconfig?.DirectorFeature?.TopActivityV2 },
5599
+ { provide: ADM_PROFILER_V2, useFactory: () => !!myconfig?.DirectorFeature?.ProfilerV2 },
5600
+ { provide: ADM_WORKLOAD_V2, useFactory: () => !!myconfig?.DirectorFeature?.WorkloadReportV2 },
5601
+ { provide: ADM_METRICS_V2, useFactory: () => !!myconfig?.DirectorFeature?.MetricsV2 }
2600
5602
  ], imports: [MODULES] }); }
2601
5603
  }
2602
5604
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmModule, decorators: [{
@@ -2607,7 +5609,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
2607
5609
  providers: [
2608
5610
  AdminModuleCommService,
2609
5611
  { provide: ADM_CONVERSATIONS, useFactory: () => !!myconfig.TransactionTimeoutSeconds },
2610
- { provide: ADM_TOPACT_V2, useFactory: () => !!myconfig?.DirectorFeature?.TopActivityV2 }
5612
+ { provide: ADM_TOPACT_V2, useFactory: () => !!myconfig?.DirectorFeature?.TopActivityV2 },
5613
+ { provide: ADM_PROFILER_V2, useFactory: () => !!myconfig?.DirectorFeature?.ProfilerV2 },
5614
+ { provide: ADM_WORKLOAD_V2, useFactory: () => !!myconfig?.DirectorFeature?.WorkloadReportV2 },
5615
+ { provide: ADM_METRICS_V2, useFactory: () => !!myconfig?.DirectorFeature?.MetricsV2 }
2611
5616
  ]
2612
5617
  }]
2613
5618
  }], ctorParameters: () => [{ type: AdminModuleCommService }] });
@@ -2620,5 +5625,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
2620
5625
  * Generated bundle index. Do not edit.
2621
5626
  */
2622
5627
 
2623
- export { ADM_CONVERSATIONS, ADM_MODULE_PATH, ADM_TOPACT_V2, AdmMessageTypes, AdmModule, MK_ADM, routeConfig, topActivityV2Enabled };
5628
+ export { ADM_CONVERSATIONS, ADM_METRICS_V2, ADM_MODULE_PATH, ADM_PROFILER_V2, ADM_TOPACT_V2, ADM_WORKLOAD_V2, AdmMessageTypes, AdmModule, MK_ADM, metricsV2Enabled, profilerV2Enabled, routeConfig, topActivityV2Enabled, workloadReportV2Enabled };
2624
5629
  //# sourceMappingURL=esfaenza-admin-module.mjs.map