diff --git a/vipra-cmd/runcfg/CMD.launch b/vipra-cmd/runcfg/CMD.launch
index d44936f9e02af0536d75e1e3a8bce5342cc2a200..b36db2e1f99f6ae77ceb1a8501b802a2a37c5e24 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="-R /home/eike/Downloads/vipra-1462648299922.zip"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-t"/>
 <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/option/BackupCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/BackupCommand.java
index d656552d4ba1bf2f84028f6ce61029c359ba7cea..57e6875873b2408ac3ca376863776f40de999d08 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/BackupCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/BackupCommand.java
@@ -3,9 +3,12 @@ package de.vipra.cmd.option;
 import java.io.File;
 import java.util.Date;
 
+import org.fusesource.jansi.Ansi;
+import org.fusesource.jansi.Ansi.Color;
 import org.zeroturnaround.zip.ZipUtil;
 
 import de.vipra.util.Config;
+import de.vipra.util.ConsoleUtils;
 import de.vipra.util.FileUtils;
 
 public class BackupCommand implements Command {
@@ -18,19 +21,43 @@ public class BackupCommand implements Command {
 
 	@Override
 	public void run() throws Exception {
-		final Config config = Config.getConfig();
-		final File tmpTarget = FileUtils.getTempFile("vipra-dump");
-		org.apache.commons.io.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();
-		org.apache.commons.io.FileUtils.copyDirectory(config.getDataDirectory(), new File(tmpTarget, "fb"));
-		org.apache.commons.io.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);
-		org.apache.commons.io.FileUtils.deleteDirectory(tmpTarget);
+		try {
+			final Config config = Config.getConfig();
+			final File tmpTarget = FileUtils.getTempFile("vipra-dump");
+			org.apache.commons.io.FileUtils.deleteDirectory(tmpTarget);
+
+			ConsoleUtils.infoNOLF(" backup database...");
+			final Process p = Runtime.getRuntime().exec("mongodump -d " + config.getDatabaseName() + " -h " + config.getDatabaseHost() + " --port "
+					+ config.getDatabasePort() + " -o " + new File(tmpTarget, "db"));
+			p.waitFor();
+			ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString());
+
+			ConsoleUtils.infoNOLF(" backup filebase...");
+			org.apache.commons.io.FileUtils.copyDirectory(config.getDataDirectory(), new File(tmpTarget, "fb"));
+			ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString());
+
+			ConsoleUtils.infoNOLF(" backup configuration...");
+			org.apache.commons.io.FileUtils.copyDirectory(Config.getGenericConfigDir(), new File(tmpTarget, "config"));
+			ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString());
+
+			ConsoleUtils.infoNOLF(" compressing...");
+			File target = new File(path);
+			if (target.exists() && target.isDirectory())
+				target = new File(target, "vipra-" + new Date().getTime() + ".zip");
+			ZipUtil.pack(tmpTarget, target);
+			org.apache.commons.io.FileUtils.deleteDirectory(tmpTarget);
+			ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString());
+
+			ConsoleUtils.info("completed: " + target.getAbsolutePath());
+		} catch (final Exception e) {
+			ConsoleUtils.print(Ansi.ansi().fg(Color.RED).a("FAILED").reset().toString());
+			if (e.getMessage().contains("mongodump")) {
+				ConsoleUtils.error("mongodump not installed");
+				return;
+			} else {
+				throw e;
+			}
+		}
 	}
 
 	@Override
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
index 857a35cd6fdbf323e959b7fb38ac9933a9ba9782..c1bc3024516bcc7c4c3d87290044d67644a0afd8 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/option/RestoreCommand.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/RestoreCommand.java
@@ -3,9 +3,12 @@ package de.vipra.cmd.option;
 import java.io.File;
 import java.io.FileNotFoundException;
 
+import org.fusesource.jansi.Ansi;
+import org.fusesource.jansi.Ansi.Color;
 import org.zeroturnaround.zip.ZipUtil;
 
 import de.vipra.util.Config;
