use vue.js for the UI
This commit is contained in:
@@ -4,16 +4,15 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class DashTypes {
|
||||
public static final List<String> DEFAULT = Arrays.asList(
|
||||
"1",//
|
||||
"2",//
|
||||
"3",//
|
||||
"4",//
|
||||
"6",//
|
||||
"\".\"",//
|
||||
"\"-\"",//
|
||||
"\"._\"",//
|
||||
"\"..- \"",//
|
||||
"\"(50,6,2,6)\""//
|
||||
);
|
||||
public static final List<String> DEFAULT = Arrays.asList("1", //
|
||||
"2", //
|
||||
"3", //
|
||||
"4", //
|
||||
"5", //
|
||||
"6", //
|
||||
"\".\"", //
|
||||
"\"-\"", //
|
||||
"\"._\"", //
|
||||
"\"..- \""//
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ public class BadRequest extends RuntimeException {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public BadRequest(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BadRequest(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.lucares.pdbui;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -12,6 +11,7 @@ import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.lucares.pdb.datastore.Proposal;
|
||||
import org.lucares.pdb.plot.api.PlotSettings;
|
||||
import org.lucares.pdbui.domain.AutocompleteProposal;
|
||||
@@ -28,6 +28,7 @@ import org.lucares.recommind.logs.Plotter;
|
||||
import org.lucares.utils.CollectionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@@ -51,9 +52,12 @@ public class PdbController implements HardcodedValues {
|
||||
|
||||
private final Plotter plotter;
|
||||
private final PerformanceDb db;
|
||||
|
||||
|
||||
private final ReentrantLock plotterLock = new ReentrantLock();
|
||||
|
||||
@Value("${mode.production:true}")
|
||||
private boolean modeProduction;
|
||||
|
||||
public PdbController(final PerformanceDb db, final Plotter plotter) {
|
||||
this.db = db;
|
||||
this.plotter = plotter;
|
||||
@@ -63,8 +67,10 @@ public class PdbController implements HardcodedValues {
|
||||
public ModelAndView index() {
|
||||
final String view = "main";
|
||||
final Map<String, Object> model = new HashMap<>();
|
||||
model.put("oldestValue", LocalDateTime.now().minusDays(7).format(DATE_FORMAT_BEGIN));
|
||||
model.put("latestValue", LocalDateTime.now().format(DATE_FORMAT_END));
|
||||
// model.put("oldestValue",
|
||||
// LocalDateTime.now().minusDays(7).format(DATE_FORMAT_BEGIN));
|
||||
// model.put("latestValue", LocalDateTime.now().format(DATE_FORMAT_END));
|
||||
model.put("isProduction", modeProduction);
|
||||
return new ModelAndView(view, model);
|
||||
}
|
||||
|
||||
@@ -77,20 +83,21 @@ public class PdbController implements HardcodedValues {
|
||||
PlotResponse createPlot(@RequestBody final PlotRequest request)
|
||||
throws InternalPlottingException, InterruptedException {
|
||||
|
||||
final PlotSettings plotSettings = PlotSettingsTransformer
|
||||
.toSettings(request);
|
||||
final PlotSettings plotSettings = PlotSettingsTransformer.toSettings(request);
|
||||
if (StringUtils.isBlank(plotSettings.getQuery())) {
|
||||
throw new BadRequest("The query must not be empty!");
|
||||
}
|
||||
|
||||
// TODO the UI should cancel requests that are in flight before sending a plot request
|
||||
// TODO the UI should cancel requests that are in flight before sending a plot
|
||||
// request
|
||||
if (plotterLock.tryLock()) {
|
||||
try {
|
||||
final PlotResult result = plotter.plot(plotSettings);
|
||||
|
||||
final String imageUrl = WEB_IMAGE_OUTPUT_PATH + "/"
|
||||
+ result.getImageName();
|
||||
final String imageUrl = WEB_IMAGE_OUTPUT_PATH + "/" + result.getImageName();
|
||||
LOGGER.trace("image url: {}", imageUrl);
|
||||
|
||||
return new PlotResponse(
|
||||
DataSeries.toMap(result.getDataSeries()), imageUrl);
|
||||
|
||||
return new PlotResponse(DataSeries.toMap(result.getDataSeries()), imageUrl);
|
||||
} catch (final NoDataPointsException e) {
|
||||
throw new NotFoundException(e);
|
||||
} finally {
|
||||
@@ -115,7 +122,7 @@ public class PdbController implements HardcodedValues {
|
||||
final int zeroBasedCaretIndex = caretIndex - 1;
|
||||
|
||||
final List<Proposal> proposals = db.autocomplete(query, zeroBasedCaretIndex);
|
||||
final List<Proposal> nonEmptyProposals = CollectionUtils.filter(proposals, p -> p.hasResults() );
|
||||
final List<Proposal> nonEmptyProposals = CollectionUtils.filter(proposals, p -> p.hasResults());
|
||||
|
||||
final List<AutocompleteProposal> autocompleteProposals = toAutocompleteProposals(nonEmptyProposals);
|
||||
Collections.sort(autocompleteProposals, new AutocompleteProposalByValue());
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
package org.lucares.pdbui.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PlotResponse {
|
||||
private List<String> imageUrls = new ArrayList<>();
|
||||
private String imageUrl = "";
|
||||
private Map<String, Integer> dataSeries;
|
||||
|
||||
public PlotResponse(final Map<String, Integer> dataSeries, final String... imageUrls) {
|
||||
public PlotResponse(final Map<String, Integer> dataSeries, final String imageUrl) {
|
||||
this.dataSeries = dataSeries;
|
||||
this.imageUrls.addAll(Arrays.asList(imageUrls));
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
|
||||
public List<String> getImageUrls() {
|
||||
return imageUrls;
|
||||
public String getImageUrl() {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
public void setImageUrls(final List<String> imageUrls) {
|
||||
this.imageUrls = imageUrls;
|
||||
public void setImageUrl(final String imageUrl) {
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getDataSeries() {
|
||||
@@ -32,7 +29,6 @@ public class PlotResponse {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(imageUrls) + " " + dataSeries;
|
||||
return imageUrl + " " + dataSeries;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,16 +5,9 @@ html {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
body{
|
||||
display: grid;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
grid:
|
||||
"search_field" auto
|
||||
"search" auto
|
||||
"navigation " auto
|
||||
"result" 1fr
|
||||
/ 1fr;
|
||||
body {
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@@ -29,6 +22,17 @@ body{
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: grid;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
grid:
|
||||
"search" auto
|
||||
"navigation " auto
|
||||
"result" 1fr
|
||||
/ 1fr;
|
||||
}
|
||||
|
||||
#logo {
|
||||
grid-area: logo;
|
||||
font-size: 1.2em;
|
||||
@@ -46,6 +50,11 @@ body{
|
||||
grid-area: search;
|
||||
background-color: #aaa;
|
||||
padding-bottom:3px;
|
||||
display: grid;
|
||||
grid:
|
||||
"search_field" auto
|
||||
"filter_bar" auto
|
||||
/ 1fr
|
||||
}
|
||||
|
||||
#navigation {
|
||||
@@ -55,26 +64,26 @@ body{
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.autocomplete .active {
|
||||
background-color: #AAA;
|
||||
}
|
||||
.autocomplete.open{
|
||||
z-index:2;
|
||||
#result {
|
||||
grid-area: result;
|
||||
background: #eee;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
/* scrollbars are nice, but with them an empty autocomplete box is shown
|
||||
|
||||
.autocomplete, #search-input-wrapper .autocomplete.open {
|
||||
overflow-y: scroll;
|
||||
#filter-bar {
|
||||
grid-area: filter_bar;
|
||||
}
|
||||
*/
|
||||
|
||||
#search-input {
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
|
||||
#search-limit-value {
|
||||
width: 4em;
|
||||
}
|
||||
@@ -96,14 +105,7 @@ body{
|
||||
margin-right:3px;
|
||||
}
|
||||
|
||||
#result {
|
||||
grid-area: result;
|
||||
background: #eee;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
|
||||
#result-image {
|
||||
height: 100%;
|
||||
@@ -134,6 +136,10 @@ body{
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#result-error-message {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.center
|
||||
{
|
||||
|
||||
564
pdb-ui/src/main/resources/resources/js/ui.js
Normal file
564
pdb-ui/src/main/resources/resources/js/ui.js
Normal file
@@ -0,0 +1,564 @@
|
||||
'use strict';
|
||||
|
||||
window.onload=function(){
|
||||
|
||||
|
||||
Vue.component('result-view', {
|
||||
props: ['searchBar', 'resultView'],
|
||||
methods: {
|
||||
prev_image: function()
|
||||
{
|
||||
var splitBy = data.searchBar.splitBy;
|
||||
if (splitBy['values'].length > 0)
|
||||
{
|
||||
splitBy['index'] = (splitBy['index']+ splitBy['values'].length-1) % splitBy['values'].length;
|
||||
plotCurrent();
|
||||
}
|
||||
},
|
||||
next_image: function()
|
||||
{
|
||||
var splitBy = data.searchBar.splitBy;
|
||||
if (splitBy['values'].length > 0)
|
||||
{
|
||||
splitBy['index'] = (splitBy['index']+1) % splitBy['values'].length;
|
||||
plotCurrent();
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div id="result">
|
||||
<div id="prev_image" v-show="searchBar.splitBy.field" v-on:click="prev_image" title="Previous Plot"><i class="fa fa-angle-double-left" aria-hidden="true"></i></div>
|
||||
<div id="next_image" v-show="searchBar.splitBy.field" v-on:click="next_image" title="Next Plot"><i class="fa fa-angle-double-right" aria-hidden="true"></i></div>
|
||||
<div id="result-image"><img v-bind:src="resultView.imageUrl" v-if="resultView.imageUrl"/></div>
|
||||
<div id="result-error-message" v-if="resultView.errorMessage">{{ resultView.errorMessage }}</div>
|
||||
</div>`
|
||||
});
|
||||
|
||||
Vue.component('navigation-bar', {
|
||||
props: [],
|
||||
methods: {
|
||||
|
||||
refresh: function()
|
||||
{
|
||||
plotCurrent();
|
||||
},
|
||||
zoomIn: function()
|
||||
{
|
||||
this.shiftDate(0.25);
|
||||
this.zoom(0.5);
|
||||
plotCurrent();
|
||||
},
|
||||
zoomOut: function()
|
||||
{
|
||||
this.shiftDate(-0.5);
|
||||
this.zoom(2);
|
||||
plotCurrent();
|
||||
},
|
||||
dateLeftShift: function()
|
||||
{
|
||||
this.shiftDate(-1);
|
||||
plotCurrent();
|
||||
},
|
||||
dateHalfLeftShift: function()
|
||||
{
|
||||
this.shiftDate(-0.5);
|
||||
plotCurrent();
|
||||
},
|
||||
dateHalfRightShift: function()
|
||||
{
|
||||
this.shiftDate(0.5);
|
||||
plotCurrent();
|
||||
},
|
||||
dateRightShift: function()
|
||||
{
|
||||
this.shiftDate(1);
|
||||
plotCurrent();
|
||||
},
|
||||
zoom: function(factor)
|
||||
{
|
||||
if (!$('#search-date-range').is(":invalid")) {
|
||||
|
||||
var dateRange = data.searchBar.dateRange;
|
||||
var tokens = dateRange.split(/ +/,2);
|
||||
|
||||
if(tokens.length == 2)
|
||||
{
|
||||
var value = parseInt(tokens[0]);
|
||||
var period = tokens[1];
|
||||
|
||||
var newValue = value*factor;
|
||||
while (newValue != Math.round(newValue)){
|
||||
|
||||
switch (period) {
|
||||
case "second":
|
||||
case "seconds":
|
||||
if (value == 1) {
|
||||
// we reached the smallest range
|
||||
}
|
||||
else if (value % 2 == 1){
|
||||
value = value -1;
|
||||
}
|
||||
break;
|
||||
case "minute":
|
||||
case "minutes":
|
||||
value = value * 60;
|
||||
period = "seconds";
|
||||
break;
|
||||
case "hour":
|
||||
case "hours":
|
||||
value = value * 60;
|
||||
period = "minutes";
|
||||
break;
|
||||
case "day":
|
||||
case "days":
|
||||
value = value * 24;
|
||||
period = "hours";
|
||||
break;
|
||||
case "week":
|
||||
case "weeks":
|
||||
value = value * 7;
|
||||
period = "days";
|
||||
break;
|
||||
case "month":
|
||||
case "months":
|
||||
value = value * 30;
|
||||
period = "days";
|
||||
break;
|
||||
default:
|
||||
console.log("unhandled value: "+ period);
|
||||
break;
|
||||
}
|
||||
|
||||
newValue = value*factor
|
||||
}
|
||||
|
||||
|
||||
data.searchBar.dateRange = newValue + " " + period;
|
||||
}
|
||||
}
|
||||
},
|
||||
shiftDate: function(directionalFactor)
|
||||
{
|
||||
var dateBefore = Date.parse(data.searchBar.dateFrom);
|
||||
var newDate = this.shiftByInterval(dateBefore, directionalFactor);
|
||||
data.searchBar.dateFrom = newDate.toString("yyyy-MM-dd HH:mm:ss");
|
||||
},
|
||||
shiftByInterval: function(date, directionalFactor)
|
||||
{
|
||||
if (!$('#search-date-range').is(":invalid")) {
|
||||
|
||||
var dateRange = data.searchBar.dateRange;
|
||||
var tokens = dateRange.split(/ +/,2);
|
||||
|
||||
if(tokens.length == 2)
|
||||
{
|
||||
var value = parseInt(tokens[0]);
|
||||
var period = tokens[1];
|
||||
var config = {};
|
||||
|
||||
value = directionalFactor * value;
|
||||
|
||||
switch (period) {
|
||||
case "second":
|
||||
case "seconds":
|
||||
config = { seconds: value };
|
||||
break;
|
||||
case "minute":
|
||||
case "minutes":
|
||||
config = { minutes: value };
|
||||
break;
|
||||
case "hour":
|
||||
case "hours":
|
||||
config = { minutes: 60*value };
|
||||
break;
|
||||
case "day":
|
||||
case "days":
|
||||
config = { days: value };
|
||||
break;
|
||||
case "week":
|
||||
case "weeks":
|
||||
config = { days: 7*value };
|
||||
break;
|
||||
case "month":
|
||||
case "months":
|
||||
config = { days: 30*value };
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
var newDate = date.add(config);
|
||||
return newDate;
|
||||
}
|
||||
}
|
||||
return date;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div id="navigation">
|
||||
<button id="nav_left" v-on:click="dateLeftShift" title="Show Older Values"><i class="fa fa-angle-double-left" aria-hidden="true"></i></button>
|
||||
<button id="nav_left_half" v-on:click="dateHalfLeftShift" title="Show Older Values"><i class="fa fa-angle-left" aria-hidden="true"></i></button>
|
||||
<div>
|
||||
<button id="zoom_in" v-on:click="zoomIn" title="Zoom In"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||
<button id="refresh" v-on:click="refresh" title="Refresh"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||
<button id="zoom_out" v-on:click="zoomOut" title="Zoom Out"><i class="fa fa-minus-circle" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<button id="nav_right_half" v-on:click="dateHalfRightShift" title="Show Newer Values"><i class="fa fa-angle-right" aria-hidden="true"></i></button>
|
||||
<button id="nav_right" v-on:click="dateRightShift" title="Show Newer Values"><i class="fa fa-angle-double-right" aria-hidden="true"></i></button>
|
||||
</div>`
|
||||
});
|
||||
|
||||
Vue.component('group-by-item', {
|
||||
props: ['groupBy'],
|
||||
template: `
|
||||
<select v-model="groupBy.selected" :id="groupBy.id">
|
||||
<option
|
||||
v-for="option in groupBy.options"
|
||||
v-bind:value="option.value"
|
||||
>{{ option.text }}</option>
|
||||
</select>`
|
||||
});
|
||||
|
||||
Vue.component('search-bar', {
|
||||
props: ['searchBar'],
|
||||
methods: {
|
||||
plot: function (event) {
|
||||
var vm = this;
|
||||
if (this.searchBar.splitByKeys.selected){
|
||||
this.splitQueries(function (fieldValues) {
|
||||
data.searchBar.splitBy['values'] = fieldValues;
|
||||
vm.enableSplitBy(fieldValues);
|
||||
plotCurrent();
|
||||
});
|
||||
|
||||
}else{
|
||||
this.disableSplitBy();
|
||||
plotCurrent();
|
||||
}
|
||||
},
|
||||
dashboard: function (event) {
|
||||
alert('dashboard');
|
||||
},
|
||||
enableSplitBy: function(fieldValues) {
|
||||
data.searchBar.splitBy['field'] = data.searchBar.splitByKeys.selected;
|
||||
data.searchBar.splitBy['values'] = fieldValues;
|
||||
data.searchBar.splitBy['index'] = 0;
|
||||
data.searchBar.splitBy['query'] = data.searchBar.query;
|
||||
},
|
||||
disableSplitBy: function() {
|
||||
data.searchBar.splitBy['field'] = '';
|
||||
data.searchBar.splitBy['values'] = [];
|
||||
data.searchBar.splitBy['index'] = 0;
|
||||
data.searchBar.splitBy['query'] = '';
|
||||
},
|
||||
splitQueries: function(successCallback)
|
||||
{
|
||||
var request = {};
|
||||
request['query'] = data.searchBar.query;
|
||||
|
||||
var error = function(e) {
|
||||
data.resultView.errorMessage = "FAILED: " + JSON.parse(e.responseText).message;
|
||||
};
|
||||
|
||||
var url = "/fields/"+encodeURIComponent(data.searchBar.splitByKeys.selected)+"/values";
|
||||
getJson(url, request, successCallback, error);
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<form id="search-bar" v-on:submit.prevent.stop>
|
||||
<div id="search-input-wrapper">
|
||||
<input
|
||||
id="search-input"
|
||||
v-model="searchBar.query"
|
||||
placeholder="field=value and anotherField=anotherValue"/>
|
||||
</div>
|
||||
<div id="filter-bar">
|
||||
<label for="search-group-by-0">Group:</label>
|
||||
<group-by-item
|
||||
v-for="item in searchBar.groupByKeys"
|
||||
v-bind:key="item.id"
|
||||
v-bind:groupBy="item"
|
||||
></group-by-item>
|
||||
<!--v-bind="{ id: 'search-group-by-'+item.id, 'selected': item.selected, 'options':item.options }"-->
|
||||
<label for="split-by">Split:</label>
|
||||
<select id="split-by" v-model="searchBar.splitByKeys.selected">
|
||||
<option
|
||||
v-for="option in searchBar.splitByKeys.options"
|
||||
v-bind:key="option.value"
|
||||
v-bind:value="option.value"
|
||||
>{{ option.text }}</option>
|
||||
</select>
|
||||
|
||||
<div class="group">
|
||||
<label for="search-limit-by">Limit:</label>
|
||||
<select id="search-limit-by" v-model="searchBar.limitBy.selected">
|
||||
<option value="NO_LIMIT" selected="selected">no limit</option>
|
||||
<option value="MOST_VALUES">most values</option>
|
||||
<option value="FEWEST_VALUES">fewest values</option>
|
||||
<option value="MAX_VALUE">max value</option>
|
||||
<option value="MIN_VALUE">min value</option>
|
||||
</select>
|
||||
<input
|
||||
type="number"
|
||||
id="search-limit-value"
|
||||
name="search-limit-value"
|
||||
min="1"
|
||||
max="1000"
|
||||
v-show="searchBar.limitBy.selected != 'NO_LIMIT'"
|
||||
v-model="searchBar.limitBy.number"/>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<label for="search-date-from">From Date:</label>
|
||||
<input
|
||||
id="search-date-from"
|
||||
v-model="searchBar.dateFrom"
|
||||
class="input_date"
|
||||
type="text"
|
||||
required="required"
|
||||
pattern="\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2]\\d|3[0-1]) [0-2]\\d:[0-5]\\d:[0-5]\\d" />
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<label for="search-date-range">Interval:</label>
|
||||
<input
|
||||
id="search-date-range"
|
||||
v-model="searchBar.dateRange"
|
||||
type="text"
|
||||
list="ranges"
|
||||
required="required"
|
||||
pattern="\\d+ (second|minute|hour|day|week|month)s?">
|
||||
<datalist id="ranges">
|
||||
<option value="60 seconds"/>
|
||||
<option value="5 minutes"/>
|
||||
<option value="1 hour"/>
|
||||
<option value="1 day"/>
|
||||
<option value="1 week"/>
|
||||
<option value="1 month"/>
|
||||
</datalist>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<label for="search-y-axis-scale">Y-Axis:</label>
|
||||
<select id="search-y-axis-scale" v-model="searchBar.axisScale">
|
||||
<option value="LINEAR" selected="selected">linear</option>
|
||||
<option value="LOG10">log 10</option>
|
||||
<option value="LOG2">log 2</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<label for="plot-type">Type:</label>
|
||||
<select id="plot-type" v-model="searchBar.plotType">
|
||||
<option value="SCATTER">Scatter</option>
|
||||
<option value="PERCENTILES">Percentiles</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="group" id="group-show-aggregate" v-show="searchBar.plotType == 'SCATTER'">
|
||||
<label for="show-aggregate">Aggregate:</label>
|
||||
<select id="show-aggregate" v-model="searchBar.showAggregate">
|
||||
<option value="NONE">-</option>
|
||||
<option value="PERCENTILES">percentiles</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<input type="checkbox" id="key-outside" v-model="searchBar.keyOutside"/>
|
||||
<label for="key-outside">Legend outside</label>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<button
|
||||
id="plot-submit"
|
||||
title="Create Plot"
|
||||
v-on:click.prevent.stop="plot"
|
||||
><i class="fa fa-area-chart" aria-hidden="true"></i> Plot</button>
|
||||
<!--
|
||||
<button
|
||||
id="dashboard-submit"
|
||||
title="Create Dashboard"
|
||||
v-on:click.prevent.stop="dashboard"
|
||||
><i class="fa fa-object-group" aria-hidden="true"></i> Dashboard</button>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</form>`
|
||||
});
|
||||
|
||||
var rootView = new Vue({
|
||||
el: '#app',
|
||||
data: data,
|
||||
created: function() {
|
||||
var self = this;
|
||||
var request = {};
|
||||
|
||||
var success = function(response){
|
||||
|
||||
var options = [{ text: '', value: '' }];
|
||||
|
||||
response.forEach( (item, index) => { options.push({text: item, value: item}); } );
|
||||
|
||||
for (var i = 0; i < 3; i++){
|
||||
self.searchBar.groupByKeys.push({
|
||||
'id': i,
|
||||
'selected': '',
|
||||
'options': options
|
||||
});
|
||||
}
|
||||
self.searchBar.splitByKeys.options = options
|
||||
};
|
||||
var error = function(e) {
|
||||
data.resultView.errorMessage = "FAILED: " + JSON.parse(e.responseText).message;
|
||||
};
|
||||
|
||||
getJson("fields", request, success, error);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
var data = {
|
||||
|
||||
searchBar: {
|
||||
query: 'pod=vapfinra01 and method = ViewService.findFieldViewGroup',
|
||||
groupByKeys: [],
|
||||
splitByKeys: {
|
||||
'selected': 'method',
|
||||
'options': []
|
||||
},
|
||||
limitBy: {
|
||||
'selected': 'NO_LIMIT',
|
||||
'number': 10
|
||||
},
|
||||
dateFrom: '2018-01-05 09:03:00', //Date.now().add({ days: -7 }).toString("yyyy-MM-dd HH:mm:ss"),
|
||||
dateRange: '1 week',
|
||||
axisScale: 'LOG10',
|
||||
plotType: 'SCATTER',
|
||||
showAggregate: 'NONE',
|
||||
keyOutside: false,
|
||||
|
||||
splitBy: {
|
||||
field: '',
|
||||
query: '',
|
||||
values: [],
|
||||
index: 0
|
||||
},
|
||||
navigation: {
|
||||
prevNext: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
},
|
||||
resultView: {
|
||||
imageUrl: '',
|
||||
errorMessage: ''
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
function plotCurrent()
|
||||
{
|
||||
//showLoadingIcon();
|
||||
|
||||
if (data.searchBar.splitBy['field']) {
|
||||
var query = createQuery();
|
||||
sendPlotRequest(query);
|
||||
}else{
|
||||
sendPlotRequest(data.searchBar.query);
|
||||
}
|
||||
}
|
||||
|
||||
function createQuery()
|
||||
{
|
||||
var splitBy = data.searchBar.splitBy;
|
||||
var query = splitBy['query'];
|
||||
if (query.length > 0) {
|
||||
query = "("+query+") and "+splitBy['field']+ " = " +splitBy['values'][splitBy['index']];
|
||||
} else {
|
||||
query = splitBy['field']+ " = " +splitBy['values'][splitBy['index']];
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
function groupBy()
|
||||
{
|
||||
var result = [];
|
||||
data.searchBar.groupByKeys.forEach(function(item) {
|
||||
if (item.selected) {
|
||||
result.push(item.selected);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function sendPlotRequest(query){
|
||||
|
||||
var request = {};
|
||||
request['query'] = query;
|
||||
request['height'] = $('#result-image').height();
|
||||
request['width'] = $('#result-image').width();
|
||||
request['groupBy'] = groupBy();
|
||||
request['limitBy'] = data.searchBar.limitBy.selected;
|
||||
request['limit'] = parseInt(data.searchBar.limitBy.number);
|
||||
request['dateFrom'] = data.searchBar.dateFrom;
|
||||
request['dateRange'] = data.searchBar.dateRange;
|
||||
request['axisScale'] = data.searchBar.axisScale;
|
||||
request['plotType'] = data.searchBar.plotType;
|
||||
request['aggregate'] = data.searchBar.showAggregate;
|
||||
request['keyOutside'] = data.searchBar.keyOutside;
|
||||
|
||||
|
||||
var success = function(response){
|
||||
data.resultView.imageUrl = response.imageUrl;
|
||||
data.resultView.errorMessage = '';
|
||||
};
|
||||
var error = function(e) {
|
||||
data.resultView.imageUrl = '';
|
||||
if (e.status == 404){
|
||||
data.resultView.errorMessage = "No data points found for query: " + query;
|
||||
}
|
||||
else if (e.status == 503){
|
||||
data.resultView.errorMessage = "Too many parallel requests.";
|
||||
}
|
||||
else{
|
||||
data.resultView.errorMessage = "FAILED: " + JSON.parse(e.responseText).message;
|
||||
}
|
||||
};
|
||||
|
||||
postJson("plots", request, success, error);
|
||||
|
||||
}
|
||||
|
||||
|
||||
function postJson(url, requestData, successCallback, errorCallback) {
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: JSON.stringify(requestData),
|
||||
contentType: 'application/json'
|
||||
})
|
||||
.done(successCallback)
|
||||
.fail(errorCallback)
|
||||
//.always(pauseInvaders)
|
||||
;
|
||||
}
|
||||
|
||||
function getJson(url, requestData, successCallback, errorCallback) {
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: url,
|
||||
data: requestData,
|
||||
contentType: 'application/json'
|
||||
})
|
||||
.done(successCallback)
|
||||
.fail(errorCallback);
|
||||
}
|
||||
|
||||
|
||||
|
||||
10947
pdb-ui/src/main/resources/resources/js/vue-2.5.16-dev.js
Normal file
10947
pdb-ui/src/main/resources/resources/js/vue-2.5.16-dev.js
Normal file
File diff suppressed because it is too large
Load Diff
6
pdb-ui/src/main/resources/resources/js/vue-2.5.16.min.js
vendored
Normal file
6
pdb-ui/src/main/resources/resources/js/vue-2.5.16.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -2,10 +2,17 @@
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="js/jquery-3.2.0.min.js"></script>
|
||||
<script type="text/javascript" src="js/search.js"></script>
|
||||
<script type="text/javascript" src="js/autocomplete.js"></script>
|
||||
<!--<script type="text/javascript" src="js/search.js"></script>-->
|
||||
<script type="text/javascript" src="js/date.js"></script>
|
||||
<script type="text/javascript" src="js/invaders.js"></script>
|
||||
{{#isProduction}}
|
||||
<script type="text/javascript" src="js/vue-2.5.16.min.js"></script>
|
||||
{{/isProduction}}
|
||||
{{^isProduction}}
|
||||
<script type="text/javascript" src="js/vue-2.5.16-dev.js"></script>
|
||||
{{/isProduction}}
|
||||
<script type="text/javascript" src="js/ui.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="css/typography.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/design.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/loading.css">
|
||||
@@ -14,98 +21,10 @@
|
||||
<link rel="stylesheet" type="text/css" href="css/invaders.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="search-input-wrapper">
|
||||
<input id="search-input" placeholder="field=value and anotherField=anotherValue" data-autocomplete="autocomplete"
|
||||
data-autocomplete-empty-message="nothing found" value="pod=vapfinra01 and method = ViewService.findFieldViewGroup" />
|
||||
</div>
|
||||
<div id="search-bar">
|
||||
<form>
|
||||
<div id="search-settings-bar">
|
||||
<div class="group">
|
||||
<label for="search-group-by-1">Group:</label>
|
||||
<select id="search-group-by-1"></select>
|
||||
<select id="search-group-by-2"></select>
|
||||
<select id="search-group-by-3"></select>
|
||||
</div>
|
||||
|
||||
<label for="split-by">Split:</label>
|
||||
<select id="split-by"></select>
|
||||
|
||||
<div class="group">
|
||||
<label for="search-limit-by">Limit:</label>
|
||||
<select id="search-limit-by">
|
||||
<option value="NO_LIMIT" selected="selected">no limit</option>
|
||||
<option value="MOST_VALUES">most values</option>
|
||||
<option value="FEWEST_VALUES">fewest values</option>
|
||||
<option value="MAX_VALUE">max value</option>
|
||||
<option value="MIN_VALUE">min value</option>
|
||||
</select>
|
||||
<input type="number" id="search-limit-value" name="search-limit-value" min="1" max="1000" value="10"/>
|
||||
</div>
|
||||
<div class="group">
|
||||
<label for="search-date-from">From Date:</label>
|
||||
<input id="search-date-from" class="input_date" type="text" value="{{oldestValue}}" required="required" pattern="\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2]\d|3[0-1]) [0-2]\d:[0-5]\d:[0-5]\d">
|
||||
</div>
|
||||
<div class="group">
|
||||
<label for="search-date-range">Interval:</label>
|
||||
<input id="search-date-range" type="text" list="ranges" required="required" value="1 week" pattern="\d+ (second|minute|hour|day|week|month)s?">
|
||||
<datalist id="ranges">
|
||||
<option value="60 seconds">
|
||||
<option value="5 minutes">
|
||||
<option value="1 hour">
|
||||
<option value="1 day">
|
||||
<option value="1 week">
|
||||
<option value="1 month">
|
||||
</datalist>
|
||||
</div>
|
||||
<div class="group">
|
||||
<label for="search-y-axis-scale">Y-Axis:</label>
|
||||
<select id="search-y-axis-scale">
|
||||
<option value="LINEAR" selected="selected">linear</option>
|
||||
<option value="LOG10">log 10</option>
|
||||
<option value="LOG2">log 2</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="group">
|
||||
<label for="plot-type">Type:</label>
|
||||
<select id="plot-type">
|
||||
<option value="SCATTER" selected="selected">Scatter</option>
|
||||
<option value="PERCENTILES">Percentiles</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="group" id="group-show-aggregate">
|
||||
<label for="show-aggregate">Aggregate:</label>
|
||||
<select id="show-aggregate">
|
||||
<option value="NONE" selected="selected">-</option>
|
||||
<option value="PERCENTILES">percentiles</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="group">
|
||||
<input type="checkbox" id="key-outside" />
|
||||
<label for="key-outside">Legend outside</label>
|
||||
</div>
|
||||
<button id="search-submit" title="Create Plot"><i class="fa fa-area-chart" aria-hidden="true"></i> Plot</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="navigation">
|
||||
<button id="nav_left" title="Show Older Values"><i class="fa fa-angle-double-left" aria-hidden="true"></i></button>
|
||||
<button id="nav_left_half" title="Show Older Values"><i class="fa fa-angle-left" aria-hidden="true"></i></button>
|
||||
<div>
|
||||
<button id="zoom_in" title="Zoom In"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
|
||||
<button id="refresh" title="Refresh"><i class="fa fa-refresh" aria-hidden="true"></i></button>
|
||||
<button id="zoom_out" title="Zoom Out"><i class="fa fa-minus-circle" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<button id="nav_right_half" title="Show Newer Values"><i class="fa fa-angle-right" aria-hidden="true"></i></button>
|
||||
<button id="nav_right" title="Show Newer Values"><i class="fa fa-angle-double-right" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div id="result">
|
||||
<div id="prev_image" title="Previous Plot"><i class="fa fa-angle-double-left" aria-hidden="true"></i></div>
|
||||
<div id="next_image" title="Next Plot"><i class="fa fa-angle-double-right" aria-hidden="true"></i></div>
|
||||
<div id="result-image"></div>
|
||||
<div id="app">
|
||||
<search-bar v-bind="{ 'searchBar': searchBar }"></search-bar>
|
||||
<navigation-bar v-bind="{ 'searchBar': searchBar }"></navigation-bar>
|
||||
<result-view v-bind="{ 'searchBar': searchBar, 'resultView': resultView }"></result-view>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user