+
diff --git a/appserver/monitoring-console/webapp/src/main/webapp/mc-render.js b/appserver/monitoring-console/webapp/src/main/webapp/mc-line-chart.js
similarity index 88%
rename from appserver/monitoring-console/webapp/src/main/webapp/mc-render.js
rename to appserver/monitoring-console/webapp/src/main/webapp/mc-line-chart.js
index 87147b9aed7..10f6572acfc 100644
--- a/appserver/monitoring-console/webapp/src/main/webapp/mc-render.js
+++ b/appserver/monitoring-console/webapp/src/main/webapp/mc-line-chart.js
@@ -40,7 +40,7 @@
/*jshint esversion: 8 */
-var MonitoringConsoleRender = (function() {
+MonitoringConsole.LineChart = (function() {
const DEFAULT_BG_COLORS = [
'rgba(153, 102, 255, 0.2)',
@@ -60,8 +60,8 @@ var MonitoringConsoleRender = (function() {
];
function createMinimumLineDataset(data, points, lineColor) {
- var min = data.observedMin;
- var minPoints = [{t:points[0].t, y:min}, {t:points[points.length-1].t, y:min}];
+ let min = data.observedMin;
+ let minPoints = [{t:points[0].t, y:min}, {t:points[points.length-1].t, y:min}];
return {
data: minPoints,
@@ -75,8 +75,8 @@ var MonitoringConsoleRender = (function() {
}
function createMaximumLineDataset(data, points, lineColor) {
- var max = data.observedMax;
- var maxPoints = [{t:points[0].t, y:max}, {t:points[points.length-1].t, y:max}];
+ let max = data.observedMax;
+ let maxPoints = [{t:points[0].t, y:max}, {t:points[points.length-1].t, y:max}];
return {
data: maxPoints,
@@ -89,8 +89,8 @@ var MonitoringConsoleRender = (function() {
}
function createAverageLineDataset(data, points, lineColor) {
- var avg = data.observedSum / data.observedValues;
- var avgPoints = [{t:points[0].t, y:avg}, {t:points[points.length-1].t, y:avg}];
+ let avg = data.observedSum / data.observedValues;
+ let avgPoints = [{t:points[0].t, y:avg}, {t:points[points.length-1].t, y:avg}];
return {
data: avgPoints,
@@ -117,7 +117,7 @@ var MonitoringConsoleRender = (function() {
if (!points1d)
return [];
let points2d = new Array(points1d.length / 2);
- for (var i = 0; i < points2d.length; i++) {
+ for (let i = 0; i < points2d.length; i++) {
points2d[i] = { t: new Date(points1d[i*2]), y: points1d[i*2+1] };
}
return points2d;
@@ -127,7 +127,7 @@ var MonitoringConsoleRender = (function() {
if (!points1d)
return [];
let points2d = new Array((points1d.length / 2) - 1);
- for (var i = 0; i < points2d.length; i++) {
+ for (let i = 0; i < points2d.length; i++) {
let t0 = points1d[i*2];
let t1 = points1d[i*2+2];
let y0 = points1d[i*2+1];
@@ -144,7 +144,7 @@ var MonitoringConsoleRender = (function() {
if (widget.options.perSec) {
return [ createMainLineDataset(data, createInstancePerSecPoints(data.points), lineColor, bgColor) ];
}
- let points = createInstancePoints(data.points)
+ let points = createInstancePoints(data.points);
let datasets = [];
datasets.push(createMainLineDataset(data, points, lineColor, bgColor));
if (points.length > 0 && widget.options.drawAvgLine) {
@@ -160,12 +160,12 @@ var MonitoringConsoleRender = (function() {
}
return {
- chart: function(update) {
- var data = update.data;
- var widget = update.widget;
- var chart = update.chart();
- var datasets = [];
- for (var j = 0; j < data.length; j++) {
+ onDataUpdate: function(update) {
+ let data = update.data;
+ let widget = update.widget;
+ let chart = update.chart();
+ let datasets = [];
+ for (let j = 0; j < data.length; j++) {
datasets = datasets.concat(
createInstanceDatasets(widget, data[j], DEFAULT_LINE_COLORS[j], DEFAULT_BG_COLORS[j]));
}
diff --git a/appserver/monitoring-console/webapp/src/main/webapp/mc-main.js b/appserver/monitoring-console/webapp/src/main/webapp/mc-main.js
new file mode 100644
index 00000000000..5094ed644c9
--- /dev/null
+++ b/appserver/monitoring-console/webapp/src/main/webapp/mc-main.js
@@ -0,0 +1,61 @@
+/*
+ DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+
+ Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved.
+
+ The contents of this file are subject to the terms of either the GNU
+ General Public License Version 2 only ("GPL") or the Common Development
+ and Distribution License("CDDL") (collectively, the "License"). You
+ may not use this file except in compliance with the License. You can
+ obtain a copy of the License at
+ https://github.com/payara/Payara/blob/master/LICENSE.txt
+ See the License for the specific
+ language governing permissions and limitations under the License.
+
+ When distributing the software, include this License Header Notice in each
+ file and include the License file at glassfish/legal/LICENSE.txt.
+
+ GPL Classpath Exception:
+ The Payara Foundation designates this particular file as subject to the "Classpath"
+ exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ file that accompanied this code.
+
+ Modifications:
+ If applicable, add the following below the License Header, with the fields
+ enclosed by brackets [] replaced by your own identifying information:
+ "Portions Copyright [year] [name of copyright owner]"
+
+ Contributor(s):
+ If you wish your version of this file to be governed by only the CDDL or
+ only the GPL Version 2, indicate your decision by adding "[Contributor]
+ elects to include this software in this distribution under the [CDDL or GPL
+ Version 2] license." If you don't indicate a single choice of license, a
+ recipient has the option to distribute your version of this file under
+ either the CDDL, the GPL Version 2 or to extend the choice of license to
+ its licensees as provided above. However, if you add GPL Version 2 code
+ and therefore, elected the GPL Version 2 license, then the option applies
+ only if the new code is made subject to such option by the copyright
+ holder.
+*/
+
+/*jshint esversion: 8 */
+
+Chart.defaults.global.defaultFontColor = "#fff";
+
+/**
+ * The different parts of the Monitoring Console are added as the below properties by the individual files.
+ */
+var MonitoringConsole = {
+ /**
+ * Functions to update the actual HTML page of the MC
+ **/
+ View: undefined,
+ /**
+ * Functions of manipulate the model of the MC (often returns a layout that is applied to the View)
+ **/
+ Model: undefined,
+ /**
+ * Functions specifically to take the data and prepare the display of a line chart using the underlying charting library.
+ **/
+ LineChart: undefined,
+};
diff --git a/appserver/monitoring-console/webapp/src/main/webapp/mc-ui.js b/appserver/monitoring-console/webapp/src/main/webapp/mc-model.js
similarity index 72%
rename from appserver/monitoring-console/webapp/src/main/webapp/mc-ui.js
rename to appserver/monitoring-console/webapp/src/main/webapp/mc-model.js
index 8154041062f..1c0a5b245b4 100644
--- a/appserver/monitoring-console/webapp/src/main/webapp/mc-ui.js
+++ b/appserver/monitoring-console/webapp/src/main/webapp/mc-model.js
@@ -40,71 +40,12 @@
/*jshint esversion: 8 */
-Chart.defaults.global.defaultFontColor = "#fff";
-
-/**
- * A utility with 'static' helper functions that have no side effect.
- *
- * Extracting such function into this object should help organise the code and allow context independent testing
- * of the helper functions in the browser.
- *
- * The MonitoringConsole object is dependent on this object but not vice versa.
- */
-var MonitoringConsoleUtils = (function() {
-
- return {
-
- getSpan: function(widget, numberOfColumns, currentColumn) {
- let span = widget.grid && widget.grid.span ? widget.grid.span : 1;
- if (typeof span === 'string') {
- if (span === 'full') {
- span = numberOfColumns;
- } else {
- span = parseInt(span);
- }
- }
- if (span > numberOfColumns - currentColumn) {
- span = numberOfColumns - currentColumn;
- }
- return span;
- },
-
- getPageId: function(name) {
- return name.replace(/[^-a-zA-Z0-9]/g, '_').toLowerCase();
- },
-
- getTimeLabel: function(value, index, values) {
- if (values.length == 0 || index == 0)
- return value;
- let span = values[values.length -1].value - values[0].value;
- if (span < 120000) { // less then two minutes
- let lastMinute = new Date(values[index-1].value).getMinutes();
- return new Date(values[index].value).getMinutes() != lastMinute ? value : ''+new Date(values[index].value).getSeconds();
- }
- return value;
- },
-
- readTextFile: function(file) {
- return new Promise(function(resolve, reject){
- var reader = new FileReader();
- reader.onload = function(evt){
- resolve(evt.target.result);
- };
- reader.onerror = function(err) {
- reject(err);
- };
- reader.readAsText(file);
- });
- },
- };
-})();
-
/**
* The object that manages the internal state of the monitoring console page.
*
- * It depends on the MonitoringConsoleUtils object.
+ * It depends on the MonitoringConsole.Utils object.
*/
-var MonitoringConsole = (function() {
+MonitoringConsole.Model = (function() {
/**
* Key used in local stage for the userInterface
*/
@@ -127,6 +68,10 @@ var MonitoringConsole = (function() {
settings: {},
};
+ function getPageId(name) {
+ return name.replace(/[^-a-zA-Z0-9]/g, '_').toLowerCase();
+ }
+
/**
* Internal API for managing set model of the user interface.
@@ -156,7 +101,7 @@ var MonitoringConsole = (function() {
*/
function sanityCheckPage(page) {
if (!page.id)
- page.id = MonitoringConsoleUtils.getPageId(page.name);
+ page.id = getPageId(page.name);
if (!page.widgets)
page.widgets = {};
if (!page.numberOfColumns || page.numberOfColumns < 1)
@@ -203,7 +148,7 @@ var MonitoringConsole = (function() {
function doCreate(name) {
if (!name)
throw "New page must have a unique name";
- var id = MonitoringConsoleUtils.getPageId(name);
+ var id = getPageId(name);
if (pages[id])
throw "A page with name "+name+" already exist";
let page = sanityCheckPage({name: name});
@@ -249,6 +194,106 @@ var MonitoringConsole = (function() {
let ui = { pages: pages, settings: settings };
return prettyPrint ? JSON.stringify(ui, null, 2) : JSON.stringify(ui);
}
+
+ function readTextFile(file) {
+ return new Promise(function(resolve, reject) {
+ let reader = new FileReader();
+ reader.onload = function(evt){
+ resolve(evt.target.result);
+ };
+ reader.onerror = function(err) {
+ reject(err);
+ };
+ reader.readAsText(file);
+ });
+ }
+
+ function doLayout(columns) {
+ let page = pages[currentPageId];
+ if (!page)
+ return [];
+ if (columns)
+ page.numberOfColumns = columns;
+ let numberOfColumns = page.numberOfColumns || 1;
+ let widgets = page.widgets;
+ // init temporary and result data structure
+ let widgetsByColumn = new Array(numberOfColumns);
+ var layout = new Array(numberOfColumns);
+ for (let col = 0; col < numberOfColumns; col++) {
+ widgetsByColumn[col] = [];
+ layout[col] = [];
+ }
+ // organise widgets in columns
+ Object.values(widgets).forEach(function(widget) {
+ let column = widget.grid && widget.grid.column ? widget.grid.column : 0;
+ widgetsByColumn[Math.min(Math.max(column, 0), widgetsByColumn.length - 1)].push(widget);
+ });
+ // order columns by item position
+ for (let col = 0; col < numberOfColumns; col++) {
+ widgetsByColumn[col] = widgetsByColumn[col].sort(function (a, b) {
+ if (!a.grid || !a.grid.item)
+ return -1;
+ if (!b.grid || !b.grid.item)
+ return 1;
+ return a.grid.item - b.grid.item;
+ });
+ }
+ // do layout by marking cells with item (left top corner in case of span), null (empty) and undefined (spanned)
+ for (let col = 0; col < numberOfColumns; col++) {
+ let columnWidgets = widgetsByColumn[col];
+ for (let item = 0; item < columnWidgets.length; item++) {
+ let widget = columnWidgets[item];
+ let span = getSpan(widget, numberOfColumns, col);
+ let info = { span: span, widget: widget};
+ let column0 = layout[col];
+ let row0 = getEmptyRowIndex(column0, span);
+ for (let spanX = 0; spanX < span; spanX++) {
+ let column = layout[col + spanX];
+ if (spanX == 0) {
+ if (!widget.grid)
+ widget.grid = { column: col, span: span }; // init grid
+ widget.grid.item = column.length; // update item position
+ } else {
+ while (column.length < row0)
+ column.push(null); // null marks empty cells
+ }
+ for (let spanY = 0; spanY < span; spanY++) {
+ column.push(spanX === 0 && spanY === 0 ? info : undefined);
+ }
+ }
+ }
+ }
+ // give the layout a uniform row number
+ let maxRows = Math.max(numberOfColumns, layout.map(column => column.length).reduce((acc, cur) => acc ? Math.max(acc, cur) : cur));
+ for (let col = 0; col < numberOfColumns; col++) {
+ while (layout[col].length < maxRows) {
+ layout[col].push(null);
+ }
+ }
+ return layout;
+ }
+
+ function getSpan(widget, numberOfColumns, currentColumn) {
+ let span = widget.grid && widget.grid.span ? widget.grid.span : 1;
+ if (typeof span === 'string') {
+ if (span === 'full') {
+ span = numberOfColumns;
+ } else {
+ span = parseInt(span);
+ }
+ }
+ if (span > numberOfColumns - currentColumn) {
+ span = numberOfColumns - currentColumn;
+ }
+ return span;
+ }
+
+ /**
+ * @return {number} row position in column where n rows are still empty ('null' marks empty)
+ */
+ function getEmptyRowIndex(column, n) {
+ return Math.max(column.length, column.findIndex((elem,index,array) => array.slice(index, index + n).every(e => e === null)));
+ }
return {
currentPage: function() {
@@ -261,7 +306,7 @@ var MonitoringConsole = (function() {
});
},
- $export: function() {
+ exportPages: function() {
return doExport(true);
},
@@ -269,11 +314,11 @@ var MonitoringConsole = (function() {
* @param {FileList|object} userInterface - a plain user interface configuration object or a file containing such an object
* @param {function} onImportComplete - optional function to call when import is done
*/
- $import: async (userInterface, onImportComplete) => {
+ importPages: async (userInterface, onImportComplete) => {
if (userInterface instanceof FileList) {
let file = userInterface[0];
if (file) {
- let json = await MonitoringConsoleUtils.readTextFile(file);
+ let json = await readTextFile(file);
doImport(JSON.parse(json));
}
} else {
@@ -302,9 +347,9 @@ var MonitoringConsole = (function() {
},
renamePage: function(name) {
- let pageId = MonitoringConsoleUtils.getPageId(name);
+ let pageId = getPageId(name);
if (pages[pageId])
- throw "Page with name already exist";
+ return false;
let page = pages[currentPageId];
page.name = name;
page.id = pageId;
@@ -312,6 +357,7 @@ var MonitoringConsole = (function() {
delete pages[currentPageId];
currentPageId = pageId;
doStore();
+ return true;
},
/**
@@ -390,49 +436,7 @@ var MonitoringConsole = (function() {
},
arrange: function(columns) {
- let page = pages[currentPageId];
- if (!page)
- return [];
- if (columns)
- page.numberOfColumns = columns;
- let numberOfColumns = page.numberOfColumns || 1;
- let widgets = page.widgets;
- let configsByColumn = new Array(numberOfColumns);
- for (let col = 0; col < numberOfColumns; col++)
- configsByColumn[col] = [];
- // insert order widgets
- Object.values(widgets).forEach(function(widget) {
- let column = widget.grid && widget.grid.column ? widget.grid.column : 0;
- configsByColumn[Math.min(Math.max(column, 0), configsByColumn.length - 1)].push(widget);
- });
- // build up rows with columns, occupy spans with empty
- var layout = new Array(numberOfColumns);
- for (let col = 0; col < numberOfColumns; col++)
- layout[col] = [];
- for (let col = 0; col < numberOfColumns; col++) {
- let orderedConfigs = configsByColumn[col].sort(function (a, b) {
- if (!a.grid || !a.grid.item)
- return -1;
- if (!b.grid || !b.grid.item)
- return 1;
- return a.grid.item - b.grid.item;
- });
- orderedConfigs.forEach(function(widget) {
- let span = MonitoringConsoleUtils.getSpan(widget, numberOfColumns, col);
- let info = { span: span, widget: widget};
- for (let spanX = 0; spanX < span; spanX++) {
- let column = layout[col + spanX];
- if (spanX == 0) {
- if (!widget.grid)
- widget.grid = { column: col, span: span }; // init grid
- widget.grid.item = column.length; // update item position
- }
- for (let spanY = 0; spanY < span; spanY++) {
- column.push(spanX === 0 && spanY === 0 ? info : null);
- }
- }
- });
- }
+ let layout = doLayout(columns);
doStore();
return layout;
},
@@ -492,6 +496,17 @@ var MonitoringConsole = (function() {
options.scales.xAxes[0].ticks.maxRotation = rotation;
options.legend.display = widget.options.showLegend === true;
}
+
+ function getTimeLabel(value, index, values) {
+ if (values.length == 0 || index == 0)
+ return value;
+ let span = values[values.length -1].value - values[0].value;
+ if (span < 120000) { // less then two minutes
+ let lastMinute = new Date(values[index-1].value).getMinutes();
+ return new Date(values[index].value).getMinutes() != lastMinute ? value : ''+new Date(values[index].value).getSeconds();
+ }
+ return value;
+ }
return {
/**
@@ -520,7 +535,7 @@ var MonitoringConsole = (function() {
round: 'second',
},
ticks: {
- callback: MonitoringConsoleUtils.getTimeLabel,
+ callback: getTimeLabel,
minRotation: 90,
maxRotation: 90,
}
@@ -633,63 +648,75 @@ var MonitoringConsole = (function() {
};
})();
- return {
-
- init: function(onDataUpdate) {
- UI.load();
- Interval.init(function() {
- let widgets = UI.currentPage().widgets;
- let payload = {
- };
- let instances = $('#cfgInstances').val();
- payload.series = Object.keys(widgets).map(function(series) {
- return {
- series: series,
- instances: instances
- };
- });
- let request = $.ajax({
- url: 'api/series/data/',
- type: 'POST',
- data: JSON.stringify(payload),
- contentType:"application/json; charset=utf-8",
- dataType:"json",
- });
- request.done(function(response) {
- Object.values(widgets).forEach(function(widget) {
- onDataUpdate({
- widget: widget,
- data: response[widget.series],
- chart: () => Charts.getOrCreate(widget),
- });
+ function doInit(onDataUpdate) {
+ UI.load();
+ Interval.init(function() {
+ let widgets = UI.currentPage().widgets;
+ let payload = {
+ };
+ let instances = $('#cfgInstances').val();
+ payload.series = Object.keys(widgets).map(function(series) {
+ return {
+ series: series,
+ instances: instances
+ };
+ });
+ let request = $.ajax({
+ url: 'api/series/data/',
+ type: 'POST',
+ data: JSON.stringify(payload),
+ contentType:"application/json; charset=utf-8",
+ dataType:"json",
+ });
+ request.done(function(response) {
+ Object.values(widgets).forEach(function(widget) {
+ onDataUpdate({
+ widget: widget,
+ data: response[widget.series],
+ chart: () => Charts.getOrCreate(widget),
});
});
- request.fail(function(jqXHR, textStatus) {
- Object.values(widgets).forEach(function(widget) {
- onDataUpdate({
- widget: widget,
- chart: () => Charts.getOrCreate(widget),
- });
+ });
+ request.fail(function(jqXHR, textStatus) {
+ Object.values(widgets).forEach(function(widget) {
+ onDataUpdate({
+ widget: widget,
+ chart: () => Charts.getOrCreate(widget),
});
});
});
- Interval.resume();
- return UI.arrange();
- },
+ });
+ Interval.resume();
+ return UI.arrange();
+ }
+
+ function doConfigureSelection(widgetUpdate) {
+ UI.configureWidget(widgetUpdate).forEach(Charts.update);
+ return UI.arrange();
+ }
+
+ function doConfigureWidget(series, widgetUpdate) {
+ UI.configureWidget(widgetUpdate, series).forEach(Charts.update);
+ return UI.arrange();
+ }
+
+ /**
+ * The public API object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+ return {
- $export: UI.$export,
- $import: function(userInterface, onImportComplete) {
- UI.$import(userInterface, () => onImportComplete(UI.arrange()));
- },
+ init: doInit,
/**
* @param {function} consumer - a function with one argument accepting the array of series names
*/
- listSeries: function(consumer) {
- $.getJSON("api/series/", consumer);
- },
-
+ listSeries: (consumer) => $.getJSON("api/series/", consumer),
+
listPages: UI.listPages,
+ exportPages: UI.exportPages,
+ importPages: function(userInterface, onImportComplete) {
+ UI.importPages(userInterface, () => onImportComplete(UI.arrange()));
+ },
/**
* API to control the chart refresh interval.
@@ -770,10 +797,33 @@ var MonitoringConsole = (function() {
return UI.arrange();
},
- configure: function(series, widgetUpdate) {
- UI.configureWidget(widgetUpdate, series).forEach(Charts.update);
- return UI.arrange();
- },
+ configure: doConfigureWidget,
+
+ moveLeft: (series) => doConfigureWidget(series, function(widget) {
+ if (!widget.grid.column || widget.grid.column > 0) {
+ widget.grid.item = undefined;
+ widget.grid.column = widget.grid.column ? widget.grid.column - 1 : 1;
+ }
+ }),
+
+ moveRight: (series) => doConfigureWidget(series, function(widget) {
+ if (!widget.grid.column || widget.grid.column < 4) {
+ widget.grid.item = undefined;
+ widget.grid.column = widget.grid.column ? widget.grid.column + 1 : 1;
+ }
+ }),
+
+ spanMore: (series) => doConfigureWidget(series, function(widget) {
+ if (! widget.grid.span || widget.grid.span < 4) {
+ widget.grid.span = !widget.grid.span ? 2 : widget.grid.span + 1;
+ }
+ }),
+
+ spanLess: (series) => doConfigureWidget(series, function(widget) {
+ if (widget.grid.span > 1) {
+ widget.grid.span -= 1;
+ }
+ }),
/**
* API for the set of selected widgets on the current page.
@@ -781,16 +831,16 @@ var MonitoringConsole = (function() {
Selection: {
listSeries: UI.selected,
+ isSingle: () => UI.selected().length == 1,
+ first: () => UI.currentPage().widgets[UI.selected()[0]],
toggle: UI.select,
clear: UI.deselect,
/**
* @param {function} widgetUpdate - a function accepting chart configuration applied to each chart
*/
- configure: function(widgetUpdate) {
- UI.configureWidget(widgetUpdate).forEach(Charts.update);
- return UI.arrange();
- },
+ configure: doConfigureSelection,
+
},
},
diff --git a/appserver/monitoring-console/webapp/src/main/webapp/mc-page.js b/appserver/monitoring-console/webapp/src/main/webapp/mc-page.js
deleted file mode 100644
index 79a44b4e6eb..00000000000
--- a/appserver/monitoring-console/webapp/src/main/webapp/mc-page.js
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
-
- Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved.
-
- The contents of this file are subject to the terms of either the GNU
- General Public License Version 2 only ("GPL") or the Common Development
- and Distribution License("CDDL") (collectively, the "License"). You
- may not use this file except in compliance with the License. You can
- obtain a copy of the License at
- https://github.com/payara/Payara/blob/master/LICENSE.txt
- See the License for the specific
- language governing permissions and limitations under the License.
-
- When distributing the software, include this License Header Notice in each
- file and include the License file at glassfish/legal/LICENSE.txt.
-
- GPL Classpath Exception:
- The Payara Foundation designates this particular file as subject to the "Classpath"
- exception as provided by the Payara Foundation in the GPL Version 2 section of the License
- file that accompanied this code.
-
- Modifications:
- If applicable, add the following below the License Header, with the fields
- enclosed by brackets [] replaced by your own identifying information:
- "Portions Copyright [year] [name of copyright owner]"
-
- Contributor(s):
- If you wish your version of this file to be governed by only the CDDL or
- only the GPL Version 2, indicate your decision by adding "[Contributor]
- elects to include this software in this distribution under the [CDDL or GPL
- Version 2] license." If you don't indicate a single choice of license, a
- recipient has the option to distribute your version of this file under
- either the CDDL, the GPL Version 2 or to extend the choice of license to
- its licensees as provided above. However, if you add GPL Version 2 code
- and therefore, elected the GPL Version 2 license, then the option applies
- only if the new code is made subject to such option by the copyright
- holder.
-*/
-
-/*jshint esversion: 8 */
-
-var MonitoringConsolePage = (function() {
-
- function download(filename, text) {
- var pom = document.createElement('a');
- pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
- pom.setAttribute('download', filename);
-
- if (document.createEvent) {
- var event = document.createEvent('MouseEvents');
- event.initEvent('click', true, true);
- pom.dispatchEvent(event);
- }
- else {
- pom.click();
- }
- }
-
- function renderPageTabs() {
- var bar = $("#pagesTabs");
- bar.empty();
- MonitoringConsole.listPages().forEach(function(page) {
- var tabId = page.id + '-tab';
- var css = "page-tab" + (page.active ? ' page-selected' : '');
- //TODO when clicking active page tab the options could open/close
- var newTab = $('', {id: tabId, "class": css, text: page.name});
- newTab.click(function() {
- onPageChange(MonitoringConsole.Page.changeTo(page.id));
- });
- bar.append(newTab);
- });
- var addPage = $('', {id: 'addPageTab', 'class': 'page-tab'}).html('+');
- addPage.click(function() {
- onPageChange(MonitoringConsole.Page.create('(Unnamed)'));
- });
- bar.append(addPage);
- }
-
- /**
- * Each chart needs to be in a relative positioned box to allow responsive sizing.
- * This fuction creates this box including the canvas element the chart is drawn upon.
- */
- function renderChartBox(cell) {
- var boxId = cell.widget.target + '-box';
- var box = $('#'+boxId);
- if (box.length > 0)
- return box.first();
- box = $('', { id: boxId, "class": "chart-box" });
- var win = $(window);
- box.append($('',{ id: cell.widget.target }));
- return box;
- }
-
- /**
- * This function refleshes the page with the given layout.
- */
- function renderPage(layout) {
- var table = $("
", { id: 'chart-grid'});
- var numberOfColumns = layout.length;
- var maxRow = 0;
- for (var col = 0; col < numberOfColumns; col++) {
- maxRow = Math.max(maxRow, layout[col].length);
- }
- var rowHeight = Math.round(($(window).height() - 100) / numberOfColumns);
- for (var row = 0; row < maxRow; row++) {
- var tr = $("
");
- for (var col = 0; col < numberOfColumns; col++) {
- if (layout[col].length <= row) {
- tr.append($("
"));
- } else {
- var cell = layout[col][row];
- if (cell) {
- var span = cell.span;
- var td = $("
", { colspan: span, rowspan: span, 'class': 'box', style: 'height: '+(span * rowHeight)+"px;"});
- td.append(renderChartCaption(cell));
- var status = $('', { "class": 'status-nodata'});
- status.append($('', {text: 'No Data'}));
- td.append(status);
- td.append(renderChartBox(cell));
- tr.append(td);
- } else if (row == 0 && layout[col].length == 0) {
- // a column with no content, span all rows
- var td = $("
", { rowspan: maxRow });
- tr.append(td);
- }
- }
- }
- table.append(tr);
- }
- $('#chart-container').empty();
- $('#chart-container').append(table);
- }
-
- function camelCaseToWords(str) {
- return str.replace(/([A-Z]+)/g, " $1").replace(/([A-Z][a-z])/g, " $1");
- }
-
- function renderChartCaption(cell) {
- var bar = $('', {"class": "caption-bar"});
- var series = cell.widget.series;
- var endOfTags = series.lastIndexOf(' ');
- var text = endOfTags <= 0
- ? camelCaseToWords(series)
- : ''+series.substring(0, endOfTags)+' '+camelCaseToWords(series.substring(endOfTags + 1));
- if (cell.widget.options.perSec) {
- text += ' (per second)';
- }
- var caption = $('', {title: 'Select '+series}).html(text);
- caption.click(function() {
- if (MonitoringConsole.Page.Widgets.Selection.toggle(series)) {
- bar.parent().addClass('chart-selected');
- } else {
- bar.parent().removeClass('chart-selected');
- }
- renderChartOptions();
- });
- bar.append(caption);
- var btnClose = $('', { "class": "btnIcon btnClose", title:'Remove chart from page' }).html('×');
- btnClose.click(function() {
- if (window.confirm('Do you really want to remove the chart from the page?')) {
- renderPage(MonitoringConsole.Page.Widgets.remove(series));
- }
- });
- bar.append(btnClose);
- var btnLarger = $('', { "class": "btnIcon", title:'Enlarge this chart' }).html('+');
- btnLarger.click(function() {
- renderPage(MonitoringConsole.Page.Widgets.configure(series, function(widget) {
- if (widget.grid.span < 4) {
- widget.grid.span = !widget.grid.span ? 1 : widget.grid.span + 1;
- }
- }));
- });
- bar.append(btnLarger);
- var btnSmaller = $('', { "class": "btnIcon", title:'Shrink this chart' }).html('−');
- btnSmaller.click(function() {
- renderPage(MonitoringConsole.Page.Widgets.configure(series, function(widget) {
- if (!widget.grid.span || widget.grid.span > 1) {
- widget.grid.span = !widget.grid.span ? 1 : widget.grid.span - 1;
- }
- }));
- });
- bar.append(btnSmaller);
- var btnRight = $('', { "class": "btnIcon", title:'Move to right' }).html('▷');
- btnRight.click(function() {
- renderPage(MonitoringConsole.Page.Widgets.configure(series, function(widget) {
- if (!widget.grid.column || widget.grid.column < 4) {
- widget.grid.item = undefined;
- widget.grid.column = widget.grid.column ? widget.grid.column + 1 : 1;
- }
- }));
- });
- bar.append(btnRight);
- var btnLeft = $('', { "class": "btnIcon", title:'Move to left'}).html('◁');
- btnLeft.click(function() {
- renderPage(MonitoringConsole.Page.Widgets.configure(series, function(widget) {
- if (!widget.grid.column || widget.grid.column > 0) {
- widget.grid.item = undefined;
- widget.grid.column = widget.grid.column ? widget.grid.column - 1 : 1;
- }
- }));
- });
- bar.append(btnLeft);
- return bar;
- }
-
- function renderChartOptions() {
- var panelConsole = $('#console');
- if (MonitoringConsole.Settings.isDispayed()) {
- if (!panelConsole.hasClass('state-show-properties')) {
- panelConsole.addClass('state-show-properties');
- var instancePanel = $("#panelCfgInstances");
- instancePanel.empty();
- $.getJSON("api/instances/", function(instances) {
- var select = $('', {id: 'cfgInstances', multiple: true});
- for (var i = 0; i < instances.length; i++) {
- select.append($('', { value: instances[i], text: instances[i], selected:true}))
- }
- instancePanel.append(select);
- });
- $('#cftPageName').val(MonitoringConsole.Page.name());
- }
- } else {
- panelConsole.removeClass('state-show-properties');
- }
- }
-
- /**
- * This function is called when data was received or was failed to receive so the new data can be applied to the page.
- *
- * Depending on the update different content is rendered within a chart box.
- */
- function onDataUpdate(update) {
- var boxId = update.widget.target + '-box';
- var box = $('#'+boxId);
- if (box.length == 0) {
- if (console)
- console.log('WARN: Box for chart ' + update.widget.series + ' not ready.');
- return;
- }
- var td = box.closest('td');
- if (update.data) {
- td.children('.status-nodata').hide();
- var points = update.data[0].points;
- if (points.length == 4 && points[1] === points[3] && !update.widget.options.perSec) {
- if (td.children('.stable').length == 0) {
- var info = $('', { 'class': 'stable' });
- info.append($('', { text: points[1] }));
- td.append(info);
- box.hide();
- }
- } else {
- td.children('.stable').remove();
- box.show();
- MonitoringConsoleRender.chart(update);
- }
- } else {
- td.children('.status-nodata').width(box.width()-10).height(box.height()-10).show();
- }
-
- if (update.widget.selected) {
- td.addClass('chart-selected');
- } else {
- td.removeClass('chart-selected');
- }
- renderChartOptions();
- }
-
- /**
- * Method to call when page changes to update UI elements accordingly
- */
- function onPageChange(layout) {
- renderPage(layout);
- renderPageTabs();
- renderChartOptions();
- }
-
- function init() {
- $('#btnExport').click(function() {
- download('monitoring-console-config.json', MonitoringConsole.$export());
- });
- $('#btnImport').click(function() {
- $('#cfgImport').click();
- });
-
- $("#cfgPerSec").on('change', function() {
- var checked = this.checked;
- MonitoringConsole.Page.Widgets.Selection.configure(function(widget) {
- widget.options.perSec = checked;
- });
- });
-
- $("#cfgStartAtZero").on('change', function() {
- var checked = this.checked;
- MonitoringConsole.Page.Widgets.Selection.configure(function(widget) {
- widget.options.beginAtZero = checked;
- });
- });
-
- $("#cfgAutoTimeTicks").on('change', function() {
- var checked = this.checked;
- MonitoringConsole.Page.Widgets.Selection.configure(function(widget) {
- widget.options.autoTimeTicks = checked;
- });
- });
- $("#cfgDrawCurves").on('change', function() {
- var checked = this.checked;
- MonitoringConsole.Page.Widgets.Selection.configure(function(widget) {
- widget.options.drawCurves = checked;
- });
- });
- $('#cfgDrawAnimations').on('change', function() {
- var checked = this.checked;
- MonitoringConsole.Page.Widgets.Selection.configure(function(widget) {
- widget.options.drawAnimations = checked;
- });
- });
- $("#cfgRotateTimeLabels").on('change', function() {
- var checked = this.checked;
- MonitoringConsole.Page.Widgets.Selection.configure(function(widget) {
- widget.options.rotateTimeLabels = checked;
- });
- });
- $('#cfgDrawAvgLine').on('change', function() {
- var checked = this.checked;
- MonitoringConsole.Page.Widgets.Selection.configure(function(widget) {
- widget.options.drawAvgLine = checked;
- });
- });
- $('#cfgDrawMinLine').on('change', function() {
- var checked = this.checked;
- MonitoringConsole.Page.Widgets.Selection.configure(function(widget) {
- widget.options.drawMinLine = checked;
- });
- });
- $('#cfgDrawMaxLine').on('change', function() {
- var checked = this.checked;
- MonitoringConsole.Page.Widgets.Selection.configure(function(widget) {
- widget.options.drawMaxLine = checked;
- });
- });
- $('#cfgShowLegend').on('change', function() {
- var checked = this.checked;
- MonitoringConsole.Page.Widgets.Selection.configure(function(widget) {
- widget.options.showLegend = checked;
- });
- });
- $('#btnRemovePage').click(function() {
- if (window.confirm("Do you really want to delete the current page?")) {
- renderPage(MonitoringConsole.Page.erase());
- renderPageTabs();
- }
- });
- $('#btnMenu').click(function() {
- MonitoringConsole.Settings.toggle();
- renderChartOptions();
- });
- $('#cftPageName').on("propertychange change keyup paste input", function() {
- MonitoringConsole.Page.rename(this.value);
- renderPageTabs();
- });
- $('#btnAddChart').click(function() {
- $("#options option:selected").each(function() {
- var series = this.value;
- renderPage(MonitoringConsole.Page.Widgets.add(series));
- });
- });
-
- MonitoringConsole.listSeries(function(names) {
- var lastNs;
- var options = $("#options");
- $.each(names, function() {
- var key = this; //.replace(/ /g, ',');
- var ns = this.substring(3, this.indexOf(' '));
- var $option = $("").val(key).text(this.substring(this.indexOf(' ')));
- if (ns == lastNs) {
- options.find('optgroup').last().append($option);
- } else {
- var group = $('').attr('label', ns);
- group.append($option);
- options.append(group);
- }
- lastNs = ns;
- });
- });
- }
-
-//TODO add helper functions that based on a model for the properties build the property settings
-
- return {
- onPageReady: function() {
- init();
- renderPage(MonitoringConsole.init(onDataUpdate));
- renderPageTabs();
- },
- onPageChange: (layout) => onPageChange(layout),
- onPageUpdate: (layout) => renderPage(layout),
- };
-})();
diff --git a/appserver/monitoring-console/webapp/src/main/webapp/mc-view.js b/appserver/monitoring-console/webapp/src/main/webapp/mc-view.js
new file mode 100644
index 00000000000..5bac208cd85
--- /dev/null
+++ b/appserver/monitoring-console/webapp/src/main/webapp/mc-view.js
@@ -0,0 +1,393 @@
+/*
+ DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+
+ Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved.
+
+ The contents of this file are subject to the terms of either the GNU
+ General Public License Version 2 only ("GPL") or the Common Development
+ and Distribution License("CDDL") (collectively, the "License"). You
+ may not use this file except in compliance with the License. You can
+ obtain a copy of the License at
+ https://github.com/payara/Payara/blob/master/LICENSE.txt
+ See the License for the specific
+ language governing permissions and limitations under the License.
+
+ When distributing the software, include this License Header Notice in each
+ file and include the License file at glassfish/legal/LICENSE.txt.
+
+ GPL Classpath Exception:
+ The Payara Foundation designates this particular file as subject to the "Classpath"
+ exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ file that accompanied this code.
+
+ Modifications:
+ If applicable, add the following below the License Header, with the fields
+ enclosed by brackets [] replaced by your own identifying information:
+ "Portions Copyright [year] [name of copyright owner]"
+
+ Contributor(s):
+ If you wish your version of this file to be governed by only the CDDL or
+ only the GPL Version 2, indicate your decision by adding "[Contributor]
+ elects to include this software in this distribution under the [CDDL or GPL
+ Version 2] license." If you don't indicate a single choice of license, a
+ recipient has the option to distribute your version of this file under
+ either the CDDL, the GPL Version 2 or to extend the choice of license to
+ its licensees as provided above. However, if you add GPL Version 2 code
+ and therefore, elected the GPL Version 2 license, then the option applies
+ only if the new code is made subject to such option by the copyright
+ holder.
+*/
+
+/*jshint esversion: 8 */
+
+/**
+ *
+ **/
+MonitoringConsole.View = (function() {
+
+ /**
+ * Updates the DOM with the page navigation tabs so it reflects current model state
+ */
+ function updatePageNavigation() {
+ let nav = $("#pagesTabs");
+ nav.empty();
+ MonitoringConsole.Model.listPages().forEach(function(page) {
+ let tabId = page.id + '-tab';
+ let css = "page-tab" + (page.active ? ' page-selected' : '');
+ let pageTab = $('', {id: tabId, "class": css, text: page.name});
+ if (page.active) {
+ pageTab.click(function() {
+ MonitoringConsole.Model.Settings.toggle();
+ updatePageAndSelectionSettings();
+ });
+ } else {
+ pageTab.click(() => onPageChange(MonitoringConsole.Model.Page.changeTo(page.id)));
+ }
+ nav.append(pageTab);
+ });
+ let addPage = $('', {id: 'addPageTab', 'class': 'page-tab'}).html('+');
+ addPage.click(() => onPageChange(MonitoringConsole.Model.Page.create('(Unnamed)')));
+ nav.append(addPage);
+ }
+
+ /**
+ * Updates the DOM with the page and selection settings so it reflects current model state
+ */
+ function updatePageAndSelectionSettings() {
+ let panelConsole = $('#console');
+ if (MonitoringConsole.Model.Settings.isDispayed()) {
+ if (!panelConsole.hasClass('state-show-settings')) {
+ panelConsole.addClass('state-show-settings');
+ }
+ let panelSettings = $('#panel-settings');
+ panelSettings
+ .empty()
+ .append($('', { title: 'Delete current page', 'class': 'btnIcon btnClose' }).html('×').click(MonitoringConsole.View.onPageDelete))
+ .append(createPageSettings())
+ .append(createDataSettings());
+ if (MonitoringConsole.Model.Page.Widgets.Selection.isSingle()) {
+ panelSettings.append(createWidgetSettings(MonitoringConsole.Model.Page.Widgets.Selection.first()));
+ }
+ } else {
+ panelConsole.removeClass('state-show-settings');
+ }
+ }
+
+ /**
+ * Each chart needs to be in a relative positioned box to allow responsive sizing.
+ * This fuction creates this box including the canvas element the chart is drawn upon.
+ */
+ function createWidgetTargetContainer(cell) {
+ let boxId = cell.widget.target + '-box';
+ let box = $('#'+boxId);
+ if (box.length > 0)
+ return box.first();
+ box = $('', { id: boxId, "class": "chart-box" });
+ let win = $(window);
+ box.append($('',{ id: cell.widget.target }));
+ return box;
+ }
+
+ function camelCaseToWords(str) {
+ return str.replace(/([A-Z]+)/g, " $1").replace(/([A-Z][a-z])/g, " $1");
+ }
+
+ function formatSeriesName(widget) {
+ let series = widget.series;
+ let endOfTags = series.lastIndexOf(' ');
+ let text = endOfTags <= 0
+ ? camelCaseToWords(series)
+ : ''+series.substring(0, endOfTags)+' '+camelCaseToWords(series.substring(endOfTags + 1));
+ if (widget.options.perSec) {
+ text += ' (per second)';
+ }
+ return text;
+ }
+
+ function createWidgetToolbar(cell) {
+ let series = cell.widget.series;
+ return $('', {"class": "caption-bar"})
+ .append($('', {title: 'Select '+series}).html(formatSeriesName(cell.widget))
+ .click(() => onWidgetToolbarClick(cell.widget)))
+ .append(createToolbarButton('Remove chart from page', '×', onWidgetDelete))
+ .append(createToolbarButton('Enlarge this chart', '+', () => onPageUpdate(MonitoringConsole.Model.Page.Widgets.spanMore(series))))
+ .append(createToolbarButton('Shrink this chart', '−', () => onPageUpdate(MonitoringConsole.Model.Page.Widgets.spanLess(series))))
+ .append(createToolbarButton('Move to right', '▷', () => onPageUpdate(MonitoringConsole.Model.Page.Widgets.moveRight(series))))
+ .append(createToolbarButton('Move to left', '◁', () => onPageUpdate(MonitoringConsole.Model.Page.Widgets.moveLeft(series))));
+ }
+
+ function createToolbarButton(title, icon, onClick) {
+ return $('', { "class": "btnIcon", title: title}).html(icon).click(onClick);
+ }
+
+ /**
+ * Creates a checkbox to configure the attributes of a widget.
+ *
+ * @param {boolean} isChecked - if the created checkbox should be checked
+ * @param {function} onChange - a function accepting two arguments: the updated widget and the checked state of the checkbox after a change
+ */
+ function createConfigurationCheckbox(isChecked, onChange) {
+ return $("", { type: 'checkbox', checked: isChecked })
+ .on('change', function() {
+ let checked = this.checked;
+ MonitoringConsole.Model.Page.Widgets.Selection.configure((widget) => onChange(widget, checked));
+ });
+ }
+
+ function createSettingsSliderRow(label, min, max, value, onChange) {
+ return createSettingsRow(label, () => $('', {type: 'range', min:min, max:max, value: value})
+ .on('input', function() {
+ let val = this.valueAsNumber;
+ onPageUpdate(MonitoringConsole.Model.Page.Widgets.Selection.configure((widget) => onChange(widget, val)));
+ }));
+ }
+
+ function createWidgetSettings(widget) {
+ return createSettingsTable('settings-widget')
+ .append(createSettingsHeaderRow('Render Options'))
+ .append(createSettingsCheckboxRow('Per Second', widget.options.perSec, (widget, checked) => widget.options.perSec = checked))
+ .append(createSettingsCheckboxRow('Begin at Zero', widget.options.beginAtZero, (widget, checked) => widget.options.beginAtZero = checked))
+ .append(createSettingsCheckboxRow('Automatic Labels', widget.options.autoTimeTicks, (widget, checked) => widget.options.autoTimeTicks = checked))
+ .append(createSettingsCheckboxRow('Use Bezier Curves', widget.options.drawCurves, (widget, checked) => widget.options.drawCurves = checked))
+ .append(createSettingsCheckboxRow('Use Animations', widget.options.drawAnimations, (widget, checked) => widget.options.drawAnimations = checked))
+ .append(createSettingsCheckboxRow('Label X-Axis at 90°', widget.options.rotateTimeLabels, (widget, checked) => widget.options.rotateTimeLabels = checked))
+ .append(createSettingsHeaderRow('Chart Options'))
+ .append(createSettingsCheckboxRow('Show Average', widget.options.drawAvgLine, (widget, checked) => widget.options.drawAvgLine = checked))
+ .append(createSettingsCheckboxRow('Show Minimum', widget.options.drawMinLine, (widget, checked) => widget.options.drawMinLine = checked))
+ .append(createSettingsCheckboxRow('Show Maximum', widget.options.drawMaxLine, (widget, checked) => widget.options.drawMaxLine = checked))
+ .append(createSettingsCheckboxRow('Show Legend', widget.options.showLegend, (widget, checked) => widget.options.showLegend = checked))
+ .append(createSettingsHeaderRow('Layout'))
+ .append(createSettingsSliderRow('Span', 1, 4, widget.grid.span || 1, (widget, value) => widget.grid.span = value))
+ .append(createSettingsSliderRow('Column', 1, 4, 1 + (widget.grid.column || 0), (widget, value) => widget.grid.column = value - 1));
+ }
+
+ function createPageSettings() {
+ let widgetsSelection = $('');
+ MonitoringConsole.Model.listSeries(function(names) {
+ let lastNs;
+ $.each(names, function() {
+ let key = this; //.replace(/ /g, ',');
+ let ns = this.substring(3, this.indexOf(' '));
+ let $option = $("").val(key).text(this.substring(this.indexOf(' ')));
+ if (ns == lastNs) {
+ widgetsSelection.find('optgroup').last().append($option);
+ } else {
+ let group = $('').attr('label', ns);
+ group.append($option);
+ widgetsSelection.append(group);
+ }
+ lastNs = ns;
+ });
+ });
+ return createSettingsTable('settings-page')
+ .append(createSettingsHeaderRow('Page'))
+ .append(createSettingsRow('Name', () => $('', { type: 'text', value: MonitoringConsole.Model.Page.name() })
+ .on("propertychange change keyup paste input", function() {
+ if (MonitoringConsole.Model.Page.rename(this.value)) {
+ updatePageNavigation();
+ }
+ })))
+ .append(createSettingsRow('Widgets', () => $('')
+ .append(widgetsSelection)
+ .append($('', {title: 'Add selected metric', text: 'Add'})
+ .click(() => onPageUpdate(MonitoringConsole.Model.Page.Widgets.add(widgetsSelection.val()))))
+ ));
+ }
+
+ function createDataSettings() {
+ let instanceSelection = $('', {multiple: true});
+ $.getJSON("api/instances/", function(instances) {
+ for (let i = 0; i < instances.length; i++) {
+ instanceSelection.append($('', { value: instances[i], text: instances[i], selected:true}))
+ }
+ });
+ return createSettingsTable('settings-data')
+ .append(createSettingsHeaderRow('Data'))
+ .append(createSettingsRow('Instances', () => instanceSelection));
+ }
+
+ function createSettingsHeaderRow(caption) {
+ return $('
', { 'class': 'settings', id: id });
+ }
+
+ function createSettingsRow(label, createInput) {
+ return $('
').append($('
').text(label)).append($('
').append(createInput()));
+ }
+
+ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~[ Event Handlers ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ function onWidgetToolbarClick(widget) {
+ MonitoringConsole.Model.Page.Widgets.Selection.toggle(widget.series);
+ onWidgetUpdate(widget);
+ updatePageAndSelectionSettings();
+ }
+
+ function onWidgetDelete() {
+ if (window.confirm('Do you really want to remove the chart from the page?')) {
+ onPageUpdate(MonitoringConsole.Model.Page.Widgets.remove(series));
+ }
+ }
+
+ function onPageExport(filename, text) {
+ let pom = document.createElement('a');
+ pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
+ pom.setAttribute('download', filename);
+
+ if (document.createEvent) {
+ let event = document.createEvent('MouseEvents');
+ event.initEvent('click', true, true);
+ pom.dispatchEvent(event);
+ }
+ else {
+ pom.click();
+ }
+ }
+
+ /**
+ * This function is called when data was received or was failed to receive so the new data can be applied to the page.
+ *
+ * Depending on the update different content is rendered within a chart box.
+ */
+ function onDataUpdate(update) {
+ let boxId = update.widget.target + '-box';
+ let box = $('#'+boxId);
+ if (box.length == 0) {
+ if (console && console.log)
+ console.log('WARN: Box for chart ' + update.widget.series + ' not ready.');
+ return;
+ }
+ let td = box.closest('.widget');
+ if (update.data) {
+ td.children('.status-nodata').hide();
+ let points = update.data[0].points;
+ if (points.length == 4 && points[1] === points[3] && !update.widget.options.perSec) {
+ if (td.children('.stable').length == 0) {
+ let info = $('', { 'class': 'stable' });
+ info.append($('', { text: points[1] }));
+ td.append(info);
+ box.hide();
+ }
+ } else {
+ td.children('.stable').remove();
+ box.show();
+ MonitoringConsole.LineChart.onDataUpdate(update);
+ }
+ } else {
+ td.children('.status-nodata').width(box.width()-10).height(box.height()-10).show();
+ }
+
+ onWidgetUpdate(update.widget);
+ }
+
+ /**
+ * Called when changes to the widget require to update the view of the widget (non data related changes)
+ */
+ function onWidgetUpdate(widget) {
+ let container = $('#' + widget.target + '-box').closest('.widget');
+ if (widget.selected) {
+ container.addClass('chart-selected');
+ } else {
+ container.removeClass('chart-selected');
+ }
+ }
+
+ /**
+ * This function refleshes the page with the given layout.
+ */
+ function onPageUpdate(layout) {
+ let numberOfColumns = layout.length;
+ let maxRows = layout[0].length;
+ let table = $("