+import de.vipra.util.ConsoleUtils;
 import de.vipra.util.FileUtils;
 
 public class RestoreCommand implements Command {
@@ -18,17 +21,36 @@ public class RestoreCommand implements Command {
 
 	@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");
-		ZipUtil.unpack(zip, tmpTarget);
-		final Config config = Config.getConfig();
-		final Process p = Runtime.getRuntime()
-				.exec("mongorestore --drop -h " + config.getDatabaseHost() + " --port " + config.getDatabasePort() + " " + new File(tmpTarget, "db"));
-		p.waitFor();
-		org.apache.commons.io.FileUtils.copyDirectory(new File(tmpTarget, "fb"), config.getDataDirectory());
-		org.apache.commons.io.FileUtils.copyDirectory(new File(tmpTarget, "config"), Config.getGenericConfigDir());
+		try {
+			final File zip = new File(path);
+			if (!zip.isFile())
+				throw new FileNotFoundException(path);
+			final File tmpTarget = FileUtils.getTempFile("vipra-dump");
+			ZipUtil.unpack(zip, tmpTarget);
+			final Config config = Config.getConfig();
+
+			ConsoleUtils.infoNOLF(" restore database...");
+			final Process p = Runtime.getRuntime().exec(
+					"mongorestore --drop -h " + config.getDatabaseHost() + " --port " + config.getDatabasePort() + " " + new File(tmpTarget, "db"));
+			p.waitFor();
+			ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString());
+
+			ConsoleUtils.infoNOLF(" restore filebase...");
+			org.apache.commons.io.FileUtils.copyDirectory(new File(tmpTarget, "fb"), config.getDataDirectory());
+			ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString());
+
+			ConsoleUtils.infoNOLF(" restore configuration...");
+			org.apache.commons.io.FileUtils.copyDirectory(new File(tmpTarget, "config"), Config.getGenericConfigDir());
+			ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString());
+		} catch (final Exception e) {
+			ConsoleUtils.print(Ansi.ansi().fg(Color.RED).a("FAILED").reset().toString());
+			if (e.getMessage().contains("mongorestore")) {
+				ConsoleUtils.error("mongorestore not installed");
+				return;
+			} else {
+				throw e;
+			}
+		}
 	}
 
 	@Override
diff --git a/vipra-ui/app/html/directives/article-link.html b/vipra-ui/app/html/directives/article-link.html
index 6573ef9fe67d929dd4b276ff98e338875d26fb4a..13791ba674658cd0ccff6086e0e9e9b0b96c2e0c 100644
--- a/vipra-ui/app/html/directives/article-link.html
+++ b/vipra-ui/app/html/directives/article-link.html
@@ -1,4 +1,4 @@
-<span>
+<div class="link-wrapper">
 	<a class="article-link" ui-sref="articles.show({id:article.id})">
 		<span ng-bind="article.title"></span>
 		<ng-transclude/>
@@ -8,4 +8,4 @@
   	<span class="badge" ng-bind="::article.topicsCount" ng-attr-title="{{::article.topicsCount}} topic(s)" ng-if="::showBadge"></span>
   </div>
   <div ng-bind="excerpt" ng-if="excerptShown" class="excerpt"></div>
-</span>
+</div>
diff --git a/vipra-ui/app/html/directives/entity-link.html b/vipra-ui/app/html/directives/entity-link.html
index 569cdb436328a4bb02514abebe883a6356121148..b0090b88d4327a846a0243059131b356f13ba7a0 100644
--- a/vipra-ui/app/html/directives/entity-link.html
+++ b/vipra-ui/app/html/directives/entity-link.html
@@ -1,7 +1,9 @@
-<span>
-	<entity-menu entity="entity" ng-if="::showMenu" />
-	<a class="entity-link" ui-sref="entities.show({id:entity.id})">
-		<span ng-bind="entity.id"></span>
-		<ng-transclude/>
-	</a>
-</span>
\ No newline at end of file
+<div class="link-wrapper">
+  <span class="ellipsis menu-padding">
+    <a class="entity-link" ui-sref="entities.show({id:entity.id})">
+      <span ng-bind="entity.id"></span>
+      <ng-transclude/>
+    </a>
+  </span>
+  <entity-menu class="menu-button" entity="entity" ng-if="::showMenu" />
+</div>
\ No newline at end of file
diff --git a/vipra-ui/app/html/directives/topic-link.html b/vipra-ui/app/html/directives/topic-link.html
index 411e1e14cf765c4deb571ba7f3d487ee0413c434..f4595e813bb01455f55d0f78355a9b38b808aaaf 100644
--- a/vipra-ui/app/html/directives/topic-link.html
+++ b/vipra-ui/app/html/directives/topic-link.html
@@ -1,10 +1,10 @@
-<span>
-  <topic-menu topic="topic" class="menu-button" ng-if="::showMenu" />
-  <span class="ellipsis">
+<div class="link-wrapper">
+  <span class="ellipsis menu-padding">
 	  <a class="topic-link" ui-sref="topics.show({id:topic.id})">
 	    <span ng-bind="topic.name"></span>
 	    <ng-transclude/>
 	  </a>
 	  <span class="badge pull-right" ng-bind="::topic.articlesCount" ng-attr-title="{{::topic.articlesCount}} article(s)" ng-if="::showBadge"></span>
 	</span>
-</span>
+  <topic-menu topic="topic" class="menu-button" ng-if="::showMenu" />
+</div>
diff --git a/vipra-ui/app/html/directives/word-link.html b/vipra-ui/app/html/directives/word-link.html
index 268cec13ec0ac7f7cb68b1c15e950b3e11410e3e..9e852ae3fa198f96e46c418f07a84981eb8495c1 100644
--- a/vipra-ui/app/html/directives/word-link.html
+++ b/vipra-ui/app/html/directives/word-link.html
@@ -1,6 +1,6 @@
-<span>
-  <word-menu class="menu-button" word="word" ng-if="::showMenu" />
-  <span class="ellipsis">
+<div class="link-wrapper">
+  <span class="ellipsis menu-padding">
   	<a ui-sref="words.show({id: word.id})" ng-bind="word.id"></a>
   </span>
