@esfaenza/admin-module 20.3.20 → 20.3.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/esfaenza-admin-module.mjs +3181 -176
- package/fesm2022/esfaenza-admin-module.mjs.map +1 -1
- package/index.d.ts +942 -22
- package/package.json +1 -1
|
@@ -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
|
|
168
|
+
let ret = await this.v2Call(this.http.post(POST_Director_GetTopActivities, req, pars, "", false));
|
|
161
169
|
return ret;
|
|
162
170
|
}
|
|
171
|
+
// Stesso URL di getProfilerDataNodes: con DirectorFeature.ProfilerV2 attivo il backend-for-frontend risponde con l'albero del profiler v2
|
|
172
|
+
async getProfilerTreeV2(be, req) {
|
|
173
|
+
var pars = new HttpParams().set("be", be ?? "");
|
|
174
|
+
let ret = await this.v2Call(this.http.post(POST_Director_GetProfilerDataNodes, req, pars, "", false));
|
|
175
|
+
return ret;
|
|
176
|
+
}
|
|
177
|
+
// null se il nodo non c'è nelle istanze o nella finestra scelte
|
|
178
|
+
async getProfilerNodeV2(be, req) {
|
|
179
|
+
var pars = new HttpParams().set("be", be ?? "");
|
|
180
|
+
let ret = await this.v2Call(this.http.post(POST_Director_GetProfilerNode, req, pars, "", false));
|
|
181
|
+
return ret;
|
|
182
|
+
}
|
|
183
|
+
async getProfilerFlatV2(be, req) {
|
|
184
|
+
var pars = new HttpParams().set("be", be ?? "");
|
|
185
|
+
let ret = await this.v2Call(this.http.post(POST_Director_GetProfilerFlat, req, pars, "", false));
|
|
186
|
+
return ret;
|
|
187
|
+
}
|
|
188
|
+
// Le chiamate delle pagine v2 non usano la gestione errori di HTTPService, che in caso di errore restituisce un Observable che non termina mai:
|
|
189
|
+
// l'await resterebbe appeso e la pagina bloccata in caricamento. L'errore arriva al chiamante (vedi errorText), tranne la sessione scaduta:
|
|
190
|
+
// per quella ripeto una chiamata qualsiasi con la gestione standard, così il redirect al login lo fa HTTPService come sulle altre pagine
|
|
191
|
+
v2(call) {
|
|
192
|
+
return call.pipe(catchError(err => {
|
|
193
|
+
if (err?.status == 401)
|
|
194
|
+
this.http.get(GET_Director_GetInstances, new HttpParams()).subscribe();
|
|
195
|
+
return throwError(() => err);
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
v2Call(call) {
|
|
199
|
+
return firstValueFrom(this.v2(call));
|
|
200
|
+
}
|
|
201
|
+
// Istanza che risponde alla chiamata, facoltativa: se il backend non la dà la pagina va avanti senza
|
|
202
|
+
async getInstanceId(be) {
|
|
203
|
+
var pars = new HttpParams().set("be", be ?? "");
|
|
204
|
+
let ret = await firstValueFrom(this.http.get(GET_Director_GetInstanceId, pars, null, false).pipe(catchError(_ => of(null))));
|
|
205
|
+
return ret;
|
|
206
|
+
}
|
|
207
|
+
// Testo di un errore delle chiamate v2: il backend-for-frontend manda il messaggio come stringa JSON (542 per i 400 del backend).
|
|
208
|
+
// Solo la prima riga, perché i 500 portano lo stack
|
|
209
|
+
errorText(err) {
|
|
210
|
+
let message = typeof err?.error === "string" ? err.error : err?.error?.message ?? err?.message ?? "";
|
|
211
|
+
return String(message).split(/\r?\n/)[0];
|
|
212
|
+
}
|
|
163
213
|
async getInfos(be) {
|
|
164
214
|
var pars = new HttpParams().set("be", be ?? "");
|
|
165
215
|
let ret = await firstValueFrom(this.http.get(GET_Director_GetInfos, pars, null, false).pipe(catchError(_ => of(null))));
|
|
@@ -184,6 +234,45 @@ class AdminModuleBaseService {
|
|
|
184
234
|
let pars = new HttpParams().set("from", from ?? "").set("to", to ?? "").set("be", be ?? "").set("global", global);
|
|
185
235
|
this.http.getFileNative(GET_Director_GetWorkloadReport, pars);
|
|
186
236
|
}
|
|
237
|
+
// Stesso URL di downloadWorkloadReport: con DirectorFeature.WorkloadReportV2 attivo il backend-for-frontend non manda più un file
|
|
238
|
+
// ma l'HTML del report come stringa JSON. Restituisce l'Observable perché la generazione dura qualche secondo e si può annullare
|
|
239
|
+
getWorkloadReportV2(be, req) {
|
|
240
|
+
let pars = new HttpParams().set("be", be ?? "");
|
|
241
|
+
if (req.snapFrom != null)
|
|
242
|
+
pars = pars.set("snapFrom", req.snapFrom).set("snapTo", req.snapTo);
|
|
243
|
+
if (req.global != null)
|
|
244
|
+
pars = pars.set("global", req.global);
|
|
245
|
+
for (let id of req.instanceIds ?? [])
|
|
246
|
+
pars = pars.append("instanceIds", id);
|
|
247
|
+
return this.v2(this.http.get(GET_Director_GetWorkloadReport, pars, null, false));
|
|
248
|
+
}
|
|
249
|
+
// Stesso URL di getMetricsList: con DirectorFeature.MetricsV2 attivo il backend-for-frontend risponde con il catalogo (nome, tipo, unità, istanze)
|
|
250
|
+
async getMetricsCatalogV2(be, req) {
|
|
251
|
+
let pars = new HttpParams().set("be", be ?? "");
|
|
252
|
+
if (req.global != null)
|
|
253
|
+
pars = pars.set("global", req.global);
|
|
254
|
+
for (let id of req.instanceIds ?? [])
|
|
255
|
+
pars = pars.append("instanceIds", id);
|
|
256
|
+
let ret = await this.v2Call(this.http.get(GET_Director_GetMetrics, pars, null, false));
|
|
257
|
+
return ret;
|
|
258
|
+
}
|
|
259
|
+
// Stesso URL di getMetrics: con DirectorFeature.MetricsV2 attivo il backend-for-frontend risponde con i campioni del backend così come sono
|
|
260
|
+
async getMetricsV2(be, req) {
|
|
261
|
+
var pars = new HttpParams().set("be", be ?? "");
|
|
262
|
+
let ret = await this.v2Call(this.http.post(POST_Director_GetMetrics, req, pars, "", false));
|
|
263
|
+
return ret;
|
|
264
|
+
}
|
|
265
|
+
async getWorkloadSnapshots(be, req) {
|
|
266
|
+
let pars = new HttpParams().set("be", be ?? "");
|
|
267
|
+
if (req.from)
|
|
268
|
+
pars = pars.set("from", req.from);
|
|
269
|
+
if (req.to)
|
|
270
|
+
pars = pars.set("to", req.to);
|
|
271
|
+
for (let id of req.instanceIds ?? [])
|
|
272
|
+
pars = pars.append("instanceIds", id);
|
|
273
|
+
let ret = await this.v2Call(this.http.get(GET_Director_GetSnapshots, pars, null, false));
|
|
274
|
+
return ret;
|
|
275
|
+
}
|
|
187
276
|
async getMetrics(be, sv) {
|
|
188
277
|
var pars = new HttpParams().set("be", be ?? "");
|
|
189
278
|
let ret = await firstValueFrom(this.http.post(POST_Director_GetMetrics, sv, pars));
|
|
@@ -753,14 +842,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
|
|
|
753
842
|
|
|
754
843
|
// Angular
|
|
755
844
|
class AdmProfilerComponent {
|
|
756
|
-
constructor(lc, localStorage, location, intercom, route, adms, mod) {
|
|
845
|
+
constructor(lc, localStorage, location, intercom, route, adms, router, mod, workloadV2) {
|
|
757
846
|
this.lc = lc;
|
|
758
847
|
this.localStorage = localStorage;
|
|
759
848
|
this.location = location;
|
|
760
849
|
this.intercom = intercom;
|
|
761
850
|
this.route = route;
|
|
762
851
|
this.adms = adms;
|
|
852
|
+
this.router = router;
|
|
763
853
|
this.mod = mod;
|
|
854
|
+
this.workloadV2 = workloadV2;
|
|
764
855
|
this.searchView = new ProfilerDataNodesView();
|
|
765
856
|
this.be = route.snapshot.params["be"] || "";
|
|
766
857
|
this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
|
|
@@ -805,7 +896,12 @@ class AdmProfilerComponent {
|
|
|
805
896
|
this.searchView.page = 1;
|
|
806
897
|
this.POSTGetItems(this.searchView);
|
|
807
898
|
}
|
|
899
|
+
// Con DirectorFeature.WorkloadReportV2 lo stesso URL non dà più un file: il report ha la sua pagina
|
|
808
900
|
downloadWorkloadReport() {
|
|
901
|
+
if (this.workloadV2) {
|
|
902
|
+
this.router.navigate([`/pages/${this.mod}/workload`, ...(this.be ? [this.be] : [])]);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
809
905
|
this.adms.downloadWorkloadReport(this.be, this.searchView.filterfrom, this.searchView.filterto, this.searchView.filterglobal);
|
|
810
906
|
}
|
|
811
907
|
async POSTGetItems(data) {
|
|
@@ -826,219 +922,3090 @@ class AdmProfilerComponent {
|
|
|
826
922
|
this.NodeDetails = await this.adms.getNodeStats(this.be, nodeId);
|
|
827
923
|
});
|
|
828
924
|
}
|
|
829
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerComponent, deps: [{ token: i1.LocalizationService }, { token: i3.PreferencesService }, { token: i5.Location }, { token: i1$1.InterComService }, { token: i4.ActivatedRoute }, { token: AdminModuleBaseService }, { token: ADM_MODULE_PATH }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
830
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: AdmProfilerComponent, isStandalone: false, selector: "adm_profiler", providers: [{ provide: LocalizationService, useClass: AdmProfilerComponentLoc }], ngImport: i0, template: "<div class=\"row\">\n <div class=\"col-12\">\n <div class=\"card app-overflow-visible\" widget>\n <div class=\"card-header\">\n <span class=\"text-uppercase\">{{'Profiler del Server' | localize: lc}}</span> \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> \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> \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> \n <span class=\"app-pointer app-padding-top-5 app-padding-left-5\" (click)=\"searchView.filterglobal = !searchView.filterglobal\">{{'Globale' | localize: lc}}</span>\n </div>\n\n <button [disabled]=\"searchView.searchinprogress\" class=\"input-group-addon btn app-btn-search app-margin-top-20 app-margin-right-5\" (click)=\"sendSearch()\" type=\"submit\"><i class=\"fa fa-search\"></i></button>\n <button class=\"input-group-addon btn app-btn-search app-margin-top-20\" (click)=\"downloadWorkloadReport()\" type=\"submit\" [title]=\"workloadV2 ? 'Workload report' : ''\"><i class=\"fa\" [class.fa-download]=\"!workloadV2\" [class.fa-file-text]=\"workloadV2\"></i></button>\n </div>\n </form>\n </div>\n <div class=\"row\">\n <div class=\"col-md-12\">\n @if (!searchView) {\n <p><em>{{'Carico i Dati' | localize: lc}}</em></p>\n }\n @if (searchView) {\n <div>\n <es-table [(ngModel)]=\"searchView\" [Hierarchy]=\"true\" [OrderByColumn]=\"false\" [ParentKey]=\"'parentid'\"\n [OwnKey]=\"'id'\" [StartsExpanded]=\"false\" [ExportFunction]=\"prepareExport.bind(this)\" [Export]=\"true\"\n [ExportFileName]=\"'Profiler Export.csv'\" [HidePaging]=\"true\" (onSearchRequest)=\"sendSearch($event);\">\n <span *th=\"'1'\">{{'ID' | localize : lc}}</span>\n <span *th=\"'2'\">{{'Descrizione' | localize: lc}}</span>\n <span *th=\"'3'\">{{'Chiamate' | localize: lc}}</span>\n <span *th=\"'4'\">{{'Tempo' | localize: lc}}</span>\n <span *th=\"'5'\">{{'Tempo Max' | localize: lc}}</span>\n <span *th=\"'6'\">{{'Tempo Min' | localize: lc}}</span>\n <span *th=\"'7'\">{{'Tempo Medio' | localize: lc}}</span>\n <span *th=\"'8'\">{{'Tempo Specifico' | localize: lc}}</span>\n <span *th=\"'9'\">{{'Percentuale' | localize: lc}}</span>\n <span *th=\"'10'\">{{'Chiamate Periodo' | localize: lc}}</span>\n <span *th=\"'11'\">{{'Tempo Periodo' | localize: lc}}</span>\n <span *th=\"'12'\">{{'Tempo Max Periodo' | localize: lc}}</span>\n <span *th=\"'13'\">{{'Tempo Min Periodo' | localize: lc}}</span>\n <span *th=\"'14'\">{{'Tempo Medio Periodo' | localize: lc}}</span>\n <span *th=\"'15'\">{{'Tempo Specifico Periodo' | localize: lc}}</span>\n <span *th=\"'16'\">{{'Percentuale Periodo' | localize: lc}}</span>\n <span *th=\"'17'\">{{'Nome Tipologia Evento' | localize: lc}}</span>\n <span *th=\"'18'\">{{'Nome Classe Attesa' | localize: lc}}</span>\n <span *th=\"'19'\">{{'Numero Istanze' | localize: lc}}</span>\n <span *td=\"'1'; let item\"><span class=\"app-link app-inline\" (click)=\"loadNodeDetails(item.id); dlgNodeDetails.show();\">{{item.id}}</span></span>\n <span *td=\"'2'; let item\" title=\"{{item.description}}\">{{item.description.length <= 50 ? item.description : item.description.substr(0,50) + \"[...]\" }}</span>\n <span *td=\"'3'; let item\">{{item.calls}}</span>\n <span *td=\"'4'; let item\">{{item.time}}</span>\n <span *td=\"'5'; let item\">{{item.peaktime}}</span>\n <span *td=\"'6'; let item\">{{item.mintime}}</span>\n <span *td=\"'7'; let item\">{{item.avgtime}}</span>\n <span *td=\"'8'; let item\">{{item.timewithoutchildren }}</span>\n <span *td=\"'9'; let item\">{{item.percentage}}</span>\n <span *td=\"'10' let item\">{{item.snapdeltacalls}}</span>\n <span *td=\"'11' let item\">{{item.snapdeltatime}}</span>\n <span *td=\"'12' let item\">{{item.snappeaktime}}</span>\n <span *td=\"'13' let item\">{{item.snapmintime}}</span>\n <span *td=\"'14' let item\">{{item.snapavgtime}}</span>\n <span *td=\"'15' let item\">{{item.snaptimewithoutchildren}}</span>\n <span *td=\"'16' let item\">{{item.snappercentage}}</span>\n <span *td=\"'17' let item\">{{item.eventtypename}}</span>\n <span *td=\"'18' let item\">{{item.waitclassname}}</span>\n <span *td=\"'19' let item\">{{item.numinstances}}</span>\n </es-table>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n\n<div bsModal #dlgNodeDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\n @if (NodeDetails) {\n <node-detail [NodeDetails]=\"NodeDetails\" (onClose)=\"dlgNodeDetails.hide()\"></node-detail>\n }\n</div>", dependencies: [{ kind: "directive", type: i7.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i7.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i7.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i8$1.ModalDirective, selector: "[bsModal]", inputs: ["config", "closeInterceptor"], outputs: ["onShow", "onShown", "onHide", "onHidden"], exportAs: ["bs-modal"] }, { kind: "directive", type: i8.EsTdDirective, selector: "[td]", inputs: ["tdVisible", "td", "tdForProperty", "tdIf", "tdClass", "tdPropertyAccessor", "tdAlignment", "tdFormat", "tdIconClass", "tdConditionField", "tdConditionValue", "tdTitle", "tdContent"] }, { kind: "directive", type: i8.EsThDirective, selector: "[th]", inputs: ["th", "thPinned", "thPinnedRight", "thPinnable", "thPinnedWidth", "thVisible", "thFixed", "thParent", "thOrder", "thOrderable", "thWrap", "thResizable", "thClass", "thShrink", "thGroupClass", "thIf", "thEmpty", "thGroup", "thAggregation", "thProperty", "thType", "thSource", "thMulti", "thStaticContent", "thBackgroundColor", "thAlignment", "thFormat", "thEditorName", "thEditorOptions", "thRendererName", "thRoutePath", "thRouteParameterProperties"] }, { kind: "component", type: i8.EsTableComponent, selector: "es-table", inputs: ["globalCheck", "autoUpdate", "seconds", "researchInProgress", "globalColVisCheck", "EsThTdProvider", "CornerMenuOptions", "ColumnsResizable", "ColumnsPinnable", "AutoUpdate", "Height", "EmptySpaceBackgroundColor", "EsTableHandledSearch", "MaxHeight", "ContainerClass", "XLSXExport", "CSVExport", "AllSearch", "HighCellDensity", "Selection", "ShowLoadingOnBootstrap", "Removal", "RemovalCondition", "Export", "ShiftClick", "CountLabel", "HidePaging", "HidePagingCount", "HidePagingButtons", "ContextMenu", "ExportFileName", "TableClass", "ExportFunction", "ExportOnlyVisibleColumns", "HasHeaderGroup", "HasSecondaryHeaderGroup", "HeaderHidden", "BodyHidden", "SelectionDisabled", "SingleSelection", "RowClassAssigner", "OrderByColumn", "MultipleOrderingDirectives", "HiddenColumns", "ColumnsOrdering", "SearchView", "SelectAll", "UseArrayModePaging", "DynamicRowColumnsDefinition", "DynamicOperations", "Hierarchy", "ParentKey", "OwnKey", "AutoSortHierarchy", "StartsExpanded", "SavePreferences", "DefaultAlignment", "PagingStyle", "RowGroupingPagingStyle", "ItemSourceProperty", "UseSelectionCache", "ShowItemGroupsColumns", "Editable", "RangeSelection", "SearchThrottle", "ArraymodeItemsPerPage"], outputs: ["globalCheckChange", "autoUpdateChange", "secondsChange", "researchInProgressChange", "globalColVisCheckChange", "onOrderChanged", "onSearchRequest", "onSelectionChanged", "onRemoval", "onAbortRemoval", "onModelChange", "onOpenContextMenu", "onCornerAction", "onDynamicOperation"] }, { kind: "component", type: i9.EsFormDateComponent, selector: "form-date", inputs: ["InOutFormat", "FloatingLabel", "DateFormat"] }, { kind: "component", type: i9.EsFormCheckboxComponent, selector: "form-checkbox", inputs: ["SliderMode"] }, { kind: "directive", type: i10.Widget, selector: "[widget]" }, { kind: "component", type: NodeDetailComponent, selector: "node-detail", inputs: ["NodeDetails"], outputs: ["onClose"] }, { kind: "pipe", type: i1.LocalizePipe, name: "localize" }] }); }
|
|
831
927
|
}
|
|
832
928
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerComponent, decorators: [{
|
|
833
929
|
type: Component,
|
|
834
|
-
args: [{ selector: "adm_profiler", providers: [{ provide: LocalizationService, useClass: AdmProfilerComponentLoc }], standalone: false, template: "<div class=\"row\">\n <div class=\"col-12\">\n <div class=\"card app-overflow-visible\" widget>\n <div class=\"card-header\">\n <span class=\"text-uppercase\">{{'Profiler del Server' | localize: lc}}</span> \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> \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> \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> \n <span class=\"app-pointer app-padding-top-5 app-padding-left-5\" (click)=\"searchView.filterglobal = !searchView.filterglobal\">{{'Globale' | localize: lc}}</span>\n </div>\n\n <button [disabled]=\"searchView.searchinprogress\" class=\"input-group-addon btn app-btn-search app-margin-top-20 app-margin-right-5\" (click)=\"sendSearch()\" type=\"submit\"><i class=\"fa fa-search\"></i></button>\n <button class=\"input-group-addon btn app-btn-search app-margin-top-20\" (click)=\"downloadWorkloadReport()\" type=\"submit\" [title]=\"workloadV2 ? 'Workload report' : ''\"><i class=\"fa\" [class.fa-download]=\"!workloadV2\" [class.fa-file-text]=\"workloadV2\"></i></button>\n </div>\n </form>\n </div>\n <div class=\"row\">\n <div class=\"col-md-12\">\n @if (!searchView) {\n <p><em>{{'Carico i Dati' | localize: lc}}</em></p>\n }\n @if (searchView) {\n <div>\n <es-table [(ngModel)]=\"searchView\" [Hierarchy]=\"true\" [OrderByColumn]=\"false\" [ParentKey]=\"'parentid'\"\n [OwnKey]=\"'id'\" [StartsExpanded]=\"false\" [ExportFunction]=\"prepareExport.bind(this)\" [Export]=\"true\"\n [ExportFileName]=\"'Profiler Export.csv'\" [HidePaging]=\"true\" (onSearchRequest)=\"sendSearch($event);\">\n <span *th=\"'1'\">{{'ID' | localize : lc}}</span>\n <span *th=\"'2'\">{{'Descrizione' | localize: lc}}</span>\n <span *th=\"'3'\">{{'Chiamate' | localize: lc}}</span>\n <span *th=\"'4'\">{{'Tempo' | localize: lc}}</span>\n <span *th=\"'5'\">{{'Tempo Max' | localize: lc}}</span>\n <span *th=\"'6'\">{{'Tempo Min' | localize: lc}}</span>\n <span *th=\"'7'\">{{'Tempo Medio' | localize: lc}}</span>\n <span *th=\"'8'\">{{'Tempo Specifico' | localize: lc}}</span>\n <span *th=\"'9'\">{{'Percentuale' | localize: lc}}</span>\n <span *th=\"'10'\">{{'Chiamate Periodo' | localize: lc}}</span>\n <span *th=\"'11'\">{{'Tempo Periodo' | localize: lc}}</span>\n <span *th=\"'12'\">{{'Tempo Max Periodo' | localize: lc}}</span>\n <span *th=\"'13'\">{{'Tempo Min Periodo' | localize: lc}}</span>\n <span *th=\"'14'\">{{'Tempo Medio Periodo' | localize: lc}}</span>\n <span *th=\"'15'\">{{'Tempo Specifico Periodo' | localize: lc}}</span>\n <span *th=\"'16'\">{{'Percentuale Periodo' | localize: lc}}</span>\n <span *th=\"'17'\">{{'Nome Tipologia Evento' | localize: lc}}</span>\n <span *th=\"'18'\">{{'Nome Classe Attesa' | localize: lc}}</span>\n <span *th=\"'19'\">{{'Numero Istanze' | localize: lc}}</span>\n <span *td=\"'1'; let item\"><span class=\"app-link app-inline\" (click)=\"loadNodeDetails(item.id); dlgNodeDetails.show();\">{{item.id}}</span></span>\n <span *td=\"'2'; let item\" title=\"{{item.description}}\">{{item.description.length <= 50 ? item.description : item.description.substr(0,50) + \"[...]\" }}</span>\n <span *td=\"'3'; let item\">{{item.calls}}</span>\n <span *td=\"'4'; let item\">{{item.time}}</span>\n <span *td=\"'5'; let item\">{{item.peaktime}}</span>\n <span *td=\"'6'; let item\">{{item.mintime}}</span>\n <span *td=\"'7'; let item\">{{item.avgtime}}</span>\n <span *td=\"'8'; let item\">{{item.timewithoutchildren }}</span>\n <span *td=\"'9'; let item\">{{item.percentage}}</span>\n <span *td=\"'10' let item\">{{item.snapdeltacalls}}</span>\n <span *td=\"'11' let item\">{{item.snapdeltatime}}</span>\n <span *td=\"'12' let item\">{{item.snappeaktime}}</span>\n <span *td=\"'13' let item\">{{item.snapmintime}}</span>\n <span *td=\"'14' let item\">{{item.snapavgtime}}</span>\n <span *td=\"'15' let item\">{{item.snaptimewithoutchildren}}</span>\n <span *td=\"'16' let item\">{{item.snappercentage}}</span>\n <span *td=\"'17' let item\">{{item.eventtypename}}</span>\n <span *td=\"'18' let item\">{{item.waitclassname}}</span>\n <span *td=\"'19' let item\">{{item.numinstances}}</span>\n </es-table>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n\n<div bsModal #dlgNodeDetails=\"bs-modal\" class=\"modal fade\" role=\"dialog\" aria-hidden=\"true\">\n @if (NodeDetails) {\n <node-detail [NodeDetails]=\"NodeDetails\" (onClose)=\"dlgNodeDetails.hide()\"></node-detail>\n }\n</div>" }]
|
|
931
|
+
}], ctorParameters: () => [{ type: i1.LocalizationService }, { type: i3.PreferencesService }, { type: i5.Location }, { type: i1$1.InterComService }, { type: i4.ActivatedRoute }, { type: AdminModuleBaseService }, { type: i4.Router }, { type: undefined, decorators: [{
|
|
836
932
|
type: Inject,
|
|
837
933
|
args: [ADM_MODULE_PATH]
|
|
934
|
+
}] }, { type: undefined, decorators: [{
|
|
935
|
+
type: Optional
|
|
936
|
+
}, {
|
|
937
|
+
type: Inject,
|
|
938
|
+
args: [ADM_WORKLOAD_V2]
|
|
838
939
|
}] }] });
|
|
839
940
|
|
|
840
|
-
|
|
841
|
-
|
|
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
|
-
|
|
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", "
|
|
848
|
-
super.set("it->en", "
|
|
849
|
-
|
|
850
|
-
super.set("it->en", "
|
|
851
|
-
super.set("it->en", "
|
|
852
|
-
super.set("it->en", "
|
|
1014
|
+
super.set("it->en", "Profiler del Server", ["Server Profiler"]);
|
|
1015
|
+
super.set("it->en", "Errore", ["Error"]);
|
|
1016
|
+
// Barra dei comandi e riga della finestra
|
|
1017
|
+
super.set("it->en", "Sorgente", ["Source"]);
|
|
1018
|
+
super.set("it->en", "Storico", ["History"]);
|
|
1019
|
+
super.set("it->en", "Istanze", ["Instances"]);
|
|
1020
|
+
super.set("it->en", "Questa istanza", ["This instance"]);
|
|
1021
|
+
super.set("it->en", "Tutto il cluster", ["Whole cluster"]);
|
|
1022
|
+
super.set("it->en", "istanze", ["instances"]);
|
|
1023
|
+
super.set("it->en", "istanza", ["instance"]);
|
|
1024
|
+
super.set("it->en", "Solo", ["Only"]);
|
|
1025
|
+
super.set("it->en", "Dal", ["From"]);
|
|
1026
|
+
super.set("it->en", "Al", ["To"]);
|
|
1027
|
+
super.set("it->en", "Valori", ["Values"]);
|
|
1028
|
+
super.set("it->en", "Dall'avvio", ["Since start"]);
|
|
1029
|
+
super.set("it->en", "Dall'ultimo snapshot", ["Since last snapshot"]);
|
|
1030
|
+
super.set("it->en", "Aggiorna", ["Refresh"]);
|
|
1031
|
+
super.set("it->en", "Scarica il workload report", ["Download the workload report"]);
|
|
1032
|
+
super.set("it->en", "Workload report", ["Workload report"]);
|
|
1033
|
+
super.set("it->en", "Delta dall'ultimo snapshot delle", ["Delta since the last snapshot at"]);
|
|
1034
|
+
super.set("it->en", "min fa", ["min ago"]);
|
|
1035
|
+
super.set("it->en", "s fa", ["s ago"]);
|
|
1036
|
+
super.set("it->en", "mai", ["never"]);
|
|
1037
|
+
super.set("it->en", "Snapshot dalle", ["Snapshots from"]);
|
|
1038
|
+
super.set("it->en", "alle", ["to"]);
|
|
1039
|
+
super.set("it->en", "Nessuno snapshot termina nella finestra scelta", ["No snapshot ends in the chosen window"]);
|
|
1040
|
+
super.set("it->en", "nodi nell'albero", ["nodes in the tree"]);
|
|
1041
|
+
super.set("it->en", "nella risposta", ["in the response"]);
|
|
1042
|
+
super.set("it->en", "eventi distinti", ["distinct events"]);
|
|
1043
|
+
super.set("it->en", "Caricamento…", ["Loading…"]);
|
|
1044
|
+
// Vista e ordinamenti
|
|
1045
|
+
super.set("it->en", "Albero", ["Tree"]);
|
|
1046
|
+
super.set("it->en", "Per nome", ["By name"]);
|
|
1047
|
+
super.set("it->en", "Ordina per", ["Sort by"]);
|
|
1048
|
+
super.set("it->en", "Tempo", ["Time"]);
|
|
1049
|
+
super.set("it->en", "Tempo proprio", ["Self time"]);
|
|
1050
|
+
super.set("it->en", "Chiamate", ["Calls"]);
|
|
1051
|
+
super.set("it->en", "Tempo medio", ["Average time"]);
|
|
1052
|
+
super.set("it->en", "Picco", ["Peak"]);
|
|
1053
|
+
super.set("it->en", "Δ Tempo", ["Δ Time"]);
|
|
1054
|
+
super.set("it->en", "Δ Tempo proprio", ["Δ Self time"]);
|
|
1055
|
+
super.set("it->en", "Δ Chiamate", ["Δ Calls"]);
|
|
1056
|
+
super.set("it->en", "Thread attivi", ["Active threads"]);
|
|
1057
|
+
super.set("it->en", "Tutti", ["All"]);
|
|
1058
|
+
super.set("it->en", "Nessuna", ["None"]);
|
|
1059
|
+
// Albero
|
|
1060
|
+
super.set("it->en", "Cerca nei nomi o per id", ["Search names or by id"]);
|
|
1061
|
+
super.set("it->en", "Per esempio MRD_METERREADINGS, ENQUEUE, ProfilesMgr", ["For example MRD_METERREADINGS, ENQUEUE, ProfilesMgr"]);
|
|
1062
|
+
super.set("it->en", "Figli per nodo", ["Children per node"]);
|
|
1063
|
+
super.set("it->en", "Soglia", ["Threshold"]);
|
|
1064
|
+
super.set("it->en", "Chiudi", ["Close"]);
|
|
1065
|
+
super.set("it->en", "Il nodo non c'è nelle istanze scelte: l'albero riparte da Idle.", ["The node is not in the chosen instances: the tree starts again from Idle."]);
|
|
1066
|
+
super.set("it->en", "nodi trovati, con i loro antenati.", ["nodes found, with their ancestors."]);
|
|
1067
|
+
super.set("it->en", "I nodi trovati sono più di 1.000: sono mostrati i primi per l'ordinamento scelto.", ["More than 1,000 nodes found: the first ones by the chosen sort order are shown."]);
|
|
1068
|
+
super.set("it->en", "Nessun nodo contiene", ["No node contains"]);
|
|
853
1069
|
super.set("it->en", "Nodo", ["Node"]);
|
|
854
|
-
super.set("it->en", "
|
|
855
|
-
super.set("it->en", "
|
|
856
|
-
super.set("it->en", "
|
|
857
|
-
super.set("it->en", "
|
|
858
|
-
super.set("it->en", "
|
|
859
|
-
super.set("it->en", "
|
|
860
|
-
super.set("it->en", "
|
|
861
|
-
super.set("it->en", "
|
|
862
|
-
super.set("it->en", "
|
|
863
|
-
super.set("it->en", "
|
|
864
|
-
super.set("it->en", "
|
|
865
|
-
super.set("it->en", "
|
|
866
|
-
super.set("it->en", "
|
|
1070
|
+
super.set("it->en", "% del padre", ["% of parent"]);
|
|
1071
|
+
super.set("it->en", "Δ Proprio", ["Δ Self"]);
|
|
1072
|
+
super.set("it->en", "Medio", ["Average"]);
|
|
1073
|
+
super.set("it->en", "Thread ora", ["Threads now"]);
|
|
1074
|
+
super.set("it->en", "del padre", ["of parent"]);
|
|
1075
|
+
super.set("it->en", "del nodo di primo livello", ["of the first-level node"]);
|
|
1076
|
+
super.set("it->en", "Esecuzione in corso da", ["Execution running for"]);
|
|
1077
|
+
super.set("it->en", "forse bloccata", ["possibly stuck"]);
|
|
1078
|
+
super.set("it->en", "Caricamento dei figli…", ["Loading children…"]);
|
|
1079
|
+
super.set("it->en", "figli non mostrati", ["children not shown"]);
|
|
1080
|
+
super.set("it->en", "chiamate", ["calls"]);
|
|
1081
|
+
super.set("it->en", "Mostra tutti", ["Show all"]);
|
|
1082
|
+
super.set("it->en", "Nessuno snapshot termina nella finestra scelta: allargala o spostala indietro.", ["No snapshot ends in the chosen window: widen it or move it back."]);
|
|
1083
|
+
super.set("it->en", "Il nodo non c'è nelle istanze scelte.", ["The node is not in the chosen instances."]);
|
|
1084
|
+
// Eventi per nome
|
|
1085
|
+
super.set("it->en", "Cerca nei nomi", ["Search names"]);
|
|
867
1086
|
super.set("it->en", "Tipo", ["Type"]);
|
|
868
|
-
super.set("it->en", "
|
|
1087
|
+
super.set("it->en", "Righe", ["Rows"]);
|
|
1088
|
+
super.set("it->en", "Anche gli eventi idle", ["Include idle events"]);
|
|
1089
|
+
super.set("it->en", "Evento", ["Event"]);
|
|
1090
|
+
super.set("it->en", "Percorsi", ["Paths"]);
|
|
1091
|
+
super.set("it->en", "% proprio", ["% self"]);
|
|
1092
|
+
super.set("it->en", "Picco dall'avvio", ["Peak since start"]);
|
|
1093
|
+
super.set("it->en", "Thread", ["Threads"]);
|
|
1094
|
+
super.set("it->en", "Nodi con più tempo", ["Nodes with the most time"]);
|
|
1095
|
+
super.set("it->en", "Dettaglio", ["Details"]);
|
|
1096
|
+
super.set("it->en", "Apri nell'albero", ["Open in tree"]);
|
|
1097
|
+
super.set("it->en", "altri", ["more"]);
|
|
1098
|
+
super.set("it->en", "Nessun evento con questi filtri.", ["No events match these filters."]);
|
|
1099
|
+
super.set("it->en", "mostrati i primi", ["showing the first"]);
|
|
1100
|
+
super.set("it->en", "La percentuale è sul tempo proprio di tutti gli eventi non idle.", ["The percentage is of the self time of all non-idle events."]);
|
|
1101
|
+
// Dettaglio
|
|
1102
|
+
super.set("it->en", "Seleziona un nodo per vederne il dettaglio.", ["Select a node to see its details."]);
|
|
1103
|
+
super.set("it->en", "Il nodo non c'è nelle istanze o nella finestra scelte.", ["The node is not in the chosen instances or window."]);
|
|
1104
|
+
super.set("it->en", "Copia", ["Copy"]);
|
|
1105
|
+
super.set("it->en", "Copiato", ["Copied"]);
|
|
1106
|
+
super.set("it->en", "Percorso", ["Path"]);
|
|
1107
|
+
super.set("it->en", "Nella finestra", ["In window"]);
|
|
1108
|
+
super.set("it->en", "Esecuzioni", ["Executions"]);
|
|
1109
|
+
super.set("it->en", "Tempo totale", ["Total time"]);
|
|
1110
|
+
super.set("it->en", "Minimo", ["Minimum"]);
|
|
1111
|
+
super.set("it->en", "Adesso", ["Now"]);
|
|
1112
|
+
super.set("it->en", "Thread dentro", ["Threads inside"]);
|
|
1113
|
+
super.set("it->en", "Esecuzione più lunga", ["Longest execution"]);
|
|
1114
|
+
super.set("it->en", "Ultima uscita", ["Last exit"]);
|
|
1115
|
+
super.set("it->en", "Tempo proprio del sottoalbero per tipo", ["Subtree self time by type"]);
|
|
1116
|
+
super.set("it->en", "Sessioni attive sul nodo e sui discendenti", ["Active sessions on node and descendants"]);
|
|
1117
|
+
super.set("it->en", "Media", ["Average"]);
|
|
1118
|
+
super.set("it->en", "picco", ["peak"]);
|
|
1119
|
+
super.set("it->en", "sessioni", ["sessions"]);
|
|
1120
|
+
super.set("it->en", "punti dell'API", ["API points"]);
|
|
1121
|
+
super.set("it->en", "uno al secondo", ["one per second"]);
|
|
1122
|
+
super.set("it->en", "uno ogni", ["one every"]);
|
|
1123
|
+
super.set("it->en", "raggruppati a", ["grouped by"]);
|
|
1124
|
+
super.set("it->en", "Nessun campione in memoria per questa finestra: il grafico copre solo le ultime due ore circa.", ["No in-memory samples for this window: the chart only covers about the last two hours."]);
|
|
1125
|
+
super.set("it->en", "Per istanza", ["By instance"]);
|
|
1126
|
+
super.set("it->en", "Istanza", ["Instance"]);
|
|
1127
|
+
super.set("it->en", "Proprio", ["Self"]);
|
|
1128
|
+
super.set("it->en", "figli diretti", ["direct children"]);
|
|
1129
|
+
super.set("it->en", "discendenti", ["descendants"]);
|
|
1130
|
+
super.set("it->en", "Apri come radice", ["Open as root"]);
|
|
869
1131
|
}
|
|
870
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type:
|
|
871
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type:
|
|
1132
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerV2Loc, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1133
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerV2Loc }); }
|
|
872
1134
|
}
|
|
873
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type:
|
|
1135
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerV2Loc, decorators: [{
|
|
874
1136
|
type: Injectable
|
|
875
1137
|
}], ctorParameters: () => [{ type: i0.Injector }] });
|
|
876
1138
|
|
|
877
1139
|
// Angular
|
|
878
|
-
|
|
879
|
-
|
|
1140
|
+
const STORAGE_KEY$3 = "HotSearchProfilerV2";
|
|
1141
|
+
const INSTANCE_THIS = "@this";
|
|
1142
|
+
const INSTANCE_ALL$1 = "@all";
|
|
1143
|
+
// All'apertura la radice con due livelli (R6), poi i figli si chiedono un livello alla volta
|
|
1144
|
+
const TREE_DEPTH = 2;
|
|
1145
|
+
const SEARCH_MIN_CHARS = 2;
|
|
1146
|
+
const SEARCH_DELAY_MS = 400;
|
|
1147
|
+
const DATES_DELAY_MS$1 = 600;
|
|
1148
|
+
// Muovendosi con la tastiera il dettaglio si chiede dopo una breve pausa, non a ogni tasto
|
|
1149
|
+
const DETAIL_DELAY_MS = 180;
|
|
1150
|
+
// Esecuzione in corso da più di così: forse bloccata (R15)
|
|
1151
|
+
const LONG_RUNNING_SECONDS = 60;
|
|
1152
|
+
const CHART_POINTS = 160;
|
|
1153
|
+
// Ogni tipo di evento ha un colore fisso, gli stessi della Top Activity v2. Wait e sleep di classe Idle usano quello di Idle
|
|
1154
|
+
const TYPE_COLORS = {
|
|
1155
|
+
Idle: "#bdbdbd", Cpu: "#43a047", Database: "#1e88e5", Wait: "#fdd835", Script: "#8e24aa", Network: "#8d6e63", IO: "#00acc1", Sleep: "#90a4ae", Throttle: "#fb8c00"
|
|
1156
|
+
};
|
|
1157
|
+
const TYPE_LABELS = {
|
|
1158
|
+
Idle: "Idle", Cpu: "CPU", Database: "DB", Wait: "Wait", Script: "Script", Network: "Net", IO: "I/O", Sleep: "Sleep", Throttle: "Throttle"
|
|
1159
|
+
};
|
|
1160
|
+
const EVENT_TYPES = ["Cpu", "Database", "Wait", "Script", "Network", "IO", "Sleep", "Throttle"];
|
|
1161
|
+
// Passando ai valori dall'ultimo snapshot l'ordinamento passa alla chiave corrispondente, e viceversa (R4)
|
|
1162
|
+
const TO_SNAP = {
|
|
1163
|
+
[ProfilerOrderBy.Time]: ProfilerOrderBy.SnapDeltaTime,
|
|
1164
|
+
[ProfilerOrderBy.TimeLessChildren]: ProfilerOrderBy.SnapDeltaTimeLessChildren,
|
|
1165
|
+
[ProfilerOrderBy.Calls]: ProfilerOrderBy.SnapDeltaCalls
|
|
1166
|
+
};
|
|
1167
|
+
const TO_BASE = {
|
|
1168
|
+
[ProfilerOrderBy.SnapDeltaTime]: ProfilerOrderBy.Time,
|
|
1169
|
+
[ProfilerOrderBy.SnapDeltaTimeLessChildren]: ProfilerOrderBy.TimeLessChildren,
|
|
1170
|
+
[ProfilerOrderBy.SnapDeltaCalls]: ProfilerOrderBy.Calls
|
|
1171
|
+
};
|
|
1172
|
+
class ProfilerV2Row {
|
|
1173
|
+
}
|
|
1174
|
+
class ProfilerV2TypePart {
|
|
1175
|
+
}
|
|
1176
|
+
class ProfilerV2Chart {
|
|
1177
|
+
}
|
|
1178
|
+
// Preferenze ricordate fra una visita e l'altra (sorgente, istanze, finestra e radice ripartono dai default)
|
|
1179
|
+
class ProfilerV2Settings {
|
|
1180
|
+
}
|
|
1181
|
+
class AdmProfilerV2Component {
|
|
1182
|
+
constructor(dateExts, lc, localStorage, intercom, route, router, location, adms, mod, workloadV2) {
|
|
880
1183
|
this.dateExts = dateExts;
|
|
881
1184
|
this.lc = lc;
|
|
882
1185
|
this.localStorage = localStorage;
|
|
883
|
-
this.location = location;
|
|
884
1186
|
this.intercom = intercom;
|
|
885
1187
|
this.route = route;
|
|
1188
|
+
this.router = router;
|
|
1189
|
+
this.location = location;
|
|
886
1190
|
this.adms = adms;
|
|
887
1191
|
this.mod = mod;
|
|
888
|
-
this.
|
|
889
|
-
this.
|
|
890
|
-
|
|
891
|
-
this.
|
|
892
|
-
|
|
893
|
-
this.
|
|
894
|
-
this.
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
this.
|
|
1192
|
+
this.workloadV2 = workloadV2;
|
|
1193
|
+
this.EventTypes = EVENT_TYPES;
|
|
1194
|
+
// Barra dei comandi
|
|
1195
|
+
this.live = true;
|
|
1196
|
+
// Di default tutto il cluster, come nella Top Activity v2
|
|
1197
|
+
this.instance = INSTANCE_ALL$1;
|
|
1198
|
+
this.filterfrom = "";
|
|
1199
|
+
this.filterto = "";
|
|
1200
|
+
// Valori dall'ultimo snapshot invece che dall'avvio (solo live)
|
|
1201
|
+
this.snapValues = false;
|
|
1202
|
+
this.view = "tree";
|
|
1203
|
+
// Albero
|
|
1204
|
+
this.orderBy = String(ProfilerOrderBy.Time);
|
|
1205
|
+
// "0" = tutti, nessuna soglia
|
|
1206
|
+
this.maxChildren = "25";
|
|
1207
|
+
this.minPercentage = "0";
|
|
1208
|
+
this.searchText = "";
|
|
1209
|
+
this.activeSearch = "";
|
|
1210
|
+
this.rows = [];
|
|
1211
|
+
this.treeLoading = false;
|
|
1212
|
+
this.matchCount = 0;
|
|
1213
|
+
// Eventi per nome
|
|
1214
|
+
this.flatSearch = "";
|
|
1215
|
+
// "*" = tutti i tipi (con un id vuoto la select mostrerebbe il segnaposto invece di "Tutti")
|
|
1216
|
+
this.flatType = "*";
|
|
1217
|
+
this.flatOrderBy = String(ProfilerOrderBy.TimeLessChildren);
|
|
1218
|
+
this.flatTop = "100";
|
|
1219
|
+
this.flatIncludeIdle = false;
|
|
1220
|
+
this.flatLoading = false;
|
|
1221
|
+
this.openFlat = new Set();
|
|
1222
|
+
this.detailLoading = false;
|
|
1223
|
+
this.detailMissing = false;
|
|
1224
|
+
this.typeParts = [];
|
|
1225
|
+
this.copied = false;
|
|
1226
|
+
// Sorgenti di select e pulsanti
|
|
1227
|
+
this.Instances = [];
|
|
1228
|
+
this.TreeOrders = [];
|
|
1229
|
+
this.FlatOrders = [];
|
|
1230
|
+
this.TreeOrderOptions = [];
|
|
1231
|
+
this.FlatOrderOptions = [];
|
|
1232
|
+
this.ChildrenOptions = [];
|
|
1233
|
+
this.ThresholdOptions = [];
|
|
1234
|
+
this.FlatTypes = [];
|
|
1235
|
+
this.FlatRows = [];
|
|
1236
|
+
this.instanceIds = [];
|
|
1237
|
+
this.rootId = null;
|
|
1238
|
+
this.nodes = new Map();
|
|
1239
|
+
this.parents = new Map();
|
|
1240
|
+
this.expanded = new Set();
|
|
1241
|
+
// Nodi di cui children è completo per i parametri correnti, anche se vuoto (per esempio tutti sotto la soglia)
|
|
1242
|
+
this.loaded = new Set();
|
|
1243
|
+
this.loadingChildren = new Set();
|
|
1244
|
+
// Una risposta arrivata dopo un ricaricamento successivo si scarta
|
|
1245
|
+
this.treeGeneration = 0;
|
|
1246
|
+
this.flatGeneration = 0;
|
|
1247
|
+
this.detailGeneration = 0;
|
|
905
1248
|
this.be = route.snapshot.params["be"] || "";
|
|
906
1249
|
this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
|
|
907
1250
|
}
|
|
908
|
-
ngOnInit() {
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1251
|
+
async ngOnInit() {
|
|
1252
|
+
this.buildSources();
|
|
1253
|
+
let saved = this.route.snapshot.queryParams["ignoreStorage"] ? null : await firstValueFrom(this.localStorage.getItem(STORAGE_KEY$3), { defaultValue: null });
|
|
1254
|
+
if (saved)
|
|
1255
|
+
this.applySettings(saved);
|
|
1256
|
+
// Gli id sono stabili: la radice può stare nell'indirizzo della pagina
|
|
1257
|
+
this.rootId = this.route.snapshot.queryParams["root"] || null;
|
|
1258
|
+
this.loadInstances();
|
|
1259
|
+
this.loadTree(false);
|
|
916
1260
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1261
|
+
ngOnDestroy() {
|
|
1262
|
+
clearTimeout(this.searchTimer);
|
|
1263
|
+
clearTimeout(this.flatSearchTimer);
|
|
1264
|
+
clearTimeout(this.datesTimer);
|
|
1265
|
+
clearTimeout(this.detailTimer);
|
|
1266
|
+
}
|
|
1267
|
+
// #region Barra dei comandi
|
|
1268
|
+
setLive(live) {
|
|
1269
|
+
if (this.live == live)
|
|
1270
|
+
return;
|
|
1271
|
+
this.live = live;
|
|
1272
|
+
if (!live) {
|
|
1273
|
+
// Sullo storico non ci sono i valori dall'ultimo snapshot né i thread attivi (R30)
|
|
1274
|
+
this.snapValues = false;
|
|
1275
|
+
this.orderBy = String(+this.orderBy == ProfilerOrderBy.ActiveThreads ? ProfilerOrderBy.Time : TO_BASE[+this.orderBy] ?? +this.orderBy);
|
|
1276
|
+
this.flatOrderBy = String(+this.flatOrderBy == ProfilerOrderBy.ActiveThreads ? ProfilerOrderBy.TimeLessChildren : TO_BASE[+this.flatOrderBy] ?? +this.flatOrderBy);
|
|
1277
|
+
// Default: le quattro ore che finiscono all'ultimo snapshot
|
|
1278
|
+
if (!this.filterfrom || !this.filterto) {
|
|
1279
|
+
let lastSnapshot = this.parseUtc((this.view == "tree" ? this.tree?.snapFrom : this.flat?.snapFrom) ?? null) ?? this.floorHour(new Date());
|
|
1280
|
+
this.filterto = this.fmt(lastSnapshot);
|
|
1281
|
+
this.filterfrom = this.fmt(new Date(lastSnapshot.getTime() - 4 * 3600 * 1000));
|
|
932
1282
|
}
|
|
933
1283
|
}
|
|
1284
|
+
this.updateOrderOptions();
|
|
1285
|
+
this.reloadAll();
|
|
1286
|
+
}
|
|
1287
|
+
setInstance(id) {
|
|
1288
|
+
this.instance = id;
|
|
1289
|
+
this.reloadAll();
|
|
1290
|
+
}
|
|
1291
|
+
datesChanged() {
|
|
1292
|
+
clearTimeout(this.datesTimer);
|
|
1293
|
+
this.datesTimer = setTimeout(() => {
|
|
1294
|
+
if (this.toDate(this.filterfrom) && this.toDate(this.filterto))
|
|
1295
|
+
this.reloadAll();
|
|
1296
|
+
}, DATES_DELAY_MS$1);
|
|
1297
|
+
}
|
|
1298
|
+
// Cambia colonne e intestazioni; richiama il server solo se cambia anche l'ordinamento
|
|
1299
|
+
setSnapValues(on) {
|
|
1300
|
+
if (this.snapValues == on)
|
|
1301
|
+
return;
|
|
1302
|
+
this.snapValues = on;
|
|
1303
|
+
let map = on ? TO_SNAP : TO_BASE;
|
|
1304
|
+
let treeOrder = String(map[+this.orderBy] ?? +this.orderBy);
|
|
1305
|
+
// La vista per nome non propone Δ Tempo: lì Tempo resta Tempo
|
|
1306
|
+
let flatMapped = map[+this.flatOrderBy];
|
|
1307
|
+
let flatOrder = String(flatMapped != null && flatMapped != ProfilerOrderBy.SnapDeltaTime ? flatMapped : +this.flatOrderBy);
|
|
1308
|
+
let reload = this.view == "tree" ? treeOrder != this.orderBy : flatOrder != this.flatOrderBy;
|
|
1309
|
+
this.orderBy = treeOrder;
|
|
1310
|
+
this.flatOrderBy = flatOrder;
|
|
1311
|
+
this.saveSettings();
|
|
1312
|
+
if (reload)
|
|
1313
|
+
this.reloadView();
|
|
1314
|
+
}
|
|
1315
|
+
setView(view) {
|
|
1316
|
+
if (this.view == view)
|
|
1317
|
+
return;
|
|
1318
|
+
this.view = view;
|
|
1319
|
+
this.reloadView();
|
|
1320
|
+
}
|
|
1321
|
+
// Aggiorna: la vista corrente e il dettaglio aperto (niente aggiornamento automatico, R5)
|
|
1322
|
+
reloadAll() {
|
|
1323
|
+
this.reloadView();
|
|
1324
|
+
if (this.selectedId)
|
|
1325
|
+
this.loadDetail(this.selectedId);
|
|
1326
|
+
}
|
|
1327
|
+
reloadView() {
|
|
1328
|
+
if (this.view == "tree")
|
|
1329
|
+
this.loadTree(true);
|
|
1330
|
+
else
|
|
1331
|
+
this.loadFlat();
|
|
934
1332
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
if (!ignoreStorage && item) {
|
|
941
|
-
this.init(item);
|
|
1333
|
+
// Con DirectorFeature.WorkloadReportV2 il report non è più un file da scaricare: ha la sua pagina
|
|
1334
|
+
downloadWorkloadReport() {
|
|
1335
|
+
if (this.workloadV2) {
|
|
1336
|
+
this.router.navigate([`/pages/${this.mod}/workload`, ...(this.be ? [this.be] : [])]);
|
|
1337
|
+
return;
|
|
942
1338
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1339
|
+
let from = this.live ? "" : this.filterfrom;
|
|
1340
|
+
let to = this.live ? "" : this.filterto;
|
|
1341
|
+
this.adms.downloadWorkloadReport(this.be, from, to, this.instance != INSTANCE_THIS);
|
|
1342
|
+
}
|
|
1343
|
+
get instanceLabel() {
|
|
1344
|
+
return this.Instances.find(t => t.id == this.instance)?.description ?? "";
|
|
1345
|
+
}
|
|
1346
|
+
get loading() {
|
|
1347
|
+
return this.view == "tree" ? this.treeLoading : this.flatLoading;
|
|
1348
|
+
}
|
|
1349
|
+
// Vista corrente, per la riga della finestra
|
|
1350
|
+
get current() {
|
|
1351
|
+
return this.view == "tree" ? this.tree : this.flat;
|
|
1352
|
+
}
|
|
1353
|
+
baseRequest(req) {
|
|
1354
|
+
req.realTime = this.live;
|
|
1355
|
+
// Dietro un bilanciatore "questa istanza" cambia da una chiamata all'altra: si manda sempre l'istanza esplicita o global (R3)
|
|
1356
|
+
if (this.instance == INSTANCE_ALL$1)
|
|
1357
|
+
req.global = true;
|
|
1358
|
+
else if (this.instance != INSTANCE_THIS)
|
|
1359
|
+
req.instanceId = this.instance;
|
|
1360
|
+
else if (this.instanceId)
|
|
1361
|
+
req.instanceId = this.instanceId;
|
|
1362
|
+
if (!this.live) {
|
|
1363
|
+
req.snapFrom = this.toDate(this.filterfrom)?.toISOString();
|
|
1364
|
+
req.snapTo = this.toDate(this.filterto)?.toISOString();
|
|
948
1365
|
}
|
|
1366
|
+
return req;
|
|
949
1367
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1368
|
+
// #endregion
|
|
1369
|
+
// #region Albero
|
|
1370
|
+
setOrderBy(id) {
|
|
1371
|
+
this.orderBy = id;
|
|
1372
|
+
this.saveSettings();
|
|
1373
|
+
this.loadTree(true);
|
|
1374
|
+
}
|
|
1375
|
+
setMaxChildren(id) {
|
|
1376
|
+
this.maxChildren = id;
|
|
1377
|
+
this.saveSettings();
|
|
1378
|
+
this.loadTree(true);
|
|
1379
|
+
}
|
|
1380
|
+
setMinPercentage(id) {
|
|
1381
|
+
this.minPercentage = id;
|
|
1382
|
+
this.saveSettings();
|
|
1383
|
+
this.loadTree(true);
|
|
1384
|
+
}
|
|
1385
|
+
searchChanged() {
|
|
1386
|
+
clearTimeout(this.searchTimer);
|
|
1387
|
+
this.searchTimer = setTimeout(() => {
|
|
1388
|
+
let text = (this.searchText ?? "").trim();
|
|
1389
|
+
let next = text.length >= SEARCH_MIN_CHARS ? text : "";
|
|
1390
|
+
if (next == this.activeSearch)
|
|
1391
|
+
return;
|
|
1392
|
+
// Svuotando la ricerca si torna all'albero di prima, con gli stessi nodi aperti
|
|
1393
|
+
if (!this.activeSearch)
|
|
1394
|
+
this.expandedBeforeSearch = new Set(this.expanded);
|
|
1395
|
+
else if (!next)
|
|
1396
|
+
this.expanded = this.expandedBeforeSearch ?? new Set();
|
|
1397
|
+
this.activeSearch = next;
|
|
1398
|
+
this.loadTree(!next);
|
|
1399
|
+
}, SEARCH_DELAY_MS);
|
|
1400
|
+
}
|
|
1401
|
+
clearSearch() {
|
|
1402
|
+
this.searchText = "";
|
|
1403
|
+
this.searchChanged();
|
|
1404
|
+
}
|
|
1405
|
+
// Nodo come radice (R13); Idle si chiede senza rootNodeId
|
|
1406
|
+
setRoot(id) {
|
|
1407
|
+
this.rootId = id && id != this.rootPathIdle ? id : null;
|
|
1408
|
+
this.searchText = "";
|
|
1409
|
+
this.activeSearch = "";
|
|
1410
|
+
this.expanded.clear();
|
|
1411
|
+
this.treeNotice = null;
|
|
1412
|
+
this.view = "tree";
|
|
1413
|
+
this.updateUrl();
|
|
1414
|
+
this.loadTree(false);
|
|
1415
|
+
}
|
|
1416
|
+
get rootPathIdle() {
|
|
1417
|
+
let root = this.tree?.idleNode;
|
|
1418
|
+
return root?.level == 0 ? root.nodeId : this.tree?.rootPath?.[0]?.nodeId;
|
|
1419
|
+
}
|
|
1420
|
+
async loadTree(keepExpanded) {
|
|
1421
|
+
let generation = ++this.treeGeneration;
|
|
1422
|
+
let searching = !!this.activeSearch;
|
|
1423
|
+
let previous = keepExpanded ? [...this.expanded] : [];
|
|
1424
|
+
let req = this.baseRequest(new ProfilerV2TreeRequest());
|
|
1425
|
+
req.orderBy = +this.orderBy;
|
|
1426
|
+
if (this.rootId)
|
|
1427
|
+
req.rootNodeId = this.rootId;
|
|
1428
|
+
if (searching)
|
|
1429
|
+
req.search = this.activeSearch;
|
|
955
1430
|
else {
|
|
956
|
-
|
|
957
|
-
this.
|
|
1431
|
+
req.maxDepth = TREE_DEPTH;
|
|
1432
|
+
if (+this.maxChildren > 0)
|
|
1433
|
+
req.maxChildren = +this.maxChildren;
|
|
1434
|
+
if (+this.minPercentage > 0)
|
|
1435
|
+
req.minPercentage = +this.minPercentage;
|
|
958
1436
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1437
|
+
this.treeLoading = true;
|
|
1438
|
+
this.treeError = null;
|
|
1439
|
+
try {
|
|
1440
|
+
let res = await this.adms.getProfilerTreeV2(this.be, req);
|
|
1441
|
+
if (generation != this.treeGeneration)
|
|
1442
|
+
return;
|
|
1443
|
+
// Radice non trovata (R23): lo si dice e si torna a Idle
|
|
1444
|
+
if (res && !res.idleNode && res.snapFrom && this.rootId) {
|
|
1445
|
+
this.rootId = null;
|
|
1446
|
+
this.updateUrl();
|
|
1447
|
+
this.treeNotice = this.lc.loc("Il nodo non c'è nelle istanze scelte: l'albero riparte da Idle.");
|
|
1448
|
+
this.loadTree(false);
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
this.tree = res;
|
|
1452
|
+
this.nodes.clear();
|
|
1453
|
+
this.parents.clear();
|
|
1454
|
+
this.loaded.clear();
|
|
1455
|
+
this.loadingChildren.clear();
|
|
1456
|
+
// I nodi che erano aperti restano aperti: quelli che non sono ancora arrivati si riaprono quando arrivano i figli del padre
|
|
1457
|
+
this.expanded = new Set(searching ? [] : previous);
|
|
1458
|
+
let root = res?.idleNode;
|
|
1459
|
+
if (root) {
|
|
1460
|
+
this.ingest(root, null, searching ? null : TREE_DEPTH, searching);
|
|
1461
|
+
this.expanded.add(root.nodeId);
|
|
1462
|
+
}
|
|
1463
|
+
if (searching) {
|
|
1464
|
+
// L'albero della ricerca contiene solo i percorsi dei nodi trovati e si mostra tutto aperto
|
|
1465
|
+
this.nodes.forEach(t => { if (t.children?.length)
|
|
1466
|
+
this.expanded.add(t.nodeId); });
|
|
1467
|
+
this.matchCount = [...this.nodes.values()].filter(t => t.searchMatch).length;
|
|
1468
|
+
}
|
|
1469
|
+
this.buildRows();
|
|
1470
|
+
this.nodes.forEach(t => this.reopen(t, generation));
|
|
1471
|
+
}
|
|
1472
|
+
catch (err) {
|
|
1473
|
+
if (generation == this.treeGeneration)
|
|
1474
|
+
this.treeError = this.errorMessage(err);
|
|
1475
|
+
}
|
|
1476
|
+
finally {
|
|
1477
|
+
if (generation == this.treeGeneration)
|
|
1478
|
+
this.treeLoading = false;
|
|
983
1479
|
}
|
|
984
|
-
// DirectorInfos anche se per qualche motivo crasha di brutto è comunque non obbligatorio, evito di non caricare la pagina solo perché lui scoppia
|
|
985
|
-
let dirinfos = await this.adms.getInfos(this.be);
|
|
986
|
-
this.searchView.searchinprogress = false;
|
|
987
|
-
this.reviewBindings(update, res, dirinfos);
|
|
988
|
-
}
|
|
989
|
-
loadNodeDetails(nodeId) {
|
|
990
|
-
this.NodeDetails = null;
|
|
991
|
-
requestAnimationFrame(async () => {
|
|
992
|
-
this.NodeDetails = await this.adms.getNodeStats(this.be, nodeId, this.searchView.filterglobal);
|
|
993
|
-
});
|
|
994
1480
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1481
|
+
ingest(node, parent, depthLeft, partial) {
|
|
1482
|
+
this.nodes.set(node.nodeId, node);
|
|
1483
|
+
if (parent)
|
|
1484
|
+
this.parents.set(node.nodeId, parent);
|
|
1485
|
+
// Oltre il limite di profondità, o nella ricerca, i figli non sono tutti: si chiedono quando il nodo si apre
|
|
1486
|
+
if (!partial && (depthLeft == null || depthLeft > 0))
|
|
1487
|
+
this.loaded.add(node.nodeId);
|
|
1488
|
+
for (let child of node.children ?? [])
|
|
1489
|
+
this.ingest(child, node, depthLeft == null ? null : depthLeft - 1, partial);
|
|
1490
|
+
}
|
|
1491
|
+
// Un nodo aperto senza figli caricati (dopo un ricaricamento o "Mostra tutti" del padre) li chiede
|
|
1492
|
+
reopen(node, generation) {
|
|
1493
|
+
if (this.expanded.has(node.nodeId) && this.needsChildren(node) && !this.loadingChildren.has(node.nodeId))
|
|
1494
|
+
this.loadChildren(node, false, generation);
|
|
1495
|
+
}
|
|
1496
|
+
hasChildren(node) {
|
|
1497
|
+
return node.numChildren > 0;
|
|
1498
|
+
}
|
|
1499
|
+
isExpanded(node) {
|
|
1500
|
+
return this.expanded.has(node.nodeId);
|
|
1501
|
+
}
|
|
1502
|
+
needsChildren(node) {
|
|
1503
|
+
return node.numChildren > 0 && !node.children?.length && !this.loaded.has(node.nodeId);
|
|
1504
|
+
}
|
|
1505
|
+
toggle(node, event) {
|
|
1506
|
+
event?.stopPropagation();
|
|
1507
|
+
if (!this.hasChildren(node))
|
|
1508
|
+
return;
|
|
1509
|
+
if (this.expanded.has(node.nodeId)) {
|
|
1510
|
+
this.expanded.delete(node.nodeId);
|
|
1511
|
+
this.buildRows();
|
|
1003
1512
|
return;
|
|
1004
1513
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1514
|
+
this.expanded.add(node.nodeId);
|
|
1515
|
+
if (this.needsChildren(node))
|
|
1516
|
+
this.loadChildren(node, false);
|
|
1517
|
+
else
|
|
1518
|
+
this.buildRows();
|
|
1519
|
+
}
|
|
1520
|
+
// R9: la stessa chiamata dei figli senza maxChildren e minPercentage
|
|
1521
|
+
showAll(node, event) {
|
|
1522
|
+
event.stopPropagation();
|
|
1523
|
+
this.loadChildren(node, true);
|
|
1524
|
+
}
|
|
1525
|
+
// R8: i figli di un nodo, che sostituiscono children e omitted* del nodo
|
|
1526
|
+
async loadChildren(node, all, generation = this.treeGeneration) {
|
|
1527
|
+
let req = this.baseRequest(new ProfilerV2TreeRequest());
|
|
1528
|
+
req.rootNodeId = node.nodeId;
|
|
1529
|
+
req.maxDepth = 1;
|
|
1530
|
+
req.orderBy = +this.orderBy;
|
|
1531
|
+
if (!all && +this.maxChildren > 0)
|
|
1532
|
+
req.maxChildren = +this.maxChildren;
|
|
1533
|
+
if (!all && +this.minPercentage > 0)
|
|
1534
|
+
req.minPercentage = +this.minPercentage;
|
|
1535
|
+
this.loadingChildren.add(node.nodeId);
|
|
1536
|
+
this.buildRows();
|
|
1537
|
+
try {
|
|
1538
|
+
let res = await this.adms.getProfilerTreeV2(this.be, req);
|
|
1539
|
+
if (generation != this.treeGeneration)
|
|
1540
|
+
return;
|
|
1541
|
+
let fresh = res?.idleNode;
|
|
1542
|
+
node.children = fresh?.children ?? [];
|
|
1543
|
+
node.numChildren = fresh?.numChildren ?? node.children.length;
|
|
1544
|
+
node.omittedChildren = fresh?.omittedChildren ?? 0;
|
|
1545
|
+
node.omittedCalls = fresh?.omittedCalls ?? 0;
|
|
1546
|
+
node.omittedTime = fresh?.omittedTime ?? 0;
|
|
1547
|
+
node.omittedSnapDeltaTime = fresh?.omittedSnapDeltaTime ?? 0;
|
|
1548
|
+
for (let child of node.children)
|
|
1549
|
+
this.ingest(child, node, 0, false);
|
|
1550
|
+
this.loaded.add(node.nodeId);
|
|
1551
|
+
node.children.forEach(t => this.reopen(t, generation));
|
|
1013
1552
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
this.seriesPropertiesMapping[props[i]].values.push(new DateValue(theD, itemref[props[i]]));
|
|
1553
|
+
catch (err) {
|
|
1554
|
+
if (generation == this.treeGeneration) {
|
|
1555
|
+
this.expanded.delete(node.nodeId);
|
|
1556
|
+
this.treeError = this.errorMessage(err);
|
|
1557
|
+
}
|
|
1020
1558
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
this.
|
|
1025
|
-
this.LastDataPoint = series.map(t => dayjs.max(t.values.map(v => this.dateExts.getDateConvertion(v.d))))[0];
|
|
1559
|
+
finally {
|
|
1560
|
+
if (generation == this.treeGeneration) {
|
|
1561
|
+
this.loadingChildren.delete(node.nodeId);
|
|
1562
|
+
this.buildRows();
|
|
1026
1563
|
}
|
|
1027
1564
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1565
|
+
}
|
|
1566
|
+
buildRows() {
|
|
1567
|
+
let rows = [];
|
|
1568
|
+
if (this.tree?.idleNode)
|
|
1569
|
+
this.addRows(rows, this.tree.idleNode, 0);
|
|
1570
|
+
this.rows = rows;
|
|
1571
|
+
}
|
|
1572
|
+
addRows(rows, node, depth) {
|
|
1573
|
+
rows.push({ kind: "node", node: node, depth: depth, key: node.nodeId });
|
|
1574
|
+
if (!this.expanded.has(node.nodeId))
|
|
1575
|
+
return;
|
|
1576
|
+
let loading = this.loadingChildren.has(node.nodeId);
|
|
1577
|
+
if (loading)
|
|
1578
|
+
rows.push({ kind: "loading", node: node, depth: depth + 1, key: node.nodeId + "#loading" });
|
|
1579
|
+
for (let child of node.children ?? [])
|
|
1580
|
+
this.addRows(rows, child, depth + 1);
|
|
1581
|
+
if (!loading && node.omittedChildren > 0 && !this.needsChildren(node))
|
|
1582
|
+
rows.push({ kind: "omitted", node: node, depth: depth + 1, key: node.nodeId + "#omitted" });
|
|
1583
|
+
}
|
|
1584
|
+
// Clic: selezione e dettaglio. Doppio clic: apre o chiude
|
|
1585
|
+
rowClicked(node) {
|
|
1586
|
+
this.select(node.nodeId);
|
|
1587
|
+
}
|
|
1588
|
+
// Su e giù cambiano nodo, destra apre (o scende al primo figlio), sinistra chiude (o sale al padre)
|
|
1589
|
+
treeKeydown(event) {
|
|
1590
|
+
let nodeRows = this.rows.filter(t => t.kind == "node");
|
|
1591
|
+
let index = nodeRows.findIndex(t => t.node.nodeId == this.selectedId);
|
|
1592
|
+
let current = index >= 0 ? nodeRows[index].node : null;
|
|
1593
|
+
let target = null;
|
|
1594
|
+
switch (event.key) {
|
|
1595
|
+
case "ArrowDown":
|
|
1596
|
+
target = nodeRows[Math.min(nodeRows.length - 1, index + 1)]?.node;
|
|
1597
|
+
break;
|
|
1598
|
+
case "ArrowUp":
|
|
1599
|
+
target = nodeRows[Math.max(0, index - 1)]?.node;
|
|
1600
|
+
break;
|
|
1601
|
+
case "ArrowRight":
|
|
1602
|
+
if (current && this.hasChildren(current) && !this.isExpanded(current))
|
|
1603
|
+
this.toggle(current);
|
|
1604
|
+
else if (current && this.isExpanded(current))
|
|
1605
|
+
target = current.children?.[0];
|
|
1606
|
+
break;
|
|
1607
|
+
case "ArrowLeft":
|
|
1608
|
+
if (current && this.isExpanded(current) && this.hasChildren(current))
|
|
1609
|
+
this.toggle(current);
|
|
1610
|
+
else if (current)
|
|
1611
|
+
target = this.parents.get(current.nodeId);
|
|
1612
|
+
break;
|
|
1613
|
+
default:
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
event.preventDefault();
|
|
1617
|
+
if (target) {
|
|
1618
|
+
this.select(target.nodeId, true);
|
|
1619
|
+
setTimeout(() => document.getElementById("pv2-row-" + target.nodeId)?.scrollIntoView({ block: "nearest" }));
|
|
1031
1620
|
}
|
|
1032
1621
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1622
|
+
isIdle(type, waitClass) {
|
|
1623
|
+
return type == "Idle" || ((type == "Wait" || type == "Sleep") && waitClass == "Idle");
|
|
1624
|
+
}
|
|
1625
|
+
// Esecuzione in corso da più di un minuto su un nodo non idle (solo live)
|
|
1626
|
+
isLongRunning(type, waitClass, activeMaxTime) {
|
|
1627
|
+
return this.live && !this.isIdle(type, waitClass) && (activeMaxTime ?? 0) >= LONG_RUNNING_SECONDS;
|
|
1628
|
+
}
|
|
1629
|
+
// Con la ricerca la parte del nome che corrisponde è evidenziata
|
|
1630
|
+
nameParts(name, search) {
|
|
1631
|
+
if (!search || !name)
|
|
1632
|
+
return [{ text: name ?? "", hit: false }];
|
|
1633
|
+
let parts = [];
|
|
1634
|
+
let lower = name.toLowerCase();
|
|
1635
|
+
let needle = search.toLowerCase();
|
|
1636
|
+
let from = 0;
|
|
1637
|
+
let at = lower.indexOf(needle);
|
|
1638
|
+
while (at >= 0) {
|
|
1639
|
+
if (at > from)
|
|
1640
|
+
parts.push({ text: name.substring(from, at), hit: false });
|
|
1641
|
+
parts.push({ text: name.substring(at, at + needle.length), hit: true });
|
|
1642
|
+
from = at + needle.length;
|
|
1643
|
+
at = lower.indexOf(needle, from);
|
|
1040
1644
|
}
|
|
1041
|
-
|
|
1645
|
+
if (from < name.length)
|
|
1646
|
+
parts.push({ text: name.substring(from), hit: false });
|
|
1647
|
+
return parts;
|
|
1648
|
+
}
|
|
1649
|
+
// #endregion
|
|
1650
|
+
// #region Valori secondo la modalità (dall'avvio o dall'ultimo snapshot)
|
|
1651
|
+
get snapMode() {
|
|
1652
|
+
return this.live && this.snapValues;
|
|
1653
|
+
}
|
|
1654
|
+
calls(node) { return this.snapMode ? node.snapDeltaCalls : node.calls; }
|
|
1655
|
+
time(node) { return this.snapMode ? node.snapDeltaTime : node.time; }
|
|
1656
|
+
self(node) { return this.snapMode ? node.snapDeltaTimeLessChildren : node.timeLessChildren; }
|
|
1657
|
+
avg(node) { return this.snapMode ? node.snapDeltaAvgTime : node.avgTime; }
|
|
1658
|
+
peak(node) { return this.snapMode ? node.snapPeakTime : node.peakTime; }
|
|
1659
|
+
parentPerc(node) { return this.snapMode ? node.snapParentPercentage : node.parentPercentage; }
|
|
1660
|
+
firstLevelPerc(node) { return this.snapMode ? node.snapPercentage : node.percentage; }
|
|
1661
|
+
omittedTime(node) { return this.snapMode ? node.omittedSnapDeltaTime : node.omittedTime; }
|
|
1662
|
+
flatCalls(item) { return this.snapMode ? item.snapDeltaCalls : item.calls; }
|
|
1663
|
+
flatSelf(item) { return this.snapMode ? item.snapDeltaTimeLessChildren : item.timeLessChildren; }
|
|
1664
|
+
flatPerc(item) { return this.snapMode ? item.snapPercentage : item.percentage; }
|
|
1665
|
+
flatTime(item) { return this.snapMode ? item.snapDeltaTime : item.time; }
|
|
1666
|
+
// La vista per nome non ha il medio dei delta: si calcola
|
|
1667
|
+
flatAvg(item) { return this.snapMode ? (item.snapDeltaCalls > 0 ? item.snapDeltaTime / item.snapDeltaCalls : null) : item.avgTime; }
|
|
1668
|
+
// #endregion
|
|
1669
|
+
// #region Eventi per nome
|
|
1670
|
+
flatChanged() {
|
|
1671
|
+
this.saveSettings();
|
|
1672
|
+
this.loadFlat();
|
|
1673
|
+
}
|
|
1674
|
+
setFlatTop(id) {
|
|
1675
|
+
this.flatTop = id;
|
|
1676
|
+
this.flatChanged();
|
|
1677
|
+
}
|
|
1678
|
+
flatSearchChanged() {
|
|
1679
|
+
clearTimeout(this.flatSearchTimer);
|
|
1680
|
+
this.flatSearchTimer = setTimeout(() => this.loadFlat(), SEARCH_DELAY_MS);
|
|
1681
|
+
}
|
|
1682
|
+
async loadFlat() {
|
|
1683
|
+
let generation = ++this.flatGeneration;
|
|
1684
|
+
let req = this.baseRequest(new ProfilerV2FlatRequest());
|
|
1685
|
+
req.orderBy = +this.flatOrderBy;
|
|
1686
|
+
req.top = +this.flatTop;
|
|
1687
|
+
let search = (this.flatSearch ?? "").trim();
|
|
1688
|
+
if (search)
|
|
1689
|
+
req.search = search;
|
|
1690
|
+
if (this.flatType && this.flatType != "*")
|
|
1691
|
+
req.eventType = this.flatType;
|
|
1692
|
+
if (this.flatIncludeIdle)
|
|
1693
|
+
req.includeIdle = true;
|
|
1694
|
+
this.flatLoading = true;
|
|
1695
|
+
this.flatError = null;
|
|
1696
|
+
try {
|
|
1697
|
+
let res = await this.adms.getProfilerFlatV2(this.be, req);
|
|
1698
|
+
if (generation != this.flatGeneration)
|
|
1699
|
+
return;
|
|
1700
|
+
this.flat = res;
|
|
1701
|
+
this.openFlat.clear();
|
|
1702
|
+
}
|
|
1703
|
+
catch (err) {
|
|
1704
|
+
if (generation == this.flatGeneration)
|
|
1705
|
+
this.flatError = this.errorMessage(err);
|
|
1706
|
+
}
|
|
1707
|
+
finally {
|
|
1708
|
+
if (generation == this.flatGeneration)
|
|
1709
|
+
this.flatLoading = false;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
flatKey(item) {
|
|
1713
|
+
return `${item.eventTypeName}|${item.waitClassName}|${item.nodeName}`;
|
|
1714
|
+
}
|
|
1715
|
+
toggleFlat(item) {
|
|
1716
|
+
let key = this.flatKey(item);
|
|
1717
|
+
if (this.openFlat.has(key))
|
|
1718
|
+
this.openFlat.delete(key);
|
|
1719
|
+
else
|
|
1720
|
+
this.openFlat.add(key);
|
|
1721
|
+
}
|
|
1722
|
+
// R20: dall'elenco dei nodi di un evento, apri nell'albero
|
|
1723
|
+
openInTree(nodeId, event) {
|
|
1724
|
+
event.stopPropagation();
|
|
1725
|
+
this.setRoot(nodeId);
|
|
1726
|
+
this.select(nodeId);
|
|
1727
|
+
}
|
|
1728
|
+
showDetail(nodeId, event) {
|
|
1729
|
+
event.stopPropagation();
|
|
1730
|
+
this.select(nodeId);
|
|
1731
|
+
}
|
|
1732
|
+
// #endregion
|
|
1733
|
+
// #region Dettaglio
|
|
1734
|
+
select(nodeId, delayed = false) {
|
|
1735
|
+
clearTimeout(this.detailTimer);
|
|
1736
|
+
if (nodeId == this.selectedId && (this.detailLoading || this.detail?.nodeId == nodeId))
|
|
1737
|
+
return;
|
|
1738
|
+
this.selectedId = nodeId;
|
|
1739
|
+
if (delayed)
|
|
1740
|
+
this.detailTimer = setTimeout(() => this.loadDetail(nodeId), DETAIL_DELAY_MS);
|
|
1741
|
+
else
|
|
1742
|
+
this.loadDetail(nodeId);
|
|
1743
|
+
}
|
|
1744
|
+
async loadDetail(nodeId) {
|
|
1745
|
+
// Se l'utente seleziona un altro nodo prima della risposta, quella vecchia si scarta (R18)
|
|
1746
|
+
let generation = ++this.detailGeneration;
|
|
1747
|
+
let req = this.baseRequest(new ProfilerV2NodeRequest());
|
|
1748
|
+
req.nodeId = nodeId;
|
|
1749
|
+
this.detailLoading = true;
|
|
1750
|
+
this.detailError = null;
|
|
1751
|
+
this.detailMissing = false;
|
|
1752
|
+
try {
|
|
1753
|
+
let res = await this.adms.getProfilerNodeV2(this.be, req);
|
|
1754
|
+
if (generation != this.detailGeneration)
|
|
1755
|
+
return;
|
|
1756
|
+
this.detail = res;
|
|
1757
|
+
this.detailMissing = !res;
|
|
1758
|
+
this.typeParts = res ? this.buildTypeParts(res) : [];
|
|
1759
|
+
this.chart = res ? this.buildChart(res) : null;
|
|
1760
|
+
}
|
|
1761
|
+
catch (err) {
|
|
1762
|
+
if (generation == this.detailGeneration) {
|
|
1763
|
+
this.detail = null;
|
|
1764
|
+
this.detailError = `${this.lc.loc("Errore")} ${err?.status ?? ""}. ${this.adms.errorText(err)}`;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
finally {
|
|
1768
|
+
if (generation == this.detailGeneration)
|
|
1769
|
+
this.detailLoading = false;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
// Mentre arriva il dettaglio di un altro nodo quello di prima resta visibile, attenuato, così il pannello non salta
|
|
1773
|
+
get detailStale() {
|
|
1774
|
+
return !!this.detail && this.detail.nodeId != this.selectedId;
|
|
1775
|
+
}
|
|
1776
|
+
async copyId(element) {
|
|
1777
|
+
try {
|
|
1778
|
+
await navigator.clipboard.writeText(this.detail.nodeId);
|
|
1779
|
+
this.copied = true;
|
|
1780
|
+
setTimeout(() => this.copied = false, 1500);
|
|
1781
|
+
}
|
|
1782
|
+
catch {
|
|
1783
|
+
// Senza appunti (pagina non sicura o permesso negato) il testo dell'id resta selezionato, pronto per Ctrl+C
|
|
1784
|
+
let range = document.createRange();
|
|
1785
|
+
range.selectNodeContents(element);
|
|
1786
|
+
let selection = window.getSelection();
|
|
1787
|
+
selection.removeAllRanges();
|
|
1788
|
+
selection.addRange(range);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
// Ripartizione del tempo proprio di nodo e discendenti: solo i tipi maggiori di zero, in ordine decrescente
|
|
1792
|
+
buildTypeParts(stats) {
|
|
1793
|
+
let parts = [
|
|
1794
|
+
["Cpu", stats.cpuPerc], ["Database", stats.dbPerc], ["Wait", stats.waitPerc], ["Script", stats.scriptPerc], ["Network", stats.networkPerc],
|
|
1795
|
+
["IO", stats.ioPerc], ["Sleep", stats.sleepPerc], ["Throttle", stats.throttlePerc], ["Idle", stats.idlePerc]
|
|
1796
|
+
];
|
|
1797
|
+
return parts.filter(t => t[1] > 0).sort((a, b) => b[1] - a[1]).map(t => ({ type: t[0], label: TYPE_LABELS[t[0]], color: TYPE_COLORS[t[0]], percentage: t[1] }));
|
|
1798
|
+
}
|
|
1799
|
+
// activeSessions ha un punto al secondo: si raggruppano in circa 160 punti con la media (R27)
|
|
1800
|
+
buildChart(stats) {
|
|
1801
|
+
let points = stats.activeSessions ?? [];
|
|
1802
|
+
if (points.length == 0)
|
|
1803
|
+
return null;
|
|
1804
|
+
let times = points.map(t => this.parseUtc(t.timestamp).getTime());
|
|
1805
|
+
let values = points.map(t => t.activity ?? 0);
|
|
1806
|
+
let interval = points.length > 1 ? Math.max(1, Math.round((times[times.length - 1] - times[0]) / (points.length - 1) / 1000)) : 1;
|
|
1807
|
+
let size = Math.max(1, Math.ceil(points.length / CHART_POINTS));
|
|
1808
|
+
let grouped = [];
|
|
1809
|
+
for (let i = 0; i < points.length; i += size) {
|
|
1810
|
+
let slice = values.slice(i, i + size);
|
|
1811
|
+
grouped.push({ t: times[i], v: slice.reduce((a, b) => a + b, 0) / slice.length });
|
|
1812
|
+
}
|
|
1813
|
+
let max = this.niceMax(Math.max(...grouped.map(t => t.v)));
|
|
1814
|
+
let n = grouped.length;
|
|
1815
|
+
let x = (i) => n > 1 ? i / (n - 1) * 100 : 50;
|
|
1816
|
+
let y = (v) => 100 - v / max * 100;
|
|
1817
|
+
let line = grouped.map((t, i) => `${i ? "L" : "M"}${x(i).toFixed(2)},${y(t.v).toFixed(2)}`).join(" ");
|
|
1818
|
+
return {
|
|
1819
|
+
line: line,
|
|
1820
|
+
area: `${line} L${x(n - 1).toFixed(2)},100 L${x(0).toFixed(2)},100 Z`,
|
|
1821
|
+
color: this.typeColor(stats.eventTypeName, stats.waitClassName),
|
|
1822
|
+
max: max,
|
|
1823
|
+
lastX: x(n - 1),
|
|
1824
|
+
lastY: y(grouped[n - 1].v),
|
|
1825
|
+
start: this.hhmm(new Date(times[0])),
|
|
1826
|
+
middle: this.hhmm(new Date(times[Math.floor(times.length / 2)])),
|
|
1827
|
+
end: this.hhmm(new Date(times[times.length - 1])),
|
|
1828
|
+
average: values.reduce((a, b) => a + b, 0) / values.length,
|
|
1829
|
+
peak: Math.max(...values),
|
|
1830
|
+
points: points.length,
|
|
1831
|
+
interval: interval,
|
|
1832
|
+
group: size * interval
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
// Massimo dell'asse arrotondato a un valore tondo: 1, 2, 2,5 o 5 per una potenza di 10
|
|
1836
|
+
niceMax(value) {
|
|
1837
|
+
if (!(value > 0))
|
|
1838
|
+
return 1;
|
|
1839
|
+
let power = Math.pow(10, Math.floor(Math.log10(value)));
|
|
1840
|
+
let fraction = value / power;
|
|
1841
|
+
return [1, 2, 2.5, 5, 10].find(t => fraction <= t + 1e-9) * power;
|
|
1842
|
+
}
|
|
1843
|
+
// #endregion
|
|
1844
|
+
// #region Presentazione
|
|
1845
|
+
typeColor(type, waitClass) {
|
|
1846
|
+
return TYPE_COLORS[this.isIdle(type, waitClass) ? "Idle" : type] ?? TYPE_COLORS.Idle;
|
|
1847
|
+
}
|
|
1848
|
+
typeLabel(type, waitClass) {
|
|
1849
|
+
let label = TYPE_LABELS[type] ?? type;
|
|
1850
|
+
return waitClass && waitClass != "None" ? `${label} · ${waitClass}` : label;
|
|
1851
|
+
}
|
|
1852
|
+
axisValue(value) {
|
|
1853
|
+
return formatNumber(value, "it", "1.0-3");
|
|
1854
|
+
}
|
|
1855
|
+
duration(seconds) {
|
|
1856
|
+
return formatDuration(seconds);
|
|
1857
|
+
}
|
|
1858
|
+
// Le date delle risposte sono UTC "dd/MM/yyyy HH:mm:ss" senza fuso: si leggono come UTC e si mostrano in ora locale
|
|
1859
|
+
parseUtc(value) {
|
|
1860
|
+
let m = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})$/.exec(value ?? "");
|
|
1861
|
+
return m ? new Date(Date.UTC(+m[3], +m[2] - 1, +m[1], +m[4], +m[5], +m[6])) : value ? new Date(value) : null;
|
|
1862
|
+
}
|
|
1863
|
+
hhmm(value) {
|
|
1864
|
+
let date = typeof value == "string" ? this.parseUtc(value) : value;
|
|
1865
|
+
return date ? dayjs(date).format("HH:mm") : "—";
|
|
1866
|
+
}
|
|
1867
|
+
ddmmhhmm(value) {
|
|
1868
|
+
let date = this.parseUtc(value);
|
|
1869
|
+
return date ? dayjs(date).format("DD/MM HH:mm") : "—";
|
|
1870
|
+
}
|
|
1871
|
+
minutesAgo(value) {
|
|
1872
|
+
let date = this.parseUtc(value);
|
|
1873
|
+
return date ? Math.max(0, Math.floor((Date.now() - date.getTime()) / 60000)) : 0;
|
|
1874
|
+
}
|
|
1875
|
+
// "{x} s fa", "{x} min fa", "{x} h {y} min fa"; "mai" se nessuno è ancora uscito
|
|
1876
|
+
relative(value) {
|
|
1877
|
+
let date = this.parseUtc(value);
|
|
1878
|
+
if (!date)
|
|
1879
|
+
return this.lc.loc("mai");
|
|
1880
|
+
let seconds = Math.max(0, Math.round((Date.now() - date.getTime()) / 1000));
|
|
1881
|
+
if (seconds < 60)
|
|
1882
|
+
return `${seconds} ${this.lc.loc("s fa")}`;
|
|
1883
|
+
let minutes = Math.floor(seconds / 60);
|
|
1884
|
+
if (minutes < 60)
|
|
1885
|
+
return `${minutes} ${this.lc.loc("min fa")}`;
|
|
1886
|
+
return `${Math.floor(minutes / 60)} h ${minutes % 60} ${this.lc.loc("min fa")}`;
|
|
1887
|
+
}
|
|
1888
|
+
errorMessage(err) {
|
|
1889
|
+
return `${this.lc.loc("Errore")} ${err?.status ?? ""}: ${this.adms.errorText(err)}`;
|
|
1890
|
+
}
|
|
1891
|
+
// #endregion
|
|
1892
|
+
// #region Supporto
|
|
1893
|
+
buildSources() {
|
|
1894
|
+
let orders = [
|
|
1895
|
+
[ProfilerOrderBy.Time, "Tempo"], [ProfilerOrderBy.TimeLessChildren, "Tempo proprio"], [ProfilerOrderBy.Calls, "Chiamate"],
|
|
1896
|
+
[ProfilerOrderBy.AvgTime, "Tempo medio"], [ProfilerOrderBy.PeakTime, "Picco"], [ProfilerOrderBy.SnapDeltaTime, "Δ Tempo"],
|
|
1897
|
+
[ProfilerOrderBy.SnapDeltaTimeLessChildren, "Δ Tempo proprio"], [ProfilerOrderBy.SnapDeltaCalls, "Δ Chiamate"], [ProfilerOrderBy.ActiveThreads, "Thread attivi"]
|
|
1898
|
+
];
|
|
1899
|
+
this.TreeOrders = orders.map(t => new KeyValue(String(t[0]), this.lc.loc(t[1])));
|
|
1900
|
+
// La vista per nome parte dal tempo proprio e non ha il Δ del tempo totale
|
|
1901
|
+
this.FlatOrders = [1, 0, 2, 3, 4, 6, 7, 8].map(t => this.TreeOrders.find(k => k.id == String(t)));
|
|
1902
|
+
this.ChildrenOptions = ["10", "25", "50", "100"].map(t => new KeyValue(t, t)).concat([new KeyValue("0", this.lc.loc("Tutti"))]);
|
|
1903
|
+
this.ThresholdOptions = [new KeyValue("0", this.lc.loc("Nessuna")), new KeyValue("0.1", "≥ 0,1%"), new KeyValue("1", "≥ 1%"), new KeyValue("5", "≥ 5%")];
|
|
1904
|
+
this.FlatTypes = [new KeyValue("*", this.lc.loc("Tutti"))].concat(EVENT_TYPES.map(t => new KeyValue(t, t)));
|
|
1905
|
+
this.FlatRows = [new KeyValue("100", "100"), new KeyValue("500", "500"), new KeyValue("1000", "1.000")];
|
|
1906
|
+
this.updateOrderOptions();
|
|
1907
|
+
this.buildInstances();
|
|
1908
|
+
}
|
|
1909
|
+
// Le chiavi dei delta e i thread attivi si propongono solo in live (R10)
|
|
1910
|
+
updateOrderOptions() {
|
|
1911
|
+
this.TreeOrderOptions = this.live ? this.TreeOrders : this.TreeOrders.slice(0, 5);
|
|
1912
|
+
this.FlatOrderOptions = this.live ? this.FlatOrders : this.FlatOrders.slice(0, 5);
|
|
1913
|
+
}
|
|
1914
|
+
// Indipendenti: l'istanza che risponde è facoltativa, l'elenco usa la gestione errori standard
|
|
1915
|
+
loadInstances() {
|
|
1916
|
+
this.adms.getInstanceId(this.be).then(t => {
|
|
1917
|
+
this.instanceId = t;
|
|
1918
|
+
this.buildInstances();
|
|
1919
|
+
});
|
|
1920
|
+
this.adms.getInstances(this.be).then(t => {
|
|
1921
|
+
this.instanceIds = (t ?? []).map(k => k.id);
|
|
1922
|
+
this.buildInstances();
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
buildInstances() {
|
|
1926
|
+
this.Instances = [
|
|
1927
|
+
new KeyValue(INSTANCE_THIS, this.instanceId ? `${this.lc.loc("Questa istanza")} (${this.instanceId})` : this.lc.loc("Questa istanza")),
|
|
1928
|
+
new KeyValue(INSTANCE_ALL$1, this.instanceIds.length ? `${this.lc.loc("Tutto il cluster")} (${this.instanceIds.length} ${this.instanceIds.length == 1 ? this.lc.loc("istanza") : this.lc.loc("istanze")})` : this.lc.loc("Tutto il cluster")),
|
|
1929
|
+
...this.instanceIds.map(t => new KeyValue(t, `${this.lc.loc("Solo")} ${t}`))
|
|
1930
|
+
];
|
|
1931
|
+
}
|
|
1932
|
+
updateUrl() {
|
|
1933
|
+
let [path, query] = this.location.path().split("?");
|
|
1934
|
+
let params = new URLSearchParams(query ?? "");
|
|
1935
|
+
if (this.rootId)
|
|
1936
|
+
params.set("root", this.rootId);
|
|
1937
|
+
else
|
|
1938
|
+
params.delete("root");
|
|
1939
|
+
this.location.replaceState(path, params.toString());
|
|
1940
|
+
}
|
|
1941
|
+
applySettings(saved) {
|
|
1942
|
+
this.orderBy = saved.orderBy ?? this.orderBy;
|
|
1943
|
+
this.maxChildren = saved.maxChildren ?? this.maxChildren;
|
|
1944
|
+
this.minPercentage = saved.minPercentage ?? this.minPercentage;
|
|
1945
|
+
this.snapValues = saved.snapValues ?? this.snapValues;
|
|
1946
|
+
this.flatOrderBy = saved.flatOrderBy ?? this.flatOrderBy;
|
|
1947
|
+
this.flatTop = saved.flatTop ?? this.flatTop;
|
|
1948
|
+
}
|
|
1949
|
+
saveSettings() {
|
|
1950
|
+
let settings = {
|
|
1951
|
+
orderBy: this.orderBy,
|
|
1952
|
+
maxChildren: this.maxChildren,
|
|
1953
|
+
minPercentage: this.minPercentage,
|
|
1954
|
+
snapValues: this.snapValues,
|
|
1955
|
+
flatOrderBy: this.flatOrderBy,
|
|
1956
|
+
flatTop: this.flatTop
|
|
1957
|
+
};
|
|
1958
|
+
this.localStorage.setItem(STORAGE_KEY$3, settings).subscribe();
|
|
1959
|
+
}
|
|
1960
|
+
toDate(value) {
|
|
1961
|
+
return value ? this.dateExts.getDateConvertion(value).toDate() : null;
|
|
1962
|
+
}
|
|
1963
|
+
fmt(date) {
|
|
1964
|
+
return dayjs(date).format(this.lc.token("getFullDateDisplayFormat"));
|
|
1965
|
+
}
|
|
1966
|
+
floorHour(date) {
|
|
1967
|
+
let ret = new Date(date);
|
|
1968
|
+
ret.setMinutes(0, 0, 0);
|
|
1969
|
+
return ret;
|
|
1970
|
+
}
|
|
1971
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerV2Component, deps: [{ token: i1$1.DateService }, { token: i1.LocalizationService }, { token: i3.PreferencesService }, { token: i1$1.InterComService }, { token: i4.ActivatedRoute }, { token: i4.Router }, { token: i5.Location }, { token: AdminModuleBaseService }, { token: ADM_MODULE_PATH }, { token: ADM_WORKLOAD_V2, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1972
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: AdmProfilerV2Component, isStandalone: false, selector: "profiler_v2", providers: [{ provide: LocalizationService, useClass: AdmProfilerV2Loc }], ngImport: i0, template: "<div class=\"row\">\r\n <div class=\"col-12\">\r\n <div class=\"card app-overflow-visible\" widget>\r\n <div class=\"card-header\">\r\n <span class=\"text-uppercase\">{{'Profiler del Server' | localize: lc}}</span>\r\n </div>\r\n <div class=\"card-body widget-body pv2-body\">\r\n <!-- Barra dei comandi: quali dati caricare. Ogni cambio ricarica la vista e il dettaglio aperto -->\r\n <div class=\"pv2-toolbar\">\r\n <div class=\"pv2-cluster\">\r\n <div class=\"pv2-group\">\r\n <span class=\"pv2-group-label\">{{'Sorgente' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n <button type=\"button\" [class.pv2-on]=\"live\" (click)=\"setLive(true)\"><span class=\"pv2-live-dot\"></span>Live</button>\r\n <button type=\"button\" [class.pv2-on]=\"!live\" (click)=\"setLive(false)\">{{'Storico' | localize: lc}}</button>\r\n </div>\r\n </div>\r\n <div class=\"pv2-field pv2-field-wide pv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"instance\" (ngModelChange)=\"setInstance($event)\" name=\"instance\" [Source]=\"Instances\" [Placeholder]=\"'Istanze' | localize: lc\"></form-select>\r\n </div>\r\n </div>\r\n\r\n @if (!live) {\r\n <div class=\"pv2-cluster\">\r\n <div class=\"pv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterfrom\" (ngModelChange)=\"filterfrom = $event; datesChanged()\" name=\"filterfrom\" [Placeholder]=\"'Dal' | localize: lc\"></form-datetime>\r\n </div>\r\n <div class=\"pv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterto\" (ngModelChange)=\"filterto = $event; datesChanged()\" name=\"filterto\" [Placeholder]=\"'Al' | localize: lc\"></form-datetime>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"pv2-group\">\r\n <span class=\"pv2-group-label\">{{'Valori' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n <button type=\"button\" [class.pv2-on]=\"!snapValues\" (click)=\"setSnapValues(false)\">{{\"Dall'avvio\" | localize: lc}}</button>\r\n <button type=\"button\" [class.pv2-on]=\"snapValues\" (click)=\"setSnapValues(true)\">{{\"Dall'ultimo snapshot\" | localize: lc}}</button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"pv2-cluster pv2-cluster-end\">\r\n <button type=\"button\" class=\"btn app-btn-search\" [disabled]=\"loading\" (click)=\"reloadAll()\" [title]=\"'Aggiorna' | localize: lc\">\r\n <i class=\"fa fa-refresh\" [class.fa-spin]=\"loading\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn app-btn-search\" (click)=\"downloadWorkloadReport()\" [title]=\"(workloadV2 ? 'Workload report' : 'Scarica il workload report') | localize: lc\">\r\n <i class=\"fa\" [class.fa-download]=\"!workloadV2\" [class.fa-file-text]=\"workloadV2\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Riga della finestra: sempre visibile sopra la vista -->\r\n <div class=\"pv2-window\">\r\n <span><strong>{{live ? 'Live' : ('Storico' | localize: lc)}}</strong> · {{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}} · {{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\">›</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\">×</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}} «{{activeSearch}}».</div>\r\n }\r\n }\r\n @if (treeError) {\r\n <div class=\"pv2-alert pv2-alert-error\">{{treeError}}</div>\r\n }\r\n\r\n <div class=\"pv2-table-wrap\" tabindex=\"0\" (keydown)=\"treeKeydown($event)\">\r\n <table class=\"pv2-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"pv2-col-node\">{{'Nodo' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Chiamate' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Tempo' | localize: lc}}</th>\r\n <th>{{snapMode ? '\u0394 ' : ''}}{{'% del padre' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{(snapMode ? '\u0394 Proprio' : 'Tempo proprio') | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Medio' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Picco' | localize: lc}}</th>\r\n @if (live) {\r\n <th class=\"pv2-num\">{{'Thread ora' | localize: lc}}</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (row of rows; track row.key) {\r\n @if (row.kind == 'node') {\r\n <tr [id]=\"'pv2-row-' + row.node.nodeId\" class=\"pv2-row\"\r\n [class.pv2-selected]=\"row.node.nodeId == selectedId\"\r\n [class.pv2-idle]=\"isIdle(row.node.eventTypeName, row.node.waitClassName)\"\r\n (click)=\"rowClicked(row.node)\" (dblclick)=\"toggle(row.node)\">\r\n <td class=\"pv2-col-node\">\r\n <div class=\"pv2-node\" [style.padding-left.px]=\"row.depth * 16\">\r\n @if (hasChildren(row.node)) {\r\n <button type=\"button\" class=\"pv2-caret\" tabindex=\"-1\" [attr.aria-expanded]=\"isExpanded(row.node)\" (click)=\"toggle(row.node, $event)\">\r\n <i class=\"fa\" [class.fa-caret-down]=\"isExpanded(row.node)\" [class.fa-caret-right]=\"!isExpanded(row.node)\"></i>\r\n </button>\r\n } @else {\r\n <span class=\"pv2-caret\"></span>\r\n }\r\n <span class=\"pv2-type\"><span class=\"pv2-dot\" [style.background]=\"typeColor(row.node.eventTypeName, row.node.waitClassName)\"></span>{{typeLabel(row.node.eventTypeName, row.node.waitClassName)}}</span>\r\n <span class=\"pv2-name\" [title]=\"row.node.nodeName\">@if (row.node.searchMatch && activeSearch) {@for (part of nameParts(row.node.nodeName, activeSearch); track $index) {@if (part.hit) {<mark>{{part.text}}</mark>} @else {<span>{{part.text}}</span>}}} @else {<span>{{row.node.nodeName}}</span>}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{calls(row.node) | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{time(row.node) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-perc\" [title]=\"(parentPerc(row.node) | pv2fmt: 'pct') + ' ' + ('del padre' | localize: lc) + ' \u00B7 ' + (firstLevelPerc(row.node) | pv2fmt: 'pct') + ' ' + ('del nodo di primo livello' | localize: lc)\">\r\n <div class=\"pv2-perc-cell\">\r\n <div class=\"pv2-bar\"><div [style.width.%]=\"parentPerc(row.node)\" [style.background]=\"typeColor(row.node.eventTypeName, row.node.waitClassName)\"></div></div>\r\n <span>{{parentPerc(row.node) | pv2fmt: 'pct'}}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{self(row.node) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{avg(row.node) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{peak(row.node) | pv2fmt: 'dur'}}</td>\r\n @if (live) {\r\n <td class=\"pv2-num pv2-threads\">\r\n @if (isLongRunning(row.node.eventTypeName, row.node.waitClassName, row.node.activeMaxTime)) {\r\n <span class=\"pv2-long\" [title]=\"('Esecuzione in corso da' | localize: lc) + ' ' + duration(row.node.activeMaxTime) + ': ' + ('forse bloccata' | localize: lc)\"><i class=\"fa fa-clock\"></i> {{duration(row.node.activeMaxTime)}}</span>\r\n }\r\n @if (row.node.activeThreads > 0) {\r\n <span class=\"pv2-active\"><span class=\"pv2-active-dot\"></span>{{row.node.activeThreads}}</span>\r\n } @else {\r\n <span class=\"pv2-muted\">{{row.node.activeThreads ?? '\u2014'}}</span>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n } @else if (row.kind == 'loading') {\r\n <tr class=\"pv2-service\">\r\n <td [attr.colspan]=\"live ? 8 : 7\">\r\n <div [style.padding-left.px]=\"row.depth * 16 + 24\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento dei figli\u2026' | localize: lc}}</div>\r\n </td>\r\n </tr>\r\n } @else {\r\n <tr class=\"pv2-service\">\r\n <td [attr.colspan]=\"live ? 8 : 7\">\r\n <div [style.padding-left.px]=\"row.depth * 16 + 24\">\r\n {{row.node.omittedChildren | pv2fmt: 'int'}} {{'figli non mostrati' | localize: lc}} · {{omittedTime(row.node) | pv2fmt: 'dur'}}\r\n @if (!snapMode) {\r\n · {{row.node.omittedCalls | pv2fmt: 'int'}} {{'chiamate' | localize: lc}}\r\n }\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"showAll(row.node, $event)\">{{'Mostra tutti' | localize: lc}}</button>\r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n @if (rows.length == 0 && !treeError) {\r\n <div class=\"pv2-empty\">\r\n @if (!tree) {\r\n {{'Caricamento\u2026' | localize: lc}}\r\n } @else if (!tree.snapFrom && !live) {\r\n {{'Nessuno snapshot termina nella finestra scelta: allargala o spostala indietro.' | localize: lc}}\r\n } @else {\r\n {{\"Il nodo non c'\u00E8 nelle istanze scelte.\" | localize: lc}}\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n @if (flatError) {\r\n <div class=\"pv2-alert pv2-alert-error\">{{flatError}}</div>\r\n }\r\n <div class=\"pv2-table-wrap\">\r\n <table class=\"pv2-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"pv2-col-node\">{{'Evento' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{'Percorsi' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Chiamate' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{(snapMode ? '\u0394 Proprio' : 'Tempo proprio') | localize: lc}}</th>\r\n <th>{{snapMode ? '\u0394 ' : ''}}{{'% proprio' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Tempo' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Medio' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{(snapMode ? \"Picco dall'avvio\" : 'Picco') | localize: lc}}</th>\r\n @if (live) {\r\n <th class=\"pv2-num\">{{'Thread' | localize: lc}}</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (item of flat?.items ?? []; track flatKey(item)) {\r\n <tr class=\"pv2-row\" [class.pv2-idle]=\"isIdle(item.eventTypeName, item.waitClassName)\" [class.pv2-selected]=\"openFlat.has(flatKey(item))\" (click)=\"toggleFlat(item)\">\r\n <td class=\"pv2-col-node\">\r\n <div class=\"pv2-node\">\r\n <span class=\"pv2-caret\"><i class=\"fa\" [class.fa-caret-down]=\"openFlat.has(flatKey(item))\" [class.fa-caret-right]=\"!openFlat.has(flatKey(item))\"></i></span>\r\n <span class=\"pv2-type\"><span class=\"pv2-dot\" [style.background]=\"typeColor(item.eventTypeName, item.waitClassName)\"></span>{{typeLabel(item.eventTypeName, item.waitClassName)}}</span>\r\n <span class=\"pv2-name\" [title]=\"item.nodeName\">@for (part of nameParts(item.nodeName, (flatSearch ?? '').trim()); track $index) {@if (part.hit) {<mark>{{part.text}}</mark>} @else {<span>{{part.text}}</span>}}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{item.numPaths | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{flatCalls(item) | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{flatSelf(item) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-perc\">\r\n <div class=\"pv2-perc-cell\">\r\n <div class=\"pv2-bar\"><div [style.width.%]=\"flatPerc(item)\" [style.background]=\"typeColor(item.eventTypeName, item.waitClassName)\"></div></div>\r\n <span>{{flatPerc(item) | pv2fmt: 'pct'}}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{flatTime(item) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{flatAvg(item) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{item.peakTime | pv2fmt: 'dur'}}</td>\r\n @if (live) {\r\n <td class=\"pv2-num pv2-threads\">\r\n @if (item.activeThreads > 0) {\r\n <span class=\"pv2-active\"><span class=\"pv2-active-dot\"></span>{{item.activeThreads}}</span>\r\n } @else {\r\n <span class=\"pv2-muted\">{{item.activeThreads ?? '\u2014'}}</span>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n @if (openFlat.has(flatKey(item))) {\r\n <tr class=\"pv2-sub\">\r\n <td [attr.colspan]=\"live ? 9 : 8\">\r\n <div class=\"pv2-sub-title\">{{'Nodi con pi\u00F9 tempo' | localize: lc}}</div>\r\n @for (id of item.nodeIds ?? []; track id) {\r\n <div class=\"pv2-sub-item\">\r\n <code>{{id}}</code>\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"showDetail(id, $event)\">{{'Dettaglio' | localize: lc}}</button>\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"openInTree(id, $event)\">{{\"Apri nell'albero\" | localize: lc}}</button>\r\n </div>\r\n }\r\n @if (item.numPaths > (item.nodeIds?.length ?? 0)) {\r\n <div class=\"pv2-muted\">+{{item.numPaths - (item.nodeIds?.length ?? 0) | pv2fmt: 'int'}} {{'altri' | localize: lc}}</div>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n @if (!flatError && (!flat || !flat.items?.length)) {\r\n <div class=\"pv2-empty\">{{(flat ? 'Nessun evento con questi filtri.' : 'Caricamento\u2026') | localize: lc}}</div>\r\n }\r\n </div>\r\n @if (flat?.items?.length) {\r\n <div class=\"pv2-footer\">\r\n {{flat.numItems | pv2fmt: 'int'}} {{'eventi distinti' | localize: lc}}@if (flat.numItems > flat.items.length) {, {{'mostrati i primi' | localize: lc}} {{flat.items.length | pv2fmt: 'int'}}}.\r\n {{'La percentuale \u00E8 sul tempo proprio di tutti gli eventi non idle.' | localize: lc}}\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Dettaglio del nodo selezionato, lo stesso per le due viste -->\r\n <aside class=\"pv2-panel pv2-detail\" [class.pv2-stale]=\"detailStale\">\r\n @if (!selectedId) {\r\n <div class=\"pv2-placeholder\">{{'Seleziona un nodo per vederne il dettaglio.' | localize: lc}}</div>\r\n } @else if (detailMissing && !detailLoading) {\r\n <div class=\"pv2-placeholder\">{{\"Il nodo non c'\u00E8 nelle istanze o nella finestra scelte.\" | localize: lc}}</div>\r\n } @else if (detailError && !detailLoading) {\r\n <div class=\"pv2-placeholder pv2-error-text\">{{detailError}}</div>\r\n } @else if (!detail) {\r\n <div class=\"pv2-placeholder\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</div>\r\n } @else {\r\n <div class=\"pv2-d-head\">\r\n <span class=\"pv2-type\"><span class=\"pv2-dot\" [style.background]=\"typeColor(detail.eventTypeName, detail.waitClassName)\"></span>{{typeLabel(detail.eventTypeName, detail.waitClassName)}}</span>\r\n @if (detailLoading) {\r\n <i class=\"fa fa-spinner fa-spin pv2-muted\"></i>\r\n }\r\n </div>\r\n <div class=\"pv2-fullname\">{{detail.nodeName}}</div>\r\n <div class=\"pv2-id\">\r\n Id <code #idText>{{detail.nodeId}}</code>\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"copyId(idText)\">{{(copied ? 'Copiato' : 'Copia') | localize: lc}}</button>\r\n </div>\r\n\r\n <div class=\"pv2-d-title\">{{'Percorso' | localize: lc}}</div>\r\n <div class=\"pv2-path\">\r\n @for (item of detail.fullWaitStack ?? []; track item.nodeId; let i = $index) {\r\n <button type=\"button\" class=\"pv2-path-item\" [style.padding-left.px]=\"i * 10\" [title]=\"item.nodeName\" (click)=\"select(item.nodeId)\">\r\n <span class=\"pv2-dot\" [style.background]=\"typeColor(item.eventTypeName, item.waitClassName)\"></span>{{item.nodeName}}\r\n </button>\r\n }\r\n <div class=\"pv2-path-item pv2-path-current\" [style.padding-left.px]=\"(detail.fullWaitStack?.length ?? 0) * 10\" [title]=\"detail.nodeName\">\r\n <span class=\"pv2-dot\" [style.background]=\"typeColor(detail.eventTypeName, detail.waitClassName)\"></span>{{detail.nodeName}}\r\n </div>\r\n </div>\r\n\r\n <div class=\"pv2-d-title\">{{(live ? \"Dall'avvio\" : 'Nella finestra') | localize: lc}}</div>\r\n <dl class=\"pv2-kv\">\r\n <dt>{{'Esecuzioni' | localize: lc}}</dt><dd>{{detail.executions | pv2fmt: 'int'}}</dd>\r\n <dt>{{'Tempo totale' | localize: lc}}</dt><dd>{{detail.elapsedTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Tempo proprio' | localize: lc}}</dt><dd>{{detail.elapsedTimeLessChildren | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Medio' | localize: lc}}</dt><dd>{{detail.avgTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Picco' | localize: lc}}</dt><dd>{{detail.peakTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Minimo' | localize: lc}}</dt><dd>{{detail.minTime ? (detail.minTime | pv2fmt: 'dur') : '\u2014'}}</dd>\r\n </dl>\r\n\r\n @if (live) {\r\n <div class=\"pv2-d-title\">{{\"Dall'ultimo snapshot\" | localize: lc}}, {{hhmm(detail.snapFrom)}}</div>\r\n <dl class=\"pv2-kv\">\r\n <dt>{{'Esecuzioni' | localize: lc}}</dt><dd>{{detail.snapDeltaExecutions | pv2fmt: 'int'}}</dd>\r\n <dt>{{'Tempo totale' | localize: lc}}</dt><dd>{{detail.snapDeltaElapsedTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Tempo proprio' | localize: lc}}</dt><dd>{{detail.snapDeltaElapsedTimeLessChildren | pv2fmt: 'dur'}}</dd>\r\n </dl>\r\n\r\n <div class=\"pv2-d-title\">{{'Adesso' | localize: lc}}</div>\r\n <dl class=\"pv2-kv\">\r\n <dt>{{'Thread dentro' | localize: lc}}</dt>\r\n <dd>\r\n @if (detail.activeThreads > 0) {\r\n <span class=\"pv2-active\"><span class=\"pv2-active-dot\"></span>{{detail.activeThreads}}</span>\r\n } @else {\r\n {{detail.activeThreads ?? '\u2014'}}\r\n }\r\n </dd>\r\n <dt>{{'Esecuzione pi\u00F9 lunga' | localize: lc}}</dt>\r\n <dd>\r\n @if (detail.activeThreads > 0) {\r\n {{detail.activeMaxTime | pv2fmt: 'dur'}}\r\n @if (isLongRunning(detail.eventTypeName, detail.waitClassName, detail.activeMaxTime)) {\r\n <span class=\"pv2-long\"><i class=\"fa fa-clock\"></i> {{'forse bloccata' | localize: lc}}</span>\r\n }\r\n } @else {\r\n \u2014\r\n }\r\n </dd>\r\n <dt>{{'Ultima uscita' | localize: lc}}</dt><dd>{{relative(detail.lastExitTime)}}</dd>\r\n </dl>\r\n }\r\n\r\n @if (typeParts.length > 0) {\r\n <div class=\"pv2-d-title\">{{'Tempo proprio del sottoalbero per tipo' | localize: lc}}</div>\r\n <div class=\"pv2-stack\">\r\n @for (part of typeParts; track part.type) {\r\n <div [style.width.%]=\"part.percentage\" [style.background]=\"part.color\" [title]=\"part.label + ' ' + (part.percentage | pv2fmt: 'pct')\"></div>\r\n }\r\n </div>\r\n <div class=\"pv2-legend\">\r\n @for (part of typeParts; track part.type) {\r\n <span class=\"pv2-legend-item\"><span class=\"pv2-swatch\" [style.background]=\"part.color\"></span>{{part.label}} <strong>{{part.percentage | pv2fmt: 'pct'}}</strong></span>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"pv2-d-title\">{{'Sessioni attive sul nodo e sui discendenti' | localize: lc}}</div>\r\n @if (chart) {\r\n <div class=\"pv2-chart\">\r\n <div class=\"pv2-chart-y\">\r\n <span>{{axisValue(chart.max)}}</span>\r\n <span>{{axisValue(chart.max / 2)}}</span>\r\n <span>0</span>\r\n </div>\r\n <div class=\"pv2-chart-plot\">\r\n <svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\r\n <line x1=\"0\" y1=\"50\" x2=\"100\" y2=\"50\" class=\"pv2-chart-grid\" vector-effect=\"non-scaling-stroke\" />\r\n <path [attr.d]=\"chart.area\" [attr.fill]=\"chart.color\" fill-opacity=\"0.25\" />\r\n <path [attr.d]=\"chart.line\" [attr.stroke]=\"chart.color\" fill=\"none\" stroke-width=\"1.5\" vector-effect=\"non-scaling-stroke\" />\r\n </svg>\r\n <span class=\"pv2-chart-last\" [style.left.%]=\"chart.lastX\" [style.top.%]=\"chart.lastY\" [style.background]=\"chart.color\"></span>\r\n </div>\r\n <span></span>\r\n <div class=\"pv2-chart-x\">\r\n <span>{{chart.start}}</span>\r\n <span>{{chart.middle}}</span>\r\n <span>{{chart.end}}</span>\r\n </div>\r\n </div>\r\n <div class=\"pv2-caption\">\r\n {{'Media' | localize: lc}} {{axisValue(chart.average)}} · {{'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}} · {{detail.numDescendants | pv2fmt: 'int'}} {{'discendenti' | localize: lc}}</span>\r\n <button type=\"button\" class=\"pv2-btn\" (click)=\"setRoot(detail.nodeId)\"><i class=\"fa fa-sitemap\"></i> {{'Apri come radice' | localize: lc}}</button>\r\n </div>\r\n }\r\n </aside>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: ["@charset \"UTF-8\";:host{display:block}:host ::ng-deep .pv2-noclear .p-select-clear-icon{display:none}.pv2-body{container-type:inline-size}.pv2-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 22px;padding-top:6px}.pv2-cluster{display:flex;flex-wrap:nowrap;align-items:center;gap:10px}.pv2-cluster-end{margin-left:auto}.pv2-field{width:190px}.pv2-field-wide{width:290px}.pv2-field-narrow{width:140px}.pv2-field-search{flex:1 1 220px;min-width:200px;max-width:340px}.pv2-toggle{display:flex;align-items:center;gap:8px;height:39px;white-space:nowrap}.pv2-pointer{cursor:pointer}.pv2-group{position:relative;display:inline-flex}.pv2-group.pv2-disabled{opacity:.5}.pv2-group-label{position:absolute;top:-8px;left:8px;z-index:1;padding:0 4px;background:var(--p-content-background, #fff);color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.pv2-seg{display:inline-flex;height:39px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);overflow:hidden;background:var(--p-content-background, #fff)}.pv2-seg button{display:inline-flex;align-items:center;gap:6px;padding:0 12px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.pv2-seg button:first-child{border-left:0}.pv2-seg button:hover:not(.pv2-on):not(:disabled){background:var(--p-content-hover-background, #f8fafc)}.pv2-seg button:disabled{cursor:default}.pv2-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.pv2-seg button.pv2-on{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca);font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.pv2-live-dot{width:8px;height:8px;border-radius:50%;background:#16a34a}.pv2-window{display:flex;flex-wrap:wrap;align-items:center;row-gap:4px;margin-top:12px;padding:7px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155);font-size:13px}.pv2-window>span+span:before{content:\"\\b7\";margin:0 10px;color:var(--p-text-muted-color, #64748b)}.pv2-warn-text{color:#92400e}.pv2-main{display:grid;grid-template-columns:minmax(0,1fr) 380px;gap:14px;align-items:start;margin-top:14px}@container (max-width: 1000px){.pv2-main{grid-template-columns:minmax(0,1fr)}.pv2-detail{position:static;max-height:none}}.pv2-panel{padding:12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0}.pv2-view-bar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 10px;padding-top:6px;margin-bottom:10px}.pv2-crumbs{display:flex;flex-wrap:wrap;align-items:center;gap:2px 4px;margin-bottom:8px;font-size:12px}.pv2-crumb{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:2px 6px;border:0;border-radius:4px;background:transparent;color:var(--p-primary-color, #6366f1);cursor:pointer}.pv2-crumb:hover{background:var(--p-highlight-background, #eef2ff)}.pv2-crumb-current{color:var(--p-text-color, #334155);font-weight:500;cursor:default}.pv2-crumb-current:hover{background:transparent}.pv2-crumb-sep{color:var(--p-text-muted-color, #64748b)}.pv2-alert{position:relative;margin-bottom:8px;padding:6px 30px 6px 10px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);font-size:13px}.pv2-alert.pv2-alert-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.pv2-alert.pv2-alert-error{border-color:#fca5a5;background:#fef2f2;color:#991b1b}.pv2-alert-close{position:absolute;top:3px;right:6px;border:0;background:transparent;color:inherit;font-size:16px;cursor:pointer}.pv2-table-wrap{max-height:68vh;overflow:auto;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px)}.pv2-table-wrap:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:1px}.pv2-table{width:100%;min-width:740px;border-collapse:separate;border-spacing:0;font-size:13px;color:var(--p-text-color, #334155)}.pv2-table th{position:sticky;top:0;z-index:1;padding:7px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-muted-color, #64748b);font-size:12px;font-weight:500;white-space:nowrap;text-align:left}.pv2-table td{padding:4px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);white-space:nowrap;vertical-align:middle}.pv2-col-node{width:100%;max-width:0}.pv2-num{text-align:right!important;font-variant-numeric:tabular-nums}.pv2-row{cursor:pointer}.pv2-row:hover td{background:var(--p-content-hover-background, #f8fafc)}.pv2-row.pv2-selected td{background:var(--p-highlight-background, #eef2ff)}.pv2-row.pv2-idle td{color:var(--p-text-muted-color, #64748b)}.pv2-row.pv2-idle td .pv2-name{opacity:.75}.pv2-node{display:flex;align-items:center;gap:6px;min-width:0}.pv2-caret{flex:none;width:18px;height:20px;padding:0;border:0;background:transparent;color:var(--p-text-muted-color, #64748b);text-align:center;cursor:pointer}.pv2-type{flex:none;display:inline-flex;align-items:center;gap:5px;padding:0 6px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:10px;color:var(--p-text-muted-color, #64748b);font-size:11px;line-height:18px;white-space:nowrap}.pv2-dot{flex:none;display:inline-block;width:8px;height:8px;border-radius:50%}.pv2-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pv2-name mark{padding:0 1px;border-radius:2px;background:#fde68a;color:#1f2937}.pv2-perc{width:1%}.pv2-perc-cell{display:flex;align-items:center;gap:8px}.pv2-bar{flex:none;width:64px;height:7px;border-radius:2px;overflow:hidden;background:var(--p-content-hover-background, #f8fafc);box-shadow:inset 0 0 0 1px var(--p-content-border-color, #e2e8f0)}.pv2-bar>div{height:100%}.pv2-threads>*+*{margin-left:8px}.pv2-active{display:inline-flex;align-items:center;gap:5px;font-weight:500}.pv2-active-dot{width:7px;height:7px;border-radius:50%;background:#16a34a;box-shadow:0 0 0 3px #16a34a2e}.pv2-long{display:inline-flex;align-items:center;gap:4px;padding:0 6px;border:1px solid #fcd34d;border-radius:10px;background:#fffbeb;color:#92400e;font-size:11px;line-height:18px;white-space:nowrap}.pv2-service td{padding:5px 10px;color:var(--p-text-muted-color, #64748b);font-size:12px;background:var(--p-content-background, #fff)}.pv2-sub td{padding:8px 10px 10px 34px;background:var(--p-content-hover-background, #f8fafc);white-space:normal}.pv2-sub-title{margin-bottom:4px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-sub-item{display:flex;align-items:center;gap:10px;padding:2px 0}.pv2-sub-item code{font-size:12px}.pv2-link{margin-left:6px;padding:0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;cursor:pointer}.pv2-link:hover{text-decoration:underline}.pv2-empty{padding:28px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.pv2-footer{margin-top:8px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-muted{color:var(--p-text-muted-color, #64748b)}.pv2-small{font-size:12px}.pv2-detail{position:sticky;top:10px;max-height:calc(100vh - 20px);overflow:auto;font-size:13px;color:var(--p-text-color, #334155);transition:opacity .15s}.pv2-detail.pv2-stale{opacity:.55}.pv2-placeholder{padding:36px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.pv2-error-text{color:#dc2626}.pv2-d-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.pv2-fullname{max-height:140px;overflow:auto;padding:8px 10px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);font-family:ui-monospace,Cascadia Mono,Consolas,monospace;font-size:12px;white-space:pre-wrap;word-break:break-word}.pv2-id{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-id code{font-size:12px}.pv2-d-title{margin:16px 0 6px;padding-top:10px;border-top:1px solid var(--p-content-border-color, #e2e8f0);color:var(--p-text-muted-color, #64748b);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.pv2-path{display:flex;flex-direction:column}.pv2-path-item{display:flex;align-items:center;gap:6px;max-width:100%;padding:2px 0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.pv2-path-item:hover{text-decoration:underline}.pv2-path-current{color:var(--p-text-color, #334155);font-weight:600;cursor:default}.pv2-path-current:hover{text-decoration:none}.pv2-kv{display:grid;grid-template-columns:auto 1fr;gap:3px 14px;margin:0}.pv2-kv dt{color:var(--p-text-muted-color, #64748b);font-weight:400}.pv2-kv dd{margin:0;text-align:right;font-variant-numeric:tabular-nums}.pv2-stack{display:flex;height:12px;border-radius:3px;overflow:hidden;background:var(--p-content-hover-background, #f8fafc)}.pv2-legend{display:flex;flex-wrap:wrap;gap:4px 12px;margin-top:6px;font-size:12px}.pv2-legend-item{display:inline-flex;align-items:center;gap:5px}.pv2-swatch{width:10px;height:10px;border-radius:2px}.pv2-chart{display:grid;grid-template-columns:auto 1fr;gap:2px 6px}.pv2-chart-y{display:flex;flex-direction:column;justify-content:space-between;height:110px;color:var(--p-text-muted-color, #64748b);font-size:11px;text-align:right;line-height:1}.pv2-chart-plot{position:relative;height:110px;border-left:1px solid var(--p-content-border-color, #e2e8f0);border-bottom:1px solid var(--p-content-border-color, #e2e8f0)}.pv2-chart-plot svg{display:block;width:100%;height:100%}.pv2-chart-grid{stroke:var(--p-content-border-color, #e2e8f0);stroke-dasharray:3 3}.pv2-chart-last{position:absolute;width:8px;height:8px;border:2px solid var(--p-content-background, #fff);border-radius:50%;transform:translate(-50%,-50%)}.pv2-chart-x{display:flex;justify-content:space-between;color:var(--p-text-muted-color, #64748b);font-size:11px}.pv2-caption{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-mini{width:100%;font-size:12px}.pv2-mini th{padding:3px 6px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);color:var(--p-text-muted-color, #64748b);font-weight:500}.pv2-mini td{padding:3px 6px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0)}.pv2-ellipsis{display:inline-block;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.pv2-d-foot{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;margin-top:16px;padding-top:10px;border-top:1px solid var(--p-content-border-color, #e2e8f0)}.pv2-btn{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:13px;cursor:pointer}.pv2-btn:hover{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}\n"], dependencies: [{ kind: "directive", type: i7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i9.EsFormInputComponent, selector: "form-input", inputs: ["minlength", "maxlength", "pattern", "Password", "Type"] }, { kind: "component", type: i9.EsFormSelectComponent, selector: "form-select", inputs: ["OptionsTemplate"] }, { kind: "component", type: i9.EsFormDateTimeComponent, selector: "form-datetime" }, { kind: "component", type: i9.EsFormCheckboxComponent, selector: "form-checkbox", inputs: ["SliderMode"] }, { kind: "directive", type: i10.Widget, selector: "[widget]" }, { kind: "pipe", type: i1.LocalizePipe, name: "localize" }, { kind: "pipe", type: ProfilerFormatPipe, name: "pv2fmt" }] }); }
|
|
1973
|
+
}
|
|
1974
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmProfilerV2Component, decorators: [{
|
|
1975
|
+
type: Component,
|
|
1976
|
+
args: [{ selector: "profiler_v2", providers: [{ provide: LocalizationService, useClass: AdmProfilerV2Loc }], standalone: false, template: "<div class=\"row\">\r\n <div class=\"col-12\">\r\n <div class=\"card app-overflow-visible\" widget>\r\n <div class=\"card-header\">\r\n <span class=\"text-uppercase\">{{'Profiler del Server' | localize: lc}}</span>\r\n </div>\r\n <div class=\"card-body widget-body pv2-body\">\r\n <!-- Barra dei comandi: quali dati caricare. Ogni cambio ricarica la vista e il dettaglio aperto -->\r\n <div class=\"pv2-toolbar\">\r\n <div class=\"pv2-cluster\">\r\n <div class=\"pv2-group\">\r\n <span class=\"pv2-group-label\">{{'Sorgente' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n <button type=\"button\" [class.pv2-on]=\"live\" (click)=\"setLive(true)\"><span class=\"pv2-live-dot\"></span>Live</button>\r\n <button type=\"button\" [class.pv2-on]=\"!live\" (click)=\"setLive(false)\">{{'Storico' | localize: lc}}</button>\r\n </div>\r\n </div>\r\n <div class=\"pv2-field pv2-field-wide pv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"instance\" (ngModelChange)=\"setInstance($event)\" name=\"instance\" [Source]=\"Instances\" [Placeholder]=\"'Istanze' | localize: lc\"></form-select>\r\n </div>\r\n </div>\r\n\r\n @if (!live) {\r\n <div class=\"pv2-cluster\">\r\n <div class=\"pv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterfrom\" (ngModelChange)=\"filterfrom = $event; datesChanged()\" name=\"filterfrom\" [Placeholder]=\"'Dal' | localize: lc\"></form-datetime>\r\n </div>\r\n <div class=\"pv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterto\" (ngModelChange)=\"filterto = $event; datesChanged()\" name=\"filterto\" [Placeholder]=\"'Al' | localize: lc\"></form-datetime>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"pv2-group\">\r\n <span class=\"pv2-group-label\">{{'Valori' | localize: lc}}</span>\r\n <div class=\"pv2-seg\">\r\n <button type=\"button\" [class.pv2-on]=\"!snapValues\" (click)=\"setSnapValues(false)\">{{\"Dall'avvio\" | localize: lc}}</button>\r\n <button type=\"button\" [class.pv2-on]=\"snapValues\" (click)=\"setSnapValues(true)\">{{\"Dall'ultimo snapshot\" | localize: lc}}</button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"pv2-cluster pv2-cluster-end\">\r\n <button type=\"button\" class=\"btn app-btn-search\" [disabled]=\"loading\" (click)=\"reloadAll()\" [title]=\"'Aggiorna' | localize: lc\">\r\n <i class=\"fa fa-refresh\" [class.fa-spin]=\"loading\"></i>\r\n </button>\r\n <button type=\"button\" class=\"btn app-btn-search\" (click)=\"downloadWorkloadReport()\" [title]=\"(workloadV2 ? 'Workload report' : 'Scarica il workload report') | localize: lc\">\r\n <i class=\"fa\" [class.fa-download]=\"!workloadV2\" [class.fa-file-text]=\"workloadV2\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Riga della finestra: sempre visibile sopra la vista -->\r\n <div class=\"pv2-window\">\r\n <span><strong>{{live ? 'Live' : ('Storico' | localize: lc)}}</strong> · {{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}} · {{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\">›</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\">×</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}} «{{activeSearch}}».</div>\r\n }\r\n }\r\n @if (treeError) {\r\n <div class=\"pv2-alert pv2-alert-error\">{{treeError}}</div>\r\n }\r\n\r\n <div class=\"pv2-table-wrap\" tabindex=\"0\" (keydown)=\"treeKeydown($event)\">\r\n <table class=\"pv2-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"pv2-col-node\">{{'Nodo' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Chiamate' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Tempo' | localize: lc}}</th>\r\n <th>{{snapMode ? '\u0394 ' : ''}}{{'% del padre' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{(snapMode ? '\u0394 Proprio' : 'Tempo proprio') | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Medio' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Picco' | localize: lc}}</th>\r\n @if (live) {\r\n <th class=\"pv2-num\">{{'Thread ora' | localize: lc}}</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (row of rows; track row.key) {\r\n @if (row.kind == 'node') {\r\n <tr [id]=\"'pv2-row-' + row.node.nodeId\" class=\"pv2-row\"\r\n [class.pv2-selected]=\"row.node.nodeId == selectedId\"\r\n [class.pv2-idle]=\"isIdle(row.node.eventTypeName, row.node.waitClassName)\"\r\n (click)=\"rowClicked(row.node)\" (dblclick)=\"toggle(row.node)\">\r\n <td class=\"pv2-col-node\">\r\n <div class=\"pv2-node\" [style.padding-left.px]=\"row.depth * 16\">\r\n @if (hasChildren(row.node)) {\r\n <button type=\"button\" class=\"pv2-caret\" tabindex=\"-1\" [attr.aria-expanded]=\"isExpanded(row.node)\" (click)=\"toggle(row.node, $event)\">\r\n <i class=\"fa\" [class.fa-caret-down]=\"isExpanded(row.node)\" [class.fa-caret-right]=\"!isExpanded(row.node)\"></i>\r\n </button>\r\n } @else {\r\n <span class=\"pv2-caret\"></span>\r\n }\r\n <span class=\"pv2-type\"><span class=\"pv2-dot\" [style.background]=\"typeColor(row.node.eventTypeName, row.node.waitClassName)\"></span>{{typeLabel(row.node.eventTypeName, row.node.waitClassName)}}</span>\r\n <span class=\"pv2-name\" [title]=\"row.node.nodeName\">@if (row.node.searchMatch && activeSearch) {@for (part of nameParts(row.node.nodeName, activeSearch); track $index) {@if (part.hit) {<mark>{{part.text}}</mark>} @else {<span>{{part.text}}</span>}}} @else {<span>{{row.node.nodeName}}</span>}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{calls(row.node) | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{time(row.node) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-perc\" [title]=\"(parentPerc(row.node) | pv2fmt: 'pct') + ' ' + ('del padre' | localize: lc) + ' \u00B7 ' + (firstLevelPerc(row.node) | pv2fmt: 'pct') + ' ' + ('del nodo di primo livello' | localize: lc)\">\r\n <div class=\"pv2-perc-cell\">\r\n <div class=\"pv2-bar\"><div [style.width.%]=\"parentPerc(row.node)\" [style.background]=\"typeColor(row.node.eventTypeName, row.node.waitClassName)\"></div></div>\r\n <span>{{parentPerc(row.node) | pv2fmt: 'pct'}}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{self(row.node) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{avg(row.node) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{peak(row.node) | pv2fmt: 'dur'}}</td>\r\n @if (live) {\r\n <td class=\"pv2-num pv2-threads\">\r\n @if (isLongRunning(row.node.eventTypeName, row.node.waitClassName, row.node.activeMaxTime)) {\r\n <span class=\"pv2-long\" [title]=\"('Esecuzione in corso da' | localize: lc) + ' ' + duration(row.node.activeMaxTime) + ': ' + ('forse bloccata' | localize: lc)\"><i class=\"fa fa-clock\"></i> {{duration(row.node.activeMaxTime)}}</span>\r\n }\r\n @if (row.node.activeThreads > 0) {\r\n <span class=\"pv2-active\"><span class=\"pv2-active-dot\"></span>{{row.node.activeThreads}}</span>\r\n } @else {\r\n <span class=\"pv2-muted\">{{row.node.activeThreads ?? '\u2014'}}</span>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n } @else if (row.kind == 'loading') {\r\n <tr class=\"pv2-service\">\r\n <td [attr.colspan]=\"live ? 8 : 7\">\r\n <div [style.padding-left.px]=\"row.depth * 16 + 24\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento dei figli\u2026' | localize: lc}}</div>\r\n </td>\r\n </tr>\r\n } @else {\r\n <tr class=\"pv2-service\">\r\n <td [attr.colspan]=\"live ? 8 : 7\">\r\n <div [style.padding-left.px]=\"row.depth * 16 + 24\">\r\n {{row.node.omittedChildren | pv2fmt: 'int'}} {{'figli non mostrati' | localize: lc}} · {{omittedTime(row.node) | pv2fmt: 'dur'}}\r\n @if (!snapMode) {\r\n · {{row.node.omittedCalls | pv2fmt: 'int'}} {{'chiamate' | localize: lc}}\r\n }\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"showAll(row.node, $event)\">{{'Mostra tutti' | localize: lc}}</button>\r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n @if (rows.length == 0 && !treeError) {\r\n <div class=\"pv2-empty\">\r\n @if (!tree) {\r\n {{'Caricamento\u2026' | localize: lc}}\r\n } @else if (!tree.snapFrom && !live) {\r\n {{'Nessuno snapshot termina nella finestra scelta: allargala o spostala indietro.' | localize: lc}}\r\n } @else {\r\n {{\"Il nodo non c'\u00E8 nelle istanze scelte.\" | localize: lc}}\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n @if (flatError) {\r\n <div class=\"pv2-alert pv2-alert-error\">{{flatError}}</div>\r\n }\r\n <div class=\"pv2-table-wrap\">\r\n <table class=\"pv2-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"pv2-col-node\">{{'Evento' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{'Percorsi' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Chiamate' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{(snapMode ? '\u0394 Proprio' : 'Tempo proprio') | localize: lc}}</th>\r\n <th>{{snapMode ? '\u0394 ' : ''}}{{'% proprio' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Tempo' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{snapMode ? '\u0394 ' : ''}}{{'Medio' | localize: lc}}</th>\r\n <th class=\"pv2-num\">{{(snapMode ? \"Picco dall'avvio\" : 'Picco') | localize: lc}}</th>\r\n @if (live) {\r\n <th class=\"pv2-num\">{{'Thread' | localize: lc}}</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (item of flat?.items ?? []; track flatKey(item)) {\r\n <tr class=\"pv2-row\" [class.pv2-idle]=\"isIdle(item.eventTypeName, item.waitClassName)\" [class.pv2-selected]=\"openFlat.has(flatKey(item))\" (click)=\"toggleFlat(item)\">\r\n <td class=\"pv2-col-node\">\r\n <div class=\"pv2-node\">\r\n <span class=\"pv2-caret\"><i class=\"fa\" [class.fa-caret-down]=\"openFlat.has(flatKey(item))\" [class.fa-caret-right]=\"!openFlat.has(flatKey(item))\"></i></span>\r\n <span class=\"pv2-type\"><span class=\"pv2-dot\" [style.background]=\"typeColor(item.eventTypeName, item.waitClassName)\"></span>{{typeLabel(item.eventTypeName, item.waitClassName)}}</span>\r\n <span class=\"pv2-name\" [title]=\"item.nodeName\">@for (part of nameParts(item.nodeName, (flatSearch ?? '').trim()); track $index) {@if (part.hit) {<mark>{{part.text}}</mark>} @else {<span>{{part.text}}</span>}}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{item.numPaths | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{flatCalls(item) | pv2fmt: 'int'}}</td>\r\n <td class=\"pv2-num\">{{flatSelf(item) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-perc\">\r\n <div class=\"pv2-perc-cell\">\r\n <div class=\"pv2-bar\"><div [style.width.%]=\"flatPerc(item)\" [style.background]=\"typeColor(item.eventTypeName, item.waitClassName)\"></div></div>\r\n <span>{{flatPerc(item) | pv2fmt: 'pct'}}</span>\r\n </div>\r\n </td>\r\n <td class=\"pv2-num\">{{flatTime(item) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{flatAvg(item) | pv2fmt: 'dur'}}</td>\r\n <td class=\"pv2-num\">{{item.peakTime | pv2fmt: 'dur'}}</td>\r\n @if (live) {\r\n <td class=\"pv2-num pv2-threads\">\r\n @if (item.activeThreads > 0) {\r\n <span class=\"pv2-active\"><span class=\"pv2-active-dot\"></span>{{item.activeThreads}}</span>\r\n } @else {\r\n <span class=\"pv2-muted\">{{item.activeThreads ?? '\u2014'}}</span>\r\n }\r\n </td>\r\n }\r\n </tr>\r\n @if (openFlat.has(flatKey(item))) {\r\n <tr class=\"pv2-sub\">\r\n <td [attr.colspan]=\"live ? 9 : 8\">\r\n <div class=\"pv2-sub-title\">{{'Nodi con pi\u00F9 tempo' | localize: lc}}</div>\r\n @for (id of item.nodeIds ?? []; track id) {\r\n <div class=\"pv2-sub-item\">\r\n <code>{{id}}</code>\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"showDetail(id, $event)\">{{'Dettaglio' | localize: lc}}</button>\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"openInTree(id, $event)\">{{\"Apri nell'albero\" | localize: lc}}</button>\r\n </div>\r\n }\r\n @if (item.numPaths > (item.nodeIds?.length ?? 0)) {\r\n <div class=\"pv2-muted\">+{{item.numPaths - (item.nodeIds?.length ?? 0) | pv2fmt: 'int'}} {{'altri' | localize: lc}}</div>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n @if (!flatError && (!flat || !flat.items?.length)) {\r\n <div class=\"pv2-empty\">{{(flat ? 'Nessun evento con questi filtri.' : 'Caricamento\u2026') | localize: lc}}</div>\r\n }\r\n </div>\r\n @if (flat?.items?.length) {\r\n <div class=\"pv2-footer\">\r\n {{flat.numItems | pv2fmt: 'int'}} {{'eventi distinti' | localize: lc}}@if (flat.numItems > flat.items.length) {, {{'mostrati i primi' | localize: lc}} {{flat.items.length | pv2fmt: 'int'}}}.\r\n {{'La percentuale \u00E8 sul tempo proprio di tutti gli eventi non idle.' | localize: lc}}\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Dettaglio del nodo selezionato, lo stesso per le due viste -->\r\n <aside class=\"pv2-panel pv2-detail\" [class.pv2-stale]=\"detailStale\">\r\n @if (!selectedId) {\r\n <div class=\"pv2-placeholder\">{{'Seleziona un nodo per vederne il dettaglio.' | localize: lc}}</div>\r\n } @else if (detailMissing && !detailLoading) {\r\n <div class=\"pv2-placeholder\">{{\"Il nodo non c'\u00E8 nelle istanze o nella finestra scelte.\" | localize: lc}}</div>\r\n } @else if (detailError && !detailLoading) {\r\n <div class=\"pv2-placeholder pv2-error-text\">{{detailError}}</div>\r\n } @else if (!detail) {\r\n <div class=\"pv2-placeholder\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</div>\r\n } @else {\r\n <div class=\"pv2-d-head\">\r\n <span class=\"pv2-type\"><span class=\"pv2-dot\" [style.background]=\"typeColor(detail.eventTypeName, detail.waitClassName)\"></span>{{typeLabel(detail.eventTypeName, detail.waitClassName)}}</span>\r\n @if (detailLoading) {\r\n <i class=\"fa fa-spinner fa-spin pv2-muted\"></i>\r\n }\r\n </div>\r\n <div class=\"pv2-fullname\">{{detail.nodeName}}</div>\r\n <div class=\"pv2-id\">\r\n Id <code #idText>{{detail.nodeId}}</code>\r\n <button type=\"button\" class=\"pv2-link\" (click)=\"copyId(idText)\">{{(copied ? 'Copiato' : 'Copia') | localize: lc}}</button>\r\n </div>\r\n\r\n <div class=\"pv2-d-title\">{{'Percorso' | localize: lc}}</div>\r\n <div class=\"pv2-path\">\r\n @for (item of detail.fullWaitStack ?? []; track item.nodeId; let i = $index) {\r\n <button type=\"button\" class=\"pv2-path-item\" [style.padding-left.px]=\"i * 10\" [title]=\"item.nodeName\" (click)=\"select(item.nodeId)\">\r\n <span class=\"pv2-dot\" [style.background]=\"typeColor(item.eventTypeName, item.waitClassName)\"></span>{{item.nodeName}}\r\n </button>\r\n }\r\n <div class=\"pv2-path-item pv2-path-current\" [style.padding-left.px]=\"(detail.fullWaitStack?.length ?? 0) * 10\" [title]=\"detail.nodeName\">\r\n <span class=\"pv2-dot\" [style.background]=\"typeColor(detail.eventTypeName, detail.waitClassName)\"></span>{{detail.nodeName}}\r\n </div>\r\n </div>\r\n\r\n <div class=\"pv2-d-title\">{{(live ? \"Dall'avvio\" : 'Nella finestra') | localize: lc}}</div>\r\n <dl class=\"pv2-kv\">\r\n <dt>{{'Esecuzioni' | localize: lc}}</dt><dd>{{detail.executions | pv2fmt: 'int'}}</dd>\r\n <dt>{{'Tempo totale' | localize: lc}}</dt><dd>{{detail.elapsedTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Tempo proprio' | localize: lc}}</dt><dd>{{detail.elapsedTimeLessChildren | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Medio' | localize: lc}}</dt><dd>{{detail.avgTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Picco' | localize: lc}}</dt><dd>{{detail.peakTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Minimo' | localize: lc}}</dt><dd>{{detail.minTime ? (detail.minTime | pv2fmt: 'dur') : '\u2014'}}</dd>\r\n </dl>\r\n\r\n @if (live) {\r\n <div class=\"pv2-d-title\">{{\"Dall'ultimo snapshot\" | localize: lc}}, {{hhmm(detail.snapFrom)}}</div>\r\n <dl class=\"pv2-kv\">\r\n <dt>{{'Esecuzioni' | localize: lc}}</dt><dd>{{detail.snapDeltaExecutions | pv2fmt: 'int'}}</dd>\r\n <dt>{{'Tempo totale' | localize: lc}}</dt><dd>{{detail.snapDeltaElapsedTime | pv2fmt: 'dur'}}</dd>\r\n <dt>{{'Tempo proprio' | localize: lc}}</dt><dd>{{detail.snapDeltaElapsedTimeLessChildren | pv2fmt: 'dur'}}</dd>\r\n </dl>\r\n\r\n <div class=\"pv2-d-title\">{{'Adesso' | localize: lc}}</div>\r\n <dl class=\"pv2-kv\">\r\n <dt>{{'Thread dentro' | localize: lc}}</dt>\r\n <dd>\r\n @if (detail.activeThreads > 0) {\r\n <span class=\"pv2-active\"><span class=\"pv2-active-dot\"></span>{{detail.activeThreads}}</span>\r\n } @else {\r\n {{detail.activeThreads ?? '\u2014'}}\r\n }\r\n </dd>\r\n <dt>{{'Esecuzione pi\u00F9 lunga' | localize: lc}}</dt>\r\n <dd>\r\n @if (detail.activeThreads > 0) {\r\n {{detail.activeMaxTime | pv2fmt: 'dur'}}\r\n @if (isLongRunning(detail.eventTypeName, detail.waitClassName, detail.activeMaxTime)) {\r\n <span class=\"pv2-long\"><i class=\"fa fa-clock\"></i> {{'forse bloccata' | localize: lc}}</span>\r\n }\r\n } @else {\r\n \u2014\r\n }\r\n </dd>\r\n <dt>{{'Ultima uscita' | localize: lc}}</dt><dd>{{relative(detail.lastExitTime)}}</dd>\r\n </dl>\r\n }\r\n\r\n @if (typeParts.length > 0) {\r\n <div class=\"pv2-d-title\">{{'Tempo proprio del sottoalbero per tipo' | localize: lc}}</div>\r\n <div class=\"pv2-stack\">\r\n @for (part of typeParts; track part.type) {\r\n <div [style.width.%]=\"part.percentage\" [style.background]=\"part.color\" [title]=\"part.label + ' ' + (part.percentage | pv2fmt: 'pct')\"></div>\r\n }\r\n </div>\r\n <div class=\"pv2-legend\">\r\n @for (part of typeParts; track part.type) {\r\n <span class=\"pv2-legend-item\"><span class=\"pv2-swatch\" [style.background]=\"part.color\"></span>{{part.label}} <strong>{{part.percentage | pv2fmt: 'pct'}}</strong></span>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"pv2-d-title\">{{'Sessioni attive sul nodo e sui discendenti' | localize: lc}}</div>\r\n @if (chart) {\r\n <div class=\"pv2-chart\">\r\n <div class=\"pv2-chart-y\">\r\n <span>{{axisValue(chart.max)}}</span>\r\n <span>{{axisValue(chart.max / 2)}}</span>\r\n <span>0</span>\r\n </div>\r\n <div class=\"pv2-chart-plot\">\r\n <svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\r\n <line x1=\"0\" y1=\"50\" x2=\"100\" y2=\"50\" class=\"pv2-chart-grid\" vector-effect=\"non-scaling-stroke\" />\r\n <path [attr.d]=\"chart.area\" [attr.fill]=\"chart.color\" fill-opacity=\"0.25\" />\r\n <path [attr.d]=\"chart.line\" [attr.stroke]=\"chart.color\" fill=\"none\" stroke-width=\"1.5\" vector-effect=\"non-scaling-stroke\" />\r\n </svg>\r\n <span class=\"pv2-chart-last\" [style.left.%]=\"chart.lastX\" [style.top.%]=\"chart.lastY\" [style.background]=\"chart.color\"></span>\r\n </div>\r\n <span></span>\r\n <div class=\"pv2-chart-x\">\r\n <span>{{chart.start}}</span>\r\n <span>{{chart.middle}}</span>\r\n <span>{{chart.end}}</span>\r\n </div>\r\n </div>\r\n <div class=\"pv2-caption\">\r\n {{'Media' | localize: lc}} {{axisValue(chart.average)}} · {{'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}} · {{detail.numDescendants | pv2fmt: 'int'}} {{'discendenti' | localize: lc}}</span>\r\n <button type=\"button\" class=\"pv2-btn\" (click)=\"setRoot(detail.nodeId)\"><i class=\"fa fa-sitemap\"></i> {{'Apri come radice' | localize: lc}}</button>\r\n </div>\r\n }\r\n </aside>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: ["@charset \"UTF-8\";:host{display:block}:host ::ng-deep .pv2-noclear .p-select-clear-icon{display:none}.pv2-body{container-type:inline-size}.pv2-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 22px;padding-top:6px}.pv2-cluster{display:flex;flex-wrap:nowrap;align-items:center;gap:10px}.pv2-cluster-end{margin-left:auto}.pv2-field{width:190px}.pv2-field-wide{width:290px}.pv2-field-narrow{width:140px}.pv2-field-search{flex:1 1 220px;min-width:200px;max-width:340px}.pv2-toggle{display:flex;align-items:center;gap:8px;height:39px;white-space:nowrap}.pv2-pointer{cursor:pointer}.pv2-group{position:relative;display:inline-flex}.pv2-group.pv2-disabled{opacity:.5}.pv2-group-label{position:absolute;top:-8px;left:8px;z-index:1;padding:0 4px;background:var(--p-content-background, #fff);color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.pv2-seg{display:inline-flex;height:39px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);overflow:hidden;background:var(--p-content-background, #fff)}.pv2-seg button{display:inline-flex;align-items:center;gap:6px;padding:0 12px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.pv2-seg button:first-child{border-left:0}.pv2-seg button:hover:not(.pv2-on):not(:disabled){background:var(--p-content-hover-background, #f8fafc)}.pv2-seg button:disabled{cursor:default}.pv2-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.pv2-seg button.pv2-on{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca);font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.pv2-live-dot{width:8px;height:8px;border-radius:50%;background:#16a34a}.pv2-window{display:flex;flex-wrap:wrap;align-items:center;row-gap:4px;margin-top:12px;padding:7px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155);font-size:13px}.pv2-window>span+span:before{content:\"\\b7\";margin:0 10px;color:var(--p-text-muted-color, #64748b)}.pv2-warn-text{color:#92400e}.pv2-main{display:grid;grid-template-columns:minmax(0,1fr) 380px;gap:14px;align-items:start;margin-top:14px}@container (max-width: 1000px){.pv2-main{grid-template-columns:minmax(0,1fr)}.pv2-detail{position:static;max-height:none}}.pv2-panel{padding:12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0}.pv2-view-bar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 10px;padding-top:6px;margin-bottom:10px}.pv2-crumbs{display:flex;flex-wrap:wrap;align-items:center;gap:2px 4px;margin-bottom:8px;font-size:12px}.pv2-crumb{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:2px 6px;border:0;border-radius:4px;background:transparent;color:var(--p-primary-color, #6366f1);cursor:pointer}.pv2-crumb:hover{background:var(--p-highlight-background, #eef2ff)}.pv2-crumb-current{color:var(--p-text-color, #334155);font-weight:500;cursor:default}.pv2-crumb-current:hover{background:transparent}.pv2-crumb-sep{color:var(--p-text-muted-color, #64748b)}.pv2-alert{position:relative;margin-bottom:8px;padding:6px 30px 6px 10px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);font-size:13px}.pv2-alert.pv2-alert-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.pv2-alert.pv2-alert-error{border-color:#fca5a5;background:#fef2f2;color:#991b1b}.pv2-alert-close{position:absolute;top:3px;right:6px;border:0;background:transparent;color:inherit;font-size:16px;cursor:pointer}.pv2-table-wrap{max-height:68vh;overflow:auto;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px)}.pv2-table-wrap:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:1px}.pv2-table{width:100%;min-width:740px;border-collapse:separate;border-spacing:0;font-size:13px;color:var(--p-text-color, #334155)}.pv2-table th{position:sticky;top:0;z-index:1;padding:7px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-muted-color, #64748b);font-size:12px;font-weight:500;white-space:nowrap;text-align:left}.pv2-table td{padding:4px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);white-space:nowrap;vertical-align:middle}.pv2-col-node{width:100%;max-width:0}.pv2-num{text-align:right!important;font-variant-numeric:tabular-nums}.pv2-row{cursor:pointer}.pv2-row:hover td{background:var(--p-content-hover-background, #f8fafc)}.pv2-row.pv2-selected td{background:var(--p-highlight-background, #eef2ff)}.pv2-row.pv2-idle td{color:var(--p-text-muted-color, #64748b)}.pv2-row.pv2-idle td .pv2-name{opacity:.75}.pv2-node{display:flex;align-items:center;gap:6px;min-width:0}.pv2-caret{flex:none;width:18px;height:20px;padding:0;border:0;background:transparent;color:var(--p-text-muted-color, #64748b);text-align:center;cursor:pointer}.pv2-type{flex:none;display:inline-flex;align-items:center;gap:5px;padding:0 6px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:10px;color:var(--p-text-muted-color, #64748b);font-size:11px;line-height:18px;white-space:nowrap}.pv2-dot{flex:none;display:inline-block;width:8px;height:8px;border-radius:50%}.pv2-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pv2-name mark{padding:0 1px;border-radius:2px;background:#fde68a;color:#1f2937}.pv2-perc{width:1%}.pv2-perc-cell{display:flex;align-items:center;gap:8px}.pv2-bar{flex:none;width:64px;height:7px;border-radius:2px;overflow:hidden;background:var(--p-content-hover-background, #f8fafc);box-shadow:inset 0 0 0 1px var(--p-content-border-color, #e2e8f0)}.pv2-bar>div{height:100%}.pv2-threads>*+*{margin-left:8px}.pv2-active{display:inline-flex;align-items:center;gap:5px;font-weight:500}.pv2-active-dot{width:7px;height:7px;border-radius:50%;background:#16a34a;box-shadow:0 0 0 3px #16a34a2e}.pv2-long{display:inline-flex;align-items:center;gap:4px;padding:0 6px;border:1px solid #fcd34d;border-radius:10px;background:#fffbeb;color:#92400e;font-size:11px;line-height:18px;white-space:nowrap}.pv2-service td{padding:5px 10px;color:var(--p-text-muted-color, #64748b);font-size:12px;background:var(--p-content-background, #fff)}.pv2-sub td{padding:8px 10px 10px 34px;background:var(--p-content-hover-background, #f8fafc);white-space:normal}.pv2-sub-title{margin-bottom:4px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-sub-item{display:flex;align-items:center;gap:10px;padding:2px 0}.pv2-sub-item code{font-size:12px}.pv2-link{margin-left:6px;padding:0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;cursor:pointer}.pv2-link:hover{text-decoration:underline}.pv2-empty{padding:28px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.pv2-footer{margin-top:8px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-muted{color:var(--p-text-muted-color, #64748b)}.pv2-small{font-size:12px}.pv2-detail{position:sticky;top:10px;max-height:calc(100vh - 20px);overflow:auto;font-size:13px;color:var(--p-text-color, #334155);transition:opacity .15s}.pv2-detail.pv2-stale{opacity:.55}.pv2-placeholder{padding:36px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.pv2-error-text{color:#dc2626}.pv2-d-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.pv2-fullname{max-height:140px;overflow:auto;padding:8px 10px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);font-family:ui-monospace,Cascadia Mono,Consolas,monospace;font-size:12px;white-space:pre-wrap;word-break:break-word}.pv2-id{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-id code{font-size:12px}.pv2-d-title{margin:16px 0 6px;padding-top:10px;border-top:1px solid var(--p-content-border-color, #e2e8f0);color:var(--p-text-muted-color, #64748b);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.pv2-path{display:flex;flex-direction:column}.pv2-path-item{display:flex;align-items:center;gap:6px;max-width:100%;padding:2px 0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.pv2-path-item:hover{text-decoration:underline}.pv2-path-current{color:var(--p-text-color, #334155);font-weight:600;cursor:default}.pv2-path-current:hover{text-decoration:none}.pv2-kv{display:grid;grid-template-columns:auto 1fr;gap:3px 14px;margin:0}.pv2-kv dt{color:var(--p-text-muted-color, #64748b);font-weight:400}.pv2-kv dd{margin:0;text-align:right;font-variant-numeric:tabular-nums}.pv2-stack{display:flex;height:12px;border-radius:3px;overflow:hidden;background:var(--p-content-hover-background, #f8fafc)}.pv2-legend{display:flex;flex-wrap:wrap;gap:4px 12px;margin-top:6px;font-size:12px}.pv2-legend-item{display:inline-flex;align-items:center;gap:5px}.pv2-swatch{width:10px;height:10px;border-radius:2px}.pv2-chart{display:grid;grid-template-columns:auto 1fr;gap:2px 6px}.pv2-chart-y{display:flex;flex-direction:column;justify-content:space-between;height:110px;color:var(--p-text-muted-color, #64748b);font-size:11px;text-align:right;line-height:1}.pv2-chart-plot{position:relative;height:110px;border-left:1px solid var(--p-content-border-color, #e2e8f0);border-bottom:1px solid var(--p-content-border-color, #e2e8f0)}.pv2-chart-plot svg{display:block;width:100%;height:100%}.pv2-chart-grid{stroke:var(--p-content-border-color, #e2e8f0);stroke-dasharray:3 3}.pv2-chart-last{position:absolute;width:8px;height:8px;border:2px solid var(--p-content-background, #fff);border-radius:50%;transform:translate(-50%,-50%)}.pv2-chart-x{display:flex;justify-content:space-between;color:var(--p-text-muted-color, #64748b);font-size:11px}.pv2-caption{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.pv2-mini{width:100%;font-size:12px}.pv2-mini th{padding:3px 6px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);color:var(--p-text-muted-color, #64748b);font-weight:500}.pv2-mini td{padding:3px 6px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0)}.pv2-ellipsis{display:inline-block;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.pv2-d-foot{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;margin-top:16px;padding-top:10px;border-top:1px solid var(--p-content-border-color, #e2e8f0)}.pv2-btn{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:13px;cursor:pointer}.pv2-btn:hover{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}\n"] }]
|
|
1977
|
+
}], ctorParameters: () => [{ type: i1$1.DateService }, { type: i1.LocalizationService }, { type: i3.PreferencesService }, { type: i1$1.InterComService }, { type: i4.ActivatedRoute }, { type: i4.Router }, { type: i5.Location }, { type: AdminModuleBaseService }, { type: undefined, decorators: [{
|
|
1978
|
+
type: Inject,
|
|
1979
|
+
args: [ADM_MODULE_PATH]
|
|
1980
|
+
}] }, { type: undefined, decorators: [{
|
|
1981
|
+
type: Optional
|
|
1982
|
+
}, {
|
|
1983
|
+
type: Inject,
|
|
1984
|
+
args: [ADM_WORKLOAD_V2]
|
|
1985
|
+
}] }] });
|
|
1986
|
+
|
|
1987
|
+
// Workload report v2 (specifica "Workload report"): richieste e risposte come le vuole e le manda il backend (camelCase).
|
|
1988
|
+
// Le date delle risposte sono UTC "dd/MM/yyyy HH:mm:ss" senza fuso
|
|
1989
|
+
// Senza snapshot il report è live; senza istanze e con global è dell'intero cluster
|
|
1990
|
+
class WorkloadReportV2Request {
|
|
1991
|
+
}
|
|
1992
|
+
// Snapshot che terminano fra from e to (ISO UTC)
|
|
1993
|
+
class WorkloadSnapshotsRequest {
|
|
1994
|
+
}
|
|
1995
|
+
class WorkloadSnapshotInstance {
|
|
1996
|
+
}
|
|
1997
|
+
class WorkloadSnapshot {
|
|
1998
|
+
}
|
|
1999
|
+
class WorkloadSnapshotList {
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
class AdmWorkloadV2Loc extends LocalizationService {
|
|
2003
|
+
constructor(injector) {
|
|
2004
|
+
super(injector);
|
|
2005
|
+
super.set("it->en", "Workload report", ["Workload report"]);
|
|
2006
|
+
super.set("it->en", "Errore", ["Error"]);
|
|
2007
|
+
// Barra dei comandi e avvisi
|
|
2008
|
+
super.set("it->en", "Tipo", ["Type"]);
|
|
2009
|
+
super.set("it->en", "Fra snapshot", ["Snapshot range"]);
|
|
2010
|
+
super.set("it->en", "Istanze", ["Instances"]);
|
|
2011
|
+
super.set("it->en", "Tutto il cluster", ["Whole cluster"]);
|
|
2012
|
+
super.set("it->en", "tutto il cluster", ["whole cluster"]);
|
|
2013
|
+
super.set("it->en", "istanze", ["instances"]);
|
|
2014
|
+
super.set("it->en", "istanza", ["instance"]);
|
|
2015
|
+
super.set("it->en", "Genera report", ["Generate report"]);
|
|
2016
|
+
super.set("it->en", "Annulla", ["Cancel"]);
|
|
2017
|
+
super.set("it->en", "Scegli il primo e l'ultimo snapshot", ["Choose the first and last snapshot"]);
|
|
2018
|
+
super.set("it->en", "Lo storico non è attivo su questo ambiente: il report si può produrre solo live.", ["History is not active on this environment: only a live report can be produced."]);
|
|
2019
|
+
super.set("it->en", "Lo snapshot {id} segue un riavvio di {istanza}: counter e meter della finestra non sono attendibili.", ["Snapshot {id} follows a restart of {istanza}: counters and meters in the window are not reliable."]);
|
|
2020
|
+
super.set("it->en", "La finestra supera 24 ore: l'attività campionata ne copre solo le ultime 24.", ["The window is longer than 24 hours: sampled activity only covers the last 24."]);
|
|
2021
|
+
// Snapshot
|
|
2022
|
+
super.set("it->en", "Snapshot", ["Snapshot"]);
|
|
2023
|
+
super.set("it->en", "Ultime 24 ore", ["Last 24 hours"]);
|
|
2024
|
+
super.set("it->en", "Ultimi 7 giorni", ["Last 7 days"]);
|
|
2025
|
+
super.set("it->en", "Tutto", ["All"]);
|
|
2026
|
+
super.set("it->en", "Caricamento…", ["Loading…"]);
|
|
2027
|
+
super.set("it->en", "Dalle", ["From"]);
|
|
2028
|
+
super.set("it->en", "alle", ["to"]);
|
|
2029
|
+
super.set("it->en", "dalle", ["from"]);
|
|
2030
|
+
super.set("it->en", "scegli l'ultimo snapshot", ["choose the last snapshot"]);
|
|
2031
|
+
super.set("it->en", "Azzera", ["Clear"]);
|
|
2032
|
+
super.set("it->en", "Un clic sceglie il primo snapshot, un secondo clic l'ultimo.", ["One click chooses the first snapshot, a second click the last."]);
|
|
2033
|
+
super.set("it->en", "Inizio", ["Start"]);
|
|
2034
|
+
super.set("it->en", "Fine", ["End"]);
|
|
2035
|
+
super.set("it->en", "Durata", ["Duration"]);
|
|
2036
|
+
super.set("it->en", "Manuale", ["Manual"]);
|
|
2037
|
+
super.set("it->en", "Riavvio", ["Restart"]);
|
|
2038
|
+
super.set("it->en", "Un'istanza è ripartita: counter e meter non sono attendibili", ["An instance restarted: counters and meters are not reliable"]);
|
|
2039
|
+
super.set("it->en", "Nessuno snapshot nel periodo scelto: prova con un periodo più lungo.", ["No snapshots in the chosen period: try a longer one."]);
|
|
2040
|
+
super.set("it->en", "Orari in ora locale", ["Times in local time"]);
|
|
2041
|
+
super.set("it->en", "g", ["d"]);
|
|
2042
|
+
// Report
|
|
2043
|
+
super.set("it->en", "Generazione del report…", ["Generating the report…"]);
|
|
2044
|
+
super.set("it->en", "Il report non si può produrre", ["The report cannot be produced"]);
|
|
2045
|
+
super.set("it->en", "generato alle", ["generated at"]);
|
|
2046
|
+
super.set("it->en", "Vai a…", ["Go to…"]);
|
|
2047
|
+
super.set("it->en", "Scarica", ["Download"]);
|
|
2048
|
+
super.set("it->en", "Apri in una scheda", ["Open in a tab"]);
|
|
2049
|
+
super.set("it->en", "Gli orari del report sono in UTC.", ["Report times are in UTC."]);
|
|
2050
|
+
super.set("it->en", "Scegli tipo e istanze, poi genera il report.", ["Choose type and instances, then generate the report."]);
|
|
2051
|
+
}
|
|
2052
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmWorkloadV2Loc, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2053
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmWorkloadV2Loc }); }
|
|
2054
|
+
}
|
|
2055
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmWorkloadV2Loc, decorators: [{
|
|
2056
|
+
type: Injectable
|
|
2057
|
+
}], ctorParameters: () => [{ type: i0.Injector }] });
|
|
2058
|
+
|
|
2059
|
+
// Angular
|
|
2060
|
+
const STORAGE_KEY$2 = "HotSearchWorkloadV2";
|
|
2061
|
+
const HOUR_MS = 3600 * 1000;
|
|
2062
|
+
// Sezioni principali del report con le ancore stabili della specifica: nel salto compaiono solo quelle presenti nel documento.
|
|
2063
|
+
// I titoli sono in inglese come nel report
|
|
2064
|
+
const SECTIONS = [
|
|
2065
|
+
["summary", "Report Summary"], ["loadprofile", "Load profile"], ["efficiency", "Instance efficiency"], ["topevents", "Top 10 events by time"],
|
|
2066
|
+
["waitclasses", "Wait classes"], ["actsumtype", "Activity summary by event type"], ["actsumclass", "Activity summary by wait class"],
|
|
2067
|
+
["aas", "Average active sessions"], ["topsess", "Top sessions"], ["topnodes", "Top profiler nodes"], ["topapis", "Top APIs"], ["topjobs", "Top jobs"],
|
|
2068
|
+
["toptenants", "Top tenants"], ["protime", "Profiler nodes by time"], ["protimelc", "Profiler nodes by self time"], ["procalls", "Profiler nodes by calls"],
|
|
2069
|
+
["wprotime", "Profiler waits"], ["iprotime", "Profiler idle"], ["sqltime", "SQL by elapsed time"], ["sqlexec", "SQL by executions"], ["sqlpeak", "SQL by peak"],
|
|
2070
|
+
["sqltext", "Complete list of SQL text"], ["netstat", "Network"], ["iostat", "IO"], ["contwaits", "Waits on latches and locks"], ["latch", "Latch activity"],
|
|
2071
|
+
["mgauges", "Metrics: gauges"], ["mcounters", "Metrics: counters"], ["mmeters", "Metrics: meters"], ["mhistograms", "Metrics: histograms"],
|
|
2072
|
+
["mtimers", "Metrics: timers"], ["parameters", "Modified parameters"]
|
|
2073
|
+
];
|
|
2074
|
+
// Ricordati fra una visita e l'altra (W12)
|
|
2075
|
+
class WorkloadV2Settings {
|
|
2076
|
+
}
|
|
2077
|
+
// Il report mostrato, con i parametri con cui è stato chiesto: resta quello anche se poi si cambiano i comandi
|
|
2078
|
+
class WorkloadV2Report {
|
|
2079
|
+
}
|
|
2080
|
+
class AdmWorkloadV2Component {
|
|
2081
|
+
constructor(lc, localStorage, intercom, route, sanitizer, adms) {
|
|
2082
|
+
this.lc = lc;
|
|
2083
|
+
this.localStorage = localStorage;
|
|
2084
|
+
this.intercom = intercom;
|
|
2085
|
+
this.route = route;
|
|
2086
|
+
this.sanitizer = sanitizer;
|
|
2087
|
+
this.adms = adms;
|
|
2088
|
+
// Comandi
|
|
2089
|
+
this.live = true;
|
|
2090
|
+
// Vuoto = tutto il cluster
|
|
2091
|
+
this.instances = [];
|
|
2092
|
+
// Giorni dell'elenco degli snapshot; "0" = tutto
|
|
2093
|
+
this.period = "7";
|
|
2094
|
+
// Elenco degli snapshot
|
|
2095
|
+
this.snapshots = [];
|
|
2096
|
+
this.historyAvailable = true;
|
|
2097
|
+
this.snapshotsLoading = false;
|
|
2098
|
+
this.snapshotsLoaded = false;
|
|
2099
|
+
// Primo e ultimo snapshot scelti, compresi (selTo null dopo il primo clic)
|
|
2100
|
+
this.selFrom = null;
|
|
2101
|
+
this.selTo = null;
|
|
2102
|
+
this.warnings = [];
|
|
2103
|
+
// Report
|
|
2104
|
+
this.generating = false;
|
|
2105
|
+
this.section = "";
|
|
2106
|
+
this.Instances = [];
|
|
2107
|
+
this.Periods = [];
|
|
2108
|
+
this.snapshotsGeneration = 0;
|
|
2109
|
+
this.be = route.snapshot.params["be"] || "";
|
|
2110
|
+
this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
|
|
2111
|
+
}
|
|
2112
|
+
async ngOnInit() {
|
|
2113
|
+
this.Periods = [
|
|
2114
|
+
new KeyValue("1", this.lc.loc("Ultime 24 ore")),
|
|
2115
|
+
new KeyValue("7", this.lc.loc("Ultimi 7 giorni")),
|
|
2116
|
+
new KeyValue("0", this.lc.loc("Tutto"))
|
|
2117
|
+
];
|
|
2118
|
+
let saved = this.route.snapshot.queryParams["ignoreStorage"] ? null : await firstValueFrom(this.localStorage.getItem(STORAGE_KEY$2), { defaultValue: null });
|
|
2119
|
+
if (saved) {
|
|
2120
|
+
this.live = saved.live ?? this.live;
|
|
2121
|
+
this.instances = saved.instances ?? this.instances;
|
|
2122
|
+
this.period = saved.period ?? this.period;
|
|
2123
|
+
}
|
|
2124
|
+
this.adms.getInstances(this.be).then(t => this.Instances = t ?? []);
|
|
2125
|
+
this.loadSnapshots();
|
|
2126
|
+
}
|
|
2127
|
+
ngOnDestroy() {
|
|
2128
|
+
this.reportSub?.unsubscribe();
|
|
2129
|
+
}
|
|
2130
|
+
// #region Comandi
|
|
2131
|
+
get clusterLabel() {
|
|
2132
|
+
return this.Instances.length ? `${this.lc.loc("Tutto il cluster")} (${this.Instances.length} ${this.Instances.length == 1 ? this.lc.loc("istanza") : this.lc.loc("istanze")})` : this.lc.loc("Tutto il cluster");
|
|
2133
|
+
}
|
|
2134
|
+
setLive(live) {
|
|
2135
|
+
if (!live && !this.historyAvailable)
|
|
2136
|
+
return;
|
|
2137
|
+
this.live = live;
|
|
2138
|
+
this.updateWarnings();
|
|
2139
|
+
this.saveSettings();
|
|
2140
|
+
}
|
|
2141
|
+
// W6: cambiando istanze l'elenco degli snapshot si ricarica
|
|
2142
|
+
instancesChanged(ids) {
|
|
2143
|
+
this.instances = ids ?? [];
|
|
2144
|
+
this.saveSettings();
|
|
2145
|
+
this.loadSnapshots();
|
|
2146
|
+
}
|
|
2147
|
+
setPeriod(id) {
|
|
2148
|
+
this.period = id;
|
|
2149
|
+
this.saveSettings();
|
|
2150
|
+
this.loadSnapshots();
|
|
2151
|
+
}
|
|
2152
|
+
// In "Fra snapshot" servono il primo e l'ultimo
|
|
2153
|
+
get canGenerate() {
|
|
2154
|
+
return this.live || (this.selFrom != null && this.selTo != null);
|
|
2155
|
+
}
|
|
2156
|
+
// #endregion
|
|
2157
|
+
// #region Snapshot
|
|
2158
|
+
async loadSnapshots() {
|
|
2159
|
+
let generation = ++this.snapshotsGeneration;
|
|
2160
|
+
let req = new WorkloadSnapshotsRequest();
|
|
2161
|
+
if (+this.period > 0)
|
|
2162
|
+
req.from = new Date(Date.now() - +this.period * 24 * HOUR_MS).toISOString();
|
|
2163
|
+
if (this.instances.length)
|
|
2164
|
+
req.instanceIds = this.instances;
|
|
2165
|
+
this.snapshotsLoading = true;
|
|
2166
|
+
this.snapshotsError = null;
|
|
2167
|
+
try {
|
|
2168
|
+
let res = await this.adms.getWorkloadSnapshots(this.be, req);
|
|
2169
|
+
if (generation != this.snapshotsGeneration)
|
|
2170
|
+
return;
|
|
2171
|
+
// Senza risposta (per esempio DefaultOnFail) vale come storico spento
|
|
2172
|
+
this.historyAvailable = res?.historyAvailable ?? false;
|
|
2173
|
+
this.snapshots = res?.snapshots ?? [];
|
|
2174
|
+
this.snapshotsLoaded = true;
|
|
2175
|
+
if (!this.historyAvailable)
|
|
2176
|
+
this.live = true;
|
|
2177
|
+
// Se gli snapshot scelti non ci sono più, la scelta si azzera (W6)
|
|
2178
|
+
let ids = new Set(this.snapshots.map(t => t.snapId));
|
|
2179
|
+
if ((this.selFrom != null && !ids.has(this.selFrom)) || (this.selTo != null && !ids.has(this.selTo)))
|
|
2180
|
+
this.clearSelection();
|
|
2181
|
+
this.updateWarnings();
|
|
2182
|
+
}
|
|
2183
|
+
catch (err) {
|
|
2184
|
+
if (generation == this.snapshotsGeneration)
|
|
2185
|
+
this.snapshotsError = `${this.lc.loc("Errore")} ${err?.status ?? ""}: ${this.adms.errorText(err)}`;
|
|
2186
|
+
}
|
|
2187
|
+
finally {
|
|
2188
|
+
if (generation == this.snapshotsGeneration)
|
|
2189
|
+
this.snapshotsLoading = false;
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
// Un clic fissa il primo snapshot, un secondo l'ultimo (in qualsiasi ordine); un terzo ricomincia
|
|
2193
|
+
pick(snapshot) {
|
|
2194
|
+
if (this.selFrom == null || this.selTo != null) {
|
|
2195
|
+
this.selFrom = snapshot.snapId;
|
|
2196
|
+
this.selTo = null;
|
|
2197
|
+
}
|
|
2198
|
+
else {
|
|
2199
|
+
let first = this.selFrom;
|
|
2200
|
+
this.selFrom = Math.min(first, snapshot.snapId);
|
|
2201
|
+
this.selTo = Math.max(first, snapshot.snapId);
|
|
2202
|
+
}
|
|
2203
|
+
this.updateWarnings();
|
|
2204
|
+
}
|
|
2205
|
+
clearSelection() {
|
|
2206
|
+
this.selFrom = null;
|
|
2207
|
+
this.selTo = null;
|
|
2208
|
+
this.updateWarnings();
|
|
2209
|
+
}
|
|
2210
|
+
inRange(snapshot) {
|
|
2211
|
+
if (this.selFrom == null)
|
|
2212
|
+
return false;
|
|
2213
|
+
return this.selTo == null ? snapshot.snapId == this.selFrom : snapshot.snapId >= this.selFrom && snapshot.snapId <= this.selTo;
|
|
2214
|
+
}
|
|
2215
|
+
isEdge(snapshot) {
|
|
2216
|
+
return snapshot.snapId == this.selFrom || snapshot.snapId == this.selTo;
|
|
2217
|
+
}
|
|
2218
|
+
// Finestra scelta: dall'inizio del primo snapshot alla fine dell'ultimo (ognuno copre l'intervallo dal precedente)
|
|
2219
|
+
get selectionWindow() {
|
|
2220
|
+
let first = this.snapshots.find(t => t.snapId == this.selFrom);
|
|
2221
|
+
let last = this.snapshots.find(t => t.snapId == (this.selTo ?? this.selFrom));
|
|
2222
|
+
return first && last ? { from: this.parseUtc(first.beginIntervalTime), to: this.parseUtc(last.endIntervalTime) } : null;
|
|
2223
|
+
}
|
|
2224
|
+
instancesTitle(snapshot) {
|
|
2225
|
+
return (snapshot.instances ?? []).map(t => t.instanceId).join("\n");
|
|
2226
|
+
}
|
|
2227
|
+
// W4: avvisi sulla finestra prima di generare, non bloccanti
|
|
2228
|
+
updateWarnings() {
|
|
2229
|
+
let warnings = [];
|
|
2230
|
+
if (!this.live && this.selFrom != null && this.selTo != null) {
|
|
2231
|
+
this.snapshots.forEach((s, i) => {
|
|
2232
|
+
if (s.restart && s.snapId >= this.selFrom && s.snapId <= this.selTo)
|
|
2233
|
+
for (let instance of this.restartedInstances(i))
|
|
2234
|
+
warnings.push(this.lc.loc("Lo snapshot {id} segue un riavvio di {istanza}: counter e meter della finestra non sono attendibili.").replace("{id}", String(s.snapId)).replace("{istanza}", instance));
|
|
2235
|
+
});
|
|
2236
|
+
let window = this.selectionWindow;
|
|
2237
|
+
if (window && window.to.getTime() - window.from.getTime() > 24 * HOUR_MS)
|
|
2238
|
+
warnings.push(this.lc.loc("La finestra supera 24 ore: l'attività campionata ne copre solo le ultime 24."));
|
|
2239
|
+
}
|
|
2240
|
+
this.warnings = warnings;
|
|
2241
|
+
}
|
|
2242
|
+
// L'istanza ripartita è quella con un avvio diverso dallo snapshot precedente (il successivo nell'elenco, che va dal più recente)
|
|
2243
|
+
restartedInstances(index) {
|
|
2244
|
+
let snapshot = this.snapshots[index];
|
|
2245
|
+
let previous = this.snapshots[index + 1];
|
|
2246
|
+
let ids = (snapshot.instances ?? []).filter(t => {
|
|
2247
|
+
let before = previous?.instances?.find(k => k.instanceId == t.instanceId);
|
|
2248
|
+
return before ? before.startupTime != t.startupTime : this.parseUtc(t.startupTime) > this.parseUtc(snapshot.beginIntervalTime);
|
|
2249
|
+
}).map(t => t.instanceId);
|
|
2250
|
+
return ids.length ? ids : (snapshot.instances ?? []).map(t => t.instanceId);
|
|
2251
|
+
}
|
|
2252
|
+
// #endregion
|
|
2253
|
+
// #region Report
|
|
2254
|
+
// Il report si chiede solo su comando (W7); durante la richiesta lo stesso comando la annulla
|
|
2255
|
+
generateOrCancel() {
|
|
2256
|
+
if (this.generating)
|
|
2257
|
+
this.cancel();
|
|
2258
|
+
else
|
|
2259
|
+
this.generate();
|
|
2260
|
+
}
|
|
2261
|
+
generate() {
|
|
2262
|
+
if (!this.canGenerate)
|
|
2263
|
+
return;
|
|
2264
|
+
let req = new WorkloadReportV2Request();
|
|
2265
|
+
// Mai global=false senza istanze: dietro un bilanciatore darebbe il report dell'istanza che risponde (W2)
|
|
2266
|
+
if (this.instances.length)
|
|
2267
|
+
req.instanceIds = [...this.instances];
|
|
2268
|
+
else
|
|
2269
|
+
req.global = true;
|
|
2270
|
+
let window = this.live ? null : this.selectionWindow;
|
|
2271
|
+
if (!this.live) {
|
|
2272
|
+
req.snapFrom = this.selFrom;
|
|
2273
|
+
req.snapTo = this.selTo;
|
|
2274
|
+
}
|
|
2275
|
+
this.generating = true;
|
|
2276
|
+
this.reportError = null;
|
|
2277
|
+
this.reportSub?.unsubscribe();
|
|
2278
|
+
this.reportSub = this.adms.getWorkloadReportV2(this.be, req).subscribe({
|
|
2279
|
+
next: html => {
|
|
2280
|
+
this.generating = false;
|
|
2281
|
+
this.showReport(html ?? "", req, window);
|
|
2282
|
+
},
|
|
2283
|
+
error: err => {
|
|
2284
|
+
this.generating = false;
|
|
2285
|
+
// La sessione scaduta la gestisce il servizio
|
|
2286
|
+
if (err?.status != 401)
|
|
2287
|
+
this.reportError = this.adms.errorText(err) || `${this.lc.loc("Errore")} ${err?.status ?? ""}`;
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
}
|
|
2291
|
+
cancel() {
|
|
2292
|
+
this.reportSub?.unsubscribe();
|
|
2293
|
+
this.reportSub = null;
|
|
2294
|
+
this.generating = false;
|
|
2295
|
+
}
|
|
2296
|
+
showReport(html, req, window) {
|
|
2297
|
+
// Le sezioni del salto sono quelle che il documento ha davvero
|
|
2298
|
+
let parsed = new DOMParser().parseFromString(html, "text/html");
|
|
2299
|
+
let sections = SECTIONS.filter(t => parsed.getElementById(t[0])).map(t => new KeyValue(t[0], t[1]));
|
|
2300
|
+
this.report = {
|
|
2301
|
+
html: html,
|
|
2302
|
+
// Il report va in un iframe con sandbox senza script: lo stile e le ancore restano dentro il riquadro.
|
|
2303
|
+
// Il sanificatore di Angular toglierebbe gli stili inline del documento, per questo lo si marca come sicuro
|
|
2304
|
+
doc: this.sanitizer.bypassSecurityTrustHtml(html),
|
|
2305
|
+
live: req.snapFrom == null,
|
|
2306
|
+
instances: req.instanceIds ?? [],
|
|
2307
|
+
snapFrom: req.snapFrom,
|
|
2308
|
+
snapTo: req.snapTo,
|
|
2309
|
+
windowFrom: window?.from,
|
|
2310
|
+
windowTo: window?.to,
|
|
2311
|
+
generatedAt: new Date(),
|
|
2312
|
+
size: new Blob([html]).size,
|
|
2313
|
+
sections: sections
|
|
2314
|
+
};
|
|
2315
|
+
this.section = "";
|
|
2316
|
+
}
|
|
2317
|
+
// W11: porta il riquadro all'ancora scelta, senza far scorrere la pagina
|
|
2318
|
+
goToSection(anchor) {
|
|
2319
|
+
this.section = anchor;
|
|
2320
|
+
let doc = this.reportFrame?.nativeElement?.contentDocument;
|
|
2321
|
+
let target = doc?.getElementById(anchor);
|
|
2322
|
+
if (target)
|
|
2323
|
+
doc.defaultView.scrollTo({ top: target.getBoundingClientRect().top + doc.defaultView.scrollY - 8, behavior: "smooth" });
|
|
2324
|
+
}
|
|
2325
|
+
// W10: l'HTML in un file UTF-8 con un nome che dice cosa contiene
|
|
2326
|
+
download() {
|
|
2327
|
+
let url = URL.createObjectURL(this.reportBlob());
|
|
2328
|
+
let link = document.createElement("a");
|
|
2329
|
+
link.href = url;
|
|
2330
|
+
link.download = this.fileName();
|
|
2331
|
+
document.body.appendChild(link);
|
|
2332
|
+
link.click();
|
|
2333
|
+
link.remove();
|
|
2334
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
2335
|
+
}
|
|
2336
|
+
openInTab() {
|
|
2337
|
+
let url = URL.createObjectURL(this.reportBlob());
|
|
2338
|
+
window.open(url, "_blank");
|
|
2339
|
+
// La scheda nuova ha già caricato il documento quando l'URL viene revocato
|
|
2340
|
+
setTimeout(() => URL.revokeObjectURL(url), 60000);
|
|
2341
|
+
}
|
|
2342
|
+
reportBlob() {
|
|
2343
|
+
return new Blob([this.report.html], { type: "text/html;charset=utf-8" });
|
|
2344
|
+
}
|
|
2345
|
+
// workload-report_{live|snap-1201-1206}_{cluster|istanze}_{yyyyMMdd-HHmm}Z.html
|
|
2346
|
+
fileName() {
|
|
2347
|
+
let r = this.report;
|
|
2348
|
+
let kind = r.live ? "live" : `snap-${r.snapFrom}-${r.snapTo}`;
|
|
2349
|
+
let scope = r.instances.length ? r.instances.map(t => t.replace(/[^\w.-]+/g, "-")).join("+") : "cluster";
|
|
2350
|
+
return `workload-report_${kind}_${scope}_${this.utcStamp(r.generatedAt)}Z.html`;
|
|
2351
|
+
}
|
|
2352
|
+
// yyyyMMdd-HHmm in UTC (il plugin utc di dayjs non è detto che ci sia)
|
|
2353
|
+
utcStamp(date) {
|
|
2354
|
+
let pad = (n) => String(n).padStart(2, "0");
|
|
2355
|
+
return `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(date.getUTCHours())}${pad(date.getUTCMinutes())}`;
|
|
2356
|
+
}
|
|
2357
|
+
// #endregion
|
|
2358
|
+
// #region Presentazione
|
|
2359
|
+
// Le date delle risposte sono UTC "dd/MM/yyyy HH:mm:ss" senza fuso: si leggono come UTC e si mostrano in ora locale (W16)
|
|
2360
|
+
parseUtc(value) {
|
|
2361
|
+
let m = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})$/.exec(value ?? "");
|
|
2362
|
+
return m ? new Date(Date.UTC(+m[3], +m[2] - 1, +m[1], +m[4], +m[5], +m[6])) : value ? new Date(value) : null;
|
|
2363
|
+
}
|
|
2364
|
+
local(value) {
|
|
2365
|
+
let date = typeof value == "string" ? this.parseUtc(value) : value;
|
|
2366
|
+
return date ? dayjs(date).format("DD/MM HH:mm") : "—";
|
|
2367
|
+
}
|
|
2368
|
+
hhmm(date) {
|
|
2369
|
+
return date ? dayjs(date).format("HH:mm") : "—";
|
|
2370
|
+
}
|
|
2371
|
+
// Fuso dell'ora locale, per dire in che ora sono gli orari della pagina
|
|
2372
|
+
get zone() {
|
|
2373
|
+
return `UTC${dayjs().format("Z")}`;
|
|
2374
|
+
}
|
|
2375
|
+
span(from, to) {
|
|
2376
|
+
let a = typeof from == "string" ? this.parseUtc(from) : from;
|
|
2377
|
+
let b = typeof to == "string" ? this.parseUtc(to) : to;
|
|
2378
|
+
if (!a || !b)
|
|
2379
|
+
return "—";
|
|
2380
|
+
let minutes = Math.max(0, Math.round((b.getTime() - a.getTime()) / 60000));
|
|
2381
|
+
if (minutes < 60)
|
|
2382
|
+
return `${minutes} min`;
|
|
2383
|
+
let hours = Math.floor(minutes / 60);
|
|
2384
|
+
if (hours < 48)
|
|
2385
|
+
return minutes % 60 ? `${hours} h ${minutes % 60} min` : `${hours} h`;
|
|
2386
|
+
let days = Math.floor(hours / 24);
|
|
2387
|
+
return hours % 24 ? `${days} ${this.lc.loc("g")} ${hours % 24} h` : `${days} ${this.lc.loc("g")}`;
|
|
2388
|
+
}
|
|
2389
|
+
kilobytes(size) {
|
|
2390
|
+
return `${Math.max(1, Math.round(size / 1024))} kB`;
|
|
2391
|
+
}
|
|
2392
|
+
scopeLabel(instances) {
|
|
2393
|
+
return instances?.length ? instances.join(", ") : this.lc.loc("tutto il cluster");
|
|
2394
|
+
}
|
|
2395
|
+
saveSettings() {
|
|
2396
|
+
let settings = { live: this.live, instances: this.instances, period: this.period };
|
|
2397
|
+
this.localStorage.setItem(STORAGE_KEY$2, settings).subscribe();
|
|
2398
|
+
}
|
|
2399
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmWorkloadV2Component, deps: [{ token: i1.LocalizationService }, { token: i3.PreferencesService }, { token: i1$1.InterComService }, { token: i4.ActivatedRoute }, { token: i5$1.DomSanitizer }, { token: AdminModuleBaseService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2400
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: AdmWorkloadV2Component, isStandalone: false, selector: "workload_v2", providers: [{ provide: LocalizationService, useClass: AdmWorkloadV2Loc }], viewQueries: [{ propertyName: "reportFrame", first: true, predicate: ["reportFrame"], descendants: true }], ngImport: i0, template: "<div class=\"row\">\r\n <div class=\"col-12\">\r\n <div class=\"card app-overflow-visible\" widget>\r\n <div class=\"card-header\">\r\n <span class=\"text-uppercase\">{{'Workload report' | localize: lc}}</span>\r\n </div>\r\n <div class=\"card-body widget-body\">\r\n <!-- Barra dei comandi: tipo, istanze, genera. Il report si chiede solo con il comando -->\r\n <div class=\"wr-toolbar\">\r\n <div class=\"wr-group\">\r\n <span class=\"wr-group-label\">{{'Tipo' | localize: lc}}</span>\r\n <div class=\"wr-seg\">\r\n <button type=\"button\" [class.wr-on]=\"live\" (click)=\"setLive(true)\"><span class=\"wr-live-dot\"></span>Live</button>\r\n <button type=\"button\" [class.wr-on]=\"!live\" [disabled]=\"!historyAvailable\" (click)=\"setLive(false)\"\r\n [title]=\"historyAvailable ? '' : ('Lo storico non \u00E8 attivo su questo ambiente: il report si pu\u00F2 produrre solo live.' | localize: lc)\">\r\n {{'Fra snapshot' | localize: lc}}\r\n </button>\r\n </div>\r\n </div>\r\n <div class=\"wr-group wr-field-wide\">\r\n <span class=\"wr-group-label\">{{'Istanze' | localize: lc}}</span>\r\n <form-multiselect class=\"wr-fill\" [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"instances\" (ngModelChange)=\"instancesChanged($event)\" name=\"instances\" [Source]=\"Instances\" [Placeholder]=\"clusterLabel\" [MaxShownElements]=\"2\"></form-multiselect>\r\n </div>\r\n <div class=\"wr-toolbar-end\">\r\n <button type=\"button\" class=\"wr-btn wr-btn-primary\" [disabled]=\"!generating && !canGenerate\" (click)=\"generateOrCancel()\"\r\n [title]=\"!generating && !canGenerate ? ('Scegli il primo e l\\'ultimo snapshot' | localize: lc) : ''\">\r\n @if (generating) {\r\n <i class=\"fa fa-times\"></i> {{'Annulla' | localize: lc}}\r\n } @else {\r\n <i class=\"fa fa-file-text\"></i> {{'Genera report' | localize: lc}}\r\n }\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Avvisi sulla finestra (W4): non bloccano la generazione -->\r\n @for (warning of warnings; track $index) {\r\n <div class=\"wr-alert wr-alert-warn\"><i class=\"fa fa-exclamation-triangle\"></i> {{warning}}</div>\r\n }\r\n @if (snapshotsLoaded && !historyAvailable) {\r\n <div class=\"wr-alert\"><i class=\"fa fa-info-circle\"></i> {{'Lo storico non \u00E8 attivo su questo ambiente: il report si pu\u00F2 produrre solo live.' | localize: lc}}</div>\r\n }\r\n\r\n <!-- Elenco degli snapshot, solo per il report fra snapshot -->\r\n @if (!live) {\r\n <div class=\"wr-panel\">\r\n <div class=\"wr-panel-header\">\r\n <span class=\"wr-title\">{{'Snapshot' | localize: lc}}</span>\r\n <div class=\"wr-seg wr-seg-small\">\r\n @for (p of Periods; track p.id) {\r\n <button type=\"button\" [class.wr-on]=\"period == p.id\" (click)=\"setPeriod(p.id)\">{{p.description}}</button>\r\n }\r\n </div>\r\n @if (snapshotsLoading) {\r\n <span class=\"wr-muted\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</span>\r\n }\r\n <span class=\"wr-spacer\"></span>\r\n @if (selectionWindow; as w) {\r\n <span class=\"wr-window\">\r\n {{'Dalle' | localize: lc}} <strong>{{local(w.from)}}</strong> {{'alle' | localize: lc}} <strong>{{local(w.to)}}</strong>\r\n ({{span(w.from, w.to)}})\r\n @if (selTo == null) {\r\n <span class=\"wr-muted\">· {{\"scegli l'ultimo snapshot\" | localize: lc}}</span>\r\n }\r\n </span>\r\n <button type=\"button\" class=\"wr-link\" (click)=\"clearSelection()\">{{'Azzera' | localize: lc}}</button>\r\n } @else {\r\n <span class=\"wr-muted\">{{\"Un clic sceglie il primo snapshot, un secondo clic l'ultimo.\" | localize: lc}}</span>\r\n }\r\n </div>\r\n\r\n @if (snapshotsError) {\r\n <div class=\"wr-alert wr-alert-error\">{{snapshotsError}}</div>\r\n }\r\n\r\n <div class=\"wr-table-wrap\">\r\n <table class=\"wr-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"wr-num\">{{'Snapshot' | localize: lc}}</th>\r\n <th>{{'Inizio' | localize: lc}}</th>\r\n <th>{{'Fine' | localize: lc}}</th>\r\n <th class=\"wr-num\">{{'Durata' | localize: lc}}</th>\r\n <th class=\"wr-num\">{{'Istanze' | localize: lc}}</th>\r\n <th>{{'Manuale' | localize: lc}}</th>\r\n <th>{{'Riavvio' | localize: lc}}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (s of snapshots; track s.snapId) {\r\n <tr class=\"wr-row\" [class.wr-in-range]=\"inRange(s)\" [class.wr-edge]=\"isEdge(s)\" [class.wr-restart]=\"s.restart\" (click)=\"pick(s)\">\r\n <td class=\"wr-num\"><strong>{{s.snapId}}</strong></td>\r\n <td>{{local(s.beginIntervalTime)}}</td>\r\n <td>{{local(s.endIntervalTime)}}</td>\r\n <td class=\"wr-num\">{{span(s.beginIntervalTime, s.endIntervalTime)}}</td>\r\n <td class=\"wr-num\" [title]=\"instancesTitle(s)\">{{s.instances?.length ?? 0}}</td>\r\n <td>\r\n @if (s.manual) {\r\n <span class=\"wr-badge\"><i class=\"fa fa-hand-pointer\"></i> {{'Manuale' | localize: lc}}</span>\r\n }\r\n </td>\r\n <td>\r\n @if (s.restart) {\r\n <span class=\"wr-badge wr-badge-warn\" [title]=\"'Un\\'istanza \u00E8 ripartita: counter e meter non sono attendibili' | localize: lc\"><i class=\"fa fa-power-off\"></i> {{'Riavvio' | localize: lc}}</span>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (snapshotsLoaded && !snapshotsLoading && !snapshotsError && snapshots.length == 0 && historyAvailable) {\r\n <div class=\"wr-empty\">{{'Nessuno snapshot nel periodo scelto: prova con un periodo pi\u00F9 lungo.' | localize: lc}}</div>\r\n }\r\n </div>\r\n <div class=\"wr-footnote\">{{'Orari in ora locale' | localize: lc}} ({{zone}})</div>\r\n </div>\r\n }\r\n\r\n <!-- Report -->\r\n <div class=\"wr-panel wr-report\">\r\n @if (generating) {\r\n <div class=\"wr-alert wr-alert-busy\">\r\n <i class=\"fa fa-spinner fa-spin\"></i> {{'Generazione del report\u2026' | localize: lc}}\r\n <button type=\"button\" class=\"wr-link\" (click)=\"cancel()\">{{'Annulla' | localize: lc}}</button>\r\n </div>\r\n }\r\n @if (reportError) {\r\n <div class=\"wr-alert wr-alert-error\">{{'Il report non si pu\u00F2 produrre' | localize: lc}}: {{reportError}}</div>\r\n }\r\n\r\n @if (report) {\r\n <div class=\"wr-report-bar\">\r\n <div class=\"wr-summary\">\r\n @if (report.live) {\r\n <strong>Live</strong>\r\n } @else {\r\n <strong>{{'Snapshot' | localize: lc}} {{report.snapFrom}}\u2013{{report.snapTo}}</strong>\r\n }\r\n · {{scopeLabel(report.instances)}}\r\n @if (!report.live && report.windowFrom) {\r\n · {{'dalle' | localize: lc}} {{local(report.windowFrom)}} {{'alle' | localize: lc}} {{local(report.windowTo)}}\r\n }\r\n · {{'generato alle' | localize: lc}} {{hhmm(report.generatedAt)}} ({{kilobytes(report.size)}})\r\n </div>\r\n <div class=\"wr-actions\">\r\n @if (report.sections.length > 0) {\r\n <div class=\"wr-field wr-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"section\" (ngModelChange)=\"goToSection($event)\" name=\"section\" [Source]=\"report.sections\" [Placeholder]=\"'Vai a\u2026' | localize: lc\"></form-select>\r\n </div>\r\n }\r\n <button type=\"button\" class=\"wr-btn\" (click)=\"download()\"><i class=\"fa fa-download\"></i> {{'Scarica' | localize: lc}}</button>\r\n <button type=\"button\" class=\"wr-btn\" (click)=\"openInTab()\"><i class=\"fa fa-external-link\"></i> {{'Apri in una scheda' | localize: lc}}</button>\r\n </div>\r\n </div>\r\n <div class=\"wr-footnote\"><i class=\"fa fa-clock\"></i> {{'Gli orari del report sono in UTC.' | localize: lc}}</div>\r\n <!-- Riquadro isolato e senza script: gli stili del report non toccano la pagina e le ancore dell'indice restano dentro (W8) -->\r\n <iframe #reportFrame class=\"wr-frame\" [class.wr-stale]=\"generating\" [srcdoc]=\"report.doc\" sandbox=\"allow-same-origin allow-popups\" title=\"Workload report\"></iframe>\r\n } @else if (!generating && !reportError) {\r\n <div class=\"wr-placeholder\">\r\n <i class=\"fa fa-file-text\"></i>\r\n {{'Scegli tipo e istanze, poi genera il report.' | localize: lc}}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .wr-noclear .p-select-clear-icon{display:none}:host ::ng-deep .wr-fill{display:block;width:100%}.wr-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 18px;padding-top:6px}.wr-toolbar-end{margin-left:auto}.wr-field{width:240px}.wr-field-wide{width:380px;max-width:100%}.wr-group{position:relative;display:inline-flex}.wr-group-label{position:absolute;top:-8px;left:8px;z-index:2;padding:0 4px;background:var(--p-content-background, #fff);color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.wr-seg{display:inline-flex;height:39px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);overflow:hidden;background:var(--p-content-background, #fff)}.wr-seg button{display:inline-flex;align-items:center;gap:6px;padding:0 14px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.wr-seg button:first-child{border-left:0}.wr-seg button:hover:not(.wr-on):not(:disabled){background:var(--p-content-hover-background, #f8fafc)}.wr-seg button:disabled{color:var(--p-text-muted-color, #64748b);opacity:.6;cursor:not-allowed}.wr-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.wr-seg button.wr-on{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca);font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.wr-seg.wr-seg-small{height:30px}.wr-seg.wr-seg-small button{padding:0 10px;font-size:12px}.wr-live-dot{width:8px;height:8px;border-radius:50%;background:#16a34a}.wr-btn{display:inline-flex;align-items:center;gap:7px;height:34px;padding:0 14px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.wr-btn:hover:not(:disabled){background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.wr-btn:disabled{opacity:.55;cursor:not-allowed}.wr-btn.wr-btn-primary{height:39px;padding:0 18px;border-color:var(--p-primary-color, #6366f1);background:var(--p-primary-color, #6366f1);color:var(--p-primary-contrast-color, #fff);font-weight:500}.wr-btn.wr-btn-primary:hover:not(:disabled){background:var(--p-primary-color, #6366f1);color:var(--p-primary-contrast-color, #fff);filter:brightness(1.08)}.wr-link{margin-left:8px;padding:0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;cursor:pointer}.wr-link:hover{text-decoration:underline}.wr-alert{margin-top:10px;padding:7px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155);font-size:13px}.wr-alert.wr-alert-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.wr-alert.wr-alert-error{border-color:#fca5a5;background:#fef2f2;color:#991b1b}.wr-alert.wr-alert-busy{margin-top:0;margin-bottom:10px;border-color:var(--p-primary-color, #6366f1);background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.wr-panel{margin-top:14px;padding:12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0}.wr-panel-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin-bottom:10px}.wr-title{font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--p-text-muted-color, #64748b)}.wr-spacer{flex:1 1 auto}.wr-window{color:var(--p-text-color, #334155);font-size:13px}.wr-muted{color:var(--p-text-muted-color, #64748b);font-size:12px}.wr-table-wrap{max-height:320px;overflow:auto;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px)}.wr-table{width:100%;border-collapse:separate;border-spacing:0;font-size:13px;color:var(--p-text-color, #334155)}.wr-table th{position:sticky;top:0;z-index:1;padding:6px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-muted-color, #64748b);font-size:12px;font-weight:500;white-space:nowrap;text-align:left}.wr-table td{padding:4px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);white-space:nowrap}.wr-num{text-align:right!important;font-variant-numeric:tabular-nums}.wr-row{cursor:pointer}.wr-row:hover td{background:var(--p-content-hover-background, #f8fafc)}.wr-row.wr-restart td:first-child{box-shadow:inset 3px 0 #f59e0b}.wr-row.wr-in-range td{background:var(--p-highlight-background, #eef2ff)}.wr-row.wr-edge td{color:var(--p-highlight-color, #4338ca);font-weight:600}.wr-badge{display:inline-flex;align-items:center;gap:4px;padding:0 7px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:10px;color:var(--p-text-muted-color, #64748b);font-size:11px;line-height:18px}.wr-badge.wr-badge-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.wr-empty{padding:22px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.wr-footnote{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.wr-report-bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px 14px;padding-top:6px}.wr-summary{color:var(--p-text-color, #334155);font-size:13px}.wr-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.wr-frame{display:block;width:100%;height:calc(100vh - 240px);min-height:520px;margin-top:8px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:#fff;transition:opacity .15s}.wr-frame.wr-stale{opacity:.5}.wr-placeholder{display:flex;flex-direction:column;align-items:center;gap:10px;padding:70px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.wr-placeholder .fa{font-size:28px;opacity:.5}\n"], dependencies: [{ kind: "directive", type: i7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i9.EsFormSelectComponent, selector: "form-select", inputs: ["OptionsTemplate"] }, { kind: "component", type: i9.EsFormMultiselectComponent, selector: "form-multiselect", inputs: ["display", "InOutFormat", "FloatingLabel", "MaxShownElements"] }, { kind: "directive", type: i10.Widget, selector: "[widget]" }, { kind: "pipe", type: i1.LocalizePipe, name: "localize" }] }); }
|
|
2401
|
+
}
|
|
2402
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmWorkloadV2Component, decorators: [{
|
|
2403
|
+
type: Component,
|
|
2404
|
+
args: [{ selector: "workload_v2", providers: [{ provide: LocalizationService, useClass: AdmWorkloadV2Loc }], standalone: false, template: "<div class=\"row\">\r\n <div class=\"col-12\">\r\n <div class=\"card app-overflow-visible\" widget>\r\n <div class=\"card-header\">\r\n <span class=\"text-uppercase\">{{'Workload report' | localize: lc}}</span>\r\n </div>\r\n <div class=\"card-body widget-body\">\r\n <!-- Barra dei comandi: tipo, istanze, genera. Il report si chiede solo con il comando -->\r\n <div class=\"wr-toolbar\">\r\n <div class=\"wr-group\">\r\n <span class=\"wr-group-label\">{{'Tipo' | localize: lc}}</span>\r\n <div class=\"wr-seg\">\r\n <button type=\"button\" [class.wr-on]=\"live\" (click)=\"setLive(true)\"><span class=\"wr-live-dot\"></span>Live</button>\r\n <button type=\"button\" [class.wr-on]=\"!live\" [disabled]=\"!historyAvailable\" (click)=\"setLive(false)\"\r\n [title]=\"historyAvailable ? '' : ('Lo storico non \u00E8 attivo su questo ambiente: il report si pu\u00F2 produrre solo live.' | localize: lc)\">\r\n {{'Fra snapshot' | localize: lc}}\r\n </button>\r\n </div>\r\n </div>\r\n <div class=\"wr-group wr-field-wide\">\r\n <span class=\"wr-group-label\">{{'Istanze' | localize: lc}}</span>\r\n <form-multiselect class=\"wr-fill\" [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"instances\" (ngModelChange)=\"instancesChanged($event)\" name=\"instances\" [Source]=\"Instances\" [Placeholder]=\"clusterLabel\" [MaxShownElements]=\"2\"></form-multiselect>\r\n </div>\r\n <div class=\"wr-toolbar-end\">\r\n <button type=\"button\" class=\"wr-btn wr-btn-primary\" [disabled]=\"!generating && !canGenerate\" (click)=\"generateOrCancel()\"\r\n [title]=\"!generating && !canGenerate ? ('Scegli il primo e l\\'ultimo snapshot' | localize: lc) : ''\">\r\n @if (generating) {\r\n <i class=\"fa fa-times\"></i> {{'Annulla' | localize: lc}}\r\n } @else {\r\n <i class=\"fa fa-file-text\"></i> {{'Genera report' | localize: lc}}\r\n }\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Avvisi sulla finestra (W4): non bloccano la generazione -->\r\n @for (warning of warnings; track $index) {\r\n <div class=\"wr-alert wr-alert-warn\"><i class=\"fa fa-exclamation-triangle\"></i> {{warning}}</div>\r\n }\r\n @if (snapshotsLoaded && !historyAvailable) {\r\n <div class=\"wr-alert\"><i class=\"fa fa-info-circle\"></i> {{'Lo storico non \u00E8 attivo su questo ambiente: il report si pu\u00F2 produrre solo live.' | localize: lc}}</div>\r\n }\r\n\r\n <!-- Elenco degli snapshot, solo per il report fra snapshot -->\r\n @if (!live) {\r\n <div class=\"wr-panel\">\r\n <div class=\"wr-panel-header\">\r\n <span class=\"wr-title\">{{'Snapshot' | localize: lc}}</span>\r\n <div class=\"wr-seg wr-seg-small\">\r\n @for (p of Periods; track p.id) {\r\n <button type=\"button\" [class.wr-on]=\"period == p.id\" (click)=\"setPeriod(p.id)\">{{p.description}}</button>\r\n }\r\n </div>\r\n @if (snapshotsLoading) {\r\n <span class=\"wr-muted\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</span>\r\n }\r\n <span class=\"wr-spacer\"></span>\r\n @if (selectionWindow; as w) {\r\n <span class=\"wr-window\">\r\n {{'Dalle' | localize: lc}} <strong>{{local(w.from)}}</strong> {{'alle' | localize: lc}} <strong>{{local(w.to)}}</strong>\r\n ({{span(w.from, w.to)}})\r\n @if (selTo == null) {\r\n <span class=\"wr-muted\">· {{\"scegli l'ultimo snapshot\" | localize: lc}}</span>\r\n }\r\n </span>\r\n <button type=\"button\" class=\"wr-link\" (click)=\"clearSelection()\">{{'Azzera' | localize: lc}}</button>\r\n } @else {\r\n <span class=\"wr-muted\">{{\"Un clic sceglie il primo snapshot, un secondo clic l'ultimo.\" | localize: lc}}</span>\r\n }\r\n </div>\r\n\r\n @if (snapshotsError) {\r\n <div class=\"wr-alert wr-alert-error\">{{snapshotsError}}</div>\r\n }\r\n\r\n <div class=\"wr-table-wrap\">\r\n <table class=\"wr-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"wr-num\">{{'Snapshot' | localize: lc}}</th>\r\n <th>{{'Inizio' | localize: lc}}</th>\r\n <th>{{'Fine' | localize: lc}}</th>\r\n <th class=\"wr-num\">{{'Durata' | localize: lc}}</th>\r\n <th class=\"wr-num\">{{'Istanze' | localize: lc}}</th>\r\n <th>{{'Manuale' | localize: lc}}</th>\r\n <th>{{'Riavvio' | localize: lc}}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (s of snapshots; track s.snapId) {\r\n <tr class=\"wr-row\" [class.wr-in-range]=\"inRange(s)\" [class.wr-edge]=\"isEdge(s)\" [class.wr-restart]=\"s.restart\" (click)=\"pick(s)\">\r\n <td class=\"wr-num\"><strong>{{s.snapId}}</strong></td>\r\n <td>{{local(s.beginIntervalTime)}}</td>\r\n <td>{{local(s.endIntervalTime)}}</td>\r\n <td class=\"wr-num\">{{span(s.beginIntervalTime, s.endIntervalTime)}}</td>\r\n <td class=\"wr-num\" [title]=\"instancesTitle(s)\">{{s.instances?.length ?? 0}}</td>\r\n <td>\r\n @if (s.manual) {\r\n <span class=\"wr-badge\"><i class=\"fa fa-hand-pointer\"></i> {{'Manuale' | localize: lc}}</span>\r\n }\r\n </td>\r\n <td>\r\n @if (s.restart) {\r\n <span class=\"wr-badge wr-badge-warn\" [title]=\"'Un\\'istanza \u00E8 ripartita: counter e meter non sono attendibili' | localize: lc\"><i class=\"fa fa-power-off\"></i> {{'Riavvio' | localize: lc}}</span>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (snapshotsLoaded && !snapshotsLoading && !snapshotsError && snapshots.length == 0 && historyAvailable) {\r\n <div class=\"wr-empty\">{{'Nessuno snapshot nel periodo scelto: prova con un periodo pi\u00F9 lungo.' | localize: lc}}</div>\r\n }\r\n </div>\r\n <div class=\"wr-footnote\">{{'Orari in ora locale' | localize: lc}} ({{zone}})</div>\r\n </div>\r\n }\r\n\r\n <!-- Report -->\r\n <div class=\"wr-panel wr-report\">\r\n @if (generating) {\r\n <div class=\"wr-alert wr-alert-busy\">\r\n <i class=\"fa fa-spinner fa-spin\"></i> {{'Generazione del report\u2026' | localize: lc}}\r\n <button type=\"button\" class=\"wr-link\" (click)=\"cancel()\">{{'Annulla' | localize: lc}}</button>\r\n </div>\r\n }\r\n @if (reportError) {\r\n <div class=\"wr-alert wr-alert-error\">{{'Il report non si pu\u00F2 produrre' | localize: lc}}: {{reportError}}</div>\r\n }\r\n\r\n @if (report) {\r\n <div class=\"wr-report-bar\">\r\n <div class=\"wr-summary\">\r\n @if (report.live) {\r\n <strong>Live</strong>\r\n } @else {\r\n <strong>{{'Snapshot' | localize: lc}} {{report.snapFrom}}\u2013{{report.snapTo}}</strong>\r\n }\r\n · {{scopeLabel(report.instances)}}\r\n @if (!report.live && report.windowFrom) {\r\n · {{'dalle' | localize: lc}} {{local(report.windowFrom)}} {{'alle' | localize: lc}} {{local(report.windowTo)}}\r\n }\r\n · {{'generato alle' | localize: lc}} {{hhmm(report.generatedAt)}} ({{kilobytes(report.size)}})\r\n </div>\r\n <div class=\"wr-actions\">\r\n @if (report.sections.length > 0) {\r\n <div class=\"wr-field wr-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"section\" (ngModelChange)=\"goToSection($event)\" name=\"section\" [Source]=\"report.sections\" [Placeholder]=\"'Vai a\u2026' | localize: lc\"></form-select>\r\n </div>\r\n }\r\n <button type=\"button\" class=\"wr-btn\" (click)=\"download()\"><i class=\"fa fa-download\"></i> {{'Scarica' | localize: lc}}</button>\r\n <button type=\"button\" class=\"wr-btn\" (click)=\"openInTab()\"><i class=\"fa fa-external-link\"></i> {{'Apri in una scheda' | localize: lc}}</button>\r\n </div>\r\n </div>\r\n <div class=\"wr-footnote\"><i class=\"fa fa-clock\"></i> {{'Gli orari del report sono in UTC.' | localize: lc}}</div>\r\n <!-- Riquadro isolato e senza script: gli stili del report non toccano la pagina e le ancore dell'indice restano dentro (W8) -->\r\n <iframe #reportFrame class=\"wr-frame\" [class.wr-stale]=\"generating\" [srcdoc]=\"report.doc\" sandbox=\"allow-same-origin allow-popups\" title=\"Workload report\"></iframe>\r\n } @else if (!generating && !reportError) {\r\n <div class=\"wr-placeholder\">\r\n <i class=\"fa fa-file-text\"></i>\r\n {{'Scegli tipo e istanze, poi genera il report.' | localize: lc}}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .wr-noclear .p-select-clear-icon{display:none}:host ::ng-deep .wr-fill{display:block;width:100%}.wr-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 18px;padding-top:6px}.wr-toolbar-end{margin-left:auto}.wr-field{width:240px}.wr-field-wide{width:380px;max-width:100%}.wr-group{position:relative;display:inline-flex}.wr-group-label{position:absolute;top:-8px;left:8px;z-index:2;padding:0 4px;background:var(--p-content-background, #fff);color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.wr-seg{display:inline-flex;height:39px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);overflow:hidden;background:var(--p-content-background, #fff)}.wr-seg button{display:inline-flex;align-items:center;gap:6px;padding:0 14px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.wr-seg button:first-child{border-left:0}.wr-seg button:hover:not(.wr-on):not(:disabled){background:var(--p-content-hover-background, #f8fafc)}.wr-seg button:disabled{color:var(--p-text-muted-color, #64748b);opacity:.6;cursor:not-allowed}.wr-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.wr-seg button.wr-on{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca);font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.wr-seg.wr-seg-small{height:30px}.wr-seg.wr-seg-small button{padding:0 10px;font-size:12px}.wr-live-dot{width:8px;height:8px;border-radius:50%;background:#16a34a}.wr-btn{display:inline-flex;align-items:center;gap:7px;height:34px;padding:0 14px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.wr-btn:hover:not(:disabled){background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.wr-btn:disabled{opacity:.55;cursor:not-allowed}.wr-btn.wr-btn-primary{height:39px;padding:0 18px;border-color:var(--p-primary-color, #6366f1);background:var(--p-primary-color, #6366f1);color:var(--p-primary-contrast-color, #fff);font-weight:500}.wr-btn.wr-btn-primary:hover:not(:disabled){background:var(--p-primary-color, #6366f1);color:var(--p-primary-contrast-color, #fff);filter:brightness(1.08)}.wr-link{margin-left:8px;padding:0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;cursor:pointer}.wr-link:hover{text-decoration:underline}.wr-alert{margin-top:10px;padding:7px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155);font-size:13px}.wr-alert.wr-alert-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.wr-alert.wr-alert-error{border-color:#fca5a5;background:#fef2f2;color:#991b1b}.wr-alert.wr-alert-busy{margin-top:0;margin-bottom:10px;border-color:var(--p-primary-color, #6366f1);background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.wr-panel{margin-top:14px;padding:12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0}.wr-panel-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin-bottom:10px}.wr-title{font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--p-text-muted-color, #64748b)}.wr-spacer{flex:1 1 auto}.wr-window{color:var(--p-text-color, #334155);font-size:13px}.wr-muted{color:var(--p-text-muted-color, #64748b);font-size:12px}.wr-table-wrap{max-height:320px;overflow:auto;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px)}.wr-table{width:100%;border-collapse:separate;border-spacing:0;font-size:13px;color:var(--p-text-color, #334155)}.wr-table th{position:sticky;top:0;z-index:1;padding:6px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-muted-color, #64748b);font-size:12px;font-weight:500;white-space:nowrap;text-align:left}.wr-table td{padding:4px 10px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);white-space:nowrap}.wr-num{text-align:right!important;font-variant-numeric:tabular-nums}.wr-row{cursor:pointer}.wr-row:hover td{background:var(--p-content-hover-background, #f8fafc)}.wr-row.wr-restart td:first-child{box-shadow:inset 3px 0 #f59e0b}.wr-row.wr-in-range td{background:var(--p-highlight-background, #eef2ff)}.wr-row.wr-edge td{color:var(--p-highlight-color, #4338ca);font-weight:600}.wr-badge{display:inline-flex;align-items:center;gap:4px;padding:0 7px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:10px;color:var(--p-text-muted-color, #64748b);font-size:11px;line-height:18px}.wr-badge.wr-badge-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.wr-empty{padding:22px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.wr-footnote{margin-top:6px;color:var(--p-text-muted-color, #64748b);font-size:12px}.wr-report-bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px 14px;padding-top:6px}.wr-summary{color:var(--p-text-color, #334155);font-size:13px}.wr-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.wr-frame{display:block;width:100%;height:calc(100vh - 240px);min-height:520px;margin-top:8px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:#fff;transition:opacity .15s}.wr-frame.wr-stale{opacity:.5}.wr-placeholder{display:flex;flex-direction:column;align-items:center;gap:10px;padding:70px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.wr-placeholder .fa{font-size:28px;opacity:.5}\n"] }]
|
|
2405
|
+
}], ctorParameters: () => [{ type: i1.LocalizationService }, { type: i3.PreferencesService }, { type: i1$1.InterComService }, { type: i4.ActivatedRoute }, { type: i5$1.DomSanitizer }, { type: AdminModuleBaseService }], propDecorators: { reportFrame: [{
|
|
2406
|
+
type: ViewChild,
|
|
2407
|
+
args: ["reportFrame"]
|
|
2408
|
+
}] } });
|
|
2409
|
+
|
|
2410
|
+
// Metriche v2 (specifica "Metriche del Director"): richieste e risposte come le vuole e le manda il backend (camelCase).
|
|
2411
|
+
// Le date delle risposte sono UTC "dd/MM/yyyy HH:mm:ss" senza fuso, al secondo
|
|
2412
|
+
var MetricV2Type;
|
|
2413
|
+
(function (MetricV2Type) {
|
|
2414
|
+
MetricV2Type[MetricV2Type["Counter"] = 0] = "Counter";
|
|
2415
|
+
MetricV2Type[MetricV2Type["Gauge"] = 1] = "Gauge";
|
|
2416
|
+
MetricV2Type[MetricV2Type["Histogram"] = 2] = "Histogram";
|
|
2417
|
+
MetricV2Type[MetricV2Type["Timer"] = 3] = "Timer";
|
|
2418
|
+
MetricV2Type[MetricV2Type["Meter"] = 4] = "Meter";
|
|
2419
|
+
})(MetricV2Type || (MetricV2Type = {}));
|
|
2420
|
+
// Senza istanze e con global, tutto il cluster
|
|
2421
|
+
class MetricsV2CatalogRequest {
|
|
2422
|
+
}
|
|
2423
|
+
// Live (realTime con snapFrom), storico per periodo (snapFrom/snapTo) o storico fra due snapshot (snapIdFrom/snapIdTo)
|
|
2424
|
+
class MetricsV2Request extends MetricsV2CatalogRequest {
|
|
2425
|
+
}
|
|
2426
|
+
class MetricsV2Info {
|
|
2427
|
+
}
|
|
2428
|
+
// Una metrica di un'istanza in un istante: i campi che non riguardano il tipo valgono 0 o null
|
|
2429
|
+
class MetricsV2Sample {
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
class AdmMetricsV2Loc extends LocalizationService {
|
|
2433
|
+
constructor(injector) {
|
|
2434
|
+
super(injector);
|
|
2435
|
+
super.set("it->en", "Metriche di Sistema", ["System Metrics"]);
|
|
2436
|
+
super.set("it->en", "Errore", ["Error"]);
|
|
2437
|
+
// Barra dei comandi
|
|
2438
|
+
super.set("it->en", "Sorgente", ["Source"]);
|
|
2439
|
+
super.set("it->en", "Storico per periodo", ["History by period"]);
|
|
2440
|
+
super.set("it->en", "Storico per snapshot", ["History by snapshot"]);
|
|
2441
|
+
super.set("it->en", "Lo storico non è attivo su questo ambiente: ci sono solo i dati live.", ["History is not active on this environment: only live data is available."]);
|
|
2442
|
+
super.set("it->en", "Finestra", ["Window"]);
|
|
2443
|
+
super.set("it->en", "1 ora", ["1 hour"]);
|
|
2444
|
+
super.set("it->en", "Dal", ["From"]);
|
|
2445
|
+
super.set("it->en", "Al", ["To"]);
|
|
2446
|
+
super.set("it->en", "Dallo snapshot", ["From snapshot"]);
|
|
2447
|
+
super.set("it->en", "Allo snapshot", ["To snapshot"]);
|
|
2448
|
+
super.set("it->en", "Dalle", ["From"]);
|
|
2449
|
+
super.set("it->en", "alle", ["at"]);
|
|
2450
|
+
super.set("it->en", "Istanze", ["Instances"]);
|
|
2451
|
+
super.set("it->en", "Tutto il cluster", ["Whole cluster"]);
|
|
2452
|
+
super.set("it->en", "istanze", ["instances"]);
|
|
2453
|
+
super.set("it->en", "istanza", ["instance"]);
|
|
2454
|
+
super.set("it->en", "Aggiornato alle", ["Updated at"]);
|
|
2455
|
+
super.set("it->en", "Pausa", ["Pause"]);
|
|
2456
|
+
super.set("it->en", "Riprendi", ["Resume"]);
|
|
2457
|
+
super.set("it->en", "Caricamento…", ["Loading…"]);
|
|
2458
|
+
super.set("it->en", "Grafici", ["Charts"]);
|
|
2459
|
+
super.set("it->en", "Tabella", ["Table"]);
|
|
2460
|
+
super.set("it->en", "Lo snapshot {id} segue un riavvio di {istanza}: lì le serie si interrompono.", ["Snapshot {id} follows a restart of {istanza}: series break there."]);
|
|
2461
|
+
// Catalogo
|
|
2462
|
+
super.set("it->en", "Cerca una metrica…", ["Search a metric…"]);
|
|
2463
|
+
super.set("it->en", "Processo", ["Process"]);
|
|
2464
|
+
super.set("it->en", "API", ["API"]);
|
|
2465
|
+
super.set("it->en", "Code asincrone", ["Async queues"]);
|
|
2466
|
+
super.set("it->en", "Attività", ["Activity"]);
|
|
2467
|
+
super.set("it->en", "di", ["of"]);
|
|
2468
|
+
super.set("it->en", "metriche scelte", ["metrics chosen"]);
|
|
2469
|
+
super.set("it->en", "Togli tutte", ["Remove all"]);
|
|
2470
|
+
super.set("it->en", "Voci", ["Items"]);
|
|
2471
|
+
super.set("it->en", "Aggiungi le {N} voci", ["Add the {N} items"]);
|
|
2472
|
+
// Grafici e tessere
|
|
2473
|
+
super.set("it->en", "Scegli una o più metriche dal catalogo.", ["Choose one or more metrics from the catalog."]);
|
|
2474
|
+
super.set("it->en", "dall'avvio", ["since start"]);
|
|
2475
|
+
super.set("it->en", "Orari in ora locale", ["Times in local time"]);
|
|
2476
|
+
super.set("it->en", "trascina su un grafico per lo zoom", ["drag on a chart to zoom"]);
|
|
2477
|
+
super.set("it->en", "Finestra intera", ["Whole window"]);
|
|
2478
|
+
super.set("it->en", "Togli", ["Remove"]);
|
|
2479
|
+
super.set("it->en", "Nessun campione di {metrica} nella finestra scelta", ["No samples of {metrica} in the chosen window"]);
|
|
2480
|
+
super.set("it->en", "riavvio", ["restart"]);
|
|
2481
|
+
super.set("it->en", "thread", ["threads"]);
|
|
2482
|
+
super.set("it->en", "Livello", ["Level"]);
|
|
2483
|
+
super.set("it->en", "Variazione", ["Change"]);
|
|
2484
|
+
super.set("it->en", "Quote", ["Shares"]);
|
|
2485
|
+
super.set("it->en", "Al secondo", ["Per second"]);
|
|
2486
|
+
super.set("it->en", "Rate mobili", ["Moving rates"]);
|
|
2487
|
+
super.set("it->en", "Percentili", ["Percentiles"]);
|
|
2488
|
+
super.set("it->en", "Frequenza", ["Frequency"]);
|
|
2489
|
+
super.set("it->en", "Chiamate e durata", ["Calls and duration"]);
|
|
2490
|
+
super.set("it->en", "In corso", ["In progress"]);
|
|
2491
|
+
super.set("it->en", "in corso", ["in progress"]);
|
|
2492
|
+
super.set("it->en", "mediana", ["median"]);
|
|
2493
|
+
super.set("it->en", "media nell'intervallo", ["interval mean"]);
|
|
2494
|
+
// Tabella
|
|
2495
|
+
super.set("it->en", "Istanza", ["Instance"]);
|
|
2496
|
+
super.set("it->en", "Istante", ["Time"]);
|
|
2497
|
+
super.set("it->en", "Intervallo (s)", ["Interval (s)"]);
|
|
2498
|
+
super.set("it->en", "Snapshot", ["Snapshot"]);
|
|
2499
|
+
super.set("it->en", "Valore", ["Value"]);
|
|
2500
|
+
super.set("it->en", "Conteggio", ["Count"]);
|
|
2501
|
+
super.set("it->en", "Quota", ["Share"]);
|
|
2502
|
+
super.set("it->en", "Mediana", ["Median"]);
|
|
2503
|
+
super.set("it->en", "Massimo", ["Maximum"]);
|
|
2504
|
+
super.set("it->en", "Chiamate", ["Calls"]);
|
|
2505
|
+
super.set("it->en", "Durata media nell'intervallo", ["Mean duration in the interval"]);
|
|
2506
|
+
super.set("it->en", "Esporta CSV", ["Export CSV"]);
|
|
2507
|
+
super.set("it->en", "Mostrate le prime 300 righe di", ["Showing the first 300 rows of"]);
|
|
2508
|
+
super.set("it->en", "l'esportazione le comprende tutte.", ["the export includes all of them."]);
|
|
2509
|
+
}
|
|
2510
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmMetricsV2Loc, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2511
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmMetricsV2Loc }); }
|
|
2512
|
+
}
|
|
2513
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmMetricsV2Loc, decorators: [{
|
|
2514
|
+
type: Injectable
|
|
2515
|
+
}], ctorParameters: () => [{ type: i0.Injector }] });
|
|
2516
|
+
|
|
2517
|
+
// Angular
|
|
2518
|
+
// Un punto di una serie: gap = non unirlo al precedente (riavvio o buco nei campioni), v2 = estremo superiore di una fascia
|
|
2519
|
+
class Mv2Point {
|
|
2520
|
+
}
|
|
2521
|
+
class Mv2Series {
|
|
2522
|
+
}
|
|
2523
|
+
class Mv2Mark {
|
|
2524
|
+
}
|
|
2525
|
+
class Mv2Path {
|
|
2526
|
+
}
|
|
2527
|
+
class Mv2Rect {
|
|
2528
|
+
}
|
|
2529
|
+
class Mv2Dot {
|
|
2530
|
+
}
|
|
2531
|
+
class Mv2Tick {
|
|
2532
|
+
}
|
|
2533
|
+
class Mv2MarkTick {
|
|
2534
|
+
}
|
|
2535
|
+
class Mv2TipRow {
|
|
2536
|
+
}
|
|
2537
|
+
const LEFT = 56;
|
|
2538
|
+
const RIGHT = 10;
|
|
2539
|
+
// Spazio in alto per l'unità, che sta sopra la prima tacca
|
|
2540
|
+
const TOP = 20;
|
|
2541
|
+
const BOTTOM = 20;
|
|
2542
|
+
const STEPS_MS = [60, 120, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400, 172800, 604800].map(t => t * 1000);
|
|
2543
|
+
// Grafico SVG delle metriche v2: lo stesso asse dei tempi per tutti i pannelli, cursore e zoom condivisi tramite la pagina.
|
|
2544
|
+
// Le serie arrivano già pronte (punti con le interruzioni segnate): qui solo geometria e interazione
|
|
2545
|
+
class MetricsV2ChartComponent {
|
|
2546
|
+
constructor(host, zone) {
|
|
2547
|
+
this.host = host;
|
|
2548
|
+
this.zone = zone;
|
|
2549
|
+
this.series = [];
|
|
2550
|
+
this.height = 170;
|
|
2551
|
+
this.marks = [];
|
|
2552
|
+
this.cursor = null;
|
|
2553
|
+
this.format = t => String(t);
|
|
2554
|
+
this.cursorChange = new EventEmitter();
|
|
2555
|
+
this.zoom = new EventEmitter();
|
|
2556
|
+
this.width = 600;
|
|
2557
|
+
this.left = LEFT;
|
|
2558
|
+
this.right = RIGHT;
|
|
2559
|
+
this.top = TOP;
|
|
2560
|
+
this.bottom = BOTTOM;
|
|
2561
|
+
this.paths = [];
|
|
2562
|
+
this.rects = [];
|
|
2563
|
+
this.dots = [];
|
|
2564
|
+
this.yTicks = [];
|
|
2565
|
+
this.xTicks = [];
|
|
2566
|
+
this.markTicks = [];
|
|
2567
|
+
// Il riquadro dei valori compare solo sul grafico sotto il mouse, la linea del cursore su tutti
|
|
2568
|
+
this.hovered = false;
|
|
2569
|
+
this.cursorX = null;
|
|
2570
|
+
this.tipTime = "";
|
|
2571
|
+
this.tipRows = [];
|
|
2572
|
+
this.dragFrom = null;
|
|
2573
|
+
this.dragTo = null;
|
|
2574
|
+
this.yMin = 0;
|
|
2575
|
+
this.yMax = 1;
|
|
2576
|
+
}
|
|
2577
|
+
ngOnInit() {
|
|
2578
|
+
this.observer = new ResizeObserver(entries => {
|
|
2579
|
+
let width = Math.floor(entries[0]?.contentRect?.width ?? 0);
|
|
2580
|
+
if (width > 0 && width != this.width)
|
|
2581
|
+
this.zone.run(() => {
|
|
2582
|
+
this.width = width;
|
|
2583
|
+
this.layout();
|
|
2584
|
+
});
|
|
2585
|
+
});
|
|
2586
|
+
this.observer.observe(this.host.nativeElement);
|
|
2587
|
+
}
|
|
2588
|
+
ngOnDestroy() {
|
|
2589
|
+
this.observer?.disconnect();
|
|
2590
|
+
}
|
|
2591
|
+
ngOnChanges(changes) {
|
|
2592
|
+
if (changes.series || changes.from || changes.to || changes.height || changes.marks)
|
|
2593
|
+
this.layout();
|
|
2594
|
+
else if (changes.cursor)
|
|
2595
|
+
this.updateCursor();
|
|
2596
|
+
}
|
|
2597
|
+
min(a, b) { return Math.min(a, b); }
|
|
2598
|
+
abs(a) { return Math.abs(a); }
|
|
2599
|
+
// #region Geometria
|
|
2600
|
+
get plotWidth() {
|
|
2601
|
+
return Math.max(10, this.width - LEFT - RIGHT);
|
|
2602
|
+
}
|
|
2603
|
+
x(t) {
|
|
2604
|
+
return LEFT + (t - this.from) / Math.max(1, this.to - this.from) * this.plotWidth;
|
|
2605
|
+
}
|
|
2606
|
+
y(v) {
|
|
2607
|
+
return TOP + (1 - (v - this.yMin) / Math.max(1e-12, this.yMax - this.yMin)) * (this.height - TOP - BOTTOM);
|
|
2608
|
+
}
|
|
2609
|
+
timeAt(x) {
|
|
2610
|
+
return this.from + (x - LEFT) / this.plotWidth * (this.to - this.from);
|
|
2611
|
+
}
|
|
2612
|
+
layout() {
|
|
2613
|
+
if (this.from == null || this.to == null)
|
|
2614
|
+
return;
|
|
2615
|
+
// Scala verticale sui punti visibili, con lo zero se i valori sono positivi
|
|
2616
|
+
let values = [];
|
|
2617
|
+
for (let s of this.series ?? [])
|
|
2618
|
+
for (let p of s.points)
|
|
2619
|
+
if (p.t >= this.from && p.t <= this.to) {
|
|
2620
|
+
values.push(p.v);
|
|
2621
|
+
if (p.v2 != null)
|
|
2622
|
+
values.push(p.v2);
|
|
2623
|
+
}
|
|
2624
|
+
let lo = values.length ? Math.min(0, ...values) : 0;
|
|
2625
|
+
let hi = values.length ? Math.max(...values) : 1;
|
|
2626
|
+
if (hi <= lo)
|
|
2627
|
+
hi = lo + 1;
|
|
2628
|
+
let step = this.niceStep((hi - lo) / 4);
|
|
2629
|
+
this.yMin = Math.floor(lo / step) * step;
|
|
2630
|
+
this.yMax = Math.ceil(hi / step) * step;
|
|
2631
|
+
this.yTicks = [];
|
|
2632
|
+
for (let v = this.yMin; v <= this.yMax + step / 2; v += step)
|
|
2633
|
+
this.yTicks.push({ key: String(v), pos: this.y(v), label: this.format(Math.abs(v) < step / 1e6 ? 0 : v) });
|
|
2634
|
+
// Asse dei tempi: tacche a passi tondi, al massimo una ogni 90 pixel circa
|
|
2635
|
+
let span = this.to - this.from;
|
|
2636
|
+
let stepMs = STEPS_MS.find(t => span / t <= this.plotWidth / 90) ?? STEPS_MS[STEPS_MS.length - 1];
|
|
2637
|
+
let longRange = span > 36 * 3600 * 1000;
|
|
2638
|
+
let offset = new Date().getTimezoneOffset() * 60000;
|
|
2639
|
+
this.xTicks = [];
|
|
2640
|
+
for (let t = Math.ceil((this.from - offset) / stepMs) * stepMs + offset; t <= this.to; t += stepMs)
|
|
2641
|
+
this.xTicks.push({ key: String(t), pos: this.x(t), label: dayjs(t).format(longRange || stepMs >= 86400000 ? "DD/MM HH:mm" : "HH:mm") });
|
|
2642
|
+
// Segni: riavvii sempre, id degli snapshot solo se c'è spazio (altrimenti uno ogni tanto)
|
|
2643
|
+
let visible = (this.marks ?? []).filter(t => t.t >= this.from && t.t <= this.to);
|
|
2644
|
+
let snaps = visible.filter(t => t.kind == "snapshot");
|
|
2645
|
+
let every = Math.max(1, Math.ceil(snaps.length / Math.max(1, this.plotWidth / 45)));
|
|
2646
|
+
this.markTicks = visible.filter(t => t.kind == "restart" || snaps.indexOf(t) % every == 0)
|
|
2647
|
+
.map((t, i) => ({ key: `${t.kind}${t.t}${i}`, pos: this.x(t.t), label: t.label, kind: t.kind }));
|
|
2648
|
+
let paths = [];
|
|
2649
|
+
let rects = [];
|
|
2650
|
+
let dots = [];
|
|
2651
|
+
let bars = (this.series ?? []).filter(t => t.kind == "bars");
|
|
2652
|
+
for (let s of this.series ?? []) {
|
|
2653
|
+
let points = s.points.filter(p => p.t >= this.from && p.t <= this.to);
|
|
2654
|
+
if (points.length == 0)
|
|
2655
|
+
continue;
|
|
2656
|
+
switch (s.kind) {
|
|
2657
|
+
case "line":
|
|
2658
|
+
case "step":
|
|
2659
|
+
paths.push({ key: s.key, d: this.linePath(points, s.kind == "step"), color: s.color, fill: false, dash: s.dash ?? null, opacity: s.opacity ?? 1 });
|
|
2660
|
+
break;
|
|
2661
|
+
case "band":
|
|
2662
|
+
paths.push({ key: s.key, d: this.bandPath(points), color: s.color, fill: true, dash: null, opacity: s.opacity ?? 0.2 });
|
|
2663
|
+
break;
|
|
2664
|
+
case "dots":
|
|
2665
|
+
points.forEach((p, i) => dots.push({ key: `${s.key}${i}`, x: this.x(p.t), y: this.y(p.v), color: s.color }));
|
|
2666
|
+
break;
|
|
2667
|
+
case "bars":
|
|
2668
|
+
let slot = this.plotWidth / Math.max(1, points.length) * 0.8;
|
|
2669
|
+
let w = Math.max(1, slot / bars.length);
|
|
2670
|
+
let index = bars.indexOf(s);
|
|
2671
|
+
let zero = this.y(Math.max(this.yMin, 0));
|
|
2672
|
+
points.forEach((p, i) => {
|
|
2673
|
+
let y = this.y(p.v);
|
|
2674
|
+
rects.push({ key: `${s.key}${i}`, x: this.x(p.t) - slot / 2 + index * w, y: Math.min(y, zero), w: w, h: Math.max(1, Math.abs(zero - y)), color: s.color });
|
|
2675
|
+
});
|
|
2676
|
+
break;
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
this.paths = paths;
|
|
2680
|
+
this.rects = rects;
|
|
2681
|
+
this.dots = dots;
|
|
2682
|
+
this.updateCursor();
|
|
2683
|
+
}
|
|
2684
|
+
// Le linee si spezzano dove il punto ha gap: niente linea fra due campioni separati da un riavvio o da un buco
|
|
2685
|
+
linePath(points, step) {
|
|
2686
|
+
let d = "";
|
|
2687
|
+
points.forEach((p, i) => {
|
|
2688
|
+
let x = this.x(p.t).toFixed(1);
|
|
2689
|
+
let y = this.y(p.v).toFixed(1);
|
|
2690
|
+
if (i == 0 || p.gap)
|
|
2691
|
+
d += `M${x},${y}`;
|
|
2692
|
+
else if (step)
|
|
2693
|
+
d += `H${x}V${y}`;
|
|
2694
|
+
else
|
|
2695
|
+
d += `L${x},${y}`;
|
|
2696
|
+
});
|
|
2697
|
+
return d;
|
|
2698
|
+
}
|
|
2699
|
+
// Fascia fra v e v2, un poligono per ogni tratto senza interruzioni
|
|
2700
|
+
bandPath(points) {
|
|
2701
|
+
let segments = [];
|
|
2702
|
+
for (let p of points) {
|
|
2703
|
+
if (p.v2 == null)
|
|
2704
|
+
continue;
|
|
2705
|
+
if (segments.length == 0 || p.gap)
|
|
2706
|
+
segments.push([]);
|
|
2707
|
+
segments[segments.length - 1].push(p);
|
|
2708
|
+
}
|
|
2709
|
+
return segments.filter(t => t.length > 0).map(seg => {
|
|
2710
|
+
let upper = seg.map((p, i) => `${i ? "L" : "M"}${this.x(p.t).toFixed(1)},${this.y(p.v2).toFixed(1)}`).join("");
|
|
2711
|
+
let lower = [...seg].reverse().map(p => `L${this.x(p.t).toFixed(1)},${this.y(p.v).toFixed(1)}`).join("");
|
|
2712
|
+
return upper + lower + "Z";
|
|
2713
|
+
}).join("");
|
|
2714
|
+
}
|
|
2715
|
+
niceStep(raw) {
|
|
2716
|
+
if (!(raw > 0))
|
|
2717
|
+
return 1;
|
|
2718
|
+
let power = Math.pow(10, Math.floor(Math.log10(raw)));
|
|
2719
|
+
return [1, 2, 2.5, 5, 10].find(t => raw / power <= t + 1e-9) * power;
|
|
2720
|
+
}
|
|
2721
|
+
// #endregion
|
|
2722
|
+
// #region Cursore e zoom
|
|
2723
|
+
updateCursor() {
|
|
2724
|
+
if (this.cursor == null || this.cursor < this.from || this.cursor > this.to) {
|
|
2725
|
+
this.cursorX = null;
|
|
2726
|
+
this.tipRows = [];
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
this.cursorX = this.x(this.cursor);
|
|
2730
|
+
this.tipTime = dayjs(this.cursor).format("DD/MM HH:mm:ss");
|
|
2731
|
+
let rows = [];
|
|
2732
|
+
for (let s of this.series ?? []) {
|
|
2733
|
+
if (s.tooltip === false || s.points.length == 0)
|
|
2734
|
+
continue;
|
|
2735
|
+
let nearest = this.nearest(s.points, this.cursor);
|
|
2736
|
+
if (nearest)
|
|
2737
|
+
rows.push({ color: s.color, label: s.label, value: this.format(nearest.v) });
|
|
2738
|
+
}
|
|
2739
|
+
this.tipRows = rows;
|
|
2740
|
+
}
|
|
2741
|
+
// Il campione più vicino, se non è più lontano di un intervallo tipico della serie
|
|
2742
|
+
nearest(points, t) {
|
|
2743
|
+
let best = null;
|
|
2744
|
+
for (let p of points)
|
|
2745
|
+
if (!best || Math.abs(p.t - t) < Math.abs(best.t - t))
|
|
2746
|
+
best = p;
|
|
2747
|
+
let typical = points.length > 1 ? (points[points.length - 1].t - points[0].t) / (points.length - 1) : Infinity;
|
|
2748
|
+
return best && Math.abs(best.t - t) <= Math.max(typical, 1000) ? best : null;
|
|
2749
|
+
}
|
|
2750
|
+
plotX(event) {
|
|
2751
|
+
let rect = this.host.nativeElement.getBoundingClientRect();
|
|
2752
|
+
return Math.min(this.width - RIGHT, Math.max(LEFT, event.clientX - rect.left));
|
|
2753
|
+
}
|
|
2754
|
+
move(event) {
|
|
2755
|
+
let x = this.plotX(event);
|
|
2756
|
+
this.hovered = true;
|
|
2757
|
+
if (this.dragFrom != null)
|
|
2758
|
+
this.dragTo = x;
|
|
2759
|
+
this.cursorChange.emit(this.timeAt(x));
|
|
2760
|
+
}
|
|
2761
|
+
leave() {
|
|
2762
|
+
this.hovered = false;
|
|
2763
|
+
this.dragFrom = null;
|
|
2764
|
+
this.dragTo = null;
|
|
2765
|
+
this.cursorChange.emit(null);
|
|
2766
|
+
}
|
|
2767
|
+
// Trascinando sull'area si sceglie la finestra dello zoom, per tutti i pannelli
|
|
2768
|
+
down(event) {
|
|
2769
|
+
if (event.button != 0)
|
|
2770
|
+
return;
|
|
2771
|
+
event.preventDefault();
|
|
2772
|
+
this.dragFrom = this.plotX(event);
|
|
2773
|
+
this.dragTo = null;
|
|
2774
|
+
}
|
|
2775
|
+
up(event) {
|
|
2776
|
+
let from = this.dragFrom;
|
|
2777
|
+
let to = this.plotX(event);
|
|
2778
|
+
this.dragFrom = null;
|
|
2779
|
+
this.dragTo = null;
|
|
2780
|
+
if (from != null && Math.abs(to - from) > 6)
|
|
2781
|
+
this.zoom.emit({ from: this.timeAt(Math.min(from, to)), to: this.timeAt(Math.max(from, to)) });
|
|
2782
|
+
}
|
|
2783
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: MetricsV2ChartComponent, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2784
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: MetricsV2ChartComponent, isStandalone: false, selector: "mv2-chart", inputs: { series: "series", from: "from", to: "to", height: "height", unit: "unit", marks: "marks", cursor: "cursor", format: "format" }, outputs: { cursorChange: "cursorChange", zoom: "zoom" }, usesOnChanges: true, ngImport: i0, template: `
|
|
2785
|
+
<div class="mv2c" [style.height.px]="height" (mousemove)="move($event)" (mouseleave)="leave()" (mousedown)="down($event)" (mouseup)="up($event)">
|
|
2786
|
+
<svg [attr.width]="width" [attr.height]="height" [attr.viewBox]="'0 0 ' + width + ' ' + height">
|
|
2787
|
+
@for (tick of yTicks; track tick.key) {
|
|
2788
|
+
<line class="mv2c-grid" [attr.x1]="left" [attr.x2]="width - right" [attr.y1]="tick.pos" [attr.y2]="tick.pos" />
|
|
2789
|
+
<text class="mv2c-ylabel" [attr.x]="left - 6" [attr.y]="tick.pos + 3">{{tick.label}}</text>
|
|
2790
|
+
}
|
|
2791
|
+
@for (tick of xTicks; track tick.key) {
|
|
2792
|
+
<text class="mv2c-xlabel" [attr.x]="tick.pos" [attr.y]="height - 5">{{tick.label}}</text>
|
|
2793
|
+
}
|
|
2794
|
+
@if (unit) {
|
|
2795
|
+
<text class="mv2c-unit" [attr.x]="4" [attr.y]="11">{{unit}}</text>
|
|
2796
|
+
}
|
|
2797
|
+
@for (mark of markTicks; track mark.key) {
|
|
2798
|
+
<line class="mv2c-mark" [class.mv2c-mark-restart]="mark.kind == 'restart'" [class.mv2c-mark-snapshot]="mark.kind == 'snapshot'" [attr.x1]="mark.pos" [attr.x2]="mark.pos" [attr.y1]="top" [attr.y2]="height - bottom" />
|
|
2799
|
+
<text class="mv2c-marklabel" [attr.x]="mark.pos + 3" [attr.y]="top + 9">{{mark.label}}</text>
|
|
2800
|
+
}
|
|
2801
|
+
@for (p of paths; track p.key) {
|
|
2802
|
+
<path [attr.d]="p.d" [attr.fill]="p.fill ? p.color : 'none'" [attr.fill-opacity]="p.fill ? p.opacity : null"
|
|
2803
|
+
[attr.stroke]="p.fill ? 'none' : p.color" [attr.stroke-opacity]="p.fill ? null : p.opacity" [attr.stroke-dasharray]="p.dash" stroke-width="1.6" stroke-linejoin="round" />
|
|
2804
|
+
}
|
|
2805
|
+
@for (r of rects; track r.key) {
|
|
2806
|
+
<rect [attr.x]="r.x" [attr.y]="r.y" [attr.width]="r.w" [attr.height]="r.h" [attr.fill]="r.color" fill-opacity="0.75" />
|
|
2807
|
+
}
|
|
2808
|
+
@for (d of dots; track d.key) {
|
|
2809
|
+
<circle [attr.cx]="d.x" [attr.cy]="d.y" r="2" [attr.fill]="d.color" />
|
|
2810
|
+
}
|
|
2811
|
+
<line class="mv2c-axis" [attr.x1]="left" [attr.x2]="width - right" [attr.y1]="height - bottom" [attr.y2]="height - bottom" />
|
|
2812
|
+
@if (cursorX != null) {
|
|
2813
|
+
<line class="mv2c-cursor" [attr.x1]="cursorX" [attr.x2]="cursorX" [attr.y1]="top" [attr.y2]="height - bottom" />
|
|
2814
|
+
}
|
|
2815
|
+
@if (dragFrom != null && dragTo != null) {
|
|
2816
|
+
<rect class="mv2c-drag" [attr.x]="min(dragFrom, dragTo)" [attr.y]="top" [attr.width]="abs(dragTo - dragFrom)" [attr.height]="height - top - bottom" />
|
|
2817
|
+
}
|
|
2818
|
+
</svg>
|
|
2819
|
+
@if (cursorX != null && tipRows.length > 0 && hovered) {
|
|
2820
|
+
<div class="mv2c-tip" [style.left.px]="cursorX > width / 2 ? null : cursorX + 10" [style.right.px]="cursorX > width / 2 ? width - cursorX + 10 : null">
|
|
2821
|
+
<div class="mv2c-tip-time">{{tipTime}}</div>
|
|
2822
|
+
@for (row of tipRows; track $index) {
|
|
2823
|
+
<div class="mv2c-tip-row"><span class="mv2c-tip-dot" [style.background]="row.color"></span>{{row.label}} <strong>{{row.value}}</strong></div>
|
|
2824
|
+
}
|
|
2825
|
+
</div>
|
|
2826
|
+
}
|
|
2827
|
+
</div>
|
|
2828
|
+
`, isInline: true, styles: [":host{display:block;position:relative;-webkit-user-select:none;user-select:none}.mv2c{position:relative;cursor:crosshair}svg{display:block;overflow:visible}.mv2c-grid{stroke:var(--p-content-border-color, #e2e8f0);stroke-dasharray:2 3}.mv2c-axis{stroke:var(--p-content-border-color, #cbd5e1)}.mv2c-ylabel{fill:var(--p-text-muted-color, #64748b);font-size:10px;text-anchor:end}.mv2c-xlabel{fill:var(--p-text-muted-color, #64748b);font-size:10px;text-anchor:middle}.mv2c-unit{fill:var(--p-text-muted-color, #64748b);font-size:10px}.mv2c-mark{stroke-width:1}.mv2c-mark-restart{stroke:#f59e0b;stroke-dasharray:4 3}.mv2c-mark-snapshot{stroke:var(--p-content-border-color, #e2e8f0)}.mv2c-marklabel{fill:var(--p-text-muted-color, #64748b);font-size:9px}.mv2c-cursor{stroke:var(--p-text-muted-color, #64748b);stroke-width:1}.mv2c-drag{fill:var(--p-primary-color, #6366f1);fill-opacity:.12}.mv2c-tip{position:absolute;top:4px;z-index:3;min-width:140px;max-width:320px;padding:6px 8px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:6px;background:var(--p-content-background, #fff);box-shadow:0 4px 14px #0f172a1f;font-size:11px;pointer-events:none}.mv2c-tip-time{margin-bottom:3px;color:var(--p-text-muted-color, #64748b)}.mv2c-tip-row{display:flex;align-items:center;gap:5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mv2c-tip-row strong{margin-left:auto;padding-left:8px}.mv2c-tip-dot{flex:none;width:8px;height:8px;border-radius:50%}\n"] }); }
|
|
2829
|
+
}
|
|
2830
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: MetricsV2ChartComponent, decorators: [{
|
|
2831
|
+
type: Component,
|
|
2832
|
+
args: [{ selector: "mv2-chart", template: `
|
|
2833
|
+
<div class="mv2c" [style.height.px]="height" (mousemove)="move($event)" (mouseleave)="leave()" (mousedown)="down($event)" (mouseup)="up($event)">
|
|
2834
|
+
<svg [attr.width]="width" [attr.height]="height" [attr.viewBox]="'0 0 ' + width + ' ' + height">
|
|
2835
|
+
@for (tick of yTicks; track tick.key) {
|
|
2836
|
+
<line class="mv2c-grid" [attr.x1]="left" [attr.x2]="width - right" [attr.y1]="tick.pos" [attr.y2]="tick.pos" />
|
|
2837
|
+
<text class="mv2c-ylabel" [attr.x]="left - 6" [attr.y]="tick.pos + 3">{{tick.label}}</text>
|
|
2838
|
+
}
|
|
2839
|
+
@for (tick of xTicks; track tick.key) {
|
|
2840
|
+
<text class="mv2c-xlabel" [attr.x]="tick.pos" [attr.y]="height - 5">{{tick.label}}</text>
|
|
2841
|
+
}
|
|
2842
|
+
@if (unit) {
|
|
2843
|
+
<text class="mv2c-unit" [attr.x]="4" [attr.y]="11">{{unit}}</text>
|
|
2844
|
+
}
|
|
2845
|
+
@for (mark of markTicks; track mark.key) {
|
|
2846
|
+
<line class="mv2c-mark" [class.mv2c-mark-restart]="mark.kind == 'restart'" [class.mv2c-mark-snapshot]="mark.kind == 'snapshot'" [attr.x1]="mark.pos" [attr.x2]="mark.pos" [attr.y1]="top" [attr.y2]="height - bottom" />
|
|
2847
|
+
<text class="mv2c-marklabel" [attr.x]="mark.pos + 3" [attr.y]="top + 9">{{mark.label}}</text>
|
|
2848
|
+
}
|
|
2849
|
+
@for (p of paths; track p.key) {
|
|
2850
|
+
<path [attr.d]="p.d" [attr.fill]="p.fill ? p.color : 'none'" [attr.fill-opacity]="p.fill ? p.opacity : null"
|
|
2851
|
+
[attr.stroke]="p.fill ? 'none' : p.color" [attr.stroke-opacity]="p.fill ? null : p.opacity" [attr.stroke-dasharray]="p.dash" stroke-width="1.6" stroke-linejoin="round" />
|
|
2852
|
+
}
|
|
2853
|
+
@for (r of rects; track r.key) {
|
|
2854
|
+
<rect [attr.x]="r.x" [attr.y]="r.y" [attr.width]="r.w" [attr.height]="r.h" [attr.fill]="r.color" fill-opacity="0.75" />
|
|
2855
|
+
}
|
|
2856
|
+
@for (d of dots; track d.key) {
|
|
2857
|
+
<circle [attr.cx]="d.x" [attr.cy]="d.y" r="2" [attr.fill]="d.color" />
|
|
2858
|
+
}
|
|
2859
|
+
<line class="mv2c-axis" [attr.x1]="left" [attr.x2]="width - right" [attr.y1]="height - bottom" [attr.y2]="height - bottom" />
|
|
2860
|
+
@if (cursorX != null) {
|
|
2861
|
+
<line class="mv2c-cursor" [attr.x1]="cursorX" [attr.x2]="cursorX" [attr.y1]="top" [attr.y2]="height - bottom" />
|
|
2862
|
+
}
|
|
2863
|
+
@if (dragFrom != null && dragTo != null) {
|
|
2864
|
+
<rect class="mv2c-drag" [attr.x]="min(dragFrom, dragTo)" [attr.y]="top" [attr.width]="abs(dragTo - dragFrom)" [attr.height]="height - top - bottom" />
|
|
2865
|
+
}
|
|
2866
|
+
</svg>
|
|
2867
|
+
@if (cursorX != null && tipRows.length > 0 && hovered) {
|
|
2868
|
+
<div class="mv2c-tip" [style.left.px]="cursorX > width / 2 ? null : cursorX + 10" [style.right.px]="cursorX > width / 2 ? width - cursorX + 10 : null">
|
|
2869
|
+
<div class="mv2c-tip-time">{{tipTime}}</div>
|
|
2870
|
+
@for (row of tipRows; track $index) {
|
|
2871
|
+
<div class="mv2c-tip-row"><span class="mv2c-tip-dot" [style.background]="row.color"></span>{{row.label}} <strong>{{row.value}}</strong></div>
|
|
2872
|
+
}
|
|
2873
|
+
</div>
|
|
2874
|
+
}
|
|
2875
|
+
</div>
|
|
2876
|
+
`, standalone: false, styles: [":host{display:block;position:relative;-webkit-user-select:none;user-select:none}.mv2c{position:relative;cursor:crosshair}svg{display:block;overflow:visible}.mv2c-grid{stroke:var(--p-content-border-color, #e2e8f0);stroke-dasharray:2 3}.mv2c-axis{stroke:var(--p-content-border-color, #cbd5e1)}.mv2c-ylabel{fill:var(--p-text-muted-color, #64748b);font-size:10px;text-anchor:end}.mv2c-xlabel{fill:var(--p-text-muted-color, #64748b);font-size:10px;text-anchor:middle}.mv2c-unit{fill:var(--p-text-muted-color, #64748b);font-size:10px}.mv2c-mark{stroke-width:1}.mv2c-mark-restart{stroke:#f59e0b;stroke-dasharray:4 3}.mv2c-mark-snapshot{stroke:var(--p-content-border-color, #e2e8f0)}.mv2c-marklabel{fill:var(--p-text-muted-color, #64748b);font-size:9px}.mv2c-cursor{stroke:var(--p-text-muted-color, #64748b);stroke-width:1}.mv2c-drag{fill:var(--p-primary-color, #6366f1);fill-opacity:.12}.mv2c-tip{position:absolute;top:4px;z-index:3;min-width:140px;max-width:320px;padding:6px 8px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:6px;background:var(--p-content-background, #fff);box-shadow:0 4px 14px #0f172a1f;font-size:11px;pointer-events:none}.mv2c-tip-time{margin-bottom:3px;color:var(--p-text-muted-color, #64748b)}.mv2c-tip-row{display:flex;align-items:center;gap:5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mv2c-tip-row strong{margin-left:auto;padding-left:8px}.mv2c-tip-dot{flex:none;width:8px;height:8px;border-radius:50%}\n"] }]
|
|
2877
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.NgZone }], propDecorators: { series: [{
|
|
2878
|
+
type: Input
|
|
2879
|
+
}], from: [{
|
|
2880
|
+
type: Input
|
|
2881
|
+
}], to: [{
|
|
2882
|
+
type: Input
|
|
2883
|
+
}], height: [{
|
|
2884
|
+
type: Input
|
|
2885
|
+
}], unit: [{
|
|
2886
|
+
type: Input
|
|
2887
|
+
}], marks: [{
|
|
2888
|
+
type: Input
|
|
2889
|
+
}], cursor: [{
|
|
2890
|
+
type: Input
|
|
2891
|
+
}], format: [{
|
|
2892
|
+
type: Input
|
|
2893
|
+
}], cursorChange: [{
|
|
2894
|
+
type: Output
|
|
2895
|
+
}], zoom: [{
|
|
2896
|
+
type: Output
|
|
2897
|
+
}] } });
|
|
2898
|
+
|
|
2899
|
+
// Angular
|
|
2900
|
+
const STORAGE_KEY$1 = "HotSearchMetricsV2";
|
|
2901
|
+
const MAX_METRICS = 12;
|
|
2902
|
+
// Ogni istanza campiona ogni 15 secondi: più spesso i campioni non cambiano (M19)
|
|
2903
|
+
const REFRESH_MS = 15000;
|
|
2904
|
+
const SELECTION_DELAY_MS = 350;
|
|
2905
|
+
const DATES_DELAY_MS = 600;
|
|
2906
|
+
const TABLE_ROWS = 300;
|
|
2907
|
+
const INSTANCE_COLORS = ["#1e88e5", "#e53935", "#43a047", "#fb8c00", "#8e24aa", "#00897b", "#6d4c41", "#3949ab", "#c0ca33", "#d81b60"];
|
|
2908
|
+
const ITEM_COLORS = ["#3949ab", "#00897b", "#f4511e", "#8e24aa", "#7cb342", "#fdd835", "#6d4c41", "#039be5", "#d81b60", "#546e7a"];
|
|
2909
|
+
// "Active threads time" è un meter in microsecondi di thread attivi: il suo rate al secondo diviso un milione è il numero medio di thread attivi
|
|
2910
|
+
const ACTIVE_THREADS_TIME = "Active threads time";
|
|
2911
|
+
const CPU_USAGE = "CPU Usage";
|
|
2912
|
+
// flatMap non c'è nel lib di compilazione della libreria
|
|
2913
|
+
const flatten = (list, map) => list.reduce((all, t) => all.concat(map(t)), []);
|
|
2914
|
+
const PRESETS = [
|
|
2915
|
+
["Processo", ["CPU Usage", "Working set", "Private memory", "Thread Pool Queue Length"]],
|
|
2916
|
+
["API", ["API Call", "Requests", "400 Errors", "500 Errors"]],
|
|
2917
|
+
["Code asincrone", null],
|
|
2918
|
+
["Attività", ["Active sessions", "Active threads", ACTIVE_THREADS_TIME]]
|
|
2919
|
+
];
|
|
2920
|
+
class Mv2CatalogEntry {
|
|
2921
|
+
}
|
|
2922
|
+
class Mv2CatalogGroup {
|
|
2923
|
+
}
|
|
2924
|
+
class Mv2Chart {
|
|
2925
|
+
}
|
|
2926
|
+
class Mv2Panel {
|
|
2927
|
+
}
|
|
2928
|
+
class Mv2TileRow {
|
|
2929
|
+
}
|
|
2930
|
+
class Mv2Tile {
|
|
2931
|
+
}
|
|
2932
|
+
class Mv2Column {
|
|
2933
|
+
}
|
|
2934
|
+
class Mv2TableSection {
|
|
2935
|
+
}
|
|
2936
|
+
// Ricordate fra una visita e l'altra (M11)
|
|
2937
|
+
class MetricsV2Settings {
|
|
2938
|
+
}
|
|
2939
|
+
class AdmMetricsV2Component {
|
|
2940
|
+
constructor(dateExts, lc, localStorage, intercom, route, adms) {
|
|
2941
|
+
this.dateExts = dateExts;
|
|
2942
|
+
this.lc = lc;
|
|
2943
|
+
this.localStorage = localStorage;
|
|
2944
|
+
this.intercom = intercom;
|
|
2945
|
+
this.route = route;
|
|
2946
|
+
this.adms = adms;
|
|
2947
|
+
this.Max = MAX_METRICS;
|
|
2948
|
+
this.Type = MetricV2Type;
|
|
2949
|
+
// Comandi
|
|
2950
|
+
this.source = "live";
|
|
2951
|
+
this.liveMinutes = "30";
|
|
2952
|
+
this.filterfrom = "";
|
|
2953
|
+
this.filterto = "";
|
|
2954
|
+
this.snapFromId = "";
|
|
2955
|
+
this.snapToId = "";
|
|
2956
|
+
// Vuoto = tutto il cluster
|
|
2957
|
+
this.instances = [];
|
|
2958
|
+
this.view = "charts";
|
|
2959
|
+
this.paused = false;
|
|
2960
|
+
// Catalogo e selezione
|
|
2961
|
+
this.catalog = [];
|
|
2962
|
+
this.groups = [];
|
|
2963
|
+
this.search = "";
|
|
2964
|
+
this.selected = [];
|
|
2965
|
+
this.catalogLoading = false;
|
|
2966
|
+
// Snapshot
|
|
2967
|
+
this.snapshots = [];
|
|
2968
|
+
this.historyAvailable = true;
|
|
2969
|
+
this.snapshotsLoaded = false;
|
|
2970
|
+
this.SnapshotOptions = [];
|
|
2971
|
+
this.warnings = [];
|
|
2972
|
+
// Dati
|
|
2973
|
+
this.dataLoading = false;
|
|
2974
|
+
this.panels = [];
|
|
2975
|
+
this.tiles = [];
|
|
2976
|
+
this.tables = [];
|
|
2977
|
+
this.marks = [];
|
|
2978
|
+
this.legend = [];
|
|
2979
|
+
this.viewFrom = null;
|
|
2980
|
+
this.viewTo = null;
|
|
2981
|
+
this.cursor = null;
|
|
2982
|
+
this.Sources = [];
|
|
2983
|
+
this.LiveWindows = [];
|
|
2984
|
+
this.Views = [];
|
|
2985
|
+
this.Instances = [];
|
|
2986
|
+
this.Presets = PRESETS.map(t => t[0]);
|
|
2987
|
+
this.data = new Map();
|
|
2988
|
+
this.seen = new Set();
|
|
2989
|
+
this.hidden = new Set();
|
|
2990
|
+
this.alts = {};
|
|
2991
|
+
this.dataGeneration = 0;
|
|
2992
|
+
this.catalogGeneration = 0;
|
|
2993
|
+
this.visibilityHandler = () => this.visibilityChanged();
|
|
2994
|
+
this.format = (value) => this.number(value);
|
|
2995
|
+
this.be = route.snapshot.params["be"] || "";
|
|
2996
|
+
this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
|
|
2997
|
+
}
|
|
2998
|
+
async ngOnInit() {
|
|
2999
|
+
this.Sources = [new KeyValue("live", "Live"), new KeyValue("period", this.lc.loc("Storico per periodo")), new KeyValue("snap", this.lc.loc("Storico per snapshot"))];
|
|
3000
|
+
this.LiveWindows = [new KeyValue("15", "15 min"), new KeyValue("30", "30 min"), new KeyValue("60", this.lc.loc("1 ora"))];
|
|
3001
|
+
this.Views = [new KeyValue("charts", this.lc.loc("Grafici")), new KeyValue("table", this.lc.loc("Tabella"))];
|
|
3002
|
+
let saved = this.route.snapshot.queryParams["ignoreStorage"] ? null : await firstValueFrom(this.localStorage.getItem(STORAGE_KEY$1), { defaultValue: null });
|
|
3003
|
+
if (saved) {
|
|
3004
|
+
this.selected = (saved.selected ?? []).slice(0, MAX_METRICS);
|
|
3005
|
+
this.source = saved.source ?? this.source;
|
|
3006
|
+
this.instances = saved.instances ?? this.instances;
|
|
3007
|
+
this.liveMinutes = saved.liveMinutes ?? this.liveMinutes;
|
|
3008
|
+
this.view = saved.view ?? this.view;
|
|
3009
|
+
}
|
|
3010
|
+
this.setLastDay();
|
|
3011
|
+
this.adms.getInstances(this.be).then(t => {
|
|
3012
|
+
this.Instances = t ?? [];
|
|
3013
|
+
this.rebuild();
|
|
3014
|
+
});
|
|
3015
|
+
this.loadSnapshots();
|
|
3016
|
+
await this.loadCatalog();
|
|
3017
|
+
this.loadData();
|
|
3018
|
+
this.refreshTimer = setInterval(() => this.refresh(), REFRESH_MS);
|
|
3019
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
3020
|
+
}
|
|
3021
|
+
ngOnDestroy() {
|
|
3022
|
+
clearInterval(this.refreshTimer);
|
|
3023
|
+
clearTimeout(this.selectionTimer);
|
|
3024
|
+
clearTimeout(this.datesTimer);
|
|
3025
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
3026
|
+
}
|
|
3027
|
+
// #region Comandi
|
|
3028
|
+
get clusterLabel() {
|
|
3029
|
+
return this.Instances.length ? `${this.lc.loc("Tutto il cluster")} (${this.Instances.length} ${this.Instances.length == 1 ? this.lc.loc("istanza") : this.lc.loc("istanze")})` : this.lc.loc("Tutto il cluster");
|
|
3030
|
+
}
|
|
3031
|
+
get zone() {
|
|
3032
|
+
return `UTC${dayjs().format("Z")}`;
|
|
3033
|
+
}
|
|
3034
|
+
setSource(id) {
|
|
3035
|
+
if (id != "live" && !this.historyAvailable)
|
|
3036
|
+
return;
|
|
3037
|
+
this.source = id;
|
|
3038
|
+
if (this.source == "snap")
|
|
3039
|
+
this.defaultSnapshots();
|
|
3040
|
+
this.updateWarnings();
|
|
3041
|
+
this.saveSettings();
|
|
3042
|
+
this.loadData();
|
|
3043
|
+
}
|
|
3044
|
+
setLiveMinutes(id) {
|
|
3045
|
+
this.liveMinutes = id;
|
|
3046
|
+
this.saveSettings();
|
|
3047
|
+
this.loadData();
|
|
3048
|
+
}
|
|
3049
|
+
datesChanged() {
|
|
3050
|
+
clearTimeout(this.datesTimer);
|
|
3051
|
+
this.datesTimer = setTimeout(() => {
|
|
3052
|
+
if (this.toDate(this.filterfrom) && this.toDate(this.filterto))
|
|
3053
|
+
this.loadData();
|
|
3054
|
+
}, DATES_DELAY_MS);
|
|
3055
|
+
}
|
|
3056
|
+
snapshotsChanged() {
|
|
3057
|
+
this.updateWarnings();
|
|
3058
|
+
this.loadData();
|
|
3059
|
+
}
|
|
3060
|
+
// Cambiando istanze si ricaricano catalogo, snapshot e dati; le metriche che il catalogo non ha più si tolgono
|
|
3061
|
+
async instancesChanged(ids) {
|
|
3062
|
+
this.instances = ids ?? [];
|
|
3063
|
+
this.saveSettings();
|
|
3064
|
+
this.loadSnapshots();
|
|
3065
|
+
await this.loadCatalog();
|
|
3066
|
+
this.loadData();
|
|
3067
|
+
}
|
|
3068
|
+
setView(id) {
|
|
3069
|
+
this.view = id;
|
|
3070
|
+
this.saveSettings();
|
|
3071
|
+
}
|
|
3072
|
+
togglePause() {
|
|
3073
|
+
this.paused = !this.paused;
|
|
3074
|
+
if (!this.paused)
|
|
3075
|
+
this.refresh();
|
|
3076
|
+
}
|
|
3077
|
+
// #endregion
|
|
3078
|
+
// #region Catalogo e selezione
|
|
3079
|
+
async loadCatalog() {
|
|
3080
|
+
let generation = ++this.catalogGeneration;
|
|
3081
|
+
this.catalogLoading = true;
|
|
3082
|
+
this.catalogError = null;
|
|
3083
|
+
try {
|
|
3084
|
+
let req = this.instances.length ? { instanceIds: this.instances } : { global: true };
|
|
3085
|
+
let res = await this.adms.getMetricsCatalogV2(this.be, req);
|
|
3086
|
+
if (generation != this.catalogGeneration)
|
|
3087
|
+
return;
|
|
3088
|
+
this.catalog = res ?? [];
|
|
3089
|
+
let names = new Set(this.catalog.map(t => t.name));
|
|
3090
|
+
let kept = this.selected.filter(t => names.has(t));
|
|
3091
|
+
if (kept.length != this.selected.length) {
|
|
3092
|
+
this.selected = kept;
|
|
3093
|
+
this.saveSettings();
|
|
3094
|
+
}
|
|
3095
|
+
this.buildGroups();
|
|
3096
|
+
}
|
|
3097
|
+
catch (err) {
|
|
3098
|
+
if (generation == this.catalogGeneration)
|
|
3099
|
+
this.catalogError = this.errorMessage(err);
|
|
3100
|
+
}
|
|
3101
|
+
finally {
|
|
3102
|
+
if (generation == this.catalogGeneration)
|
|
3103
|
+
this.catalogLoading = false;
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
// Gruppi per tipo; le voci di un totale stanno sotto il totale, chiuse (M10)
|
|
3107
|
+
buildGroups() {
|
|
3108
|
+
let text = (this.search ?? "").trim().toLowerCase();
|
|
3109
|
+
let matches = (info) => !text || info.name.toLowerCase().includes(text);
|
|
3110
|
+
let parentOf = (info) => this.catalog.filter(t => t != info && info.name.startsWith(t.name + " - ")).sort((a, b) => b.name.length - a.name.length)[0];
|
|
3111
|
+
let opened = new Set(flatten(this.groups, g => g.entries.filter(e => e.open).map(e => e.info.name)));
|
|
3112
|
+
let types = [MetricV2Type.Gauge, MetricV2Type.Counter, MetricV2Type.Meter, MetricV2Type.Histogram, MetricV2Type.Timer];
|
|
3113
|
+
this.groups = types.map(type => {
|
|
3114
|
+
let infos = this.catalog.filter(t => t.metricType == type);
|
|
3115
|
+
let entries = infos.filter(t => !parentOf(t)).map(info => {
|
|
3116
|
+
let items = infos.filter(t => parentOf(t) == info);
|
|
3117
|
+
return { info: info, items: items.filter(t => matches(t) || matches(info)), open: opened.has(info.name) || (!!text && items.some(matches)) };
|
|
3118
|
+
}).filter(t => matches(t.info) || t.items.length > 0);
|
|
3119
|
+
return { type: type, label: MetricV2Type[type], entries: entries, count: infos.length };
|
|
3120
|
+
}).filter(t => t.entries.length > 0);
|
|
3121
|
+
}
|
|
3122
|
+
isSelected(name) {
|
|
3123
|
+
return this.selected.includes(name);
|
|
3124
|
+
}
|
|
3125
|
+
canSelect(name) {
|
|
3126
|
+
return this.isSelected(name) || this.selected.length < MAX_METRICS;
|
|
3127
|
+
}
|
|
3128
|
+
toggleMetric(name) {
|
|
3129
|
+
if (this.isSelected(name))
|
|
3130
|
+
this.selected = this.selected.filter(t => t != name);
|
|
3131
|
+
else if (this.selected.length < MAX_METRICS)
|
|
3132
|
+
this.selected = [...this.selected, name];
|
|
3133
|
+
this.selectionChanged();
|
|
3134
|
+
}
|
|
3135
|
+
addItems(entry) {
|
|
3136
|
+
let missing = [entry.info, ...entry.items].map(t => t.name).filter(t => !this.isSelected(t));
|
|
3137
|
+
this.selected = [...this.selected, ...missing].slice(0, MAX_METRICS);
|
|
3138
|
+
entry.open = true;
|
|
3139
|
+
this.selectionChanged();
|
|
3140
|
+
}
|
|
3141
|
+
clearSelection() {
|
|
3142
|
+
this.selected = [];
|
|
3143
|
+
this.selectionChanged();
|
|
3144
|
+
}
|
|
3145
|
+
// Insiemi pronti (M9): le metriche che il catalogo non ha si ignorano
|
|
3146
|
+
applyPreset(name) {
|
|
3147
|
+
let preset = PRESETS.find(t => t[0] == name);
|
|
3148
|
+
let names = preset[1] ?? this.catalog.filter(t => t.name.startsWith("AQ - ") && !this.catalog.some(k => k != t && t.name.startsWith(k.name + " - "))).map(t => t.name);
|
|
3149
|
+
let available = new Set(this.catalog.map(t => t.name));
|
|
3150
|
+
this.selected = names.filter(t => available.has(t)).slice(0, MAX_METRICS);
|
|
3151
|
+
this.selectionChanged();
|
|
3152
|
+
}
|
|
3153
|
+
removeMetric(name) {
|
|
3154
|
+
this.selected = this.selected.filter(t => t != name);
|
|
3155
|
+
this.selectionChanged();
|
|
3156
|
+
}
|
|
3157
|
+
// Più clic di seguito diventano una sola richiesta
|
|
3158
|
+
selectionChanged() {
|
|
3159
|
+
this.saveSettings();
|
|
3160
|
+
clearTimeout(this.selectionTimer);
|
|
3161
|
+
this.selectionTimer = setTimeout(() => this.loadData(), SELECTION_DELAY_MS);
|
|
3162
|
+
}
|
|
3163
|
+
catalogUnit(info) {
|
|
3164
|
+
if (info.name == CPU_USAGE)
|
|
3165
|
+
return "CPU";
|
|
3166
|
+
let unit = info.unit || "";
|
|
3167
|
+
if (info.metricType == MetricV2Type.Timer && info.durationUnit)
|
|
3168
|
+
return [unit, this.durationLabel(info.durationUnit)].filter(t => t).join(" · ");
|
|
3169
|
+
return unit;
|
|
3170
|
+
}
|
|
3171
|
+
addItemsLabel(entry) {
|
|
3172
|
+
return this.lc.loc("Aggiungi le {N} voci").replace("{N}", String(entry.items.length));
|
|
3173
|
+
}
|
|
3174
|
+
emptyLabel(name) {
|
|
3175
|
+
return this.lc.loc("Nessun campione di {metrica} nella finestra scelta").replace("{metrica}", name);
|
|
3176
|
+
}
|
|
3177
|
+
// "2 di 3" se la metrica non c'è su tutte le istanze
|
|
3178
|
+
instancesLabel(info) {
|
|
3179
|
+
let total = this.instances.length || this.Instances.length;
|
|
3180
|
+
return total && info.instances?.length < total ? `${info.instances.length} ${this.lc.loc("di")} ${total}` : "";
|
|
3181
|
+
}
|
|
3182
|
+
// #endregion
|
|
3183
|
+
// #region Snapshot
|
|
3184
|
+
async loadSnapshots() {
|
|
3185
|
+
try {
|
|
3186
|
+
let req = new WorkloadSnapshotsRequest();
|
|
3187
|
+
req.from = new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString();
|
|
3188
|
+
if (this.instances.length)
|
|
3189
|
+
req.instanceIds = this.instances;
|
|
3190
|
+
let res = await this.adms.getWorkloadSnapshots(this.be, req);
|
|
3191
|
+
this.historyAvailable = res?.historyAvailable ?? false;
|
|
3192
|
+
this.snapshots = res?.snapshots ?? [];
|
|
3193
|
+
}
|
|
3194
|
+
catch {
|
|
3195
|
+
// Senza elenco degli snapshot resta il live: le sorgenti storiche restano disponibili per periodo
|
|
3196
|
+
this.snapshots = [];
|
|
3197
|
+
}
|
|
3198
|
+
this.snapshotsLoaded = true;
|
|
3199
|
+
this.SnapshotOptions = this.snapshots.map(t => new KeyValue(String(t.snapId), `${t.snapId} · ${this.local(t.endIntervalTime)}${t.restart ? " ⟲" : ""}${t.manual ? " ✋" : ""}`));
|
|
3200
|
+
if (!this.historyAvailable && this.source != "live") {
|
|
3201
|
+
this.source = "live";
|
|
3202
|
+
this.loadData();
|
|
3203
|
+
}
|
|
3204
|
+
let ids = new Set(this.snapshots.map(t => String(t.snapId)));
|
|
3205
|
+
if (!ids.has(this.snapFromId) || !ids.has(this.snapToId))
|
|
3206
|
+
this.defaultSnapshots();
|
|
3207
|
+
this.updateWarnings();
|
|
3208
|
+
}
|
|
3209
|
+
// Di default le ultime 12 ore di snapshot
|
|
3210
|
+
defaultSnapshots() {
|
|
3211
|
+
if (this.snapshots.length == 0 || (this.snapFromId && this.snapToId && this.snapshots.some(t => String(t.snapId) == this.snapFromId)))
|
|
3212
|
+
return;
|
|
3213
|
+
this.snapToId = String(this.snapshots[0].snapId);
|
|
3214
|
+
this.snapFromId = String(this.snapshots[Math.min(11, this.snapshots.length - 1)].snapId);
|
|
3215
|
+
}
|
|
3216
|
+
get snapshotWindow() {
|
|
3217
|
+
let ids = [+this.snapFromId, +this.snapToId];
|
|
3218
|
+
let first = this.snapshots.find(t => t.snapId == Math.min(...ids));
|
|
3219
|
+
let last = this.snapshots.find(t => t.snapId == Math.max(...ids));
|
|
3220
|
+
return first && last ? { from: this.parseUtc(first.beginIntervalTime), to: this.parseUtc(last.endIntervalTime) } : null;
|
|
3221
|
+
}
|
|
3222
|
+
// M4: un riavvio fra i due snapshot interrompe le serie
|
|
3223
|
+
updateWarnings() {
|
|
3224
|
+
let warnings = [];
|
|
3225
|
+
if (this.source == "snap" && this.snapFromId && this.snapToId) {
|
|
3226
|
+
let lo = Math.min(+this.snapFromId, +this.snapToId);
|
|
3227
|
+
let hi = Math.max(+this.snapFromId, +this.snapToId);
|
|
3228
|
+
this.snapshots.forEach((s, i) => {
|
|
3229
|
+
if (s.restart && s.snapId > lo && s.snapId <= hi) {
|
|
3230
|
+
let previous = this.snapshots[i + 1];
|
|
3231
|
+
let restarted = (s.instances ?? []).filter(t => {
|
|
3232
|
+
let before = previous?.instances?.find(k => k.instanceId == t.instanceId);
|
|
3233
|
+
return before ? before.startupTime != t.startupTime : true;
|
|
3234
|
+
}).map(t => t.instanceId);
|
|
3235
|
+
for (let instance of restarted)
|
|
3236
|
+
warnings.push(this.lc.loc("Lo snapshot {id} segue un riavvio di {istanza}: lì le serie si interrompono.").replace("{id}", String(s.snapId)).replace("{istanza}", instance));
|
|
3237
|
+
}
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3240
|
+
this.warnings = warnings;
|
|
3241
|
+
}
|
|
3242
|
+
// #endregion
|
|
3243
|
+
// #region Dati
|
|
3244
|
+
request() {
|
|
3245
|
+
let req = new MetricsV2Request();
|
|
3246
|
+
req.metricNames = [...this.selected];
|
|
3247
|
+
// Mai global=false senza istanze (M6)
|
|
3248
|
+
if (this.instances.length)
|
|
3249
|
+
req.instanceIds = [...this.instances];
|
|
3250
|
+
else
|
|
3251
|
+
req.global = true;
|
|
3252
|
+
if (this.source == "live") {
|
|
3253
|
+
req.realTime = true;
|
|
3254
|
+
req.snapFrom = new Date(Date.now() - +this.liveMinutes * 60000).toISOString();
|
|
3255
|
+
}
|
|
3256
|
+
else if (this.source == "period") {
|
|
3257
|
+
req.realTime = false;
|
|
3258
|
+
req.snapFrom = this.toDate(this.filterfrom)?.toISOString();
|
|
3259
|
+
req.snapTo = this.toDate(this.filterto)?.toISOString();
|
|
3260
|
+
}
|
|
3261
|
+
else {
|
|
3262
|
+
req.realTime = false;
|
|
3263
|
+
req.snapIdFrom = Math.min(+this.snapFromId, +this.snapToId);
|
|
3264
|
+
req.snapIdTo = Math.max(+this.snapFromId, +this.snapToId);
|
|
3265
|
+
}
|
|
3266
|
+
return req;
|
|
3267
|
+
}
|
|
3268
|
+
// Caricamento completo della finestra scelta; senza metriche scelte non parte nessuna chiamata (M8)
|
|
3269
|
+
async loadData() {
|
|
3270
|
+
let generation = ++this.dataGeneration;
|
|
3271
|
+
clearTimeout(this.selectionTimer);
|
|
3272
|
+
this.viewFrom = null;
|
|
3273
|
+
this.viewTo = null;
|
|
3274
|
+
this.dataError = null;
|
|
3275
|
+
if (this.selected.length == 0 || (this.source == "snap" && (!this.snapFromId || !this.snapToId))) {
|
|
3276
|
+
this.data = new Map();
|
|
3277
|
+
this.seen = new Set();
|
|
3278
|
+
this.rebuild();
|
|
3279
|
+
return;
|
|
3280
|
+
}
|
|
3281
|
+
let req = this.request();
|
|
3282
|
+
this.setDomain(req);
|
|
3283
|
+
this.dataLoading = true;
|
|
3284
|
+
try {
|
|
3285
|
+
let res = await this.adms.getMetricsV2(this.be, req);
|
|
3286
|
+
if (generation != this.dataGeneration)
|
|
3287
|
+
return;
|
|
3288
|
+
this.data = new Map();
|
|
3289
|
+
this.seen = new Set();
|
|
3290
|
+
this.merge(res ?? []);
|
|
3291
|
+
this.lastUpdate = new Date();
|
|
3292
|
+
this.rebuild();
|
|
3293
|
+
}
|
|
3294
|
+
catch (err) {
|
|
3295
|
+
if (generation == this.dataGeneration) {
|
|
3296
|
+
this.dataError = this.errorMessage(err);
|
|
3297
|
+
this.data = new Map();
|
|
3298
|
+
this.rebuild();
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
finally {
|
|
3302
|
+
if (generation == this.dataGeneration)
|
|
3303
|
+
this.dataLoading = false;
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
// M19: solo i campioni nuovi, da quello più vecchio fra gli ultimi delle istanze in pagina; sullo storico niente aggiornamento
|
|
3307
|
+
async refresh() {
|
|
3308
|
+
if (this.source != "live" || this.paused || document.hidden || this.dataLoading || this.selected.length == 0)
|
|
3309
|
+
return;
|
|
3310
|
+
let lastByInstance = new Map();
|
|
3311
|
+
this.data.forEach(byInstance => byInstance.forEach((list, instance) => {
|
|
3312
|
+
let last = list.length ? this.time(list[list.length - 1]) : null;
|
|
3313
|
+
if (last != null)
|
|
3314
|
+
lastByInstance.set(instance, Math.max(lastByInstance.get(instance) ?? 0, last));
|
|
3315
|
+
}));
|
|
3316
|
+
if (lastByInstance.size == 0)
|
|
3317
|
+
return this.loadData();
|
|
3318
|
+
let generation = this.dataGeneration;
|
|
3319
|
+
let req = this.request();
|
|
3320
|
+
req.snapFrom = new Date(Math.min(...lastByInstance.values())).toISOString();
|
|
3321
|
+
try {
|
|
3322
|
+
let res = await this.adms.getMetricsV2(this.be, req);
|
|
3323
|
+
if (generation != this.dataGeneration)
|
|
3324
|
+
return;
|
|
3325
|
+
this.merge(res ?? []);
|
|
3326
|
+
this.setDomain(this.request());
|
|
3327
|
+
// Fuori dalla finestra live si scartano
|
|
3328
|
+
this.data.forEach(byInstance => byInstance.forEach((list, instance) => byInstance.set(instance, list.filter(t => this.time(t) >= this.domainFrom))));
|
|
3329
|
+
this.lastUpdate = new Date();
|
|
3330
|
+
this.rebuild();
|
|
3331
|
+
}
|
|
3332
|
+
catch {
|
|
3333
|
+
// Un aggiornamento mancato si recupera al giro dopo
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
// Nella fusione si tiene la versione già in pagina dei campioni ripetuti, che ha le differenze (chiave istanza, metrica, istante)
|
|
3337
|
+
merge(samples) {
|
|
3338
|
+
for (let s of samples) {
|
|
3339
|
+
let key = `${s.instanceId}|${s.name}|${s.sampleTime}`;
|
|
3340
|
+
if (this.seen.has(key))
|
|
3341
|
+
continue;
|
|
3342
|
+
this.seen.add(key);
|
|
3343
|
+
if (!this.data.has(s.name))
|
|
3344
|
+
this.data.set(s.name, new Map());
|
|
3345
|
+
let byInstance = this.data.get(s.name);
|
|
3346
|
+
let instance = s.instanceId ?? "";
|
|
3347
|
+
if (!byInstance.has(instance))
|
|
3348
|
+
byInstance.set(instance, []);
|
|
3349
|
+
byInstance.get(instance).push(s);
|
|
3350
|
+
}
|
|
3351
|
+
this.data.forEach(byInstance => byInstance.forEach(list => list.sort((a, b) => this.time(a) - this.time(b))));
|
|
3352
|
+
}
|
|
3353
|
+
setDomain(req) {
|
|
3354
|
+
if (this.source == "live") {
|
|
3355
|
+
this.domainTo = Date.now();
|
|
3356
|
+
this.domainFrom = this.domainTo - +this.liveMinutes * 60000;
|
|
3357
|
+
}
|
|
3358
|
+
else if (this.source == "period") {
|
|
3359
|
+
this.domainFrom = new Date(req.snapFrom ?? Date.now() - 24 * 3600 * 1000).getTime();
|
|
3360
|
+
this.domainTo = new Date(req.snapTo ?? Date.now()).getTime();
|
|
3361
|
+
}
|
|
3362
|
+
else {
|
|
3363
|
+
let window = this.snapshotWindow;
|
|
3364
|
+
this.domainFrom = window?.from?.getTime() ?? Date.now() - 12 * 3600 * 1000;
|
|
3365
|
+
// I campioni sono alla fine degli snapshot (ogni istanza con i suoi secondi)
|
|
3366
|
+
this.domainTo = (window?.to?.getTime() ?? Date.now()) + 60000;
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
// #endregion
|
|
3370
|
+
// #region Grafici, tessere e tabella
|
|
3371
|
+
get chartFrom() { return this.viewFrom ?? this.domainFrom; }
|
|
3372
|
+
get chartTo() { return this.viewTo ?? this.domainTo; }
|
|
3373
|
+
zoomTo(range) {
|
|
3374
|
+
this.viewFrom = range.from;
|
|
3375
|
+
this.viewTo = range.to;
|
|
3376
|
+
}
|
|
3377
|
+
resetZoom() {
|
|
3378
|
+
this.viewFrom = null;
|
|
3379
|
+
this.viewTo = null;
|
|
3380
|
+
}
|
|
3381
|
+
toggleInstance(instance) {
|
|
3382
|
+
if (this.hidden.has(instance))
|
|
3383
|
+
this.hidden.delete(instance);
|
|
3384
|
+
else
|
|
3385
|
+
this.hidden.add(instance);
|
|
3386
|
+
this.rebuild();
|
|
3387
|
+
}
|
|
3388
|
+
setAlt(panel, alt) {
|
|
3389
|
+
this.alts[panel.info.name] = alt;
|
|
3390
|
+
this.rebuild();
|
|
3391
|
+
}
|
|
3392
|
+
info(name) {
|
|
3393
|
+
return this.catalog.find(t => t.name == name) ?? { name: name, metricType: MetricV2Type.Gauge, unit: "", rateUnit: null, durationUnit: null, instances: [] };
|
|
3394
|
+
}
|
|
3395
|
+
// Colore fisso per istanza, lo stesso in tutti i pannelli
|
|
3396
|
+
color(instance) {
|
|
3397
|
+
let all = [...new Set([...this.Instances.map(t => t.id), ...flatten(this.catalog, t => t.instances ?? [])])].sort();
|
|
3398
|
+
let index = all.indexOf(instance);
|
|
3399
|
+
return INSTANCE_COLORS[(index >= 0 ? index : all.length) % INSTANCE_COLORS.length];
|
|
3400
|
+
}
|
|
3401
|
+
rebuild() {
|
|
3402
|
+
let instances = new Set();
|
|
3403
|
+
this.data.forEach(byInstance => byInstance.forEach((_, instance) => instances.add(instance)));
|
|
3404
|
+
this.legend = [...instances].sort().map(t => ({ instance: t, color: this.color(t), hidden: this.hidden.has(t) }));
|
|
3405
|
+
this.marks = this.buildMarks();
|
|
3406
|
+
this.panels = this.selected.map(name => this.buildPanel(this.info(name)));
|
|
3407
|
+
this.tiles = this.selected.map(name => this.buildTile(this.info(name)));
|
|
3408
|
+
this.tables = this.selected.map(name => this.buildTable(this.info(name), this.tables.find(t => t.info.name == name)));
|
|
3409
|
+
}
|
|
3410
|
+
visibleSeries(name) {
|
|
3411
|
+
let byInstance = this.data.get(name);
|
|
3412
|
+
return byInstance ? [...byInstance.entries()].filter(t => !this.hidden.has(t[0]) && t[1].length > 0).sort((a, b) => a[0].localeCompare(b[0])) : [];
|
|
3413
|
+
}
|
|
3414
|
+
// M14: riavvii (un campione senza intervallo che non è il primo della serie) e, sullo storico per snapshot, gli id degli snapshot
|
|
3415
|
+
buildMarks() {
|
|
3416
|
+
let marks = [];
|
|
3417
|
+
let seen = new Set();
|
|
3418
|
+
this.data.forEach(byInstance => byInstance.forEach((list, instance) => list.forEach((s, i) => {
|
|
3419
|
+
if (i > 0 && s.intervalSeconds == null) {
|
|
3420
|
+
let t = this.time(s);
|
|
3421
|
+
let key = `${instance}|${Math.round(t / 60000)}`;
|
|
3422
|
+
if (!seen.has(key)) {
|
|
3423
|
+
seen.add(key);
|
|
3424
|
+
marks.push({ t: t, label: this.lc.loc("riavvio"), kind: "restart" });
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
})));
|
|
3428
|
+
if (this.source == "snap") {
|
|
3429
|
+
let bySnap = new Map();
|
|
3430
|
+
this.data.forEach(byInstance => byInstance.forEach(list => list.forEach(s => {
|
|
3431
|
+
if (s.snapId != null && !bySnap.has(s.snapId))
|
|
3432
|
+
bySnap.set(s.snapId, this.time(s));
|
|
3433
|
+
})));
|
|
3434
|
+
[...bySnap.entries()].sort((a, b) => a[1] - b[1]).forEach(([id, t]) => marks.push({ t: t, label: String(id), kind: "snapshot" }));
|
|
3435
|
+
}
|
|
3436
|
+
return marks;
|
|
3437
|
+
}
|
|
3438
|
+
// Punti di una serie: si salta dove il valore manca e si interrompe la linea a un riavvio o se fra due campioni passa più del doppio dell'intervallo tipico
|
|
3439
|
+
points(list, value, upper) {
|
|
3440
|
+
let times = list.map(t => this.time(t));
|
|
3441
|
+
let gaps = times.slice(1).map((t, i) => t - times[i]).sort((a, b) => a - b);
|
|
3442
|
+
let typical = gaps.length ? gaps[Math.floor(gaps.length / 2)] : 0;
|
|
3443
|
+
let out = [];
|
|
3444
|
+
let pendingGap = false;
|
|
3445
|
+
list.forEach((s, i) => {
|
|
3446
|
+
let v = value(s);
|
|
3447
|
+
let gap = i > 0 && (s.intervalSeconds == null || (typical > 0 && times[i] - times[i - 1] > 2 * typical));
|
|
3448
|
+
if (v == null || isNaN(v)) {
|
|
3449
|
+
pendingGap = true;
|
|
3450
|
+
return;
|
|
3451
|
+
}
|
|
3452
|
+
out.push({ t: times[i], v: v, v2: upper ? upper(s) : undefined, gap: gap || pendingGap });
|
|
3453
|
+
pendingGap = false;
|
|
3454
|
+
});
|
|
3455
|
+
return out;
|
|
3456
|
+
}
|
|
3457
|
+
// Valore principale per tipo (tessere e grafico di default)
|
|
3458
|
+
main(info, s) {
|
|
3459
|
+
switch (info.metricType) {
|
|
3460
|
+
case MetricV2Type.Gauge: return s.value;
|
|
3461
|
+
case MetricV2Type.Counter: return s.count;
|
|
3462
|
+
case MetricV2Type.Meter: return info.name == ACTIVE_THREADS_TIME ? (s.ratePerSecond == null ? null : s.ratePerSecond / 1000000) : s.ratePerSecond;
|
|
3463
|
+
case MetricV2Type.Histogram: return s.median;
|
|
3464
|
+
case MetricV2Type.Timer: return s.intervalMean;
|
|
3465
|
+
}
|
|
3466
|
+
return s.value;
|
|
3467
|
+
}
|
|
3468
|
+
mainUnit(info) {
|
|
3469
|
+
switch (info.metricType) {
|
|
3470
|
+
case MetricV2Type.Gauge: return info.name == CPU_USAGE ? "CPU" : info.unit || "";
|
|
3471
|
+
case MetricV2Type.Counter: return info.unit || "";
|
|
3472
|
+
case MetricV2Type.Meter: return info.name == ACTIVE_THREADS_TIME ? this.lc.loc("thread") : `${info.unit || ""}/s`;
|
|
3473
|
+
case MetricV2Type.Histogram: return info.unit || "";
|
|
3474
|
+
case MetricV2Type.Timer: return this.durationLabel(info.durationUnit);
|
|
3475
|
+
}
|
|
3476
|
+
return info.unit || "";
|
|
3477
|
+
}
|
|
3478
|
+
buildPanel(info) {
|
|
3479
|
+
let series = this.visibleSeries(info.name);
|
|
3480
|
+
let items = this.itemsOf(info);
|
|
3481
|
+
let alts = [];
|
|
3482
|
+
switch (info.metricType) {
|
|
3483
|
+
case MetricV2Type.Counter:
|
|
3484
|
+
alts = [new KeyValue("level", this.lc.loc("Livello")), new KeyValue("delta", this.lc.loc("Variazione"))];
|
|
3485
|
+
if (items.length) {
|
|
3486
|
+
alts.push(new KeyValue("items", this.lc.loc("Voci")));
|
|
3487
|
+
if (this.source == "live")
|
|
3488
|
+
alts.push(new KeyValue("share", this.lc.loc("Quote")));
|
|
3489
|
+
}
|
|
3490
|
+
break;
|
|
3491
|
+
case MetricV2Type.Meter:
|
|
3492
|
+
alts = [new KeyValue("rate", this.lc.loc("Al secondo")), new KeyValue("moving", this.lc.loc("Rate mobili"))];
|
|
3493
|
+
if (items.length)
|
|
3494
|
+
alts.push(new KeyValue("items", this.lc.loc("Voci")));
|
|
3495
|
+
break;
|
|
3496
|
+
case MetricV2Type.Histogram:
|
|
3497
|
+
alts = [new KeyValue("percentiles", this.lc.loc("Percentili")), new KeyValue("rate", this.lc.loc("Frequenza"))];
|
|
3498
|
+
break;
|
|
3499
|
+
case MetricV2Type.Timer:
|
|
3500
|
+
alts = [new KeyValue("duration", this.lc.loc("Chiamate e durata")), new KeyValue("active", this.lc.loc("In corso"))];
|
|
3501
|
+
break;
|
|
3502
|
+
}
|
|
3503
|
+
let alt = alts.find(t => t.id == this.alts[info.name])?.id ?? alts[0]?.id ?? "";
|
|
3504
|
+
return { info: info, alts: alts, alt: alt, charts: series.length ? this.buildCharts(info, alt, series, items) : [], empty: series.length == 0 };
|
|
3505
|
+
}
|
|
3506
|
+
// Le voci scelte di un totale, per l'area impilata e le quote
|
|
3507
|
+
itemsOf(info) {
|
|
3508
|
+
if (info.metricType != MetricV2Type.Counter && info.metricType != MetricV2Type.Meter)
|
|
3509
|
+
return [];
|
|
3510
|
+
return this.selected.filter(t => t.startsWith(info.name + " - ") && !this.selected.some(k => k != info.name && t.startsWith(k + " - ") && k.startsWith(info.name + " - "))).map(t => this.info(t));
|
|
3511
|
+
}
|
|
3512
|
+
buildCharts(info, alt, series, items) {
|
|
3513
|
+
let line = (key, instance, points, extra = {}) => ({ key: `${key}|${instance}`, label: instance, color: this.color(instance), kind: "line", points: points, ...extra });
|
|
3514
|
+
let unit = info.unit || "";
|
|
3515
|
+
let rateUnit = `${unit}${this.rateLabel(info.rateUnit)}`;
|
|
3516
|
+
switch (info.metricType) {
|
|
3517
|
+
case MetricV2Type.Gauge:
|
|
3518
|
+
return [{ key: "value", unit: this.mainUnit(info), height: 170, series: series.map(([i, l]) => line("value", i, this.points(l, s => s.value))) }];
|
|
3519
|
+
case MetricV2Type.Counter:
|
|
3520
|
+
if (alt == "delta")
|
|
3521
|
+
return [{ key: "delta", unit: unit, height: 170, series: series.map(([i, l]) => line("delta", i, this.points(l, s => s.deltaCount), { kind: "bars" })) }];
|
|
3522
|
+
if (alt == "items")
|
|
3523
|
+
return [{ key: "items", unit: unit, height: 190, series: this.stacked(items, s => s.count) }];
|
|
3524
|
+
if (alt == "share")
|
|
3525
|
+
return [{ key: "share", unit: "%", height: 170, series: this.itemLines(items, s => s.percent) }];
|
|
3526
|
+
return [{ key: "level", unit: unit, height: 170, series: series.map(([i, l]) => line("level", i, this.points(l, s => s.count))) }];
|
|
3527
|
+
case MetricV2Type.Meter:
|
|
3528
|
+
if (alt == "moving")
|
|
3529
|
+
return [{
|
|
3530
|
+
key: "moving", unit: rateUnit, height: 170,
|
|
3531
|
+
series: flatten(series, ([i, l]) => [
|
|
3532
|
+
line("m1", i, this.points(l, s => s.oneMinuteRate), { label: `${i} · 1 min` }),
|
|
3533
|
+
line("m5", i, this.points(l, s => s.fiveMinuteRate), { label: `${i} · 5 min`, dash: "5 3" }),
|
|
3534
|
+
line("m15", i, this.points(l, s => s.fifteenMinuteRate), { label: `${i} · 15 min`, dash: "1 3" })
|
|
3535
|
+
])
|
|
3536
|
+
}];
|
|
3537
|
+
if (alt == "items")
|
|
3538
|
+
return [{ key: "items", unit: `${unit}/s`, height: 190, series: this.stacked(items, s => s.ratePerSecond) }];
|
|
3539
|
+
return [{ key: "rate", unit: this.mainUnit(info), height: 170, series: series.map(([i, l]) => line("rate", i, this.points(l, s => this.main(info, s)))) }];
|
|
3540
|
+
case MetricV2Type.Histogram:
|
|
3541
|
+
if (alt == "rate")
|
|
3542
|
+
return [{ key: "rate", unit: `${unit}/s`, height: 170, series: series.map(([i, l]) => line("rate", i, this.points(l, s => s.ratePerSecond))) }];
|
|
3543
|
+
return [{ key: "percentiles", unit: unit, height: 190, series: flatten(series, ([i, l]) => this.percentileBand(i, l, s => s.median, `${i} · ${this.lc.loc("mediana")}`)) }];
|
|
3544
|
+
case MetricV2Type.Timer:
|
|
3545
|
+
if (alt == "active")
|
|
3546
|
+
return [{ key: "active", unit: this.lc.loc("in corso"), height: 170, series: series.map(([i, l]) => line("active", i, this.points(l, s => s.activeSessions), { kind: "step" })) }];
|
|
3547
|
+
return [
|
|
3548
|
+
{ key: "calls", unit: `${unit}/s`, height: 120, series: series.map(([i, l]) => line("calls", i, this.points(l, s => s.ratePerSecond))) },
|
|
3549
|
+
{ key: "duration", unit: this.durationLabel(info.durationUnit), height: 170, series: flatten(series, ([i, l]) => this.percentileBand(i, l, s => s.intervalMean, `${i} · ${this.lc.loc("media nell'intervallo")}`, true)) }
|
|
3550
|
+
];
|
|
3551
|
+
}
|
|
3552
|
+
return [];
|
|
3553
|
+
}
|
|
3554
|
+
// Fascia dei percentili del reservoir per istanza: per l'histogram mediana con max a punti, per il timer la durata media dell'intervallo
|
|
3555
|
+
percentileBand(instance, list, center, label, timer = false) {
|
|
3556
|
+
let color = this.color(instance);
|
|
3557
|
+
let ret = [];
|
|
3558
|
+
if (!timer)
|
|
3559
|
+
ret.push({ key: `b1|${instance}`, label: instance, color: color, kind: "band", points: this.points(list, s => s.median, s => s.percentile75), opacity: 0.26, tooltip: false });
|
|
3560
|
+
ret.push({ key: `b2|${instance}`, label: instance, color: color, kind: "band", points: this.points(list, s => timer ? s.median : s.percentile75, s => s.percentile95), opacity: 0.15, tooltip: false });
|
|
3561
|
+
ret.push({ key: `b3|${instance}`, label: instance, color: color, kind: "band", points: this.points(list, s => s.percentile95, s => s.percentile99), opacity: 0.07, tooltip: false });
|
|
3562
|
+
ret.push({ key: `c|${instance}`, label: label, color: color, kind: "line", points: this.points(list, center) });
|
|
3563
|
+
if (!timer)
|
|
3564
|
+
ret.push({ key: `max|${instance}`, label: `${instance} · max`, color: color, kind: "dots", points: this.points(list, s => s.max) });
|
|
3565
|
+
return ret;
|
|
3566
|
+
}
|
|
3567
|
+
// Le voci di un totale sommate sulle istanze visibili, campione per campione (live: a 15 secondi; storico: per snapshot)
|
|
3568
|
+
bucketed(item, value, average) {
|
|
3569
|
+
let buckets = new Map();
|
|
3570
|
+
for (let [, list] of this.visibleSeries(item.name))
|
|
3571
|
+
for (let s of list) {
|
|
3572
|
+
let v = value(s);
|
|
3573
|
+
if (v == null)
|
|
3574
|
+
continue;
|
|
3575
|
+
let key = s.snapId ?? Math.round(this.time(s) / REFRESH_MS);
|
|
3576
|
+
let b = buckets.get(key) ?? { t: this.time(s), v: 0, n: 0 };
|
|
3577
|
+
b.v += v;
|
|
3578
|
+
b.n++;
|
|
3579
|
+
buckets.set(key, b);
|
|
3580
|
+
}
|
|
3581
|
+
return new Map([...buckets.entries()].map(([k, b]) => [k, { t: b.t, v: average ? b.v / b.n : b.v }]));
|
|
3582
|
+
}
|
|
3583
|
+
stacked(items, value) {
|
|
3584
|
+
let perItem = items.map(t => this.bucketed(t, value, false));
|
|
3585
|
+
let keys = [...new Set(flatten(perItem, t => [...t.keys()]))].sort((a, b) => a - b);
|
|
3586
|
+
let base = new Map(keys.map(k => [k, 0]));
|
|
3587
|
+
let bands = items.map((item, i) => {
|
|
3588
|
+
let points = keys.filter(k => perItem[i].has(k)).map(k => {
|
|
3589
|
+
let b = perItem[i].get(k);
|
|
3590
|
+
let lo = base.get(k);
|
|
3591
|
+
base.set(k, lo + b.v);
|
|
3592
|
+
return { t: b.t, v: lo, v2: lo + b.v };
|
|
3593
|
+
});
|
|
3594
|
+
let label = item.name.substring(item.name.lastIndexOf(" - ") + 3);
|
|
3595
|
+
return { key: `stack|${item.name}`, label: label, color: ITEM_COLORS[i % ITEM_COLORS.length], kind: "band", points: points, opacity: 0.55, tooltip: false };
|
|
3596
|
+
});
|
|
3597
|
+
// Linee invisibili solo per il riquadro dei valori: il valore della voce, non la somma impilata
|
|
3598
|
+
let tips = items.map((item, i) => ({
|
|
3599
|
+
key: `stacktip|${item.name}`, label: item.name.substring(item.name.lastIndexOf(" - ") + 3), color: ITEM_COLORS[i % ITEM_COLORS.length], kind: "line",
|
|
3600
|
+
points: [...perItem[i].values()].sort((a, b) => a.t - b.t).map(b => ({ t: b.t, v: b.v })), opacity: 0
|
|
3601
|
+
}));
|
|
3602
|
+
return bands.reverse().concat(tips);
|
|
3603
|
+
}
|
|
3604
|
+
itemLines(items, value) {
|
|
3605
|
+
return items.map((item, i) => ({
|
|
3606
|
+
key: `share|${item.name}`, label: item.name.substring(item.name.lastIndexOf(" - ") + 3), color: ITEM_COLORS[i % ITEM_COLORS.length], kind: "line",
|
|
3607
|
+
points: [...this.bucketed(item, value, true).values()].sort((a, b) => a.t - b.t).map(b => ({ t: b.t, v: b.v }))
|
|
3608
|
+
}));
|
|
3609
|
+
}
|
|
3610
|
+
// M17: valore principale dell'ultimo campione di ogni istanza, mini grafico, istante del campione
|
|
3611
|
+
buildTile(info) {
|
|
3612
|
+
let rows = [];
|
|
3613
|
+
let sparks = [];
|
|
3614
|
+
let latest = 0;
|
|
3615
|
+
for (let [instance, list] of this.visibleSeries(info.name)) {
|
|
3616
|
+
let last = [...list].reverse().find(t => this.main(info, t) != null);
|
|
3617
|
+
if (!last)
|
|
3618
|
+
continue;
|
|
3619
|
+
latest = Math.max(latest, this.time(last));
|
|
3620
|
+
rows.push({
|
|
3621
|
+
instance: instance, color: this.color(instance), value: `${this.number(this.main(info, last))} ${this.mainUnit(info)}`.trim(),
|
|
3622
|
+
extra: info.metricType == MetricV2Type.Meter ? `${this.number(last.count)} ${this.lc.loc("dall'avvio")}` : ""
|
|
3623
|
+
});
|
|
3624
|
+
let points = this.points(list.slice(-80), s => this.main(info, s));
|
|
3625
|
+
if (points.length > 1) {
|
|
3626
|
+
let lo = Math.min(...points.map(t => t.v));
|
|
3627
|
+
let hi = Math.max(...points.map(t => t.v));
|
|
3628
|
+
let t0 = points[0].t;
|
|
3629
|
+
let t1 = points[points.length - 1].t;
|
|
3630
|
+
let d = points.map((p, i) => `${i == 0 || p.gap ? "M" : "L"}${((p.t - t0) / Math.max(1, t1 - t0) * 120).toFixed(1)},${(26 - (hi > lo ? (p.v - lo) / (hi - lo) : 0.5) * 24).toFixed(1)}`).join("");
|
|
3631
|
+
sparks.push({ d: d, color: this.color(instance) });
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
return { info: info, rows: rows, sparks: sparks, time: latest ? dayjs(latest).format("HH:mm:ss") : "" };
|
|
3635
|
+
}
|
|
3636
|
+
// M18: colonne comuni e del tipo; ordinabile; esportabile
|
|
3637
|
+
buildTable(info, previous) {
|
|
3638
|
+
let unit = info.unit ? ` (${info.unit})` : "";
|
|
3639
|
+
let dur = info.durationUnit ? ` (${this.durationLabel(info.durationUnit)})` : "";
|
|
3640
|
+
let columns = [
|
|
3641
|
+
{ key: "instance", label: this.lc.loc("Istanza"), num: false },
|
|
3642
|
+
{ key: "time", label: `${this.lc.loc("Istante")} (${this.zone})`, num: false },
|
|
3643
|
+
{ key: "interval", label: this.lc.loc("Intervallo (s)"), num: true }
|
|
3644
|
+
];
|
|
3645
|
+
if (this.source != "live")
|
|
3646
|
+
columns.push({ key: "snapId", label: this.lc.loc("Snapshot"), num: true });
|
|
3647
|
+
let typed = [];
|
|
3648
|
+
switch (info.metricType) {
|
|
3649
|
+
case MetricV2Type.Gauge:
|
|
3650
|
+
typed = [["value", `${this.lc.loc("Valore")}${info.name == CPU_USAGE ? " (CPU)" : unit}`, s => s.value]];
|
|
3651
|
+
break;
|
|
3652
|
+
case MetricV2Type.Counter:
|
|
3653
|
+
typed = [["count", `${this.lc.loc("Conteggio")}${unit}`, s => s.count], ["delta", `${this.lc.loc("Variazione")}${unit}`, s => s.deltaCount], ["percent", `${this.lc.loc("Quota")} (%)`, s => s.percent]];
|
|
3654
|
+
break;
|
|
3655
|
+
case MetricV2Type.Meter:
|
|
3656
|
+
typed = [["count", `${this.lc.loc("Conteggio")}${unit}`, s => s.count], ["rate", `${this.lc.loc("Al secondo")} (${this.mainUnit(info)})`, s => this.main(info, s)],
|
|
3657
|
+
["m1", `Rate 1 min (${info.unit || ""}${this.rateLabel(info.rateUnit)})`, s => s.oneMinuteRate], ["m5", "Rate 5 min", s => s.fiveMinuteRate], ["m15", "Rate 15 min", s => s.fifteenMinuteRate]];
|
|
3658
|
+
break;
|
|
3659
|
+
case MetricV2Type.Histogram:
|
|
3660
|
+
typed = [["count", this.lc.loc("Conteggio"), s => s.count], ["median", `${this.lc.loc("Mediana")}${unit}`, s => s.median], ["p95", "p95", s => s.percentile95], ["p99", "p99", s => s.percentile99], ["max", this.lc.loc("Massimo"), s => s.max]];
|
|
3661
|
+
break;
|
|
3662
|
+
case MetricV2Type.Timer:
|
|
3663
|
+
typed = [["count", this.lc.loc("Chiamate"), s => s.count], ["rate", `${this.lc.loc("Al secondo")} (/s)`, s => s.ratePerSecond], ["intervalMean", `${this.lc.loc("Durata media nell'intervallo")}${dur}`, s => s.intervalMean],
|
|
3664
|
+
["median", `${this.lc.loc("Mediana")}${dur}`, s => s.median], ["p95", "p95", s => s.percentile95], ["p99", "p99", s => s.percentile99], ["active", this.lc.loc("In corso"), s => s.activeSessions]];
|
|
3665
|
+
break;
|
|
3666
|
+
}
|
|
3667
|
+
columns.push(...typed.map(([key, label]) => ({ key: key, label: label, num: true })));
|
|
3668
|
+
let rows = flatten(this.visibleSeries(info.name), ([instance, list]) => list.map(s => {
|
|
3669
|
+
let row = { instance: instance, time: this.time(s), utc: s.sampleTime, interval: s.intervalSeconds, snapId: s.snapId };
|
|
3670
|
+
typed.forEach(([key, , value]) => row[key] = value(s));
|
|
3671
|
+
return row;
|
|
3672
|
+
}));
|
|
3673
|
+
let section = { info: info, columns: columns, rows: [], total: rows.length, sortKey: previous?.sortKey ?? "time", sortDesc: previous?.sortDesc ?? true };
|
|
3674
|
+
section.rows = this.sortRows(rows, section);
|
|
3675
|
+
return section;
|
|
3676
|
+
}
|
|
3677
|
+
sortBy(section, column) {
|
|
3678
|
+
section.sortDesc = section.sortKey == column.key ? !section.sortDesc : column.num || column.key == "time";
|
|
3679
|
+
section.sortKey = column.key;
|
|
3680
|
+
let rebuilt = this.buildTable(section.info, section);
|
|
3681
|
+
section.rows = rebuilt.rows;
|
|
3682
|
+
}
|
|
3683
|
+
sortRows(rows, section) {
|
|
3684
|
+
let key = section.sortKey;
|
|
3685
|
+
let sign = section.sortDesc ? -1 : 1;
|
|
3686
|
+
return rows.sort((a, b) => {
|
|
3687
|
+
let x = a[key];
|
|
3688
|
+
let y = b[key];
|
|
3689
|
+
if (x == null && y == null)
|
|
3690
|
+
return 0;
|
|
3691
|
+
if (x == null)
|
|
3692
|
+
return 1;
|
|
3693
|
+
if (y == null)
|
|
3694
|
+
return -1;
|
|
3695
|
+
return (typeof x == "string" ? x.localeCompare(y) : x - y) * sign;
|
|
3696
|
+
});
|
|
3697
|
+
}
|
|
3698
|
+
shownRows(section) {
|
|
3699
|
+
return section.rows.slice(0, TABLE_ROWS);
|
|
3700
|
+
}
|
|
3701
|
+
cell(row, column) {
|
|
3702
|
+
let v = row[column.key];
|
|
3703
|
+
if (column.key == "time")
|
|
3704
|
+
return dayjs(v).format("DD/MM HH:mm:ss");
|
|
3705
|
+
if (column.key == "instance" || column.key == "snapId")
|
|
3706
|
+
return v ?? "—";
|
|
3707
|
+
return this.number(v);
|
|
3708
|
+
}
|
|
3709
|
+
// CSV delle righe della metrica, con gli orari in UTC e le unità nelle intestazioni
|
|
3710
|
+
exportCsv(section) {
|
|
3711
|
+
let quote = (v) => {
|
|
3712
|
+
let s = v == null ? "" : String(v);
|
|
3713
|
+
return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
|
3714
|
+
};
|
|
3715
|
+
let columns = section.columns.map(t => t.key == "time" ? { ...t, label: `${this.lc.loc("Istante")} (UTC)` } : t);
|
|
3716
|
+
let lines = [columns.map(t => quote(t.label)).join(",")];
|
|
3717
|
+
for (let row of section.rows)
|
|
3718
|
+
lines.push(columns.map(t => quote(t.key == "time" ? this.isoUtc(row["utc"]) : row[t.key])).join(","));
|
|
3719
|
+
let blob = new Blob(["" + lines.join("\r\n")], { type: "text/csv;charset=utf-8" });
|
|
3720
|
+
let url = URL.createObjectURL(blob);
|
|
3721
|
+
let link = document.createElement("a");
|
|
3722
|
+
link.href = url;
|
|
3723
|
+
link.download = `metriche_${section.info.name.replace(/[^\w.-]+/g, "-")}.csv`;
|
|
3724
|
+
document.body.appendChild(link);
|
|
3725
|
+
link.click();
|
|
3726
|
+
link.remove();
|
|
3727
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
3728
|
+
}
|
|
3729
|
+
// #endregion
|
|
3730
|
+
// #region Supporto
|
|
3731
|
+
visibilityChanged() {
|
|
3732
|
+
// Tornando sulla pagina si recupera subito invece di aspettare il giro dopo
|
|
3733
|
+
if (!document.hidden)
|
|
3734
|
+
this.refresh();
|
|
3735
|
+
}
|
|
3736
|
+
errorMessage(err) {
|
|
3737
|
+
return err?.status == 542 ? this.adms.errorText(err) : `${this.lc.loc("Errore")} ${err?.status ?? ""}: ${this.adms.errorText(err)}`;
|
|
3738
|
+
}
|
|
3739
|
+
number(value) {
|
|
3740
|
+
if (value == null || isNaN(value))
|
|
3741
|
+
return "—";
|
|
3742
|
+
let abs = Math.abs(value);
|
|
3743
|
+
let digits = abs >= 1000 ? "1.0-0" : abs >= 100 ? "1.0-1" : abs >= 1 ? "1.0-2" : "1.0-3";
|
|
3744
|
+
return formatNumber(value, "it", digits);
|
|
3745
|
+
}
|
|
3746
|
+
durationLabel(unit) {
|
|
3747
|
+
switch (unit) {
|
|
3748
|
+
case "Nanoseconds": return "ns";
|
|
3749
|
+
case "Microseconds": return "µs";
|
|
3750
|
+
case "Milliseconds": return "ms";
|
|
3751
|
+
case "Seconds": return "s";
|
|
3752
|
+
case "Minutes": return "min";
|
|
3753
|
+
case "Hours": return "h";
|
|
3754
|
+
}
|
|
3755
|
+
return unit ?? "";
|
|
3756
|
+
}
|
|
3757
|
+
rateLabel(unit) {
|
|
3758
|
+
switch (unit) {
|
|
3759
|
+
case "Seconds": return "/s";
|
|
3760
|
+
case "Minutes": return "/min";
|
|
3761
|
+
case "Hours": return "/h";
|
|
3762
|
+
case "Days": return "/g";
|
|
3763
|
+
}
|
|
3764
|
+
return unit ? `/${unit}` : "";
|
|
3765
|
+
}
|
|
3766
|
+
// Le date delle risposte sono UTC "dd/MM/yyyy HH:mm:ss" senza fuso: si leggono come UTC e si mostrano in ora locale (M24)
|
|
3767
|
+
parseUtc(value) {
|
|
3768
|
+
let m = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})$/.exec(value ?? "");
|
|
3769
|
+
return m ? new Date(Date.UTC(+m[3], +m[2] - 1, +m[1], +m[4], +m[5], +m[6])) : value ? new Date(value) : null;
|
|
3770
|
+
}
|
|
3771
|
+
time(s) {
|
|
3772
|
+
return this.parseUtc(s.sampleTime)?.getTime() ?? 0;
|
|
3773
|
+
}
|
|
3774
|
+
isoUtc(value) {
|
|
3775
|
+
return this.parseUtc(value)?.toISOString().replace(".000Z", "Z") ?? "";
|
|
3776
|
+
}
|
|
3777
|
+
local(value) {
|
|
3778
|
+
let date = typeof value == "string" ? this.parseUtc(value) : value;
|
|
3779
|
+
return date ? dayjs(date).format("DD/MM HH:mm") : "—";
|
|
3780
|
+
}
|
|
3781
|
+
hhmmss(date) {
|
|
3782
|
+
return date ? dayjs(date).format("HH:mm:ss") : "";
|
|
3783
|
+
}
|
|
3784
|
+
setLastDay() {
|
|
3785
|
+
let now = new Date();
|
|
3786
|
+
this.filterto = this.fmt(now);
|
|
3787
|
+
this.filterfrom = this.fmt(new Date(now.getTime() - 24 * 3600 * 1000));
|
|
3788
|
+
}
|
|
3789
|
+
toDate(value) {
|
|
3790
|
+
return value ? this.dateExts.getDateConvertion(value).toDate() : null;
|
|
3791
|
+
}
|
|
3792
|
+
fmt(date) {
|
|
3793
|
+
return dayjs(date).format(this.lc.token("getFullDateDisplayFormat"));
|
|
3794
|
+
}
|
|
3795
|
+
saveSettings() {
|
|
3796
|
+
let settings = { selected: this.selected, source: this.source, instances: this.instances, liveMinutes: this.liveMinutes, view: this.view };
|
|
3797
|
+
this.localStorage.setItem(STORAGE_KEY$1, settings).subscribe();
|
|
3798
|
+
}
|
|
3799
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmMetricsV2Component, deps: [{ token: i1$1.DateService }, { token: i1.LocalizationService }, { token: i3.PreferencesService }, { token: i1$1.InterComService }, { token: i4.ActivatedRoute }, { token: AdminModuleBaseService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3800
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: AdmMetricsV2Component, isStandalone: false, selector: "metrics_v2", providers: [{ provide: LocalizationService, useClass: AdmMetricsV2Loc }], ngImport: i0, template: "<div class=\"row\">\r\n <div class=\"col-12\">\r\n <div class=\"card app-overflow-visible\" widget>\r\n <div class=\"card-header\">\r\n <span class=\"text-uppercase\">{{'Metriche di Sistema' | localize: lc}}</span>\r\n </div>\r\n <div class=\"card-body widget-body mv2-body\">\r\n <!-- Barra dei comandi: sorgente, finestra, istanze, vista, aggiornamento -->\r\n <div class=\"mv2-toolbar\">\r\n <div class=\"mv2-group\">\r\n <span class=\"mv2-group-label\">{{'Sorgente' | localize: lc}}</span>\r\n <div class=\"mv2-seg\">\r\n @for (o of Sources; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"source == o.id\" [disabled]=\"o.id != 'live' && !historyAvailable\" (click)=\"setSource(o.id)\"\r\n [title]=\"o.id != 'live' && !historyAvailable ? ('Lo storico non \u00E8 attivo su questo ambiente: ci sono solo i dati live.' | localize: lc) : ''\">\r\n @if (o.id == 'live') {\r\n <span class=\"mv2-live-dot\" [class.mv2-live-paused]=\"paused\"></span>\r\n }\r\n {{o.description}}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (source == 'live') {\r\n <div class=\"mv2-group\">\r\n <span class=\"mv2-group-label\">{{'Finestra' | localize: lc}}</span>\r\n <div class=\"mv2-seg\">\r\n @for (o of LiveWindows; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"liveMinutes == o.id\" (click)=\"setLiveMinutes(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n } @else if (source == 'period') {\r\n <div class=\"mv2-cluster\">\r\n <div class=\"mv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterfrom\" (ngModelChange)=\"filterfrom = $event; datesChanged()\" name=\"filterfrom\" [Placeholder]=\"'Dal' | localize: lc\"></form-datetime>\r\n </div>\r\n <div class=\"mv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterto\" (ngModelChange)=\"filterto = $event; datesChanged()\" name=\"filterto\" [Placeholder]=\"'Al' | localize: lc\"></form-datetime>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"mv2-cluster\">\r\n <div class=\"mv2-field mv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"snapFromId\" (ngModelChange)=\"snapFromId = $event; snapshotsChanged()\" name=\"snapFrom\" [Source]=\"SnapshotOptions\" [Placeholder]=\"'Dallo snapshot' | localize: lc\"></form-select>\r\n </div>\r\n <div class=\"mv2-field mv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"snapToId\" (ngModelChange)=\"snapToId = $event; snapshotsChanged()\" name=\"snapTo\" [Source]=\"SnapshotOptions\" [Placeholder]=\"'Allo snapshot' | localize: lc\"></form-select>\r\n </div>\r\n @if (snapshotWindow; as w) {\r\n <span class=\"mv2-muted\">{{'Dalle' | localize: lc}} {{local(w.from)}} {{'alle' | localize: lc}} {{local(w.to)}}</span>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"mv2-group mv2-field-wide\">\r\n <span class=\"mv2-group-label\">{{'Istanze' | localize: lc}}</span>\r\n <form-multiselect class=\"mv2-fill\" [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"instances\" (ngModelChange)=\"instancesChanged($event)\" name=\"instances\" [Source]=\"Instances\" [Placeholder]=\"clusterLabel\" [MaxShownElements]=\"2\"></form-multiselect>\r\n </div>\r\n\r\n <div class=\"mv2-cluster mv2-cluster-end\">\r\n @if (source == 'live') {\r\n <span class=\"mv2-muted\">\r\n @if (dataLoading) {\r\n <i class=\"fa fa-spinner fa-spin\"></i>\r\n }\r\n @if (lastUpdate) {\r\n {{'Aggiornato alle' | localize: lc}} {{hhmmss(lastUpdate)}}\r\n }\r\n </span>\r\n <button type=\"button\" class=\"mv2-btn\" (click)=\"togglePause()\">\r\n <i class=\"fa\" [class.fa-pause]=\"!paused\" [class.fa-play]=\"paused\"></i> {{(paused ? 'Riprendi' : 'Pausa') | localize: lc}}\r\n </button>\r\n } @else if (dataLoading) {\r\n <span class=\"mv2-muted\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</span>\r\n }\r\n <div class=\"mv2-seg\">\r\n @for (o of Views; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"view == o.id\" (click)=\"setView(o.id)\">\r\n <i class=\"fa\" [class.fa-line-chart]=\"o.id == 'charts'\" [class.fa-table]=\"o.id == 'table'\"></i> {{o.description}}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Avvisi -->\r\n @for (warning of warnings; track $index) {\r\n <div class=\"mv2-alert mv2-alert-warn\"><i class=\"fa fa-exclamation-triangle\"></i> {{warning}}</div>\r\n }\r\n @if (snapshotsLoaded && !historyAvailable) {\r\n <div class=\"mv2-alert\"><i class=\"fa fa-info-circle\"></i> {{'Lo storico non \u00E8 attivo su questo ambiente: ci sono solo i dati live.' | localize: lc}}</div>\r\n }\r\n @if (dataError) {\r\n <div class=\"mv2-alert mv2-alert-error\">{{dataError}}</div>\r\n }\r\n\r\n <div class=\"mv2-main\">\r\n <!-- Catalogo -->\r\n <aside class=\"mv2-panel mv2-catalog\">\r\n <div class=\"mv2-field-full\">\r\n <form-input [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"search\" (ngModelChange)=\"search = $event; buildGroups()\" name=\"search\" [Placeholder]=\"'Cerca una metrica\u2026' | localize: lc\"></form-input>\r\n </div>\r\n <div class=\"mv2-presets\">\r\n @for (p of Presets; track p) {\r\n <button type=\"button\" class=\"mv2-chip\" (click)=\"applyPreset(p)\">{{p | localize: lc}}</button>\r\n }\r\n </div>\r\n <div class=\"mv2-counter\">\r\n <span>{{selected.length}} {{'di' | localize: lc}} {{Max}} {{'metriche scelte' | localize: lc}}</span>\r\n @if (selected.length > 0) {\r\n <button type=\"button\" class=\"mv2-link\" (click)=\"clearSelection()\">{{'Togli tutte' | localize: lc}}</button>\r\n }\r\n </div>\r\n\r\n @if (catalogLoading) {\r\n <div class=\"mv2-muted mv2-pad\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</div>\r\n }\r\n @if (catalogError) {\r\n <div class=\"mv2-alert mv2-alert-error\">{{catalogError}}</div>\r\n }\r\n @for (group of groups; track group.type) {\r\n <div class=\"mv2-cat-group\">\r\n <div class=\"mv2-cat-title\">{{group.label}} <span class=\"mv2-muted\">{{group.count}}</span></div>\r\n @for (entry of group.entries; track entry.info.name) {\r\n <div class=\"mv2-cat-item\" [class.mv2-cat-disabled]=\"!canSelect(entry.info.name)\">\r\n <label class=\"mv2-check\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(entry.info.name)\" [disabled]=\"!canSelect(entry.info.name)\" (change)=\"toggleMetric(entry.info.name)\" />\r\n <span class=\"mv2-cat-name\" [title]=\"entry.info.name\">{{entry.info.name}}</span>\r\n </label>\r\n <span class=\"mv2-cat-meta\">\r\n {{catalogUnit(entry.info)}}\r\n @if (instancesLabel(entry.info)) {\r\n <span class=\"mv2-cat-inst\" [title]=\"entry.info.instances.join('\\n')\">{{instancesLabel(entry.info)}}</span>\r\n }\r\n </span>\r\n @if (entry.items.length > 0) {\r\n <button type=\"button\" class=\"mv2-caret\" (click)=\"entry.open = !entry.open\" [title]=\"'Voci' | localize: lc\">\r\n <i class=\"fa\" [class.fa-caret-down]=\"entry.open\" [class.fa-caret-right]=\"!entry.open\"></i> {{entry.items.length}}\r\n </button>\r\n }\r\n </div>\r\n @if (entry.open && entry.items.length > 0) {\r\n <div class=\"mv2-cat-items\">\r\n <button type=\"button\" class=\"mv2-link\" (click)=\"addItems(entry)\">{{addItemsLabel(entry)}}</button>\r\n @for (item of entry.items; track item.name) {\r\n <div class=\"mv2-cat-item mv2-cat-sub\" [class.mv2-cat-disabled]=\"!canSelect(item.name)\">\r\n <label class=\"mv2-check\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(item.name)\" [disabled]=\"!canSelect(item.name)\" (change)=\"toggleMetric(item.name)\" />\r\n <span class=\"mv2-cat-name\" [title]=\"item.name\">{{item.name.substring(entry.info.name.length + 3)}}</span>\r\n </label>\r\n @if (instancesLabel(item)) {\r\n <span class=\"mv2-cat-inst\" [title]=\"item.instances.join('\\n')\">{{instancesLabel(item)}}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n </aside>\r\n\r\n <!-- Grafici, tessere e tabella -->\r\n <section class=\"mv2-area\">\r\n @if (selected.length == 0) {\r\n <div class=\"mv2-panel mv2-placeholder\">\r\n <i class=\"fa fa-line-chart\"></i>\r\n {{'Scegli una o pi\u00F9 metriche dal catalogo.' | localize: lc}}\r\n </div>\r\n } @else {\r\n <!-- Tessere dei valori correnti -->\r\n <div class=\"mv2-tiles\">\r\n @for (tile of tiles; track tile.info.name) {\r\n <div class=\"mv2-tile\">\r\n <div class=\"mv2-tile-name\" [title]=\"tile.info.name\">{{tile.info.name}}</div>\r\n @for (row of tile.rows; track row.instance) {\r\n <div class=\"mv2-tile-row\">\r\n <span class=\"mv2-dot\" [style.background]=\"row.color\" [title]=\"row.instance\"></span>\r\n <strong>{{row.value}}</strong>\r\n @if (row.extra) {\r\n <span class=\"mv2-muted\">{{row.extra}}</span>\r\n }\r\n </div>\r\n } @empty {\r\n <div class=\"mv2-muted\">\u2014</div>\r\n }\r\n <svg class=\"mv2-spark\" viewBox=\"0 0 120 28\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\r\n @for (spark of tile.sparks; track $index) {\r\n <path [attr.d]=\"spark.d\" [attr.stroke]=\"spark.color\" fill=\"none\" stroke-width=\"1.3\" vector-effect=\"non-scaling-stroke\" />\r\n }\r\n </svg>\r\n @if (tile.time) {\r\n <div class=\"mv2-muted mv2-small\">{{'alle' | localize: lc}} {{tile.time}}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Legenda comune: un clic nasconde o mostra l'istanza ovunque -->\r\n <div class=\"mv2-legend-bar\">\r\n <div class=\"mv2-legend\">\r\n @for (item of legend; track item.instance) {\r\n <button type=\"button\" class=\"mv2-legend-item\" [class.mv2-legend-off]=\"item.hidden\" (click)=\"toggleInstance(item.instance)\">\r\n <span class=\"mv2-dot\" [style.background]=\"item.color\"></span>{{item.instance}}\r\n </button>\r\n }\r\n </div>\r\n <span class=\"mv2-muted mv2-small\">{{'Orari in ora locale' | localize: lc}} ({{zone}}) · {{'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)\">×</button>\r\n </div>\r\n @if (panel.empty) {\r\n <div class=\"mv2-empty\">\r\n @if (dataLoading) {\r\n <i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}\r\n } @else {\r\n {{emptyLabel(panel.info.name)}}\r\n }\r\n </div>\r\n } @else {\r\n @for (chart of panel.charts; track chart.key) {\r\n <mv2-chart [series]=\"chart.series\" [from]=\"chartFrom\" [to]=\"chartTo\" [height]=\"chart.height\" [unit]=\"chart.unit\" [marks]=\"marks\"\r\n [cursor]=\"cursor\" [format]=\"format\" (cursorChange)=\"cursor = $event\" (zoom)=\"zoomTo($event)\"></mv2-chart>\r\n }\r\n }\r\n </div>\r\n }\r\n } @else {\r\n @for (section of tables; track section.info.name) {\r\n <div class=\"mv2-panel\">\r\n <div class=\"mv2-panel-header\">\r\n <span class=\"mv2-panel-title\">{{section.info.name}}</span>\r\n <span class=\"mv2-type\">{{Type[section.info.metricType]}}</span>\r\n <span class=\"mv2-spacer\"></span>\r\n @if (section.total > 0) {\r\n <button type=\"button\" class=\"mv2-btn\" (click)=\"exportCsv(section)\"><i class=\"fa fa-download\"></i> {{'Esporta CSV' | localize: lc}}</button>\r\n }\r\n </div>\r\n @if (section.total == 0) {\r\n <div class=\"mv2-empty\">{{emptyLabel(section.info.name)}}</div>\r\n } @else {\r\n <div class=\"mv2-table-wrap\">\r\n <table class=\"mv2-table\">\r\n <thead>\r\n <tr>\r\n @for (column of section.columns; track column.key) {\r\n <th [class.mv2-num]=\"column.num\" (click)=\"sortBy(section, column)\">\r\n {{column.label}}\r\n @if (section.sortKey == column.key) {\r\n <i class=\"fa\" [class.fa-caret-down]=\"section.sortDesc\" [class.fa-caret-up]=\"!section.sortDesc\"></i>\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (row of shownRows(section); track $index) {\r\n <tr>\r\n @for (column of section.columns; track column.key) {\r\n <td [class.mv2-num]=\"column.num\">\r\n @if (column.key == 'instance') {\r\n <span class=\"mv2-dot\" [style.background]=\"color(row['instance'])\"></span>\r\n }\r\n {{cell(row, column)}}\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n @if (section.total > section.rows.length || section.total > 300) {\r\n <div class=\"mv2-muted mv2-small mv2-pad\">{{'Mostrate le prime 300 righe di' | localize: lc}} {{section.total}}; {{\"l'esportazione le comprende tutte.\" | localize: lc}}</div>\r\n }\r\n }\r\n </div>\r\n }\r\n }\r\n }\r\n </section>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .mv2-noclear .p-select-clear-icon{display:none}:host ::ng-deep .mv2-fill{display:block;width:100%}.mv2-body{container-type:inline-size}.mv2-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 18px;padding-top:6px}.mv2-cluster{display:flex;flex-wrap:wrap;align-items:center;gap:10px}.mv2-cluster-end{margin-left:auto}.mv2-field{width:200px}.mv2-field-wide{width:340px;max-width:100%}.mv2-field-full{width:100%}.mv2-group{position:relative;display:inline-flex}.mv2-group-label{position:absolute;top:-8px;left:8px;z-index:2;padding:0 4px;background:var(--p-content-background, #fff);color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.mv2-seg{display:inline-flex;height:39px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);overflow:hidden;background:var(--p-content-background, #fff)}.mv2-seg button{display:inline-flex;align-items:center;gap:6px;padding:0 12px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.mv2-seg button:first-child{border-left:0}.mv2-seg button:hover:not(.mv2-on):not(:disabled){background:var(--p-content-hover-background, #f8fafc)}.mv2-seg button:disabled{color:var(--p-text-muted-color, #64748b);opacity:.6;cursor:not-allowed}.mv2-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.mv2-seg button.mv2-on{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca);font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.mv2-seg.mv2-seg-small{height:28px}.mv2-seg.mv2-seg-small button{padding:0 9px;font-size:12px}.mv2-live-dot{width:8px;height:8px;border-radius:50%;background:#16a34a}.mv2-live-dot.mv2-live-paused{background:var(--p-text-muted-color, #64748b)}.mv2-btn{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.mv2-btn:hover{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.mv2-icon-btn{width:26px;height:26px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--p-text-muted-color, #64748b);font-size:18px;line-height:1;cursor:pointer}.mv2-icon-btn:hover{background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155)}.mv2-link{padding:0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;cursor:pointer}.mv2-link:hover{text-decoration:underline}.mv2-muted{color:var(--p-text-muted-color, #64748b);font-size:12px}.mv2-small{font-size:11px}.mv2-pad{padding:6px 2px}.mv2-spacer{flex:1 1 auto}.mv2-alert{margin-top:10px;padding:7px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155);font-size:13px}.mv2-alert.mv2-alert-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.mv2-alert.mv2-alert-error{border-color:#fca5a5;background:#fef2f2;color:#991b1b}.mv2-main{display:grid;grid-template-columns:300px minmax(0,1fr);gap:14px;align-items:start;margin-top:14px}@container (max-width: 1000px){.mv2-main{grid-template-columns:minmax(0,1fr)}.mv2-catalog{position:static;max-height:360px}}.mv2-panel{padding:12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0}.mv2-catalog{position:sticky;top:10px;max-height:calc(100vh - 20px);overflow:auto;font-size:13px;color:var(--p-text-color, #334155)}.mv2-presets{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 8px}.mv2-chip{padding:3px 10px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:12px;background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:12px;cursor:pointer}.mv2-chip:hover{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.mv2-counter{display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);color:var(--p-text-muted-color, #64748b);font-size:12px}.mv2-cat-group{margin-top:10px}.mv2-cat-title{margin-bottom:4px;color:var(--p-text-muted-color, #64748b);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.mv2-cat-item{display:flex;align-items:center;gap:6px;padding:2px 0;min-width:0}.mv2-cat-item.mv2-cat-disabled{opacity:.5}.mv2-cat-sub{padding-left:18px}.mv2-check{display:flex;align-items:center;gap:6px;flex:1 1 auto;min-width:0;margin:0;cursor:pointer}.mv2-check input{flex:none;display:inline-block;width:auto;height:auto;margin:0;accent-color:var(--p-primary-color, #6366f1)}.mv2-cat-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mv2-cat-meta{flex:none;color:var(--p-text-muted-color, #64748b);font-size:11px;white-space:nowrap}.mv2-cat-inst{margin-left:4px;padding:0 5px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:8px;color:var(--p-text-muted-color, #64748b);font-size:10px;cursor:help}.mv2-caret{flex:none;padding:0 4px;border:0;background:transparent;color:var(--p-text-muted-color, #64748b);font-size:11px;cursor:pointer}.mv2-cat-items{padding:2px 0 4px 18px}.mv2-area{display:flex;flex-direction:column;gap:12px;min-width:0}.mv2-placeholder{display:flex;flex-direction:column;align-items:center;gap:10px;padding:70px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.mv2-placeholder .fa{font-size:28px;opacity:.5}.mv2-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}.mv2-tile{display:flex;flex-direction:column;gap:3px;padding:10px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0;font-size:13px}.mv2-tile-name{overflow:hidden;color:var(--p-text-muted-color, #64748b);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.mv2-tile-row{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}.mv2-spark{display:block;width:100%;height:28px;margin-top:2px}.mv2-dot{display:inline-block;flex:none;width:9px;height:9px;margin-right:4px;border-radius:50%}.mv2-legend-bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px}.mv2-legend{display:flex;flex-wrap:wrap;gap:4px 6px}.mv2-legend-item{display:inline-flex;align-items:center;padding:2px 8px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:12px;background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:12px;cursor:pointer}.mv2-legend-item.mv2-legend-off{opacity:.45;text-decoration:line-through}.mv2-chart-panel{padding-bottom:6px}.mv2-panel-header{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;margin-bottom:6px}.mv2-panel-title{font-size:13px;font-weight:600;color:var(--p-text-color, #334155)}.mv2-type{padding:0 7px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:10px;color:var(--p-text-muted-color, #64748b);font-size:11px;line-height:18px}.mv2-empty{padding:26px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.mv2-table-wrap{max-height:420px;overflow:auto;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px)}.mv2-table{width:100%;border-collapse:separate;border-spacing:0;font-size:12px;color:var(--p-text-color, #334155)}.mv2-table th{position:sticky;top:0;z-index:1;padding:6px 8px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-muted-color, #64748b);font-weight:500;white-space:nowrap;cursor:pointer}.mv2-table td{padding:3px 8px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);white-space:nowrap}.mv2-num{text-align:right!important;font-variant-numeric:tabular-nums}\n"], dependencies: [{ kind: "directive", type: i7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i9.EsFormInputComponent, selector: "form-input", inputs: ["minlength", "maxlength", "pattern", "Password", "Type"] }, { kind: "component", type: i9.EsFormSelectComponent, selector: "form-select", inputs: ["OptionsTemplate"] }, { kind: "component", type: i9.EsFormDateTimeComponent, selector: "form-datetime" }, { kind: "component", type: i9.EsFormMultiselectComponent, selector: "form-multiselect", inputs: ["display", "InOutFormat", "FloatingLabel", "MaxShownElements"] }, { kind: "directive", type: i10.Widget, selector: "[widget]" }, { kind: "component", type: MetricsV2ChartComponent, selector: "mv2-chart", inputs: ["series", "from", "to", "height", "unit", "marks", "cursor", "format"], outputs: ["cursorChange", "zoom"] }, { kind: "pipe", type: i1.LocalizePipe, name: "localize" }] }); }
|
|
3801
|
+
}
|
|
3802
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmMetricsV2Component, decorators: [{
|
|
3803
|
+
type: Component,
|
|
3804
|
+
args: [{ selector: "metrics_v2", providers: [{ provide: LocalizationService, useClass: AdmMetricsV2Loc }], standalone: false, template: "<div class=\"row\">\r\n <div class=\"col-12\">\r\n <div class=\"card app-overflow-visible\" widget>\r\n <div class=\"card-header\">\r\n <span class=\"text-uppercase\">{{'Metriche di Sistema' | localize: lc}}</span>\r\n </div>\r\n <div class=\"card-body widget-body mv2-body\">\r\n <!-- Barra dei comandi: sorgente, finestra, istanze, vista, aggiornamento -->\r\n <div class=\"mv2-toolbar\">\r\n <div class=\"mv2-group\">\r\n <span class=\"mv2-group-label\">{{'Sorgente' | localize: lc}}</span>\r\n <div class=\"mv2-seg\">\r\n @for (o of Sources; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"source == o.id\" [disabled]=\"o.id != 'live' && !historyAvailable\" (click)=\"setSource(o.id)\"\r\n [title]=\"o.id != 'live' && !historyAvailable ? ('Lo storico non \u00E8 attivo su questo ambiente: ci sono solo i dati live.' | localize: lc) : ''\">\r\n @if (o.id == 'live') {\r\n <span class=\"mv2-live-dot\" [class.mv2-live-paused]=\"paused\"></span>\r\n }\r\n {{o.description}}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (source == 'live') {\r\n <div class=\"mv2-group\">\r\n <span class=\"mv2-group-label\">{{'Finestra' | localize: lc}}</span>\r\n <div class=\"mv2-seg\">\r\n @for (o of LiveWindows; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"liveMinutes == o.id\" (click)=\"setLiveMinutes(o.id)\">{{o.description}}</button>\r\n }\r\n </div>\r\n </div>\r\n } @else if (source == 'period') {\r\n <div class=\"mv2-cluster\">\r\n <div class=\"mv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterfrom\" (ngModelChange)=\"filterfrom = $event; datesChanged()\" name=\"filterfrom\" [Placeholder]=\"'Dal' | localize: lc\"></form-datetime>\r\n </div>\r\n <div class=\"mv2-field\">\r\n <form-datetime [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"filterto\" (ngModelChange)=\"filterto = $event; datesChanged()\" name=\"filterto\" [Placeholder]=\"'Al' | localize: lc\"></form-datetime>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"mv2-cluster\">\r\n <div class=\"mv2-field mv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"snapFromId\" (ngModelChange)=\"snapFromId = $event; snapshotsChanged()\" name=\"snapFrom\" [Source]=\"SnapshotOptions\" [Placeholder]=\"'Dallo snapshot' | localize: lc\"></form-select>\r\n </div>\r\n <div class=\"mv2-field mv2-noclear\">\r\n <form-select [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"snapToId\" (ngModelChange)=\"snapToId = $event; snapshotsChanged()\" name=\"snapTo\" [Source]=\"SnapshotOptions\" [Placeholder]=\"'Allo snapshot' | localize: lc\"></form-select>\r\n </div>\r\n @if (snapshotWindow; as w) {\r\n <span class=\"mv2-muted\">{{'Dalle' | localize: lc}} {{local(w.from)}} {{'alle' | localize: lc}} {{local(w.to)}}</span>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"mv2-group mv2-field-wide\">\r\n <span class=\"mv2-group-label\">{{'Istanze' | localize: lc}}</span>\r\n <form-multiselect class=\"mv2-fill\" [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"instances\" (ngModelChange)=\"instancesChanged($event)\" name=\"instances\" [Source]=\"Instances\" [Placeholder]=\"clusterLabel\" [MaxShownElements]=\"2\"></form-multiselect>\r\n </div>\r\n\r\n <div class=\"mv2-cluster mv2-cluster-end\">\r\n @if (source == 'live') {\r\n <span class=\"mv2-muted\">\r\n @if (dataLoading) {\r\n <i class=\"fa fa-spinner fa-spin\"></i>\r\n }\r\n @if (lastUpdate) {\r\n {{'Aggiornato alle' | localize: lc}} {{hhmmss(lastUpdate)}}\r\n }\r\n </span>\r\n <button type=\"button\" class=\"mv2-btn\" (click)=\"togglePause()\">\r\n <i class=\"fa\" [class.fa-pause]=\"!paused\" [class.fa-play]=\"paused\"></i> {{(paused ? 'Riprendi' : 'Pausa') | localize: lc}}\r\n </button>\r\n } @else if (dataLoading) {\r\n <span class=\"mv2-muted\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</span>\r\n }\r\n <div class=\"mv2-seg\">\r\n @for (o of Views; track o.id) {\r\n <button type=\"button\" [class.mv2-on]=\"view == o.id\" (click)=\"setView(o.id)\">\r\n <i class=\"fa\" [class.fa-line-chart]=\"o.id == 'charts'\" [class.fa-table]=\"o.id == 'table'\"></i> {{o.description}}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Avvisi -->\r\n @for (warning of warnings; track $index) {\r\n <div class=\"mv2-alert mv2-alert-warn\"><i class=\"fa fa-exclamation-triangle\"></i> {{warning}}</div>\r\n }\r\n @if (snapshotsLoaded && !historyAvailable) {\r\n <div class=\"mv2-alert\"><i class=\"fa fa-info-circle\"></i> {{'Lo storico non \u00E8 attivo su questo ambiente: ci sono solo i dati live.' | localize: lc}}</div>\r\n }\r\n @if (dataError) {\r\n <div class=\"mv2-alert mv2-alert-error\">{{dataError}}</div>\r\n }\r\n\r\n <div class=\"mv2-main\">\r\n <!-- Catalogo -->\r\n <aside class=\"mv2-panel mv2-catalog\">\r\n <div class=\"mv2-field-full\">\r\n <form-input [FormLayout]=\"false\" [Validation]=\"false\" [ngModel]=\"search\" (ngModelChange)=\"search = $event; buildGroups()\" name=\"search\" [Placeholder]=\"'Cerca una metrica\u2026' | localize: lc\"></form-input>\r\n </div>\r\n <div class=\"mv2-presets\">\r\n @for (p of Presets; track p) {\r\n <button type=\"button\" class=\"mv2-chip\" (click)=\"applyPreset(p)\">{{p | localize: lc}}</button>\r\n }\r\n </div>\r\n <div class=\"mv2-counter\">\r\n <span>{{selected.length}} {{'di' | localize: lc}} {{Max}} {{'metriche scelte' | localize: lc}}</span>\r\n @if (selected.length > 0) {\r\n <button type=\"button\" class=\"mv2-link\" (click)=\"clearSelection()\">{{'Togli tutte' | localize: lc}}</button>\r\n }\r\n </div>\r\n\r\n @if (catalogLoading) {\r\n <div class=\"mv2-muted mv2-pad\"><i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}</div>\r\n }\r\n @if (catalogError) {\r\n <div class=\"mv2-alert mv2-alert-error\">{{catalogError}}</div>\r\n }\r\n @for (group of groups; track group.type) {\r\n <div class=\"mv2-cat-group\">\r\n <div class=\"mv2-cat-title\">{{group.label}} <span class=\"mv2-muted\">{{group.count}}</span></div>\r\n @for (entry of group.entries; track entry.info.name) {\r\n <div class=\"mv2-cat-item\" [class.mv2-cat-disabled]=\"!canSelect(entry.info.name)\">\r\n <label class=\"mv2-check\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(entry.info.name)\" [disabled]=\"!canSelect(entry.info.name)\" (change)=\"toggleMetric(entry.info.name)\" />\r\n <span class=\"mv2-cat-name\" [title]=\"entry.info.name\">{{entry.info.name}}</span>\r\n </label>\r\n <span class=\"mv2-cat-meta\">\r\n {{catalogUnit(entry.info)}}\r\n @if (instancesLabel(entry.info)) {\r\n <span class=\"mv2-cat-inst\" [title]=\"entry.info.instances.join('\\n')\">{{instancesLabel(entry.info)}}</span>\r\n }\r\n </span>\r\n @if (entry.items.length > 0) {\r\n <button type=\"button\" class=\"mv2-caret\" (click)=\"entry.open = !entry.open\" [title]=\"'Voci' | localize: lc\">\r\n <i class=\"fa\" [class.fa-caret-down]=\"entry.open\" [class.fa-caret-right]=\"!entry.open\"></i> {{entry.items.length}}\r\n </button>\r\n }\r\n </div>\r\n @if (entry.open && entry.items.length > 0) {\r\n <div class=\"mv2-cat-items\">\r\n <button type=\"button\" class=\"mv2-link\" (click)=\"addItems(entry)\">{{addItemsLabel(entry)}}</button>\r\n @for (item of entry.items; track item.name) {\r\n <div class=\"mv2-cat-item mv2-cat-sub\" [class.mv2-cat-disabled]=\"!canSelect(item.name)\">\r\n <label class=\"mv2-check\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(item.name)\" [disabled]=\"!canSelect(item.name)\" (change)=\"toggleMetric(item.name)\" />\r\n <span class=\"mv2-cat-name\" [title]=\"item.name\">{{item.name.substring(entry.info.name.length + 3)}}</span>\r\n </label>\r\n @if (instancesLabel(item)) {\r\n <span class=\"mv2-cat-inst\" [title]=\"item.instances.join('\\n')\">{{instancesLabel(item)}}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n </aside>\r\n\r\n <!-- Grafici, tessere e tabella -->\r\n <section class=\"mv2-area\">\r\n @if (selected.length == 0) {\r\n <div class=\"mv2-panel mv2-placeholder\">\r\n <i class=\"fa fa-line-chart\"></i>\r\n {{'Scegli una o pi\u00F9 metriche dal catalogo.' | localize: lc}}\r\n </div>\r\n } @else {\r\n <!-- Tessere dei valori correnti -->\r\n <div class=\"mv2-tiles\">\r\n @for (tile of tiles; track tile.info.name) {\r\n <div class=\"mv2-tile\">\r\n <div class=\"mv2-tile-name\" [title]=\"tile.info.name\">{{tile.info.name}}</div>\r\n @for (row of tile.rows; track row.instance) {\r\n <div class=\"mv2-tile-row\">\r\n <span class=\"mv2-dot\" [style.background]=\"row.color\" [title]=\"row.instance\"></span>\r\n <strong>{{row.value}}</strong>\r\n @if (row.extra) {\r\n <span class=\"mv2-muted\">{{row.extra}}</span>\r\n }\r\n </div>\r\n } @empty {\r\n <div class=\"mv2-muted\">\u2014</div>\r\n }\r\n <svg class=\"mv2-spark\" viewBox=\"0 0 120 28\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\r\n @for (spark of tile.sparks; track $index) {\r\n <path [attr.d]=\"spark.d\" [attr.stroke]=\"spark.color\" fill=\"none\" stroke-width=\"1.3\" vector-effect=\"non-scaling-stroke\" />\r\n }\r\n </svg>\r\n @if (tile.time) {\r\n <div class=\"mv2-muted mv2-small\">{{'alle' | localize: lc}} {{tile.time}}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Legenda comune: un clic nasconde o mostra l'istanza ovunque -->\r\n <div class=\"mv2-legend-bar\">\r\n <div class=\"mv2-legend\">\r\n @for (item of legend; track item.instance) {\r\n <button type=\"button\" class=\"mv2-legend-item\" [class.mv2-legend-off]=\"item.hidden\" (click)=\"toggleInstance(item.instance)\">\r\n <span class=\"mv2-dot\" [style.background]=\"item.color\"></span>{{item.instance}}\r\n </button>\r\n }\r\n </div>\r\n <span class=\"mv2-muted mv2-small\">{{'Orari in ora locale' | localize: lc}} ({{zone}}) · {{'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)\">×</button>\r\n </div>\r\n @if (panel.empty) {\r\n <div class=\"mv2-empty\">\r\n @if (dataLoading) {\r\n <i class=\"fa fa-spinner fa-spin\"></i> {{'Caricamento\u2026' | localize: lc}}\r\n } @else {\r\n {{emptyLabel(panel.info.name)}}\r\n }\r\n </div>\r\n } @else {\r\n @for (chart of panel.charts; track chart.key) {\r\n <mv2-chart [series]=\"chart.series\" [from]=\"chartFrom\" [to]=\"chartTo\" [height]=\"chart.height\" [unit]=\"chart.unit\" [marks]=\"marks\"\r\n [cursor]=\"cursor\" [format]=\"format\" (cursorChange)=\"cursor = $event\" (zoom)=\"zoomTo($event)\"></mv2-chart>\r\n }\r\n }\r\n </div>\r\n }\r\n } @else {\r\n @for (section of tables; track section.info.name) {\r\n <div class=\"mv2-panel\">\r\n <div class=\"mv2-panel-header\">\r\n <span class=\"mv2-panel-title\">{{section.info.name}}</span>\r\n <span class=\"mv2-type\">{{Type[section.info.metricType]}}</span>\r\n <span class=\"mv2-spacer\"></span>\r\n @if (section.total > 0) {\r\n <button type=\"button\" class=\"mv2-btn\" (click)=\"exportCsv(section)\"><i class=\"fa fa-download\"></i> {{'Esporta CSV' | localize: lc}}</button>\r\n }\r\n </div>\r\n @if (section.total == 0) {\r\n <div class=\"mv2-empty\">{{emptyLabel(section.info.name)}}</div>\r\n } @else {\r\n <div class=\"mv2-table-wrap\">\r\n <table class=\"mv2-table\">\r\n <thead>\r\n <tr>\r\n @for (column of section.columns; track column.key) {\r\n <th [class.mv2-num]=\"column.num\" (click)=\"sortBy(section, column)\">\r\n {{column.label}}\r\n @if (section.sortKey == column.key) {\r\n <i class=\"fa\" [class.fa-caret-down]=\"section.sortDesc\" [class.fa-caret-up]=\"!section.sortDesc\"></i>\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (row of shownRows(section); track $index) {\r\n <tr>\r\n @for (column of section.columns; track column.key) {\r\n <td [class.mv2-num]=\"column.num\">\r\n @if (column.key == 'instance') {\r\n <span class=\"mv2-dot\" [style.background]=\"color(row['instance'])\"></span>\r\n }\r\n {{cell(row, column)}}\r\n </td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n @if (section.total > section.rows.length || section.total > 300) {\r\n <div class=\"mv2-muted mv2-small mv2-pad\">{{'Mostrate le prime 300 righe di' | localize: lc}} {{section.total}}; {{\"l'esportazione le comprende tutte.\" | localize: lc}}</div>\r\n }\r\n }\r\n </div>\r\n }\r\n }\r\n }\r\n </section>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block}:host ::ng-deep .mv2-noclear .p-select-clear-icon{display:none}:host ::ng-deep .mv2-fill{display:block;width:100%}.mv2-body{container-type:inline-size}.mv2-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px 18px;padding-top:6px}.mv2-cluster{display:flex;flex-wrap:wrap;align-items:center;gap:10px}.mv2-cluster-end{margin-left:auto}.mv2-field{width:200px}.mv2-field-wide{width:340px;max-width:100%}.mv2-field-full{width:100%}.mv2-group{position:relative;display:inline-flex}.mv2-group-label{position:absolute;top:-8px;left:8px;z-index:2;padding:0 4px;background:var(--p-content-background, #fff);color:var(--p-text-muted-color, #64748b);font-size:12px;line-height:15px;white-space:nowrap;pointer-events:none}.mv2-seg{display:inline-flex;height:39px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);overflow:hidden;background:var(--p-content-background, #fff)}.mv2-seg button{display:inline-flex;align-items:center;gap:6px;padding:0 12px;border:0;border-left:1px solid var(--p-form-field-border-color, #cbd5e1);background:transparent;color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.mv2-seg button:first-child{border-left:0}.mv2-seg button:hover:not(.mv2-on):not(:disabled){background:var(--p-content-hover-background, #f8fafc)}.mv2-seg button:disabled{color:var(--p-text-muted-color, #64748b);opacity:.6;cursor:not-allowed}.mv2-seg button:focus-visible{outline:2px solid var(--p-primary-color, #6366f1);outline-offset:-2px}.mv2-seg button.mv2-on{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca);font-weight:500;box-shadow:inset 0 -2px 0 var(--p-primary-color, #6366f1)}.mv2-seg.mv2-seg-small{height:28px}.mv2-seg.mv2-seg-small button{padding:0 9px;font-size:12px}.mv2-live-dot{width:8px;height:8px;border-radius:50%;background:#16a34a}.mv2-live-dot.mv2-live-paused{background:var(--p-text-muted-color, #64748b)}.mv2-btn{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:13px;white-space:nowrap;cursor:pointer}.mv2-btn:hover{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.mv2-icon-btn{width:26px;height:26px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--p-text-muted-color, #64748b);font-size:18px;line-height:1;cursor:pointer}.mv2-icon-btn:hover{background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155)}.mv2-link{padding:0;border:0;background:transparent;color:var(--p-primary-color, #6366f1);font-size:12px;cursor:pointer}.mv2-link:hover{text-decoration:underline}.mv2-muted{color:var(--p-text-muted-color, #64748b);font-size:12px}.mv2-small{font-size:11px}.mv2-pad{padding:6px 2px}.mv2-spacer{flex:1 1 auto}.mv2-alert{margin-top:10px;padding:7px 12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-color, #334155);font-size:13px}.mv2-alert.mv2-alert-warn{border-color:#fcd34d;background:#fffbeb;color:#92400e}.mv2-alert.mv2-alert-error{border-color:#fca5a5;background:#fef2f2;color:#991b1b}.mv2-main{display:grid;grid-template-columns:300px minmax(0,1fr);gap:14px;align-items:start;margin-top:14px}@container (max-width: 1000px){.mv2-main{grid-template-columns:minmax(0,1fr)}.mv2-catalog{position:static;max-height:360px}}.mv2-panel{padding:12px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0}.mv2-catalog{position:sticky;top:10px;max-height:calc(100vh - 20px);overflow:auto;font-size:13px;color:var(--p-text-color, #334155)}.mv2-presets{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 8px}.mv2-chip{padding:3px 10px;border:1px solid var(--p-form-field-border-color, #cbd5e1);border-radius:12px;background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:12px;cursor:pointer}.mv2-chip:hover{background:var(--p-highlight-background, #eef2ff);color:var(--p-highlight-color, #4338ca)}.mv2-counter{display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);color:var(--p-text-muted-color, #64748b);font-size:12px}.mv2-cat-group{margin-top:10px}.mv2-cat-title{margin-bottom:4px;color:var(--p-text-muted-color, #64748b);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.mv2-cat-item{display:flex;align-items:center;gap:6px;padding:2px 0;min-width:0}.mv2-cat-item.mv2-cat-disabled{opacity:.5}.mv2-cat-sub{padding-left:18px}.mv2-check{display:flex;align-items:center;gap:6px;flex:1 1 auto;min-width:0;margin:0;cursor:pointer}.mv2-check input{flex:none;display:inline-block;width:auto;height:auto;margin:0;accent-color:var(--p-primary-color, #6366f1)}.mv2-cat-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mv2-cat-meta{flex:none;color:var(--p-text-muted-color, #64748b);font-size:11px;white-space:nowrap}.mv2-cat-inst{margin-left:4px;padding:0 5px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:8px;color:var(--p-text-muted-color, #64748b);font-size:10px;cursor:help}.mv2-caret{flex:none;padding:0 4px;border:0;background:transparent;color:var(--p-text-muted-color, #64748b);font-size:11px;cursor:pointer}.mv2-cat-items{padding:2px 0 4px 18px}.mv2-area{display:flex;flex-direction:column;gap:12px;min-width:0}.mv2-placeholder{display:flex;flex-direction:column;align-items:center;gap:10px;padding:70px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.mv2-placeholder .fa{font-size:28px;opacity:.5}.mv2-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}.mv2-tile{display:flex;flex-direction:column;gap:3px;padding:10px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px);background:var(--p-content-background, #fff);min-width:0;font-size:13px}.mv2-tile-name{overflow:hidden;color:var(--p-text-muted-color, #64748b);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.mv2-tile-row{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}.mv2-spark{display:block;width:100%;height:28px;margin-top:2px}.mv2-dot{display:inline-block;flex:none;width:9px;height:9px;margin-right:4px;border-radius:50%}.mv2-legend-bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px}.mv2-legend{display:flex;flex-wrap:wrap;gap:4px 6px}.mv2-legend-item{display:inline-flex;align-items:center;padding:2px 8px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:12px;background:var(--p-content-background, #fff);color:var(--p-text-color, #334155);font-size:12px;cursor:pointer}.mv2-legend-item.mv2-legend-off{opacity:.45;text-decoration:line-through}.mv2-chart-panel{padding-bottom:6px}.mv2-panel-header{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;margin-bottom:6px}.mv2-panel-title{font-size:13px;font-weight:600;color:var(--p-text-color, #334155)}.mv2-type{padding:0 7px;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:10px;color:var(--p-text-muted-color, #64748b);font-size:11px;line-height:18px}.mv2-empty{padding:26px 12px;color:var(--p-text-muted-color, #64748b);text-align:center}.mv2-table-wrap{max-height:420px;overflow:auto;border:1px solid var(--p-content-border-color, #e2e8f0);border-radius:var(--p-form-field-border-radius, 6px)}.mv2-table{width:100%;border-collapse:separate;border-spacing:0;font-size:12px;color:var(--p-text-color, #334155)}.mv2-table th{position:sticky;top:0;z-index:1;padding:6px 8px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);background:var(--p-content-hover-background, #f8fafc);color:var(--p-text-muted-color, #64748b);font-weight:500;white-space:nowrap;cursor:pointer}.mv2-table td{padding:3px 8px;border-bottom:1px solid var(--p-content-border-color, #e2e8f0);white-space:nowrap}.mv2-num{text-align:right!important;font-variant-numeric:tabular-nums}\n"] }]
|
|
3805
|
+
}], ctorParameters: () => [{ type: i1$1.DateService }, { type: i1.LocalizationService }, { type: i3.PreferencesService }, { type: i1$1.InterComService }, { type: i4.ActivatedRoute }, { type: AdminModuleBaseService }] });
|
|
3806
|
+
|
|
3807
|
+
class TopActivitiesDataView extends AppSearch {
|
|
3808
|
+
constructor() { super(); }
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
class AdmTopActivitieLoc extends LocalizationService {
|
|
3812
|
+
constructor(injector) {
|
|
3813
|
+
super(injector);
|
|
3814
|
+
super.set("it->en", "Sessioni Attive", ["Active Sessions"]);
|
|
3815
|
+
super.set("it->en", "Prime Istruzioni", ["Top Nodes"]);
|
|
3816
|
+
super.set("it->en", "Prime Sessioni", ["Top Sessions"]);
|
|
3817
|
+
super.set("it->en", "Carico i Dati", ["Loading Data"]);
|
|
3818
|
+
super.set("it->en", "Attività Principali", ["Top Activities"]);
|
|
3819
|
+
super.set("it->en", "Descrizione Nodo", ["Node Description"]);
|
|
3820
|
+
super.set("it->en", "Nodo", ["Node"]);
|
|
3821
|
+
super.set("it->en", "Attività", ["Activity"]);
|
|
3822
|
+
super.set("it->en", "Limite", ["Limit"]);
|
|
3823
|
+
super.set("it->en", "Tipo Nodo", ["Node Type"]);
|
|
3824
|
+
super.set("it->en", "Utente", ["User"]);
|
|
3825
|
+
super.set("it->en", "Tenant", ["Tenant"]);
|
|
3826
|
+
super.set("it->en", "Processo", ["Process"]);
|
|
3827
|
+
super.set("it->en", "Aggiorna ogni", ["Update every"]);
|
|
3828
|
+
super.set("it->en", "secondo/i", ["second/s"]);
|
|
3829
|
+
super.set("it->en", "Thread Massimi", ["Maximum Threads"]);
|
|
3830
|
+
super.set("it->en", "CPU", ["CPUs"]);
|
|
3831
|
+
super.set("it->en", "Classe Attesa", ["Wait Class Name"]);
|
|
3832
|
+
super.set("it->en", "Seriale", ["Serial"]);
|
|
3833
|
+
super.set("it->en", "Stato", ["Status"]);
|
|
3834
|
+
super.set("it->en", "Tipo", ["Type"]);
|
|
3835
|
+
super.set("it->en", "Utente", ["User"]);
|
|
3836
|
+
}
|
|
3837
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivitieLoc, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3838
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivitieLoc }); }
|
|
3839
|
+
}
|
|
3840
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivitieLoc, decorators: [{
|
|
3841
|
+
type: Injectable
|
|
3842
|
+
}], ctorParameters: () => [{ type: i0.Injector }] });
|
|
3843
|
+
|
|
3844
|
+
// Angular
|
|
3845
|
+
class AdmTopActivitiesComponent {
|
|
3846
|
+
constructor(dateExts, lc, localStorage, location, intercom, route, adms, mod) {
|
|
3847
|
+
this.dateExts = dateExts;
|
|
3848
|
+
this.lc = lc;
|
|
3849
|
+
this.localStorage = localStorage;
|
|
3850
|
+
this.location = location;
|
|
3851
|
+
this.intercom = intercom;
|
|
3852
|
+
this.route = route;
|
|
3853
|
+
this.adms = adms;
|
|
3854
|
+
this.mod = mod;
|
|
3855
|
+
this.searchView = new TopActivitiesDataView();
|
|
3856
|
+
this.autoUpdate = false;
|
|
3857
|
+
this.seconds = "5";
|
|
3858
|
+
this.rtWindow = null;
|
|
3859
|
+
this.destroyed$ = new ReplaySubject(1);
|
|
3860
|
+
this.seriesPropertiesMapping = {};
|
|
3861
|
+
this.propertiesSeriesMapping = {
|
|
3862
|
+
["cputime"]: "CPU",
|
|
3863
|
+
["waittime"]: "Wait",
|
|
3864
|
+
["scripttime"]: "Scripting",
|
|
3865
|
+
["dbtime"]: "DB",
|
|
3866
|
+
["nettime"]: "Network",
|
|
3867
|
+
["throttletime"]: "Throttling",
|
|
3868
|
+
["sleeptime"]: "Sleep",
|
|
3869
|
+
["iotime"]: "IO"
|
|
3870
|
+
};
|
|
3871
|
+
this.seriesPropertiesMapping2 = {};
|
|
3872
|
+
this.be = route.snapshot.params["be"] || "";
|
|
3873
|
+
this.intercom.send(InboundMessageTypes.Navigation, { sub: false, routeTitle: this.lc.loc(this.route.snapshot.data["title"]) });
|
|
3874
|
+
}
|
|
3875
|
+
ngOnInit() {
|
|
3876
|
+
// Ogni minuto calcolo qual è la finestra del realTime
|
|
3877
|
+
// interval(60 * 1000).pipe(takeUntil(this.destroyed$)).subscribe(_ => {
|
|
3878
|
+
// this.updateRealTimeWindow();
|
|
3879
|
+
// });
|
|
3880
|
+
this.storageSub = this.route.queryParams.subscribe(params => {
|
|
3881
|
+
this.Init(params["ignoreStorage"]);
|
|
3882
|
+
});
|
|
3883
|
+
}
|
|
3884
|
+
async updateRealTimeWindow() {
|
|
3885
|
+
let data = new TopActivitiesDataView();
|
|
3886
|
+
data.filterbe = this.be;
|
|
3887
|
+
data = await this.adms.getTopActivities(this.be, data);
|
|
3888
|
+
let reference = data.items[0];
|
|
3889
|
+
for (let i = 0; i < reference.actsessions.length; i++) {
|
|
3890
|
+
let itemref = reference.actsessions[i];
|
|
3891
|
+
let theD = this.dateExts.getDateConvertion(itemref.sampletime);
|
|
3892
|
+
if (!this.rtWindow)
|
|
3893
|
+
this.rtWindow = { from: theD, to: theD };
|
|
3894
|
+
else {
|
|
3895
|
+
if (theD.isBefore(this.rtWindow.from))
|
|
3896
|
+
this.rtWindow.from = theD;
|
|
3897
|
+
if (theD.isAfter(this.rtWindow.to))
|
|
3898
|
+
this.rtWindow.to = theD;
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
Init(ignoreStore) {
|
|
3903
|
+
this.doInit(ignoreStore);
|
|
3904
|
+
}
|
|
3905
|
+
async doInit(ignoreStorage) {
|
|
3906
|
+
let item = await this.localStorage.getItem("HotSearchTopAct").toPromise();
|
|
3907
|
+
if (!ignoreStorage && item) {
|
|
3908
|
+
this.init(item);
|
|
3909
|
+
}
|
|
3910
|
+
else {
|
|
3911
|
+
this.init(null);
|
|
3912
|
+
this.location.replaceState(`/pages/${this.mod}/topact`);
|
|
3913
|
+
this.route.snapshot.queryParams = [];
|
|
3914
|
+
await this.localStorage.removeItem("HotSearchTopAct").toPromise();
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
init(HotPotato) {
|
|
3918
|
+
if (HotPotato) {
|
|
3919
|
+
this.searchView = HotPotato;
|
|
3920
|
+
this.POSTGetItems(this.searchView, false);
|
|
3921
|
+
}
|
|
3922
|
+
else {
|
|
3923
|
+
this.searchView = new TopActivitiesDataView();
|
|
3924
|
+
this.sendSearch();
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
ngOnDestroy() {
|
|
3928
|
+
this.destroyed$.next(true);
|
|
3929
|
+
this.destroyed$.complete();
|
|
3930
|
+
if (this.storageSub)
|
|
3931
|
+
this.storageSub.unsubscribe();
|
|
3932
|
+
}
|
|
3933
|
+
async sendSearch(resetPage = true, update = false, searchViewOverride = null) {
|
|
3934
|
+
let sv = searchViewOverride ?? this.searchView;
|
|
3935
|
+
if ((sv.filterfrom || sv.filterto) && !this.rtWindow)
|
|
3936
|
+
await this.updateRealTimeWindow();
|
|
3937
|
+
let dateRequestOnRealtimeWindow = sv.filterfrom && this.dateExts.getDateConvertion(sv.filterfrom).isAfter(this.rtWindow.from) &&
|
|
3938
|
+
sv.filterto && this.dateExts.getDateConvertion(sv.filterto).isBefore(this.rtWindow.to);
|
|
3939
|
+
sv.filterrealtime = dateRequestOnRealtimeWindow;
|
|
3940
|
+
sv.searchinprogress = true;
|
|
3941
|
+
this.POSTGetItems(sv, update);
|
|
3942
|
+
}
|
|
3943
|
+
async POSTGetItems(data, update) {
|
|
3944
|
+
data.filterbe = this.be;
|
|
3945
|
+
let res = await this.adms.getTopActivities(this.be, data);
|
|
3946
|
+
//Se sono in modalità di update non devo toccare la searchView attuale se non per il searchinprogress che viene messo dall'es table
|
|
3947
|
+
if (!update) {
|
|
3948
|
+
this.searchView = res;
|
|
3949
|
+
this.localStorage.setItem("HotSearchTopAct", this.searchView).subscribe();
|
|
3950
|
+
}
|
|
3951
|
+
// DirectorInfos anche se per qualche motivo crasha di brutto è comunque non obbligatorio, evito di non caricare la pagina solo perché lui scoppia
|
|
3952
|
+
let dirinfos = await this.adms.getInfos(this.be);
|
|
3953
|
+
this.searchView.searchinprogress = false;
|
|
3954
|
+
this.reviewBindings(update, res, dirinfos);
|
|
3955
|
+
}
|
|
3956
|
+
loadNodeDetails(nodeId) {
|
|
3957
|
+
this.NodeDetails = null;
|
|
3958
|
+
requestAnimationFrame(async () => {
|
|
3959
|
+
this.NodeDetails = await this.adms.getNodeStats(this.be, nodeId, this.searchView.filterglobal);
|
|
3960
|
+
});
|
|
3961
|
+
}
|
|
3962
|
+
reviewBindings(update, searchViewOverride, dirInfos) {
|
|
3963
|
+
let reference = (searchViewOverride ?? this.searchView).items[0];
|
|
3964
|
+
this.TopNodes = reference.topnodes;
|
|
3965
|
+
this.TopSessions = reference.topsessions;
|
|
3966
|
+
this.DirInfos = dirInfos?.cluster ? dirInfos : null;
|
|
3967
|
+
this.DirInfosGlobal = !dirInfos?.cluster ? dirInfos : null;
|
|
3968
|
+
if (!reference.actsessions || reference.actsessions.length == 0) {
|
|
3969
|
+
this.TopActiveSessions = null;
|
|
3970
|
+
return;
|
|
3971
|
+
}
|
|
3972
|
+
let series = [];
|
|
3973
|
+
let props = Object.keys(reference.actsessions[0]);
|
|
3974
|
+
for (let i = 0; i < props.length; i++) {
|
|
3975
|
+
if (props[i] != 'sampletime') {
|
|
3976
|
+
let s = new LineSerie(this.propertiesSeriesMapping[props[i]], [], null, null, "sessions");
|
|
3977
|
+
series.push(s);
|
|
3978
|
+
this.seriesPropertiesMapping[props[i]] = s;
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
for (let i = 0; i < reference.actsessions.length; i++) {
|
|
3982
|
+
let itemref = reference.actsessions[i];
|
|
3983
|
+
let theD = new Date(itemref.sampletime);
|
|
3984
|
+
for (let i = 0; i < props.length; i++)
|
|
3985
|
+
if (props[i] != 'sampletime' && (!update || this.dateExts.getDateConvertion(theD).isAfter(this.LastDataPoint)))
|
|
3986
|
+
this.seriesPropertiesMapping[props[i]].values.push(new DateValue(theD, itemref[props[i]]));
|
|
3987
|
+
}
|
|
3988
|
+
if (update && this.ChartInstance) {
|
|
3989
|
+
let data = new LineChartData(series, new Limit(dirInfos?.threads, this.lc.loc("Thread Massimi")));
|
|
3990
|
+
if (series.some(t => t.values.length > 0)) {
|
|
3991
|
+
this.ChartInstance.addData(data);
|
|
3992
|
+
this.LastDataPoint = series.map(t => dayjs.max(t.values.map(v => this.dateExts.getDateConvertion(v.d))))[0];
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
else {
|
|
3996
|
+
this.TopActiveSessions = new LineChartData(series, new Limit(dirInfos?.threads, this.lc.loc("Thread Massimi")));
|
|
3997
|
+
this.LastDataPoint = series.map(t => dayjs.max(t.values.map(v => this.dateExts.getDateConvertion(v.d))))[0];
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
autoUpdateChanged() {
|
|
4001
|
+
if (!/^[1-9]\d*$/.test(this.seconds))
|
|
4002
|
+
this.seconds = '10';
|
|
4003
|
+
const secondsInMs = parseInt(this.seconds, null) * 1000;
|
|
4004
|
+
if (this.autoUpdate) {
|
|
4005
|
+
this.sendSearch(false, true);
|
|
4006
|
+
this.updateTimer = setInterval(() => { this.sendSearch(false, true); }, secondsInMs);
|
|
4007
|
+
}
|
|
4008
|
+
else
|
|
1042
4009
|
clearInterval(this.updateTimer);
|
|
1043
4010
|
}
|
|
1044
4011
|
dataSelectionChanged(event) {
|
|
@@ -1208,6 +4175,7 @@ class AdmTopActivityV2Loc extends LocalizationService {
|
|
|
1208
4175
|
super.set("it->en", "Macchina", ["Machine"]);
|
|
1209
4176
|
super.set("it->en", "Tipo", ["Type"]);
|
|
1210
4177
|
super.set("it->en", "Processo", ["Process"]);
|
|
4178
|
+
super.set("it->en", "Errore", ["Error"]);
|
|
1211
4179
|
}
|
|
1212
4180
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivityV2Loc, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1213
4181
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmTopActivityV2Loc }); }
|
|
@@ -1451,13 +4419,22 @@ class AdmTopActivityV2Component {
|
|
|
1451
4419
|
this.applyResult(req, res);
|
|
1452
4420
|
this.saveSettings();
|
|
1453
4421
|
}
|
|
1454
|
-
catch {
|
|
1455
|
-
//
|
|
4422
|
+
catch (err) {
|
|
4423
|
+
// Finestra non valida e simili arrivano come 542 (avviso), il resto come errore. La sessione scaduta la gestisce il servizio
|
|
4424
|
+
if (err?.status != 401)
|
|
4425
|
+
this.showError(err);
|
|
1456
4426
|
}
|
|
1457
4427
|
finally {
|
|
1458
4428
|
this.loading = false;
|
|
1459
4429
|
}
|
|
1460
4430
|
}
|
|
4431
|
+
showError(err) {
|
|
4432
|
+
let text = this.adms.errorText(err);
|
|
4433
|
+
if (err?.status == 542)
|
|
4434
|
+
this.adms.msgExts.simpleWarning(text);
|
|
4435
|
+
else
|
|
4436
|
+
this.adms.msgExts.simpleError(`${this.lc.loc("Errore")} ${err?.status ?? ""}: ${text}`);
|
|
4437
|
+
}
|
|
1461
4438
|
buildRequest() {
|
|
1462
4439
|
let to = this.toDate(this.filterto) ?? new Date();
|
|
1463
4440
|
let from = this.toDate(this.filterfrom) ?? new Date(to.getTime() - 3600 * 1000);
|
|
@@ -2491,13 +5468,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
|
|
|
2491
5468
|
// La Top Activity v2 prende la rotta "topact" solo se DirectorFeature.TopActivityV2 è attivo nella configurazione del modulo,
|
|
2492
5469
|
// altrimenti la guardia fallisce e la rotta successiva apre la pagina di sempre
|
|
2493
5470
|
const topActivityV2Enabled = () => inject(ADM_TOPACT_V2, { optional: true }) ?? false;
|
|
5471
|
+
// Lo stesso per il profiler con DirectorFeature.ProfilerV2
|
|
5472
|
+
const profilerV2Enabled = () => inject(ADM_PROFILER_V2, { optional: true }) ?? false;
|
|
5473
|
+
// La pagina del workload report c'è solo con DirectorFeature.WorkloadReportV2: prima il report era solo un file da scaricare dal profiler
|
|
5474
|
+
const workloadReportV2Enabled = () => inject(ADM_WORKLOAD_V2, { optional: true }) ?? false;
|
|
5475
|
+
// Le metriche v2 prendono la rotta "metrics" solo con DirectorFeature.MetricsV2, come la Top Activity
|
|
5476
|
+
const metricsV2Enabled = () => inject(ADM_METRICS_V2, { optional: true }) ?? false;
|
|
2494
5477
|
const routeConfig = [
|
|
2495
5478
|
{
|
|
2496
5479
|
path: "", children: [
|
|
2497
5480
|
{ path: "serverworkers/:be", component: AdmWrkComponent, data: { title: "Ricerca Processi Applicativi", internalNavigation: false } },
|
|
5481
|
+
{ path: "profiler/:be", component: AdmProfilerV2Component, canMatch: [profilerV2Enabled], data: { title: "Profiler del Server", internalNavigation: false } },
|
|
2498
5482
|
{ path: "profiler/:be", component: AdmProfilerComponent, data: { title: "Profiler del Server", internalNavigation: false } },
|
|
5483
|
+
{ path: "workload/:be", component: AdmWorkloadV2Component, canMatch: [workloadReportV2Enabled], data: { title: "Workload report", internalNavigation: false } },
|
|
2499
5484
|
{ path: "topact/:be", component: AdmTopActivityV2Component, canMatch: [topActivityV2Enabled], data: { title: "Attività Principali", internalNavigation: false } },
|
|
2500
5485
|
{ path: "topact/:be", component: AdmTopActivitiesComponent, data: { title: "Attività Principali", internalNavigation: false } },
|
|
5486
|
+
{ path: "metrics/:be", component: AdmMetricsV2Component, canMatch: [metricsV2Enabled], data: { title: "Metriche di Sistema", internalNavigation: false } },
|
|
2501
5487
|
{ path: "metrics/:be", component: AdmMetricsComponent, data: { title: "Metriche di Sistema", internalNavigation: false } },
|
|
2502
5488
|
{ path: "serverlogs/:be", component: AdmLogsComponent, data: { title: "Log del Server", internalNavigation: false } },
|
|
2503
5489
|
{ path: "serveraudits/:be", component: AdmAuditsComponent, data: { title: "Ricerca Audit Applicativi", internalNavigation: false } },
|
|
@@ -2505,9 +5491,12 @@ const routeConfig = [
|
|
|
2505
5491
|
{ path: "serverviews/:be", component: AdmDynamicViewsComponent, data: { title: "Ricerca Viste Dinamiche Applicative", internalNavigation: false } },
|
|
2506
5492
|
{ path: "metadataquery/:be", component: MtdQueryComponent, data: { title: "Ricerca Viste Dinamiche Applicative", internalNavigation: false } },
|
|
2507
5493
|
{ path: "serverworkers", component: AdmWrkComponent, data: { title: "Ricerca Processi Applicativi", internalNavigation: false } },
|
|
5494
|
+
{ path: "profiler", component: AdmProfilerV2Component, canMatch: [profilerV2Enabled], data: { title: "Profiler del Server", internalNavigation: false } },
|
|
2508
5495
|
{ path: "profiler", component: AdmProfilerComponent, data: { title: "Profiler del Server", internalNavigation: false } },
|
|
5496
|
+
{ path: "workload", component: AdmWorkloadV2Component, canMatch: [workloadReportV2Enabled], data: { title: "Workload report", internalNavigation: false } },
|
|
2509
5497
|
{ path: "topact", component: AdmTopActivityV2Component, canMatch: [topActivityV2Enabled], data: { title: "Attività Principali", internalNavigation: false } },
|
|
2510
5498
|
{ path: "topact", component: AdmTopActivitiesComponent, data: { title: "Attività Principali", internalNavigation: false } },
|
|
5499
|
+
{ path: "metrics", component: AdmMetricsV2Component, canMatch: [metricsV2Enabled], data: { title: "Metriche di Sistema", internalNavigation: false } },
|
|
2511
5500
|
{ path: "metrics", component: AdmMetricsComponent, data: { title: "Metriche di Sistema", internalNavigation: false } },
|
|
2512
5501
|
{ path: "serverlogs", component: AdmLogsComponent, data: { title: "Log del Server", internalNavigation: false } },
|
|
2513
5502
|
{ path: "serveraudits", component: AdmAuditsComponent, data: { title: "Ricerca Audit Applicativi", internalNavigation: false } },
|
|
@@ -2541,6 +5530,10 @@ const MODULES = [
|
|
|
2541
5530
|
const COMPONENTS = [
|
|
2542
5531
|
AdmWrkComponent,
|
|
2543
5532
|
AdmProfilerComponent,
|
|
5533
|
+
AdmProfilerV2Component,
|
|
5534
|
+
AdmWorkloadV2Component,
|
|
5535
|
+
AdmMetricsV2Component,
|
|
5536
|
+
MetricsV2ChartComponent,
|
|
2544
5537
|
AdmTopActivitiesComponent,
|
|
2545
5538
|
AdmTopActivityV2Component,
|
|
2546
5539
|
SessionDetailComponent,
|
|
@@ -2554,7 +5547,8 @@ const COMPONENTS = [
|
|
|
2554
5547
|
AdmSystemParametersComponent
|
|
2555
5548
|
];
|
|
2556
5549
|
const PIPES = [
|
|
2557
|
-
FormatTimePipe
|
|
5550
|
+
FormatTimePipe,
|
|
5551
|
+
ProfilerFormatPipe
|
|
2558
5552
|
];
|
|
2559
5553
|
class AdmModule {
|
|
2560
5554
|
constructor(commService) {
|
|
@@ -2565,6 +5559,10 @@ class AdmModule {
|
|
|
2565
5559
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmModule, deps: [{ token: AdminModuleCommService }], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
2566
5560
|
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.34", ngImport: i0, type: AdmModule, declarations: [AdmWrkComponent,
|
|
2567
5561
|
AdmProfilerComponent,
|
|
5562
|
+
AdmProfilerV2Component,
|
|
5563
|
+
AdmWorkloadV2Component,
|
|
5564
|
+
AdmMetricsV2Component,
|
|
5565
|
+
MetricsV2ChartComponent,
|
|
2568
5566
|
AdmTopActivitiesComponent,
|
|
2569
5567
|
AdmTopActivityV2Component,
|
|
2570
5568
|
SessionDetailComponent,
|
|
@@ -2592,11 +5590,15 @@ class AdmModule {
|
|
|
2592
5590
|
EsGertrudeModule,
|
|
2593
5591
|
EsChartsModule,
|
|
2594
5592
|
ComponentsModule,
|
|
2595
|
-
DirectivesModule, i4.RouterModule, FormatTimePipe
|
|
5593
|
+
DirectivesModule, i4.RouterModule, FormatTimePipe,
|
|
5594
|
+
ProfilerFormatPipe] }); }
|
|
2596
5595
|
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmModule, providers: [
|
|
2597
5596
|
AdminModuleCommService,
|
|
2598
5597
|
{ provide: ADM_CONVERSATIONS, useFactory: () => !!myconfig.TransactionTimeoutSeconds },
|
|
2599
|
-
{ provide: ADM_TOPACT_V2, useFactory: () => !!myconfig?.DirectorFeature?.TopActivityV2 }
|
|
5598
|
+
{ provide: ADM_TOPACT_V2, useFactory: () => !!myconfig?.DirectorFeature?.TopActivityV2 },
|
|
5599
|
+
{ provide: ADM_PROFILER_V2, useFactory: () => !!myconfig?.DirectorFeature?.ProfilerV2 },
|
|
5600
|
+
{ provide: ADM_WORKLOAD_V2, useFactory: () => !!myconfig?.DirectorFeature?.WorkloadReportV2 },
|
|
5601
|
+
{ provide: ADM_METRICS_V2, useFactory: () => !!myconfig?.DirectorFeature?.MetricsV2 }
|
|
2600
5602
|
], imports: [MODULES] }); }
|
|
2601
5603
|
}
|
|
2602
5604
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: AdmModule, decorators: [{
|
|
@@ -2607,7 +5609,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
|
|
|
2607
5609
|
providers: [
|
|
2608
5610
|
AdminModuleCommService,
|
|
2609
5611
|
{ provide: ADM_CONVERSATIONS, useFactory: () => !!myconfig.TransactionTimeoutSeconds },
|
|
2610
|
-
{ provide: ADM_TOPACT_V2, useFactory: () => !!myconfig?.DirectorFeature?.TopActivityV2 }
|
|
5612
|
+
{ provide: ADM_TOPACT_V2, useFactory: () => !!myconfig?.DirectorFeature?.TopActivityV2 },
|
|
5613
|
+
{ provide: ADM_PROFILER_V2, useFactory: () => !!myconfig?.DirectorFeature?.ProfilerV2 },
|
|
5614
|
+
{ provide: ADM_WORKLOAD_V2, useFactory: () => !!myconfig?.DirectorFeature?.WorkloadReportV2 },
|
|
5615
|
+
{ provide: ADM_METRICS_V2, useFactory: () => !!myconfig?.DirectorFeature?.MetricsV2 }
|
|
2611
5616
|
]
|
|
2612
5617
|
}]
|
|
2613
5618
|
}], ctorParameters: () => [{ type: AdminModuleCommService }] });
|
|
@@ -2620,5 +5625,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
|
|
|
2620
5625
|
* Generated bundle index. Do not edit.
|
|
2621
5626
|
*/
|
|
2622
5627
|
|
|
2623
|
-
export { ADM_CONVERSATIONS, ADM_MODULE_PATH, ADM_TOPACT_V2, AdmMessageTypes, AdmModule, MK_ADM, routeConfig, topActivityV2Enabled };
|
|
5628
|
+
export { ADM_CONVERSATIONS, ADM_METRICS_V2, ADM_MODULE_PATH, ADM_PROFILER_V2, ADM_TOPACT_V2, ADM_WORKLOAD_V2, AdmMessageTypes, AdmModule, MK_ADM, metricsV2Enabled, profilerV2Enabled, routeConfig, topActivityV2Enabled, workloadReportV2Enabled };
|
|
2624
5629
|
//# sourceMappingURL=esfaenza-admin-module.mjs.map
|