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