-</span>
\ No newline at end of file
+  <word-menu class="menu-button" word="word" ng-if="::showMenu" />
+</div>
\ No newline at end of file
diff --git a/vipra-ui/app/index.html b/vipra-ui/app/index.html
index 175de2910553996dbe8d8c19822a4653987ccd5f..12e07f0b4e64f4514508a95ceec54bf5d81b38bb 100644
--- a/vipra-ui/app/index.html
+++ b/vipra-ui/app/index.html
@@ -66,6 +66,11 @@
               <i class="fa fa-question-circle"></i>
             </a>
           </li>
+          <li title="Feedback">
+            <a tabindex="0" ng-click="showFeedbackModal()">
+              <i class="fa fa-comment-o"></i>
+            </a>
+          </li>
         </ul>
       </div>
     </div>
@@ -116,9 +121,20 @@
       </div>
     </div>
   </div>
-  <div class="alerts">
-    <bs-alert ng-model="alert" type="alert.type" ng-repeat="alert in alerts"/>
+  <div id="feedbackModal" class="modal" tabindex="-1" role="dialog" data-backdrop="static">
+    <div class="modal-dialog modal-lg">
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-label="Close" ng-show="rootModels.topicModel" ng-cloak><span aria-hidden="true">&times;</span></button>
+          <h4 class="modal-title">Evaluation</h4>
+        </div>
+        <div class="modal-body">
+          <iframe id="feedbackIframe" frameborder="0" marginheight="0" marginwidth="0">Wird geladen...</iframe>
+        </div>
+      </div>
+    </div>
   </div>
+  <div class="alerts"></div>
   <div class="overlay-message" ng-show="fatal">
     <div class="message" ng-bind="fatal"></div>
   </div>
@@ -135,5 +151,13 @@
   <script src="js/config.js"></script>
   <script src="js/app.js"></script>
   <script src="js/templates.js"></script>
+  <script>
+    /* jshint ignore:start */
+    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+    ga('create', 'UA-77619702-1', 'auto');
+  </script>
 </body>
 </html>
