From 96910b51e09355361eaf0c548e4837011210a683 Mon Sep 17 00:00:00 2001
From: Eike Cochu <eike@cochu.com>
Date: Sat, 7 May 2016 20:44:37 +0200
Subject: [PATCH] various fixes, updated websocket url

---
 .project                                      |  11 --
 README.md                                     |  39 +++++++
 Vagrantfile                                   |  20 ----
 docker/.gitignore                             |   3 +-
 vipra-cmd/pom.xml                             |   7 ++
 vipra-cmd/runcfg/CMD.launch                   |   2 +-
 .../java/de/vipra/cmd/CommandLineOptions.java |  22 +++-
 .../src/main/java/de/vipra/cmd/Main.java      |   8 ++
 .../de/vipra/cmd/option/BackupCommand.java    |  41 +++++++
 .../de/vipra/cmd/option/ImportCommand.java    |   3 +-
 .../de/vipra/cmd/option/IndexingCommand.java  |   3 +-
 .../de/vipra/cmd/option/ModelingCommand.java  |   3 +-
 .../de/vipra/cmd/option/RestoreCommand.java   |  41 +++++++
 vipra-ui/README.md                            |   5 +-
 .../app/html/directives/article-popover.html  |  21 ++++
 vipra-ui/app/html/index.html                  |   9 +-
 vipra-ui/app/html/network.html                |  13 ++-
 vipra-ui/app/img/vipra.png                    | Bin 0 -> 11513 bytes
 vipra-ui/app/img/vipra.svg                    | 105 ++++++++++++++----
 vipra-ui/app/index.html                       |  10 +-
 vipra-ui/app/js/app.js                        |  41 +++----
 vipra-ui/app/js/config.js                     |   5 -
 vipra-ui/app/js/controllers.js                |  30 ++++-
 vipra-ui/app/js/directives.js                 |  30 +++++
 vipra-ui/app/js/helpers.js                    |  13 +++
 vipra-ui/app/less/app.less                    |  76 +++++++++----
 vipra-ui/bower.json                           |   3 +-
 vipra-ui/gulpfile.js                          |   5 +-
 vipra-ui/package.json                         |   2 -
 .../main/java/de/vipra/util/FileUtils.java    |  10 ++
 .../java/de/vipra/util/IPCMessageCode.java    |   9 +-
 .../src/main/java/de/vipra/util/LockFile.java |   2 +-
 vm/.gitignore                                 |   3 -
 vm/README.md                                  |   3 -
 vm/bootstrap.sh                               |  57 ----------
 vm/webroot/.gitkeep                           |   0
 36 files changed, 454 insertions(+), 201 deletions(-)
 delete mode 100644 .project
 delete mode 100644 Vagrantfile
 create mode 100644 vipra-cmd/src/main/java/de/vipra/cmd/option/BackupCommand.java
 create mode 100644 vipra-cmd/src/main/java/de/vipra/cmd/option/RestoreCommand.java
 create mode 100644 vipra-ui/app/html/directives/article-popover.html
 create mode 100644 vipra-ui/app/img/vipra.png
 delete mode 100644 vm/.gitignore
 delete mode 100644 vm/README.md
 delete mode 100644 vm/bootstrap.sh
 delete mode 100644 vm/webroot/.gitkeep

diff --git a/.project b/.project
deleted file mode 100644
index 4c665a96..00000000
--- a/.project
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>vipra-config</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-	</buildSpec>
-	<natures>
-	</natures>
-</projectDescription>
diff --git a/README.md b/README.md
index 32e3e6a7..0c479413 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,8 @@ This application was created by Eike Cochu for his master's degree thesis in com
 
 ## Installation
 
+### Manual
+
 1. Deploy vipra.war or exploded vipra directory to a Java application server. Deploy to ROOT (/) or a different context path (/vipra, ...)
 2. Edit WEB-INF/classes/config.json if MongoDB or ElasticSearch do not run with default configuration (host/port)
 3. Deploy vipra-ui to a static webserver 
@@ -22,6 +24,43 @@ This application was created by Eike Cochu for his master's degree thesis in com
 
 if everything is left default, application should be available under: http://someserver/ and backend: http://someserver:8080/rest/
 
+### Docker
+
+1. Load docker image: `docker pull eikecochu/vipra`
+2. Create and run docker container: `docker run -p 80:80 -p 6789:6789 -p 9300:9300 -p 27017:27017 eikecochu/vipra`
+3. Install vipra: ``
+
+### Nginx Proxy
+
+To proxy requests through nginx, use this server configuration (replace `#SERVER_NAME_HERE#`):
+
+```bash
+server {
+    listen 80;
+    server_name #SERVER_NAME_HERE#;
+
+    access_log off;
+    error_log /var/log/nginx/vipra.error.log warn;
+
+    include include.d/security;
+    include include.d/ssl;
+
+    location / {
+        proxy_buffering off;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_pass http://localhost:80/;
+    }
+
+location /ws {
+        proxy_pass http://localhost:80/ws;
+        proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection "upgrade";
+    }
+}
+```
+
 ## Development
 
 The following steps were reproduced in that order on a fully updated Ubuntu 15.10 virtual system to create a fully operational development environment for the Vipra projects. These steps are only required for project development. If you want to create a virtual machine for development, create a hard drive with at least 10 GB in size. 
