@esfaenza/admin-module 20.3.19 → 20.3.21

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,3085 @@ 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-o]=\"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-o]=\"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", "Solo", ["Only"]);
1024
+ super.set("it->en", "Dal", ["From"]);
1025
+ super.set("it->en", "Al", ["To"]);
1026
+ super.set("it->en", "Valori", ["Values"]);
1027
+ super.set("it->en", "Dall'avvio", ["Since start"]);
1028
+ super.set("it->en", "Dall'ultimo snapshot", ["Since last snapshot"]);
1029
+ super.set("it->en", "Aggiorna", ["Refresh"]);
1030
+ super.set("it->en", "Scarica il workload report", ["Download the workload report"]);
1031
+ super.set("it->en", "Workload report", ["Workload report"]);
1032
+ super.set("it->en", "Delta dall'ultimo snapshot delle", ["Delta since the last snapshot at"]);
1033
+ super.set("it->en", "min fa", ["min ago"]);
1034
+ super.set("it->en", "s fa", ["s ago"]);
1035
+ super.set("it->en", "mai", ["never"]);
1036
+ super.set("it->en", "Snapshot dalle", ["Snapshots from"]);
1037
+ super.set("it->en", "alle", ["to"]);
1038
+ super.set("it->en", "Nessuno snapshot termina nella finestra scelta", ["No snapshot ends in the chosen window"]);
1039
+ super.set("it->en", "nodi nell'albero", ["nodes in the tree"]);
1040
+ super.set("it->en", "nella risposta", ["in the response"]);
1041
+ super.set("it->en", "eventi distinti", ["distinct events"]);
1042
+ super.set("it->en", "Caricamento…", ["Loading…"]);
1043
+ // Vista e ordinamenti
1044
+ super.set("it->en", "Albero", ["Tree"]);
1045
+ super.set("it->en", "Per nome", ["By name"]);
1046
+ super.set("it->en", "Ordina per", ["Sort by"]);
1047
+ super.set("it->en", "Tempo", ["Time"]);
1048
+ super.set("it->en", "Tempo proprio", ["Self time"]);
1049
+ super.set("it->en", "Chiamate", ["Calls"]);
1050
+ super.set("it->en", "Tempo medio", ["Average time"]);
1051
+ super.set("it->en", "Picco", ["Peak"]);
1052
+ super.set("it->en", "Δ Tempo", ["Δ Time"]);
1053
+ super.set("it->en", "Δ Tempo proprio", ["Δ Self time"]);
1054
+ super.set("it->en", "Δ Chiamate", ["Δ Calls"]);
1055
+ super.set("it->en", "Thread attivi", ["Active threads"]);
1056
+ super.set("it->en", "Tutti", ["All"]);
1057
+ super.set("it->en", "Nessuna", ["None"]);
1058
+ // Albero
1059
+ super.set("it->en", "Cerca nei nomi o per id", ["Search names or by id"]);
1060
+ super.set("it->en", "Per esempio MRD_METERREADINGS, ENQUEUE, ProfilesMgr", ["For example MRD_METERREADINGS, ENQUEUE, ProfilesMgr"]);
1061
+ super.set("it->en", "Figli per nodo", ["Children per node"]);
1062
+ super.set("it->en", "Soglia", ["Threshold"]);
1063
+ super.set("it->en", "Chiudi", ["Close"]);
1064
+ 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."]);
1065
+ super.set("it->en", "nodi trovati, con i loro antenati.", ["nodes found, with their ancestors."]);
1066
+ 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."]);
1067
+ super.set("it->en", "Nessun nodo contiene", ["No node contains"]);
853
1068
  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"]);
1069
+ super.set("it->en", "% del padre", ["% of parent"]);
1070
+ super.set("it->en", "Δ Proprio", ["Δ Self"]);
1071
+ super.set("it->en", "Medio", ["Average"]);
1072
+ super.set("it->en", "Thread ora", ["Threads now"]);
1073
+ super.set("it->en", "del padre", ["of parent"]);
1074
+ super.set("it->en", "del nodo di primo livello", ["of the first-level node"]);
1075
+ super.set("it->en", "Esecuzione in corso da", ["Execution running for"]);
1076
+ super.set("it->en", "forse bloccata", ["possibly stuck"]);
1077
+ super.set("it->en", "Caricamento dei figli…", ["Loading children…"]);
1078
+ super.set("it->en", "figli non mostrati", ["children not shown"]);
1079
+ super.set("it->en", "chiamate", ["calls"]);
1080
+ super.set("it->en", "Mostra tutti", ["Show all"]);
1081
+ 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."]);
1082
+ super.set("it->en", "Il nodo non c'è nelle istanze scelte.", ["The node is not in the chosen instances."]);
1083
+ // Eventi per nome
1084
+ super.set("it->en", "Cerca nei nomi", ["Search names"]);
867
1085
  super.set("it->en", "Tipo", ["Type"]);
868
- super.set("it->en", "Utente", ["User"]);
1086
+ super.set("it->en", "Righe", ["Rows"]);
1087
+ super.set("it->en", "Anche gli eventi idle", ["Include idle events"]);
1088
+ super.set("it->en", "Evento", ["Event"]);
1089
+ super.set("it->en", "Percorsi", ["Paths"]);
1090
+ super.set("it->en", "% proprio", ["% self"]);
1091
+ super.set("it->en", "Picco dall'avvio", ["Peak since start"]);
1092
+ super.set("it->en", "Thread", ["Threads"]);
1093
+ super.set("it->en", "Nodi con più tempo", ["Nodes with the most time"]);
1094
+ super.set("it->en", "Dettaglio", ["Details"]);
1095
+ super.set("it->en", "Apri nell'albero", ["Open in tree"]);
1096
+ super.set("it->en", "altri", ["more"]);
1097
+ super.set("it->en", "Nessun evento con questi filtri.", ["No events match these filters."]);
1098
+ super.set("it->en", "mostrati i primi", ["showing the first"]);
1099
+ 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."]);
1100
+ // Dettaglio
1101
+ super.set("it->en", "Seleziona un nodo per vederne il dettaglio.", ["Select a node to see its details."]);
1102
+ super.set("it->en", "Il nodo non c'è nelle istanze o nella finestra scelte.", ["The node is not in the chosen instances or window."]);
1103
+ super.set("it->en", "Copia", ["Copy"]);
1104
+ super.set("it->en", "Copiato", ["Copied"]);
1105
+ super.set("it->en", "Percorso", ["Path"]);
1106
+ super.set("it->en", "Nella finestra", ["In window"]);
1107
+ super.set("it->en", "Esecuzioni", ["Executions"]);
1108
+ super.set("it->en", "Tempo totale", ["Total time"]);
1109
+ super.set("it->en", "Minimo", ["Minimum"]);
1110
+ super.set("it->en", "Adesso", ["Now"]);
1111
+ super.set("it->en", "Thread dentro", ["Threads inside"]);
1112
+ super.set("it->en", "Esecuzione più lunga", ["Longest execution"]);
1113
+ super.set("it->en", "Ultima uscita", ["Last exit"]);
1114
+ super.set("it->en", "Tempo proprio del sottoalbero per tipo", ["Subtree self time by type"]);
1115
+ super.set("it->en", "Sessioni attive sul nodo e sui discendenti", ["Active sessions on node and descendants"]);
1116
+ super.set("it->en", "Media", ["Average"]);
1117
+ super.set("it->en", "picco", ["peak"]);
1118
+ super.set("it->en", "sessioni", ["sessions"]);
1119
+ super.set("it->en", "punti dell'API", ["API points"]);
1120
+ super.set("it->en", "uno al secondo", ["one per second"]);
1121
+ super.set("it->en", "uno ogni", ["one every"]);
1122
+ super.set("it->en", "raggruppati a", ["grouped by"]);
1123
+ 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."]);
1124
+ super.set("it->en", "Per istanza", ["By instance"]);
1125
+ super.set("it->en", "Istanza", ["Instance"]);
1126
+ super.set("it->en", "Proprio", ["Self"]);
1127
+ super.set("it->en", "figli diretti", ["direct children"]);
1128
+ super.set("it->en", "discendenti", ["descendants"]);
1129
+ super.set("it->en", "Apri come radice", ["Open as root"]);
869
1130
  }
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 }); }
1131
+ 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 }); }
1132
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerV2Loc }); }
872
1133
  }
873
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivitieLoc, decorators: [{
1134
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerV2Loc, decorators: [{
874
1135
  type: Injectable
875
1136
  }], ctorParameters: () => [{ type: i0.Injector }] });
876
1137
 
877
1138
  // Angular
878
- class AdmTopActivitiesComponent {
879
- constructor(dateExts, lc, localStorage, location, intercom, route, adms, mod) {
1139
+ const STORAGE_KEY$3 = "HotSearchProfilerV2";
1140
+ const INSTANCE_THIS = "@this";
1141
+ const INSTANCE_ALL$1 = "@all";
1142
+ // All'apertura la radice con due livelli (R6), poi i figli si chiedono un livello alla volta
1143
+ const TREE_DEPTH = 2;
1144
+ const SEARCH_MIN_CHARS = 2;
1145
+ const SEARCH_DELAY_MS = 400;
1146
+ const DATES_DELAY_MS$1 = 600;
1147
+ // Muovendosi con la tastiera il dettaglio si chiede dopo una breve pausa, non a ogni tasto
1148
+ const DETAIL_DELAY_MS = 180;
1149
+ // Esecuzione in corso da più di così: forse bloccata (R15)
1150
+ const LONG_RUNNING_SECONDS = 60;
1151
+ const CHART_POINTS = 160;
1152
+ // Ogni tipo di evento ha un colore fisso, gli stessi della Top Activity v2. Wait e sleep di classe Idle usano quello di Idle
1153
+ const TYPE_COLORS = {
1154
+ Idle: "#bdbdbd", Cpu: "#43a047", Database: "#1e88e5", Wait: "#fdd835", Script: "#8e24aa", Network: "#8d6e63", IO: "#00acc1", Sleep: "#90a4ae", Throttle: "#fb8c00"
1155
+ };
1156
+ const TYPE_LABELS = {
1157
+ Idle: "Idle", Cpu: "CPU", Database: "DB", Wait: "Wait", Script: "Script", Network: "Net", IO: "I/O", Sleep: "Sleep", Throttle: "Throttle"
1158
+ };
1159
+ const EVENT_TYPES = ["Cpu", "Database", "Wait", "Script", "Network", "IO", "Sleep", "Throttle"];
1160
+ // Passando ai valori dall'ultimo snapshot l'ordinamento passa alla chiave corrispondente, e viceversa (R4)
1161
+ const TO_SNAP = {
1162
+ [ProfilerOrderBy.Time]: ProfilerOrderBy.SnapDeltaTime,
1163
+ [ProfilerOrderBy.TimeLessChildren]: ProfilerOrderBy.SnapDeltaTimeLessChildren,
1164
+ [ProfilerOrderBy.Calls]: ProfilerOrderBy.SnapDeltaCalls
1165
+ };
1166
+ const TO_BASE = {
1167
+ [ProfilerOrderBy.SnapDeltaTime]: ProfilerOrderBy.Time,
1168
+ [ProfilerOrderBy.SnapDeltaTimeLessChildren]: ProfilerOrderBy.TimeLessChildren,
1169
+ [ProfilerOrderBy.SnapDeltaCalls]: ProfilerOrderBy.Calls
1170
+ };
1171
+ class ProfilerV2Row {
1172
+ }
1173
+ class ProfilerV2TypePart {
1174
+ }
1175
+ class ProfilerV2Chart {
1176
+ }
1177
+ // Preferenze ricordate fra una visita e l'altra (sorgente, istanze, finestra e radice ripartono dai default)
1178
+ class ProfilerV2Settings {
1179
+ }
1180
+ class AdmProfilerV2Component {
1181
+ constructor(dateExts, lc, localStorage, intercom, route, router, location, adms, mod, workloadV2) {
880
1182
  this.dateExts = dateExts;
881
1183
  this.lc = lc;
882
1184
  this.localStorage = localStorage;
883
- this.location = location;
884
1185
  this.intercom = intercom;
885
1186
  this.route = route;
1187
+ this.router = router;
1188
+ this.location = location;
886
1189
  this.adms = adms;
887
1190
  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 = {};
1191
+ this.workloadV2 = workloadV2;
1192
+ this.EventTypes = EVENT_TYPES;
1193
+ // Barra dei comandi
1194
+ this.live = true;
1195
+ // Di default tutto il cluster, come nella Top Activity v2
1196
+ this.instance = INSTANCE_ALL$1;
1197
+ this.filterfrom = "";
1198
+ this.filterto = "";
1199
+ // Valori dall'ultimo snapshot invece che dall'avvio (solo live)
1200
+ this.snapValues = false;
1201
+ this.view = "tree";
1202
+ // Albero
1203
+ this.orderBy = String(ProfilerOrderBy.Time);
1204
+ // "0" = tutti, nessuna soglia
1205
+ this.maxChildren = "25";
1206
+ this.minPercentage = "0";
1207
+ this.searchText = "";
1208
+ this.activeSearch = "";
1209
+ this.rows = [];
1210
+ this.treeLoading = false;
1211
+ this.matchCount = 0;
1212
+ // Eventi per nome
1213
+ this.flatSearch = "";
1214
+ this.flatType = "";
1215
+ this.flatOrderBy = String(ProfilerOrderBy.TimeLessChildren);
1216
+ this.flatTop = "100";
1217
+ this.flatIncludeIdle = false;
1218
+ this.flatLoading = false;
1219
+ this.openFlat = new Set();
1220
+ this.detailLoading = false;
1221
+ this.detailMissing = false;
1222
+ this.typeParts = [];
1223
+ this.copied = false;
1224
+ // Sorgenti di select e pulsanti
1225
+ this.Instances = [];
1226
+ this.TreeOrders = [];
1227
+ this.FlatOrders = [];
1228
+ this.TreeOrderOptions = [];
1229
+ this.FlatOrderOptions = [];
1230
+ this.ChildrenOptions = [];
1231
+ this.ThresholdOptions = [];
1232
+ this.FlatTypes = [];
1233
+ this.FlatRows = [];
1234
+ this.instanceIds = [];
1235
+ this.rootId = null;
1236
+ this.nodes = new Map();
1237
+ this.parents = new Map();
1238
+ this.expanded = new Set();
1239
+ // Nodi di cui children è completo per i parametri correnti, anche se vuoto (per esempio tutti sotto la soglia)
1240
+ this.loaded = new Set();
1241
+ this.loadingChildren = new Set();
1242
+ // Una risposta arrivata dopo un ricaricamento successivo si scarta
1243
+ this.treeGeneration = 0;
1244
+ this.flatGeneration = 0;
1245
+ this.detailGeneration = 0;
905
1246
  this.be = route.snapshot.params["be"] || "";
906
1247
  this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
907
1248
  }
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
- });
1249
+ async ngOnInit() {
1250
+ this.buildSources();
1251
+ let saved = this.route.snapshot.queryParams["ignoreStorage"] ? null : await firstValueFrom(this.localStorage.getItem(STORAGE_KEY$3), { defaultValue: null });
1252
+ if (saved)
1253
+ this.applySettings(saved);
1254
+ // Gli id sono stabili: la radice può stare nell'indirizzo della pagina
1255
+ this.rootId = this.route.snapshot.queryParams["root"] || null;
1256
+ this.loadInstances();
1257
+ this.loadTree(false);
916
1258
  }
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;
1259
+ ngOnDestroy() {
1260
+ clearTimeout(this.searchTimer);
1261
+ clearTimeout(this.flatSearchTimer);
1262
+ clearTimeout(this.datesTimer);
1263
+ clearTimeout(this.detailTimer);
1264
+ }
1265
+ // #region Barra dei comandi
1266
+ setLive(live) {
1267
+ if (this.live == live)
1268
+ return;
1269
+ this.live = live;
1270
+ if (!live) {
1271
+ // Sullo storico non ci sono i valori dall'ultimo snapshot né i thread attivi (R30)
1272
+ this.snapValues = false;
1273
+ this.orderBy = String(+this.orderBy == ProfilerOrderBy.ActiveThreads ? ProfilerOrderBy.Time : TO_BASE[+this.orderBy] ?? +this.orderBy);
1274
+ this.flatOrderBy = String(+this.flatOrderBy == ProfilerOrderBy.ActiveThreads ? ProfilerOrderBy.TimeLessChildren : TO_BASE[+this.flatOrderBy] ?? +this.flatOrderBy);
1275
+ // Default: le quattro ore che finiscono all'ultimo snapshot
1276
+ if (!this.filterfrom || !this.filterto) {
1277
+ let lastSnapshot = this.parseUtc((this.view == "tree" ? this.tree?.snapFrom : this.flat?.snapFrom) ?? null) ?? this.floorHour(new Date());
1278
+ this.filterto = this.fmt(lastSnapshot);
1279
+ this.filterfrom = this.fmt(new Date(lastSnapshot.getTime() - 4 * 3600 * 1000));
932
1280
  }
933
1281
  }
1282
+ this.updateOrderOptions();
1283
+ this.reloadAll();
1284
+ }
1285
+ setInstance(id) {
1286
+ this.instance = id;
1287
+ this.reloadAll();
1288
+ }
1289
+ datesChanged() {
1290
+ clearTimeout(this.datesTimer);
1291
+ this.datesTimer = setTimeout(() => {
1292
+ if (this.toDate(this.filterfrom) && this.toDate(this.filterto))
1293
+ this.reloadAll();
1294
+ }, DATES_DELAY_MS$1);
1295
+ }
1296
+ // Cambia colonne e intestazioni; richiama il server solo se cambia anche l'ordinamento
1297
+ setSnapValues(on) {
1298
+ if (this.snapValues == on)
1299
+ return;
1300
+ this.snapValues = on;
1301
+ let map = on ? TO_SNAP : TO_BASE;
1302
+ let treeOrder = String(map[+this.orderBy] ?? +this.orderBy);
1303
+ // La vista per nome non propone Δ Tempo: lì Tempo resta Tempo
1304
+ let flatMapped = map[+this.flatOrderBy];
1305
+ let flatOrder = String(flatMapped != null && flatMapped != ProfilerOrderBy.SnapDeltaTime ? flatMapped : +this.flatOrderBy);
1306
+ let reload = this.view == "tree" ? treeOrder != this.orderBy : flatOrder != this.flatOrderBy;
1307
+ this.orderBy = treeOrder;
1308
+ this.flatOrderBy = flatOrder;
1309
+ this.saveSettings();
1310
+ if (reload)
1311
+ this.reloadView();
1312
+ }
1313
+ setView(view) {
1314
+ if (this.view == view)
1315
+ return;
1316
+ this.view = view;
1317
+ this.reloadView();
1318
+ }
1319
+ // Aggiorna: la vista corrente e il dettaglio aperto (niente aggiornamento automatico, R5)
1320
+ reloadAll() {
1321
+ this.reloadView();
1322
+ if (this.selectedId)
1323
+ this.loadDetail(this.selectedId);
1324
+ }
1325
+ reloadView() {
1326
+ if (this.view == "tree")
1327
+ this.loadTree(true);
1328
+ else
1329
+ this.loadFlat();
934
1330
  }
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);
1331
+ // Con DirectorFeature.WorkloadReportV2 il report non è più un file da scaricare: ha la sua pagina
1332
+ downloadWorkloadReport() {
1333
+ if (this.workloadV2) {
1334
+ this.router.navigate([`/pages/${this.mod}/workload`, ...(this.be ? [this.be] : [])]);
1335
+ return;
942
1336
  }
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();
1337
+ let from = this.live ? "" : this.filterfrom;
1338
+ let to = this.live ? "" : this.filterto;
1339
+ this.adms.downloadWorkloadReport(this.be, from, to, this.instance != INSTANCE_THIS);
1340
+ }
1341
+ get instanceLabel() {
1342
+ return this.Instances.find(t => t.id == this.instance)?.description ?? "";
1343
+ }
1344
+ get loading() {
1345
+ return this.view == "tree" ? this.treeLoading : this.flatLoading;
1346
+ }
1347
+ // Vista corrente, per la riga della finestra
1348
+ get current() {
1349
+ return this.view == "tree" ? this.tree : this.flat;
1350
+ }
1351
+ baseRequest(req) {
1352
+ req.realTime = this.live;
1353
+ // Dietro un bilanciatore "questa istanza" cambia da una chiamata all'altra: si manda sempre l'istanza esplicita o global (R3)
1354
+ if (this.instance == INSTANCE_ALL$1)
1355
+ req.global = true;
1356
+ else if (this.instance != INSTANCE_THIS)
1357
+ req.instanceId = this.instance;
1358
+ else if (this.instanceId)
1359
+ req.instanceId = this.instanceId;
1360
+ if (!this.live) {
1361
+ req.snapFrom = this.toDate(this.filterfrom)?.toISOString();
1362
+ req.snapTo = this.toDate(this.filterto)?.toISOString();
948
1363
  }
1364
+ return req;
949
1365
  }
