From 6fa0a93ed38d074484528cfb75e3cdaeba9dbb82 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Fri, 24 Jan 2020 17:50:19 +0100 Subject: [PATCH 01/15] SMARTTASK-202001 adds basic watches settings with list of watches --- .../fish/payara/monitoring/alert/Alert.java | 4 + .../payara/monitoring/alert/AlertService.java | 14 ++ .../payara/monitoring/alert/Condition.java | 10 ++ .../fish/payara/monitoring/alert/Watch.java | 13 ++ .../fish/payara/monitoring/model/Metric.java | 4 + .../store/InMemoryAlarmService.java | 11 +- .../monitoring-console/webapp/JS_DOCS.md | 33 +++- .../payara/monitoring/web/ApiResponses.java | 2 + .../web/MonitoringConsoleResource.java | 113 +++++++++++-- .../main/webapp/css/monitoring-console.css | 29 +++- .../webapp/src/main/webapp/index.html | 6 +- .../src/main/webapp/js/mc-view-components.js | 159 ++++++++++++++---- .../src/main/webapp/js/mc-view-units.js | 18 ++ .../webapp/src/main/webapp/js/mc-view.js | 62 ++++++- 14 files changed, 413 insertions(+), 65 deletions(-) diff --git a/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/Alert.java b/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/Alert.java index 92731a54b03..0af4fbd0293 100644 --- a/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/Alert.java +++ b/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/Alert.java @@ -84,6 +84,10 @@ public enum Level { public boolean isLessSevereThan(Level other) { return ordinal() > other.ordinal(); } + + public static fish.payara.monitoring.alert.Alert.Level parse(String level) { + return valueOf(level.toUpperCase()); + } } public static final class Frame implements Iterable { diff --git a/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/AlertService.java b/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/AlertService.java index 846e358ee15..2fda659c094 100644 --- a/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/AlertService.java +++ b/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/AlertService.java @@ -93,6 +93,12 @@ default Collection alerts() { * Watches */ + /** + * @param name name of the {@link Watch} to look up + * @return the {@link Watch} with the give name or {@code null} if no such watch exist. + */ + Watch watchByName(String name); + /** * Adds a watch to the evaluation loop. To remove the watch just use {@link Watch#stop()}. * @@ -100,6 +106,13 @@ default Collection alerts() { */ void addWatch(Watch watch); + /** + * Removes the given watch. Alerts triggered by this watch will end now. + * + * @param watch the watch to end, not null + */ + void removeWatch(Watch watch); + /** * @return All watches registered for evaluation. */ @@ -111,4 +124,5 @@ default Collection alerts() { * {@link #watches()}. */ Collection wachtesFor(Series series); + } diff --git a/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/Condition.java b/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/Condition.java index 5c82126872e..893a30bacee 100644 --- a/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/Condition.java +++ b/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/Condition.java @@ -42,6 +42,7 @@ import static java.lang.Math.abs; import static java.lang.Math.min; +import java.util.NoSuchElementException; import java.util.Objects; import fish.payara.monitoring.model.SeriesDataset; @@ -74,6 +75,15 @@ public enum Operator { public String toString() { return symbol; } + + public static Operator parse(String symbol) { + for (Operator op : values()) { + if (op.symbol.equals(symbol) ) { + return op; + } + } + throw new NoSuchElementException("Operator with symbol does not exist: " + symbol); + } } public final Operator comparison; diff --git a/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/Watch.java b/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/Watch.java index 64e59fd0fca..fc18975953b 100644 --- a/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/Watch.java +++ b/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/alert/Watch.java @@ -118,6 +118,7 @@ public Level getLevel() { private final Metric[] captured; private final Map statesByInstanceSeries = new ConcurrentHashMap<>(); private final AtomicBoolean stopped = new AtomicBoolean(false); + private boolean disabled; public Watch(String name, Metric watched) { this(name, watched, Circumstance.UNSPECIFIED, Circumstance.UNSPECIFIED, Circumstance.UNSPECIFIED); @@ -155,6 +156,18 @@ public boolean isStopped() { return stopped.get(); } + public boolean isDisabled() { + return disabled; + } + + public void disable() { + this.disabled = true; + } + + public void enable() { + this.disabled = false; + } + public List check(SeriesLookup lookup) { if (isStopped()) { return emptyList(); diff --git a/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/model/Metric.java b/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/model/Metric.java index 64064250a92..bb96910158c 100644 --- a/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/model/Metric.java +++ b/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/model/Metric.java @@ -75,4 +75,8 @@ public boolean equalTo(Metric other) { public String toString() { return series + " unit:" + unit.toString(); } + + public static Metric parse(String series, String unit) { + return new Metric(new Series(series), Unit.fromShortName(unit)); + } } diff --git a/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/store/InMemoryAlarmService.java b/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/store/InMemoryAlarmService.java index 29d3bc73c68..e2cb6436985 100644 --- a/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/store/InMemoryAlarmService.java +++ b/appserver/monitoring-console/core/src/main/java/fish/payara/monitoring/store/InMemoryAlarmService.java @@ -198,6 +198,11 @@ public Collection alertsFor(Series series) { return matches == null ? emptyList() : unmodifiableCollection(matches); } + @Override + public Watch watchByName(String name) { + return watchesByName.get(name); + } + @Override public void addWatch(Watch watch) { Watch existing = watchesByName.put(watch.name, watch); @@ -209,7 +214,8 @@ public void addWatch(Watch watch) { target.computeIfAbsent(series, key -> new ConcurrentHashMap<>()).put(watch.name, watch); } - private void removeWatch(Watch watch) { + @Override + public void removeWatch(Watch watch) { watch.stop(); if (watchesByName.get(watch.name) == watch) { watchesByName.remove(watch.name); @@ -378,6 +384,9 @@ private void checkWatches(Collection> watches) { } private void checkWatch(Watch watch) { + if (watch.isDisabled()) { + return; + } for (Alert newlyRaised : watch.check(monitoringData)) { Deque seriesAlerts = alerts.computeIfAbsent(newlyRaised.getSeries(), key -> new ConcurrentLinkedDeque<>()); diff --git a/appserver/monitoring-console/webapp/JS_DOCS.md b/appserver/monitoring-console/webapp/JS_DOCS.md index 5c7e4cb13b9..7a383f810db 100644 --- a/appserver/monitoring-console/webapp/JS_DOCS.md +++ b/appserver/monitoring-console/webapp/JS_DOCS.md @@ -218,10 +218,11 @@ end = number Watch Data (as received from server): ``` -WATCH = { name, series, unit, red, amber, green } +WATCH = { name, series, unit, disabled, red, amber, green, states } name = string series = string unit = string +disabled = boolean red = CIRCUMSTANCE amber = CIRCUMSTANCE green = CIRCUMSTANCE @@ -229,9 +230,12 @@ CIRCUMSTANCE = { level, start, stop, suppress, surpressingSeries, surpre level = string, start = CONDITION stop = CONDITION -suppress = CONDITION +suppress = CONDITION surpressingSeries = string surpressingUnit = string +states = SERIES_STATES +SERIES_STATES = { *:INSTANCE_STATES } +INSTANCE_STATES = { *:string } ``` @@ -462,6 +466,31 @@ type = undefined | 'pre' * `fields` is a list of attribute keys that works as filter as well as giving the order; can be undefined or empty to use all attributes in their given order. + +### Watch Manager API +Describes the model expected by the `WatchManager` component which combines the `WatchList` and `WatchBuilder` components. +The manager shows a configuration with a list of watches which also allows to create new watches. + +``` +WATCH_LIST = { id, items, colors, actions } +WATCH_BUILDER = { id, colors, actions } +WATCH_MANAGER = { id, items, colors, actions } +items = [ WATCH ] +actions = { onCreate, onDelete, onDisable, onEnable } +onCreate = fn (WATCH, onSuccess, onFailure) => () +onDelete = fn (name, onSuccess, onFailure) => () +onDisable = fn (name, onSuccess, onFailure) => () +onEnable = fn (name, onSuccess, onFailure) => () +colors = { red, amber, green } +red = string +amber = string +green = string +``` +* `WATCH` refers to an object as described for update data structures +* `onDelete` is a function to call to delete the watch by its `name` (a `string`) +* `onCreate` is a function to create new watches + + ## Data Driven Chart Plugins Code can be found in `md-line-chart.js`. diff --git a/appserver/monitoring-console/webapp/src/main/java/fish/payara/monitoring/web/ApiResponses.java b/appserver/monitoring-console/webapp/src/main/java/fish/payara/monitoring/web/ApiResponses.java index 3370e78fe8f..e5afffb89ac 100644 --- a/appserver/monitoring-console/webapp/src/main/java/fish/payara/monitoring/web/ApiResponses.java +++ b/appserver/monitoring-console/webapp/src/main/java/fish/payara/monitoring/web/ApiResponses.java @@ -160,6 +160,7 @@ public static final class WatchData { public final String name; public final String series; public final String unit; + public final boolean disabled; public final CircumstanceData red; public final CircumstanceData amber; public final CircumstanceData green; @@ -169,6 +170,7 @@ public WatchData(Watch watch) { this.name = watch.name; this.series = watch.watched.series.toString(); this.unit = watch.watched.unit.toString(); + this.disabled = watch.isDisabled(); this.red = watch.red.isUnspecified() ? null : new CircumstanceData(watch.red); this.amber = watch.amber.isUnspecified() ? null : new CircumstanceData(watch.amber); this.green = watch.green.isUnspecified() ? null : new CircumstanceData(watch.green); diff --git a/appserver/monitoring-console/webapp/src/main/java/fish/payara/monitoring/web/MonitoringConsoleResource.java b/appserver/monitoring-console/webapp/src/main/java/fish/payara/monitoring/web/MonitoringConsoleResource.java index c3d5bf4a75e..20bbf5452db 100644 --- a/appserver/monitoring-console/webapp/src/main/java/fish/payara/monitoring/web/MonitoringConsoleResource.java +++ b/appserver/monitoring-console/webapp/src/main/java/fish/payara/monitoring/web/MonitoringConsoleResource.java @@ -52,18 +52,28 @@ import javax.enterprise.context.RequestScoped; import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; +import javax.ws.rs.PATCH; import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; import org.glassfish.internal.api.Globals; import fish.payara.monitoring.alert.Alert; import fish.payara.monitoring.alert.AlertService; +import fish.payara.monitoring.alert.Circumstance; +import fish.payara.monitoring.alert.Condition; +import fish.payara.monitoring.alert.Condition.Operator; import fish.payara.monitoring.alert.Watch; +import fish.payara.monitoring.model.Metric; import fish.payara.monitoring.model.Series; import fish.payara.monitoring.model.SeriesAnnotation; import fish.payara.monitoring.model.SeriesDataset; @@ -73,8 +83,11 @@ import fish.payara.monitoring.web.ApiRequests.SeriesRequest; import fish.payara.monitoring.web.ApiResponses.AlertsResponse; import fish.payara.monitoring.web.ApiResponses.AnnotationData; +import fish.payara.monitoring.web.ApiResponses.CircumstanceData; +import fish.payara.monitoring.web.ApiResponses.ConditionData; import fish.payara.monitoring.web.ApiResponses.RequestTraceResponse; import fish.payara.monitoring.web.ApiResponses.SeriesResponse; +import fish.payara.monitoring.web.ApiResponses.WatchData; import fish.payara.monitoring.web.ApiResponses.WatchesResponse; import fish.payara.notification.requesttracing.RequestTrace; import fish.payara.nucleus.requesttracing.RequestTracingService; @@ -118,7 +131,7 @@ public List getAnnotationsData(@PathParam("series") String serie Series key = seriesOrNull(series); return key == null ? emptyList() - : getDataStore().selectAnnotations(key).stream().map(AnnotationData::new).collect(toList()); + : getDataStore().selectAnnotations(key).stream().map(AnnotationData::new).collect(toList()); } @GET @@ -143,20 +156,20 @@ public SeriesResponse getSeriesData(SeriesRequest request) { List queryData = key == null || query.excludes(DataType.POINTS) // || Series.ANY.equalTo(key) && query.truncates(POINTS) // if all alerts are requested don't send any particular data ? emptyList() - : dataStore.selectSeries(key, query.instances); - List queryAnnotations = key == null || query.excludes(DataType.ANNOTATIONS) - ? emptyList() - : dataStore.selectAnnotations(key, query.instances); - Collection queryWatches = key == null || query.excludes(DataType.WATCHES) - ? emptyList() - : alertService.wachtesFor(key); - Collection queryAlerts = key == null || query.excludes(DataType.ALERTS) - ? emptyList() - : alertService.alertsFor(key); - data.add(queryData); - watches.add(queryWatches); - annotations.add(queryAnnotations); - alerts.add(queryAlerts); + : dataStore.selectSeries(key, query.instances); + List queryAnnotations = key == null || query.excludes(DataType.ANNOTATIONS) + ? emptyList() + : dataStore.selectAnnotations(key, query.instances); + Collection queryWatches = key == null || query.excludes(DataType.WATCHES) + ? emptyList() + : alertService.wachtesFor(key); + Collection queryAlerts = key == null || query.excludes(DataType.ALERTS) + ? emptyList() + : alertService.alertsFor(key); + data.add(queryData); + watches.add(queryWatches); + annotations.add(queryAnnotations); + alerts.add(queryAlerts); } return new SeriesResponse(request.queries, data, annotations, watches, alerts, alertService.getAlertStatistics()); } @@ -213,4 +226,74 @@ public void acknowledgeAlert(@PathParam("serial") int serial) { public WatchesResponse getWatchesData() { return new WatchesResponse(getAlertService().watches()); } + + @DELETE + @Path("/watches/data/{name}/") + public Response deleteWatch(@PathParam("name") String name) { + AlertService alertService = getAlertService(); + Watch watch = alertService.watchByName(name); + if (watch != null) { + alertService.removeWatch(watch); + } + return Response.noContent().build(); + } + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Path("/watches/data/") + public Response createWatch(WatchData data) { + Circumstance red = createCircumstance(data.red); + Circumstance amber = createCircumstance(data.amber); + Circumstance green = createCircumstance(data.green); + Metric metric = Metric.parse(data.series, data.unit); + Watch watch = new Watch(data.name, metric, red, amber, green); + getAlertService().addWatch(watch); + return Response.noContent().build(); + } + + @PATCH + @Path("/watches/data/{name}/") + public Response patchWatch(@PathParam("name") String name, @QueryParam("disable") boolean disable) { + AlertService alertService = getAlertService(); + Watch watch = alertService.watchByName(name); + if (watch == null) { + return Response.status(Status.NOT_FOUND).build(); + } + if (disable) { + watch.disable(); + } else { + watch.enable(); + } + return Response.noContent().build(); + } + + private static Circumstance createCircumstance(CircumstanceData data) { + if (data == null) { + return Circumstance.UNSPECIFIED; + } + Circumstance res = new Circumstance(fish.payara.monitoring.alert.Alert.Level.parse(data.level), + createCondition(data.start), createCondition(data.stop)); + if (data.suppress != null) { + res = res.suppressedWhen(Metric.parse(data.surpressingSeries, data.surpressingUnit), + createCondition(data.suppress)); + } + return res; + } + + private static Condition createCondition(ConditionData data) { + if (data == null) { + return Condition.NONE; + } + Condition res = new Condition(Operator.parse(data.operator), data.threshold); + if (data.forMillis != null) { + res = res.forLastMillis(data.forMillis.longValue()); + } + if (data.forTimes != null) { + res = res.forLastTimes(data.forTimes.intValue()); + } + if (data.onAverage) { + res = res.onAverage(); + } + return res; + } } diff --git a/appserver/monitoring-console/webapp/src/main/webapp/css/monitoring-console.css b/appserver/monitoring-console/webapp/src/main/webapp/css/monitoring-console.css index b24d495d153..ba1d5a5adf8 100644 --- a/appserver/monitoring-console/webapp/src/main/webapp/css/monitoring-console.css +++ b/appserver/monitoring-console/webapp/src/main/webapp/css/monitoring-console.css @@ -317,7 +317,9 @@ button:hover { #console.state-show-settings #Settings { display: block; } -#console.state-show-settings #panel-grid, #console.state-show-settings #panel-trace { +#console.state-show-settings #panel-grid, +#console.state-show-settings #panel-trace, +#console.state-show-settings #WatchManager { width: calc(100% - 390px); } @@ -641,4 +643,29 @@ table.AnnotationTable { } .AnnotationTable tr:hover { background-color: #0096D6; +} + + +/* Watch Manager/Settings Component */ +.WatchManager { + position: absolute; + top: 32px; + left: 0; + z-index: 110; + padding: 10px; + box-sizing: border-box; + width: 100%; +} + +/* Watch List Component */ +.WatchItem { + background-color: #002433; + padding: 10px; + margin: 5px 0; +} +.WatchItem.state-disabled h3 { + color: #888; +} +.WatchList .WatchItem .WatchCondition { + padding: 0.25em 0.5em; } \ No newline at end of file diff --git a/appserver/monitoring-console/webapp/src/main/webapp/index.html b/appserver/monitoring-console/webapp/src/main/webapp/index.html index 5ed044c3550..b1c45b86575 100644 --- a/appserver/monitoring-console/webapp/src/main/webapp/index.html +++ b/appserver/monitoring-console/webapp/src/main/webapp/index.html @@ -47,8 +47,8 @@ - - + + @@ -66,6 +66,8 @@
+
+
diff --git a/appserver/monitoring-console/webapp/src/main/webapp/js/mc-view-components.js b/appserver/monitoring-console/webapp/src/main/webapp/js/mc-view-components.js index 6b4aabcf1a4..afa1daef8f2 100644 --- a/appserver/monitoring-console/webapp/src/main/webapp/js/mc-view-components.js +++ b/appserver/monitoring-console/webapp/src/main/webapp/js/mc-view-components.js @@ -544,47 +544,13 @@ MonitoringConsole.View.Components = (function() { let endFrame = item.frames[0]; let circumstance = item.watch[endFrame.level]; let group = $('
', { 'class': 'Group' }); - appendProperty(group, 'Start', formatCondition(item, circumstance.start)); + appendProperty(group, 'Start', formatCondition(circumstance.start, item.unit)); if (circumstance.stop) { - appendProperty(group, 'Stop', formatCondition(item, circumstance.stop)); + appendProperty(group, 'Stop', formatCondition(circumstance.stop, item.unit)); } return group; } - function formatCondition(item, condition) { - if (condition === undefined) - return ''; - let any = condition.forTimes === 0 || condition.forMillis === 0; - let anyN = condition.forTimes < 0 || condition.forMillis < 0; - let threshold = Units.converter(item.unit).format(condition.threshold); - let text = ''; - let forText = ''; - let forValue; - if (any || anyN) { - text += 'any 1 '; - } - text += 'value ' + condition.operator + threshold; - if (condition.forTimes > 0 || condition.forMillis > 0) { - if (condition.onAverage) { - forText += ' for average of last '; - } else if (anyN) { - forText += ' in last '; - } else { - forText += ' for last '; - } - } - if (condition.forTimes !== 0) { - forValue = Math.abs(condition.forTimes) + 'x'; - } - if (condition.forMillis !== undefined) { - forValue = Units.converter('ms').format(Math.abs(condition.forMillis)); - } - let desc = $('').append(text); - if (forText != '') - desc.append($('', { text: forText})).append(forValue); - return desc; - } - function createStatisticsGroup(item, verbose) { let endFrame = item.frames[0]; let startFrame = item.frames[item.frames.length - 1]; @@ -811,6 +777,90 @@ MonitoringConsole.View.Components = (function() { }; })(); + /** + * Lists existing watches to explain their logic to the user. + */ + const WatchList = (function() { + + function createComponent(model) { + const config = { 'class': 'WatchList' }; + if (model.id) + config.id = model.id; + const list = $('
', config); + for (let item of model.items) { + list.append(createItem(item, model.colors, model.actions)); + } + //TODO show states + return list; + } + + function createItem(item, colors, actions) { + const watch = $('
', { 'class': 'WatchItem ' + 'state-' + (item.disabled ? 'disabled' : 'enabled') }); + const general = $('

').text(item.name); + //TODO make disabled into menu for the "widget" + watch.append(general); + for (let level of ['red', 'amber', 'green']) + if (item[level]) + watch.append(createCircumstance(level, item[level], item.unit, item.series, colors[level])); + return watch; + } + + function createCircumstance(level, model, unit, series, color) { + function plainText(condition) { + let text = condition.text(); + return text.substring(text.indexOf('value') + 5); + } + const circumstance = $('
', { 'class': 'WatchCondition', style: 'color: '+ color +';'}); + let = text = '' + Units.Alerts.name(level) + ': ' + Units.names()[unit] + ' ' + series + ' '; + if (model.start) + text += plainText(formatCondition(model.start, unit)); + if (model.suppress) + text += ' unless ' + model.surpressingSeries + ' ' + plainText(formatCondition(model.suppress, modelsurpressingUnit)); + if (model.stop) + text += ' until ' + plainText(formatCondition(model.stop, unit)); + return circumstance.html(text); + } + + return { createComponent: createComponent }; + })(); + + /** + * A component that creates the form to compose a single new watch + */ + const WatchBuilder = (function() { + + function createComponent(model) { + const config = { 'class': 'WatchBuilder' }; + if (model.id) + config.id = model.id; + const builder = $('
', config); + + return builder; + } + + return { createComponent: createComponent }; + + })(); + + /** + * Combines the WatchList and WatchBuilder into one component to list and create watches. + */ + const WatchManager = (function() { + + function createComponent(model) { + const config = { 'class': 'WatchManager' }; + if (model.id) + config.id = model.id; + const manager = $('
', config); + model.id = undefined; // id should not be set by sub-components + manager.append(WatchList.createComponent(model)); + manager.append(WatchBuilder.createComponent(model)); + return manager; + } + + return { createComponent: createComponent }; + })(); + /* * Shared functions @@ -820,9 +870,43 @@ MonitoringConsole.View.Components = (function() { parent.append($('') .append($('', { text: label + ':' })) .append($('<' + tag + '/>').append(value)) - ).append('\n'); // so browser will line break; + ).append('\n'); // so browser will line break; } + function formatCondition(condition, unit) { + if (condition === undefined) + return ''; + let any = condition.forTimes === 0 || condition.forMillis === 0; + let anyN = condition.forTimes < 0 || condition.forMillis < 0; + let threshold = Units.converter(unit).format(condition.threshold); + let text = ''; + let forText = ''; + let forValue; + if (any || anyN) { + text += 'any 1 '; + } + text += 'value ' + condition.operator + ' ' + threshold; + if (condition.forTimes > 0 || condition.forMillis > 0) { + if (condition.onAverage) { + forText += ' for average of last '; + } else if (anyN) { + forText += ' in last '; + } else { + forText += ' for last '; + } + } + if (condition.forTimes !== 0) { + forValue = Math.abs(condition.forTimes) + 'x'; + } + if (condition.forMillis !== undefined) { + forValue = Units.converter('ms').format(Math.abs(condition.forMillis)); + } + let desc = $('').append(text); + if (forText != '') + desc.append($('', { text: forText})).append(forValue); + return desc; + } + /* * Public API below: * @@ -835,6 +919,7 @@ MonitoringConsole.View.Components = (function() { createMenu: (model) => Menu.createComponent(model), createAlertTable: (model) => AlertTable.createComponent(model), createAnnotationTable: (model) => AnnotationTable.createComponent(model), + createWatchManager: (model) => WatchManager.createComponent(model), }; })(); \ No newline at end of file diff --git a/appserver/monitoring-console/webapp/src/main/webapp/js/mc-view-units.js b/appserver/monitoring-console/webapp/src/main/webapp/js/mc-view-units.js index 26ba077d6df..fa6ef9990ba 100644 --- a/appserver/monitoring-console/webapp/src/main/webapp/js/mc-view-units.js +++ b/appserver/monitoring-console/webapp/src/main/webapp/js/mc-view-units.js @@ -115,6 +115,21 @@ MonitoringConsole.View.Units = (function() { percent: PERCENT_FACTORS, }; + const UNIT_NAMES = { + count: 'Count', + ms: 'Milliseconds', + ns: 'Nanoseconds', + bytes: 'Bytes', + percent: 'Percentage' + }; + + const ALERT_STATUS_NAMES = { + white: 'Normal', + green: 'Healthy', + amber: 'Degraded', + red: 'Unhealthy' + }; + function parseNumber(valueAsString, factors) { if (!valueAsString || typeof valueAsString === 'string' && valueAsString.trim() === '') return undefined; @@ -251,8 +266,11 @@ MonitoringConsole.View.Units = (function() { return { Alerts: { maxLevel: maxAlertLevel, + name: (level) => ALERT_STATUS_NAMES[level == undefined ? 'white' : level], }, + names: () => UNIT_NAMES, + formatTime: formatTime, formatNumber: formatNumber, formatMilliseconds: (valueAsNumber) => formatNumber(valueAsNumber, MS_FACTORS), diff --git a/appserver/monitoring-console/webapp/src/main/webapp/js/mc-view.js b/appserver/monitoring-console/webapp/src/main/webapp/js/mc-view.js index b07ea64c8e5..ac305fbf325 100644 --- a/appserver/monitoring-console/webapp/src/main/webapp/js/mc-view.js +++ b/appserver/monitoring-console/webapp/src/main/webapp/js/mc-view.js @@ -67,14 +67,15 @@ MonitoringConsole.View = (function() { /** * Updates the DOM with the page navigation tabs so it reflects current model state */ - function updatePageNavigation() { + function updatePageNavigation(selectedPage) { let pages = MonitoringConsole.Model.listPages(); - let activePage = pages.find(page => page.active); + let activePage = selectedPage || pages.find(page => page.active).name; let items = pages.filter(page => !page.active).map(function(page) { return { label: page.name ? page.name : '(Unnamed)', onClick: () => onPageChange(MonitoringConsole.Model.Page.changeTo(page.id)) }; }); + items.push({ label: 'Watches', onClick: onOpenWatchSettings }); let nav = { id: 'Navigation', groups: [ - {label: activePage.name, items: items } + {label: activePage, items: items } ]}; $('#Navigation').replaceWith(Components.createMenu(nav)); } @@ -107,7 +108,7 @@ MonitoringConsole.View = (function() { { label: 'Hide', icon: '×', hidden: !settingsOpen, onClick: toggleSettings }, { label: 'Show', icon: '+', hidden: settingsOpen, onClick: toggleSettings }, { label: 'Import...', icon: '⎗', description: 'Import Configuration...', onClick: () => $('#cfgImport').click() }, - { label: 'Export...', icon: '⎘', description: 'Export Configuration...', onClick: MonitoringConsole.View.onPageExport }, + { label: 'Export...', icon: '⎘', description: 'Export Configuration...', onClick: MonitoringConsole.View.onPageExport }, ]}, ]}; $('#Menu').replaceWith(Components.createMenu(menu)); @@ -243,6 +244,7 @@ MonitoringConsole.View = (function() { { type: 'value', unit: 'sec', value: MonitoringConsole.Model.Settings.Rotation.interval(), onChange: (val) => MonitoringConsole.Model.Settings.Rotation.interval(val) }, { label: 'enabled', type: 'checkbox', value: MonitoringConsole.Model.Settings.Rotation.isEnabled(), onChange: (checked) => MonitoringConsole.Model.Settings.Rotation.enabled(checked) }, ]}, + { label: 'Watches', input: $('