diff --git a/Vagrantfile b/Vagrantfile
deleted file mode 100644
index 28cab3ce..00000000
--- a/Vagrantfile
+++ /dev/null
@@ -1,20 +0,0 @@
-Vagrant.configure(2) do |config|
-  config.vm.box = 'ubuntu/wily64'
-
-  ENV['LC_ALL']="en_US.UTF-8"
-
-  config.vm.provider "virtualbox" do |v|
-    v.memory = 8192
-    v.cpus = 2
-  end
-
-  config.vm.define "master" do |master|
-    master.vm.hostname = 'vipra-master'
-    master.vm.network :private_network, ip: '192.168.10.10'
-    master.vm.network :forwarded_port, guest: 27017, host: 27017 # MongoDB
-    master.vm.network :forwarded_port, guest: 8080,  host: 8000  # Tomcat
-    master.vm.network :forwarded_port, guest: 9200,  host: 9200  # ElasticSearch REST API
-    master.vm.network :forwarded_port, guest: 9300,  host: 9300  # ElasticSearch Native
-  end
-
-end
diff --git a/docker/.gitignore b/docker/.gitignore
index faf8905d..3e782a5e 100644
--- a/docker/.gitignore
+++ b/docker/.gitignore
@@ -1,2 +1,3 @@
 webapps/*
-webroot/*
\ No newline at end of file
+webroot/*
+data/
\ No newline at end of file
diff --git a/vipra-cmd/pom.xml b/vipra-cmd/pom.xml
index efc95329..0569678a 100644
--- a/vipra-cmd/pom.xml
+++ b/vipra-cmd/pom.xml
@@ -64,6 +64,13 @@
 			<version>1.1.0</version>
 		</dependency>
 
+		<!-- ZIP -->
+		<dependency>
+			<groupId>org.zeroturnaround</groupId>
+			<artifactId>zt-zip</artifactId>
+			<version>1.9</version>
+		</dependency>
+
 		<!-- Workspace -->
 		<dependency>
 			<groupId>de.vipra</groupId>
diff --git a/vipra-cmd/runcfg/CMD.launch b/vipra-cmd/runcfg/CMD.launch
index 0e3fecb1..b81b445d 100644
--- a/vipra-cmd/runcfg/CMD.launch
+++ b/vipra-cmd/runcfg/CMD.launch
@@ -11,7 +11,7 @@
 </listAttribute>
 <stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.m2e.launchconfig.classpathProvider"/>
 <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="de.vipra.cmd.Main"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-Ai"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-B /home/eike/Downloads"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="vipra-cmd"/>
 <stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.m2e.launchconfig.sourcepathProvider"/>
 <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea"/>
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/CommandLineOptions.java b/vipra-cmd/src/main/java/de/vipra/cmd/CommandLineOptions.java
index 45e3b4ad..9df57f19 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/CommandLineOptions.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/CommandLineOptions.java
@@ -18,7 +18,7 @@ public class CommandLineOptions {
 	public static final Option LIST = Option.builder("l").longOpt("list").desc("list available models").build();
 	public static final Option SILENT = Option.builder("s").longOpt("silent").desc("suppress all output").build();
 	public static final Option TEST = Option.builder("t").longOpt("test").desc("test database connections").build();
-	public static final Option ALL = Option.builder("A").longOpt("all").desc("select all models (-S all)c").build();
+	public static final Option ALL = Option.builder("A").longOpt("all").desc("select all models (-S all)").build();
 
 	public static final Option INDEX = Option.builder("i").longOpt("index").desc("create index for models").hasArgs().argName("[models...]")
 			.optionalArg(true).build();
@@ -38,6 +38,8 @@ public class CommandLineOptions {
 			.optionalArg(true).build();
 	public static final Option SELECT = Option.builder("S").longOpt("select").desc("select models").hasArgs().argName("models...").build();
 	public static final Option MESSAGE = Option.builder("m").longOpt("message").desc("send a global message").hasArg().argName("message").build();
+	public static final Option BACKUP = Option.builder("B").longOpt("backup").desc("backup data/filebase").hasArg().argName("path").build();
+	public static final Option RESTORE = Option.builder("R").longOpt("restore").desc("restore data/filebase").hasArg().argName("path").build();
 
 	private final Options options;
 	private CommandLine cmd;
@@ -45,7 +47,7 @@ public class CommandLineOptions {
 
 	public CommandLineOptions() {
 		final Option[] optionsArray = { CLEAR, DEBUG, HELP, INDEX, LIST, REREAD, SILENT, TEST, ALL, CREATE, DELETE, EDIT, PRINT, IMPORT, MODEL,
-				SELECT, MESSAGE };
+				SELECT, MESSAGE, BACKUP, RESTORE };
 		options = new Options();
 		for (final Option option : optionsArray)
 			options.addOption(option);
@@ -226,4 +228,20 @@ public class CommandLineOptions {
 		return getOptionValue(MESSAGE);
 	}
 
+	public boolean isBackup() {
+		return hasOption(BACKUP);
+	}
+
+	public String backupPath() {
+		return getOptionValue(BACKUP);
+	}
+
+	public boolean isRestore() {
+		return hasOption(RESTORE);
+	}
+
+	public String restorePath() {
+		return getOptionValue(RESTORE);
+	}
+
 }
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/Main.java b/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
index 9502c5cd..565276c1 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/Main.java
@@ -7,6 +7,7 @@ import java.util.ListIterator;
 import org.mongodb.morphia.logging.MorphiaLoggerFactory;
 import org.mongodb.morphia.logging.slf4j.SLF4JLoggerImplFactory;
 
+import de.vipra.cmd.option.BackupCommand;
 import de.vipra.cmd.option.ClearCommand;
 import de.vipra.cmd.option.Command;
 import de.vipra.cmd.option.CreateModelCommand;
@@ -18,6 +19,7 @@ import de.vipra.cmd.option.ListModelsCommand;
 import de.vipra.cmd.option.MessageCommand;
 import de.vipra.cmd.option.ModelingCommand;
 import de.vipra.cmd.option.PrintModelCommand;
+import de.vipra.cmd.option.RestoreCommand;
 import de.vipra.cmd.option.TestCommand;
 import de.vipra.util.ConsoleUtils;
 import de.vipra.util.LockFile;
@@ -83,6 +85,12 @@ public class Main {
 		if (opts.isMessage())
 			commands.add(new MessageCommand(opts.messageToSend()));
 
+		if (opts.isBackup())
+			commands.add(new BackupCommand(opts.backupPath()));
+
+		if (opts.isRestore())
+			commands.add(new RestoreCommand(opts.restorePath()));
+
 		if (opts.isClear())
 			commands.add(new ClearCommand());
 
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/BackupCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/BackupCommand.java
new file mode 100644
index 00000000..5a9b5b2e
--- /dev/null
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/BackupCommand.java
@@ -0,0 +1,41 @@
+package de.vipra.cmd.option;
+
+import java.io.File;
+import java.util.Date;
+
+import org.zeroturnaround.zip.ZipUtil;
+
+import de.vipra.util.Config;
+import de.vipra.util.FileUtils;
+
+public class BackupCommand implements Command {
+
+	private final String path;
+
+	public BackupCommand(final String path) {
+		this.path = path;
+	}
+
+	@Override
+	public void run() throws Exception {
+		final Config config = Config.getConfig();
+		final File tmpTarget = FileUtils.getTempFile("vipra-dump");
+		FileUtils.deleteDirectory(tmpTarget);
+		final Process p = Runtime.getRuntime().exec("mongodump -d " + config.getDatabaseName() + " -h " + config.getDatabaseHost() + " --port "
+				+ config.getDatabasePort() + " -o " + new File(tmpTarget, "db"));
+		p.waitFor();
+		FileUtils.copyDirectory(config.getDataDirectory(), new File(tmpTarget, "fb"));
+		FileUtils.copyDirectory(Config.getGenericConfigDir(), new File(tmpTarget, "config"));
+		File target = new File(path);
+		if (target.exists() && target.isDirectory())
+			target = new File(target, "vipra-" + new Date().getTime() + ".zip");
+		ZipUtil.pack(tmpTarget, target);
+		FileUtils.deleteDirectory(tmpTarget);
+	}
+
+	@Override
+	public boolean requiresLock() {
+		return true;
+	}
+
+}
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
index 116b67c2..597f9d88 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/ImportCommand.java
@@ -349,9 +349,8 @@ public class ImportCommand implements Command {
 		processor = new Processor();
 		final IPCClient ipcClient = new IPCClient();
 		for (final TopicModelConfig modelConfig : config.getTopicModelConfigs(models)) {
-			ipcClient.send(new IPCMessage(IPCMessageCode.START_IMPORTING).message("Started importing for model '" + modelConfig.getName() + "'"));
+			ipcClient.send(new IPCMessage(IPCMessageCode.IMPORTING).message("Started importing for model '" + modelConfig.getName() + "'"));
 			importForModel(modelConfig);
-			ipcClient.send(new IPCMessage(IPCMessageCode.STOP_IMPORTING).message("Finished importing for model '" + modelConfig.getName() + "'"));
 		}
 		ipcClient.close();
 	}
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java
index adb8e7c7..ffcf1d9c 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java
@@ -96,9 +96,8 @@ public class IndexingCommand implements Command {
 		elasticSerializer = new ESSerializer<>(ArticleFull.class);
 		final IPCClient ipcClient = new IPCClient();
 		for (final TopicModelConfig modelConfig : config.getTopicModelConfigs(models)) {
-			ipcClient.send(new IPCMessage(IPCMessageCode.START_INDEXING).message("Started indexing model '" + modelConfig.getName() + "'"));
+			ipcClient.send(new IPCMessage(IPCMessageCode.INDEXING).message("Started indexing model '" + modelConfig.getName() + "'"));
 			indexForModel(modelConfig);
-			ipcClient.send(new IPCMessage(IPCMessageCode.STOP_INDEXING).message("Finished indexing model '" + modelConfig.getName() + "'"));
 		}
 		ipcClient.close();
 	}
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java
index ca2627cf..5e1b095b 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java
@@ -62,9 +62,8 @@ public class ModelingCommand implements Command {
 
 		final IPCClient ipcClient = new IPCClient();
 		for (final TopicModelConfig modelConfig : config.getTopicModelConfigs(models)) {
-			ipcClient.send(new IPCMessage(IPCMessageCode.START_MODELING).message("Started generating model '" + modelConfig.getName() + "'"));
+			ipcClient.send(new IPCMessage(IPCMessageCode.MODELING).message("Started generating model '" + modelConfig.getName() + "'"));
 			modelForModel(modelConfig);
-			ipcClient.send(new IPCMessage(IPCMessageCode.STOP_MODELING).message("Finished generating model '" + modelConfig.getName() + "'"));
 		}
 		ipcClient.close();
 	}
diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/RestoreCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/RestoreCommand.java
new file mode 100644
index 00000000..f68f906d
--- /dev/null
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/RestoreCommand.java
@@ -0,0 +1,41 @@
+package de.vipra.cmd.option;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+import org.zeroturnaround.zip.ZipUtil;
+
+import de.vipra.util.Config;
+import de.vipra.util.FileUtils;
+
+public class RestoreCommand implements Command {
+
+	private final String path;
+
+	public RestoreCommand(final String path) {
+		this.path = path;
+	}
+
+	@Override
+	public void run() throws Exception {
+		final File zip = new File(path);
+		if (!zip.isFile())
+			throw new FileNotFoundException(path);
+		final File tmpTarget = FileUtils.getTempFile("vipra-dump");
+		FileUtils.deleteDirectory(tmpTarget);
+		ZipUtil.unpack(zip, tmpTarget);
+		final Config config = Config.getConfig();
+		final Process p = Runtime.getRuntime().exec("mongorestore --drop -d " + config.getDatabaseName() + " -h " + config.getDatabaseHost()
+				+ " --port " + config.getDatabasePort() + " " + new File(tmpTarget, "db"));
+		p.waitFor();
+		FileUtils.copyDirectory(new File(tmpTarget, "fb"), config.getDataDirectory());
+		FileUtils.copyDirectory(new File(tmpTarget, "config"), Config.getGenericConfigDir());
+		FileUtils.deleteDirectory(tmpTarget);
+	}
+
+	@Override
+	public boolean requiresLock() {
+		return true;
+	}
+
+}
diff --git a/vipra-ui/README.md b/vipra-ui/README.md
index 333bb4de..899023c7 100644
--- a/vipra-ui/README.md
+++ b/vipra-ui/README.md
@@ -16,14 +16,17 @@ The frontend UI project. Built with AngularJS and many other Javascript and CSS
 * [randomColor](https://github.com/davidmerfield/randomColor) by David Merfield (CC0 1.0)
 * [FontAwesome](https://github.com/FortAwesome/Font-Awesome) by Dave Gandy (SIL OFL 1.1/MIT)
 * [Bower](https://github.com/bower/bower) by Twitter and other contributors (MIT)
+* [Reconnecting Websocket](https://github.com/joewalnes/reconnecting-websocket) by Joe Walnes (MIT)
+* [Awesome Bootstrap Checkbox](https://github.com/flatlogic/awesome-bootstrap-checkbox) by flatlogic.com (MIT)
 * [Gulp](https://github.com/gulpjs/gulp) by Fractal (MIT)
 * [gulp-less](https://github.com/plus3network/gulp-less) by Plus 3 Network (MIT)
+* [gulp-gzip](https://github.com/jstuckey/gulp-gzip) by Jeremy Stuckey (MIT)
 * [gulp-concat](https://github.com/contra/gulp-concat) by Fractal (MIT)
 * [gulp-uglify](https://github.com/terinjokes/gulp-uglify) by Terin Stock (MIT)
 * [gulp-clean-css](https://github.com/scniro/gulp-clean-css) by scniro (MIT)
 * [gulp-webserver](https://github.com/schickling/gulp-webserver) by Johannes Schickling (MIT)
 * [gulp-ng-annotate](https://github.com/Kagami/gulp-ng-annotate) by Kagami Hiiragi and contributors (CC0 1.0)
-* [gulp-templatecache](https://github.com/miickel/gulp-angular-templatecache) by Mickel (MIT)
+* [gulp-angular-templatecache](https://github.com/miickel/gulp-angular-templatecache) by Mickel (MIT)
 
 ## Installation
 
diff --git a/vipra-ui/app/html/directives/article-popover.html b/vipra-ui/app/html/directives/article-popover.html
new file mode 100644
index 00000000..c30e304c
--- /dev/null
+++ b/vipra-ui/app/html/directives/article-popover.html
@@ -0,0 +1,21 @@
+<a ui-sref="articles.show({id: currentArticle.id})" ng-bind="currentArticle.title"></a>
+
+<table class="table table-bordered table-condensed table-fixed nomargin">
+  <tbody>
+    <tr>
+      <th class="infocol">Date</th>
+      <td ng-bind="currentArticleDate"></td>
+    </tr>
+    <tr ng-if="currentArticle.url">
+      <th>URL</th>
+      <td class="break-words">
+        <a ng-href="{{currentArticle.url}}" target="_blank">
+          <i class="fa fa-link"></i>
+          <span ng-bind="currentArticle.url"></span>
+        </a>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+<div class="text-justify article-popover-text" ng-bind-html="currentArticle.text"></div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/index.html b/vipra-ui/app/html/index.html
index 44abb260..ed11e007 100644
--- a/vipra-ui/app/html/index.html
+++ b/vipra-ui/app/html/index.html
@@ -3,7 +3,7 @@
     <div class="container">
       <div class="row row-spaced" ng-hide="search">
         <div class="col-md-12 text-center text-logo">
-          \ˈvī-prə\
+          VIPRA
         </div>
       </div>
       <div class="row row-spaced">
@@ -61,6 +61,13 @@
       </div>
     </div>
   </div>
+  <div class="container" ng-show="!searching && !search">
+    <div class="row">
+      <div class="col-md-offset-1 col-md-10 col-sm-12">
+        <div class="embed-responsive embed-responsive-16by9 screencast"></div>
+      </div>
+    </div>
+  </div>
   <div class="container">
     <div class="row row-spaced">
       <div class="col-md-12 text-center" ng-show="searching" ng-cloak>
diff --git a/vipra-ui/app/html/network.html b/vipra-ui/app/html/network.html
index 83610c2c..fe9e9ab4 100644
--- a/vipra-ui/app/html/network.html
+++ b/vipra-ui/app/html/network.html
@@ -1,6 +1,6 @@
 <div ng-cloak ng-hide="!rootModels.topicModel || state.name !== 'network'">
   <div class="fullsize navpadding">
-    <div class="graph-legend overlay" ng-class="{collapsed:legendCollapsed}">
+    <div class="graph-item top-left small overlay" ng-class="{collapsed:legendCollapsed}">
       <i class="collapser fa pointer pull-right" ng-class="{'fa-chevron-up':!legendCollapsed,'fa-chevron-down':legendCollapsed}" ng-click="legendCollapsed=!legendCollapsed"></i>
       <div ng-hide="legendCollapsed">
         <div class="checkbox">
@@ -19,7 +19,7 @@
           <input type="checkbox" id="showWords" ng-model="shown.words">
           <label for="showWords">Words</label>
         </div>
-        <div>
+        <div ng-show="!type">
           Window
           <window-dropdown ng-model="selectedWindow" windows="windows" />
         </div>
@@ -45,8 +45,17 @@
             </tr>
           </table>
         </div>
+        <div>
+          <button class="btn btn-default btn-block" ng-click="completeNetwork()">
+            Load missing relations
+          </button>
+        </div>
       </div>
     </div>
+    <div class="graph-item bottom-left large overlay" ng-show="currentArticle" ng-class="{collapsed:articlePopoverCollapsed}">
+      <i class="collapser fa pointer pull-right" ng-class="{'fa-chevron-down':!articlePopoverCollapsed,'fa-chevron-up':articlePopoverCollapsed}" ng-click="articlePopoverCollapsed=!articlePopoverCollapsed"></i>
+      <article-popover article="currentArticle" ng-show="!articlePopoverCollapsed"/>
+    </div>
     <div class="fullsize" id="visgraph"></div>
   </div>
 </div>
diff --git a/vipra-ui/app/img/vipra.png b/vipra-ui/app/img/vipra.png
new file mode 100644
index 0000000000000000000000000000000000000000..fbaf9113edc1c7e4f56597a4c10095efcf6c380f
GIT binary patch
literal 11513
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

literal 0
HcmV?d00001

diff --git a/vipra-ui/app/img/vipra.svg b/vipra-ui/app/img/vipra.svg
index 57735f3c..33da8381 100644
--- a/vipra-ui/app/img/vipra.svg
+++ b/vipra-ui/app/img/vipra.svg
@@ -9,15 +9,51 @@
    xmlns="http://www.w3.org/2000/svg"
    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   width="43.380642mm"
-   height="10.914062mm"
-   viewBox="0 0 153.71094 38.671875"
-   id="svg2"
+   width="64.600754mm"
+   height="15.858673mm"
+   viewBox="0 0 228.90031 56.192148"
+   id="svg4341"
    version="1.1"
    inkscape:version="0.91 r13725"
+   inkscape:export-filename="/home/eike/repos/master/ma-impl/vipra-ui/app/img/vipra.png"
+   inkscape:export-xdpi="104.2"
+   inkscape:export-ydpi="104.2"
    sodipodi:docname="vipra.svg">
   <defs
-     id="defs4" />
+     id="defs4343">
+    <filter
+       style="color-interpolation-filters:sRGB"
+       inkscape:label="Drop Shadow"
+       id="filter4947">
+      <feFlood
+         flood-opacity="0.498039"
+         flood-color="rgb(0,0,0)"
+         result="flood"
+         id="feFlood4949" />
+      <feComposite
+         in="flood"
+         in2="SourceGraphic"
+         operator="in"
+         result="composite1"
+         id="feComposite4951" />
+      <feGaussianBlur
+         in="composite1"
+         stdDeviation="2"
+         result="blur"
+         id="feGaussianBlur4953" />
+      <feOffset
+         dx="2"
+         dy="2"
+         result="offset"
+         id="feOffset4955" />
+      <feComposite
+         in="SourceGraphic"
+         in2="offset"
+         operator="over"
+         result="composite2"
+         id="feComposite4957" />
+    </filter>
+  </defs>
   <sodipodi:namedview
      id="base"
      pagecolor="#ffffff"
@@ -25,11 +61,11 @@
      borderopacity="1.0"
      inkscape:pageopacity="0.0"
      inkscape:pageshadow="2"
-     inkscape:zoom="3.959798"
-     inkscape:cx="123.60597"
-     inkscape:cy="-39.672792"
+     inkscape:zoom="2.8"
+     inkscape:cx="62.626262"
+     inkscape:cy="-82.676013"
      inkscape:document-units="px"
-     inkscape:current-layer="layer1"
+     inkscape:current-layer="g4904"
      showgrid="false"
      fit-margin-top="0"
      fit-margin-left="0"
@@ -41,7 +77,7 @@
      inkscape:window-y="24"
      inkscape:window-maximized="1" />
   <metadata
-     id="metadata7">
+     id="metadata4346">
     <rdf:RDF>
       <cc:Work
          rdf:about="">
@@ -56,17 +92,42 @@
      inkscape:label="Ebene 1"
      inkscape:groupmode="layer"
      id="layer1"
-     transform="translate(0,-1013.6903)">
-    <text
-       xml:space="preserve"
-       style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#e6e6e6;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
-       x="0"
-       y="1044.0419"
-       id="text3336"
-       sodipodi:linespacing="125%"><tspan
-         sodipodi:role="line"
-         id="tspan3338"
-         x="0"
-         y="1044.0419">\ˈvī-prə\</tspan></text>
+     transform="translate(19.075026,-1000.8527)">
+    <g
+       style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="text4349">
+      <g
+         id="g4904"
+         transform="matrix(0.73612555,0,0,0.73612555,-30.10349,966.59467)"
+         style="fill:#eeeeee;fill-opacity:1;filter:url(#filter4947)"
+         inkscape:export-xdpi="104.2"
+         inkscape:export-ydpi="104.2">
+        <path
+           id="path4894"
+           style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:87.5px;font-family:'Hoefler Text';-inkscape-font-specification:'Hoefler Text Medium';fill:#eeeeee;fill-opacity:1"
+           d="m 108.2695,55.262097 c 0,-0.7 -0.2625,-1.05 -0.525,-1.05 -4.1125,0 -6.3,0.525 -10.237495,0.525 -4.725,0 -7.2625,-0.525 -11.4625,-0.525 -0.4375,0 -0.7875,0.6125 -0.7875,1.225 0,0.7 0.175,1.225 0.525,1.225 5.8625,0.35 7.6125,2.1 7.6125,4.725 0,1.1375 -0.35,2.3625 -0.7875,3.7625 l -15.75,38.762503 -15.225,-39.812503 c -0.525,-1.4875 -0.9625,-2.7125 -0.9625,-3.85 0,-2.1 1.6625,-3.325 7.2625,-3.5875 0.35,0 0.4375,-0.525 0.4375,-1.225 0,-0.6125 -0.35,-1.225 -0.7,-1.225 -5.775,0 -8.225,0.525 -13.65,0.525 -5.425,0 -6.7375,-0.525 -12.5125,-0.525 -0.35,0 -0.6125,0.6125 -0.6125,1.225 0,0.7 0.525,1.225 0.875,1.225 8.05,0.9625 8.8375,2.1 11.375,8.8375 l 19.5125,50.312503 c 0.175,0.35 0.6125,0.7 1.225,0.7 0.7,0 1.05,-0.2625 1.225,-0.7 l 21.9625,-50.312503 c 2.5375,-5.6875 3.849995,-8.225 10.237495,-8.8375 0.6125,0 0.9625,-0.7875 0.9625,-1.4 z"
+           inkscape:connector-curvature="0" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path4896"
+           style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:87.5px;font-family:'Hoefler Text';-inkscape-font-specification:'Hoefler Text Medium';fill:#eeeeee;fill-opacity:1"
+           d="m 135.74099,113.8871 c 0,-0.7 -0.2625,-1.1375 -0.7,-1.225 -8.4875,-0.9625 -9.0125,-2.8875 -9.0125,-7.35 l 0,-41.212503 c 0,-4.55 0.525,-6.3875 9.0125,-7.4375 0.4375,0 0.7,-0.525 0.7,-1.225 0,-0.6125 -0.2625,-1.225 -0.7,-1.225 -5.775,0 -8.05,0.525 -13.475,0.525 -5.425,0 -7.7,-0.525 -13.5625,-0.525 -0.35,0 -0.7,0.6125 -0.7,1.225 0,0.7 0.35,1.225 0.7,1.225 8.4875,1.05 9.1,2.8875 9.1,7.4375 l 0,41.212503 c 0,4.4625 -0.6125,6.3875 -9.1,7.35 -0.35,0.0875 -0.7,0.525 -0.7,1.225 0,0.7 0.35,1.225 0.7,1.225 5.8625,0 8.1375,-0.525 13.5625,-0.525 5.425,0 7.7,0.525 13.475,0.525 0.4375,0 0.7,-0.525 0.7,-1.225 z" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path4898"
+           style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:87.5px;font-family:'Hoefler Text';-inkscape-font-specification:'Hoefler Text Medium';fill:#eeeeee;fill-opacity:1"
+           d="m 183.00154,72.062097 c 0,-10.7625 -6.5625,-17.85 -21.9625,-17.85 -5.775,0 -8.05,0.525 -13.475,0.525 -5.425,0 -7.7875,-0.525 -13.5625,-0.525 -0.35,0 -0.7,0.6125 -0.7,1.225 0,0.7 0.35,1.225 0.7,1.225 8.4875,1.05 9.1,2.8875 9.1,7.4375 l 0,41.212503 c 0,4.4625 -0.6125,6.3875 -9.1,7.35 -0.35,0.0875 -0.7,0.525 -0.7,1.225 0,0.7 0.35,1.225 0.7,1.225 5.775,0 8.1375,-0.525 13.5625,-0.525 5.425,0 7.7,0.525 13.475,0.525 0.4375,0 0.7,-0.525 0.7,-1.225 0,-0.7 -0.2625,-1.1375 -0.7,-1.225 -8.4875,-0.9625 -9.0125,-2.8875 -9.0125,-7.35 l 0,-15.487503 c 3.0625,0.2625 5.8625,0.4375 10.325,0.4375 11.6375,0 20.65,-7.875 20.65,-18.2 z m -9.7125,0 c 0,8.3125 -4.2875,15.3125 -14.9625,15.3125 -4.725,0 -6.3,-0.4375 -6.3,-3.15 l 0,-20.125 c 0,-4.55 0.175,-6.9125 8.6625,-6.9125 6.3875,0 12.6,4.2875 12.6,14.875 z" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path4900"
+           style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:87.5px;font-family:'Hoefler Text';-inkscape-font-specification:'Hoefler Text Medium';fill:#eeeeee;fill-opacity:1"
+           d="m 239.14743,113.8871 c 0,-0.7 -0.2625,-1.1375 -0.6125,-1.225 -7.875,-0.875 -10.15,-3.675 -11.6375,-6.125 l -14.175,-21.525003 c 7.9625,-2.8 13.7375,-8.4 13.7375,-15.05 0,-9.625 -7,-15.75 -20.475,-15.75 -5.775,0 -8.1375,0.525 -13.5625,0.525 -5.425,0 -7.7,-0.525 -13.475,-0.525 -0.4375,0 -0.7,0.6125 -0.7,1.225 0,0.7 0.2625,1.225 0.7,1.225 8.4875,1.05 9.0125,2.8875 9.0125,7.4375 l 0,41.212503 c 0,4.4625 -0.525,6.3875 -9.0125,7.35 -0.4375,0.0875 -0.7,0.525 -0.7,1.225 0,0.7 0.2625,1.225 0.7,1.225 5.775,0 8.05,-0.525 13.475,-0.525 5.425,0 7.7875,0.525 13.5625,0.525 0.35,0 0.7,-0.525 0.7,-1.225 0,-0.7 -0.35,-1.1375 -0.7,-1.225 -8.4875,-0.9625 -9.1,-2.8875 -9.1,-7.35 l 0,-18.812503 c 6.65,0 7.525,0.7 11.4625,7 l 13.3,21.612503 c 2.5375,0 5.425,-0.525 7.9625,-0.525 1.925,0 5.8625,0.525 8.8375,0.525 0.4375,0 0.7,-0.525 0.7,-1.225 z m -22.3125,-43.925003 c 0,9.1875 -3.5875,14 -13.5625,14 -2.5375,0 -4.4625,-0.0875 -6.3875,-0.35 l 0,-19.5125 c 0,-4.55 0.175,-6.9125 8.75,-6.9125 8.4875,0 11.2,5.425 11.2,12.775 z" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path4902"
+           style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:87.5px;font-family:'Hoefler Text';-inkscape-font-specification:'Hoefler Text Medium';fill:#eeeeee;fill-opacity:1"
+           d="m 300.02185,113.8871 c 0,-0.7 -0.525,-1.1375 -0.875,-1.225 -7,-0.7875 -9.5375,-4.2875 -11.1125,-8.75 l -18.2,-50.312503 c -0.0875,-0.35 -0.525,-0.7 -1.225,-0.7 -0.6125,0 -1.05,0.2625 -1.225,0.7 l -20.475,50.312503 c -2.275,5.1625 -4.025,8.225 -10.15,8.75 -0.35,0.0875 -0.9625,0.525 -0.9625,1.225 -0.0875,0.7 0.175,1.225 0.6125,1.225 4.1125,0 5.95,-0.525 9.8,-0.525 4.8125,0 6.0375,0.525 11.2,0.525 0.35,0 0.7,-0.525 0.7,-1.225 0,-0.7 -0.175,-1.225 -0.525,-1.225 -5.5125,-0.2625 -7.0875,-1.8375 -7.0875,-4.2 0,-1.225 0.4375,-2.7125 0.9625,-4.2 l 4.9875,-13.562503 18.025,0 5.075,14.612503 c 0.4375,1.4 0.7875,2.625 0.7875,3.675 0,2.1875 -1.6625,3.4125 -7.35,3.675 -0.35,0 -0.525,0.525 -0.525,1.225 0,0.7 0.35,1.225 0.7,1.225 5.775,0 8.3125,-0.525 13.7375,-0.525 4.8125,0 6.65,0.525 12.425,0.525 0.35,0 0.7,-0.525 0.7,-1.225 z m -26.5125,-25.987503 -16.0125,0 8.3125,-22.4 7.7,22.4 z" />
+      </g>
+    </g>
   </g>
 </svg>
diff --git a/vipra-ui/app/index.html b/vipra-ui/app/index.html
index af9a4e37..8c976eac 100644
--- a/vipra-ui/app/index.html
+++ b/vipra-ui/app/index.html
@@ -9,11 +9,6 @@
   <!-- stylesheets -->
   <link href="css/vendor.css" rel="stylesheet">
   <link href="css/app.css" rel="stylesheet">
-  <!-- javascript -->
-  <script src="js/vendor.js"></script>
-  <script src="js/config.js"></script>
-  <script src="js/app.js"></script>
-  <script src="js/templates.js"></script>
 </head>
 <body ng-attr-class="{{state?state.name:''}}">
   <nav class="navbar navbar-default navbar-fixed-top">
@@ -140,5 +135,10 @@
       <polyline style="animation-delay:1.5s" class="logo-shape" points="0,0 100,75 100,120 0,0" fill="#0079a2" />
     </svg>
   </div>
+  <!-- javascript -->
+  <script src="js/vendor.js"></script>
+  <script src="js/config.js"></script>
+  <script src="js/app.js"></script>
+  <script src="js/templates.js"></script>
 </body>
 </html>
\ No newline at end of file
diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js
index 2985c762..6754e3f7 100644
--- a/vipra-ui/app/js/app.js
+++ b/vipra-ui/app/js/app.js
@@ -2,7 +2,7 @@
  * Vipra Application
  * Main application file
  ******************************************************************************/
