use vue.js for the UI
This commit is contained in:
@@ -4,16 +4,15 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DashTypes {
|
public class DashTypes {
|
||||||
public static final List<String> DEFAULT = Arrays.asList(
|
public static final List<String> DEFAULT = Arrays.asList("1", //
|
||||||
"1",//
|
"2", //
|
||||||
"2",//
|
"3", //
|
||||||
"3",//
|
"4", //
|
||||||
"4",//
|
"5", //
|
||||||
"6",//
|
"6", //
|
||||||
"\".\"",//
|
"\".\"", //
|
||||||
"\"-\"",//
|
"\"-\"", //
|
||||||
"\"._\"",//
|
"\"._\"", //
|
||||||
"\"..- \"",//
|
"\"..- \""//
|
||||||
"\"(50,6,2,6)\""//
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ public class BadRequest extends RuntimeException {
|
|||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BadRequest(final String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
public BadRequest(final Throwable cause) {
|
public BadRequest(final Throwable cause) {
|
||||||
super(cause);
|
super(cause);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.lucares.pdbui;
|
package org.lucares.pdbui;
|
||||||
|
|
||||||
import java.text.Collator;
|
import java.text.Collator;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -12,6 +11,7 @@ import java.util.Map;
|
|||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.lucares.pdb.datastore.Proposal;
|
import org.lucares.pdb.datastore.Proposal;
|
||||||
import org.lucares.pdb.plot.api.PlotSettings;
|
import org.lucares.pdb.plot.api.PlotSettings;
|
||||||
import org.lucares.pdbui.domain.AutocompleteProposal;
|
import org.lucares.pdbui.domain.AutocompleteProposal;
|
||||||
@@ -28,6 +28,7 @@ import org.lucares.recommind.logs.Plotter;
|
|||||||
import org.lucares.utils.CollectionUtils;
|
import org.lucares.utils.CollectionUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
@@ -54,6 +55,9 @@ public class PdbController implements HardcodedValues {
|
|||||||
|
|
||||||
private final ReentrantLock plotterLock = new ReentrantLock();
|
private final ReentrantLock plotterLock = new ReentrantLock();
|
||||||
|
|
||||||
|
@Value("${mode.production:true}")
|
||||||
|
private boolean modeProduction;
|
||||||
|
|
||||||
public PdbController(final PerformanceDb db, final Plotter plotter) {
|
public PdbController(final PerformanceDb db, final Plotter plotter) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.plotter = plotter;
|
this.plotter = plotter;
|
||||||
@@ -63,8 +67,10 @@ public class PdbController implements HardcodedValues {
|
|||||||
public ModelAndView index() {
|
public ModelAndView index() {
|
||||||
final String view = "main";
|
final String view = "main";
|
||||||
final Map<String, Object> model = new HashMap<>();
|
final Map<String, Object> model = new HashMap<>();
|
||||||
model.put("oldestValue", LocalDateTime.now().minusDays(7).format(DATE_FORMAT_BEGIN));
|
// model.put("oldestValue",
|
||||||
model.put("latestValue", LocalDateTime.now().format(DATE_FORMAT_END));
|
// 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);
|
return new ModelAndView(view, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,20 +83,21 @@ public class PdbController implements HardcodedValues {
|
|||||||
PlotResponse createPlot(@RequestBody final PlotRequest request)
|
PlotResponse createPlot(@RequestBody final PlotRequest request)
|
||||||
throws InternalPlottingException, InterruptedException {
|
throws InternalPlottingException, InterruptedException {
|
||||||
|
|
||||||
final PlotSettings plotSettings = PlotSettingsTransformer
|
final PlotSettings plotSettings = PlotSettingsTransformer.toSettings(request);
|
||||||
.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()) {
|
if (plotterLock.tryLock()) {
|
||||||
try {
|
try {
|
||||||
final PlotResult result = plotter.plot(plotSettings);
|
final PlotResult result = plotter.plot(plotSettings);
|
||||||
|
|
||||||
final String imageUrl = WEB_IMAGE_OUTPUT_PATH + "/"
|
final String imageUrl = WEB_IMAGE_OUTPUT_PATH + "/" + result.getImageName();
|
||||||
+ result.getImageName();
|
|
||||||
LOGGER.trace("image url: {}", imageUrl);
|
LOGGER.trace("image url: {}", imageUrl);
|
||||||
|
|
||||||
return new PlotResponse(
|
return new PlotResponse(DataSeries.toMap(result.getDataSeries()), imageUrl);
|
||||||
DataSeries.toMap(result.getDataSeries()), imageUrl);
|
|
||||||
} catch (final NoDataPointsException e) {
|
} catch (final NoDataPointsException e) {
|
||||||
throw new NotFoundException(e);
|
throw new NotFoundException(e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -115,7 +122,7 @@ public class PdbController implements HardcodedValues {
|
|||||||
final int zeroBasedCaretIndex = caretIndex - 1;
|
final int zeroBasedCaretIndex = caretIndex - 1;
|
||||||
|
|
||||||
final List<Proposal> proposals = db.autocomplete(query, zeroBasedCaretIndex);
|
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);
|
final List<AutocompleteProposal> autocompleteProposals = toAutocompleteProposals(nonEmptyProposals);
|
||||||
Collections.sort(autocompleteProposals, new AutocompleteProposalByValue());
|
Collections.sort(autocompleteProposals, new AutocompleteProposalByValue());
|
||||||
|
|||||||
@@ -1,25 +1,22 @@
|
|||||||
package org.lucares.pdbui.domain;
|
package org.lucares.pdbui.domain;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class PlotResponse {
|
public class PlotResponse {
|
||||||
private List<String> imageUrls = new ArrayList<>();
|
private String imageUrl = "";
|
||||||
private Map<String, Integer> dataSeries;
|
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.dataSeries = dataSeries;
|
||||||
this.imageUrls.addAll(Arrays.asList(imageUrls));
|
this.imageUrl = imageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getImageUrls() {
|
public String getImageUrl() {
|
||||||
return imageUrls;
|
return imageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setImageUrls(final List<String> imageUrls) {
|
public void setImageUrl(final String imageUrl) {
|
||||||
this.imageUrls = imageUrls;
|
this.imageUrl = imageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Integer> getDataSeries() {
|
public Map<String, Integer> getDataSeries() {
|
||||||
@@ -32,7 +29,6 @@ public class PlotResponse {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.valueOf(imageUrls) + " " + dataSeries;
|
return imageUrl + " " + dataSeries;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,9 @@ html {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body{
|
body {
|
||||||
display: grid;
|
margin:0;
|
||||||
height: 100vh;
|
padding:0;
|
||||||
margin: 0;
|
|
||||||
grid:
|
|
||||||
"search_field" auto
|
|
||||||
"search" auto
|
|
||||||
"navigation " auto
|
|
||||||
"result" 1fr
|
|
||||||
/ 1fr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -29,6 +22,17 @@ body{
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: grid;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
grid:
|
||||||
|
"search" auto
|
||||||
|
"navigation " auto
|
||||||
|
"result" 1fr
|
||||||
|
/ 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
#logo {
|
#logo {
|
||||||
grid-area: logo;
|
grid-area: logo;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
@@ -46,6 +50,11 @@ body{
|
|||||||
grid-area: search;
|
grid-area: search;
|
||||||
background-color: #aaa;
|
background-color: #aaa;
|
||||||
padding-bottom:3px;
|
padding-bottom:3px;
|
||||||
|
display: grid;
|
||||||
|
grid:
|
||||||
|
"search_field" auto
|
||||||
|
"filter_bar" auto
|
||||||
|
/ 1fr
|
||||||
}
|
}
|
||||||
|
|
||||||
#navigation {
|
#navigation {
|
||||||
@@ -55,26 +64,26 @@ body{
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete .active {
|
#result {
|
||||||
background-color: #AAA;
|
grid-area: result;
|
||||||
}
|
background: #eee;
|
||||||
.autocomplete.open{
|
margin: 0;
|
||||||
z-index:2;
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position:relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* scrollbars are nice, but with them an empty autocomplete box is shown
|
#filter-bar {
|
||||||
|
grid-area: filter_bar;
|
||||||
.autocomplete, #search-input-wrapper .autocomplete.open {
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
#search-input {
|
#search-input {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#search-limit-value {
|
#search-limit-value {
|
||||||
width: 4em;
|
width: 4em;
|
||||||
}
|
}
|
||||||
@@ -96,14 +105,7 @@ body{
|
|||||||
margin-right:3px;
|
margin-right:3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#result {
|
|
||||||
grid-area: result;
|
|
||||||
background: #eee;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
position:relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#result-image {
|
#result-image {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -134,6 +136,10 @@ body{
|
|||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#result-error-message {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.center
|
.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>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script type="text/javascript" src="js/jquery-3.2.0.min.js"></script>
|
<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/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/date.js"></script>
|
||||||
<script type="text/javascript" src="js/invaders.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/typography.css">
|
||||||
<link rel="stylesheet" type="text/css" href="css/design.css">
|
<link rel="stylesheet" type="text/css" href="css/design.css">
|
||||||
<link rel="stylesheet" type="text/css" href="css/loading.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">
|
<link rel="stylesheet" type="text/css" href="css/invaders.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="app">
|
||||||
<div id="search-input-wrapper">
|
<search-bar v-bind="{ 'searchBar': searchBar }"></search-bar>
|
||||||
<input id="search-input" placeholder="field=value and anotherField=anotherValue" data-autocomplete="autocomplete"
|
<navigation-bar v-bind="{ 'searchBar': searchBar }"></navigation-bar>
|
||||||
data-autocomplete-empty-message="nothing found" value="pod=vapfinra01 and method = ViewService.findFieldViewGroup" />
|
<result-view v-bind="{ 'searchBar': searchBar, 'resultView': resultView }"></result-view>
|
||||||
</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>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user