950
- init(HotPotato) {
951
- if (HotPotato) {
952
- this.searchView = HotPotato;
953
- this.POSTGetItems(this.searchView, false);
954
- }
1366
+ // #endregion
1367
+ // #region Albero
1368
+ setOrderBy(id) {
1369
+ this.orderBy = id;
1370
+ this.saveSettings();
1371
+ this.loadTree(true);
1372
+ }
1373
+ setMaxChildren(id) {
1374
+ this.maxChildren = id;
1375
+ this.saveSettings();
1376
+ this.loadTree(true);
1377
+ }
1378
+ setMinPercentage(id) {
1379
+ this.minPercentage = id;
1380
+ this.saveSettings();
1381
+ this.loadTree(true);
1382
+ }
1383
+ searchChanged() {
1384
+ clearTimeout(this.searchTimer);
1385
+ this.searchTimer = setTimeout(() => {
1386
+ let text = (this.searchText ?? "").trim();
1387
+ let next = text.length >= SEARCH_MIN_CHARS ? text : "";
1388
+ if (next == this.activeSearch)
1389
+ return;
1390
+ // Svuotando la ricerca si torna all'albero di prima, con gli stessi nodi aperti
1391
+ if (!this.activeSearch)
1392
+ this.expandedBeforeSearch = new Set(this.expanded);
1393
+ else if (!next)
1394
+ this.expanded = this.expandedBeforeSearch ?? new Set();
1395
+ this.activeSearch = next;
1396
+ this.loadTree(!next);
1397
+ }, SEARCH_DELAY_MS);
1398
+ }
1399
+ clearSearch() {
1400
+ this.searchText = "";
1401
+ this.searchChanged();
1402
+ }
1403
+ // Nodo come radice (R13); Idle si chiede senza rootNodeId
1404
+ setRoot(id) {
1405
+ this.rootId = id && id != this.rootPathIdle ? id : null;
1406
+ this.searchText = "";
1407
+ this.activeSearch = "";
1408
+ this.expanded.clear();
1409
+ this.treeNotice = null;
1410
+ this.view = "tree";
1411
+ this.updateUrl();
1412
+ this.loadTree(false);
1413
+ }
1414
+ get rootPathIdle() {
1415
+ let root = this.tree?.idleNode;
1416
+ return root?.level == 0 ? root.nodeId : this.tree?.rootPath?.[0]?.nodeId;
1417
+ }
1418
+ async loadTree(keepExpanded) {
1419
+ let generation = ++this.treeGeneration;
1420
+ let searching = !!this.activeSearch;
1421
+ let previous = keepExpanded ? [...this.expanded] : [];
1422
+ let req = this.baseRequest(new ProfilerV2TreeRequest());
1423
+ req.orderBy = +this.orderBy;
1424
+ if (this.rootId)
1425
+ req.rootNodeId = this.rootId;
1426
+ if (searching)
1427
+ req.search = this.activeSearch;
955
1428
  else {
956
- this.searchView = new TopActivitiesDataView();
957
- this.sendSearch();
1429
+ req.maxDepth = TREE_DEPTH;
1430
+ if (+this.maxChildren > 0)
1431
+ req.maxChildren = +this.maxChildren;
1432
+ if (+this.minPercentage > 0)
1433
+ req.minPercentage = +this.minPercentage;
958
1434
  }
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();
1435
+ this.treeLoading = true;
1436
+ this.treeError = null;
1437
+ try {
1438
+ let res = await this.adms.getProfilerTreeV2(this.be, req);
1439
+ if (generation != this.treeGeneration)
1440
+ return;
1441
+ // Radice non trovata (R23): lo si dice e si torna a Idle
1442
+ if (res && !res.idleNode && res.snapFrom && this.rootId) {
1443
+ this.rootId = null;
1444
+ this.updateUrl();
1445
+ this.treeNotice = this.lc.loc("Il nodo non c'è nelle istanze scelte: l'albero riparte da Idle.");
1446
+ this.loadTree(false);
1447
+ return;
1448
+ }
1449
+ this.tree = res;
1450
+ this.nodes.clear();
1451
+ this.parents.clear();
1452
+ this.loaded.clear();
1453
+ this.loadingChildren.clear();
1454
+ // I nodi che erano aperti restano aperti: quelli che non sono ancora arrivati si riaprono quando arrivano i figli del padre
1455
+ this.expanded = new Set(searching ? [] : previous);
1456
+ let root = res?.idleNode;
1457
+ if (root) {
1458
+ this.ingest(root, null, searching ? null : TREE_DEPTH, searching);
1459
+ this.expanded.add(root.nodeId);
1460
+ }
1461
+ if (searching) {
1462
+ // L'albero della ricerca contiene solo i percorsi dei nodi trovati e si mostra tutto aperto
1463
+ this.nodes.forEach(t => { if (t.children?.length)
1464
+ this.expanded.add(t.nodeId); });
1465
+ this.matchCount = [...this.nodes.values()].filter(t => t.searchMatch).length;
1466
+ }
1467
+ this.buildRows();
1468
+ this.nodes.forEach(t => this.reopen(t, generation));
1469
+ }
1470
+ catch (err) {
1471
+ if (generation == this.treeGeneration)
1472
+ this.treeError = this.errorMessage(err);
1473
+ }
1474
+ finally {
1475
+ if (generation == this.treeGeneration)
1476
+ this.treeLoading = false;
983
1477
  }
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
1478
  }
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;
1479
+ ingest(node, parent, depthLeft, partial) {
1480
+ this.nodes.set(node.nodeId, node);
1481
+ if (parent)
1482
+ this.parents.set(node.nodeId, parent);
1483
+ // Oltre il limite di profondità, o nella ricerca, i figli non sono tutti: si chiedono quando il nodo si apre
1484
+ if (!partial && (depthLeft == null || depthLeft > 0))
1485
+ this.loaded.add(node.nodeId);
1486
+ for (let child of node.children ?? [])
1487
+ this.ingest(child, node, depthLeft == null ? null : depthLeft - 1, partial);
1488
+ }
1489
+ // Un nodo aperto senza figli caricati (dopo un ricaricamento o "Mostra tutti" del padre) li chiede
1490
+ reopen(node, generation) {
1491
+ if (this.expanded.has(node.nodeId) && this.needsChildren(node) && !this.loadingChildren.has(node.nodeId))
1492
+ this.loadChildren(node, false, generation);
1493
+ }
1494
+ hasChildren(node) {
1495
+ return node.numChildren > 0;
1496
+ }
1497
+ isExpanded(node) {
1498
+ return this.expanded.has(node.nodeId);
1499
+ }
1500
+ needsChildren(node) {
1501
+ return node.numChildren > 0 && !node.children?.length && !this.loaded.has(node.nodeId);
1502
+ }
1503
+ toggle(node, event) {
1504
+ event?.stopPropagation();
1505
+ if (!this.hasChildren(node))
1506
+ return;
1507
+ if (this.expanded.has(node.nodeId)) {
1508
+ this.expanded.delete(node.nodeId);
1509
+ this.buildRows();
1003
1510
  return;
1004
1511
  }
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
- }
1512
+ this.expanded.add(node.nodeId);
1513
+ if (this.needsChildren(node))
1514
+ this.loadChildren(node, false);
1515
+ else
1516
+ this.buildRows();
1517
+ }
1518
+ // R9: la stessa chiamata dei figli senza maxChildren e minPercentage
1519
+ showAll(node, event) {
1520
+ event.stopPropagation();
1521
+ this.loadChildren(node, true);
1522
+ }
1523
+ // R8: i figli di un nodo, che sostituiscono children e omitted* del nodo
1524
+ async loadChildren(node, all, generation = this.treeGeneration) {
1525
+ let req = this.baseRequest(new ProfilerV2TreeRequest());
1526
+ req.rootNodeId = node.nodeId;
1527
+ req.maxDepth = 1;
1528
+ req.orderBy = +this.orderBy;
1529
+ if (!all && +this.maxChildren > 0)
1530
+ req.maxChildren = +this.maxChildren;
1531
+ if (!all && +this.minPercentage > 0)
1532
+ req.minPercentage = +this.minPercentage;
1533
+ this.loadingChildren.add(node.nodeId);
1534
+ this.buildRows();
1535
+ try {
1536
+ let res = await this.adms.getProfilerTreeV2(this.be, req);
1537
+ if (generation != this.treeGeneration)
1538
+ return;
1539
+ let fresh = res?.idleNode;
1540
+ node.children = fresh?.children ?? [];
1541
+ node.numChildren = fresh?.numChildren ?? node.children.length;
1542
+ node.omittedChildren = fresh?.omittedChildren ?? 0;
1543
+ node.omittedCalls = fresh?.omittedCalls ?? 0;
1544
+ node.omittedTime = fresh?.omittedTime ?? 0;
1545
+ node.omittedSnapDeltaTime = fresh?.omittedSnapDeltaTime ?? 0;
1546
+ for (let child of node.children)
1547
+ this.ingest(child, node, 0, false);
1548
+ this.loaded.add(node.nodeId);
1549
+ node.children.forEach(t => this.reopen(t, generation));
1013
1550
  }
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]]));
1551
+ catch (err) {
1552
+ if (generation == this.treeGeneration) {
1553
+ this.expanded.delete(node.nodeId);
1554
+ this.treeError = this.errorMessage(err);
1555
+ }
1020
1556
  }
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];
1557
+ finally {
1558
+ if (generation == this.treeGeneration) {
1559
+ this.loadingChildren.delete(node.nodeId);
1560
+ this.buildRows();
1026
1561
  }
1027
1562
  }
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];
1563
+ }
1564
+ buildRows() {
1565
+ let rows = [];
1566
+ if (this.tree?.idleNode)
1567
+ this.addRows(rows, this.tree.idleNode, 0);
1568
+ this.rows = rows;
1569
+ }
1570
+ addRows(rows, node, depth) {
1571
+ rows.push({ kind: "node", node: node, depth: depth, key: node.nodeId });
1572
+ if (!this.expanded.has(node.nodeId))
1573
+ return;
1574
+ let loading = this.loadingChildren.has(node.nodeId);
1575
+ if (loading)
1576
+ rows.push({ kind: "loading", node: node, depth: depth + 1, key: node.nodeId + "#loading" });
1577
+ for (let child of node.children ?? [])
1578
+ this.addRows(rows, child, depth + 1);
1579
+ if (!loading && node.omittedChildren > 0 && !this.needsChildren(node))
1580
+ rows.push({ kind: "omitted", node: node, depth: depth + 1, key: node.nodeId + "#omitted" });
1581
+ }
1582
+ // Clic: selezione e dettaglio. Doppio clic: apre o chiude
1583
+ rowClicked(node) {
1584
+ this.select(node.nodeId);
1585
+ }
1586
+ // Su e giù cambiano nodo, destra apre (o scende al primo figlio), sinistra chiude (o sale al padre)
1587
+ treeKeydown(event) {
1588
+ let nodeRows = this.rows.filter(t => t.kind == "node");
1589
+ let index = nodeRows.findIndex(t => t.node.nodeId == this.selectedId);
1590
+ let current = index >= 0 ? nodeRows[index].node : null;
1591
+ let target = null;
1592
+ switch (event.key) {
1593
+ case "ArrowDown":
1594
+ target = nodeRows[Math.min(nodeRows.length - 1, index + 1)]?.node;
1595
+ break;
1596
+ case "ArrowUp":
1597
+ target = nodeRows[Math.max(0, index - 1)]?.node;
1598
+ break;
1599
+ case "ArrowRight":
1600
+ if (current && this.hasChildren(current) && !this.isExpanded(current))
1601
+ this.toggle(current);
1602
+ else if (current && this.isExpanded(current))
1603
+ target = current.children?.[0];
1604
+ break;
1605
+ case "ArrowLeft":
1606
+ if (current && this.isExpanded(current) && this.hasChildren(current))
1607
+ this.toggle(current);
1608
+ else if (current)
1609
+ target = this.parents.get(current.nodeId);
1610
+ break;
1611
+ default:
1612
+ return;
1613
+ }
1614
+ event.preventDefault();
1615
+ if (target) {
1616
+ this.select(target.nodeId, true);
1617
+ setTimeout(() => document.getElementById("pv2-row-" + target.nodeId)?.scrollIntoView({ block: "nearest" }));
1031
1618
  }
1032
1619
  }
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);
1620
+ isIdle(type, waitClass) {
1621
+ return type == "Idle" || ((type == "Wait" || type == "Sleep") && waitClass == "Idle");
1622
+ }
1623
+ // Esecuzione in corso da più di un minuto su un nodo non idle (solo live)
1624
+ isLongRunning(type, waitClass, activeMaxTime) {
1625
+ return this.live && !this.isIdle(type, waitClass) && (activeMaxTime ?? 0) >= LONG_RUNNING_SECONDS;
1626
+ }
1627
+ // Con la ricerca la parte del nome che corrisponde è evidenziata
1628
+ nameParts(name, search) {
1629
+ if (!search || !name)
1630
+ return [{ text: name ?? "", hit: false }];
1631
+ let parts = [];
1632
+ let lower = name.toLowerCase();
1633
+ let needle = search.toLowerCase();
1634
+ let from = 0;
1635
+ let at = lower.indexOf(needle);
1636
+ while (at >= 0) {
1637
+ if (at > from)
1638
+ parts.push({ text: name.substring(from, at), hit: false });
1639
+ parts.push({ text: name.substring(at, at + needle.length), hit: true });
1640
+ from = at + needle.length;
1641
+ at = lower.indexOf(needle, from);
1040
1642
  }
1041
- else
1643
+ if (from < name.length)
1644
+ parts.push({ text: name.substring(from), hit: false });
1645
+ return parts;
1646
+ }
1647
+ // #endregion
1648
+ // #region Valori secondo la modalità (dall'avvio o dall'ultimo snapshot)
1649
+ get snapMode() {
1650
+ return this.live && this.snapValues;
1651
+ }
1652
+ calls(node) { return this.snapMode ? node.snapDeltaCalls : node.calls; }
1653
+ time(node) { return this.snapMode ? node.snapDeltaTime : node.time; }
1654
+ self(node) { return this.snapMode ? node.snapDeltaTimeLessChildren : node.timeLessChildren; }
1655
+ avg(node) { return this.snapMode ? node.snapDeltaAvgTime : node.avgTime; }
1656
+ peak(node) { return this.snapMode ? node.snapPeakTime : node.peakTime; }
1657
+ parentPerc(node) { return this.snapMode ? node.snapParentPercentage : node.parentPercentage; }
1658
+ firstLevelPerc(node) { return this.snapMode ? node.snapPercentage : node.percentage; }
1659
+ omittedTime(node) { return this.snapMode ? node.omittedSnapDeltaTime : node.omittedTime; }
1660
+ flatCalls(item) { return this.snapMode ? item.snapDeltaCalls : item.calls; }
1661
+ flatSelf(item) { return this.snapMode ? item.snapDeltaTimeLessChildren : item.timeLessChildren; }
1662
+ flatPerc(item) { return this.snapMode ? item.snapPercentage : item.percentage; }
1663
+ flatTime(item) { return this.snapMode ? item.snapDeltaTime : item.time; }
1664
+ // La vista per nome non ha il medio dei delta: si calcola
1665
+ flatAvg(item) { return this.snapMode ? (item.snapDeltaCalls > 0 ? item.snapDeltaTime / item.snapDeltaCalls : null) : item.avgTime; }
1666
+ // #endregion
1667
+ // #region Eventi per nome
1668
+ flatChanged() {
1669
+ this.saveSettings();
1670
+ this.loadFlat();
1671
+ }
1672
+ setFlatTop(id) {
1673
+ this.flatTop = id;
1674
+ this.flatChanged();
1675
+ }
1676
+ flatSearchChanged() {
1677
+ clearTimeout(this.flatSearchTimer);
1678
+ this.flatSearchTimer = setTimeout(() => this.loadFlat(), SEARCH_DELAY_MS);
1679
+ }
1680
+ async loadFlat() {
1681
+ let generation = ++this.flatGeneration;
1682
+ let req = this.baseRequest(new ProfilerV2FlatRequest());
1683
+ req.orderBy = +this.flatOrderBy;
1684
+ req.top = +this.flatTop;
1685
+ let search = (this.flatSearch ?? "").trim();
1686
+ if (search)
1687
+ req.search = search;
1688
+ if (this.flatType)
1689
+ req.eventType = this.flatType;
1690
+ if (this.flatIncludeIdle)
1691
+ req.includeIdle = true;
1692
+ this.flatLoading = true;
1693
+ this.flatError = null;
1694
+ try {
1695
+ let res = await this.adms.getProfilerFlatV2(this.be, req);
1696
+ if (generation != this.flatGeneration)
1697
+ return;
1698
+ this.flat = res;
1699
+ this.openFlat.clear();
1700
+ }
1701
+ catch (err) {
1702
+ if (generation == this.flatGeneration)
1703
+ this.flatError = this.errorMessage(err);
1704
+ }
1705
+ finally {
1706
+ if (generation == this.flatGeneration)
1707
+ this.flatLoading = false;
1708
+ }
1709
+ }
1710
+ flatKey(item) {
1711
+ return `${item.eventTypeName}|${item.waitClassName}|${item.nodeName}`;
1712
+ }
1713
+ toggleFlat(item) {
1714
+ let key = this.flatKey(item);
1715
+ if (this.openFlat.has(key))
1716
+ this.openFlat.delete(key);
1717
+ else
1718
+ this.openFlat.add(key);
1719
+ }
1720
+ // R20: dall'elenco dei nodi di un evento, apri nell'albero
1721
+ openInTree(nodeId, event) {
1722
+ event.stopPropagation();
1723
+ this.setRoot(nodeId);
1724
+ this.select(nodeId);
1725
+ }
1726
+ showDetail(nodeId, event) {
1727
+ event.stopPropagation();
1728
+ this.select(nodeId);
1729
+ }
1730
+ // #endregion
1731
+ // #region Dettaglio
1732
+ select(nodeId, delayed = false) {
1733
+ clearTimeout(this.detailTimer);
1734
+ if (nodeId == this.selectedId && (this.detailLoading || this.detail?.nodeId == nodeId))
1735
+ return;
1736
+ this.selectedId = nodeId;
1737
+ if (delayed)
1738
+ this.detailTimer = setTimeout(() => this.loadDetail(nodeId), DETAIL_DELAY_MS);
1739
+ else
1740
+ this.loadDetail(nodeId);
1741
+ }
1742
+ async loadDetail(nodeId) {
1743
+ // Se l'utente seleziona un altro nodo prima della risposta, quella vecchia si scarta (R18)
1744
+ let generation = ++this.detailGeneration;
1745
+ let req = this.baseRequest(new ProfilerV2NodeRequest());
1746
+ req.nodeId = nodeId;
1747
+ this.detailLoading = true;
1748
+ this.detailError = null;
1749
+ this.detailMissing = false;
1750
+ try {
1751
+ let res = await this.adms.getProfilerNodeV2(this.be, req);
1752
+ if (generation != this.detailGeneration)
1753
+ return;
1754
+ this.detail = res;
1755
+ this.detailMissing = !res;
1756
+ this.typeParts = res ? this.buildTypeParts(res) : [];
1757
+ this.chart = res ? this.buildChart(res) : null;
1758
+ }
1759
+ catch (err) {
1760
+ if (generation == this.detailGeneration) {
1761
+ this.detail = null;
1762
+ this.detailError = `${this.lc.loc("Errore")} ${err?.status ?? ""}. ${this.adms.errorText(err)}`;
1763
+ }
1764
+ }
1765
+ finally {
1766
+ if (generation == this.detailGeneration)
1767
+ this.detailLoading = false;
1768
+ }
1769
+ }
1770
+ // Mentre arriva il dettaglio di un altro nodo quello di prima resta visibile, attenuato, così il pannello non salta
1771
+ get detailStale() {
1772
+ return !!this.detail && this.detail.nodeId != this.selectedId;
1773
+ }
1774
+ async copyId(element) {
1775
+ try {
1776
+ await navigator.clipboard.writeText(this.detail.nodeId);
1777
+ this.copied = true;
1778
+ setTimeout(() => this.copied = false, 1500);
1779
+ }
1780
+ catch {
1781
+ // Senza appunti (pagina non sicura o permesso negato) il testo dell'id resta selezionato, pronto per Ctrl+C
1782
+ let range = document.createRange();
1783
+ range.selectNodeContents(element);
1784
+ let selection = window.getSelection();
1785
+ selection.removeAllRanges();
1786
+ selection.addRange(range);
1787
+ }
1788
+ }
1789
+ // Ripartizione del tempo proprio di nodo e discendenti: solo i tipi maggiori di zero, in ordine decrescente
1790
+ buildTypeParts(stats) {
1791
+ let parts = [
1792
+ ["Cpu", stats.cpuPerc], ["Database", stats.dbPerc], ["Wait", stats.waitPerc], ["Script", stats.scriptPerc], ["Network", stats.networkPerc],
1793
+ ["IO", stats.ioPerc], ["Sleep", stats.sleepPerc], ["Throttle", stats.throttlePerc], ["Idle", stats.idlePerc]
1794
+ ];
1795
+ 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] }));
1796
+ }
1797
+ // activeSessions ha un punto al secondo: si raggruppano in circa 160 punti con la media (R27)
1798
+ buildChart(stats) {
1799
+ let points = stats.activeSessions ?? [];
1800
+ if (points.length == 0)
1801
+ return null;
1802
+ let times = points.map(t => this.parseUtc(t.timestamp).getTime());
1803
+ let values = points.map(t => t.activity ?? 0);
1804
+ let interval = points.length > 1 ? Math.max(1, Math.round((times[times.length - 1] - times[0]) / (points.length - 1) / 1000)) : 1;
1805
+ let size = Math.max(1, Math.ceil(points.length / CHART_POINTS));
1806
+ let grouped = [];
1807
+ for (let i = 0; i < points.length; i += size) {
1808
+ let slice = values.slice(i, i + size);
1809
+ grouped.push({ t: times[i], v: slice.reduce((a, b) => a + b, 0) / slice.length });
1810
+ }
1811
+ let max = this.niceMax(Math.max(...grouped.map(t => t.v)));
1812
+ let n = grouped.length;
1813
+ let x = (i) => n > 1 ? i / (n - 1) * 100 : 50;
1814
+ let y = (v) => 100 - v / max * 100;
1815
+ let line = grouped.map((t, i) => `${i ? "L" : "M"}${x(i).toFixed(2)},${y(t.v).toFixed(2)}`).join(" ");
1816
+ return {
1817
+ line: line,
1818
+ area: `${line} L${x(n - 1).toFixed(2)},100 L${x(0).toFixed(2)},100 Z`,
1819
+ color: this.typeColor(stats.eventTypeName, stats.waitClassName),
1820
+ max: max,
1821
+ lastX: x(n - 1),
1822
+ lastY: y(grouped[n - 1].v),
1823
+ start: this.hhmm(new Date(times[0])),
1824
+ middle: this.hhmm(new Date(times[Math.floor(times.length / 2)])),
1825
+ end: this.hhmm(new Date(times[times.length - 1])),
1826
+ average: values.reduce((a, b) => a + b, 0) / values.length,
1827
+ peak: Math.max(...values),
1828
+ points: points.length,
1829
+ interval: interval,
1830
+ group: size * interval
1831
+ };
1832
+ }
1833
+ // Massimo dell'asse arrotondato a un valore tondo: 1, 2, 2,5 o 5 per una potenza di 10
1834
+ niceMax(value) {
1835
+ if (!(value > 0))
1836
+ return 1;
1837
+ let power = Math.pow(10, Math.floor(Math.log10(value)));
1838
+ let fraction = value / power;
1839
+ return [1, 2, 2.5, 5, 10].find(t => fraction <= t + 1e-9) * power;
1840
+ }
1841
+ // #endregion
1842
+ // #region Presentazione
1843
+ typeColor(type, waitClass) {
1844
+ return TYPE_COLORS[this.isIdle(type, waitClass) ? "Idle" : type] ?? TYPE_COLORS.Idle;
1845
+ }
1846
+ typeLabel(type, waitClass) {
1847
+ let label = TYPE_LABELS[type] ?? type;
1848
+ return waitClass && waitClass != "None" ? `${label} · ${waitClass}` : label;
1849
+ }
1850
+ axisValue(value) {
1851
+ return formatNumber(value, "it", "1.0-3");
1852
+ }
1853
+ duration(seconds) {
1854
+ return formatDuration(seconds);
1855
+ }
1856
+ // Le date delle risposte sono UTC "dd/MM/yyyy HH:mm:ss" senza fuso: si leggono come UTC e si mostrano in ora locale
1857
+ parseUtc(value) {
1858
+ let m = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})$/.exec(value ?? "");
1859
+ return m ? new Date(Date.UTC(+m[3], +m[2] - 1, +m[1], +m[4], +m[5], +m[6])) : value ? new Date(value) : null;
1860
+ }
1861
+ hhmm(value) {
1862
+ let date = typeof value == "string" ? this.parseUtc(value) : value;
1863
+ return date ? dayjs(date).format("HH:mm") : "—";
1864
+ }
1865
+ ddmmhhmm(value) {
1866
+ let date = this.parseUtc(value);
1867
+ return date ? dayjs(date).format("DD/MM HH:mm") : "—";
1868
+ }
1869
+ minutesAgo(value) {
1870
+ let date = this.parseUtc(value);
1871
+ return date ? Math.max(0, Math.floor((Date.now() - date.getTime()) / 60000)) : 0;
1872
+ }
1873
+ // "{x} s fa", "{x} min fa", "{x} h {y} min fa"; "mai" se nessuno è ancora uscito
1874
+ relative(value) {
1875
+ let date = this.parseUtc(value);
1876
+ if (!date)
1877
+ return this.lc.loc("mai");
1878
+ let seconds = Math.max(0, Math.round((Date.now() - date.getTime()) / 1000));
1879
+ if (seconds < 60)
1880
+ return `${seconds} ${this.lc.loc("s fa")}`;
1881
+ let minutes = Math.floor(seconds / 60);
1882
+ if (minutes < 60)
1883
+ return `${minutes} ${this.lc.loc("min fa")}`;
1884
+ return `${Math.floor(minutes / 60)} h ${minutes % 60} ${this.lc.loc("min fa")}`;
1885
+ }
1886
+ errorMessage(err) {
1887
+ return `${this.lc.loc("Errore")} ${err?.status ?? ""}: ${this.adms.errorText(err)}`;
1888
+ }
1889
+ // #endregion
1890
+ // #region Supporto
1891
+ buildSources() {
1892
+ let orders = [
1893
+ [ProfilerOrderBy.Time, "Tempo"], [ProfilerOrderBy.TimeLessChildren, "Tempo proprio"], [ProfilerOrderBy.Calls, "Chiamate"],
1894
+ [ProfilerOrderBy.AvgTime, "Tempo medio"], [ProfilerOrderBy.PeakTime, "Picco"], [ProfilerOrderBy.SnapDeltaTime, "Δ Tempo"],
1895
+ [ProfilerOrderBy.SnapDeltaTimeLessChildren, "Δ Tempo proprio"], [ProfilerOrderBy.SnapDeltaCalls, "Δ Chiamate"], [ProfilerOrderBy.ActiveThreads, "Thread attivi"]
1896
+ ];
1897
+ this.TreeOrders = orders.map(t => new KeyValue(String(t[0]), this.lc.loc(t[1])));
1898
+ // La vista per nome parte dal tempo proprio e non ha il Δ del tempo totale
1899
+ this.FlatOrders = [1, 0, 2, 3, 4, 6, 7, 8].map(t => this.TreeOrders.find(k => k.id == String(t)));
1900
+ this.ChildrenOptions = ["10", "25", "50", "100"].map(t => new KeyValue(t, t)).concat([new KeyValue("0", this.lc.loc("Tutti"))]);
1901
+ this.ThresholdOptions = [new KeyValue("0", this.lc.loc("Nessuna")), new KeyValue("0.1", "≥ 0,1%"), new KeyValue("1", "≥ 1%"), new KeyValue("5", "≥ 5%")];
1902
+ this.FlatTypes = [new KeyValue("", this.lc.loc("Tutti"))].concat(EVENT_TYPES.map(t => new KeyValue(t, t)));
1903
+ this.FlatRows = [new KeyValue("100", "100"), new KeyValue("500", "500"), new KeyValue("1000", "1.000")];
1904
+ this.updateOrderOptions();
1905
+ this.buildInstances();
1906
+ }
1907
+ // Le chiavi dei delta e i thread attivi si propongono solo in live (R10)
1908
+ updateOrderOptions() {
1909
+ this.TreeOrderOptions = this.live ? this.TreeOrders : this.TreeOrders.slice(0, 5);
1910
+ this.FlatOrderOptions = this.live ? this.FlatOrders : this.FlatOrders.slice(0, 5);
1911
+ }
1912
+ // Indipendenti: l'istanza che risponde è facoltativa, l'elenco usa la gestione errori standard
1913
+ loadInstances() {
1914
+ this.adms.getInstanceId(this.be).then(t => {
1915
+ this.instanceId = t;
1916
+ this.buildInstances();
1917
+ });
1918
+ this.adms.getInstances(this.be).then(t => {
1919
+ this.instanceIds = (t ?? []).map(k => k.id);
1920
+ this.buildInstances();
1921
+ });
1922
+ }
1923
+ buildInstances() {
1924
+ this.Instances = [
1925
+ new KeyValue(INSTANCE_THIS, this.instanceId ? `${this.lc.loc("Questa istanza")} (${this.instanceId})` : this.lc.loc("Questa istanza")),
1926
+ new KeyValue(INSTANCE_ALL$1, this.instanceIds.length ? `${this.lc.loc("Tutto il cluster")} (${this.instanceIds.length} ${this.lc.loc("istanze")})` : this.lc.loc("Tutto il cluster")),
1927
+ ...this.instanceIds.map(t => new KeyValue(t, `${this.lc.loc("Solo")} ${t}`))
1928
+ ];
1929
+ }
1930
+ updateUrl() {
1931
+ let [path, query] = this.location.path().split("?");
1932
+ let params = new URLSearchParams(query ?? "");
1933
+ if (this.rootId)
1934
+ params.set("root", this.rootId);
1935
+ else
1936
+ params.delete("root");
1937
+ this.location.replaceState(path, params.toString());
1938
+ }
1939
+ applySettings(saved) {
1940
+ this.orderBy = saved.orderBy ?? this.orderBy;
1941
+ this.maxChildren = saved.maxChildren ?? this.maxChildren;
1942
+ this.minPercentage = saved.minPercentage ?? this.minPercentage;
1943
+ this.snapValues = saved.snapValues ?? this.snapValues;
1944
+ this.flatOrderBy = saved.flatOrderBy ?? this.flatOrderBy;
1945
+ this.flatTop = saved.flatTop ?? this.flatTop;
1946
+ }
1947
+ saveSettings() {
1948
+ let settings = {
1949
+ orderBy: this.orderBy,
1950
+ maxChildren: this.maxChildren,
1951
+ minPercentage: this.minPercentage,
1952
+ snapValues: this.snapValues,
1953
+ flatOrderBy: this.flatOrderBy,
1954
+ flatTop: this.flatTop
1955
+ };
1956
+ this.localStorage.setItem(STORAGE_KEY$3, settings).subscribe();
1957
+ }
1958
+ toDate(value) {
1959
+ return value ? this.dateExts.getDateConvertion(value).toDate() : null;
1960
+ }
1961
+ fmt(date) {
1962
+ return dayjs(date).format(this.lc.token("getFullDateDisplayFormat"));
1963
+ }
1964
+ floorHour(date) {
1965
+ let ret = new Date(date);
1966
+ ret.setMinutes(0, 0, 0);
1967
+ return ret;
1968
+ }
1969
+ 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 }); }
1970
+ 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-o]=\"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-o\"></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-o\"></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) 420px;gap:14px;align-items:start;margin-top:14px}@container (max-width: 1150px){.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:860px;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" }] }); }
1971
+ }
1972
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerV2Component, decorators: [{
1973
+ type: Component,
1974
+ 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-o]=\"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-o\"></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-o\"></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) 420px;gap:14px;align-items:start;margin-top:14px}@container (max-width: 1150px){.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:860px;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"] }]
1975
+ }], 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: [{
1976
+ type: Inject,
1977
+ args: [ADM_MODULE_PATH]
1978
+ }] }, { type: undefined, decorators: [{
1979
+ type: Optional
1980
+ }, {
1981
+ type: Inject,
1982
+ args: [ADM_WORKLOAD_V2]
1983
+ }] }] });
1984
+
1985
+ // Workload report v2 (specifica "Workload report"): richieste e risposte come le vuole e le manda il backend (camelCase).
1986
+ // Le date delle risposte sono UTC "dd/MM/yyyy HH:mm:ss" senza fuso
1987
+ // Senza snapshot il report è live; senza istanze e con global è dell'intero cluster
1988
+ class WorkloadReportV2Request {
1989
+ }
1990
+ // Snapshot che terminano fra from e to (ISO UTC)
1991
+ class WorkloadSnapshotsRequest {
1992
+ }
1993
+ class WorkloadSnapshotInstance {
1994
+ }
1995
+ class WorkloadSnapshot {
1996
+ }
1997
+ class WorkloadSnapshotList {
1998
+ }
1999
+
2000
+ class AdmWorkloadV2Loc extends LocalizationService {
2001
+ constructor(injector) {
2002
+ super(injector);
2003
+ super.set("it->en", "Workload report", ["Workload report"]);
2004
+ super.set("it->en", "Errore", ["Error"]);
2005
+ // Barra dei comandi e avvisi
2006
+ super.set("it->en", "Tipo", ["Type"]);
2007
+ super.set("it->en", "Fra snapshot", ["Snapshot range"]);
2008
+ super.set("it->en", "Istanze", ["Instances"]);
2009
+ super.set("it->en", "Tutto il cluster", ["Whole cluster"]);
2010
+ super.set("it->en", "tutto il cluster", ["whole cluster"]);
2011
+ super.set("it->en", "istanze", ["instances"]);
2012
+ super.set("it->en", "Genera report", ["Generate report"]);
2013
+ super.set("it->en", "Annulla", ["Cancel"]);
2014
+ super.set("it->en", "Scegli il primo e l'ultimo snapshot", ["Choose the first and last snapshot"]);
2015
+ 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."]);
2016
+ 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."]);
2017
+ 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."]);
2018
+ // Snapshot
2019
+ super.set("it->en", "Snapshot", ["Snapshot"]);
2020
+ super.set("it->en", "Ultime 24 ore", ["Last 24 hours"]);
2021
+ super.set("it->en", "Ultimi 7 giorni", ["Last 7 days"]);
2022
+ super.set("it->en", "Tutto", ["All"]);
2023
+ super.set("it->en", "Caricamento…", ["Loading…"]);
2024
+ super.set("it->en", "Dalle", ["From"]);
2025
+ super.set("it->en", "alle", ["to"]);
2026
+ super.set("it->en", "dalle", ["from"]);
2027
+ super.set("it->en", "scegli l'ultimo snapshot", ["choose the last snapshot"]);
2028
+ super.set("it->en", "Azzera", ["Clear"]);
2029
+ 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."]);
2030
+ super.set("it->en", "Inizio", ["Start"]);
2031
+ super.set("it->en", "Fine", ["End"]);
2032
+ super.set("it->en", "Durata", ["Duration"]);
2033
+ super.set("it->en", "Manuale", ["Manual"]);
2034
+ super.set("it->en", "Riavvio", ["Restart"]);
2035
+ super.set("it->en", "Un'istanza è ripartita: counter e meter non sono attendibili", ["An instance restarted: counters and meters are not reliable"]);
2036
+ 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."]);
2037
+ super.set("it->en", "Orari in ora locale", ["Times in local time"]);
2038
+ super.set("it->en", "g", ["d"]);
2039
+ // Report
2040
+ super.set("it->en", "Generazione del report…", ["Generating the report…"]);
2041
+ super.set("it->en", "Il report non si può produrre", ["The report cannot be produced"]);
2042
+ super.set("it->en", "generato alle", ["generated at"]);
2043
+ super.set("it->en", "Vai a…", ["Go to…"]);
2044
+ super.set("it->en", "Scarica", ["Download"]);
2045
+ super.set("it->en", "Apri in una scheda", ["Open in a tab"]);
2046
+ super.set("it->en", "Gli orari del report sono in UTC.", ["Report times are in UTC."]);
2047
+ super.set("it->en", "Scegli tipo e istanze, poi genera il report.", ["Choose type and instances, then generate the report."]);
2048
+ }
2049
+ 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 }); }
2050
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmWorkloadV2Loc }); }
2051
+ }
2052
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmWorkloadV2Loc, decorators: [{
2053
+ type: Injectable
2054
+ }], ctorParameters: () => [{ type: i0.Injector }] });
2055
+
2056
+ // Angular
2057
+ const STORAGE_KEY$2 = "HotSearchWorkloadV2";
2058
+ const HOUR_MS = 3600 * 1000;
2059
+ // Sezioni principali del report con le ancore stabili della specifica: nel salto compaiono solo quelle presenti nel documento.
2060
+ // I titoli sono in inglese come nel report
2061
+ const SECTIONS = [
2062
+ ["summary", "Report Summary"], ["loadprofile", "Load profile"], ["efficiency", "Instance efficiency"], ["topevents", "Top 10 events by time"],
2063
+ ["waitclasses", "Wait classes"], ["actsumtype", "Activity summary by event type"], ["actsumclass", "Activity summary by wait class"],
2064
+ ["aas", "Average active sessions"], ["topsess", "Top sessions"], ["topnodes", "Top profiler nodes"], ["topapis", "Top APIs"], ["topjobs", "Top jobs"],
2065
+ ["toptenants", "Top tenants"], ["protime", "Profiler nodes by time"], ["protimelc", "Profiler nodes by self time"], ["procalls", "Profiler nodes by calls"],
2066
+ ["wprotime", "Profiler waits"], ["iprotime", "Profiler idle"], ["sqltime", "SQL by elapsed time"], ["sqlexec", "SQL by executions"], ["sqlpeak", "SQL by peak"],
2067
+ ["sqltext", "Complete list of SQL text"], ["netstat", "Network"], ["iostat", "IO"], ["contwaits", "Waits on latches and locks"], ["latch", "Latch activity"],
2068
+ ["mgauges", "Metrics: gauges"], ["mcounters", "Metrics: counters"], ["mmeters", "Metrics: meters"], ["mhistograms", "Metrics: histograms"],
2069
+ ["mtimers", "Metrics: timers"], ["parameters", "Modified parameters"]
2070
+ ];
2071
+ // Ricordati fra una visita e l'altra (W12)
2072
+ class WorkloadV2Settings {
2073
+ }
2074
+ // Il report mostrato, con i parametri con cui è stato chiesto: resta quello anche se poi si cambiano i comandi
2075
+ class WorkloadV2Report {
2076
+ }
2077
+ class AdmWorkloadV2Component {
2078
+ constructor(lc, localStorage, intercom, route, sanitizer, adms) {
2079
+ this.lc = lc;
2080
+ this.localStorage = localStorage;
2081
+ this.intercom = intercom;
2082
+ this.route = route;
2083
+ this.sanitizer = sanitizer;
2084
+ this.adms = adms;
2085
+ // Comandi
2086
+ this.live = true;
2087
+ // Vuoto = tutto il cluster
2088
+ this.instances = [];
2089
+ // Giorni dell'elenco degli snapshot; "0" = tutto
2090
+ this.period = "7";
2091
+ // Elenco degli snapshot
2092
+ this.snapshots = [];
2093
+ this.historyAvailable = true;
2094
+ this.snapshotsLoading = false;
2095
+ this.snapshotsLoaded = false;
2096
+ // Primo e ultimo snapshot scelti, compresi (selTo null dopo il primo clic)
2097
+ this.selFrom = null;
2098
+ this.selTo = null;
2099
+ this.warnings = [];
2100
+ // Report
2101
+ this.generating = false;
2102
+ this.section = "";
2103
+ this.Instances = [];
2104
+ this.Periods = [];
2105
+ this.snapshotsGeneration = 0;
2106
+ this.be = route.snapshot.params["be"] || "";
2107
+ this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
2108
+ }
2109
+ async ngOnInit() {
2110
+ this.Periods = [
2111
+ new KeyValue("1", this.lc.loc("Ultime 24 ore")),
2112
+ new KeyValue("7", this.lc.loc("Ultimi 7 giorni")),
2113
+ new KeyValue("0", this.lc.loc("Tutto"))
2114
+ ];
2115
+ let saved = this.route.snapshot.queryParams["ignoreStorage"] ? null : await firstValueFrom(this.localStorage.getItem(STORAGE_KEY$2), { defaultValue: null });
2116
+ if (saved) {
2117
+ this.live = saved.live ?? this.live;
2118
+ this.instances = saved.instances ?? this.instances;
2119
+ this.period = saved.period ?? this.period;
2120
+ }
2121
+ this.adms.getInstances(this.be).then(t => this.Instances = t ?? []);
2122
+ this.loadSnapshots();
2123
+ }
2124
+ ngOnDestroy() {
2125
+ this.reportSub?.unsubscribe();
2126
+ }
2127
+ // #region Comandi
2128
+ get clusterLabel() {
2129
+ return this.Instances.length ? `${this.lc.loc("Tutto il cluster")} (${this.Instances.length} ${this.lc.loc("istanze")})` : this.lc.loc("Tutto il cluster");
2130
+ }
2131
+ setLive(live) {
2132
+ if (!live && !this.historyAvailable)
2133
+ return;
2134
+ this.live = live;
2135
+ this.updateWarnings();
2136
+ this.saveSettings();
2137
+ }
2138
+ // W6: cambiando istanze l'elenco degli snapshot si ricarica
2139
+ instancesChanged(ids) {
2140
+ this.instances = ids ?? [];
2141
+ this.saveSettings();
2142
+ this.loadSnapshots();
2143
+ }
2144
+ setPeriod(id) {
2145
+ this.period = id;
2146
+ this.saveSettings();
2147
+ this.loadSnapshots();
2148
+ }
2149
+ // In "Fra snapshot" servono il primo e l'ultimo
2150
+ get canGenerate() {
2151
+ return this.live || (this.selFrom != null && this.selTo != null);
2152
+ }
2153
+ // #endregion
2154
+ // #region Snapshot
2155
+ async loadSnapshots() {
2156
+ let generation = ++this.snapshotsGeneration;
2157
+ let req = new WorkloadSnapshotsRequest();
2158
+ if (+this.period > 0)
2159
+ req.from = new Date(Date.now() - +this.period * 24 * HOUR_MS).toISOString();
2160
+ if (this.instances.length)
2161
+ req.instanceIds = this.instances;
2162
+ this.snapshotsLoading = true;
2163
+ this.snapshotsError = null;
2164
+ try {
2165
+ let res = await this.adms.getWorkloadSnapshots(this.be, req);
2166
+ if (generation != this.snapshotsGeneration)
2167
+ return;
2168
+ // Senza risposta (per esempio DefaultOnFail) vale come storico spento
2169
+ this.historyAvailable = res?.historyAvailable ?? false;
2170
+ this.snapshots = res?.snapshots ?? [];
2171
+ this.snapshotsLoaded = true;
2172
+ if (!this.historyAvailable)
2173
+ this.live = true;
2174
+ // Se gli snapshot scelti non ci sono più, la scelta si azzera (W6)
2175
+ let ids = new Set(this.snapshots.map(t => t.snapId));
2176
+ if ((this.selFrom != null && !ids.has(this.selFrom)) || (this.selTo != null && !ids.has(this.selTo)))
2177
+ this.clearSelection();
2178
+ this.updateWarnings();
2179
+ }
2180
+ catch (err) {
2181
+ if (generation == this.snapshotsGeneration)
2182
+ this.snapshotsError = `${this.lc.loc("Errore")} ${err?.status ?? ""}: ${this.adms.errorText(err)}`;
2183
+ }
2184
+ finally {
2185
+ if (generation == this.snapshotsGeneration)
2186
+ this.snapshotsLoading = false;
2187
+ }
2188
+ }
2189
+ // Un clic fissa il primo snapshot, un secondo l'ultimo (in qualsiasi ordine); un terzo ricomincia
2190
+ pick(snapshot) {
2191
+ if (this.selFrom == null || this.selTo != null) {
2192
+ this.selFrom = snapshot.snapId;
2193
+ this.selTo = null;
2194
+ }
2195
+ else {
2196
+ let first = this.selFrom;
2197
+ this.selFrom = Math.min(first, snapshot.snapId);
2198
+ this.selTo = Math.max(first, snapshot.snapId);
2199
+ }
2200
+ this.updateWarnings();
2201
+ }
2202
+ clearSelection() {
2203
+ this.selFrom = null;
2204
+ this.selTo = null;
2205
+ this.updateWarnings();
2206
+ }
2207
+ inRange(snapshot) {
2208
+ if (this.selFrom == null)
2209
+ return false;
2210
+ return this.selTo == null ? snapshot.snapId == this.selFrom : snapshot.snapId >= this.selFrom && snapshot.snapId <= this.selTo;
2211
+ }
2212
+ isEdge(snapshot) {
2213
+ return snapshot.snapId == this.selFrom || snapshot.snapId == this.selTo;
2214
+ }
2215
+ // Finestra scelta: dall'inizio del primo snapshot alla fine dell'ultimo (ognuno copre l'intervallo dal precedente)
2216
+ get selectionWindow() {
2217
+ let first = this.snapshots.find(t => t.snapId == this.selFrom);
2218
+ let last = this.snapshots.find(t => t.snapId == (this.selTo ?? this.selFrom));
2219
+ return first && last ? { from: this.parseUtc(first.beginIntervalTime), to: this.parseUtc(last.endIntervalTime) } : null;
2220
+ }
2221
+ instancesTitle(snapshot) {
2222
+ return (snapshot.instances ?? []).map(t => t.instanceId).join("\n");
2223
+ }
2224
+ // W4: avvisi sulla finestra prima di generare, non bloccanti
2225
+ updateWarnings() {
2226
+ let warnings = [];
2227
+ if (!this.live && this.selFrom != null && this.selTo != null) {
2228
+ this.snapshots.forEach((s, i) => {
2229
+ if (s.restart && s.snapId >= this.selFrom && s.snapId <= this.selTo)
2230
+ for (let instance of this.restartedInstances(i))
2231
+ 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));
2232
+ });
2233
+ let window = this.selectionWindow;
2234
+ if (window && window.to.getTime() - window.from.getTime() > 24 * HOUR_MS)
2235
+ warnings.push(this.lc.loc("La finestra supera 24 ore: l'attività campionata ne copre solo le ultime 24."));
2236
+ }
2237
+ this.warnings = warnings;
2238
+ }
2239
+ // L'istanza ripartita è quella con un avvio diverso dallo snapshot precedente (il successivo nell'elenco, che va dal più recente)
2240
+ restartedInstances(index) {
2241
+ let snapshot = this.snapshots[index];
2242
+ let previous = this.snapshots[index + 1];
2243
+ let ids = (snapshot.instances ?? []).filter(t => {
2244
+ let before = previous?.instances?.find(k => k.instanceId == t.instanceId);
2245
+ return before ? before.startupTime != t.startupTime : this.parseUtc(t.startupTime) > this.parseUtc(snapshot.beginIntervalTime);
2246
+ }).map(t => t.instanceId);
2247
+ return ids.length ? ids : (snapshot.instances ?? []).map(t => t.instanceId);
2248
+ }
2249
+ // #endregion
2250
+ // #region Report
2251
+ // Il report si chiede solo su comando (W7); durante la richiesta lo stesso comando la annulla
2252
+ generateOrCancel() {
2253
+ if (this.generating)
2254
+ this.cancel();
2255
+ else
2256
+ this.generate();
2257
+ }
2258
+ generate() {
2259
+ if (!this.canGenerate)
2260
+ return;
2261
+ let req = new WorkloadReportV2Request();
2262
+ // Mai global=false senza istanze: dietro un bilanciatore darebbe il report dell'istanza che risponde (W2)
2263
+ if (this.instances.length)
2264
+ req.instanceIds = [...this.instances];
2265
+ else
2266
+ req.global = true;
2267
+ let window = this.live ? null : this.selectionWindow;
2268
+ if (!this.live) {
2269
+ req.snapFrom = this.selFrom;
2270
+ req.snapTo = this.selTo;
2271
+ }
2272
+ this.generating = true;
2273
+ this.reportError = null;
2274
+ this.reportSub?.unsubscribe();
2275
+ this.reportSub = this.adms.getWorkloadReportV2(this.be, req).subscribe({
2276
+ next: html => {
2277
+ this.generating = false;
2278
+ this.showReport(html ?? "", req, window);
2279
+ },
2280
+ error: err => {
2281
+ this.generating = false;
2282
+ // La sessione scaduta la gestisce il servizio
2283
+ if (err?.status != 401)
2284
+ this.reportError = this.adms.errorText(err) || `${this.lc.loc("Errore")} ${err?.status ?? ""}`;
2285
+ }
2286
+ });
2287
+ }
2288
+ cancel() {
2289
+ this.reportSub?.unsubscribe();
2290
+ this.reportSub = null;
2291
+ this.generating = false;
2292
+ }
2293
+ showReport(html, req, window) {
2294
+ // Le sezioni del salto sono quelle che il documento ha davvero
2295
+ let parsed = new DOMParser().parseFromString(html, "text/html");
2296
+ let sections = SECTIONS.filter(t => parsed.getElementById(t[0])).map(t => new KeyValue(t[0], t[1]));
2297
+ this.report = {
2298
+ html: html,
2299
+ // Il report va in un iframe con sandbox senza script: lo stile e le ancore restano dentro il riquadro.
2300
+ // Il sanificatore di Angular toglierebbe gli stili inline del documento, per questo lo si marca come sicuro
2301
+ doc: this.sanitizer.bypassSecurityTrustHtml(html),
2302
+ live: req.snapFrom == null,
2303
+ instances: req.instanceIds ?? [],
2304
+ snapFrom: req.snapFrom,
2305
+ snapTo: req.snapTo,
2306
+ windowFrom: window?.from,
2307
+ windowTo: window?.to,
2308
+ generatedAt: new Date(),
2309
+ size: new Blob([html]).size,
2310
+ sections: sections
2311
+ };
2312
+ this.section = "";
2313
+ }
2314
+ // W11: porta il riquadro all'ancora scelta, senza far scorrere la pagina
2315
+ goToSection(anchor) {
2316
+ this.section = anchor;
2317
+ let doc = this.reportFrame?.nativeElement?.contentDocument;
2318
+ let target = doc?.getElementById(anchor);
2319
+ if (target)
2320
+ doc.defaultView.scrollTo({ top: target.getBoundingClientRect().top + doc.defaultView.scrollY - 8, behavior: "smooth" });
2321
+ }
2322
+ // W10: l'HTML in un file UTF-8 con un nome che dice cosa contiene
2323
+ download() {
2324
+ let url = URL.createObjectURL(this.reportBlob());
2325
+ let link = document.createElement("a");
2326
+ link.href = url;
2327
+ link.download = this.fileName();
2328
+ document.body.appendChild(link);
2329
+ link.click();
2330
+ link.remove();
2331
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
2332
+ }
2333
+ openInTab() {
2334
+ let url = URL.createObjectURL(this.reportBlob());
2335
+ window.open(url, "_blank");
2336
+ // La scheda nuova ha già caricato il documento quando l'URL viene revocato
2337
+ setTimeout(() => URL.revokeObjectURL(url), 60000);
2338
+ }
2339
+ reportBlob() {
2340
+ return new Blob([this.report.html], { type: "text/html;charset=utf-8" });
2341
+ }
2342
+ // workload-report_{live|snap-1201-1206}_{cluster|istanze}_{yyyyMMdd-HHmm}Z.html
2343
+ fileName() {
2344
+ let r = this.report;
2345
+ let kind = r.live ? "live" : `snap-${r.snapFrom}-${r.snapTo}`;
2346
+ let scope = r.instances.length ? r.instances.map(t => t.replace(/[^\w.-]+/g, "-")).join("+") : "cluster";
2347
+ return `workload-report_${kind}_${scope}_${this.utcStamp(r.generatedAt)}Z.html`;
2348
+ }
2349
+ // yyyyMMdd-HHmm in UTC (il plugin utc di dayjs non è detto che ci sia)
2350
+ utcStamp(date) {
2351
+ let pad = (n) => String(n).padStart(2, "0");
2352
+ return `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(date.getUTCHours())}${pad(date.getUTCMinutes())}`;
2353
+ }
2354
+ // #endregion
2355
+ // #region Presentazione
2356
+ // 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)
2357
+ parseUtc(value) {
2358
+ let m = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})$/.exec(value ?? "");
2359
+ return m ? new Date(Date.UTC(+m[3], +m[2] - 1, +m[1], +m[4], +m[5], +m[6])) : value ? new Date(value) : null;
2360
+ }
2361
+ local(value) {
2362
+ let date = typeof value == "string" ? this.parseUtc(value) : value;
2363
+ return date ? dayjs(date).format("DD/MM HH:mm") : "—";
2364
+ }
2365
+ hhmm(date) {
2366
+ return date ? dayjs(date).format("HH:mm") : "—";
2367
+ }
2368
+ // Fuso dell'ora locale, per dire in che ora sono gli orari della pagina
2369
+ get zone() {
2370
+ return `UTC${dayjs().format("Z")}`;
2371
+ }
2372
+ span(from, to) {
2373
+ let a = typeof from == "string" ? this.parseUtc(from) : from;
2374
+ let b = typeof to == "string" ? this.parseUtc(to) : to;
2375
+ if (!a || !b)
2376
+ return "—";
2377
+ let minutes = Math.max(0, Math.round((b.getTime() - a.getTime()) / 60000));
2378
+ if (minutes < 60)
2379
+ return `${minutes} min`;
2380
+ let hours = Math.floor(minutes / 60);
2381
+ if (hours < 48)
2382
+ return minutes % 60 ? `${hours} h ${minutes % 60} min` : `${hours} h`;
2383
+ let days = Math.floor(hours / 24);
2384
+ return hours % 24 ? `${days} ${this.lc.loc("g")} ${hours % 24} h` : `${days} ${this.lc.loc("g")}`;
2385
+ }
2386
+ kilobytes(size) {
2387
+ return `${Math.max(1, Math.round(size / 1024))} kB`;
2388
+ }
2389
+ scopeLabel(instances) {
2390
+ return instances?.length ? instances.join(", ") : this.lc.loc("tutto il cluster");
2391
+ }
2392
+ saveSettings() {
2393
+ let settings = { live: this.live, instances: this.instances, period: this.period };
2394
+ this.localStorage.setItem(STORAGE_KEY$2, settings).subscribe();
2395
+ }
2396
+ 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 }); }
2397
+ 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-o\"></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-o\"></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-o\"></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-o\"></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" }] }); }
2398
+ }
2399
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmWorkloadV2Component, decorators: [{
2400
+ type: Component,
2401
+ 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-o\"></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-o\"></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-o\"></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-o\"></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"] }]
2402
+ }], ctorParameters: () => [{ type: i1.LocalizationService }, { type: i3.PreferencesService }, { type: i1$1.InterComService }, { type: i4.ActivatedRoute }, { type: i5$1.DomSanitizer }, { type: AdminModuleBaseService }], propDecorators: { reportFrame: [{
2403
+ type: ViewChild,
2404
+ args: ["reportFrame"]
2405
+ }] } });
2406
+
2407
+ // Metriche v2 (specifica "Metriche del Director"): richieste e risposte come le vuole e le manda il backend (camelCase).
2408
+ // Le date delle risposte sono UTC "dd/MM/yyyy HH:mm:ss" senza fuso, al secondo
2409
+ var MetricV2Type;
2410
+ (function (MetricV2Type) {
2411
+ MetricV2Type[MetricV2Type["Counter"] = 0] = "Counter";
2412
+ MetricV2Type[MetricV2Type["Gauge"] = 1] = "Gauge";
2413
+ MetricV2Type[MetricV2Type["Histogram"] = 2] = "Histogram";
2414
+ MetricV2Type[MetricV2Type["Timer"] = 3] = "Timer";
2415
+ MetricV2Type[MetricV2Type["Meter"] = 4] = "Meter";
2416
+ })(MetricV2Type || (MetricV2Type = {}));
2417
+ // Senza istanze e con global, tutto il cluster
2418
+ class MetricsV2CatalogRequest {
2419
+ }
2420
+ // Live (realTime con snapFrom), storico per periodo (snapFrom/snapTo) o storico fra due snapshot (snapIdFrom/snapIdTo)
2421
+ class MetricsV2Request extends MetricsV2CatalogRequest {
2422
+ }
2423
+ class MetricsV2Info {
2424
+ }
2425
+ // Una metrica di un'istanza in un istante: i campi che non riguardano il tipo valgono 0 o null
2426
+ class MetricsV2Sample {
2427
+ }
2428
+
2429
+ class AdmMetricsV2Loc extends LocalizationService {
2430
+ constructor(injector) {
2431
+ super(injector);
2432
+ super.set("it->en", "Metriche di Sistema", ["System Metrics"]);
2433
+ super.set("it->en", "Errore", ["Error"]);
2434
+ // Barra dei comandi
2435
+ super.set("it->en", "Sorgente", ["Source"]);
2436
+ super.set("it->en", "Storico per periodo", ["History by period"]);
2437
+ super.set("it->en", "Storico per snapshot", ["History by snapshot"]);
2438
+ 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."]);
2439
+ super.set("it->en", "Finestra", ["Window"]);
2440
+ super.set("it->en", "1 ora", ["1 hour"]);
2441
+ super.set("it->en", "Dal", ["From"]);
2442
+ super.set("it->en", "Al", ["To"]);
2443
+ super.set("it->en", "Dallo snapshot", ["From snapshot"]);
2444
+ super.set("it->en", "Allo snapshot", ["To snapshot"]);
2445
+ super.set("it->en", "Dalle", ["From"]);
2446
+ super.set("it->en", "alle", ["at"]);
2447
+ super.set("it->en", "Istanze", ["Instances"]);
2448
+ super.set("it->en", "Tutto il cluster", ["Whole cluster"]);
2449
+ super.set("it->en", "istanze", ["instances"]);
2450
+ super.set("it->en", "Aggiornato alle", ["Updated at"]);
2451
+ super.set("it->en", "Pausa", ["Pause"]);
2452
+ super.set("it->en", "Riprendi", ["Resume"]);
2453
+ super.set("it->en", "Caricamento…", ["Loading…"]);
2454
+ super.set("it->en", "Grafici", ["Charts"]);
2455
+ super.set("it->en", "Tabella", ["Table"]);
2456
+ 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."]);
2457
+ // Catalogo
2458
+ super.set("it->en", "Cerca una metrica…", ["Search a metric…"]);
2459
+ super.set("it->en", "Processo", ["Process"]);
2460
+ super.set("it->en", "API", ["API"]);
2461
+ super.set("it->en", "Code asincrone", ["Async queues"]);
2462
+ super.set("it->en", "Attività", ["Activity"]);
2463
+ super.set("it->en", "di", ["of"]);
2464
+ super.set("it->en", "metriche scelte", ["metrics chosen"]);
2465
+ super.set("it->en", "Togli tutte", ["Remove all"]);
2466
+ super.set("it->en", "Voci", ["Items"]);
2467
+ super.set("it->en", "Aggiungi le {N} voci", ["Add the {N} items"]);
2468
+ // Grafici e tessere
2469
+ super.set("it->en", "Scegli una o più metriche dal catalogo.", ["Choose one or more metrics from the catalog."]);
2470
+ super.set("it->en", "dall'avvio", ["since start"]);
2471
+ super.set("it->en", "Orari in ora locale", ["Times in local time"]);
2472
+ super.set("it->en", "trascina su un grafico per lo zoom", ["drag on a chart to zoom"]);
2473
+ super.set("it->en", "Finestra intera", ["Whole window"]);
2474
+ super.set("it->en", "Togli", ["Remove"]);
2475
+ super.set("it->en", "Nessun campione di {metrica} nella finestra scelta", ["No samples of {metrica} in the chosen window"]);
2476
+ super.set("it->en", "riavvio", ["restart"]);
2477
+ super.set("it->en", "thread", ["threads"]);
2478
+ super.set("it->en", "Livello", ["Level"]);
2479
+ super.set("it->en", "Variazione", ["Change"]);
2480
+ super.set("it->en", "Quote", ["Shares"]);
2481
+ super.set("it->en", "Al secondo", ["Per second"]);
2482
+ super.set("it->en", "Rate mobili", ["Moving rates"]);
2483
+ super.set("it->en", "Percentili", ["Percentiles"]);
2484
+ super.set("it->en", "Frequenza", ["Frequency"]);
2485
+ super.set("it->en", "Chiamate e durata", ["Calls and duration"]);
2486
+ super.set("it->en", "In corso", ["In progress"]);
2487
+ super.set("it->en", "in corso", ["in progress"]);
2488
+ super.set("it->en", "mediana", ["median"]);
2489
+ super.set("it->en", "media nell'intervallo", ["interval mean"]);
2490
+ // Tabella
2491
+ super.set("it->en", "Istanza", ["Instance"]);
2492
+ super.set("it->en", "Istante", ["Time"]);
2493
+ super.set("it->en", "Intervallo (s)", ["Interval (s)"]);
2494
+ super.set("it->en", "Snapshot", ["Snapshot"]);
2495
+ super.set("it->en", "Valore", ["Value"]);
2496
+ super.set("it->en", "Conteggio", ["Count"]);
2497
+ super.set("it->en", "Quota", ["Share"]);
2498
+ super.set("it->en", "Mediana", ["Median"]);
2499
+ super.set("it->en", "Massimo", ["Maximum"]);
2500
+ super.set("it->en", "Chiamate", ["Calls"]);
2501
+ super.set("it->en", "Durata media nell'intervallo", ["Mean duration in the interval"]);
2502
+ super.set("it->en", "Esporta CSV", ["Export CSV"]);
2503
+ super.set("it->en", "Mostrate le prime 300 righe di", ["Showing the first 300 rows of"]);
2504
+ super.set("it->en", "l'esportazione le comprende tutte.", ["the export includes all of them."]);
2505
+ }
2506
+ 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 }); }
2507
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmMetricsV2Loc }); }
2508
+ }
2509
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmMetricsV2Loc, decorators: [{
2510
+ type: Injectable
2511
+ }], ctorParameters: () => [{ type: i0.Injector }] });
2512
+
2513
+ // Angular
2514
+ // Un punto di una serie: gap = non unirlo al precedente (riavvio o buco nei campioni), v2 = estremo superiore di una fascia
2515
+ class Mv2Point {
2516
+ }
2517
+ class Mv2Series {
2518
+ }
2519
+ class Mv2Mark {
2520
+ }
2521
+ class Mv2Path {
2522
+ }
2523
+ class Mv2Rect {
2524
+ }
2525
+ class Mv2Dot {
2526
+ }
2527
+ class Mv2Tick {
2528
+ }
2529
+ class Mv2MarkTick {
2530
+ }
2531
+ class Mv2TipRow {
2532
+ }
2533
+ const LEFT = 56;
2534
+ const RIGHT = 10;
2535
+ const TOP = 8;
2536
+ const BOTTOM = 20;
2537
+ const STEPS_MS = [60, 120, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400, 172800, 604800].map(t => t * 1000);
2538
+ // Grafico SVG delle metriche v2: lo stesso asse dei tempi per tutti i pannelli, cursore e zoom condivisi tramite la pagina.
2539
+ // Le serie arrivano già pronte (punti con le interruzioni segnate): qui solo geometria e interazione
2540
+ class MetricsV2ChartComponent {
2541
+ constructor(host, zone) {
2542
+ this.host = host;
2543
+ this.zone = zone;
2544
+ this.series = [];
2545
+ this.height = 170;
2546
+ this.marks = [];
2547
+ this.cursor = null;
2548
+ this.format = t => String(t);
2549
+ this.cursorChange = new EventEmitter();
2550
+ this.zoom = new EventEmitter();
2551
+ this.width = 600;
2552
+ this.left = LEFT;
2553
+ this.right = RIGHT;
2554
+ this.top = TOP;
2555
+ this.bottom = BOTTOM;
2556
+ this.paths = [];
2557
+ this.rects = [];
2558
+ this.dots = [];
2559
+ this.yTicks = [];
2560
+ this.xTicks = [];
2561
+ this.markTicks = [];
2562
+ // Il riquadro dei valori compare solo sul grafico sotto il mouse, la linea del cursore su tutti
2563
+ this.hovered = false;
2564
+ this.cursorX = null;
2565
+ this.tipTime = "";
2566
+ this.tipRows = [];
2567
+ this.dragFrom = null;
2568
+ this.dragTo = null;
2569
+ this.yMin = 0;
2570
+ this.yMax = 1;
2571
+ }
2572
+ ngOnInit() {
2573
+ this.observer = new ResizeObserver(entries => {
2574
+ let width = Math.floor(entries[0]?.contentRect?.width ?? 0);
2575
+ if (width > 0 && width != this.width)
2576
+ this.zone.run(() => {
2577
+ this.width = width;
2578
+ this.layout();
2579
+ });
2580
+ });
2581
+ this.observer.observe(this.host.nativeElement);
2582
+ }
2583
+ ngOnDestroy() {
2584
+ this.observer?.disconnect();
2585
+ }
2586
+ ngOnChanges(changes) {
2587
+ if (changes.series || changes.from || changes.to || changes.height || changes.marks)
2588
+ this.layout();
2589
+ else if (changes.cursor)
2590
+ this.updateCursor();
2591
+ }
2592
+ min(a, b) { return Math.min(a, b); }
2593
+ abs(a) { return Math.abs(a); }
2594
+ // #region Geometria
2595
+ get plotWidth() {
2596
+ return Math.max(10, this.width - LEFT - RIGHT);
2597
+ }
2598
+ x(t) {
2599
+ return LEFT + (t - this.from) / Math.max(1, this.to - this.from) * this.plotWidth;
2600
+ }
2601
+ y(v) {
2602
+ return TOP + (1 - (v - this.yMin) / Math.max(1e-12, this.yMax - this.yMin)) * (this.height - TOP - BOTTOM);
2603
+ }
2604
+ timeAt(x) {
2605
+ return this.from + (x - LEFT) / this.plotWidth * (this.to - this.from);
2606
+ }
2607
+ layout() {
2608
+ if (this.from == null || this.to == null)
2609
+ return;
2610
+ // Scala verticale sui punti visibili, con lo zero se i valori sono positivi
2611
+ let values = [];
2612
+ for (let s of this.series ?? [])
2613
+ for (let p of s.points)
2614
+ if (p.t >= this.from && p.t <= this.to) {
2615
+ values.push(p.v);
2616
+ if (p.v2 != null)
2617
+ values.push(p.v2);
2618
+ }
2619
+ let lo = values.length ? Math.min(0, ...values) : 0;
2620
+ let hi = values.length ? Math.max(...values) : 1;
2621
+ if (hi <= lo)
2622
+ hi = lo + 1;
2623
+ let step = this.niceStep((hi - lo) / 4);
2624
+ this.yMin = Math.floor(lo / step) * step;
2625
+ this.yMax = Math.ceil(hi / step) * step;
2626
+ this.yTicks = [];
2627
+ for (let v = this.yMin; v <= this.yMax + step / 2; v += step)
2628
+ this.yTicks.push({ key: String(v), pos: this.y(v), label: this.format(Math.abs(v) < step / 1e6 ? 0 : v) });
2629
+ // Asse dei tempi: tacche a passi tondi, al massimo una ogni 90 pixel circa
2630
+ let span = this.to - this.from;
2631
+ let stepMs = STEPS_MS.find(t => span / t <= this.plotWidth / 90) ?? STEPS_MS[STEPS_MS.length - 1];
2632
+ let longRange = span > 36 * 3600 * 1000;
2633
+ let offset = new Date().getTimezoneOffset() * 60000;
2634
+ this.xTicks = [];
2635
+ for (let t = Math.ceil((this.from - offset) / stepMs) * stepMs + offset; t <= this.to; t += stepMs)
2636
+ this.xTicks.push({ key: String(t), pos: this.x(t), label: dayjs(t).format(longRange || stepMs >= 86400000 ? "DD/MM HH:mm" : "HH:mm") });
2637
+ // Segni: riavvii sempre, id degli snapshot solo se c'è spazio (altrimenti uno ogni tanto)
2638
+ let visible = (this.marks ?? []).filter(t => t.t >= this.from && t.t <= this.to);
2639
+ let snaps = visible.filter(t => t.kind == "snapshot");
2640
+ let every = Math.max(1, Math.ceil(snaps.length / Math.max(1, this.plotWidth / 45)));
2641
+ this.markTicks = visible.filter(t => t.kind == "restart" || snaps.indexOf(t) % every == 0)
2642
+ .map((t, i) => ({ key: `${t.kind}${t.t}${i}`, pos: this.x(t.t), label: t.label, kind: t.kind }));
2643
+ let paths = [];
2644
+ let rects = [];
2645
+ let dots = [];
2646
+ let bars = (this.series ?? []).filter(t => t.kind == "bars");
2647
+ for (let s of this.series ?? []) {
2648
+ let points = s.points.filter(p => p.t >= this.from && p.t <= this.to);
2649
+ if (points.length == 0)
2650
+ continue;
2651
+ switch (s.kind) {
2652
+ case "line":
2653
+ case "step":
2654
+ 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 });
2655
+ break;
2656
+ case "band":
2657
+ paths.push({ key: s.key, d: this.bandPath(points), color: s.color, fill: true, dash: null, opacity: s.opacity ?? 0.2 });
2658
+ break;
2659
+ case "dots":
2660
+ points.forEach((p, i) => dots.push({ key: `${s.key}${i}`, x: this.x(p.t), y: this.y(p.v), color: s.color }));
2661
+ break;
2662
+ case "bars":
2663
+ let slot = this.plotWidth / Math.max(1, points.length) * 0.8;
2664
+ let w = Math.max(1, slot / bars.length);
2665
+ let index = bars.indexOf(s);
2666
+ let zero = this.y(Math.max(this.yMin, 0));
2667
+ points.forEach((p, i) => {
2668
+ let y = this.y(p.v);
2669
+ 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 });
2670
+ });
2671
+ break;
2672
+ }
2673
+ }
2674
+ this.paths = paths;
2675
+ this.rects = rects;
2676
+ this.dots = dots;
2677
+ this.updateCursor();
2678
+ }
2679
+ // Le linee si spezzano dove il punto ha gap: niente linea fra due campioni separati da un riavvio o da un buco
2680
+ linePath(points, step) {
2681
+ let d = "";
2682
+ points.forEach((p, i) => {
2683
+ let x = this.x(p.t).toFixed(1);
2684
+ let y = this.y(p.v).toFixed(1);
2685
+ if (i == 0 || p.gap)
2686
+ d += `M${x},${y}`;
2687
+ else if (step)
2688
+ d += `H${x}V${y}`;
2689
+ else
2690
+ d += `L${x},${y}`;
2691
+ });
2692
+ return d;
2693
+ }
2694
+ // Fascia fra v e v2, un poligono per ogni tratto senza interruzioni
2695
+ bandPath(points) {
2696
+ let segments = [];
2697
+ for (let p of points) {
2698
+ if (p.v2 == null)
2699
+ continue;
2700
+ if (segments.length == 0 || p.gap)
2701
+ segments.push([]);
2702
+ segments[segments.length - 1].push(p);
2703
+ }
2704
+ return segments.filter(t => t.length > 0).map(seg => {
2705
+ let upper = seg.map((p, i) => `${i ? "L" : "M"}${this.x(p.t).toFixed(1)},${this.y(p.v2).toFixed(1)}`).join("");
2706
+ let lower = [...seg].reverse().map(p => `L${this.x(p.t).toFixed(1)},${this.y(p.v).toFixed(1)}`).join("");
2707
+ return upper + lower + "Z";
2708
+ }).join("");
2709
+ }
2710
+ niceStep(raw) {
2711
+ if (!(raw > 0))
2712
+ return 1;
2713
+ let power = Math.pow(10, Math.floor(Math.log10(raw)));
2714
+ return [1, 2, 2.5, 5, 10].find(t => raw / power <= t + 1e-9) * power;
2715
+ }
2716
+ // #endregion
2717
+ // #region Cursore e zoom
2718
+ updateCursor() {
2719
+ if (this.cursor == null || this.cursor < this.from || this.cursor > this.to) {
2720
+ this.cursorX = null;
2721
+ this.tipRows = [];
2722
+ return;
2723
+ }
2724
+ this.cursorX = this.x(this.cursor);
2725
+ this.tipTime = dayjs(this.cursor).format("DD/MM HH:mm:ss");
2726
+ let rows = [];
2727
+ for (let s of this.series ?? []) {
2728
+ if (s.tooltip === false || s.points.length == 0)
2729
+ continue;
2730
+ let nearest = this.nearest(s.points, this.cursor);
2731
+ if (nearest)
2732
+ rows.push({ color: s.color, label: s.label, value: this.format(nearest.v) });
2733
+ }
2734
+ this.tipRows = rows;
2735
+ }
2736
+ // Il campione più vicino, se non è più lontano di un intervallo tipico della serie
2737
+ nearest(points, t) {
2738
+ let best = null;
2739
+ for (let p of points)
2740
+ if (!best || Math.abs(p.t - t) < Math.abs(best.t - t))
2741
+ best = p;
2742
+ let typical = points.length > 1 ? (points[points.length - 1].t - points[0].t) / (points.length - 1) : Infinity;
2743
+ return best && Math.abs(best.t - t) <= Math.max(typical, 1000) ? best : null;
2744
+ }
2745
+ plotX(event) {
2746
+ let rect = this.host.nativeElement.getBoundingClientRect();
2747
+ return Math.min(this.width - RIGHT, Math.max(LEFT, event.clientX - rect.left));
2748
+ }
2749
+ move(event) {
2750
+ let x = this.plotX(event);
2751
+ this.hovered = true;
2752
+ if (this.dragFrom != null)
2753
+ this.dragTo = x;
2754
+ this.cursorChange.emit(this.timeAt(x));
2755
+ }
2756
+ leave() {
2757
+ this.hovered = false;
2758
+ this.dragFrom = null;
2759
+ this.dragTo = null;
2760
+ this.cursorChange.emit(null);
2761
+ }
2762
+ // Trascinando sull'area si sceglie la finestra dello zoom, per tutti i pannelli
2763
+ down(event) {
2764
+ if (event.button != 0)
2765
+ return;
2766
+ event.preventDefault();
2767
+ this.dragFrom = this.plotX(event);
2768
+ this.dragTo = null;
2769
+ }
2770
+ up(event) {
2771
+ let from = this.dragFrom;
2772
+ let to = this.plotX(event);
2773
+ this.dragFrom = null;
2774
+ this.dragTo = null;
2775
+ if (from != null && Math.abs(to - from) > 6)
2776
+ this.zoom.emit({ from: this.timeAt(Math.min(from, to)), to: this.timeAt(Math.max(from, to)) });
2777
+ }
2778
+ 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 }); }
2779
+ 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: `
2780
+ <div class="mv2c" [style.height.px]="height" (mousemove)="move($event)" (mouseleave)="leave()" (mousedown)="down($event)" (mouseup)="up($event)">
2781
+ <svg [attr.width]="width" [attr.height]="height" [attr.viewBox]="'0 0 ' + width + ' ' + height">
2782
+ @for (tick of yTicks; track tick.key) {
2783
+ <line class="mv2c-grid" [attr.x1]="left" [attr.x2]="width - right" [attr.y1]="tick.pos" [attr.y2]="tick.pos" />
2784
+ <text class="mv2c-ylabel" [attr.x]="left - 6" [attr.y]="tick.pos + 3">{{tick.label}}</text>
2785
+ }
2786
+ @for (tick of xTicks; track tick.key) {
2787
+ <text class="mv2c-xlabel" [attr.x]="tick.pos" [attr.y]="height - 5">{{tick.label}}</text>
2788
+ }
2789
+ @if (unit) {
2790
+ <text class="mv2c-unit" [attr.x]="4" [attr.y]="10">{{unit}}</text>
2791
+ }
2792
+ @for (mark of markTicks; track mark.key) {
2793
+ <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" />
2794
+ <text class="mv2c-marklabel" [attr.x]="mark.pos + 3" [attr.y]="top + 9">{{mark.label}}</text>
2795
+ }
2796
+ @for (p of paths; track p.key) {
2797
+ <path [attr.d]="p.d" [attr.fill]="p.fill ? p.color : 'none'" [attr.fill-opacity]="p.fill ? p.opacity : null"
2798
+ [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" />
2799
+ }
2800
+ @for (r of rects; track r.key) {
2801
+ <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" />
2802
+ }
2803
+ @for (d of dots; track d.key) {
2804
+ <circle [attr.cx]="d.x" [attr.cy]="d.y" r="2" [attr.fill]="d.color" />
2805
+ }
2806
+ <line class="mv2c-axis" [attr.x1]="left" [attr.x2]="width - right" [attr.y1]="height - bottom" [attr.y2]="height - bottom" />
2807
+ @if (cursorX != null) {
2808
+ <line class="mv2c-cursor" [attr.x1]="cursorX" [attr.x2]="cursorX" [attr.y1]="top" [attr.y2]="height - bottom" />
2809
+ }
2810
+ @if (dragFrom != null && dragTo != null) {
2811
+ <rect class="mv2c-drag" [attr.x]="min(dragFrom, dragTo)" [attr.y]="top" [attr.width]="abs(dragTo - dragFrom)" [attr.height]="height - top - bottom" />
2812
+ }
2813
+ </svg>
2814
+ @if (cursorX != null && tipRows.length > 0 && hovered) {
2815
+ <div class="mv2c-tip" [style.left.px]="cursorX > width / 2 ? null : cursorX + 10" [style.right.px]="cursorX > width / 2 ? width - cursorX + 10 : null">
2816
+ <div class="mv2c-tip-time">{{tipTime}}</div>
2817
+ @for (row of tipRows; track $index) {
2818
+ <div class="mv2c-tip-row"><span class="mv2c-tip-dot" [style.background]="row.color"></span>{{row.label}} <strong>{{row.value}}</strong></div>
2819
+ }
2820
+ </div>
2821
+ }
2822
+ </div>
2823
+ `, 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"] }); }
2824
+ }
2825
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: MetricsV2ChartComponent, decorators: [{
2826
+ type: Component,
2827
+ args: [{ selector: "mv2-chart", template: `
2828
+ <div class="mv2c" [style.height.px]="height" (mousemove)="move($event)" (mouseleave)="leave()" (mousedown)="down($event)" (mouseup)="up($event)">
2829
+ <svg [attr.width]="width" [attr.height]="height" [attr.viewBox]="'0 0 ' + width + ' ' + height">
2830
+ @for (tick of yTicks; track tick.key) {
2831
+ <line class="mv2c-grid" [attr.x1]="left" [attr.x2]="width - right" [attr.y1]="tick.pos" [attr.y2]="tick.pos" />
2832
+ <text class="mv2c-ylabel" [attr.x]="left - 6" [attr.y]="tick.pos + 3">{{tick.label}}</text>
2833
+ }
2834
+ @for (tick of xTicks; track tick.key) {
2835
+ <text class="mv2c-xlabel" [attr.x]="tick.pos" [attr.y]="height - 5">{{tick.label}}</text>
2836
+ }
2837
+ @if (unit) {
2838
+ <text class="mv2c-unit" [attr.x]="4" [attr.y]="10">{{unit}}</text>
2839
+ }
2840
+ @for (mark of markTicks; track mark.key) {
2841
+ <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" />
2842
+ <text class="mv2c-marklabel" [attr.x]="mark.pos + 3" [attr.y]="top + 9">{{mark.label}}</text>
2843
+ }
2844
+ @for (p of paths; track p.key) {
2845
+ <path [attr.d]="p.d" [attr.fill]="p.fill ? p.color : 'none'" [attr.fill-opacity]="p.fill ? p.opacity : null"
2846
+ [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" />
2847
+ }
2848
+ @for (r of rects; track r.key) {
2849
+ <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" />
2850
+ }
2851
+ @for (d of dots; track d.key) {
2852
+ <circle [attr.cx]="d.x" [attr.cy]="d.y" r="2" [attr.fill]="d.color" />
2853
+ }
2854
+ <line class="mv2c-axis" [attr.x1]="left" [attr.x2]="width - right" [attr.y1]="height - bottom" [attr.y2]="height - bottom" />
2855
+ @if (cursorX != null) {
2856
+ <line class="mv2c-cursor" [attr.x1]="cursorX" [attr.x2]="cursorX" [attr.y1]="top" [attr.y2]="height - bottom" />
2857
+ }
2858
+ @if (dragFrom != null && dragTo != null) {
2859
+ <rect class="mv2c-drag" [attr.x]="min(dragFrom, dragTo)" [attr.y]="top" [attr.width]="abs(dragTo - dragFrom)" [attr.height]="height - top - bottom" />
2860
+ }
2861
+ </svg>
2862
+ @if (cursorX != null && tipRows.length > 0 && hovered) {
2863
+ <div class="mv2c-tip" [style.left.px]="cursorX > width / 2 ? null : cursorX + 10" [style.right.px]="cursorX > width / 2 ? width - cursorX + 10 : null">
2864
+ <div class="mv2c-tip-time">{{tipTime}}</div>
2865
+ @for (row of tipRows; track $index) {
2866
+ <div class="mv2c-tip-row"><span class="mv2c-tip-dot" [style.background]="row.color"></span>{{row.label}} <strong>{{row.value}}</strong></div>
2867
+ }
2868
+ </div>
2869
+ }
2870
+ </div>
2871
+ `, 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"] }]
2872
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.NgZone }], propDecorators: { series: [{
2873
+ type: Input
2874
+ }], from: [{
2875
+ type: Input
2876
+ }], to: [{
2877
+ type: Input
2878
+ }], height: [{
2879
+ type: Input
2880
+ }], unit: [{
2881
+ type: Input
2882
+ }], marks: [{
2883
+ type: Input
2884
+ }], cursor: [{
2885
+ type: Input
2886
+ }], format: [{
2887
+ type: Input
2888
+ }], cursorChange: [{
2889
+ type: Output
2890
+ }], zoom: [{
2891
+ type: Output
2892
+ }] } });
2893
+
2894
+ // Angular
2895
+ const STORAGE_KEY$1 = "HotSearchMetricsV2";
2896
+ const MAX_METRICS = 12;
2897
+ // Ogni istanza campiona ogni 15 secondi: più spesso i campioni non cambiano (M19)
2898
+ const REFRESH_MS = 15000;
2899
+ const SELECTION_DELAY_MS = 350;
2900
+ const DATES_DELAY_MS = 600;
2901
+ const TABLE_ROWS = 300;
2902
+ const INSTANCE_COLORS = ["#1e88e5", "#e53935", "#43a047", "#fb8c00", "#8e24aa", "#00897b", "#6d4c41", "#3949ab", "#c0ca33", "#d81b60"];
2903
+ const ITEM_COLORS = ["#3949ab", "#00897b", "#f4511e", "#8e24aa", "#7cb342", "#fdd835", "#6d4c41", "#039be5", "#d81b60", "#546e7a"];
2904
+ // "Active threads time" è un meter in microsecondi di thread attivi: il suo rate al secondo diviso un milione è il numero medio di thread attivi
2905
+ const ACTIVE_THREADS_TIME = "Active threads time";
2906
+ const CPU_USAGE = "CPU Usage";
2907
+ // flatMap non c'è nel lib di compilazione della libreria
2908
+ const flatten = (list, map) => list.reduce((all, t) => all.concat(map(t)), []);
2909
+ const PRESETS = [
2910
+ ["Processo", ["CPU Usage", "Working set", "Private memory", "Thread Pool Queue Length"]],
2911
+ ["API", ["API Call", "Requests", "400 Errors", "500 Errors"]],
2912
+ ["Code asincrone", null],
2913
+ ["Attività", ["Active sessions", "Active threads", ACTIVE_THREADS_TIME]]
2914
+ ];
2915
+ class Mv2CatalogEntry {
2916
+ }
2917
+ class Mv2CatalogGroup {
2918
+ }
2919
+ class Mv2Chart {
2920
+ }
2921
+ class Mv2Panel {
2922
+ }
2923
+ class Mv2TileRow {
2924
+ }
2925
+ class Mv2Tile {
2926
+ }
2927
+ class Mv2Column {
2928
+ }
2929
+ class Mv2TableSection {
2930
+ }
2931
+ // Ricordate fra una visita e l'altra (M11)
2932
+ class MetricsV2Settings {
2933
+ }
2934
+ class AdmMetricsV2Component {
2935
+ constructor(dateExts, lc, localStorage, intercom, route, adms) {
2936
+ this.dateExts = dateExts;
2937
+ this.lc = lc;
2938
+ this.localStorage = localStorage;
2939
+ this.intercom = intercom;
2940
+ this.route = route;
2941
+ this.adms = adms;
2942
+ this.Max = MAX_METRICS;
2943
+ this.Type = MetricV2Type;
2944
+ // Comandi
2945
+ this.source = "live";
2946
+ this.liveMinutes = "30";
2947
+ this.filterfrom = "";
2948
+ this.filterto = "";
2949
+ this.snapFromId = "";
2950
+ this.snapToId = "";
2951
+ // Vuoto = tutto il cluster
2952
+ this.instances = [];
2953
+ this.view = "charts";
2954
+ this.paused = false;
2955
+ // Catalogo e selezione
2956
+ this.catalog = [];
2957
+ this.groups = [];
2958
+ this.search = "";
2959
+ this.selected = [];
2960
+ this.catalogLoading = false;
2961
+ // Snapshot
2962
+ this.snapshots = [];
2963
+ this.historyAvailable = true;
2964
+ this.snapshotsLoaded = false;
2965
+ this.SnapshotOptions = [];
2966
+ this.warnings = [];
2967
+ // Dati
2968
+ this.dataLoading = false;
2969
+ this.panels = [];
2970
+ this.tiles = [];
2971
+ this.tables = [];
2972
+ this.marks = [];
2973
+ this.legend = [];
2974
+ this.viewFrom = null;
2975
+ this.viewTo = null;
2976
+ this.cursor = null;
2977
+ this.Sources = [];
2978
+ this.LiveWindows = [];
2979
+ this.Views = [];
2980
+ this.Instances = [];
2981
+ this.Presets = PRESETS.map(t => t[0]);
2982
+ this.data = new Map();
2983
+ this.seen = new Set();
2984
+ this.hidden = new Set();
2985
+ this.alts = {};
2986
+ this.dataGeneration = 0;
2987
+ this.catalogGeneration = 0;
2988
+ this.visibilityHandler = () => this.visibilityChanged();
2989
+ this.format = (value) => this.number(value);
2990
+ this.be = route.snapshot.params["be"] || "";
2991
+ this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
2992
+ }
2993
+ async ngOnInit() {
2994
+ this.Sources = [new KeyValue("live", "Live"), new KeyValue("period", this.lc.loc("Storico per periodo")), new KeyValue("snap", this.lc.loc("Storico per snapshot"))];
2995
+ this.LiveWindows = [new KeyValue("15", "15 min"), new KeyValue("30", "30 min"), new KeyValue("60", this.lc.loc("1 ora"))];
2996
+ this.Views = [new KeyValue("charts", this.lc.loc("Grafici")), new KeyValue("table", this.lc.loc("Tabella"))];
2997
+ let saved = this.route.snapshot.queryParams["ignoreStorage"] ? null : await firstValueFrom(this.localStorage.getItem(STORAGE_KEY$1), { defaultValue: null });
2998
+ if (saved) {
2999
+ this.selected = (saved.selected ?? []).slice(0, MAX_METRICS);
3000
+ this.source = saved.source ?? this.source;
3001
+ this.instances = saved.instances ?? this.instances;
3002
+ this.liveMinutes = saved.liveMinutes ?? this.liveMinutes;
3003
+ this.view = saved.view ?? this.view;
3004
+ }
3005
+ this.setLastDay();
3006
+ this.adms.getInstances(this.be).then(t => {
3007
+ this.Instances = t ?? [];
3008
+ this.rebuild();
3009
+ });
3010
+ this.loadSnapshots();
3011
+ await this.loadCatalog();
3012
+ this.loadData();
3013
+ this.refreshTimer = setInterval(() => this.refresh(), REFRESH_MS);
3014
+ document.addEventListener("visibilitychange", this.visibilityHandler);
3015
+ }
3016
+ ngOnDestroy() {
3017
+ clearInterval(this.refreshTimer);
3018
+ clearTimeout(this.selectionTimer);
3019
+ clearTimeout(this.datesTimer);
3020
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
3021
+ }
3022
+ // #region Comandi
3023
+ get clusterLabel() {
3024
+ return this.Instances.length ? `${this.lc.loc("Tutto il cluster")} (${this.Instances.length} ${this.lc.loc("istanze")})` : this.lc.loc("Tutto il cluster");
3025
+ }
3026
+ get zone() {
3027
+ return `UTC${dayjs().format("Z")}`;
3028
+ }
3029
+ setSource(id) {
3030
+ if (id != "live" && !this.historyAvailable)
3031
+ return;
3032
+ this.source = id;
3033
+ if (this.source == "snap")
3034
+ this.defaultSnapshots();
3035
+ this.updateWarnings();
3036
+ this.saveSettings();
3037
+ this.loadData();
3038
+ }
3039
+ setLiveMinutes(id) {
3040
+ this.liveMinutes = id;
3041
+ this.saveSettings();
3042
+ this.loadData();
3043
+ }
3044
+ datesChanged() {
3045
+ clearTimeout(this.datesTimer);
3046
+ this.datesTimer = setTimeout(() => {
3047
+ if (this.toDate(this.filterfrom) && this.toDate(this.filterto))
3048
+ this.loadData();
3049
+ }, DATES_DELAY_MS);
3050
+ }
3051
+ snapshotsChanged() {
3052
+ this.updateWarnings();
3053
+ this.loadData();
3054
+ }
3055
+ // Cambiando istanze si ricaricano catalogo, snapshot e dati; le metriche che il catalogo non ha più si tolgono
3056
+ async instancesChanged(ids) {
3057
+ this.instances = ids ?? [];
3058
+ this.saveSettings();
3059
+ this.loadSnapshots();
3060
+ await this.loadCatalog();
3061
+ this.loadData();
3062
+ }
3063
+ setView(id) {
3064
+ this.view = id;
3065
+ this.saveSettings();
3066
+ }
3067
+ togglePause() {
3068
+ this.paused = !this.paused;
3069
+ if (!this.paused)
3070
+ this.refresh();
3071
+ }
3072
+ // #endregion
3073
+ // #region Catalogo e selezione
3074
+ async loadCatalog() {
3075
+ let generation = ++this.catalogGeneration;
3076
+ this.catalogLoading = true;
3077
+ this.catalogError = null;
3078
+ try {
3079
+ let req = this.instances.length ? { instanceIds: this.instances } : { global: true };
3080
+ let res = await this.adms.getMetricsCatalogV2(this.be, req);
3081
+ if (generation != this.catalogGeneration)
3082
+ return;
3083
+ this.catalog = res ?? [];
3084
+ let names = new Set(this.catalog.map(t => t.name));
3085
+ let kept = this.selected.filter(t => names.has(t));
3086
+ if (kept.length != this.selected.length) {
3087
+ this.selected = kept;
3088
+ this.saveSettings();
3089
+ }
3090
+ this.buildGroups();
3091
+ }
3092
+ catch (err) {
3093
+ if (generation == this.catalogGeneration)
3094
+ this.catalogError = this.errorMessage(err);
3095
+ }
3096
+ finally {
3097
+ if (generation == this.catalogGeneration)
3098
+ this.catalogLoading = false;
3099
+ }
3100
+ }
3101
+ // Gruppi per tipo; le voci di un totale stanno sotto il totale, chiuse (M10)
3102
+ buildGroups() {
3103
+ let text = (this.search ?? "").trim().toLowerCase();
3104
+ let matches = (info) => !text || info.name.toLowerCase().includes(text);
3105
+ let parentOf = (info) => this.catalog.filter(t => t != info && info.name.startsWith(t.name + " - ")).sort((a, b) => b.name.length - a.name.length)[0];
3106
+ let opened = new Set(flatten(this.groups, g => g.entries.filter(e => e.open).map(e => e.info.name)));
3107
+ let types = [MetricV2Type.Gauge, MetricV2Type.Counter, MetricV2Type.Meter, MetricV2Type.Histogram, MetricV2Type.Timer];
3108
+ this.groups = types.map(type => {
3109
+ let infos = this.catalog.filter(t => t.metricType == type);
3110
+ let entries = infos.filter(t => !parentOf(t)).map(info => {
3111
+ let items = infos.filter(t => parentOf(t) == info);
3112
+ return { info: info, items: items.filter(t => matches(t) || matches(info)), open: opened.has(info.name) || (!!text && items.some(matches)) };
3113
+ }).filter(t => matches(t.info) || t.items.length > 0);
3114
+ return { type: type, label: MetricV2Type[type], entries: entries, count: infos.length };
3115
+ }).filter(t => t.entries.length > 0);
3116
+ }
3117
+ isSelected(name) {
3118
+ return this.selected.includes(name);
3119
+ }
3120
+ canSelect(name) {
3121
+ return this.isSelected(name) || this.selected.length < MAX_METRICS;
3122
+ }
3123
+ toggleMetric(name) {
3124
+ if (this.isSelected(name))
3125
+ this.selected = this.selected.filter(t => t != name);
3126
+ else if (this.selected.length < MAX_METRICS)
3127
+ this.selected = [...this.selected, name];
3128
+ this.selectionChanged();
3129
+ }
3130
+ addItems(entry) {
3131
+ let missing = [entry.info, ...entry.items].map(t => t.name).filter(t => !this.isSelected(t));
3132
+ this.selected = [...this.selected, ...missing].slice(0, MAX_METRICS);
3133
+ entry.open = true;
3134
+ this.selectionChanged();
3135
+ }
3136
+ clearSelection() {
3137
+ this.selected = [];
3138
+ this.selectionChanged();
3139
+ }
3140
+ // Insiemi pronti (M9): le metriche che il catalogo non ha si ignorano
3141
+ applyPreset(name) {
3142
+ let preset = PRESETS.find(t => t[0] == name);
3143
+ 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);
3144
+ let available = new Set(this.catalog.map(t => t.name));
3145
+ this.selected = names.filter(t => available.has(t)).slice(0, MAX_METRICS);
3146
+ this.selectionChanged();
3147
+ }
3148
+ removeMetric(name) {
3149
+ this.selected = this.selected.filter(t => t != name);
3150
+ this.selectionChanged();
3151
+ }
3152
+ // Più clic di seguito diventano una sola richiesta
3153
+ selectionChanged() {
3154
+ this.saveSettings();
3155
+ clearTimeout(this.selectionTimer);
3156
+ this.selectionTimer = setTimeout(() => this.loadData(), SELECTION_DELAY_MS);
3157
+ }
3158
+ catalogUnit(info) {
3159
+ if (info.name == CPU_USAGE)
3160
+ return "CPU";
3161
+ let unit = info.unit || "";
3162
+ if (info.metricType == MetricV2Type.Timer && info.durationUnit)
3163
+ return [unit, this.durationLabel(info.durationUnit)].filter(t => t).join(" · ");
3164
+ return unit;
3165
+ }
3166
+ addItemsLabel(entry) {
3167
+ return this.lc.loc("Aggiungi le {N} voci").replace("{N}", String(entry.items.length));
3168
+ }
3169
+ emptyLabel(name) {
3170
+ return this.lc.loc("Nessun campione di {metrica} nella finestra scelta").replace("{metrica}", name);
3171
+ }
3172
+ // "2 di 3" se la metrica non c'è su tutte le istanze
3173
+ instancesLabel(info) {
3174
+ let total = this.instances.length || this.Instances.length;
3175
+ return total && info.instances?.length < total ? `${info.instances.length} ${this.lc.loc("di")} ${total}` : "";
3176
+ }
3177
+ // #endregion
3178
+ // #region Snapshot
3179
+ async loadSnapshots() {
3180
+ try {
3181
+ let req = new WorkloadSnapshotsRequest();
3182
+ req.from = new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString();
3183
+ if (this.instances.length)
3184
+ req.instanceIds = this.instances;
3185
+ let res = await this.adms.getWorkloadSnapshots(this.be, req);
3186
+ this.historyAvailable = res?.historyAvailable ?? false;
3187
+ this.snapshots = res?.snapshots ?? [];
3188
+ }
3189
+ catch {
3190
+ // Senza elenco degli snapshot resta il live: le sorgenti storiche restano disponibili per periodo
3191
+ this.snapshots = [];
3192
+ }
3193
+ this.snapshotsLoaded = true;
3194
+ this.SnapshotOptions = this.snapshots.map(t => new KeyValue(String(t.snapId), `${t.snapId} · ${this.local(t.endIntervalTime)}${t.restart ? " ⟲" : ""}${t.manual ? " ✋" : ""}`));
3195
+ if (!this.historyAvailable && this.source != "live") {
3196
+ this.source = "live";
3197
+ this.loadData();
3198
+ }
3199
+ let ids = new Set(this.snapshots.map(t => String(t.snapId)));
3200
+ if (!ids.has(this.snapFromId) || !ids.has(this.snapToId))
3201
+ this.defaultSnapshots();
3202
+ this.updateWarnings();
3203
+ }
3204
+ // Di default le ultime 12 ore di snapshot
3205
+ defaultSnapshots() {
3206
+ if (this.snapshots.length == 0 || (this.snapFromId && this.snapToId && this.snapshots.some(t => String(t.snapId) == this.snapFromId)))
3207
+ return;
3208
+ this.snapToId = String(this.snapshots[0].snapId);
3209
+ this.snapFromId = String(this.snapshots[Math.min(11, this.snapshots.length - 1)].snapId);
3210
+ }
3211
+ get snapshotWindow() {
3212
+ let ids = [+this.snapFromId, +this.snapToId];
3213
+ let first = this.snapshots.find(t => t.snapId == Math.min(...ids));
3214
+ let last = this.snapshots.find(t => t.snapId == Math.max(...ids));
3215
+ return first && last ? { from: this.parseUtc(first.beginIntervalTime), to: this.parseUtc(last.endIntervalTime) } : null;
3216
+ }
3217
+ // M4: un riavvio fra i due snapshot interrompe le serie
3218
+ updateWarnings() {
3219
+ let warnings = [];
3220
+ if (this.source == "snap" && this.snapFromId && this.snapToId) {
3221
+ let lo = Math.min(+this.snapFromId, +this.snapToId);
3222
+ let hi = Math.max(+this.snapFromId, +this.snapToId);
3223
+ this.snapshots.forEach((s, i) => {
3224
+ if (s.restart && s.snapId > lo && s.snapId <= hi) {
3225
+ let previous = this.snapshots[i + 1];
3226
+ let restarted = (s.instances ?? []).filter(t => {
3227
+ let before = previous?.instances?.find(k => k.instanceId == t.instanceId);
3228
+ return before ? before.startupTime != t.startupTime : true;
3229
+ }).map(t => t.instanceId);
3230
+ for (let instance of restarted)
3231
+ 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));
3232
+ }
3233
+ });
3234
+ }
3235
+ this.warnings = warnings;
3236
+ }
3237
+ // #endregion
3238
+ // #region Dati
3239
+ request() {
3240
+ let req = new MetricsV2Request();
3241
+ req.metricNames = [...this.selected];
3242
+ // Mai global=false senza istanze (M6)
3243
+ if (this.instances.length)
3244
+ req.instanceIds = [...this.instances];
3245
+ else
3246
+ req.global = true;
3247
+ if (this.source == "live") {
3248
+ req.realTime = true;
3249
+ req.snapFrom = new Date(Date.now() - +this.liveMinutes * 60000).toISOString();
3250
+ }
3251
+ else if (this.source == "period") {
3252
+ req.realTime = false;
3253
+ req.snapFrom = this.toDate(this.filterfrom)?.toISOString();
3254
+ req.snapTo = this.toDate(this.filterto)?.toISOString();
3255
+ }
3256
+ else {
3257
+ req.realTime = false;
3258
+ req.snapIdFrom = Math.min(+this.snapFromId, +this.snapToId);
3259
+ req.snapIdTo = Math.max(+this.snapFromId, +this.snapToId);
3260
+ }
3261
+ return req;
3262
+ }
3263
+ // Caricamento completo della finestra scelta; senza metriche scelte non parte nessuna chiamata (M8)
3264
+ async loadData() {
3265
+ let generation = ++this.dataGeneration;
3266
+ clearTimeout(this.selectionTimer);
3267
+ this.viewFrom = null;
3268
+ this.viewTo = null;
3269
+ this.dataError = null;
3270
+ if (this.selected.length == 0 || (this.source == "snap" && (!this.snapFromId || !this.snapToId))) {
3271
+ this.data = new Map();
3272
+ this.seen = new Set();
3273
+ this.rebuild();
3274
+ return;
3275
+ }
3276
+ let req = this.request();
3277
+ this.setDomain(req);
3278
+ this.dataLoading = true;
3279
+ try {
3280
+ let res = await this.adms.getMetricsV2(this.be, req);
3281
+ if (generation != this.dataGeneration)
3282
+ return;
3283
+ this.data = new Map();
3284
+ this.seen = new Set();
3285
+ this.merge(res ?? []);
3286
+ this.lastUpdate = new Date();
3287
+ this.rebuild();
3288
+ }
3289
+ catch (err) {
3290
+ if (generation == this.dataGeneration) {
3291
+ this.dataError = this.errorMessage(err);
3292
+ this.data = new Map();
3293
+ this.rebuild();
3294
+ }
3295
+ }
3296
+ finally {
3297
+ if (generation == this.dataGeneration)
3298
+ this.dataLoading = false;
3299
+ }
3300
+ }
3301
+ // M19: solo i campioni nuovi, da quello più vecchio fra gli ultimi delle istanze in pagina; sullo storico niente aggiornamento
3302
+ async refresh() {
3303
+ if (this.source != "live" || this.paused || document.hidden || this.dataLoading || this.selected.length == 0)
3304
+ return;
3305
+ let lastByInstance = new Map();
3306
+ this.data.forEach(byInstance => byInstance.forEach((list, instance) => {
3307
+ let last = list.length ? this.time(list[list.length - 1]) : null;
3308
+ if (last != null)
3309
+ lastByInstance.set(instance, Math.max(lastByInstance.get(instance) ?? 0, last));
3310
+ }));
3311
+ if (lastByInstance.size == 0)
3312
+ return this.loadData();
3313
+ let generation = this.dataGeneration;
3314
+ let req = this.request();
3315
+ req.snapFrom = new Date(Math.min(...lastByInstance.values())).toISOString();
3316
+ try {
3317
+ let res = await this.adms.getMetricsV2(this.be, req);
3318
+ if (generation != this.dataGeneration)
3319
+ return;
3320
+ this.merge(res ?? []);
3321
+ this.setDomain(this.request());
3322
+ // Fuori dalla finestra live si scartano
3323
+ this.data.forEach(byInstance => byInstance.forEach((list, instance) => byInstance.set(instance, list.filter(t => this.time(t) >= this.domainFrom))));
3324
+ this.lastUpdate = new Date();
3325
+ this.rebuild();
3326
+ }
3327
+ catch {
3328
+ // Un aggiornamento mancato si recupera al giro dopo
3329
+ }
3330
+ }
3331
+ // Nella fusione si tiene la versione già in pagina dei campioni ripetuti, che ha le differenze (chiave istanza, metrica, istante)
3332
+ merge(samples) {
3333
+ for (let s of samples) {
3334
+ let key = `${s.instanceId}|${s.name}|${s.sampleTime}`;
3335
+ if (this.seen.has(key))
3336
+ continue;
3337
+ this.seen.add(key);
3338
+ if (!this.data.has(s.name))
3339
+ this.data.set(s.name, new Map());
3340
+ let byInstance = this.data.get(s.name);
3341
+ let instance = s.instanceId ?? "";
3342
+ if (!byInstance.has(instance))
3343
+ byInstance.set(instance, []);
3344
+ byInstance.get(instance).push(s);
3345
+ }
3346
+ this.data.forEach(byInstance => byInstance.forEach(list => list.sort((a, b) => this.time(a) - this.time(b))));
3347
+ }
3348
+ setDomain(req) {
3349
+ if (this.source == "live") {
3350
+ this.domainTo = Date.now();
3351
+ this.domainFrom = this.domainTo - +this.liveMinutes * 60000;
3352
+ }
3353
+ else if (this.source == "period") {
3354
+ this.domainFrom = new Date(req.snapFrom ?? Date.now() - 24 * 3600 * 1000).getTime();
3355
+ this.domainTo = new Date(req.snapTo ?? Date.now()).getTime();
3356
+ }
3357
+ else {
3358
+ let window = this.snapshotWindow;
3359
+ this.domainFrom = window?.from?.getTime() ?? Date.now() - 12 * 3600 * 1000;
3360
+ // I campioni sono alla fine degli snapshot (ogni istanza con i suoi secondi)
3361
+ this.domainTo = (window?.to?.getTime() ?? Date.now()) + 60000;
3362
+ }
3363
+ }
3364
+ // #endregion
3365
+ // #region Grafici, tessere e tabella
3366
+ get chartFrom() { return this.viewFrom ?? this.domainFrom; }
3367
+ get chartTo() { return this.viewTo ?? this.domainTo; }
3368
+ zoomTo(range) {
3369
+ this.viewFrom = range.from;
3370
+ this.viewTo = range.to;
3371
+ }
3372
+ resetZoom() {
3373
+ this.viewFrom = null;
3374
+ this.viewTo = null;
3375
+ }
3376
+ toggleInstance(instance) {
3377
+ if (this.hidden.has(instance))
3378
+ this.hidden.delete(instance);
3379
+ else
3380
+ this.hidden.add(instance);
3381
+ this.rebuild();
3382
+ }
3383
+ setAlt(panel, alt) {
3384
+ this.alts[panel.info.name] = alt;
3385
+ this.rebuild();
3386
+ }
3387
+ info(name) {
3388
+ return this.catalog.find(t => t.name == name) ?? { name: name, metricType: MetricV2Type.Gauge, unit: "", rateUnit: null, durationUnit: null, instances: [] };
3389
+ }
3390
+ // Colore fisso per istanza, lo stesso in tutti i pannelli
3391
+ color(instance) {
3392
+ let all = [...new Set([...this.Instances.map(t => t.id), ...flatten(this.catalog, t => t.instances ?? [])])].sort();
3393
+ let index = all.indexOf(instance);
3394
+ return INSTANCE_COLORS[(index >= 0 ? index : all.length) % INSTANCE_COLORS.length];
3395
+ }
3396
+ rebuild() {
3397
+ let instances = new Set();
3398
+ this.data.forEach(byInstance => byInstance.forEach((_, instance) => instances.add(instance)));
3399
+ this.legend = [...instances].sort().map(t => ({ instance: t, color: this.color(t), hidden: this.hidden.has(t) }));
3400
+ this.marks = this.buildMarks();
3401
+ this.panels = this.selected.map(name => this.buildPanel(this.info(name)));
3402
+ this.tiles = this.selected.map(name => this.buildTile(this.info(name)));
3403
+ this.tables = this.selected.map(name => this.buildTable(this.info(name), this.tables.find(t => t.info.name == name)));
3404
+ }
3405
+ visibleSeries(name) {
3406
+ let byInstance = this.data.get(name);
3407
+ return byInstance ? [...byInstance.entries()].filter(t => !this.hidden.has(t[0]) && t[1].length > 0).sort((a, b) => a[0].localeCompare(b[0])) : [];
3408
+ }
3409
+ // M14: riavvii (un campione senza intervallo che non è il primo della serie) e, sullo storico per snapshot, gli id degli snapshot
3410
+ buildMarks() {
3411
+ let marks = [];
3412
+ let seen = new Set();
3413
+ this.data.forEach(byInstance => byInstance.forEach((list, instance) => list.forEach((s, i) => {
3414
+ if (i > 0 && s.intervalSeconds == null) {
3415
+ let t = this.time(s);
3416
+ let key = `${instance}|${Math.round(t / 60000)}`;
3417
+ if (!seen.has(key)) {
3418
+ seen.add(key);
3419
+ marks.push({ t: t, label: this.lc.loc("riavvio"), kind: "restart" });
3420
+ }
3421
+ }
3422
+ })));
3423
+ if (this.source == "snap") {
3424
+ let bySnap = new Map();
3425
+ this.data.forEach(byInstance => byInstance.forEach(list => list.forEach(s => {
3426
+ if (s.snapId != null && !bySnap.has(s.snapId))
3427
+ bySnap.set(s.snapId, this.time(s));
3428
+ })));
3429
+ [...bySnap.entries()].sort((a, b) => a[1] - b[1]).forEach(([id, t]) => marks.push({ t: t, label: String(id), kind: "snapshot" }));
3430
+ }
3431
+ return marks;
3432
+ }
3433
+ // 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
3434
+ points(list, value, upper) {
3435
+ let times = list.map(t => this.time(t));
3436
+ let gaps = times.slice(1).map((t, i) => t - times[i]).sort((a, b) => a - b);
3437
+ let typical = gaps.length ? gaps[Math.floor(gaps.length / 2)] : 0;
3438
+ let out = [];
3439
+ let pendingGap = false;
3440
+ list.forEach((s, i) => {
3441
+ let v = value(s);
3442
+ let gap = i > 0 && (s.intervalSeconds == null || (typical > 0 && times[i] - times[i - 1] > 2 * typical));
3443
+ if (v == null || isNaN(v)) {
3444
+ pendingGap = true;
3445
+ return;
3446
+ }
3447
+ out.push({ t: times[i], v: v, v2: upper ? upper(s) : undefined, gap: gap || pendingGap });
3448
+ pendingGap = false;
3449
+ });
3450
+ return out;
3451
+ }
3452
+ // Valore principale per tipo (tessere e grafico di default)
3453
+ main(info, s) {
3454
+ switch (info.metricType) {
3455
+ case MetricV2Type.Gauge: return s.value;
3456
+ case MetricV2Type.Counter: return s.count;
3457
+ case MetricV2Type.Meter: return info.name == ACTIVE_THREADS_TIME ? (s.ratePerSecond == null ? null : s.ratePerSecond / 1000000) : s.ratePerSecond;
3458
+ case MetricV2Type.Histogram: return s.median;
3459
+ case MetricV2Type.Timer: return s.intervalMean;
3460
+ }
3461
+ return s.value;
3462
+ }
3463
+ mainUnit(info) {
3464
+ switch (info.metricType) {
3465
+ case MetricV2Type.Gauge: return info.name == CPU_USAGE ? "CPU" : info.unit || "";
3466
+ case MetricV2Type.Counter: return info.unit || "";
3467
+ case MetricV2Type.Meter: return info.name == ACTIVE_THREADS_TIME ? this.lc.loc("thread") : `${info.unit || ""}/s`;
3468
+ case MetricV2Type.Histogram: return info.unit || "";
3469
+ case MetricV2Type.Timer: return this.durationLabel(info.durationUnit);
3470
+ }
3471
+ return info.unit || "";
3472
+ }
3473
+ buildPanel(info) {
3474
+ let series = this.visibleSeries(info.name);
3475
+ let items = this.itemsOf(info);
3476
+ let alts = [];
3477
+ switch (info.metricType) {
3478
+ case MetricV2Type.Counter:
3479
+ alts = [new KeyValue("level", this.lc.loc("Livello")), new KeyValue("delta", this.lc.loc("Variazione"))];
3480
+ if (items.length) {
3481
+ alts.push(new KeyValue("items", this.lc.loc("Voci")));
3482
+ if (this.source == "live")
3483
+ alts.push(new KeyValue("share", this.lc.loc("Quote")));
3484
+ }
3485
+ break;
3486
+ case MetricV2Type.Meter:
3487
+ alts = [new KeyValue("rate", this.lc.loc("Al secondo")), new KeyValue("moving", this.lc.loc("Rate mobili"))];
3488
+ if (items.length)
3489
+ alts.push(new KeyValue("items", this.lc.loc("Voci")));
3490
+ break;
3491
+ case MetricV2Type.Histogram:
3492
+ alts = [new KeyValue("percentiles", this.lc.loc("Percentili")), new KeyValue("rate", this.lc.loc("Frequenza"))];
3493
+ break;
3494
+ case MetricV2Type.Timer:
3495
+ alts = [new KeyValue("duration", this.lc.loc("Chiamate e durata")), new KeyValue("active", this.lc.loc("In corso"))];
3496
+ break;
3497
+ }
3498
+ let alt = alts.find(t => t.id == this.alts[info.name])?.id ?? alts[0]?.id ?? "";
3499
+ return { info: info, alts: alts, alt: alt, charts: series.length ? this.buildCharts(info, alt, series, items) : [], empty: series.length == 0 };
3500
+ }
3501
+ // Le voci scelte di un totale, per l'area impilata e le quote
3502
+ itemsOf(info) {
3503
+ if (info.metricType != MetricV2Type.Counter && info.metricType != MetricV2Type.Meter)
3504
+ return [];
3505
+ 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));
3506
+ }
3507
+ buildCharts(info, alt, series, items) {
3508
+ let line = (key, instance, points, extra = {}) => ({ key: `${key}|${instance}`, label: instance, color: this.color(instance), kind: "line", points: points, ...extra });
3509
+ let unit = info.unit || "";
3510
+ let rateUnit = `${unit}${this.rateLabel(info.rateUnit)}`;
3511
+ switch (info.metricType) {
3512
+ case MetricV2Type.Gauge:
3513
+ return [{ key: "value", unit: this.mainUnit(info), height: 170, series: series.map(([i, l]) => line("value", i, this.points(l, s => s.value))) }];
3514
+ case MetricV2Type.Counter:
3515
+ if (alt == "delta")
3516
+ return [{ key: "delta", unit: unit, height: 170, series: series.map(([i, l]) => line("delta", i, this.points(l, s => s.deltaCount), { kind: "bars" })) }];
3517
+ if (alt == "items")
3518
+ return [{ key: "items", unit: unit, height: 190, series: this.stacked(items, s => s.count) }];
3519
+ if (alt == "share")
3520
+ return [{ key: "share", unit: "%", height: 170, series: this.itemLines(items, s => s.percent) }];
3521
+ return [{ key: "level", unit: unit, height: 170, series: series.map(([i, l]) => line("level", i, this.points(l, s => s.count))) }];
3522
+ case MetricV2Type.Meter:
3523
+ if (alt == "moving")
3524
+ return [{
3525
+ key: "moving", unit: rateUnit, height: 170,
3526
+ series: flatten(series, ([i, l]) => [
3527
+ line("m1", i, this.points(l, s => s.oneMinuteRate), { label: `${i} · 1 min` }),
3528
+ line("m5", i, this.points(l, s => s.fiveMinuteRate), { label: `${i} · 5 min`, dash: "5 3" }),
3529
+ line("m15", i, this.points(l, s => s.fifteenMinuteRate), { label: `${i} · 15 min`, dash: "1 3" })
3530
+ ])
3531
+ }];
3532
+ if (alt == "items")
3533
+ return [{ key: "items", unit: `${unit}/s`, height: 190, series: this.stacked(items, s => s.ratePerSecond) }];
3534
+ 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)))) }];
3535
+ case MetricV2Type.Histogram:
3536
+ if (alt == "rate")
3537
+ return [{ key: "rate", unit: `${unit}/s`, height: 170, series: series.map(([i, l]) => line("rate", i, this.points(l, s => s.ratePerSecond))) }];
3538
+ return [{ key: "percentiles", unit: unit, height: 190, series: flatten(series, ([i, l]) => this.percentileBand(i, l, s => s.median, `${i} · ${this.lc.loc("mediana")}`)) }];
3539
+ case MetricV2Type.Timer:
3540
+ if (alt == "active")
3541
+ 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" })) }];
3542
+ return [
3543
+ { key: "calls", unit: `${unit}/s`, height: 120, series: series.map(([i, l]) => line("calls", i, this.points(l, s => s.ratePerSecond))) },
3544
+ { 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)) }
3545
+ ];
3546
+ }
3547
+ return [];
3548
+ }
3549
+ // Fascia dei percentili del reservoir per istanza: per l'histogram mediana con max a punti, per il timer la durata media dell'intervallo
3550
+ percentileBand(instance, list, center, label, timer = false) {
3551
+ let color = this.color(instance);
3552
+ let ret = [];
3553
+ if (!timer)
3554
+ 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 });
3555
+ 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 });
3556
+ 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 });
3557
+ ret.push({ key: `c|${instance}`, label: label, color: color, kind: "line", points: this.points(list, center) });
3558
+ if (!timer)
3559
+ ret.push({ key: `max|${instance}`, label: `${instance} · max`, color: color, kind: "dots", points: this.points(list, s => s.max) });
3560
+ return ret;
3561
+ }
3562
+ // Le voci di un totale sommate sulle istanze visibili, campione per campione (live: a 15 secondi; storico: per snapshot)
3563
+ bucketed(item, value, average) {
3564
+ let buckets = new Map();
3565
+ for (let [, list] of this.visibleSeries(item.name))
3566
+ for (let s of list) {
3567
+ let v = value(s);
3568
+ if (v == null)
3569
+ continue;
3570
+ let key = s.snapId ?? Math.round(this.time(s) / REFRESH_MS);
3571
+ let b = buckets.get(key) ?? { t: this.time(s), v: 0, n: 0 };
3572
+ b.v += v;
3573
+ b.n++;
3574
+ buckets.set(key, b);
3575
+ }
3576
+ return new Map([...buckets.entries()].map(([k, b]) => [k, { t: b.t, v: average ? b.v / b.n : b.v }]));
3577
+ }
3578
+ stacked(items, value) {
3579
+ let perItem = items.map(t => this.bucketed(t, value, false));
3580
+ let keys = [...new Set(flatten(perItem, t => [...t.keys()]))].sort((a, b) => a - b);
3581
+ let base = new Map(keys.map(k => [k, 0]));
3582
+ let bands = items.map((item, i) => {
3583
+ let points = keys.filter(k => perItem[i].has(k)).map(k => {
3584
+ let b = perItem[i].get(k);
3585
+ let lo = base.get(k);
3586
+ base.set(k, lo + b.v);
3587
+ return { t: b.t, v: lo, v2: lo + b.v };
3588
+ });
3589
+ let label = item.name.substring(item.name.lastIndexOf(" - ") + 3);
3590
+ return { key: `stack|${item.name}`, label: label, color: ITEM_COLORS[i % ITEM_COLORS.length], kind: "band", points: points, opacity: 0.55, tooltip: false };
3591
+ });
3592
+ // Linee invisibili solo per il riquadro dei valori: il valore della voce, non la somma impilata
3593
+ let tips = items.map((item, i) => ({
3594
+ key: `stacktip|${item.name}`, label: item.name.substring(item.name.lastIndexOf(" - ") + 3), color: ITEM_COLORS[i % ITEM_COLORS.length], kind: "line",
3595
+ points: [...perItem[i].values()].sort((a, b) => a.t - b.t).map(b => ({ t: b.t, v: b.v })), opacity: 0
3596
+ }));
3597
+ return bands.reverse().concat(tips);
3598
+ }
3599
+ itemLines(items, value) {
3600
+ return items.map((item, i) => ({
3601
+ key: `share|${item.name}`, label: item.name.substring(item.name.lastIndexOf(" - ") + 3), color: ITEM_COLORS[i % ITEM_COLORS.length], kind: "line",
3602
+ points: [...this.bucketed(item, value, true).values()].sort((a, b) => a.t - b.t).map(b => ({ t: b.t, v: b.v }))
3603
+ }));
3604
+ }
3605
+ // M17: valore principale dell'ultimo campione di ogni istanza, mini grafico, istante del campione
3606
+ buildTile(info) {
3607
+ let rows = [];
3608
+ let sparks = [];
3609
+ let latest = 0;
3610
+ for (let [instance, list] of this.visibleSeries(info.name)) {
3611
+ let last = [...list].reverse().find(t => this.main(info, t) != null);
3612
+ if (!last)
3613
+ continue;
3614
+ latest = Math.max(latest, this.time(last));
3615
+ rows.push({
3616
+ instance: instance, color: this.color(instance), value: `${this.number(this.main(info, last))} ${this.mainUnit(info)}`.trim(),
3617
+ extra: info.metricType == MetricV2Type.Meter ? `${this.number(last.count)} ${this.lc.loc("dall'avvio")}` : ""
3618
+ });
3619
+ let points = this.points(list.slice(-80), s => this.main(info, s));
3620
+ if (points.length > 1) {
3621
+ let lo = Math.min(...points.map(t => t.v));
3622
+ let hi = Math.max(...points.map(t => t.v));
3623
+ let t0 = points[0].t;
3624
+ let t1 = points[points.length - 1].t;
3625
+ 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("");
3626
+ sparks.push({ d: d, color: this.color(instance) });
3627
+ }
3628
+ }
3629
+ return { info: info, rows: rows, sparks: sparks, time: latest ? dayjs(latest).format("HH:mm:ss") : "" };
3630
+ }
3631
+ // M18: colonne comuni e del tipo; ordinabile; esportabile
3632
+ buildTable(info, previous) {
3633
+ let unit = info.unit ? ` (${info.unit})` : "";
3634
+ let dur = info.durationUnit ? ` (${this.durationLabel(info.durationUnit)})` : "";
3635
+ let columns = [
3636
+ { key: "instance", label: this.lc.loc("Istanza"), num: false },
3637
+ { key: "time", label: `${this.lc.loc("Istante")} (${this.zone})`, num: false },
3638
+ { key: "interval", label: this.lc.loc("Intervallo (s)"), num: true }
3639
+ ];
3640
+ if (this.source != "live")
3641
+ columns.push({ key: "snapId", label: this.lc.loc("Snapshot"), num: true });
3642
+ let typed = [];
3643
+ switch (info.metricType) {
3644
+ case MetricV2Type.Gauge:
3645
+ typed = [["value", `${this.lc.loc("Valore")}${info.name == CPU_USAGE ? " (CPU)" : unit}`, s => s.value]];
3646
+ break;
3647
+ case MetricV2Type.Counter:
3648
+ 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]];
3649
+ break;
3650
+ case MetricV2Type.Meter:
3651
+ typed = [["count", `${this.lc.loc("Conteggio")}${unit}`, s => s.count], ["rate", `${this.lc.loc("Al secondo")} (${this.mainUnit(info)})`, s => this.main(info, s)],
3652
+ ["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]];
3653
+ break;
3654
+ case MetricV2Type.Histogram:
3655
+ 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]];
3656
+ break;
3657
+ case MetricV2Type.Timer:
3658
+ 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],
3659
+ ["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]];
3660
+ break;
3661
+ }
3662
+ columns.push(...typed.map(([key, label]) => ({ key: key, label: label, num: true })));
3663
+ let rows = flatten(this.visibleSeries(info.name), ([instance, list]) => list.map(s => {
3664
+ let row = { instance: instance, time: this.time(s), utc: s.sampleTime, interval: s.intervalSeconds, snapId: s.snapId };
3665
+ typed.forEach(([key, , value]) => row[key] = value(s));
3666
+ return row;
3667
+ }));
3668
+ let section = { info: info, columns: columns, rows: [], total: rows.length, sortKey: previous?.sortKey ?? "time", sortDesc: previous?.sortDesc ?? true };
3669
+ section.rows = this.sortRows(rows, section);
3670
+ return section;
3671
+ }
3672
+ sortBy(section, column) {
3673
+ section.sortDesc = section.sortKey == column.key ? !section.sortDesc : column.num || column.key == "time";
3674
+ section.sortKey = column.key;
3675
+ let rebuilt = this.buildTable(section.info, section);
3676
+ section.rows = rebuilt.rows;
3677
+ }
3678
+ sortRows(rows, section) {
3679
+ let key = section.sortKey;
3680
+ let sign = section.sortDesc ? -1 : 1;
3681
+ return rows.sort((a, b) => {
3682
+ let x = a[key];
3683
+ let y = b[key];
3684
+ if (x == null && y == null)
3685
+ return 0;
3686
+ if (x == null)
3687
+ return 1;
3688
+ if (y == null)
3689
+ return -1;
3690
+ return (typeof x == "string" ? x.localeCompare(y) : x - y) * sign;
3691
+ });
3692
+ }
3693
+ shownRows(section) {
3694
+ return section.rows.slice(0, TABLE_ROWS);
3695
+ }
3696
+ cell(row, column) {
3697
+ let v = row[column.key];
3698
+ if (column.key == "time")
3699
+ return dayjs(v).format("DD/MM HH:mm:ss");
3700
+ if (column.key == "instance" || column.key == "snapId")
3701
+ return v ?? "—";
3702
+ return this.number(v);
3703
+ }
3704
+ // CSV delle righe della metrica, con gli orari in UTC e le unità nelle intestazioni
3705
+ exportCsv(section) {
3706
+ let quote = (v) => {
3707
+ let s = v == null ? "" : String(v);
3708
+ return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
3709
+ };
3710
+ let columns = section.columns.map(t => t.key == "time" ? { ...t, label: `${this.lc.loc("Istante")} (UTC)` } : t);
3711
+ let lines = [columns.map(t => quote(t.label)).join(",")];
3712
+ for (let row of section.rows)
3713
+ lines.push(columns.map(t => quote(t.key == "time" ? this.isoUtc(row["utc"]) : row[t.key])).join(","));
3714
+ let blob = new Blob(["" + lines.join("\r\n")], { type: "text/csv;charset=utf-8" });
3715
+ let url = URL.createObjectURL(blob);
3716
+ let link = document.createElement("a");
3717
+ link.href = url;
3718
+ link.download = `metriche_${section.info.name.replace(/[^\w.-]+/g, "-")}.csv`;
3719
+ document.body.appendChild(link);
3720
+ link.click();
3721
+ link.remove();
3722
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
3723
+ }
3724
+ // #endregion
3725
+ // #region Supporto
3726
+ visibilityChanged() {
3727
+ // Tornando sulla pagina si recupera subito invece di aspettare il giro dopo
3728
+ if (!document.hidden)
3729
+ this.refresh();
3730
+ }
3731
+ errorMessage(err) {
3732
+ return err?.status == 542 ? this.adms.errorText(err) : `${this.lc.loc("Errore")} ${err?.status ?? ""}: ${this.adms.errorText(err)}`;
3733
+ }
3734
+ number(value) {
3735
+ if (value == null || isNaN(value))
3736
+ return "—";
3737
+ let abs = Math.abs(value);
3738
+ let digits = abs >= 1000 ? "1.0-0" : abs >= 100 ? "1.0-1" : abs >= 1 ? "1.0-2" : "1.0-3";
3739
+ return formatNumber(value, "it", digits);
3740
+ }
3741
+ durationLabel(unit) {
3742
+ switch (unit) {
3743
+ case "Nanoseconds": return "ns";
3744
+ case "Microseconds": return "µs";
3745
+ case "Milliseconds": return "ms";
3746
+ case "Seconds": return "s";
3747
+ case "Minutes": return "min";
3748
+ case "Hours": return "h";
3749
+ }
3750
+ return unit ?? "";
3751
+ }
3752
+ rateLabel(unit) {
3753
+ switch (unit) {
3754
+ case "Seconds": return "/s";
3755
+ case "Minutes": return "/min";
3756
+ case "Hours": return "/h";
3757
+ case "Days": return "/g";
3758
+ }
3759
+ return unit ? `/${unit}` : "";
3760
+ }
3761
+ // 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)
3762
+ parseUtc(value) {
3763
+ let m = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})$/.exec(value ?? "");
3764
+ return m ? new Date(Date.UTC(+m[3], +m[2] - 1, +m[1], +m[4], +m[5], +m[6])) : value ? new Date(value) : null;
3765
+ }
3766
+ time(s) {
3767
+ return this.parseUtc(s.sampleTime)?.getTime() ?? 0;
3768
+ }
3769
+ isoUtc(value) {
3770
+ return this.parseUtc(value)?.toISOString().replace(".000Z", "Z") ?? "";
3771
+ }
3772
+ local(value) {
3773
+ let date = typeof value == "string" ? this.parseUtc(value) : value;
3774
+ return date ? dayjs(date).format("DD/MM HH:mm") : "—";
3775
+ }
3776
+ hhmmss(date) {
3777
+ return date ? dayjs(date).format("HH:mm:ss") : "";
3778
+ }
3779
+ setLastDay() {
3780
+ let now = new Date();
3781
+ this.filterto = this.fmt(now);
3782
+ this.filterfrom = this.fmt(new Date(now.getTime() - 24 * 3600 * 1000));
3783
+ }
3784
+ toDate(value) {
3785
+ return value ? this.dateExts.getDateConvertion(value).toDate() : null;
3786
+ }
3787
+ fmt(date) {
3788
+ return dayjs(date).format(this.lc.token("getFullDateDisplayFormat"));
3789
+ }
3790
+ saveSettings() {
3791
+ let settings = { selected: this.selected, source: this.source, instances: this.instances, liveMinutes: this.liveMinutes, view: this.view };
3792
+ this.localStorage.setItem(STORAGE_KEY$1, settings).subscribe();
3793
+ }
3794
+ 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 }); }
3795
+ 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;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" }] }); }
3796
+ }
3797
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmMetricsV2Component, decorators: [{
3798
+ type: Component,
3799
+ 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;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"] }]
3800
+ }], ctorParameters: () => [{ type: i1$1.DateService }, { type: i1.LocalizationService }, { type: i3.PreferencesService }, { type: i1$1.InterComService }, { type: i4.ActivatedRoute }, { type: AdminModuleBaseService }] });
3801
+
3802
+ class TopActivitiesDataView extends AppSearch {
3803
+ constructor() { super(); }
3804
+ }
3805
+
3806
+ class AdmTopActivitieLoc extends LocalizationService {
3807
+ constructor(injector) {
3808
+ super(injector);
3809
+ super.set("it->en", "Sessioni Attive", ["Active Sessions"]);
3810
+ super.set("it->en", "Prime Istruzioni", ["Top Nodes"]);
3811
+ super.set("it->en", "Prime Sessioni", ["Top Sessions"]);
3812
+ super.set("it->en", "Carico i Dati", ["Loading Data"]);
3813
+ super.set("it->en", "Attività Principali", ["Top Activities"]);
3814
+ super.set("it->en", "Descrizione Nodo", ["Node Description"]);
3815
+ super.set("it->en", "Nodo", ["Node"]);
3816
+ super.set("it->en", "Attività", ["Activity"]);
3817
+ super.set("it->en", "Limite", ["Limit"]);
3818
+ super.set("it->en", "Tipo Nodo", ["Node Type"]);
3819
+ super.set("it->en", "Utente", ["User"]);
3820
+ super.set("it->en", "Tenant", ["Tenant"]);
3821
+ super.set("it->en", "Processo", ["Process"]);
3822
+ super.set("it->en", "Aggiorna ogni", ["Update every"]);
3823
+ super.set("it->en", "secondo/i", ["second/s"]);
3824
+ super.set("it->en", "Thread Massimi", ["Maximum Threads"]);
3825
+ super.set("it->en", "CPU", ["CPUs"]);
3826
+ super.set("it->en", "Classe Attesa", ["Wait Class Name"]);
3827
+ super.set("it->en", "Seriale", ["Serial"]);
3828
+ super.set("it->en", "Stato", ["Status"]);
3829
+ super.set("it->en", "Tipo", ["Type"]);
3830
+ super.set("it->en", "Utente", ["User"]);
3831
+ }
3832
+ 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 }); }
3833
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivitieLoc }); }
3834
+ }
3835
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivitieLoc, decorators: [{
3836
+ type: Injectable
3837
+ }], ctorParameters: () => [{ type: i0.Injector }] });
3838
+
3839
+ // Angular
3840
+ class AdmTopActivitiesComponent {
3841
+ constructor(dateExts, lc, localStorage, location, intercom, route, adms, mod) {
3842
+ this.dateExts = dateExts;
3843
+ this.lc = lc;
3844
+ this.localStorage = localStorage;
3845
+ this.location = location;
3846
+ this.intercom = intercom;
3847
+ this.route = route;
3848
+ this.adms = adms;
3849
+ this.mod = mod;
3850
+ this.searchView = new TopActivitiesDataView();
3851
+ this.autoUpdate = false;
3852
+ this.seconds = "5";
3853
+ this.rtWindow = null;
3854
+ this.destroyed$ = new ReplaySubject(1);
3855
+ this.seriesPropertiesMapping = {};
3856
+ this.propertiesSeriesMapping = {
3857
+ ["cputime"]: "CPU",
3858
+ ["waittime"]: "Wait",
3859
+ ["scripttime"]: "Scripting",
3860
+ ["dbtime"]: "DB",
3861
+ ["nettime"]: "Network",
3862
+ ["throttletime"]: "Throttling",
3863
+ ["sleeptime"]: "Sleep",
3864
+ ["iotime"]: "IO"
3865
+ };
3866
+ this.seriesPropertiesMapping2 = {};
3867
+ this.be = route.snapshot.params["be"] || "";
3868
+ this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
3869
+ }
3870
+ ngOnInit() {
3871
+ // Ogni minuto calcolo qual è la finestra del realTime
3872
+ // interval(60 * 1000).pipe(takeUntil(this.destroyed$)).subscribe(_ => {
3873
+ // this.updateRealTimeWindow();
3874
+ // });
3875
+ this.storageSub = this.route.queryParams.subscribe(params => {
3876
+ this.Init(params["ignoreStorage"]);
3877
+ });
3878
+ }
3879
+ async updateRealTimeWindow() {
3880
+ let data = new TopActivitiesDataView();
3881
+ data.filterbe = this.be;
3882
+ data = await this.adms.getTopActivities(this.be, data);
3883
+ let reference = data.items[0];
3884
+ for (let i = 0; i < reference.actsessions.length; i++) {
3885
+ let itemref = reference.actsessions[i];
3886
+ let theD = this.dateExts.getDateConvertion(itemref.sampletime);
3887
+ if (!this.rtWindow)
3888
+ this.rtWindow = { from: theD, to: theD };
3889
+ else {
3890
+ if (theD.isBefore(this.rtWindow.from))
3891
+ this.rtWindow.from = theD;
3892
+ if (theD.isAfter(this.rtWindow.to))
3893
+ this.rtWindow.to = theD;
3894
+ }
3895
+ }
3896
+ }
3897
+ Init(ignoreStore) {
3898
+ this.doInit(ignoreStore);
3899
+ }
3900
+ async doInit(ignoreStorage) {
3901
+ let item = await this.localStorage.getItem("HotSearchTopAct").toPromise();
3902
+ if (!ignoreStorage && item) {
3903
+ this.init(item);
3904
+ }
3905
+ else {
3906
+ this.init(null);
3907
+ this.location.replaceState(`/pages/${this.mod}/topact`);
3908
+ this.route.snapshot.queryParams = [];
3909
+ await this.localStorage.removeItem("HotSearchTopAct").toPromise();
3910
+ }
3911
+ }
3912
+ init(HotPotato) {
3913
+ if (HotPotato) {
3914
+ this.searchView = HotPotato;
3915
+ this.POSTGetItems(this.searchView, false);
3916
+ }
3917
+ else {
3918
+ this.searchView = new TopActivitiesDataView();
3919
+ this.sendSearch();
3920
+ }
3921
+ }
3922
+ ngOnDestroy() {
3923
+ this.destroyed$.next(true);
3924
+ this.destroyed$.complete();
3925
+ if (this.storageSub)
3926
+ this.storageSub.unsubscribe();
3927
+ }
3928
+ async sendSearch(resetPage = true, update = false, searchViewOverride = null) {
3929
+ let sv = searchViewOverride ?? this.searchView;
3930
+ if ((sv.filterfrom || sv.filterto) && !this.rtWindow)
3931
+ await this.updateRealTimeWindow();
3932
+ let dateRequestOnRealtimeWindow = sv.filterfrom && this.dateExts.getDateConvertion(sv.filterfrom).isAfter(this.rtWindow.from) &&
3933
+ sv.filterto && this.dateExts.getDateConvertion(sv.filterto).isBefore(this.rtWindow.to);
3934
+ sv.filterrealtime = dateRequestOnRealtimeWindow;
3935
+ sv.searchinprogress = true;
3936
+ this.POSTGetItems(sv, update);
3937
+ }
3938
+ async POSTGetItems(data, update) {
3939
+ data.filterbe = this.be;
3940
+ let res = await this.adms.getTopActivities(this.be, data);
3941
+ //Se sono in modalità di update non devo toccare la searchView attuale se non per il searchinprogress che viene messo dall'es table
3942
+ if (!update) {
3943
+ this.searchView = res;
3944
+ this.localStorage.setItem("HotSearchTopAct", this.searchView).subscribe();
3945
+ }
3946
+ // DirectorInfos anche se per qualche motivo crasha di brutto è comunque non obbligatorio, evito di non caricare la pagina solo perché lui scoppia
3947
+ let dirinfos = await this.adms.getInfos(this.be);
3948
+ this.searchView.searchinprogress = false;
3949
+ this.reviewBindings(update, res, dirinfos);
3950
+ }
3951
+ loadNodeDetails(nodeId) {
3952
+ this.NodeDetails = null;
3953
+ requestAnimationFrame(async () => {
3954
+ this.NodeDetails = await this.adms.getNodeStats(this.be, nodeId, this.searchView.filterglobal);
3955
+ });
3956
+ }
3957
+ reviewBindings(update, searchViewOverride, dirInfos) {
3958
+ let reference = (searchViewOverride ?? this.searchView).items[0];
3959
+ this.TopNodes = reference.topnodes;
3960
+ this.TopSessions = reference.topsessions;
3961
+ this.DirInfos = dirInfos?.cluster ? dirInfos : null;
3962
+ this.DirInfosGlobal = !dirInfos?.cluster ? dirInfos : null;
3963
+ if (!reference.actsessions || reference.actsessions.length == 0) {
3964
+ this.TopActiveSessions = null;
3965
+ return;
3966
+ }
3967
+ let series = [];
3968
+ let props = Object.keys(reference.actsessions[0]);
3969
+ for (let i = 0; i < props.length; i++) {
3970
+ if (props[i] != 'sampletime') {
3971
+ let s = new LineSerie(this.propertiesSeriesMapping[props[i]], [], null, null, "sessions");
3972
+ series.push(s);
3973
+ this.seriesPropertiesMapping[props[i]] = s;
3974
+ }
3975
+ }
3976
+ for (let i = 0; i < reference.actsessions.length; i++) {
3977
+ let itemref = reference.actsessions[i];
3978
+ let theD = new Date(itemref.sampletime);
3979
+ for (let i = 0; i < props.length; i++)
3980
+ if (props[i] != 'sampletime' && (!update || this.dateExts.getDateConvertion(theD).isAfter(this.LastDataPoint)))
3981
+ this.seriesPropertiesMapping[props[i]].values.push(new DateValue(theD, itemref[props[i]]));
3982
+ }
3983
+ if (update && this.ChartInstance) {
3984
+ let data = new LineChartData(series, new Limit(dirInfos?.threads, this.lc.loc("Thread Massimi")));
3985
+ if (series.some(t => t.values.length > 0)) {
3986
+ this.ChartInstance.addData(data);
3987
+ this.LastDataPoint = series.map(t => dayjs.max(t.values.map(v => this.dateExts.getDateConvertion(v.d))))[0];
3988
+ }
3989
+ }
3990
+ else {
3991
+ this.TopActiveSessions = new LineChartData(series, new Limit(dirInfos?.threads, this.lc.loc("Thread Massimi")));
3992
+ this.LastDataPoint = series.map(t => dayjs.max(t.values.map(v => this.dateExts.getDateConvertion(v.d))))[0];
3993
+ }
3994
+ }
3995
+ autoUpdateChanged() {
3996
+ if (!/^[1-9]\d*$/.test(this.seconds))
3997
+ this.seconds = '10';
3998
+ const secondsInMs = parseInt(this.seconds, null) * 1000;
3999
+ if (this.autoUpdate) {
4000
+ this.sendSearch(false, true);
4001
+ this.updateTimer = setInterval(() => { this.sendSearch(false, true); }, secondsInMs);
4002
+ }
4003
+ else
1042
4004
  clearInterval(this.updateTimer);
1043
4005
  }
1044
4006
  dataSelectionChanged(event) {
@@ -1176,6 +4138,9 @@ class AdmTopActivityV2Loc extends LocalizationService {
1176
4138
  super.set("it->en", "Sessioni attive medie per", ["Average active sessions by"]);
1177
4139
  super.set("it->en", "Picco", ["Peak"]);
1178
4140
  super.set("it->en", "un punto ogni", ["one point every"]);
4141
+ super.set("it->en", "stima per campionamento", ["sampling estimate"]);
4142
+ super.set("it->en", "Valori stimati contando i campioni, come in Oracle, invece che misurati", ["Values estimated by counting samples, as in Oracle, rather than measured"]);
4143
+ super.set("it->en", "Stima contando i campioni: il totale può differire di poco da quello misurato delle sessioni", ["Estimated by counting samples: the total may differ slightly from the measured sessions total"]);
1179
4144
  super.set("it->en", "Filtri", ["Filters"]);
1180
4145
  super.set("it->en", "Rimuovi", ["Remove"]);
1181
4146
  super.set("it->en", "Clic per filtrare", ["Click to filter"]);
@@ -1205,6 +4170,7 @@ class AdmTopActivityV2Loc extends LocalizationService {
1205
4170
  super.set("it->en", "Macchina", ["Machine"]);
1206
4171
  super.set("it->en", "Tipo", ["Type"]);
1207
4172
  super.set("it->en", "Processo", ["Process"]);
4173
+ super.set("it->en", "Errore", ["Error"]);
1208
4174
  }
1209
4175
  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 }); }