-/* globals angular, $, Vipra */
+/* globals angular, $, Vipra, ReconnectingWebSocket */
 (function() {
 
   "use strict";
@@ -230,32 +230,23 @@
     $rootScope.showMessage = function(msg) {
       AlertFactory.showAlert(msg, {time:7000});
     };
+    
+    var socket = new ReconnectingWebSocket(Vipra.getWSURL(), null, {
+      reconnectInterval: 10000
+    });
 
-    window.vipraWS = function() {
-      var socket = new WebSocket(Vipra.config.websocketUrl);
-
-      socket.onmessage = function(event) {
-        try {
-          var data = JSON.parse(event.data);
-          $rootScope.$apply(function() {
-            $rootScope.handleWSMessage(data);
-          });
-        } catch(e) {
-          $rootScope.$apply(function() {
-            $rootScope.showMessage(event.data);
-          });
-        }
-      };
-
-      socket.onclose = function() {
-        setTimeout(function() {
-          window.vipraWS();
-        }, 5000);
-      };
+    socket.onmessage = function(event) {
+      try {
+        var data = JSON.parse(event.data);
+        $rootScope.$apply(function() {
+          $rootScope.handleWSMessage(data);
+        });
+      } catch(e) {
+        $rootScope.$apply(function() {
+          $rootScope.showMessage(event.data);
+        });
+      }
     };
-
-    window.vipraWS();
-
   }]);
 
   $(document).on('change', '.btn-file :file', function() {
diff --git a/vipra-ui/app/js/config.js b/vipra-ui/app/js/config.js
index 6a10054b..09dd5420 100644
--- a/vipra-ui/app/js/config.js
+++ b/vipra-ui/app/js/config.js
@@ -20,11 +20,6 @@
      *                      this is the restUrl
      */
     restUrl: '/rest',
-
-    /*
-     * Point this URL to the backend websocket. The default is /ws.
-     */
-    websocketUrl: '/ws'
   };
 
 })();