\ No newline at end of file
diff --git a/vipra-ui/app/js/alerter.js b/vipra-ui/app/js/alerter.js
new file mode 100644
index 0000000000000000000000000000000000000000..6e9c1e589f2e6eb379729a69d09c6b1f848c1e60
--- /dev/null
+++ b/vipra-ui/app/js/alerter.js
@@ -0,0 +1,311 @@
+/******************************************************************************
+ * Vipra Application
+ * Alerter library
+ ******************************************************************************/
+/* globals $, Alerter */
+;(function(g) {
+  'use strict';
+
+  if(g.Alerter) return;
+
+  g.Alerter = function(area, create, classes, style) {
+    this.area = area || 'alerts';
+    this.alerts = [];
+    this.queue = [];
+    this.settings = $.extend({}, Alerter.defaultSettings);
+    this.alertDefaults = $.extend({}, Alerter.alertDefaults);
+    if(create)
+      this.createAlertArea(classes, style);
+    Alerter.instances.push(this);
+  };
+
+  Alerter.instances = [];
+
+  Alerter.defaultSettings = {
+    prepend: true,
+    maxAlerts: 0,
+    queue: true,
+    merge: false
+  };
+
+  Alerter.alertDefaults = {
+    dismissible: true,
+    fade: true,
+    fadeTime: 300,
+    fadeAfter: 5000
+  };
+
+  Alerter.prototype.setSettings = function(s, opts) {
+    if(s)
+      this.settings = $.extend({}, this.settings, s);
+    if(opts)
+      this.setAlertDefaults(opts);
+    return this;
+  };
+
+  Alerter.prototype.getSettings = function() {
+    return this.settings;
+  };
+
+  Alerter.prototype.setAlertDefaults = function(opts) {
+    this.alertDefaults = $.extend({}, this.alertDefaults, opts);
+    return this;
+  };
+
+  Alerter.prototype.showAlert = function(type, title, msg, opts) {
+    return this.showAlerts(type, [{title: title, msg: msg}], opts);
+  };
+
+  Alerter.prototype.showAlerts = function(type, body, opts) {
+    opts = $.extend({}, this.alertDefaults, opts);
+
+    if(this.settings.merge && this.mergeAlerts(type, body, opts)) {
+      return;
+    }
+
+    var id = '_' + Math.random().toString(36).substr(2, 9),
+      c = {
+        instance: this,
+        id: id,
+        type: type,
+        opts: opts,
+        time: new Date().getTime(),
+        body: body
+      };
+
+    if(this.settings.maxAlerts > 0 && Object.keys(this.alerts).length >= this.settings.maxAlerts) {
+      if(this.settings.queue) {
+        this.queue.push(c);
+        return;
+      } else {
+        this.removeOldestAlert();
+      }
+    }
+
+    var html = this.createAlert(c),
+      el = c.el = this.settings.prepend ? $(html).prependTo('.' + this.area) : $(html).appendTo('.' + this.area);
+
+    this.alerts[id] = c;
+
+    if(opts.fade) {
+      el.hide().fadeIn(opts.fadeTime, function() {
+        this.restartAlert(id);
+      }.bind(this));
+    } else {
+      this.restartAlert(id);
+    }
+
+    return c;
+  };
+
+  Alerter.prototype.showSuccess = function(title, msg, opts) {
+    return this.showAlert('success', title, msg, opts);
+  };
+
+  Alerter.prototype.showInfo = function(title, msg, opts) {
+    return this.showAlert('info', title, msg, opts);
+  };
+
+  Alerter.prototype.showWarning = function(title, msg, opts) {
+    return this.showAlert('warning', title, msg, opts);
+  };
+
+  Alerter.prototype.showDanger = function(title, msg, opts) {
+    return this.showAlert('danger', title, msg, opts);
+  };
+
+  Alerter.prototype.showPrimary = function(title, msg, opts) {
+    return this.showAlert('primary', title, msg, opts);
+  };
+
+  Alerter.prototype.unqueueAlert = function() {
+    if(this.queue.length) {
+      var c = this.queue.shift();
+      this.showAlerts(c.type, c.body, c.opts);
+    }
+    return this;
+  };
+
+  Alerter.prototype.mergeAlert = function(type, title, msg, opts) {
+    this.mergeAlerts(type, [{title: title, msg: msg}], opts);
+    return this;
+  };
+
+  Alerter.prototype.mergeAlerts = function(type, body, opts) {
+    for(var id in this.alerts) {
+      if(this.alerts[id].type === type) {
+        return this.editAlerts(id, body, opts, true);
+      }
+    }
+    if(this.settings.queue) {
+      for(var i = 0; i < this.queue.length; i++) {
+        if(this.queue[i].type === type) {
+          return this.editAlerts(this.queue[i].id, body, opts, false);
+        }
+      }
+    }
+    return false;
+  };
+
+  Alerter.prototype.getAlert = function(id) {
+    var c = this.alerts[id];
+    if(c) return c;
+    if(this.settings.queue) {
+      for(var i = 0; i < this.queue.length; i++) {
+        if(this.queue[i].id === id) {
+          return this.queue[i];
+        }
+      }
+    }
+    return null;
+  };
+
+  Alerter.prototype.editAlert = function(id, title, msg, opts, restart) {
+    this.editAlerts(id, [{title: title, msg: msg}], restart);
+    return this;
+  };
+
+  Alerter.prototype.editAlerts = function(id, body, opts, restart) {
+    var c = this.getAlert(id);
+    if(c) {
+      c.body.push.apply(c.body, body);
+      if(opts) {
+        for(var key in opts) {
+          if(key !== 'body') {
+            c[key] = opts[key];
+          }
+        }
+      }
+      if(c.el) {
+        var elBody = c.el.find('.alert-body');
+        for(var i = 0; i < body.length; i++) {
+          elBody.append(this.createAlertBody(body[i].title, body[i].msg));
+        }
+      }
+      if(restart) {
+        this.restartAlert(id);
+      }
+      return true;
+    }
+    return false;
+  };
+
+  Alerter.prototype.restartAlert = function(id) {
+    var c = this.getAlert(id);
+    if(c) {
+      clearTimeout(c.timeout);
+      if(c.opts.fadeAfter && c.opts.fadeAfter > 0) {
+        c.timeout = setTimeout(function() {
+          this.removeAlert(id);
+        }.bind(this), c.opts.fadeAfter);
+      }
+    }
+    return this;
+  };
+
+  Alerter.prototype.removeAlert = function(id) {
+    var c = this.getAlert(id);
+    if(c) {
+      if(c.opts.fade) {
+        c.el.fadeOut(c.opts.fadeTime, function() {
+          $(this).remove();
+          delete this.alerts[id];
+          this.unqueueAlert();
+        }.bind(this));
+      } else {
+        c.el.remove();
+        delete this.alerts[id];
+        this.unqueueAlert();
+      }
+    }
+    return this;
+  };
+
+  Alerter.prototype.removeAlerts = function() {
+    this.queue = [];
+    for(var id in this.alerts)
+      this.removeAlert(id);
+    return this;
+  };
+
+  Alerter.prototype.getOldestAlert = function() {
+    var oldest = null;
+    for(var id in this.alerts) {
+      if(!oldest || this.alerts[id].time < oldest.time)
+        oldest = this.alerts[id];
+    }
+    return oldest;
+  };
+
+  Alerter.prototype.removeOldestAlert = function() {
+    var oldest = this.getOldestAlert();
+    if(oldest) {
+      this.removeAlert(oldest.id);
+    }
+    return this;
+  };
+
+  Alerter.prototype.createAlert = function(c) {
+    var msgs = "";
+    for(var i = 0; i < c.body.length; i++)
+      msgs += this.createAlertBody(c.body[i].title, c.body[i].msg);
+    return '<div id="' + c.id + '" class="alert alert-' + c.type + ' ' + (c.opts.dismissible ? 'alert-dismissible' : '') +
+      '" role="alert">' + (c.opts.dismissible ? '<button onclick="Alerter.removeAlertById(\'' + 
+      c.id + '\')" type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' : '') +
+      '<div class="alert-body">' + msgs + '</div></div>';
+  };
+
+  Alerter.prototype.createAlertBody = function(title, msg) {
+    return '<div><strong>' + (title ? title : '') + '</strong> ' + msg + '</div>';
+  };
+
+  Alerter.prototype.createAlertArea = function(classes, style) {
+    $('<div class="' + this.area + ' ' + classes + '" style="' + style + '"></div>').appendTo('body');
+    return this;
+  };
+
+  /**
+   * Singleton
+   */
+
+  var _A = new Alerter('alerts');
+  Alerter.instances.push(_A);
+
+  Alerter.setSettings = _A.setSettings.bind(_A);
+  Alerter.getSettings = _A.getSettings.bind(_A);
+  Alerter.setAlertDefaults = _A.setAlertDefaults.bind(_A);
+  Alerter.showAlert = _A.showAlert.bind(_A);
+  Alerter.showAlerts = _A.showAlerts.bind(_A);
+  Alerter.showSuccess = _A.showSuccess.bind(_A);
+  Alerter.showInfo = _A.showInfo.bind(_A);
+  Alerter.showWarning = _A.showWarning.bind(_A);
+  Alerter.showDanger = _A.showDanger.bind(_A);
+  Alerter.showPrimary = _A.showPrimary.bind(_A);
+  Alerter.unqueueAlert = _A.unqueueAlert.bind(_A);
+  Alerter.mergeAlert = _A.mergeAlert.bind(_A);
+  Alerter.mergeAlerts = _A.mergeAlerts.bind(_A);
+  Alerter.getAlert = _A.getAlert.bind(_A);
+  Alerter.editAlert = _A.editAlert.bind(_A);
+  Alerter.editAlerts = _A.editAlerts.bind(_A);
+  Alerter.restartAlert = _A.restartAlert.bind(_A);
+  Alerter.removeAlert = _A.removeAlert.bind(_A);
+  Alerter.removeAlerts = _A.removeAlerts.bind(_A);
+  Alerter.getOldestAlert = _A.getOldestAlert.bind(_A);
+  Alerter.removeOldestAlert = _A.removeOldestAlert.bind(_A);
+  Alerter.createAlert = _A.createAlert.bind(_A);
+  Alerter.createAlertBody = _A.createAlertBody.bind(_A);
+  Alerter.createAlertArea = _A.createAlertArea.bind(_A);
+
+  Alerter.removeAlertById = function(id) {
+    for(var i = 0; i < Alerter.instances.length; i++) {
+      Alerter.instances[i].removeAlert(id);
+    }
+  };
+
+  Alerter.removeAllAlerts = function() {
+    for(var i = 0; i < Alerter.instances.length; i++) {
+      Alerter.instances[i].removeAlerts();
+    }
+  };
+
+})(this);
\ No newline at end of file
diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js
index 6754e3f7ddf41904aff5718b64686304201b36ef..55301b93cbe6c9a77f5cc91fdd68a7e5e9151583 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, ReconnectingWebSocket */
+/* globals angular, $, Vipra, ReconnectingWebSocket, Alerter */
 (function() {
 
   "use strict";
@@ -14,6 +14,8 @@
     'ui.router',
     'cfp.hotkeys',
     'nya.bootstrap.select',
+    'angulartics',
+    'angulartics.google.analytics',
     'vipra.controllers',
     'vipra.directives',
     'vipra.factories',
@@ -182,14 +184,11 @@
             } else if (rejection.data) {
               if (angular.isArray(rejection.data)) {
                 for (var i = 0; i < rejection.data.length; i++) {
-                  $rootScope.alerts.push(angular.extend({
-                    type: 'danger'
-                  }, rejection.data[i]));
+                  var rd = rejection.data[i];
+                  Alerter.showDanger(rd.title, rd.detail);
                 }
               } else {
-                $rootScope.alerts.push(angular.extend({
-                  type: 'danger'
-                }, rejection.data));
+                Alerter.showDanger(rejection.data.title, rejection.data.detail);
               }
             }
             return $q.reject(rejection);
@@ -197,10 +196,17 @@
         };
       });
 