1210
4176
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivityV2Loc }); }
@@ -1448,13 +4414,22 @@ class AdmTopActivityV2Component {
1448
4414
  this.applyResult(req, res);
1449
4415
  this.saveSettings();
1450
4416
  }
1451
- catch {
1452
- // L'errore (finestra non valida, backend non raggiungibile...) lo mostra già HTTPService
4417
+ catch (err) {
4418
+ // Finestra non valida e simili arrivano come 542 (avviso), il resto come errore. La sessione scaduta la gestisce il servizio
4419
+ if (err?.status != 401)
4420
+ this.showError(err);
1453
4421
  }
1454
4422
  finally {
1455
4423
  this.loading = false;
1456
4424
  }
1457
4425
  }
4426
+ showError(err) {
4427
+ let text = this.adms.errorText(err);
4428
+ if (err?.status == 542)
4429
+ this.adms.msgExts.simpleWarning(text);
4430
+ else
4431
+ this.adms.msgExts.simpleError(`${this.lc.loc("Errore")} ${err?.status ?? ""}: ${text}`);
4432
+ }
1458
4433
  buildRequest() {
1459
4434
  let to = this.toDate(this.filterto) ?? new Date();
1460
4435
  let from = this.toDate(this.filterfrom) ?? new Date(to.getTime() - 3600 * 1000);
@@ -1761,11 +4736,11 @@ class AdmTopActivityV2Component {
1761
4736
  return m ? new Date(Date.UTC(+m[3], +m[2] - 1, +m[1], +m[4], +m[5], +m[6])) : new Date(value);
1762
4737
  }
1763
4738
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivityV2Component, deps: [{ token: i1$1.DateService }, { token: i1.LocalizationService }, { token: i3.PreferencesService }, { token: i1$1.InterComService }, { token: i4.ActivatedRoute }, { token: AdminModuleBaseService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
1764
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: AdmTopActivityV2Component, isStandalone: false, selector: "topact_v2", providers: [{ provide: LocalizationService, useClass: AdmTopActivityV2Loc }], viewQueries: [{ propertyName: "dlgNodeDetails", first: true, predicate: ["dlgNodeDetails"], descendants: true }, { propertyName: "dlgSessionDetails", first: true, predicate: ["dlgSessionDetails"], 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\">\r\n {{'Attivit\u00E0 Principali' | localize: lc}}\r\n @if (DirInfos) {\r\n <span>&nbsp;-&nbsp;\r\n {{'' + DirInfos.cpus + ' ' + ('CPU' | localize: lc) + (DirInfos.rssplan ? ' - ' + DirInfos.rssplan : '')}}&nbsp;\r\n Load average: {{ DirInfos.lavg1 | number : '1.1-2' : 'it'}}&nbsp;{{ DirInfos.lavg5 | number : '1.1-2' : 'it'}}&nbsp;{{ DirInfos.lavg15 | number : '1.1-2' : 'it'}}\r\n </span>\r\n }\r\n </span>\r\n </div>\r\n <div class=\"card-body widget-body\">\r\n <!-- Barra in alto: quali dati caricare (finestra, istanza, origine). Come disegnarli sta nell'intestazione del grafico -->\r\n <form role=\"form\">\r\n <!-- Tre gruppi che vanno a capo interi: periodo, sorgente (con la ricerca), aggiornamento automatico -->\r\n <div class=\"tav2-toolbar\">\r\n <div class=\"tav2-cluster\">\r\n <div class=\"tav2-field\">\r\n <form-datetime [readonly]=\"refreshSeconds != '0'\" [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"filterfrom\" name=\"filterfrom\" [Placeholder]=\"'Da' | localize: lc\" (inputChange)=\"datesEdited()\"></form-datetime>\r\n </div>\r\n <div class=\"tav2-field\">\r\n <form-datetime [readonly]=\"refreshSeconds != '0'\" [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"filterto\" name=\"filterto\" [Placeholder]=\"'A' | localize: lc\" (inputChange)=\"datesEdited()\"></form-datetime>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Ultime' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (h of QuickRanges; track h) {\r\n <button type=\"button\" [class.tav2-on]=\"activeRange == h\" (click)=\"quickRange(h)\">{{h}} h</button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"tav2-cluster\">\r\n <div class=\"tav2-field tav2-field-wide tav2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"instance\" name=\"instance\" [Source]=\"Instances\" [Placeholder]=\"'Istanza' | localize: lc\" (inputChange)=\"search()\"></form-select>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Origine dati' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of DataSources; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"dataSource == o.id\" (click)=\"setDataSource(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <button [disabled]=\"loading || refreshSeconds != '0'\" class=\"btn app-btn-search\" (click)=\"searchClicked()\" type=\"submit\" [title]=\"'Cerca' | localize: lc\">\r\n <i class=\"fa\" [class.fa-search]=\"!loading\" [class.fa-spinner]=\"loading\" [class.fa-spin]=\"loading\"></i>\r\n </button>\r\n </div>\r\n\r\n <div class=\"tav2-cluster tav2-cluster-end\">\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\"><i class=\"fa fa-refresh\"></i> {{'Aggiornamento automatico' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of RefreshOptions; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"refreshSeconds == o.id\" (click)=\"setRefresh(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <!-- Grafico -->\r\n <div class=\"tav2-panel\">\r\n <div class=\"tav2-panel-header tav2-chart-header\">\r\n <div class=\"tav2-heading\">\r\n <span class=\"tav2-title\">{{'Sessioni attive medie per' | localize: lc}} {{dimensionLabel(result?.dimension ?? +dimension) | lowercase}}</span>\r\n @if (result) {\r\n <span class=\"tav2-muted\">\r\n {{'Media' | localize: lc}} <strong>{{result.averageActiveSessions | number : '1.2-3' : 'it'}}</strong>\r\n &middot; {{'Picco' | localize: lc}} <strong>{{result.peakActiveSessions | number : '1.2-3' : 'it'}}</strong>\r\n &middot; {{'un punto ogni' | localize: lc}} {{result.bucketSeconds}} s\r\n @if (loading) {\r\n &nbsp;<i class=\"fa fa-spinner fa-spin\"></i>\r\n }\r\n </span>\r\n }\r\n </div>\r\n\r\n <!-- Come disegnare i dati -->\r\n <div class=\"tav2-view-controls\">\r\n <div class=\"tav2-field tav2-field-wide tav2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"dimension\" name=\"dimension\" [Source]=\"Dimensions\" [Placeholder]=\"'Dividi per' | localize: lc\" (inputChange)=\"search()\"></form-select>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Risoluzione' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of Resolutions; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"resolution == o.id\" (click)=\"setResolution(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Limite' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of LimitOptions; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"limitValue == o.id\" (click)=\"setLimit(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"tav2-toggle\">\r\n <form-checkbox [FormLayout]=\"false\" [SliderMode]=\"true\" [Validation]=\"false\" [(ngModel)]=\"totalActivity\" (inputChange)=\"search()\"></form-checkbox>\r\n <span class=\"tav2-pointer\" (click)=\"totalActivity = !totalActivity; search()\">{{'Attivit\u00E0 totale' | localize: lc}}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (filters.length > 0) {\r\n <div class=\"tav2-chips\">\r\n <span class=\"tav2-muted\">{{'Filtri' | localize: lc}}</span>\r\n @for (chip of filters; track $index) {\r\n <span class=\"tav2-chip\">\r\n {{chip.label}}\r\n <button type=\"button\" [title]=\"'Rimuovi' | localize: lc\" (click)=\"removeFilter($index)\">&times;</button>\r\n </span>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"tav2-chart\">\r\n @if (chartData) {\r\n <es-line-chart [MinGridDistance]=\"65\"\r\n [Timeline]=\"false\"\r\n [FinalInstants]=\"false\"\r\n [Stacked]=\"true\"\r\n [Step]=\"true\"\r\n [Fill]=\"true\"\r\n [MergeDatasets]=\"true\"\r\n [Data]=\"chartData\"\r\n [Max]=\"chartMax\"\r\n [Legend]=\"false\"\r\n name=\"topactv2\"\r\n [DataGroupingThreshold]=\"99999999\"\r\n (chartLoaded)=\"onChartLoaded($event)\"></es-line-chart>\r\n } @else {\r\n <div class=\"tav2-empty\">{{(loading ? 'Carico i Dati' : 'Nessun dato nella finestra') | localize: lc}}</div>\r\n }\r\n </div>\r\n\r\n <!-- Legenda: un clic su una voce la aggiunge ai filtri (\"altri\" non \u00E8 filtrabile) -->\r\n @if (legend.length > 0) {\r\n <div class=\"tav2-legend\">\r\n @for (item of legend; track $index) {\r\n <span class=\"tav2-legend-item\" [class.tav2-clickable]=\"!item.other\" [title]=\"item.other ? '' : ('Clic per filtrare' | localize: lc)\" (click)=\"addFilter(item)\">\r\n <span class=\"tav2-swatch\" [style.background]=\"item.color\"></span>\r\n {{item.label}}\r\n <span class=\"tav2-muted\">{{item.percentage | number : '1.0-1' : 'it'}}%</span>\r\n </span>\r\n }\r\n @if (result?.totalActivity) {\r\n <span class=\"tav2-legend-item\">\r\n <span class=\"tav2-line\" [style.border-color]=\"TotalColor\"></span>\r\n {{'Attivit\u00E0 totale' | localize: lc}}\r\n </span>\r\n }\r\n @if (result?.maximumThreads != null) {\r\n <span class=\"tav2-legend-item\">\r\n <span class=\"tav2-line\" [style.border-color]=\"LimitColor\"></span>\r\n {{limitLabel}} <strong>{{result.maximumThreads}}</strong>\r\n </span>\r\n }\r\n </div>\r\n }\r\n\r\n @if (memoryFrom) {\r\n <div class=\"tav2-note\">\r\n <i class=\"fa fa-info-circle\"></i>\r\n {{'Prima di questo istante i dati vengono dallo storico (un campione ogni 10 secondi)' | localize: lc}}: {{memoryFrom}}\r\n </div>\r\n }\r\n\r\n <!-- Time slider: finestra di 5 minuti per le tabelle -->\r\n @if (sliderMax > sliderMin) {\r\n <div class=\"tav2-slider\">\r\n <span class=\"tav2-slider-label\">\r\n <span class=\"tav2-muted\">{{'Finestra tabelle' | localize: lc}}</span>\r\n <strong>{{tablesWindowLabel}}</strong>\r\n </span>\r\n <input type=\"range\" [min]=\"sliderMin\" [max]=\"sliderMax\" [step]=\"sliderStep\" [ngModel]=\"sliderValue\" (ngModelChange)=\"sliderValue = $event; sliderMoving()\" (change)=\"sliderChanged()\" />\r\n <span class=\"tav2-badge\" [class.tav2-badge-off]=\"!sliderAnchored\">{{(sliderAnchored ? 'Ultimi 5 minuti' : 'Finestra fissa') | localize: lc}}</span>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Tabelle -->\r\n <div class=\"tav2-tables\">\r\n @for (table of tables; track $index; let i = $index) {\r\n <div class=\"tav2-panel\">\r\n <div class=\"tav2-panel-header\">\r\n <span class=\"tav2-title\">{{'Prime righe per' | localize: lc}}</span>\r\n <div class=\"tav2-field tav2-field-wide tav2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"tableDims[i]\" name=\"tabledim{{i}}\" [Source]=\"Dimensions\" (inputChange)=\"search()\"></form-select>\r\n </div>\r\n <span class=\"tav2-muted\">{{'Attivit\u00E0' | localize: lc}} <strong>{{table.activity | number : '1.2-3' : 'it'}}</strong></span>\r\n </div>\r\n <es-table [(ngModel)]=\"table.items\" [HidePaging]=\"true\">\r\n <ng-template #header>\r\n <th>{{dimensionLabel(table.dimension)}}</th>\r\n <th class=\"app-col-min\">{{'Attivit\u00E0' | localize: lc}}</th>\r\n <th class=\"app-col-min tav2-right\">%</th>\r\n @if (table.dimension == Dim.UserSession) {\r\n <th>{{'Utente' | localize: lc}}</th>\r\n <th>{{'Tipo' | localize: lc}}</th>\r\n <th>{{'Processo' | localize: lc}}</th>\r\n }\r\n @if (table.dimension == Dim.WaitEvent) {\r\n <th class=\"app-col-min app-nowrap\">{{'Tipo' | localize: lc}}</th>\r\n }\r\n </ng-template>\r\n <ng-template #body let-item>\r\n <td>\r\n @if (isClickable(item)) {\r\n <span class=\"app-link tav2-ellipsis\" [title]=\"item.label || item.key\" (click)=\"openItem(item)\">{{itemLabel(item, table)}}</span>\r\n } @else {\r\n <span class=\"tav2-ellipsis\" [class.tav2-muted]=\"item.other || !item.label\">{{itemLabel(item, table)}}</span>\r\n }\r\n </td>\r\n <td class=\"app-nowrap\">\r\n <div class=\"tav2-bar-cell\">\r\n <div class=\"tav2-bar\">\r\n @for (part of item.breakdown; track $index) {\r\n <div [style.width.%]=\"breakdownWidth(item, part)\" [style.background]=\"breakdownColor(part)\" [title]=\"part.key\"></div>\r\n }\r\n </div>\r\n <span>{{item.activity | number : '1.2-3' : 'it'}}</span>\r\n </div>\r\n </td>\r\n <td class=\"app-nowrap tav2-right\">{{item.percentage | number : '1.1-1' : 'it'}}</td>\r\n @if (table.dimension == Dim.UserSession) {\r\n <td class=\"app-nowrap\">\r\n {{item.session?.userName}}\r\n @if (item.session?.tenantId) {\r\n <span class=\"tav2-muted\">&middot; {{item.session.tenantId}}</span>\r\n }\r\n </td>\r\n <td class=\"tav2-muted\">{{item.session?.sessionType | titlecase}}</td>\r\n <td><span class=\"tav2-ellipsis tav2-ellipsis-short\" [title]=\"item.session?.bgProcessName || item.session?.jobName || ''\">{{item.session?.bgProcessName || item.session?.jobName}}</span></td>\r\n }\r\n @if (table.dimension == Dim.WaitEvent) {\r\n <td class=\"app-nowrap\">\r\n {{item.event?.eventType}}\r\n @if (item.event?.waitClass && item.event.waitClass != 'None') {\r\n <span class=\"tav2-muted\">&middot; {{item.event.waitClass}}</span>\r\n }\r\n </td>\r\n }\r\n </ng-template>\r\n </es-table>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<div bsModal #dlgNodeDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\r\n @if (NodeDetails) {\r\n <node-detail [NodeDetails]=\"NodeDetails\" (onClose)=\"dlgNodeDetails.hide()\"></node-detail>\r\n }\r\n</div>\r\n\r\n<div bsModal #dlgSessionDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\r\n @if (SessionDetails) {\r\n <session-detail [SessionDetails]=\"SessionDetails\" (onClose)=\"dlgSessionDetails.hide()\" (onNodeSelected)=\"loadNodeDetails($event)\"></session-detail>\r\n }\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .tav2-noclear .p-select-clear-icon{display:none}.tav2-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 22px;padding-top:6px}.tav2-cluster{display:flex;flex-wrap:nowrap;align-items:center;gap:10px}.tav2-cluster-end{margin-left:auto}.tav2-field{width:165px}.tav2-field-wide{width:210px}.tav2-spacer{flex:1 1 auto}.tav2-toggle{display:flex;align-items:center;gap:8px;height:39px;white-space:nowrap}.tav2-pointer{cursor:pointer}.tav2-group{position:relative;display:inline-flex}.tav2-group-label{position:absolute;top:-8px;left:8px;z-index:1;padding:0 4px;background:#fff;color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.tav2-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:#fff}.tav2-seg button{padding:0 12px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:#334155;font-size:13px;white-space:nowrap;cursor:pointer}.tav2-seg button:first-child{border-left:0}.tav2-seg button:hover:not(.tav2-on){background:#f8fafc}.tav2-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.tav2-seg button.tav2-on{background:#eef2ff;color:#4338ca;font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.tav2-panel{margin-top:14px;padding:10px 12px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:#fff;min-width:0}.tav2-panel-header{display:flex;flex-wrap:wrap;align-items:center;gap:6px 14px;min-height:32px;margin-bottom:6px}.tav2-chart-header{justify-content:space-between;padding-top:8px;margin-bottom:10px}.tav2-heading{display:flex;flex-direction:column;gap:2px}.tav2-view-controls{display:flex;flex-wrap:wrap;align-items:center;gap:14px 10px}.tav2-title{font-size:12px;font-weight:500;letter-spacing:.03em;text-transform:uppercase;color:#344154}.tav2-muted{color:var(--p-text-muted-color, #64748b);font-size:12px}.tav2-right{text-align:right}.tav2-ellipsis{display:inline-block;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.tav2-ellipsis-short{max-width:170px}.tav2-chips{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:6px}.tav2-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 10px;border-radius:12px;background:#eef2ff;color:#3730a3;font-size:12px}.tav2-chip button{border:0;background:transparent;color:inherit;cursor:pointer;font-size:15px;line-height:1;padding:0 4px}.tav2-chart{position:relative;height:300px}.tav2-empty{display:flex;align-items:center;justify-content:center;height:100%;border:1px dashed var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);color:var(--p-text-muted-color, #64748b)}.tav2-legend{display:flex;flex-wrap:wrap;gap:4px 6px;margin-top:6px}.tav2-legend-item{display:inline-flex;align-items:center;gap:6px;padding:2px 8px;border:1px solid transparent;border-radius:4px;font-size:12px;white-space:nowrap}.tav2-legend-item.tav2-clickable{cursor:pointer}.tav2-legend-item.tav2-clickable:hover{border-color:var(--p-content-border-color, #e2e8f0);background:#f8fafc}.tav2-swatch{flex:none;width:10px;height:10px;border-radius:2px}.tav2-line{flex:none;width:16px;height:0;border-top:2px solid}.tav2-note{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.tav2-slider{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin-top:10px;padding-top:10px;border-top:1px solid var(--p-content-border-color, #e2e8f0)}.tav2-slider input[type=range]{flex:1 1 260px;min-width:200px;accent-color:var(--p-primary-color, #6366f1);cursor:pointer}.tav2-slider-label{display:inline-flex;flex-direction:column;line-height:1.2;font-size:12px}.tav2-badge{padding:2px 8px;border-radius:10px;background:#eef2ff;color:#3730a3;font-size:11px;white-space:nowrap}.tav2-badge.tav2-badge-off{background:#f1f5f9;color:var(--p-text-muted-color, #64748b)}.tav2-tables{display:grid;grid-template-columns:repeat(auto-fit,minmax(560px,1fr));gap:0 14px}.tav2-bar-cell{display:flex;align-items:center;gap:8px}.tav2-bar{display:flex;flex:none;width:70px;height:8px;border-radius:2px;overflow:hidden;background:#edf2f7}\n"], dependencies: [{ kind: "directive", type: i7.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i7.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i7.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { 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: "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.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: "component", type: i2.EsLineChartComponent, selector: "es-line-chart", inputs: ["Data", "DataArray", "Step", "DataGroupBucketSize", "DataGroupingThreshold", "StrokeSize", "AllowGaps", "MergeDatasets", "FinalInstants", "Min", "Max", "Fill", "Stacked"] }, { kind: "directive", type: i10.Widget, selector: "[widget]" }, { kind: "component", type: SessionDetailComponent, selector: "session-detail", inputs: ["SessionDetails"], outputs: ["onClose", "onNodeSelected"] }, { kind: "component", type: NodeDetailComponent, selector: "node-detail", inputs: ["NodeDetails"], outputs: ["onClose"] }, { kind: "pipe", type: i5.LowerCasePipe, name: "lowercase" }, { kind: "pipe", type: i5.DecimalPipe, name: "number" }, { kind: "pipe", type: i5.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: i1.LocalizePipe, name: "localize" }] }); }
4739
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: AdmTopActivityV2Component, isStandalone: false, selector: "topact_v2", providers: [{ provide: LocalizationService, useClass: AdmTopActivityV2Loc }], viewQueries: [{ propertyName: "dlgNodeDetails", first: true, predicate: ["dlgNodeDetails"], descendants: true }, { propertyName: "dlgSessionDetails", first: true, predicate: ["dlgSessionDetails"], 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\">\r\n {{'Attivit\u00E0 Principali' | localize: lc}}\r\n @if (DirInfos) {\r\n <span>&nbsp;-&nbsp;\r\n {{'' + DirInfos.cpus + ' ' + ('CPU' | localize: lc) + (DirInfos.rssplan ? ' - ' + DirInfos.rssplan : '')}}&nbsp;\r\n Load average: {{ DirInfos.lavg1 | number : '1.1-2' : 'it'}}&nbsp;{{ DirInfos.lavg5 | number : '1.1-2' : 'it'}}&nbsp;{{ DirInfos.lavg15 | number : '1.1-2' : 'it'}}\r\n </span>\r\n }\r\n </span>\r\n </div>\r\n <div class=\"card-body widget-body\">\r\n <!-- Barra in alto: quali dati caricare (finestra, istanza, origine). Come disegnarli sta nell'intestazione del grafico -->\r\n <form role=\"form\">\r\n <!-- Tre gruppi che vanno a capo interi: periodo, sorgente (con la ricerca), aggiornamento automatico -->\r\n <div class=\"tav2-toolbar\">\r\n <div class=\"tav2-cluster\">\r\n <div class=\"tav2-field\">\r\n <form-datetime [readonly]=\"refreshSeconds != '0'\" [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"filterfrom\" name=\"filterfrom\" [Placeholder]=\"'Da' | localize: lc\" (inputChange)=\"datesEdited()\"></form-datetime>\r\n </div>\r\n <div class=\"tav2-field\">\r\n <form-datetime [readonly]=\"refreshSeconds != '0'\" [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"filterto\" name=\"filterto\" [Placeholder]=\"'A' | localize: lc\" (inputChange)=\"datesEdited()\"></form-datetime>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Ultime' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (h of QuickRanges; track h) {\r\n <button type=\"button\" [class.tav2-on]=\"activeRange == h\" (click)=\"quickRange(h)\">{{h}} h</button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"tav2-cluster\">\r\n <div class=\"tav2-field tav2-field-wide tav2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"instance\" name=\"instance\" [Source]=\"Instances\" [Placeholder]=\"'Istanza' | localize: lc\" (inputChange)=\"search()\"></form-select>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Origine dati' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of DataSources; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"dataSource == o.id\" (click)=\"setDataSource(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <button [disabled]=\"loading || refreshSeconds != '0'\" class=\"btn app-btn-search\" (click)=\"searchClicked()\" type=\"submit\" [title]=\"'Cerca' | localize: lc\">\r\n <i class=\"fa\" [class.fa-search]=\"!loading\" [class.fa-spinner]=\"loading\" [class.fa-spin]=\"loading\"></i>\r\n </button>\r\n </div>\r\n\r\n <div class=\"tav2-cluster tav2-cluster-end\">\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\"><i class=\"fa fa-refresh\"></i> {{'Aggiornamento automatico' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of RefreshOptions; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"refreshSeconds == o.id\" (click)=\"setRefresh(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <!-- Grafico -->\r\n <div class=\"tav2-panel\">\r\n <div class=\"tav2-panel-header tav2-chart-header\">\r\n <div class=\"tav2-heading\">\r\n <span class=\"tav2-title\">{{'Sessioni attive medie per' | localize: lc}} {{dimensionLabel(result?.dimension ?? +dimension) | lowercase}}</span>\r\n @if (result) {\r\n <span class=\"tav2-muted\">\r\n {{'Media' | localize: lc}} <strong>{{result.estimated ? '\u2248 ' : ''}}{{result.averageActiveSessions | number : '1.2-3' : 'it'}}</strong>\r\n &middot; {{'Picco' | localize: lc}} <strong>{{result.estimated ? '\u2248 ' : ''}}{{result.peakActiveSessions | number : '1.2-3' : 'it'}}</strong>\r\n &middot; {{'un punto ogni' | localize: lc}} {{result.bucketSeconds}} s\r\n @if (result.estimated) {\r\n <span class=\"tav2-estimate\" [title]=\"'Valori stimati contando i campioni, come in Oracle, invece che misurati' | localize: lc\">{{'stima per campionamento' | localize: lc}}</span>\r\n }\r\n @if (loading) {\r\n &nbsp;<i class=\"fa fa-spinner fa-spin\"></i>\r\n }\r\n </span>\r\n }\r\n </div>\r\n\r\n <!-- Come disegnare i dati -->\r\n <div class=\"tav2-view-controls\">\r\n <div class=\"tav2-field tav2-field-wide tav2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"dimension\" name=\"dimension\" [Source]=\"Dimensions\" [Placeholder]=\"'Dividi per' | localize: lc\" (inputChange)=\"search()\"></form-select>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Risoluzione' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of Resolutions; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"resolution == o.id\" (click)=\"setResolution(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Limite' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of LimitOptions; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"limitValue == o.id\" (click)=\"setLimit(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"tav2-toggle\">\r\n <form-checkbox [FormLayout]=\"false\" [SliderMode]=\"true\" [Validation]=\"false\" [(ngModel)]=\"totalActivity\" (inputChange)=\"search()\"></form-checkbox>\r\n <span class=\"tav2-pointer\" (click)=\"totalActivity = !totalActivity; search()\">{{'Attivit\u00E0 totale' | localize: lc}}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (filters.length > 0) {\r\n <div class=\"tav2-chips\">\r\n <span class=\"tav2-muted\">{{'Filtri' | localize: lc}}</span>\r\n @for (chip of filters; track $index) {\r\n <span class=\"tav2-chip\">\r\n {{chip.label}}\r\n <button type=\"button\" [title]=\"'Rimuovi' | localize: lc\" (click)=\"removeFilter($index)\">&times;</button>\r\n </span>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"tav2-chart\">\r\n @if (chartData) {\r\n <es-line-chart [MinGridDistance]=\"65\"\r\n [Timeline]=\"false\"\r\n [FinalInstants]=\"false\"\r\n [Stacked]=\"true\"\r\n [Step]=\"true\"\r\n [Fill]=\"true\"\r\n [MergeDatasets]=\"true\"\r\n [Data]=\"chartData\"\r\n [Max]=\"chartMax\"\r\n [Legend]=\"false\"\r\n name=\"topactv2\"\r\n [DataGroupingThreshold]=\"99999999\"\r\n (chartLoaded)=\"onChartLoaded($event)\"></es-line-chart>\r\n } @else {\r\n <div class=\"tav2-empty\">{{(loading ? 'Carico i Dati' : 'Nessun dato nella finestra') | localize: lc}}</div>\r\n }\r\n </div>\r\n\r\n <!-- Legenda: un clic su una voce la aggiunge ai filtri (\"altri\" non \u00E8 filtrabile) -->\r\n @if (legend.length > 0) {\r\n <div class=\"tav2-legend\">\r\n @for (item of legend; track $index) {\r\n <span class=\"tav2-legend-item\" [class.tav2-clickable]=\"!item.other\" [title]=\"item.other ? '' : ('Clic per filtrare' | localize: lc)\" (click)=\"addFilter(item)\">\r\n <span class=\"tav2-swatch\" [style.background]=\"item.color\"></span>\r\n {{item.label}}\r\n <span class=\"tav2-muted\">{{item.percentage | number : '1.0-1' : 'it'}}%</span>\r\n </span>\r\n }\r\n @if (result?.totalActivity) {\r\n <span class=\"tav2-legend-item\">\r\n <span class=\"tav2-line\" [style.border-color]=\"TotalColor\"></span>\r\n {{'Attivit\u00E0 totale' | localize: lc}}\r\n </span>\r\n }\r\n @if (result?.maximumThreads != null) {\r\n <span class=\"tav2-legend-item\">\r\n <span class=\"tav2-line\" [style.border-color]=\"LimitColor\"></span>\r\n {{limitLabel}} <strong>{{result.maximumThreads}}</strong>\r\n </span>\r\n }\r\n </div>\r\n }\r\n\r\n @if (memoryFrom) {\r\n <div class=\"tav2-note\">\r\n <i class=\"fa fa-info-circle\"></i>\r\n {{'Prima di questo istante i dati vengono dallo storico (un campione ogni 10 secondi)' | localize: lc}}: {{memoryFrom}}\r\n </div>\r\n }\r\n\r\n <!-- Time slider: finestra di 5 minuti per le tabelle -->\r\n @if (sliderMax > sliderMin) {\r\n <div class=\"tav2-slider\">\r\n <span class=\"tav2-slider-label\">\r\n <span class=\"tav2-muted\">{{'Finestra tabelle' | localize: lc}}</span>\r\n <strong>{{tablesWindowLabel}}</strong>\r\n </span>\r\n <input type=\"range\" [min]=\"sliderMin\" [max]=\"sliderMax\" [step]=\"sliderStep\" [ngModel]=\"sliderValue\" (ngModelChange)=\"sliderValue = $event; sliderMoving()\" (change)=\"sliderChanged()\" />\r\n <span class=\"tav2-badge\" [class.tav2-badge-off]=\"!sliderAnchored\">{{(sliderAnchored ? 'Ultimi 5 minuti' : 'Finestra fissa') | localize: lc}}</span>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Tabelle -->\r\n <div class=\"tav2-tables\">\r\n @for (table of tables; track $index; let i = $index) {\r\n <div class=\"tav2-panel\">\r\n <div class=\"tav2-panel-header\">\r\n <span class=\"tav2-title\">{{'Prime righe per' | localize: lc}}</span>\r\n <div class=\"tav2-field tav2-field-wide tav2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"tableDims[i]\" name=\"tabledim{{i}}\" [Source]=\"Dimensions\" (inputChange)=\"search()\"></form-select>\r\n </div>\r\n <span class=\"tav2-muted\">{{'Attivit\u00E0' | localize: lc}} <strong>{{table.estimated ? '\u2248 ' : ''}}{{table.activity | number : '1.2-3' : 'it'}}</strong></span>\r\n @if (table.estimated) {\r\n <span class=\"tav2-estimate\" [title]=\"'Stima contando i campioni: il totale pu\u00F2 differire di poco da quello misurato delle sessioni' | localize: lc\">{{'stima per campionamento' | localize: lc}}</span>\r\n }\r\n </div>\r\n <es-table [(ngModel)]=\"table.items\" [HidePaging]=\"true\">\r\n <ng-template #header>\r\n <th>{{dimensionLabel(table.dimension)}}</th>\r\n <th class=\"app-col-min\">{{'Attivit\u00E0' | localize: lc}}</th>\r\n <th class=\"app-col-min tav2-right\">%</th>\r\n @if (table.dimension == Dim.UserSession) {\r\n <th>{{'Utente' | localize: lc}}</th>\r\n <th>{{'Tipo' | localize: lc}}</th>\r\n <th>{{'Processo' | localize: lc}}</th>\r\n }\r\n @if (table.dimension == Dim.WaitEvent) {\r\n <th class=\"app-col-min app-nowrap\">{{'Tipo' | localize: lc}}</th>\r\n }\r\n </ng-template>\r\n <ng-template #body let-item>\r\n <td>\r\n @if (isClickable(item)) {\r\n <span class=\"app-link tav2-ellipsis\" [title]=\"item.label || item.key\" (click)=\"openItem(item)\">{{itemLabel(item, table)}}</span>\r\n } @else {\r\n <span class=\"tav2-ellipsis\" [class.tav2-muted]=\"item.other || !item.label\">{{itemLabel(item, table)}}</span>\r\n }\r\n </td>\r\n <td class=\"app-nowrap\">\r\n <div class=\"tav2-bar-cell\">\r\n <div class=\"tav2-bar\">\r\n @for (part of item.breakdown; track $index) {\r\n <div [style.width.%]=\"breakdownWidth(item, part)\" [style.background]=\"breakdownColor(part)\" [title]=\"part.key\"></div>\r\n }\r\n </div>\r\n <span>{{table.estimated ? '\u2248 ' : ''}}{{item.activity | number : '1.2-3' : 'it'}}</span>\r\n </div>\r\n </td>\r\n <td class=\"app-nowrap tav2-right\">{{item.percentage | number : '1.1-1' : 'it'}}</td>\r\n @if (table.dimension == Dim.UserSession) {\r\n <td class=\"app-nowrap\">\r\n {{item.session?.userName}}\r\n @if (item.session?.tenantId) {\r\n <span class=\"tav2-muted\">&middot; {{item.session.tenantId}}</span>\r\n }\r\n </td>\r\n <td class=\"tav2-muted\">{{item.session?.sessionType | titlecase}}</td>\r\n <td><span class=\"tav2-ellipsis tav2-ellipsis-short\" [title]=\"item.session?.bgProcessName || item.session?.jobName || ''\">{{item.session?.bgProcessName || item.session?.jobName}}</span></td>\r\n }\r\n @if (table.dimension == Dim.WaitEvent) {\r\n <td class=\"app-nowrap\">\r\n {{item.event?.eventType}}\r\n @if (item.event?.waitClass && item.event.waitClass != 'None') {\r\n <span class=\"tav2-muted\">&middot; {{item.event.waitClass}}</span>\r\n }\r\n </td>\r\n }\r\n </ng-template>\r\n </es-table>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<div bsModal #dlgNodeDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\r\n @if (NodeDetails) {\r\n <node-detail [NodeDetails]=\"NodeDetails\" (onClose)=\"dlgNodeDetails.hide()\"></node-detail>\r\n }\r\n</div>\r\n\r\n<div bsModal #dlgSessionDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\r\n @if (SessionDetails) {\r\n <session-detail [SessionDetails]=\"SessionDetails\" (onClose)=\"dlgSessionDetails.hide()\" (onNodeSelected)=\"loadNodeDetails($event)\"></session-detail>\r\n }\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .tav2-noclear .p-select-clear-icon{display:none}.tav2-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 22px;padding-top:6px}.tav2-cluster{display:flex;flex-wrap:nowrap;align-items:center;gap:10px}.tav2-cluster-end{margin-left:auto}.tav2-field{width:165px}.tav2-field-wide{width:210px}.tav2-spacer{flex:1 1 auto}.tav2-toggle{display:flex;align-items:center;gap:8px;height:39px;white-space:nowrap}.tav2-pointer{cursor:pointer}.tav2-group{position:relative;display:inline-flex}.tav2-group-label{position:absolute;top:-8px;left:8px;z-index:1;padding:0 4px;background:#fff;color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.tav2-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:#fff}.tav2-seg button{padding:0 12px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:#334155;font-size:13px;white-space:nowrap;cursor:pointer}.tav2-seg button:first-child{border-left:0}.tav2-seg button:hover:not(.tav2-on){background:#f8fafc}.tav2-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.tav2-seg button.tav2-on{background:#eef2ff;color:#4338ca;font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.tav2-panel{margin-top:14px;padding:10px 12px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:#fff;min-width:0}.tav2-panel-header{display:flex;flex-wrap:wrap;align-items:center;gap:6px 14px;min-height:32px;margin-bottom:6px}.tav2-chart-header{justify-content:space-between;padding-top:8px;margin-bottom:10px}.tav2-heading{display:flex;flex-direction:column;gap:2px}.tav2-view-controls{display:flex;flex-wrap:wrap;align-items:center;gap:14px 10px}.tav2-title{font-size:12px;font-weight:500;letter-spacing:.03em;text-transform:uppercase;color:#344154}.tav2-muted{color:var(--p-text-muted-color, #64748b);font-size:12px}.tav2-right{text-align:right}.tav2-estimate{display:inline-block;margin-left:6px;padding:0 6px;border:1px solid #fcd34d;border-radius:10px;background:#fffbeb;color:#92400e;font-size:11px;line-height:16px;white-space:nowrap;cursor:help}.tav2-ellipsis{display:inline-block;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.tav2-ellipsis-short{max-width:170px}.tav2-tables .tav2-panel{container-type:inline-size}@container (min-width: 900px){.tav2-ellipsis{max-width:520px}.tav2-ellipsis-short{max-width:300px}}.tav2-chips{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:6px}.tav2-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 10px;border-radius:12px;background:#eef2ff;color:#3730a3;font-size:12px}.tav2-chip button{border:0;background:transparent;color:inherit;cursor:pointer;font-size:15px;line-height:1;padding:0 4px}.tav2-chart{position:relative;height:300px}.tav2-empty{display:flex;align-items:center;justify-content:center;height:100%;border:1px dashed var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);color:var(--p-text-muted-color, #64748b)}.tav2-legend{display:flex;flex-wrap:wrap;gap:4px 6px;margin-top:6px}.tav2-legend-item{display:inline-flex;align-items:center;gap:6px;padding:2px 8px;border:1px solid transparent;border-radius:4px;font-size:12px;white-space:nowrap}.tav2-legend-item.tav2-clickable{cursor:pointer}.tav2-legend-item.tav2-clickable:hover{border-color:var(--p-content-border-color, #e2e8f0);background:#f8fafc}.tav2-swatch{flex:none;width:10px;height:10px;border-radius:2px}.tav2-line{flex:none;width:16px;height:0;border-top:2px solid}.tav2-note{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.tav2-slider{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin-top:10px;padding-top:10px;border-top:1px solid var(--p-content-border-color, #e2e8f0)}.tav2-slider input[type=range]{flex:1 1 260px;min-width:200px;accent-color:var(--p-primary-color, #6366f1);cursor:pointer}.tav2-slider-label{display:inline-flex;flex-direction:column;line-height:1.2;font-size:12px}.tav2-badge{padding:2px 8px;border-radius:10px;background:#eef2ff;color:#3730a3;font-size:11px;white-space:nowrap}.tav2-badge.tav2-badge-off{background:#f1f5f9;color:var(--p-text-muted-color, #64748b)}.tav2-tables{display:grid;grid-template-columns:repeat(auto-fit,minmax(560px,1fr));gap:0 14px}.tav2-bar-cell{display:flex;align-items:center;gap:8px}.tav2-bar{display:flex;flex:none;width:70px;height:8px;border-radius:2px;overflow:hidden;background:#edf2f7}\n"], dependencies: [{ kind: "directive", type: i7.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i7.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i7.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { 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: "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.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: "component", type: i2.EsLineChartComponent, selector: "es-line-chart", inputs: ["Data", "DataArray", "Step", "DataGroupBucketSize", "DataGroupingThreshold", "StrokeSize", "AllowGaps", "MergeDatasets", "FinalInstants", "Min", "Max", "Fill", "Stacked"] }, { kind: "directive", type: i10.Widget, selector: "[widget]" }, { kind: "component", type: SessionDetailComponent, selector: "session-detail", inputs: ["SessionDetails"], outputs: ["onClose", "onNodeSelected"] }, { kind: "component", type: NodeDetailComponent, selector: "node-detail", inputs: ["NodeDetails"], outputs: ["onClose"] }, { kind: "pipe", type: i5.LowerCasePipe, name: "lowercase" }, { kind: "pipe", type: i5.DecimalPipe, name: "number" }, { kind: "pipe", type: i5.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: i1.LocalizePipe, name: "localize" }] }); }
1765
4740
  }
1766
4741
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivityV2Component, decorators: [{
1767
4742
  type: Component,
1768
- args: [{ selector: "topact_v2", providers: [{ provide: LocalizationService, useClass: AdmTopActivityV2Loc }], 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\">\r\n {{'Attivit\u00E0 Principali' | localize: lc}}\r\n @if (DirInfos) {\r\n <span>&nbsp;-&nbsp;\r\n {{'' + DirInfos.cpus + ' ' + ('CPU' | localize: lc) + (DirInfos.rssplan ? ' - ' + DirInfos.rssplan : '')}}&nbsp;\r\n Load average: {{ DirInfos.lavg1 | number : '1.1-2' : 'it'}}&nbsp;{{ DirInfos.lavg5 | number : '1.1-2' : 'it'}}&nbsp;{{ DirInfos.lavg15 | number : '1.1-2' : 'it'}}\r\n </span>\r\n }\r\n </span>\r\n </div>\r\n <div class=\"card-body widget-body\">\r\n <!-- Barra in alto: quali dati caricare (finestra, istanza, origine). Come disegnarli sta nell'intestazione del grafico -->\r\n <form role=\"form\">\r\n <!-- Tre gruppi che vanno a capo interi: periodo, sorgente (con la ricerca), aggiornamento automatico -->\r\n <div class=\"tav2-toolbar\">\r\n <div class=\"tav2-cluster\">\r\n <div class=\"tav2-field\">\r\n <form-datetime [readonly]=\"refreshSeconds != '0'\" [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"filterfrom\" name=\"filterfrom\" [Placeholder]=\"'Da' | localize: lc\" (inputChange)=\"datesEdited()\"></form-datetime>\r\n </div>\r\n <div class=\"tav2-field\">\r\n <form-datetime [readonly]=\"refreshSeconds != '0'\" [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"filterto\" name=\"filterto\" [Placeholder]=\"'A' | localize: lc\" (inputChange)=\"datesEdited()\"></form-datetime>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Ultime' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (h of QuickRanges; track h) {\r\n <button type=\"button\" [class.tav2-on]=\"activeRange == h\" (click)=\"quickRange(h)\">{{h}} h</button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"tav2-cluster\">\r\n <div class=\"tav2-field tav2-field-wide tav2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"instance\" name=\"instance\" [Source]=\"Instances\" [Placeholder]=\"'Istanza' | localize: lc\" (inputChange)=\"search()\"></form-select>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Origine dati' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of DataSources; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"dataSource == o.id\" (click)=\"setDataSource(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <button [disabled]=\"loading || refreshSeconds != '0'\" class=\"btn app-btn-search\" (click)=\"searchClicked()\" type=\"submit\" [title]=\"'Cerca' | localize: lc\">\r\n <i class=\"fa\" [class.fa-search]=\"!loading\" [class.fa-spinner]=\"loading\" [class.fa-spin]=\"loading\"></i>\r\n </button>\r\n </div>\r\n\r\n <div class=\"tav2-cluster tav2-cluster-end\">\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\"><i class=\"fa fa-refresh\"></i> {{'Aggiornamento automatico' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of RefreshOptions; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"refreshSeconds == o.id\" (click)=\"setRefresh(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <!-- Grafico -->\r\n <div class=\"tav2-panel\">\r\n <div class=\"tav2-panel-header tav2-chart-header\">\r\n <div class=\"tav2-heading\">\r\n <span class=\"tav2-title\">{{'Sessioni attive medie per' | localize: lc}} {{dimensionLabel(result?.dimension ?? +dimension) | lowercase}}</span>\r\n @if (result) {\r\n <span class=\"tav2-muted\">\r\n {{'Media' | localize: lc}} <strong>{{result.averageActiveSessions | number : '1.2-3' : 'it'}}</strong>\r\n &middot; {{'Picco' | localize: lc}} <strong>{{result.peakActiveSessions | number : '1.2-3' : 'it'}}</strong>\r\n &middot; {{'un punto ogni' | localize: lc}} {{result.bucketSeconds}} s\r\n @if (loading) {\r\n &nbsp;<i class=\"fa fa-spinner fa-spin\"></i>\r\n }\r\n </span>\r\n }\r\n </div>\r\n\r\n <!-- Come disegnare i dati -->\r\n <div class=\"tav2-view-controls\">\r\n <div class=\"tav2-field tav2-field-wide tav2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"dimension\" name=\"dimension\" [Source]=\"Dimensions\" [Placeholder]=\"'Dividi per' | localize: lc\" (inputChange)=\"search()\"></form-select>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Risoluzione' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of Resolutions; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"resolution == o.id\" (click)=\"setResolution(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Limite' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of LimitOptions; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"limitValue == o.id\" (click)=\"setLimit(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"tav2-toggle\">\r\n <form-checkbox [FormLayout]=\"false\" [SliderMode]=\"true\" [Validation]=\"false\" [(ngModel)]=\"totalActivity\" (inputChange)=\"search()\"></form-checkbox>\r\n <span class=\"tav2-pointer\" (click)=\"totalActivity = !totalActivity; search()\">{{'Attivit\u00E0 totale' | localize: lc}}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (filters.length > 0) {\r\n <div class=\"tav2-chips\">\r\n <span class=\"tav2-muted\">{{'Filtri' | localize: lc}}</span>\r\n @for (chip of filters; track $index) {\r\n <span class=\"tav2-chip\">\r\n {{chip.label}}\r\n <button type=\"button\" [title]=\"'Rimuovi' | localize: lc\" (click)=\"removeFilter($index)\">&times;</button>\r\n </span>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"tav2-chart\">\r\n @if (chartData) {\r\n <es-line-chart [MinGridDistance]=\"65\"\r\n [Timeline]=\"false\"\r\n [FinalInstants]=\"false\"\r\n [Stacked]=\"true\"\r\n [Step]=\"true\"\r\n [Fill]=\"true\"\r\n [MergeDatasets]=\"true\"\r\n [Data]=\"chartData\"\r\n [Max]=\"chartMax\"\r\n [Legend]=\"false\"\r\n name=\"topactv2\"\r\n [DataGroupingThreshold]=\"99999999\"\r\n (chartLoaded)=\"onChartLoaded($event)\"></es-line-chart>\r\n } @else {\r\n <div class=\"tav2-empty\">{{(loading ? 'Carico i Dati' : 'Nessun dato nella finestra') | localize: lc}}</div>\r\n }\r\n </div>\r\n\r\n <!-- Legenda: un clic su una voce la aggiunge ai filtri (\"altri\" non \u00E8 filtrabile) -->\r\n @if (legend.length > 0) {\r\n <div class=\"tav2-legend\">\r\n @for (item of legend; track $index) {\r\n <span class=\"tav2-legend-item\" [class.tav2-clickable]=\"!item.other\" [title]=\"item.other ? '' : ('Clic per filtrare' | localize: lc)\" (click)=\"addFilter(item)\">\r\n <span class=\"tav2-swatch\" [style.background]=\"item.color\"></span>\r\n {{item.label}}\r\n <span class=\"tav2-muted\">{{item.percentage | number : '1.0-1' : 'it'}}%</span>\r\n </span>\r\n }\r\n @if (result?.totalActivity) {\r\n <span class=\"tav2-legend-item\">\r\n <span class=\"tav2-line\" [style.border-color]=\"TotalColor\"></span>\r\n {{'Attivit\u00E0 totale' | localize: lc}}\r\n </span>\r\n }\r\n @if (result?.maximumThreads != null) {\r\n <span class=\"tav2-legend-item\">\r\n <span class=\"tav2-line\" [style.border-color]=\"LimitColor\"></span>\r\n {{limitLabel}} <strong>{{result.maximumThreads}}</strong>\r\n </span>\r\n }\r\n </div>\r\n }\r\n\r\n @if (memoryFrom) {\r\n <div class=\"tav2-note\">\r\n <i class=\"fa fa-info-circle\"></i>\r\n {{'Prima di questo istante i dati vengono dallo storico (un campione ogni 10 secondi)' | localize: lc}}: {{memoryFrom}}\r\n </div>\r\n }\r\n\r\n <!-- Time slider: finestra di 5 minuti per le tabelle -->\r\n @if (sliderMax > sliderMin) {\r\n <div class=\"tav2-slider\">\r\n <span class=\"tav2-slider-label\">\r\n <span class=\"tav2-muted\">{{'Finestra tabelle' | localize: lc}}</span>\r\n <strong>{{tablesWindowLabel}}</strong>\r\n </span>\r\n <input type=\"range\" [min]=\"sliderMin\" [max]=\"sliderMax\" [step]=\"sliderStep\" [ngModel]=\"sliderValue\" (ngModelChange)=\"sliderValue = $event; sliderMoving()\" (change)=\"sliderChanged()\" />\r\n <span class=\"tav2-badge\" [class.tav2-badge-off]=\"!sliderAnchored\">{{(sliderAnchored ? 'Ultimi 5 minuti' : 'Finestra fissa') | localize: lc}}</span>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Tabelle -->\r\n <div class=\"tav2-tables\">\r\n @for (table of tables; track $index; let i = $index) {\r\n <div class=\"tav2-panel\">\r\n <div class=\"tav2-panel-header\">\r\n <span class=\"tav2-title\">{{'Prime righe per' | localize: lc}}</span>\r\n <div class=\"tav2-field tav2-field-wide tav2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"tableDims[i]\" name=\"tabledim{{i}}\" [Source]=\"Dimensions\" (inputChange)=\"search()\"></form-select>\r\n </div>\r\n <span class=\"tav2-muted\">{{'Attivit\u00E0' | localize: lc}} <strong>{{table.activity | number : '1.2-3' : 'it'}}</strong></span>\r\n </div>\r\n <es-table [(ngModel)]=\"table.items\" [HidePaging]=\"true\">\r\n <ng-template #header>\r\n <th>{{dimensionLabel(table.dimension)}}</th>\r\n <th class=\"app-col-min\">{{'Attivit\u00E0' | localize: lc}}</th>\r\n <th class=\"app-col-min tav2-right\">%</th>\r\n @if (table.dimension == Dim.UserSession) {\r\n <th>{{'Utente' | localize: lc}}</th>\r\n <th>{{'Tipo' | localize: lc}}</th>\r\n <th>{{'Processo' | localize: lc}}</th>\r\n }\r\n @if (table.dimension == Dim.WaitEvent) {\r\n <th class=\"app-col-min app-nowrap\">{{'Tipo' | localize: lc}}</th>\r\n }\r\n </ng-template>\r\n <ng-template #body let-item>\r\n <td>\r\n @if (isClickable(item)) {\r\n <span class=\"app-link tav2-ellipsis\" [title]=\"item.label || item.key\" (click)=\"openItem(item)\">{{itemLabel(item, table)}}</span>\r\n } @else {\r\n <span class=\"tav2-ellipsis\" [class.tav2-muted]=\"item.other || !item.label\">{{itemLabel(item, table)}}</span>\r\n }\r\n </td>\r\n <td class=\"app-nowrap\">\r\n <div class=\"tav2-bar-cell\">\r\n <div class=\"tav2-bar\">\r\n @for (part of item.breakdown; track $index) {\r\n <div [style.width.%]=\"breakdownWidth(item, part)\" [style.background]=\"breakdownColor(part)\" [title]=\"part.key\"></div>\r\n }\r\n </div>\r\n <span>{{item.activity | number : '1.2-3' : 'it'}}</span>\r\n </div>\r\n </td>\r\n <td class=\"app-nowrap tav2-right\">{{item.percentage | number : '1.1-1' : 'it'}}</td>\r\n @if (table.dimension == Dim.UserSession) {\r\n <td class=\"app-nowrap\">\r\n {{item.session?.userName}}\r\n @if (item.session?.tenantId) {\r\n <span class=\"tav2-muted\">&middot; {{item.session.tenantId}}</span>\r\n }\r\n </td>\r\n <td class=\"tav2-muted\">{{item.session?.sessionType | titlecase}}</td>\r\n <td><span class=\"tav2-ellipsis tav2-ellipsis-short\" [title]=\"item.session?.bgProcessName || item.session?.jobName || ''\">{{item.session?.bgProcessName || item.session?.jobName}}</span></td>\r\n }\r\n @if (table.dimension == Dim.WaitEvent) {\r\n <td class=\"app-nowrap\">\r\n {{item.event?.eventType}}\r\n @if (item.event?.waitClass && item.event.waitClass != 'None') {\r\n <span class=\"tav2-muted\">&middot; {{item.event.waitClass}}</span>\r\n }\r\n </td>\r\n }\r\n </ng-template>\r\n </es-table>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<div bsModal #dlgNodeDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\r\n @if (NodeDetails) {\r\n <node-detail [NodeDetails]=\"NodeDetails\" (onClose)=\"dlgNodeDetails.hide()\"></node-detail>\r\n }\r\n</div>\r\n\r\n<div bsModal #dlgSessionDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\r\n @if (SessionDetails) {\r\n <session-detail [SessionDetails]=\"SessionDetails\" (onClose)=\"dlgSessionDetails.hide()\" (onNodeSelected)=\"loadNodeDetails($event)\"></session-detail>\r\n }\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .tav2-noclear .p-select-clear-icon{display:none}.tav2-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 22px;padding-top:6px}.tav2-cluster{display:flex;flex-wrap:nowrap;align-items:center;gap:10px}.tav2-cluster-end{margin-left:auto}.tav2-field{width:165px}.tav2-field-wide{width:210px}.tav2-spacer{flex:1 1 auto}.tav2-toggle{display:flex;align-items:center;gap:8px;height:39px;white-space:nowrap}.tav2-pointer{cursor:pointer}.tav2-group{position:relative;display:inline-flex}.tav2-group-label{position:absolute;top:-8px;left:8px;z-index:1;padding:0 4px;background:#fff;color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.tav2-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:#fff}.tav2-seg button{padding:0 12px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:#334155;font-size:13px;white-space:nowrap;cursor:pointer}.tav2-seg button:first-child{border-left:0}.tav2-seg button:hover:not(.tav2-on){background:#f8fafc}.tav2-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.tav2-seg button.tav2-on{background:#eef2ff;color:#4338ca;font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.tav2-panel{margin-top:14px;padding:10px 12px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:#fff;min-width:0}.tav2-panel-header{display:flex;flex-wrap:wrap;align-items:center;gap:6px 14px;min-height:32px;margin-bottom:6px}.tav2-chart-header{justify-content:space-between;padding-top:8px;margin-bottom:10px}.tav2-heading{display:flex;flex-direction:column;gap:2px}.tav2-view-controls{display:flex;flex-wrap:wrap;align-items:center;gap:14px 10px}.tav2-title{font-size:12px;font-weight:500;letter-spacing:.03em;text-transform:uppercase;color:#344154}.tav2-muted{color:var(--p-text-muted-color, #64748b);font-size:12px}.tav2-right{text-align:right}.tav2-ellipsis{display:inline-block;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.tav2-ellipsis-short{max-width:170px}.tav2-chips{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:6px}.tav2-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 10px;border-radius:12px;background:#eef2ff;color:#3730a3;font-size:12px}.tav2-chip button{border:0;background:transparent;color:inherit;cursor:pointer;font-size:15px;line-height:1;padding:0 4px}.tav2-chart{position:relative;height:300px}.tav2-empty{display:flex;align-items:center;justify-content:center;height:100%;border:1px dashed var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);color:var(--p-text-muted-color, #64748b)}.tav2-legend{display:flex;flex-wrap:wrap;gap:4px 6px;margin-top:6px}.tav2-legend-item{display:inline-flex;align-items:center;gap:6px;padding:2px 8px;border:1px solid transparent;border-radius:4px;font-size:12px;white-space:nowrap}.tav2-legend-item.tav2-clickable{cursor:pointer}.tav2-legend-item.tav2-clickable:hover{border-color:var(--p-content-border-color, #e2e8f0);background:#f8fafc}.tav2-swatch{flex:none;width:10px;height:10px;border-radius:2px}.tav2-line{flex:none;width:16px;height:0;border-top:2px solid}.tav2-note{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.tav2-slider{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin-top:10px;padding-top:10px;border-top:1px solid var(--p-content-border-color, #e2e8f0)}.tav2-slider input[type=range]{flex:1 1 260px;min-width:200px;accent-color:var(--p-primary-color, #6366f1);cursor:pointer}.tav2-slider-label{display:inline-flex;flex-direction:column;line-height:1.2;font-size:12px}.tav2-badge{padding:2px 8px;border-radius:10px;background:#eef2ff;color:#3730a3;font-size:11px;white-space:nowrap}.tav2-badge.tav2-badge-off{background:#f1f5f9;color:var(--p-text-muted-color, #64748b)}.tav2-tables{display:grid;grid-template-columns:repeat(auto-fit,minmax(560px,1fr));gap:0 14px}.tav2-bar-cell{display:flex;align-items:center;gap:8px}.tav2-bar{display:flex;flex:none;width:70px;height:8px;border-radius:2px;overflow:hidden;background:#edf2f7}\n"] }]
4743
+ args: [{ selector: "topact_v2", providers: [{ provide: LocalizationService, useClass: AdmTopActivityV2Loc }], 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\">\r\n {{'Attivit\u00E0 Principali' | localize: lc}}\r\n @if (DirInfos) {\r\n <span>&nbsp;-&nbsp;\r\n {{'' + DirInfos.cpus + ' ' + ('CPU' | localize: lc) + (DirInfos.rssplan ? ' - ' + DirInfos.rssplan : '')}}&nbsp;\r\n Load average: {{ DirInfos.lavg1 | number : '1.1-2' : 'it'}}&nbsp;{{ DirInfos.lavg5 | number : '1.1-2' : 'it'}}&nbsp;{{ DirInfos.lavg15 | number : '1.1-2' : 'it'}}\r\n </span>\r\n }\r\n </span>\r\n </div>\r\n <div class=\"card-body widget-body\">\r\n <!-- Barra in alto: quali dati caricare (finestra, istanza, origine). Come disegnarli sta nell'intestazione del grafico -->\r\n <form role=\"form\">\r\n <!-- Tre gruppi che vanno a capo interi: periodo, sorgente (con la ricerca), aggiornamento automatico -->\r\n <div class=\"tav2-toolbar\">\r\n <div class=\"tav2-cluster\">\r\n <div class=\"tav2-field\">\r\n <form-datetime [readonly]=\"refreshSeconds != '0'\" [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"filterfrom\" name=\"filterfrom\" [Placeholder]=\"'Da' | localize: lc\" (inputChange)=\"datesEdited()\"></form-datetime>\r\n </div>\r\n <div class=\"tav2-field\">\r\n <form-datetime [readonly]=\"refreshSeconds != '0'\" [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"filterto\" name=\"filterto\" [Placeholder]=\"'A' | localize: lc\" (inputChange)=\"datesEdited()\"></form-datetime>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Ultime' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (h of QuickRanges; track h) {\r\n <button type=\"button\" [class.tav2-on]=\"activeRange == h\" (click)=\"quickRange(h)\">{{h}} h</button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"tav2-cluster\">\r\n <div class=\"tav2-field tav2-field-wide tav2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"instance\" name=\"instance\" [Source]=\"Instances\" [Placeholder]=\"'Istanza' | localize: lc\" (inputChange)=\"search()\"></form-select>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Origine dati' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of DataSources; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"dataSource == o.id\" (click)=\"setDataSource(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <button [disabled]=\"loading || refreshSeconds != '0'\" class=\"btn app-btn-search\" (click)=\"searchClicked()\" type=\"submit\" [title]=\"'Cerca' | localize: lc\">\r\n <i class=\"fa\" [class.fa-search]=\"!loading\" [class.fa-spinner]=\"loading\" [class.fa-spin]=\"loading\"></i>\r\n </button>\r\n </div>\r\n\r\n <div class=\"tav2-cluster tav2-cluster-end\">\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\"><i class=\"fa fa-refresh\"></i> {{'Aggiornamento automatico' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of RefreshOptions; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"refreshSeconds == o.id\" (click)=\"setRefresh(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n\r\n <!-- Grafico -->\r\n <div class=\"tav2-panel\">\r\n <div class=\"tav2-panel-header tav2-chart-header\">\r\n <div class=\"tav2-heading\">\r\n <span class=\"tav2-title\">{{'Sessioni attive medie per' | localize: lc}} {{dimensionLabel(result?.dimension ?? +dimension) | lowercase}}</span>\r\n @if (result) {\r\n <span class=\"tav2-muted\">\r\n {{'Media' | localize: lc}} <strong>{{result.estimated ? '\u2248 ' : ''}}{{result.averageActiveSessions | number : '1.2-3' : 'it'}}</strong>\r\n &middot; {{'Picco' | localize: lc}} <strong>{{result.estimated ? '\u2248 ' : ''}}{{result.peakActiveSessions | number : '1.2-3' : 'it'}}</strong>\r\n &middot; {{'un punto ogni' | localize: lc}} {{result.bucketSeconds}} s\r\n @if (result.estimated) {\r\n <span class=\"tav2-estimate\" [title]=\"'Valori stimati contando i campioni, come in Oracle, invece che misurati' | localize: lc\">{{'stima per campionamento' | localize: lc}}</span>\r\n }\r\n @if (loading) {\r\n &nbsp;<i class=\"fa fa-spinner fa-spin\"></i>\r\n }\r\n </span>\r\n }\r\n </div>\r\n\r\n <!-- Come disegnare i dati -->\r\n <div class=\"tav2-view-controls\">\r\n <div class=\"tav2-field tav2-field-wide tav2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"dimension\" name=\"dimension\" [Source]=\"Dimensions\" [Placeholder]=\"'Dividi per' | localize: lc\" (inputChange)=\"search()\"></form-select>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Risoluzione' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of Resolutions; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"resolution == o.id\" (click)=\"setResolution(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"tav2-group\">\r\n <span class=\"tav2-group-label\">{{'Limite' | localize: lc}}</span>\r\n <div class=\"tav2-seg\">\r\n @for (o of LimitOptions; track o.id) {\r\n <button type=\"button\" [class.tav2-on]=\"limitValue == o.id\" (click)=\"setLimit(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"tav2-toggle\">\r\n <form-checkbox [FormLayout]=\"false\" [SliderMode]=\"true\" [Validation]=\"false\" [(ngModel)]=\"totalActivity\" (inputChange)=\"search()\"></form-checkbox>\r\n <span class=\"tav2-pointer\" (click)=\"totalActivity = !totalActivity; search()\">{{'Attivit\u00E0 totale' | localize: lc}}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (filters.length > 0) {\r\n <div class=\"tav2-chips\">\r\n <span class=\"tav2-muted\">{{'Filtri' | localize: lc}}</span>\r\n @for (chip of filters; track $index) {\r\n <span class=\"tav2-chip\">\r\n {{chip.label}}\r\n <button type=\"button\" [title]=\"'Rimuovi' | localize: lc\" (click)=\"removeFilter($index)\">&times;</button>\r\n </span>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"tav2-chart\">\r\n @if (chartData) {\r\n <es-line-chart [MinGridDistance]=\"65\"\r\n [Timeline]=\"false\"\r\n [FinalInstants]=\"false\"\r\n [Stacked]=\"true\"\r\n [Step]=\"true\"\r\n [Fill]=\"true\"\r\n [MergeDatasets]=\"true\"\r\n [Data]=\"chartData\"\r\n [Max]=\"chartMax\"\r\n [Legend]=\"false\"\r\n name=\"topactv2\"\r\n [DataGroupingThreshold]=\"99999999\"\r\n (chartLoaded)=\"onChartLoaded($event)\"></es-line-chart>\r\n } @else {\r\n <div class=\"tav2-empty\">{{(loading ? 'Carico i Dati' : 'Nessun dato nella finestra') | localize: lc}}</div>\r\n }\r\n </div>\r\n\r\n <!-- Legenda: un clic su una voce la aggiunge ai filtri (\"altri\" non \u00E8 filtrabile) -->\r\n @if (legend.length > 0) {\r\n <div class=\"tav2-legend\">\r\n @for (item of legend; track $index) {\r\n <span class=\"tav2-legend-item\" [class.tav2-clickable]=\"!item.other\" [title]=\"item.other ? '' : ('Clic per filtrare' | localize: lc)\" (click)=\"addFilter(item)\">\r\n <span class=\"tav2-swatch\" [style.background]=\"item.color\"></span>\r\n {{item.label}}\r\n <span class=\"tav2-muted\">{{item.percentage | number : '1.0-1' : 'it'}}%</span>\r\n </span>\r\n }\r\n @if (result?.totalActivity) {\r\n <span class=\"tav2-legend-item\">\r\n <span class=\"tav2-line\" [style.border-color]=\"TotalColor\"></span>\r\n {{'Attivit\u00E0 totale' | localize: lc}}\r\n </span>\r\n }\r\n @if (result?.maximumThreads != null) {\r\n <span class=\"tav2-legend-item\">\r\n <span class=\"tav2-line\" [style.border-color]=\"LimitColor\"></span>\r\n {{limitLabel}} <strong>{{result.maximumThreads}}</strong>\r\n </span>\r\n }\r\n </div>\r\n }\r\n\r\n @if (memoryFrom) {\r\n <div class=\"tav2-note\">\r\n <i class=\"fa fa-info-circle\"></i>\r\n {{'Prima di questo istante i dati vengono dallo storico (un campione ogni 10 secondi)' | localize: lc}}: {{memoryFrom}}\r\n </div>\r\n }\r\n\r\n <!-- Time slider: finestra di 5 minuti per le tabelle -->\r\n @if (sliderMax > sliderMin) {\r\n <div class=\"tav2-slider\">\r\n <span class=\"tav2-slider-label\">\r\n <span class=\"tav2-muted\">{{'Finestra tabelle' | localize: lc}}</span>\r\n <strong>{{tablesWindowLabel}}</strong>\r\n </span>\r\n <input type=\"range\" [min]=\"sliderMin\" [max]=\"sliderMax\" [step]=\"sliderStep\" [ngModel]=\"sliderValue\" (ngModelChange)=\"sliderValue = $event; sliderMoving()\" (change)=\"sliderChanged()\" />\r\n <span class=\"tav2-badge\" [class.tav2-badge-off]=\"!sliderAnchored\">{{(sliderAnchored ? 'Ultimi 5 minuti' : 'Finestra fissa') | localize: lc}}</span>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Tabelle -->\r\n <div class=\"tav2-tables\">\r\n @for (table of tables; track $index; let i = $index) {\r\n <div class=\"tav2-panel\">\r\n <div class=\"tav2-panel-header\">\r\n <span class=\"tav2-title\">{{'Prime righe per' | localize: lc}}</span>\r\n <div class=\"tav2-field tav2-field-wide tav2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [(ngModel)]=\"tableDims[i]\" name=\"tabledim{{i}}\" [Source]=\"Dimensions\" (inputChange)=\"search()\"></form-select>\r\n </div>\r\n <span class=\"tav2-muted\">{{'Attivit\u00E0' | localize: lc}} <strong>{{table.estimated ? '\u2248 ' : ''}}{{table.activity | number : '1.2-3' : 'it'}}</strong></span>\r\n @if (table.estimated) {\r\n <span class=\"tav2-estimate\" [title]=\"'Stima contando i campioni: il totale pu\u00F2 differire di poco da quello misurato delle sessioni' | localize: lc\">{{'stima per campionamento' | localize: lc}}</span>\r\n }\r\n </div>\r\n <es-table [(ngModel)]=\"table.items\" [HidePaging]=\"true\">\r\n <ng-template #header>\r\n <th>{{dimensionLabel(table.dimension)}}</th>\r\n <th class=\"app-col-min\">{{'Attivit\u00E0' | localize: lc}}</th>\r\n <th class=\"app-col-min tav2-right\">%</th>\r\n @if (table.dimension == Dim.UserSession) {\r\n <th>{{'Utente' | localize: lc}}</th>\r\n <th>{{'Tipo' | localize: lc}}</th>\r\n <th>{{'Processo' | localize: lc}}</th>\r\n }\r\n @if (table.dimension == Dim.WaitEvent) {\r\n <th class=\"app-col-min app-nowrap\">{{'Tipo' | localize: lc}}</th>\r\n }\r\n </ng-template>\r\n <ng-template #body let-item>\r\n <td>\r\n @if (isClickable(item)) {\r\n <span class=\"app-link tav2-ellipsis\" [title]=\"item.label || item.key\" (click)=\"openItem(item)\">{{itemLabel(item, table)}}</span>\r\n } @else {\r\n <span class=\"tav2-ellipsis\" [class.tav2-muted]=\"item.other || !item.label\">{{itemLabel(item, table)}}</span>\r\n }\r\n </td>\r\n <td class=\"app-nowrap\">\r\n <div class=\"tav2-bar-cell\">\r\n <div class=\"tav2-bar\">\r\n @for (part of item.breakdown; track $index) {\r\n <div [style.width.%]=\"breakdownWidth(item, part)\" [style.background]=\"breakdownColor(part)\" [title]=\"part.key\"></div>\r\n }\r\n </div>\r\n <span>{{table.estimated ? '\u2248 ' : ''}}{{item.activity | number : '1.2-3' : 'it'}}</span>\r\n </div>\r\n </td>\r\n <td class=\"app-nowrap tav2-right\">{{item.percentage | number : '1.1-1' : 'it'}}</td>\r\n @if (table.dimension == Dim.UserSession) {\r\n <td class=\"app-nowrap\">\r\n {{item.session?.userName}}\r\n @if (item.session?.tenantId) {\r\n <span class=\"tav2-muted\">&middot; {{item.session.tenantId}}</span>\r\n }\r\n </td>\r\n <td class=\"tav2-muted\">{{item.session?.sessionType | titlecase}}</td>\r\n <td><span class=\"tav2-ellipsis tav2-ellipsis-short\" [title]=\"item.session?.bgProcessName || item.session?.jobName || ''\">{{item.session?.bgProcessName || item.session?.jobName}}</span></td>\r\n }\r\n @if (table.dimension == Dim.WaitEvent) {\r\n <td class=\"app-nowrap\">\r\n {{item.event?.eventType}}\r\n @if (item.event?.waitClass && item.event.waitClass != 'None') {\r\n <span class=\"tav2-muted\">&middot; {{item.event.waitClass}}</span>\r\n }\r\n </td>\r\n }\r\n </ng-template>\r\n </es-table>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<div bsModal #dlgNodeDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\r\n @if (NodeDetails) {\r\n <node-detail [NodeDetails]=\"NodeDetails\" (onClose)=\"dlgNodeDetails.hide()\"></node-detail>\r\n }\r\n</div>\r\n\r\n<div bsModal #dlgSessionDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\r\n @if (SessionDetails) {\r\n <session-detail [SessionDetails]=\"SessionDetails\" (onClose)=\"dlgSessionDetails.hide()\" (onNodeSelected)=\"loadNodeDetails($event)\"></session-detail>\r\n }\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .tav2-noclear .p-select-clear-icon{display:none}.tav2-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 22px;padding-top:6px}.tav2-cluster{display:flex;flex-wrap:nowrap;align-items:center;gap:10px}.tav2-cluster-end{margin-left:auto}.tav2-field{width:165px}.tav2-field-wide{width:210px}.tav2-spacer{flex:1 1 auto}.tav2-toggle{display:flex;align-items:center;gap:8px;height:39px;white-space:nowrap}.tav2-pointer{cursor:pointer}.tav2-group{position:relative;display:inline-flex}.tav2-group-label{position:absolute;top:-8px;left:8px;z-index:1;padding:0 4px;background:#fff;color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.tav2-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:#fff}.tav2-seg button{padding:0 12px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:#334155;font-size:13px;white-space:nowrap;cursor:pointer}.tav2-seg button:first-child{border-left:0}.tav2-seg button:hover:not(.tav2-on){background:#f8fafc}.tav2-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.tav2-seg button.tav2-on{background:#eef2ff;color:#4338ca;font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.tav2-panel{margin-top:14px;padding:10px 12px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:#fff;min-width:0}.tav2-panel-header{display:flex;flex-wrap:wrap;align-items:center;gap:6px 14px;min-height:32px;margin-bottom:6px}.tav2-chart-header{justify-content:space-between;padding-top:8px;margin-bottom:10px}.tav2-heading{display:flex;flex-direction:column;gap:2px}.tav2-view-controls{display:flex;flex-wrap:wrap;align-items:center;gap:14px 10px}.tav2-title{font-size:12px;font-weight:500;letter-spacing:.03em;text-transform:uppercase;color:#344154}.tav2-muted{color:var(--p-text-muted-color, #64748b);font-size:12px}.tav2-right{text-align:right}.tav2-estimate{display:inline-block;margin-left:6px;padding:0 6px;border:1px solid #fcd34d;border-radius:10px;background:#fffbeb;color:#92400e;font-size:11px;line-height:16px;white-space:nowrap;cursor:help}.tav2-ellipsis{display:inline-block;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.tav2-ellipsis-short{max-width:170px}.tav2-tables .tav2-panel{container-type:inline-size}@container (min-width: 900px){.tav2-ellipsis{max-width:520px}.tav2-ellipsis-short{max-width:300px}}.tav2-chips{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:6px}.tav2-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 10px;border-radius:12px;background:#eef2ff;color:#3730a3;font-size:12px}.tav2-chip button{border:0;background:transparent;color:inherit;cursor:pointer;font-size:15px;line-height:1;padding:0 4px}.tav2-chart{position:relative;height:300px}.tav2-empty{display:flex;align-items:center;justify-content:center;height:100%;border:1px dashed var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);color:var(--p-text-muted-color, #64748b)}.tav2-legend{display:flex;flex-wrap:wrap;gap:4px 6px;margin-top:6px}.tav2-legend-item{display:inline-flex;align-items:center;gap:6px;padding:2px 8px;border:1px solid transparent;border-radius:4px;font-size:12px;white-space:nowrap}.tav2-legend-item.tav2-clickable{cursor:pointer}.tav2-legend-item.tav2-clickable:hover{border-color:var(--p-content-border-color, #e2e8f0);background:#f8fafc}.tav2-swatch{flex:none;width:10px;height:10px;border-radius:2px}.tav2-line{flex:none;width:16px;height:0;border-top:2px solid}.tav2-note{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.tav2-slider{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin-top:10px;padding-top:10px;border-top:1px solid var(--p-content-border-color, #e2e8f0)}.tav2-slider input[type=range]{flex:1 1 260px;min-width:200px;accent-color:var(--p-primary-color, #6366f1);cursor:pointer}.tav2-slider-label{display:inline-flex;flex-direction:column;line-height:1.2;font-size:12px}.tav2-badge{padding:2px 8px;border-radius:10px;background:#eef2ff;color:#3730a3;font-size:11px;white-space:nowrap}.tav2-badge.tav2-badge-off{background:#f1f5f9;color:var(--p-text-muted-color, #64748b)}.tav2-tables{display:grid;grid-template-columns:repeat(auto-fit,minmax(560px,1fr));gap:0 14px}.tav2-bar-cell{display:flex;align-items:center;gap:8px}.tav2-bar{display:flex;flex:none;width:70px;height:8px;border-radius:2px;overflow:hidden;background:#edf2f7}\n"] }]
1769
4744
  }], ctorParameters: () => [{ type: i1$1.DateService }, { type: i1.LocalizationService }, { type: i3.PreferencesService }, { type: i1$1.InterComService }, { type: i4.ActivatedRoute }, { type: AdminModuleBaseService }, { type: i0.NgZone }], propDecorators: { dlgNodeDetails: [{
1770
4745
  type: ViewChild,
1771
4746
  args: ["dlgNodeDetails"]
@@ -2488,13 +5463,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
2488
5463
  // La Top Activity v2 prende la rotta "topact" solo se DirectorFeature.TopActivityV2 è attivo nella configurazione del modulo,
2489
5464
  // altrimenti la guardia fallisce e la rotta successiva apre la pagina di sempre
2490
5465
  const topActivityV2Enabled = () => inject(ADM_TOPACT_V2, { optional: true }) ?? false;
5466
+ // Lo stesso per il profiler con DirectorFeature.ProfilerV2
5467
+ const profilerV2Enabled = () => inject(ADM_PROFILER_V2, { optional: true }) ?? false;
5468
+ // La pagina del workload report c'è solo con DirectorFeature.WorkloadReportV2: prima il report era solo un file da scaricare dal profiler
5469
+ const workloadReportV2Enabled = () => inject(ADM_WORKLOAD_V2, { optional: true }) ?? false;
5470
+ // Le metriche v2 prendono la rotta "metrics" solo con DirectorFeature.MetricsV2, come la Top Activity
5471
+ const metricsV2Enabled = () => inject(ADM_METRICS_V2, { optional: true }) ?? false;
2491
5472
  const routeConfig = [
2492
5473
  {
2493
5474
  path: "", children: [
2494
5475
  { path: "serverworkers/:be", component: AdmWrkComponent, data: { title: "Ricerca Processi Applicativi", internalNavigation: false } },
5476
+ { path: "profiler/:be", component: AdmProfilerV2Component, canMatch: [profilerV2Enabled], data: { title: "Profiler del Server", internalNavigation: false } },
2495
5477
  { path: "profiler/:be", component: AdmProfilerComponent, data: { title: "Profiler del Server", internalNavigation: false } },
5478
+ { path: "workload/:be", component: AdmWorkloadV2Component, canMatch: [workloadReportV2Enabled], data: { title: "Workload report", internalNavigation: false } },
2496
5479
  { path: "topact/:be", component: AdmTopActivityV2Component, canMatch: [topActivityV2Enabled], data: { title: "Attività Principali", internalNavigation: false } },
2497
5480
  { path: "topact/:be", component: AdmTopActivitiesComponent, data: { title: "Attività Principali", internalNavigation: false } },
5481
+ { path: "metrics/:be", component: AdmMetricsV2Component, canMatch: [metricsV2Enabled], data: { title: "Metriche di Sistema", internalNavigation: false } },
2498
5482
  { path: "metrics/:be", component: AdmMetricsComponent, data: { title: "Metriche di Sistema", internalNavigation: false } },
2499
5483
  { path: "serverlogs/:be", component: AdmLogsComponent, data: { title: "Log del Server", internalNavigation: false } },
2500
5484
  { path: "serveraudits/:be", component: AdmAuditsComponent, data: { title: "Ricerca Audit Applicativi", internalNavigation: false } },
@@ -2502,9 +5486,12 @@ const routeConfig = [
2502
5486
  { path: "serverviews/:be", component: AdmDynamicViewsComponent, data: { title: "Ricerca Viste Dinamiche Applicative", internalNavigation: false } },
2503
5487
  { path: "metadataquery/:be", component: MtdQueryComponent, data: { title: "Ricerca Viste Dinamiche Applicative", internalNavigation: false } },
2504
5488
  { path: "serverworkers", component: AdmWrkComponent, data: { title: "Ricerca Processi Applicativi", internalNavigation: false } },
5489
+ { path: "profiler", component: AdmProfilerV2Component, canMatch: [profilerV2Enabled], data: { title: "Profiler del Server", internalNavigation: false } },
2505
5490
  { path: "profiler", component: AdmProfilerComponent, data: { title: "Profiler del Server", internalNavigation: false } },
5491
+ { path: "workload", component: AdmWorkloadV2Component, canMatch: [workloadReportV2Enabled], data: { title: "Workload report", internalNavigation: false } },
2506
5492
  { path: "topact", component: AdmTopActivityV2Component, canMatch: [topActivityV2Enabled], data: { title: "Attività Principali", internalNavigation: false } },
2507
5493
  { path: "topact", component: AdmTopActivitiesComponent, data: { title: "Attività Principali", internalNavigation: false } },
5494
+ { path: "metrics", component: AdmMetricsV2Component, canMatch: [metricsV2Enabled], data: { title: "Metriche di Sistema", internalNavigation: false } },
2508
5495
  { path: "metrics", component: AdmMetricsComponent, data: { title: "Metriche di Sistema", internalNavigation: false } },
2509
5496
  { path: "serverlogs", component: AdmLogsComponent, data: { title: "Log del Server", internalNavigation: false } },
2510
5497
  { path: "serveraudits", component: AdmAuditsComponent, data: { title: "Ricerca Audit Applicativi", internalNavigation: false } },
@@ -2538,6 +5525,10 @@ const MODULES = [
2538
5525
  const COMPONENTS = [
2539
5526
  AdmWrkComponent,
2540
5527
  AdmProfilerComponent,
5528
+ AdmProfilerV2Component,
5529
+ AdmWorkloadV2Component,
5530
+ AdmMetricsV2Component,
5531
+ MetricsV2ChartComponent,
2541
5532
  AdmTopActivitiesComponent,
2542
5533
  AdmTopActivityV2Component,
2543
5534
  SessionDetailComponent,
@@ -2551,7 +5542,8 @@ const COMPONENTS = [
2551
5542
  AdmSystemParametersComponent
2552
5543
  ];
2553
5544
  const PIPES = [
2554
- FormatTimePipe
5545
+ FormatTimePipe,
5546
+ ProfilerFormatPipe
2555
5547
  ];
2556
5548
  class AdmModule {
2557
5549
  constructor(commService) {
@@ -2562,6 +5554,10 @@ class AdmModule {
2562
5554
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmModule, deps: [{ token: AdminModuleCommService }], target: i0.ɵɵFactoryTarget.NgModule }); }
2563
5555
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.34", ngImport: i0, type: AdmModule, declarations: [AdmWrkComponent,
2564
5556
  AdmProfilerComponent,
5557
+ AdmProfilerV2Component,
5558
+ AdmWorkloadV2Component,
5559
+ AdmMetricsV2Component,
5560
+ MetricsV2ChartComponent,
2565
5561
  AdmTopActivitiesComponent,
2566
5562
  AdmTopActivityV2Component,
2567
5563
  SessionDetailComponent,
@@ -2589,11 +5585,15 @@ class AdmModule {
2589
5585
  EsGertrudeModule,
2590
5586
  EsChartsModule,
2591
5587
  ComponentsModule,
2592
- DirectivesModule, i4.RouterModule, FormatTimePipe] }); }
5588
+ DirectivesModule, i4.RouterModule, FormatTimePipe,
5589
+ ProfilerFormatPipe] }); }
2593
5590
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmModule, providers: [
2594
5591
  AdminModuleCommService,
2595
5592
  { provide: ADM_CONVERSATIONS, useFactory: () => !!myconfig.TransactionTimeoutSeconds },
2596
- { provide: ADM_TOPACT_V2, useFactory: () => !!myconfig?.DirectorFeature?.TopActivityV2 }
5593
+ { provide: ADM_TOPACT_V2, useFactory: () => !!myconfig?.DirectorFeature?.TopActivityV2 },
5594
+ { provide: ADM_PROFILER_V2, useFactory: () => !!myconfig?.DirectorFeature?.ProfilerV2 },
5595
+ { provide: ADM_WORKLOAD_V2, useFactory: () => !!myconfig?.DirectorFeature?.WorkloadReportV2 },
5596
+ { provide: ADM_METRICS_V2, useFactory: () => !!myconfig?.DirectorFeature?.MetricsV2 }
2597
5597
  ], imports: [MODULES] }); }
2598
5598
  }
2599
5599
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmModule, decorators: [{
@@ -2604,7 +5604,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
2604
5604
  providers: [
2605
5605
  AdminModuleCommService,
2606
5606
  { provide: ADM_CONVERSATIONS, useFactory: () => !!myconfig.TransactionTimeoutSeconds },
2607
- { provide: ADM_TOPACT_V2, useFactory: () => !!myconfig?.DirectorFeature?.TopActivityV2 }
5607
+ { provide: ADM_TOPACT_V2, useFactory: () => !!myconfig?.DirectorFeature?.TopActivityV2 },
5608
+ { provide: ADM_PROFILER_V2, useFactory: () => !!myconfig?.DirectorFeature?.ProfilerV2 },
5609
+ { provide: ADM_WORKLOAD_V2, useFactory: () => !!myconfig?.DirectorFeature?.WorkloadReportV2 },
5610
+ { provide: ADM_METRICS_V2, useFactory: () => !!myconfig?.DirectorFeature?.MetricsV2 }
2608
5611
  ]
2609
5612
  }]
2610
5613
  }], ctorParameters: () => [{ type: AdminModuleCommService }] });
@@ -2617,5 +5620,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
2617
5620
  * Generated bundle index. Do not edit.
2618
5621
  */
2619
5622
 
2620
- export { ADM_CONVERSATIONS, ADM_MODULE_PATH, ADM_TOPACT_V2, AdmMessageTypes, AdmModule, MK_ADM, routeConfig, topActivityV2Enabled };
5623
+ 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 };
2621
5624
  //# sourceMappingURL=esfaenza-admin-module.mjs.map