diff --git a/build.sh b/build.sh index fe85c1ddfc7b986f4c3361ca3d0e5475a3585d58..110b7c5f3e6444753fea2ff1548ca14f6f617399 100755 --- a/build.sh +++ b/build.sh @@ -132,6 +132,7 @@ else echo "compiling vipra-backend" | tee -a $LOG echo "-------------------------------" >> $LOG $MVN -f ./vipra-backend/pom.xml package >> $LOG 2>&1 + $MVN -f ./vipra-backend/pom.xml compile war:exploded >> $LOG 2>&1 if [ $? -ne 0 ]; then echo "error" exit 1 diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..b9c0fbfab68a424d85b60cb6bc158efa33fadf9b --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,20 @@ +FROM ubuntu:15.10 +MAINTAINER Eike Cochu <eike@cochu.com> +ENV ES_VERSION 2.3.1 +ENV TOMCAT_MAJOR 8 +ENV TOMCAT_VERSION 8.0.33 +RUN apt-get update && \ + apt-get install -y wget nano openjdk-8-jdk mongodb supervisor && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ + mkdir -p /var/log/supervisor /data/db && \ + wget --no-check-certificate -O- https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/$ES_VERSION/elasticsearch-$ES_VERSION.tar.gz | tar xvfz - && \ + mv elasticsearch-$ES_VERSION elasticsearch && \ + wget -O- http://apache.openmirror.de/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz | tar xvfz - && \ + mv apache-tomcat-$TOMCAT_VERSION tomcat +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY tomcat-users.xml /tomcat/conf/tomcat-users.xml +COPY elasticsearch.yml /elasticsearch/config/elasticsearch.yml +COPY vipra.war /tomcat/webapps/vipra.war +EXPOSE 8080 9200 9300 27017 +CMD ["/usr/bin/supervisord"] \ No newline at end of file diff --git a/docker/docker-build.sh b/docker/docker-build.sh new file mode 100755 index 0000000000000000000000000000000000000000..fd1787c89ee9d3fee67ecf57e5df96734694214c --- /dev/null +++ b/docker/docker-build.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +VIPRA_WAR="../vipra-backend/target/vipra.war" + +if [ ! -f $VIPRA_WAR ]; then + echo "vipra.war not found. Did you run build.sh?" + exit 1 +fi + +cp $VIPRA_WAR . +docker build -t eikecochu/vipra . +rm vipra.war +exit 0 \ No newline at end of file diff --git a/docker/docker-run.sh b/docker/docker-run.sh new file mode 100755 index 0000000000000000000000000000000000000000..d7680269c28e136e79c14dfd07737114dafb5315 --- /dev/null +++ b/docker/docker-run.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +HOST_TOMCAT=8080 +HOST_ELASTICSEARCH_REST=9200 +HOST_ELASTICSEARCH_API=9300 +HOST_MONGODB=27017 + +docker run -d -p $HOST_TOMCAT:8080 -p $HOST_ELASTICSEARCH_REST:9200 -p $HOST_ELASTICSEARCH_API:9300 -p $HOST_MONGODB:27017 eikecochu/vipra \ No newline at end of file diff --git a/docker/elasticsearch.yml b/docker/elasticsearch.yml new file mode 100644 index 0000000000000000000000000000000000000000..ba6a829ef79a6245c2d2c9e09f3ea963d8f911cb --- /dev/null +++ b/docker/elasticsearch.yml @@ -0,0 +1,96 @@ +# ======================== Elasticsearch Configuration ========================= +# +# NOTE: Elasticsearch comes with reasonable defaults for most settings. +# Before you set out to tweak and tune the configuration, make sure you +# understand what are you trying to accomplish and the consequences. +# +# The primary way of configuring a node is via this file. This template lists +# the most important settings you may want to configure for a production cluster. +# +# Please see the documentation for further information on configuration options: +# <http://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration.html> +# +# ---------------------------------- Cluster ----------------------------------- +# +# Use a descriptive name for your cluster: +# +# cluster.name: my-application +# +# ------------------------------------ Node ------------------------------------ +# +# Use a descriptive name for the node: +# +# node.name: node-1 +# +# Add custom attributes to the node: +# +# node.rack: r1 +# +# ----------------------------------- Paths ------------------------------------ +# +# Path to directory where to store the data (separate multiple locations by comma): +# +# path.data: /path/to/data +# +# Path to log files: +# +# path.logs: /path/to/logs +# +# ----------------------------------- Memory ----------------------------------- +# +# Lock the memory on startup: +# +# bootstrap.mlockall: true +# +# Make sure that the `ES_HEAP_SIZE` environment variable is set to about half the memory +# available on the system and that the owner of the process is allowed to use this limit. +# +# Elasticsearch performs poorly when the system is swapping the memory. +# +# ---------------------------------- Network ----------------------------------- +# +# Set the bind address to a specific IP (IPv4 or IPv6): +# +# network.host: 192.168.0.1 +# +# Set a custom port for HTTP: +# +# http.port: 9200 +# +# For more information, see the documentation at: +# <http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-network.html> +# +# --------------------------------- Discovery ---------------------------------- +# +# Pass an initial list of hosts to perform discovery when new node is started: +# The default list of hosts is ["127.0.0.1", "[::1]"] +# +# discovery.zen.ping.unicast.hosts: ["host1", "host2"] +# +# Prevent the "split brain" by configuring the majority of nodes (total number of nodes / 2 + 1): +# +# discovery.zen.minimum_master_nodes: 3 +# +# For more information, see the documentation at: +# <http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-discovery.html> +# +# ---------------------------------- Gateway ----------------------------------- +# +# Block initial recovery after a full cluster restart until N nodes are started: +# +# gateway.recover_after_nodes: 3 +# +# For more information, see the documentation at: +# <http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-gateway.html> +# +# ---------------------------------- Various ----------------------------------- +# +# Disable starting multiple nodes on a single system: +# +# node.max_local_storage_nodes: 1 +# +# Require explicit names when deleting indices: +# +# action.destructive_requires_name: true + +network.host: 0.0.0.0 \ No newline at end of file diff --git a/docker/supervisord.conf b/docker/supervisord.conf new file mode 100644 index 0000000000000000000000000000000000000000..d2cc713bad9668bf0f1da9a922889ed5dead7c13 --- /dev/null +++ b/docker/supervisord.conf @@ -0,0 +1,19 @@ +[supervisord] +nodaemon=true + +[program:mongodb] +command=/usr/bin/mongod +autostart=true +autorestart=true + +[program:elasticsearch] +command=/elasticsearch/bin/elasticsearch -Des.insecure.allow.root=true +autostart=true +autorestart=true + +[program:tomcat] +environment=CATALINA_HOME="/tomcat",CATALINA_BASE="/tomcat",CATALINA_TMPDIR="/tomcat/temp" +command=/tomcat/bin/catalina.sh run +autostart=true +autorestart=true +startsecs=10 \ No newline at end of file diff --git a/docker/tomcat-users.xml b/docker/tomcat-users.xml new file mode 100644 index 0000000000000000000000000000000000000000..9e4410d0d2108de34705645d15737620bc425fb3 --- /dev/null +++ b/docker/tomcat-users.xml @@ -0,0 +1,49 @@ +<?xml version='1.0' encoding='utf-8'?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<tomcat-users xmlns="http://tomcat.apache.org/xml" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd" + version="1.0"> +<!-- + NOTE: By default, no user is included in the "manager-gui" role required + to operate the "/manager/html" web application. If you wish to use this app, + you must define such a user - the username and password are arbitrary. It is + strongly recommended that you do NOT use one of the users in the commented out + section below since they are intended for use with the examples web + application. +--> +<!-- + NOTE: The sample user and role entries below are intended for use with the + examples web application. They are wrapped in a comment and thus are ignored + when reading this file. If you wish to configure these users for use with the + examples web application, do not forget to remove the <!.. ..> that surrounds + them. You will also need to set the passwords to something appropriate. +--> +<!-- + <role rolename="tomcat"/> + <role rolename="role1"/> + <user username="tomcat" password="<must-be-changed>" roles="tomcat"/> + <user username="both" password="<must-be-changed>" roles="tomcat,role1"/> + <user username="role1" password="<must-be-changed>" roles="role1"/> +--> + <role rolename="manager-gui" /> + <role rolename="manager-status" /> + <role rolename="manager-script" /> + <role rolename="manager-jmx" /> + <user username="vipra" password="vipra" roles="manager-gui,manager-status,manager-script,manager-jmx" /> +</tomcat-users> diff --git a/vipra b/vipra index 82a0b94380184212cfd927124e77037254e7ca76..bd0b989ad934d701d91d9fb573f042535c13aac0 100755 --- a/vipra +++ b/vipra @@ -1,5 +1,7 @@ #!/bin/bash +DIR="$(dirname "$(readlink -f "$0")")" + # check for java hash java > /dev/null 2>&1 if [ $? -ne 0 ]; then @@ -7,17 +9,24 @@ if [ $? -ne 0 ]; then exit 1 fi -# path -JAR="vipra-cmd-0.0.1-SNAPSHOT.jar" -DIR="./vipra-cmd/target" -JARFILE="$DIR/$JAR" +JARS="$VIPRA_JAR +/opt/vipra/vipra-cmd/target/vipra-cmd*.jar +vipra-cmd*.jar +$DIR/vipra-cmd/target/vipra-cmd*.jar" +for f in $JARS +do + if [ -f $f ]; then + JARFILE="$f" + break + fi +done # check if jar exists if [ ! -f "$JARFILE" ]; then - echo "executable jar not found. Did you run build.sh?" + echo "executable jar not found." exit 1 fi # run java -jar "$JARFILE" "$@" -exit $? +exit $? \ No newline at end of file diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/CreateModelCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/CreateModelCommand.java index 8d11e4f07b1444d89fbc5566b53dcf4943ce65fe..1d09d032dac5c5e867a6b8837f4178ff76bfcdcc 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/option/CreateModelCommand.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/CreateModelCommand.java @@ -1,9 +1,15 @@ package de.vipra.cmd.option; import java.io.File; +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; import de.vipra.util.Config; import de.vipra.util.ConsoleUtils; +import de.vipra.util.ex.DatabaseException; import de.vipra.util.model.TopicModelConfig; import de.vipra.util.model.TopicModelFull; import de.vipra.util.service.MongoService; @@ -11,18 +17,35 @@ import de.vipra.util.service.MongoService; public class CreateModelCommand implements Command { private final String[] names; + private Config config; + private MongoService<TopicModelFull, String> dbTopicModels; public CreateModelCommand(final String[] names) { this.names = names; } + private void restoreModelFromDir(final String name, final File modelDir) + throws JsonParseException, JsonMappingException, IOException, DatabaseException { + final TopicModelConfig modelConfig = TopicModelConfig.readFromFile(modelDir); + createModel(name, modelConfig, modelDir); + } + + private void createModel(final String name, final TopicModelConfig modelConfig, final File modelDir) + throws JsonGenerationException, JsonMappingException, IOException, DatabaseException { + modelConfig.setName(name); + modelConfig.saveToFile(modelDir); + final TopicModelFull topicModel = new TopicModelFull(name, modelConfig); + dbTopicModels.createSingle(topicModel); + config.getTopicModelConfigs().put(name, modelConfig); + } + @Override public void run() throws Exception { if (names.length == 0) return; - final Config config = Config.getConfig(); - final MongoService<TopicModelFull, String> dbTopicModels = MongoService.getDatabaseService(config, TopicModelFull.class); + config = Config.getConfig(); + dbTopicModels = MongoService.getDatabaseService(config, TopicModelFull.class); final TopicModelConfig modelConfig; @@ -36,15 +59,20 @@ public class CreateModelCommand implements Command { if (name.toLowerCase().equals("all")) throw new Exception("invalid model name: " + name); final File modelDir = new File(config.getDataDirectory(), name); - if (modelDir.exists()) - throw new Exception("model with that name already exists: " + name); + if (modelDir.exists()) { + try { + TopicModelFull topicModel = dbTopicModels.getSingle(name); + if (topicModel != null) + throw new Exception("model with that name already exists: " + name); + } catch (Exception e) {} + restoreModelFromDir(name, modelDir); + ConsoleUtils.info("model restored: " + name); + continue; + } if (!modelDir.mkdirs()) throw new Exception("could not create model directory: " + modelDir.getAbsolutePath()); - modelConfig.setName(name); - final TopicModelFull topicModel = new TopicModelFull(name, modelConfig); - dbTopicModels.createSingle(topicModel); - config.getTopicModelConfigs().put(name, modelConfig); + createModel(name, modelConfig, modelDir); ConsoleUtils.info("model created: " + name); } } diff --git a/vipra-ui/app/index.html b/vipra-ui/app/index.html index df85c4507feaa5186ed39685983625c634f98989..925f65b3f405b70c0fef95a297ccad5ee14bcdd6 100644 --- a/vipra-ui/app/index.html +++ b/vipra-ui/app/index.html @@ -151,7 +151,7 @@ <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 class="modal-title">Report a bug/problem</h4> + <h4 class="modal-title">Report a bug</h4> </div> <div class="modal-body"> <form class="form-horizontal" name="bugReportForm"> @@ -159,14 +159,14 @@ <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> <input tabindex="0" type="checkbox" ng-model="bug.reproducible" id="reproducible"> - <label class="check" for="reproducible">Problem is reproducible</label> + <label class="check" for="reproducible">Bug is reproducible</label> </div> </div> </div> <div class="form-group"> <label for="description" class="col-sm-2 control-label">Description</label> <div class="col-sm-10"> - <textarea class="form-control resize-vertical" id="description" rows="10" placeholder="Describe the problem..." ng-model="bug.description"></textarea> + <textarea class="form-control resize-vertical" id="description" rows="10" placeholder="Describe the bug..." ng-model="bug.description" autofocus></textarea> </div> </div> <div class="form-group"> diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index 500739ee45d0976e3995343695d1ab8d2f6cd56f..dff06132ecf77e8bf996be4e96d0db6279475911 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -149,6 +149,14 @@ } }); + hotkeys.add({ + combo: 'b', + description: 'Report a bug', + callback: function() { + $scope.reportBug(); + } + }); + $scope.showCheatSheet = hotkeys.toggleCheatSheet; } ]); @@ -1006,14 +1014,6 @@ $state.transitionTo('index'); }); - hotkeys.add({ - combo: 'r', - description: 'Rename topic', - callback: function() { - $scope.startRename(); - } - }); - $('.tab-sequences').on('mouseleave', '.compare-row', function() { $('[word-id=' + $(this).attr('word-id') + ']').removeClass('highlight'); }); diff --git a/vipra-ui/gulpfile.js b/vipra-ui/gulpfile.js index ebe1a2b64cb376ac94b4fd5d3be7bf17d9c42203..1ab3e2c9014c7054d1b92378e6d668157e87aef4 100644 --- a/vipra-ui/gulpfile.js +++ b/vipra-ui/gulpfile.js @@ -59,6 +59,14 @@ gulp.task('js', function() { .pipe(gulp.dest('public/js')); }); +gulp.task('js-rel', function() { + gulp.src(['app/js/**/*.js', '!app/js/dev.js']) + .pipe(concat('app.js')) + .pipe(ngannotate()) + //.pipe(uglify()) + .pipe(gulp.dest('public/js')); +}); + gulp.task('html', function() { gulp.src('app/index.html') .pipe(gulp.dest('public')); @@ -97,7 +105,7 @@ gulp.task('assets', function() { .pipe(gulp.dest('public/img')); }); -gulp.task('build', ['less', 'js', 'html', 'img', 'public', 'assets']); +gulp.task('build', ['less', 'js-rel', 'html', 'img', 'public', 'assets']); gulp.task('watch', function() { gulp.watch('app/less/**/*.less', ['less']); diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicModelConfig.java b/vipra-util/src/main/java/de/vipra/util/model/TopicModelConfig.java index e0dee92240bc839ad886df3563629761a8cca3b4..1f116b4f3a199463055f77bc83831c2dc63a70ea 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/TopicModelConfig.java +++ b/vipra-util/src/main/java/de/vipra/util/model/TopicModelConfig.java @@ -1,12 +1,19 @@ package de.vipra.util.model; import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; import java.io.Serializable; +import org.apache.commons.io.FileUtils; import org.mongodb.morphia.annotations.Embedded; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import de.vipra.util.Config; import de.vipra.util.Constants; import de.vipra.util.Constants.ProcessorMode; import de.vipra.util.Constants.WindowResolution; @@ -16,6 +23,8 @@ import de.vipra.util.Constants.WindowResolution; @Embedded public class TopicModelConfig implements Serializable { + public static final String FILE_NAME = "config.json"; + private String name; private String description; private int kTopics = Constants.K_TOPICS; @@ -214,6 +223,19 @@ public class TopicModelConfig implements Serializable { return new File(dataDir, name); } + public void saveToFile(final File modelDir) throws JsonGenerationException, JsonMappingException, IOException { + final File file = new File(modelDir, FILE_NAME); + final String value = Config.mapper.writeValueAsString(this); + FileUtils.write(file, value, Constants.FILEBASE_ENCODING, false); + } + + public static TopicModelConfig readFromFile(final File modelDir) throws JsonParseException, JsonMappingException, IOException { + final File file = new File(modelDir, FILE_NAME); + if (!file.exists()) + throw new FileNotFoundException(file.getAbsolutePath()); + return Config.mapper.readValue(file, TopicModelConfig.class); + } + @Override public String toString() { return "[window=" + windowResolution + ", mode=" + processorMode + ", k=" + kTopics + ", iter=" + staticIterations + "/"