show rendered image

added loading icon
image is scaled to available space
This commit is contained in:
2016-12-21 20:06:11 +01:00
parent d1e39513f3
commit bc5d1b0b7b
19 changed files with 461 additions and 47 deletions

View File

@@ -0,0 +1,10 @@
package org.lucares.recommind.logs;
public class InternalPlottingException extends Exception {
private static final long serialVersionUID = 1L;
public InternalPlottingException(final String message, final Throwable cause) {
super(message, cause);
}
}

View File

@@ -7,6 +7,7 @@ import java.io.OutputStreamWriter;
import java.io.Writer; import java.io.Writer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@@ -20,34 +21,72 @@ import org.lucares.performance.db.FileUtils;
import org.lucares.performance.db.PerformanceDb; import org.lucares.performance.db.PerformanceDb;
public class Plotter { public class Plotter {
public static void main(final String[] args) throws Exception {
final Path dataDirectory = Paths.get(args[0]);
final Path outputDirectory = Paths.get(args[1]);
final String query = args[2];
final Path tmpBaseDir = Paths.get(args[0], "tmp");
Files.createDirectories(tmpBaseDir);
Files.createDirectories(outputDirectory);
final Path tmpDirectory = Files.createTempDirectory(tmpBaseDir, "gnuplot");
try {
private final PerformanceDb db;
private final Path tmpBaseDir;
private final Path outputDir;
public Plotter(final PerformanceDb db, final Path tmpBaseDir, final Path outputDir) {
this.db = db;
this.tmpBaseDir = tmpBaseDir;
this.outputDir = outputDir;
if (!Files.isDirectory(tmpBaseDir, LinkOption.NOFOLLOW_LINKS)) {
throw new IllegalArgumentException(tmpBaseDir + " is not a directory");
}
if (!Files.isDirectory(outputDir)) {
throw new IllegalArgumentException(outputDir + " is not a directory");
}
}
public Path getOutputDir() {
return outputDir;
}
public File plot(final String query, final int height, final int width) throws InternalPlottingException {
try {
final Collection<DataSeries> dataSeries = new ArrayList<>(); final Collection<DataSeries> dataSeries = new ArrayList<>();
try (PerformanceDb db = new PerformanceDb(dataDirectory)) {
final Stream<Entry> entries = db.get(query).singleGroup().asStream(); final Stream<Entry> entries = db.get(query).singleGroup().asStream();
final File dataFile = File.createTempFile("data", ".dat", tmpDirectory.toFile()); final File dataFile = File.createTempFile("data", ".dat", tmpBaseDir.toFile());
final DataSeries dataSerie = new DataSeries(dataFile, query); final DataSeries dataSerie = new DataSeries(dataFile, query);
toCsv(entries, dataFile); toCsv(entries, dataFile);
dataSeries.add(dataSerie); dataSeries.add(dataSerie);
final File outputFile = File.createTempFile("out", ".png", outputDir.toFile());
final Gnuplot gnuplot = new Gnuplot(tmpBaseDir);
final GnuplotSettings settings = new GnuplotSettings(outputFile);
settings.setHeight(height);
settings.setWidth(width);
gnuplot.plot(settings, dataSeries);
return outputFile;
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Plotting was interrupted.");
} catch (final IOException e) {
throw new InternalPlottingException("Plotting failed.", e);
}
}
public static void main(final String[] args) throws Exception {
final Path dataDirectory = Paths.get(args[0]);
final Path outputDirectory = Paths.get(args[1]);
final String query = args[2];
final Path tmpBaseDir = Paths.get(args[0], "tmp", "gnuplot");
Files.createDirectories(tmpBaseDir);
Files.createDirectories(outputDirectory);
try {
try (PerformanceDb db = new PerformanceDb(dataDirectory)) {
final Plotter plotter = new Plotter(db, tmpBaseDir, outputDirectory);
final File image = plotter.plot(query, 1600, 1200);
System.out.println("plotted image: " + image);
} }
final File outputFile = File.createTempFile("out", ".png", outputDirectory.toFile());
final Gnuplot gnuplot = new Gnuplot(tmpDirectory);
final GnuplotSettings settings = new GnuplotSettings(outputFile);
gnuplot.plot(settings, dataSeries);
} finally { } finally {
FileUtils.delete(tmpDirectory); FileUtils.delete(tmpBaseDir);
} }
} }

View File

@@ -1,6 +1,8 @@
dependencies { dependencies {
compile project(':performanceDb') compile project(':performanceDb')
compile project(':pdb-plotting')
compile("org.springframework.boot:spring-boot-starter-web:1.4.2.RELEASE") compile("org.springframework.boot:spring-boot-starter-web:1.4.2.RELEASE")
testCompile("org.springframework.boot:spring-boot-starter-test:1.4.2.RELEASE") testCompile("org.springframework.boot:spring-boot-starter-test:1.4.2.RELEASE")

View File

@@ -0,0 +1,9 @@
package org.lucares.pdbui;
public interface HardcodedValues {
/**
* The path for generated images relative to the context root.
*/
String WEB_IMAGE_OUTPUT_PATH = "img-generated";
}

View File

@@ -0,0 +1,18 @@
package org.lucares.pdbui;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "Internal Server Error")
public class InternalServerError extends RuntimeException {
private static final long serialVersionUID = 1L;
public InternalServerError(final String message, final Throwable cause) {
super(message, cause);
}
public InternalServerError(final Throwable cause) {
super(cause);
}
}

View File

@@ -2,11 +2,12 @@ package org.lucares.pdbui;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration @Configuration
@ComponentScan("org.lucares.pdbui") @ComponentScan("org.lucares.pdbui")
// @PropertySource("classpath:/config.system.properties") @PropertySource("classpath:/config.system.properties")
// @PropertySource("classpath:/config.user.properties") @PropertySource("classpath:/config.user.properties")
public class MySpringConfiguration { public class MySpringConfiguration {
} }

View File

@@ -1,13 +1,9 @@
package org.lucares.pdbui; package org.lucares.pdbui;
import java.nio.file.Paths;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
public class MyWebapp { public class MyWebapp {
public static final String IMAGE_DIR = Paths.get("/tmp/images").toFile().getAbsolutePath() + "/";
public static void main(final String[] args) throws Exception { public static void main(final String[] args) throws Exception {
SpringApplication.run(MySpringConfiguration.class, args); SpringApplication.run(MySpringConfiguration.class, args);
} }

View File

@@ -1,7 +1,12 @@
package org.lucares.pdbui; package org.lucares.pdbui;
import java.io.File;
import java.nio.file.Path;
import org.lucares.pdbui.domain.PlotRequest; import org.lucares.pdbui.domain.PlotRequest;
import org.lucares.pdbui.domain.PlotResponse; import org.lucares.pdbui.domain.PlotResponse;
import org.lucares.recommind.logs.InternalPlottingException;
import org.lucares.recommind.logs.Plotter;
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;
@@ -12,12 +17,12 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller @Controller
@EnableAutoConfiguration @EnableAutoConfiguration
public class PdbController { public class PdbController implements HardcodedValues {
private final PdbRepository pdbRepository; private final Plotter plotter;
public PdbController(final PdbRepository pdbRepository) { public PdbController(final Plotter plotter) {
this.pdbRepository = pdbRepository; this.plotter = plotter;
} }
@RequestMapping(path = "/plots", // @RequestMapping(path = "/plots", //
@@ -27,8 +32,17 @@ public class PdbController {
) )
@ResponseBody @ResponseBody
PlotResponse createPlot(@RequestBody final PlotRequest request) { PlotResponse createPlot(@RequestBody final PlotRequest request) {
try {
System.out.println(request.getQuery()); System.out.println(request.getQuery());
return new PlotResponse("img/abc.png"); final File image = plotter.plot(request.getQuery(), request.getHeight(), request.getWidth());
final Path relativeImagePath = plotter.getOutputDir().relativize(image.toPath());
return new PlotResponse(WEB_IMAGE_OUTPUT_PATH + "/" + relativeImagePath.toString());
} catch (final InternalPlottingException e) {
throw new InternalServerError(e);
}
} }
} }

View File

@@ -1,15 +1,24 @@
package org.lucares.pdbui; package org.lucares.pdbui;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.lucares.performance.db.PerformanceDb; import org.lucares.performance.db.PerformanceDb;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@Repository @Repository
public class PdbRepository implements DisposableBean { public class PdbRepository implements DisposableBean {
private final PerformanceDb db; private final PerformanceDb db;
public PdbRepository(final PerformanceDb db) { public PdbRepository(@Value("${db.base}") final String dbBaseDir) {
this.db = db; final Path dataDirectory = Paths.get(dbBaseDir);
this.db = new PerformanceDb(dataDirectory);
}
public PerformanceDb getDb() {
return db;
} }
@Override @Override

View File

@@ -0,0 +1,42 @@
package org.lucares.pdbui;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.lucares.performance.db.PerformanceDb;
import org.lucares.recommind.logs.Plotter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.stereotype.Component;
@Component
public class PlotterBeanFactory extends AbstractFactoryBean<Plotter> implements PropertyKeys {
private final PerformanceDb db;
private final Path tmpDir;
private final Path outputDir;
@Autowired
public PlotterBeanFactory(final PdbRepository dbRepository, @Value("${" + TMP_DIR + "}") final String tmpDir,
@Value("${" + PATH_GENERATED_IMAGES + "}") final String outputDir) {
this.db = dbRepository.getDb();
this.tmpDir = Paths.get(tmpDir);
this.outputDir = Paths.get(outputDir);
}
@Override
public Class<?> getObjectType() {
return Plotter.class;
}
@Override
protected Plotter createInstance() throws Exception {
Files.createDirectories(tmpDir);
Files.createDirectories(outputDir);
return new Plotter(db, tmpDir, outputDir);
}
}

View File

@@ -0,0 +1,14 @@
package org.lucares.pdbui;
public interface PropertyKeys {
/**
* The path for generated images
*/
String PATH_GENERATED_IMAGES = "path.output";
/**
* Path for temporary files
*/
String TMP_DIR = "path.tmp";
}

View File

@@ -1,14 +1,27 @@
package org.lucares.pdbui; package org.lucares.pdbui;
import java.nio.file.Paths;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration @Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter { public class WebConfiguration extends WebMvcConfigurerAdapter implements HardcodedValues, PropertyKeys {
private final String outputDir;
public WebConfiguration(@Value("${" + PATH_GENERATED_IMAGES + "}") final String outputDir) {
this.outputDir = outputDir;
}
@Override @Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) { public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/img/**").addResourceLocations("file:" + MyWebapp.IMAGE_DIR);
final String pathPattern = "/" + WEB_IMAGE_OUTPUT_PATH + "/**";
final String resourceLocation = "file:" + Paths.get(outputDir).toAbsolutePath() + "/";
registry.addResourceHandler(pathPattern).addResourceLocations(resourceLocation);
} }
} }

View File

@@ -3,6 +3,10 @@ package org.lucares.pdbui.domain;
public class PlotRequest { public class PlotRequest {
private String query; private String query;
private int height;
private int width;
public String getQuery() { public String getQuery() {
return query; return query;
} }
@@ -11,8 +15,24 @@ public class PlotRequest {
this.query = query; this.query = query;
} }
public int getWidth() {
return width;
}
public void setWidth(final int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(final int height) {
this.height = height;
}
@Override @Override
public String toString() { public String toString() {
return query; return query + ":" + height + "x" + width;
} }
} }

View File

@@ -1 +1,4 @@
db.base=/tmp/db db.base=/tmp/db
path.tmp=/tmp/pdb/tmp
path.output=/tmp/pdb/out

View File

@@ -1,4 +1,5 @@
body { html, body {
height: 100%;
margin:0; margin:0;
padding:0; padding:0;
} }
@@ -11,11 +12,19 @@ body {
font-style: normal; font-style: normal;
} }
#content{
display: flex;
flex-flow: column;
height: 100%;
}
#top-menu-bar { #top-menu-bar {
background-color: black; background-color: black;
color: white; color: white;
padding: 3px; padding: 3px;
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
} }
#logo { #logo {
@@ -26,6 +35,9 @@ body {
background-color: #aaa; background-color: #aaa;
text-align: right; text-align: right;
padding-bottom:3px; padding-bottom:3px;
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
} }
#search-bar #search-query { #search-bar #search-query {
@@ -42,8 +54,13 @@ body {
} }
#result-view { #result-view {
background: red; background: #eee;
font-size: 32px; bottom: 0;
left:0;
right: 0;
flex-grow: 1;
flex-shrink: 1;
flex-basis: auto;
} }
#result-view i { #result-view i {
@@ -53,3 +70,10 @@ body {
line-height: 1.2em; line-height: 1.2em;
} }
.center
{
display: flex;
justify-content: center;
align-items: center;
}

View File

@@ -0,0 +1,189 @@
.uil-cube-css {
background: none;
position: relative;
width: 200px;
height: 200px;
}
width: 100%;
@-webkit-keyframes uil-cube-css {
0% {
-ms-transform: scale(1.4);
-moz-transform: scale(1.4);
-webkit-transform: scale(1.4);
-o-transform: scale(1.4);
transform: scale(1.4);
}
100% {
-ms-transform: scale(1);
-moz-transform: scale(1);
-webkit-transform: scale(1);
-o-transform: scale(1);
transform: scale(1);
}
}
@-webkit-keyframes uil-cube-css {
0% {
-ms-transform: scale(1.4);
-moz-transform: scale(1.4);
-webkit-transform: scale(1.4);
-o-transform: scale(1.4);
transform: scale(1.4);
}
100% {
-ms-transform: scale(1);
-moz-transform: scale(1);
-webkit-transform: scale(1);
-o-transform: scale(1);
transform: scale(1);
}
}
@-moz-keyframes uil-cube-css {
0% {
-ms-transform: scale(1.4);
-moz-transform: scale(1.4);
-webkit-transform: scale(1.4);
-o-transform: scale(1.4);
transform: scale(1.4);
}
100% {
-ms-transform: scale(1);
-moz-transform: scale(1);
-webkit-transform: scale(1);
-o-transform: scale(1);
transform: scale(1);
}
}
@-ms-keyframes uil-cube-css {
0% {
-ms-transform: scale(1.4);
-moz-transform: scale(1.4);
-webkit-transform: scale(1.4);
-o-transform: scale(1.4);
transform: scale(1.4);
}
100% {
-ms-transform: scale(1);
-moz-transform: scale(1);
-webkit-transform: scale(1);
-o-transform: scale(1);
transform: scale(1);
}
}
@-moz-keyframes uil-cube-css {
0% {
-ms-transform: scale(1.4);
-moz-transform: scale(1.4);
-webkit-transform: scale(1.4);
-o-transform: scale(1.4);
transform: scale(1.4);
}
100% {
-ms-transform: scale(1);
-moz-transform: scale(1);
-webkit-transform: scale(1);
-o-transform: scale(1);
transform: scale(1);
}
}
@-webkit-keyframes uil-cube-css {
0% {
-ms-transform: scale(1.4);
-moz-transform: scale(1.4);
-webkit-transform: scale(1.4);
-o-transform: scale(1.4);
transform: scale(1.4);
}
100% {
-ms-transform: scale(1);
-moz-transform: scale(1);
-webkit-transform: scale(1);
-o-transform: scale(1);
transform: scale(1);
}
}
@-o-keyframes uil-cube-css {
0% {
-ms-transform: scale(1.4);
-moz-transform: scale(1.4);
-webkit-transform: scale(1.4);
-o-transform: scale(1.4);
transform: scale(1.4);
}
100% {
-ms-transform: scale(1);
-moz-transform: scale(1);
-webkit-transform: scale(1);
-o-transform: scale(1);
transform: scale(1);
}
}
@keyframes uil-cube-css {
0% {
-ms-transform: scale(1.4);
-moz-transform: scale(1.4);
-webkit-transform: scale(1.4);
-o-transform: scale(1.4);
transform: scale(1.4);
}
100% {
-ms-transform: scale(1);
-moz-transform: scale(1);
-webkit-transform: scale(1);
-o-transform: scale(1);
transform: scale(1);
}
}
.uil-cube-css > div {
position: absolute;
width: 80px;
height: 80px;
-ms-animation: uil-cube-css 1s cubic-bezier(0.2, 0.8, 0.2, 0.8) infinite;
-moz-animation: uil-cube-css 1s cubic-bezier(0.2, 0.8, 0.2, 0.8) infinite;
-webkit-animation: uil-cube-css 1s cubic-bezier(0.2, 0.8, 0.2, 0.8) infinite;
-o-animation: uil-cube-css 1s cubic-bezier(0.2, 0.8, 0.2, 0.8) infinite;
animation: uil-cube-css 1s cubic-bezier(0.2, 0.8, 0.2, 0.8) infinite;
}
.uil-cube-css > div:nth-of-type(1) {
top: 10px;
left: 10px;
background: #cec9c9;
opacity: 0.9;
-ms-animation-delay: 0s;
-moz-animation-delay: 0s;
-webkit-animation-delay: 0s;
-o-animation-delay: 0s;
animation-delay: 0s;
}
.uil-cube-css > div:nth-of-type(2) {
top: 10px;
left: 110px;
background: #cec9c9;
opacity: 0.8;
-ms-animation-delay: 0.1s;
-moz-animation-delay: 0.1s;
-webkit-animation-delay: 0.1s;
-o-animation-delay: 0.1s;
animation-delay: 0.1s;
}
.uil-cube-css > div:nth-of-type(3) {
top: 110px;
left: 10px;
background: #cec9c9;
opacity: 0.7;
-ms-animation-delay: 0.3s;
-moz-animation-delay: 0.3s;
-webkit-animation-delay: 0.3s;
-o-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.uil-cube-css > div:nth-of-type(4) {
top: 110px;
left: 110px;
background: #cec9c9;
opacity: 0.6;
-ms-animation-delay: 0.2s;
-moz-animation-delay: 0.2s;
-webkit-animation-delay: 0.2s;
-o-animation-delay: 0.2s;
animation-delay: 0.2s;
}

View File

@@ -6,16 +6,19 @@
<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/icons.css"> <link rel="stylesheet" type="text/css" href="css/icons.css">
<link rel="stylesheet" type="text/css" href="css/loading.css">
</head> </head>
<body> <body>
<div id="content">
<div id="top-menu-bar"> <div id="top-menu-bar">
<div id="logo">LOGO</div> <div id="logo">LOGO</div>
</div> </div>
<div id="search-bar"> <div id="search-bar">
<textarea id="search-query"></textarea> <textarea id="search-query">method=AuditService.logEvent</textarea>
<button id="search-submit"><i class="fa fa-search"> Search</i></button> <button id="search-submit"><i class="fa fa-search"> Search</i></button>
</div> </div>
<div id="result-view"> <div id="result-view">
</div> </div>
</div>
</body> </body>
</html> </html>

View File

@@ -4,12 +4,15 @@ $(document).ready(function(){
$('#search-submit').click(function(event){ $('#search-submit').click(function(event){
event.preventDefault(); // prevent submit of form which would reload the page event.preventDefault(); // prevent submit of form which would reload the page
showLoadingIcon();
var request = {}; var request = {};
request['query'] = $('#search-query').val(); request['query'] = $('#search-query').val();
request['height'] = $('#result-view').height()-10;
request['width'] = $('#result-view').width()-10;
var success = function(response){ var success = function(response){
$('#result-view').text("SUCCESS: "+response.imageUrls); $('#result-view').html('<img src=\"'+response.imageUrls+'" />');
}; };
var error = function(e) { var error = function(e) {
//var response = JSON.parse(e.responseText); //var response = JSON.parse(e.responseText);
@@ -22,6 +25,11 @@ $(document).ready(function(){
}); });
}); });
function showLoadingIcon()
{
$('#result-view').html("<div class='center'><div class='uil-cube-css' style='-webkit-transform:scale(0.41)'><div /><div></div><div></div><div></div></div></div>");
}
function postJson(url, requestData, successCallback, errorCallback) { function postJson(url, requestData, successCallback, errorCallback) {