\ No newline at end of file
diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js
index 31325a87..349a0dc0 100644
--- a/vipra-ui/app/js/controllers.js
+++ b/vipra-ui/app/js/controllers.js
@@ -218,6 +218,8 @@
         }, function(data) {
           $scope.searching = false;
           $scope.searchResults = data;
+        }, function() {
+          $scope.searching = false;
         });
       };
 
@@ -292,9 +294,9 @@
 
       $scope.shown = {
         articles: true,
-        similararticles: true,
+        similararticles: false,
         topics: true,
-        words: true
+        words: false
       };
 
       var newNode = function(title, type, show, dbid, color, shape, loader) {
@@ -324,6 +326,9 @@
           },
           font: {
             color: '#000'
+          },
+          shadow: {
+            enabled: true
           }
         };
       };
@@ -350,7 +355,7 @@
       };
 
       var wordNode = function(word) {
-        return newNode(word.id, 'word', 'word.show', word.id, $scope.colors.words, 'box', $scope.loadWord);
+        return newNode(word.id, 'word', 'words.show', word.id, $scope.colors.words, 'box', $scope.loadWord);
       };
 
       var edgeExists = function(idA, idB) {
@@ -413,11 +418,18 @@
         }, 500);
       };
 
+      $scope.deselect = function() {
+        $scope.$apply(function() {
+          $scope.currentArticle = null;
+        });
+      };
+
       $scope.loadArticle = function(node) {
         ArticleFactory.get({
           id: node.dbid,
           fields: '_all'
         }, function(data) {
+          $scope.currentArticle = data;
           var i;
           if (data.topics) {
             for (i = 0; i < data.topics.length; i++)
@@ -436,7 +448,8 @@
       $scope.loadTopic = function(node) {
         if ($scope.shown.articles) {
           TopicFactory.articles({
-            id: node.dbid
+            id: node.dbid,
+            limit: 50
           }, function(data) {
             constructor(data, node, articleNode);
           });
@@ -494,6 +507,14 @@
         });
       };
 
+      $scope.completeNetwork = function() {
+        var nodes = $scope.nodes.get(),
+          updates = [];
+        for(var i = 0; i < nodes.length; i++)
+          nodes[i].loader(nodes[i]);
+        $scope.nodes.update(updates);
+      };
+
       $scope.$watch('rootModels.topicModel', function(newVal) {
         if ($scope.rootNode && $scope.rootNode.topicModel.id !== newVal.id)
           $state.transitionTo('index');
@@ -544,6 +565,7 @@
       var container = document.getElementById("visgraph");
       $scope.graph = new vis.Network(container, $scope.data, $scope.options);
       $scope.graph.on('selectNode', $scope.select);
+      $scope.graph.on('deselectNode', $scope.deselect);
       $scope.graph.on('doubleClick', $scope.open);
 
       if($stateParams.type) {
diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js
index a2989a5e..ea7f9b94 100644
--- a/vipra-ui/app/js/directives.js
+++ b/vipra-ui/app/js/directives.js
@@ -508,4 +508,34 @@
     };
   }]);
 
+  app.directive('articlePopover', ['ArticleFactory', function(ArticleFactory) {
+    return {
+      scope: {
+        id: '=',
+        article: '='
+      },
+      link: function($scope) {
+        $scope.$watch('id', function() {
+          if(!$scope.id) {
+            $scope.currentArticle = null;
+            return;
+          }
+
+          ArticleFactory.get({
+            id: $scope.id
+          }, function(data) {
+            $scope.currentArticle = data;
+            $scope.currentArticleDate = $scope.currentArticle ? Vipra.formatDate($scope.currentArticle.date) : null;
+          });
+        });
+
+        $scope.$watch('article', function() {
+          $scope.currentArticle = $scope.article;
+          $scope.currentArticleDate = $scope.article ? Vipra.formatDate($scope.currentArticle.date) : null;
+        });
+      },
+      templateUrl: 'html/directives/article-popover.html'
+    };
+  }]);
+
 })();