+      Alerter.setSettings({
+        prepend: false,
+        merge: true
+      }, {
+        fadeAfter: 0
+      });
+
     }
   ]);
 
-  app.run(['$rootScope', '$state', 'AlertFactory', function($rootScope, $state, AlertFactory) {
+  app.run(['$rootScope', '$state', function($rootScope, $state) {
 
     $rootScope.loading = {};
 
@@ -217,6 +223,7 @@
     $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState) {
       $rootScope.oldState = fromState;
       $rootScope.state = toState;
+      Alerter.removeAlerts();
     });
 
     $rootScope.$on('$stateChangeStart', function() {
@@ -224,11 +231,11 @@
     });
 
     $rootScope.handleWSMessage = function(data) {
-      AlertFactory.showAlert(data.message, {time:7000});
+      Alerter.showInfo(null, data.message, {fadeAfter: 7000});
     };
 
     $rootScope.showMessage = function(msg) {
-      AlertFactory.showAlert(msg, {time:7000});
+      Alerter.showInfo(null, msg, {fadeAfter: 7000});
     };
     
     var socket = new ReconnectingWebSocket(Vipra.getWSURL(), null, {
diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js
index 0ea47e94957666a4de36fe6e3f9d1790457f0a43..ea0a1d647815b812ea5ae6e565bebc90d3e19ed1 100644
--- a/vipra-ui/app/js/controllers.js
+++ b/vipra-ui/app/js/controllers.js
@@ -62,6 +62,11 @@
         localStorage.tm = topicModel.id;
       };
 
+      $scope.showFeedbackModal = function() {
+        $('#feedbackModal').modal();
+        $('#feedbackIframe').attr('src', 'https://docs.google.com/forms/d/1RjXyGgw8F3v7QsfgOQKz0Koa2Dkr1wDNx3veRZr9I3o/viewform?embedded=true');
+      };
+
       $scope.menubarSearch = function(query) {
         $state.transitionTo('index', {
           q: query
@@ -281,15 +286,9 @@
           randomSeed: 1
         },
         physics: {
-          maxVelocity: 70,
           barnesHut: {
-            centralGravity: 0.5,
-            springConstant: 0.01,
-            gravitationalConstant: -7000,
-            avoidOverlap: 0.2
-          },
-          stabilization: {
-            fit: false
+            springConstant: 0.008,
+            gravitationalConstant: -3500
           }
         },
         interaction: {
@@ -1585,7 +1584,7 @@
       $scope.calculatePages();
 
       $scope.changePage = function(page) {
-        $scope.page = page;
+        $scope.page = parseInt(page, 10);
       };
 
       $scope.toPage = function() {
diff --git a/vipra-ui/app/js/factories.js b/vipra-ui/app/js/factories.js
index 430f6af6acfa1ee836f697e63d7f83f17dab5943..49c0546dfed7b99b76585eaadc8bad979e37b5df 100644
--- a/vipra-ui/app/js/factories.js
+++ b/vipra-ui/app/js/factories.js
@@ -88,35 +88,4 @@
     return $myResource(Vipra.config.restUrl + '/windows/:id');
   }]);
 
-  app.factory('AlertFactory', [function() {
-    var alerts = $("#alerts");
-    if(alerts.length === 0) {
-      alerts = $('<div id="alerts" class="alerts"></div>').appendTo('body');
-    }
-
-    function showAlert(msg, config) {
-      config = angular.merge({}, {
-        type: 'info',
-        time: 0,
-        dismissible: true
-      }, config);
-      var classes = 'alert alert-' + config.type;
-      if(config.dismissible) {
-        classes += ' alert-dismissible';
-      }
-      var alert = $('<div class="' + classes + '" role="alert" style="display:none">' +  (config.dismissible ? 
-        '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' : '') + 
-        msg + '</div>').appendTo(alerts).fadeIn();
-      if(config.time > 0) {
-        setTimeout(function() {
-          alert.fadeOut(300, function() { $(this).remove(); });
-        }, config.time);
-      }
-    }
-
-    return {
-      showAlert: showAlert
-    };
-  }]);
-
 })();
\ No newline at end of file
diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less
index 525ddd12b3219419577a353745b85d89e9a99d41..7927ebaf59bfb86c9ac3db59f0b719e6f8feed8d 100644
--- a/vipra-ui/app/less/app.less
+++ b/vipra-ui/app/less/app.less
@@ -631,11 +631,16 @@ entity-menu {
   cursor: text !important;
 }
 
+.link-wrapper {
+  position: relative;
+}
+
 .menu-button {
   position: absolute;
+  top: 0;
 }
 
-.menu-button + * {
+.menu-padding {
   padding-left: 15px;
 }
 
@@ -862,6 +867,31 @@ entity-menu {
   margin: 10px auto;
 }
 
+#feedbackModal {
+  .modal-dialog {
+    height: 100%;
+    margin-top: 0;
+    margin-bottom: 0;
+    padding-top: 30px;
+    padding-bottom: 30px;
+  }
+  .modal-content {
+    height: 100%;
+  }
+  .modal-body {
+    position: absolute;
+    top: 56px;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    padding: 0;
+  }
+  iframe {
+    width: 100%;
+    height: 100%;
+  }
+}
+
 @keyframes spin {
   100% {
     -webkit-transform: rotateY(360deg);
diff --git a/vipra-ui/bower.json b/vipra-ui/bower.json
index 8c9f679495ef4808a7334a6cdaabac02eddf1bbe..42042c28c8c789ac235ed23d8352d766df5da2df 100644
--- a/vipra-ui/bower.json
+++ b/vipra-ui/bower.json
@@ -34,6 +34,7 @@
     "bootbox.js": "bootbox#^4.x",
     "angular-hotkeys": "chieffancypants/angular-hotkeys#^1.x",
     "eonasdan-bootstrap-datetimepicker": "^4.x",
-    "reconnectingWebsocket": "joewalnes/reconnecting-websocket#^1.0.0"
+    "reconnectingWebsocket": "joewalnes/reconnecting-websocket#^1.0.0",
+    "angulartics-google-analytics": "^0.1.4"
   }
 }
diff --git a/vipra-ui/gulpfile.js b/vipra-ui/gulpfile.js
index 01da53265233c67e0b2a18762ce9a7a3b110160a..28223ec704fb022461bd78ba4cf1ce07d1d7139b 100644
--- a/vipra-ui/gulpfile.js
+++ b/vipra-ui/gulpfile.js
@@ -28,7 +28,9 @@ var assets = {
     'bower_components/randomcolor/randomColor.js',
     'bower_components/bootbox.js/bootbox.js',
     'bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js',
-    'bower_components/reconnectingWebsocket/reconnecting-websocket.min.js'
+    'bower_components/reconnectingWebsocket/reconnecting-websocket.min.js',
+    'bower_components/angulartics/dist/angulartics.min.js',
+    'bower_components/angulartics-google-analytics/dist/angulartics-google-analytics.min.js'
   ],
   css: [
     'bower_components/bootstrap/dist/css/bootstrap.min.css',
diff --git a/vipra-util/src/main/java/de/vipra/util/Mongo.java b/vipra-util/src/main/java/de/vipra/util/Mongo.java
index cc0c78643769256fd4c7fe4e2994df8c548d0a89..93ee04414396672cb3a711213a53c72b62f46ff5 100644
--- a/vipra-util/src/main/java/de/vipra/util/Mongo.java
+++ b/vipra-util/src/main/java/de/vipra/util/Mongo.java
@@ -41,8 +41,11 @@ public class Mongo {
 		client = new MongoClient(host + ":" + port, options);
 
 		morphia = new Morphia();
-		morphia.mapPackage("de.vipra.util.model");
 		datastore = morphia.createDatastore(client, databaseName);
+
+		morphia.mapPackage("de.vipra.util.model");
+		datastore.ensureIndexes();
+		datastore.ensureCaps();
 	}
 
 	public MongoClient getClient() {
diff --git a/vipra-util/src/main/java/de/vipra/util/model/Article.java b/vipra-util/src/main/java/de/vipra/util/model/Article.java
index b2c16747384b43ca7b885f97604be30c5ce7494e..3f47d2fbe31431659fec38966f559461ed6e88f5 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/Article.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/Article.java
@@ -13,7 +13,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 @JsonIgnoreProperties(ignoreUnknown = true)
 @SuppressWarnings("serial")
 @Entity(value = "articles", noClassnameStored = true)
-@Indexes({ @Index("title"), @Index("date"), @Index("-created") })
+@Indexes({ @Index("title"), @Index("-title"), @Index("topicsCount"), @Index("-topicsCount") })
 public class Article implements Model<ObjectId>, Serializable {
 
 	@Id
diff --git a/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java b/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java
index a4bf05c0ce1aa10a927616d10f2305eb03809045..38d202dec42a3e5bd2d2cd5053c8c614224895ed 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java
@@ -32,7 +32,8 @@ import de.vipra.util.an.QueryIgnore;
 @JsonIgnoreProperties(ignoreUnknown = true)
 @SuppressWarnings("serial")
 @Entity(value = "articles", noClassnameStored = true)
-@Indexes({ @Index("title"), @Index("date"), @Index("-created") })
+@Indexes({ @Index("title"), @Index("-title"), @Index("date"), @Index("-date"), @Index("topicsCount"), @Index("-topicsCount"), @Index("created"),
+		@Index("-created"), @Index("modified"), @Index("-modified") })
 public class ArticleFull implements Model<ObjectId>, Serializable {
 
 	public static final Logger log = LoggerFactory.getLogger(ArticleFull.class);
diff --git a/vipra-util/src/main/java/de/vipra/util/model/Sequence.java b/vipra-util/src/main/java/de/vipra/util/model/Sequence.java
index f83d0c66916041fd64d18163a4f970ce22d1dfa3..46f61d39fd0af184470c1af6f2f522a87ce0bfe5 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/Sequence.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/Sequence.java
@@ -6,12 +6,15 @@ import org.bson.types.ObjectId;
 import org.mongodb.morphia.annotations.Embedded;
 import org.mongodb.morphia.annotations.Entity;
 import org.mongodb.morphia.annotations.Id;
+import org.mongodb.morphia.annotations.Index;
+import org.mongodb.morphia.annotations.Indexes;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
 @SuppressWarnings("serial")
 @Entity(value = "sequences", noClassnameStored = true)
+@Indexes({ @Index("relevance"), @Index("-relevance"), @Index("relevanceChange"), @Index("-relevanceChange") })
 public class Sequence implements Model<ObjectId>, Comparable<Sequence>, Serializable {
 
 	@Id
diff --git a/vipra-util/src/main/java/de/vipra/util/model/SequenceFull.java b/vipra-util/src/main/java/de/vipra/util/model/SequenceFull.java
index dbdfd71a4011077e8c8ccd899b58d416802609de..9af2291bd4566caa8486f7f705877e28dce71cdb 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/SequenceFull.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/SequenceFull.java
@@ -8,6 +8,8 @@ import org.bson.types.ObjectId;
 import org.mongodb.morphia.annotations.Embedded;
 import org.mongodb.morphia.annotations.Entity;
 import org.mongodb.morphia.annotations.Id;
+import org.mongodb.morphia.annotations.Index;
+import org.mongodb.morphia.annotations.Indexes;
 import org.mongodb.morphia.annotations.PrePersist;
 import org.mongodb.morphia.annotations.Reference;
 
@@ -18,6 +20,8 @@ import de.vipra.util.an.QueryIgnore;
 @JsonIgnoreProperties(ignoreUnknown = true)
 @SuppressWarnings("serial")
 @Entity(value = "sequences", noClassnameStored = true)
+@Indexes({ @Index("relevance"), @Index("-relevance"), @Index("relevanceChange"), @Index("-relevanceChange"), @Index("created"), @Index("-created"),
+		@Index("modified"), @Index("-modified") })
 public class SequenceFull implements Model<ObjectId>, Comparable<SequenceFull>, Serializable {
 
 	@Id
diff --git a/vipra-util/src/main/java/de/vipra/util/model/TextEntity.java b/vipra-util/src/main/java/de/vipra/util/model/TextEntity.java
index e5423b2969afcc52db6304a17baa2dd27c855c78..e8313bc25afcb29b17626ae173def6cc59d26b25 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/TextEntity.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/TextEntity.java
@@ -6,12 +6,15 @@ import java.util.List;
 
 import org.mongodb.morphia.annotations.Entity;
 import org.mongodb.morphia.annotations.Id;
+import org.mongodb.morphia.annotations.Index;
+import org.mongodb.morphia.annotations.Indexes;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
 @SuppressWarnings("serial")
 @Entity(value = "textentities", noClassnameStored = true)
+@Indexes({ @Index("url"), @Index("-url") })
 public class TextEntity implements Model<String>, Serializable {
 
 	@Id
diff --git a/vipra-util/src/main/java/de/vipra/util/model/TextEntityFull.java b/vipra-util/src/main/java/de/vipra/util/model/TextEntityFull.java
index 2058565eed7f57fd44259bcf3be60dad50b275a6..44e6484ffef899328db596ded9122b6c31f41c02 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/TextEntityFull.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/TextEntityFull.java
@@ -6,6 +6,8 @@ import java.util.List;
 
 import org.mongodb.morphia.annotations.Entity;
 import org.mongodb.morphia.annotations.Id;
+import org.mongodb.morphia.annotations.Index;
+import org.mongodb.morphia.annotations.Indexes;
 import org.mongodb.morphia.annotations.PrePersist;
 import org.mongodb.morphia.annotations.Reference;
 
@@ -13,6 +15,7 @@ import de.vipra.util.an.QueryIgnore;
 
 @SuppressWarnings("serial")
 @Entity(value = "textentities", noClassnameStored = true)
+@Indexes({ @Index("url"), @Index("-url"), @Index("created"), @Index("-created"), @Index("modified"), @Index("-modified") })
 public class TextEntityFull implements Model<String>, Serializable {
 
 	@Id
diff --git a/vipra-util/src/main/java/de/vipra/util/model/Topic.java b/vipra-util/src/main/java/de/vipra/util/model/Topic.java
index a52d6ca6fbd799f5139d52d4f08426b370c83814..ef9696e175b1e6f0f96924ad2abdd352f380221c 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/Topic.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/Topic.java
@@ -15,7 +15,7 @@ import de.vipra.util.MongoUtils;
 @JsonIgnoreProperties(ignoreUnknown = true)
 @SuppressWarnings("serial")
 @Entity(value = "topics", noClassnameStored = true)
-@Indexes({ @Index("name"), @Index("-created") })
+@Indexes({ @Index("name"), @Index("-name"), @Index("articlesCount"), @Index("-articlesCount") })
 public class Topic implements Model<ObjectId>, Serializable {
 
 	@Id
diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java
index 5947d68087714e087ccc46dca28fc46927103f1c..77fec9e13684e501dffd96675a36fb80e17a4398 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java
@@ -23,7 +23,8 @@ import de.vipra.util.an.QueryIgnore;
 @JsonIgnoreProperties(ignoreUnknown = true)
 @SuppressWarnings("serial")
 @Entity(value = "topics", noClassnameStored = true)
-@Indexes({ @Index("name"), @Index("-created") })
+@Indexes({ @Index("name"), @Index("-name"), @Index("articlesCount"), @Index("-articlesCount"), @Index("created"), @Index("-created"),
+		@Index("modified"), @Index("-modified") })
 public class TopicFull implements Model<ObjectId>, Serializable {
 
 	@Id