\ No newline at end of file
diff --git a/vipra-ui/app/js/helpers.js b/vipra-ui/app/js/helpers.js
index c3e02bbf..c8eba435 100644
--- a/vipra-ui/app/js/helpers.js
+++ b/vipra-ui/app/js/helpers.js
@@ -168,6 +168,19 @@
     }
   };
 
+  Vipra.getWSURL = function() {
+    if(Vipra.config.websocketUrl) return Vipra.config.websocketUrl;
+    var protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
+    var path = Vipra.config.restUrl.replace(/rest$/, 'ws');
+    if(Vipra.config.restUrl.indexOf('//') !== -1) {
+      // contains protocol
+      return path.replace(/^[^/]+\/\//, protocol + '//');
+    } else {
+      // contains no protocol
+      return protocol + location.hostname + path;
+    }
+  };
+
   /**
    * Polyfills
    */
diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less
index cb294ea3..abb14385 100644
--- a/vipra-ui/app/less/app.less
+++ b/vipra-ui/app/less/app.less
@@ -3,6 +3,7 @@
 @sidebar-padding: 5px;
 @sidebar-width: 250px;
 @bar-color: #f8f8f8;
+@graph-spacing: 5px;
 
 html {
   position: relative;
@@ -85,32 +86,51 @@ a:hover {
   bottom: 50px;
 }
 
-.graph-legend {
-  @spacing: 5px;
+.graph-item {
   position: absolute;
-  top: 10px;
-  left: 10px;
-  padding: @spacing*2;
+  padding: @graph-spacing*2;
   background: rgba(128, 128, 128, 0.1);
   border-radius: 5px;
-  &:not(.collapsed) {
+  &:hover {
+    background: rgb(242, 242, 242);
+  }
+  &.top-left {
+    top: 10px;
+    left: 10px;
+  }
+  &.bottom-left {
+    bottom: 10px;
+    left: 10px;
+  }
+  &.top-right {
+    top: 10px;
+    right: 10px;
+  }
+  &.bottom-right {
+    bottom: 10px;
+    right: 10px;
+  }
+  &.small:not(.collapsed) {
     width: 220px;
   }
+  &.large:not(.collapsed) {
+    width: 400px;
+  }
   label + label {
-    padding-left: @spacing;
+    padding-left: @graph-spacing;
   }
   label,
   .checkbox {
     margin: 0;
   }
   div + div {
-    margin-top: @spacing;
+    margin-top: @graph-spacing;
   }
   table,
   table button {
     width: 100%;
     td + td {
-      padding-left: @spacing;
+      padding-left: @graph-spacing;
     }
   }
   .collapser {
@@ -120,6 +140,10 @@ a:hover {
   }
 }
 
+.graph-popover {
+
+}
+
 .initial {
   float: left;
   font-size: 70px;
@@ -741,8 +765,10 @@ entity-menu {
     padding: 80px 0 30px 0;
   }
   .text-logo {
-    font-size: 50px;
+    font-size: 60px;
+    letter-spacing: -8px;
     color: #fff;
+    text-shadow: 0 0 5px rgba(255,255,255,0.5);
   }
 }
 
@@ -755,10 +781,13 @@ entity-menu {
 
 .index {
   .navbar-default {
-    border: 0;
-    background: rgba(255,255,255,0.6);
+    border-color: rgba(255,255,255,.2);
+    background: 0;
     .navbar-nav>li>a {
       color: #555;
+      &:hover {
+        background-color: rgba(255,255,255,.2);
+      }
     }
   }
 }
@@ -808,16 +837,23 @@ entity-menu {
   outline: none;
 }
 
-@-moz-keyframes spin {
-  100% {
-    -moz-transform: rotateY(360deg);
-  }
+.break-words {
+  word-break: break-all;
 }
 
-@-webkit-keyframes spin {
-  100% {
-    -webkit-transform: rotateY(360deg);
-  }
+.article-popover-text {
+  max-height: 300px;
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+.cfp-hotkeys-key {
+  width: 40px;
+}
+
+.screencast {
+  background: #eee;
+  margin: 10px auto;
 }
 
 @keyframes spin {
diff --git a/vipra-ui/bower.json b/vipra-ui/bower.json
index e29148b2..8c9f6794 100644
--- a/vipra-ui/bower.json
+++ b/vipra-ui/bower.json
@@ -33,6 +33,7 @@
     "randomcolor": "randomColor#^0.x",
     "bootbox.js": "bootbox#^4.x",
     "angular-hotkeys": "chieffancypants/angular-hotkeys#^1.x",
-    "eonasdan-bootstrap-datetimepicker": "^4.x"
+    "eonasdan-bootstrap-datetimepicker": "^4.x",
+    "reconnectingWebsocket": "joewalnes/reconnecting-websocket#^1.0.0"
   }
 }
diff --git a/vipra-ui/gulpfile.js b/vipra-ui/gulpfile.js
index 90043934..01da5326 100644
--- a/vipra-ui/gulpfile.js
+++ b/vipra-ui/gulpfile.js
@@ -27,7 +27,8 @@ var assets = {
     'bower_components/nya-bootstrap-select/dist/js/nya-bs-select.min.js',
     'bower_components/randomcolor/randomColor.js',
     'bower_components/bootbox.js/bootbox.js',
-    'bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js'
+    'bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js',
+    'bower_components/reconnectingWebsocket/reconnecting-websocket.min.js'
   ],
   css: [
     'bower_components/bootstrap/dist/css/bootstrap.min.css',
@@ -70,7 +71,7 @@ gulp.task('js', function() {
 });
 
 gulp.task('js-rel', function() {
-  gulp.src(['app/js/**/*.js', '!app/js/config.js'])
+  gulp.src(['app/js/**/*.js', '!app/js/config.js', '!app/js/dev.js'])
     .pipe(concat('app.js'))
     .pipe(ngannotate())
     .pipe(uglify())
diff --git a/vipra-ui/package.json b/vipra-ui/package.json
index 6a28e95a..7cccad3b 100644
--- a/vipra-ui/package.json
+++ b/vipra-ui/package.json
@@ -10,9 +10,7 @@
     "gulp-angular-templatecache": "^1.8.0",
     "gulp-clean-css": "^2.0.3",
     "gulp-concat": "^2.6.0",
-    "gulp-cssnano": "^2.1.0",
     "gulp-gzip": "^1.2.0",
-    "gulp-include": "^2.1.0",
     "gulp-less": "^3.0.5",
     "gulp-ng-annotate": "^2.0.0",
     "gulp-uglify": "^1.5.1",
diff --git a/vipra-util/src/main/java/de/vipra/util/FileUtils.java b/vipra-util/src/main/java/de/vipra/util/FileUtils.java
index 6a018fa4..c3395210 100644
--- a/vipra-util/src/main/java/de/vipra/util/FileUtils.java
+++ b/vipra-util/src/main/java/de/vipra/util/FileUtils.java
@@ -157,4 +157,14 @@ public class FileUtils extends org.apache.commons.io.FileUtils {
 		}).init(file);
 	}
 
+	public static File getTempFile(final String fileName) {
+		return new File(PathUtils.tempDir(), fileName);
+	}
+
+	public static File createTempFile(final String fileName) throws IOException {
+		final File file = getTempFile(fileName);
+		file.createNewFile();
+		return file;
+	}
+
 }
diff --git a/vipra-util/src/main/java/de/vipra/util/IPCMessageCode.java b/vipra-util/src/main/java/de/vipra/util/IPCMessageCode.java
index 59fb104e..811020be 100644
--- a/vipra-util/src/main/java/de/vipra/util/IPCMessageCode.java
+++ b/vipra-util/src/main/java/de/vipra/util/IPCMessageCode.java
@@ -3,10 +3,7 @@ package de.vipra.util;
 public enum IPCMessageCode {
 	TEST,
 	MESSAGE,
-	START_MODELING,
-	STOP_MODELING,
-	START_INDEXING,
-	STOP_INDEXING,
-	START_IMPORTING,
-	STOP_IMPORTING
+	MODELING,
+	INDEXING,
+	IMPORTING
 }
\ No newline at end of file
diff --git a/vipra-util/src/main/java/de/vipra/util/LockFile.java b/vipra-util/src/main/java/de/vipra/util/LockFile.java
index b68de7f6..68826b0f 100644
--- a/vipra-util/src/main/java/de/vipra/util/LockFile.java
+++ b/vipra-util/src/main/java/de/vipra/util/LockFile.java
@@ -7,7 +7,7 @@ import de.vipra.util.ex.LockFileException;
 
 public class LockFile {
 
-	private final File file = new File(PathUtils.tempDir(), "vipra.lock");
+	private final File file = FileUtils.getTempFile("vipra.lock");
 
 	public void lock() throws LockFileException {
 		if (isLocked())
diff --git a/vm/.gitignore b/vm/.gitignore
deleted file mode 100644
index dba9e7ff..00000000
--- a/vm/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-data/
-webapps/*
-webroot/*
\ No newline at end of file
diff --git a/vm/README.md b/vm/README.md
deleted file mode 100644
index 2a64d8d5..00000000
--- a/vm/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# VM
-
-This is the development vagrant vm base. It contains the vm bootstrap file.
\ No newline at end of file
diff --git a/vm/bootstrap.sh b/vm/bootstrap.sh
deleted file mode 100644
index 3f16a108..00000000
--- a/vm/bootstrap.sh
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/bin/sh
-
-# -----------------------------------------------------------------------------
-# upgrade system
-
-apt-get update
-apt-get upgrade -y
-
-# -----------------------------------------------------------------------------
-# install packages
-
-apt-get install git gdebi-core openjdk-8-jdk openjdk-8-jre mongodb tomcat8 -y
-
-# -----------------------------------------------------------------------------
-# setup tomcat
-
-# change user
-echo "TOMCAT8_USER=vagrant" >> /etc/default/tomcat8
-echo "TOMCAT8_GROUP=vagrant" >> /etc/default/tomcat8
-chown -R vagrant.vagrant /var/log/tomcat8 /var/cache/tomcat8 /var/lib/tomcat8/conf/*
-
-# webapp directory
-rm -rf /vagrant/vm/webapps
-mkdir /vagrant/vm/webapps
-ln -sf /vagrant/vm/webapps /var/lib/tomcat8/webapps
-
-systemctl enable tomcat8
-systemctl restart tomcat8
-
-# -----------------------------------------------------------------------------
-# install elasticsearch
-
-wget https://download.elasticsearch.org/elasticsearch/release/org/elasticsearch/distribution/deb/elasticsearch/2.1.1/elasticsearch-2.1.1.deb
-gdebi -n elasticsearch-2.1.1.deb
-rm elasticsearch-2.1.1.deb
-
-systemctl enable elasticsearch
-systemctl restart elasticsearch
-
-# -----------------------------------------------------------------------------
-# disable firewall
-
-ufw disable
-
-# -----------------------------------------------------------------------------
-# cleanup
-
-apt-get autoremove -y
-apt-get clean
-
-echo ""
-echo "--------------------------------------------------------------"
-echo "--- Provisioning complete."
-echo "--------------------------------------------------------------"
-echo ""
-
-exit 0
\ No newline at end of file
diff --git a/vm/webroot/.gitkeep b/vm/webroot/.gitkeep
deleted file mode 100644
index e69de29b..00000000
-- 
GitLab