thingsboard-developers
Changes
msa/docker/docker-compose.yml 13(+12 -1)
msa/docker/tb-web-ui.env 9(+9 -0)
msa/pom.xml 1(+1 -0)
msa/web-ui/.gitignore 31(+31 -0)
msa/web-ui/build.gradle 125(+125 -0)
msa/web-ui/config/default.yml 30(+30 -0)
msa/web-ui/config/logger.js 59(+59 -0)
msa/web-ui/config/tb-web-ui.conf 20(+20 -0)
msa/web-ui/docker/Dockerfile 26(+26 -0)
msa/web-ui/docker/start-web-ui.sh 29(+29 -0)
msa/web-ui/install.js 42(+42 -0)
msa/web-ui/package.json 38(+38 -0)
msa/web-ui/pom.xml 372(+372 -0)
msa/web-ui/server.js 129(+129 -0)
msa/web-ui/src/main/assembly/windows.xml 75(+75 -0)
msa/web-ui/src/main/scripts/init/tb-web-ui 233(+233 -0)
ui/package.json 2(+1 -1)
Details
msa/docker/docker-compose.yml 13(+12 -1)
diff --git a/msa/docker/docker-compose.yml b/msa/docker/docker-compose.yml
index a077c0e..727936c 100644
--- a/msa/docker/docker-compose.yml
+++ b/msa/docker/docker-compose.yml
@@ -29,7 +29,7 @@ services:
environment:
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_LISTENERS: INSIDE://:9093,OUTSIDE://:9092
- KAFKA_ADVERTISED_LISTENERS: INSIDE://:9093,OUTSIDE://${KAFKA_HOSTNAME}:9092
+ KAFKA_ADVERTISED_LISTENERS: INSIDE://:9093,OUTSIDE://${EXTERNAL_HOSTNAME}:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE
KAFKA_CREATE_TOPICS: "${KAFKA_TOPICS}"
@@ -44,3 +44,14 @@ services:
- tb-js-executor.env
depends_on:
- kafka
+ tb-web-ui:
+ image: "local-maven-build/tb-web-ui:latest"
+ ports:
+ - "8090:8090"
+ environment:
+ HTTP_BIND_ADDRESS: 0.0.0.0
+ HTTP_BIND_PORT: 8090
+ TB_HOST: ${EXTERNAL_HOSTNAME}
+ TB_PORT: 8080
+ env_file:
+ - tb-web-ui.env
msa/docker/tb-web-ui.env 9(+9 -0)
diff --git a/msa/docker/tb-web-ui.env b/msa/docker/tb-web-ui.env
new file mode 100644
index 0000000..8d6157b
--- /dev/null
+++ b/msa/docker/tb-web-ui.env
@@ -0,0 +1,9 @@
+
+HTTP_BIND_ADDRESS=0.0.0.0
+HTTP_BIND_PORT=8090
+TB_HOST=localhost
+TB_PORT=8080
+LOGGER_LEVEL=debug
+LOG_FOLDER=logs
+LOGGER_FILENAME=tb-web-ui-%DATE%.log
+DOCKER_MODE=true
\ No newline at end of file
msa/pom.xml 1(+1 -0)
diff --git a/msa/pom.xml b/msa/pom.xml
index 85d45a1..3241d0c 100644
--- a/msa/pom.xml
+++ b/msa/pom.xml
@@ -36,6 +36,7 @@
<modules>
<module>js-executor</module>
+ <module>web-ui</module>
</modules>
</project>
msa/web-ui/.gitignore 31(+31 -0)
diff --git a/msa/web-ui/.gitignore b/msa/web-ui/.gitignore
new file mode 100644
index 0000000..c5cfd3d
--- /dev/null
+++ b/msa/web-ui/.gitignore
@@ -0,0 +1,31 @@
+*.toDelete
+output/**
+*.class
+*~
+*.iml
+*/.idea/**
+.idea/**
+.idea
+*.log
+*.log.[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
+*/.classpath
+.classpath
+*/.project
+.project
+.cache/**
+target/
+logs/
+build/
+.settings/
+/bin
+bin/
+**/dependency-reduced-pom.xml
+pom.xml.versionsBackup
+.DS_Store
+**/.gradle
+**/local.properties
+**/build
+**/target
+**/.env
+node_modules
+package-lock.json
msa/web-ui/build.gradle 125(+125 -0)
diff --git a/msa/web-ui/build.gradle b/msa/web-ui/build.gradle
new file mode 100644
index 0000000..7372c0a
--- /dev/null
+++ b/msa/web-ui/build.gradle
@@ -0,0 +1,125 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed 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.
+ */
+import org.apache.tools.ant.filters.ReplaceTokens
+
+buildscript {
+ ext {
+ osPackageVersion = "3.8.0"
+ }
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}")
+ }
+}
+
+apply plugin: "nebula.ospackage"
+
+buildDir = projectBuildDir
+version = projectVersion
+distsDirName = "./"
+
+// OS Package plugin configuration
+ospackage {
+ packageName = pkgName
+ version = "${project.version}"
+ release = 1
+ os = LINUX
+ type = BINARY
+
+ into pkgInstallFolder
+
+ user pkgUser
+ permissionGroup pkgUser
+
+ // Copy the executable file
+ from("target/package/linux/bin/${pkgName}") {
+ fileMode 0500
+ into "bin"
+ }
+
+ // Copy the init file
+ from("target/package/linux/init/${pkgName}") {
+ fileMode 0500
+ into "init"
+ }
+
+ // Copy the config files
+ from("target/package/linux/conf") {
+ fileType CONFIG | NOREPLACE
+ fileMode 0754
+ into "conf"
+ }
+
+ // Copy web files
+ from("target/package/linux/web") {
+ into "web"
+ }
+
+}
+
+// Configure our RPM build task
+buildRpm {
+
+ arch = X86_64
+
+ version = projectVersion.replace('-', '')
+ archiveName = "${pkgName}.rpm"
+
+ preInstall file("${buildDir}/control/rpm/preinst")
+ postInstall file("${buildDir}/control/rpm/postinst")
+ preUninstall file("${buildDir}/control/rpm/prerm")
+ postUninstall file("${buildDir}/control/rpm/postrm")
+
+ user pkgUser
+ permissionGroup pkgUser
+
+ // Copy the system unit files
+ from("${buildDir}/control/${pkgName}.service") {
+ addParentDirs = false
+ fileMode 0644
+ into "/usr/lib/systemd/system"
+ }
+
+ directory(pkgLogFolder, 0755)
+ link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
+}
+
+// Same as the buildRpm task
+buildDeb {
+
+ arch = "amd64"
+
+ archiveName = "${pkgName}.deb"
+
+ configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf")
+ configurationFile("${pkgInstallFolder}/conf/custom-environment-variables.yml")
+ configurationFile("${pkgInstallFolder}/conf/default.yml")
+ configurationFile("${pkgInstallFolder}/conf/logger.js")
+
+ preInstall file("${buildDir}/control/deb/preinst")
+ postInstall file("${buildDir}/control/deb/postinst")
+ preUninstall file("${buildDir}/control/deb/prerm")
+ postUninstall file("${buildDir}/control/deb/postrm")
+
+ user pkgUser
+ permissionGroup pkgUser
+
+ directory(pkgLogFolder, 0755)
+ link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/init/${pkgName}")
+ link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
+}
diff --git a/msa/web-ui/config/custom-environment-variables.yml b/msa/web-ui/config/custom-environment-variables.yml
new file mode 100644
index 0000000..9472a50
--- /dev/null
+++ b/msa/web-ui/config/custom-environment-variables.yml
@@ -0,0 +1,30 @@
+#
+# Copyright © 2016-2018 The Thingsboard Authors
+#
+# Licensed 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.
+#
+
+server:
+ # Server bind address
+ address: "HTTP_BIND_ADDRESS"
+ # Server bind port
+ port: "HTTP_BIND_PORT"
+thingsboard:
+ # ThingsBoard node host
+ host: "TB_HOST"
+ # ThingsBoard node port
+ port: "TB_PORT"
+logger:
+ level: "LOGGER_LEVEL"
+ path: "LOG_FOLDER"
+ filename: "LOGGER_FILENAME"
msa/web-ui/config/default.yml 30(+30 -0)
diff --git a/msa/web-ui/config/default.yml b/msa/web-ui/config/default.yml
new file mode 100644
index 0000000..cf27a14
--- /dev/null
+++ b/msa/web-ui/config/default.yml
@@ -0,0 +1,30 @@
+#
+# Copyright © 2016-2018 The Thingsboard Authors
+#
+# Licensed 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.
+#
+
+server:
+ # Server bind address
+ address: "0.0.0.0"
+ # Server bind port
+ port: "8090"
+thingsboard:
+ # ThingsBoard node host
+ host: "localhost"
+ # ThingsBoard node port
+ port: "8080"
+logger:
+ level: "info"
+ path: "logs"
+ filename: "tb-web-ui-%DATE%.log"
msa/web-ui/config/logger.js 59(+59 -0)
diff --git a/msa/web-ui/config/logger.js b/msa/web-ui/config/logger.js
new file mode 100644
index 0000000..695b453
--- /dev/null
+++ b/msa/web-ui/config/logger.js
@@ -0,0 +1,59 @@
+/*
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed 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.
+ */
+var config = require('config'),
+ path = require('path'),
+ DailyRotateFile = require('winston-daily-rotate-file');
+
+const { createLogger, format, transports } = require('winston');
+const { combine, timestamp, label, printf, splat } = format;
+
+var loggerTransports = [];
+
+if (process.env.NODE_ENV !== 'production' || process.env.DOCKER_MODE === 'true') {
+ loggerTransports.push(new transports.Console({
+ handleExceptions: true
+ }));
+} else {
+ var filename = path.join(config.get('logger.path'), config.get('logger.filename'));
+ var transport = new (DailyRotateFile)({
+ filename: filename,
+ datePattern: 'YYYY-MM-DD-HH',
+ zippedArchive: true,
+ maxSize: '20m',
+ maxFiles: '14d',
+ handleExceptions: true
+ });
+ loggerTransports.push(transport);
+}
+
+const tbFormat = printf(info => {
+ return `${info.timestamp} [${info.label}] ${info.level.toUpperCase()}: ${info.message}`;
+});
+
+function _logger(moduleLabel) {
+ return createLogger({
+ level: config.get('logger.level'),
+ format:combine(
+ splat(),
+ label({ label: moduleLabel }),
+ timestamp({format: 'YYYY-MM-DD HH:mm:ss,SSS'}),
+ tbFormat
+ ),
+ transports: loggerTransports
+ });
+}
+
+module.exports = _logger;
\ No newline at end of file
msa/web-ui/config/tb-web-ui.conf 20(+20 -0)
diff --git a/msa/web-ui/config/tb-web-ui.conf b/msa/web-ui/config/tb-web-ui.conf
new file mode 100644
index 0000000..d002038
--- /dev/null
+++ b/msa/web-ui/config/tb-web-ui.conf
@@ -0,0 +1,20 @@
+#
+# Copyright © 2016-2018 The Thingsboard Authors
+#
+# Licensed 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.
+#
+
+export NODE_CONFIG_DIR=${pkg.installFolder}/conf
+export LOG_FOLDER=${pkg.logFolder}
+export NODE_ENV=production
+export WEB_FOLDER=${pkg.installFolder}/web
\ No newline at end of file
msa/web-ui/docker/Dockerfile 26(+26 -0)
diff --git a/msa/web-ui/docker/Dockerfile b/msa/web-ui/docker/Dockerfile
new file mode 100644
index 0000000..7a75cc3
--- /dev/null
+++ b/msa/web-ui/docker/Dockerfile
@@ -0,0 +1,26 @@
+#
+# Copyright © 2016-2018 The Thingsboard Authors
+#
+# Licensed 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.
+#
+
+FROM debian:stretch
+
+COPY start-web-ui.sh ${pkg.name}.deb /tmp/
+
+RUN chmod a+x /tmp/*.sh \
+ && mv /tmp/start-web-ui.sh /usr/bin
+
+RUN dpkg -i /tmp/${pkg.name}.deb
+
+CMD ["start-web-ui.sh"]
msa/web-ui/docker/start-web-ui.sh 29(+29 -0)
diff --git a/msa/web-ui/docker/start-web-ui.sh b/msa/web-ui/docker/start-web-ui.sh
new file mode 100755
index 0000000..af7c686
--- /dev/null
+++ b/msa/web-ui/docker/start-web-ui.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# Copyright © 2016-2018 The Thingsboard Authors
+#
+# Licensed 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.
+#
+
+
+echo "Starting '${project.name}' ..."
+
+CONF_FOLDER="${pkg.installFolder}/conf"
+
+mainfile=${pkg.installFolder}/bin/${pkg.name}
+configfile=${pkg.name}.conf
+identity=${pkg.name}
+
+source "${CONF_FOLDER}/${configfile}"
+
+su -s /bin/sh -c "$mainfile"
msa/web-ui/install.js 42(+42 -0)
diff --git a/msa/web-ui/install.js b/msa/web-ui/install.js
new file mode 100644
index 0000000..63e73db
--- /dev/null
+++ b/msa/web-ui/install.js
@@ -0,0 +1,42 @@
+/*
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed 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.
+ */
+const fs = require('fs');
+const fse = require('fs-extra');
+const path = require('path');
+
+let _projectRoot = null;
+
+
+(async() => {
+ await fse.move(path.join(projectRoot(), 'target', 'thingsboard-web-ui-linux'),
+ path.join(targetPackageDir('linux'), 'bin', 'tb-web-ui'),
+ {overwrite: true});
+ await fse.move(path.join(projectRoot(), 'target', 'thingsboard-web-ui-win.exe'),
+ path.join(targetPackageDir('windows'), 'bin', 'tb-web-ui.exe'),
+ {overwrite: true});
+})();
+
+
+function projectRoot() {
+ if (!_projectRoot) {
+ _projectRoot = __dirname;
+ }
+ return _projectRoot;
+}
+
+function targetPackageDir(platform) {
+ return path.join(projectRoot(), 'target', 'package', platform);
+}
msa/web-ui/package.json 38(+38 -0)
diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json
new file mode 100644
index 0000000..f30f2fc
--- /dev/null
+++ b/msa/web-ui/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "thingsboard-web-ui",
+ "private": true,
+ "version": "2.2.0",
+ "description": "ThingsBoard Web UI Microservice",
+ "main": "server.js",
+ "bin": "server.js",
+ "scripts": {
+ "install": "pkg -t node8-linux-x64,node8-win-x64 --out-path ./target . && node install.js",
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "WEB_FOLDER=./target/web nodemon server.js",
+ "start-prod": "NODE_ENV=production nodemon server.js"
+ },
+ "dependencies": {
+ "config": "^1.30.0",
+ "connect-history-api-fallback": "^1.5.0",
+ "express": "^4.16.3",
+ "http": "0.0.0",
+ "http-proxy": "^1.17.0",
+ "js-yaml": "^3.12.0",
+ "winston": "^3.0.0",
+ "winston-daily-rotate-file": "^3.2.1"
+ },
+ "engine": "node >= 5.9.0",
+ "nyc": {
+ "exclude": [
+ "test",
+ "__tests__",
+ "node_modules",
+ "target"
+ ]
+ },
+ "devDependencies": {
+ "fs-extra": "^6.0.1",
+ "nodemon": "^1.17.5",
+ "pkg": "^4.3.3"
+ }
+}
msa/web-ui/pom.xml 372(+372 -0)
diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml
new file mode 100644
index 0000000..54c3bb4
--- /dev/null
+++ b/msa/web-ui/pom.xml
@@ -0,0 +1,372 @@
+<!--
+
+ Copyright © 2016-2018 The Thingsboard Authors
+
+ Licensed 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.thingsboard</groupId>
+ <version>2.2.0-SNAPSHOT</version>
+ <artifactId>msa</artifactId>
+ </parent>
+ <groupId>org.thingsboard.msa</groupId>
+ <artifactId>web-ui</artifactId>
+ <packaging>pom</packaging>
+
+ <name>ThingsBoard Web UI Microservice</name>
+ <url>https://thingsboard.io</url>
+ <description>Service for hosting ThingsBoard Web UI</description>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <main.dir>${basedir}/../..</main.dir>
+ <pkg.name>tb-web-ui</pkg.name>
+ <pkg.user>thingsboard</pkg.user>
+ <pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
+ <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
+ <pkg.linux.dist>${project.build.directory}/package/linux</pkg.linux.dist>
+ <pkg.win.dist>${project.build.directory}/package/windows</pkg.win.dist>
+ <dockerfile.skip>true</dockerfile.skip>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.thingsboard</groupId>
+ <artifactId>ui</artifactId>
+ <version>${project.version}</version>
+ <type>jar</type>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.winsw</groupId>
+ <artifactId>winsw</artifactId>
+ <classifier>bin</classifier>
+ <type>exe</type>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.github.eirslett</groupId>
+ <artifactId>frontend-maven-plugin</artifactId>
+ <version>1.0</version>
+ <configuration>
+ <installDirectory>target</installDirectory>
+ <workingDirectory>${basedir}</workingDirectory>
+ </configuration>
+ <executions>
+ <execution>
+ <id>install node and npm</id>
+ <goals>
+ <goal>install-node-and-npm</goal>
+ </goals>
+ <configuration>
+ <nodeVersion>v8.11.3</nodeVersion>
+ <npmVersion>5.6.0</npmVersion>
+ </configuration>
+ </execution>
+ <execution>
+ <id>npm install</id>
+ <goals>
+ <goal>npm</goal>
+ </goals>
+ <configuration>
+ <arguments>install</arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>extract-web-ui</id>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.thingsboard</groupId>
+ <artifactId>ui</artifactId>
+ <type>jar</type>
+ <overWrite>false</overWrite>
+ <outputDirectory>${project.build.directory}/web</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-winsw-service</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>com.sun.winsw</groupId>
+ <artifactId>winsw</artifactId>
+ <classifier>bin</classifier>
+ <type>exe</type>
+ <destFileName>service.exe</destFileName>
+ </artifactItem>
+ </artifactItems>
+ <outputDirectory>${pkg.win.dist}</outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-linux-conf</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${pkg.linux.dist}/conf</outputDirectory>
+ <resources>
+ <resource>
+ <directory>config</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <filters>
+ <filter>src/main/filters/unix.properties</filter>
+ </filters>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-linux-init</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${pkg.linux.dist}/init</outputDirectory>
+ <resources>
+ <resource>
+ <directory>src/main/scripts/init</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <filters>
+ <filter>src/main/filters/unix.properties</filter>
+ </filters>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-win-conf</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${pkg.win.dist}/conf</outputDirectory>
+ <resources>
+ <resource>
+ <directory>config</directory>
+ <excludes>
+ <exclude>tb-web-ui.conf</exclude>
+ </excludes>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <filters>
+ <filter>src/main/filters/windows.properties</filter>
+ </filters>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-control</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/control</outputDirectory>
+ <resources>
+ <resource>
+ <directory>src/main/scripts/control</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <filters>
+ <filter>src/main/filters/unix.properties</filter>
+ </filters>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-windows-control</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${pkg.win.dist}</outputDirectory>
+ <resources>
+ <resource>
+ <directory>src/main/scripts/windows</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <filters>
+ <filter>src/main/filters/windows.properties</filter>
+ </filters>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-docker-config</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}</outputDirectory>
+ <resources>
+ <resource>
+ <directory>docker</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.fortasoft</groupId>
+ <artifactId>gradle-maven-plugin</artifactId>
+ <configuration>
+ <tasks>
+ <task>build</task>
+ <task>buildDeb</task>
+ <task>buildRpm</task>
+ </tasks>
+ <args>
+ <arg>-PprojectBuildDir=${project.build.directory}</arg>
+ <arg>-PprojectVersion=${project.version}</arg>
+ <arg>-PpkgName=${pkg.name}</arg>
+ <arg>-PpkgUser=${pkg.user}</arg>
+ <arg>-PpkgInstallFolder=${pkg.installFolder}</arg>
+ <arg>-PpkgLogFolder=${pkg.unixLogFolder}</arg>
+ </args>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>invoke</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>3.0.0</version>
+ <configuration>
+ <finalName>${pkg.name}</finalName>
+ <descriptors>
+ <descriptor>src/main/assembly/windows.xml</descriptor>
+ </descriptors>
+ </configuration>
+ <executions>
+ <execution>
+ <id>assembly</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>com.spotify</groupId>
+ <artifactId>dockerfile-maven-plugin</artifactId>
+ <version>1.4.4</version>
+ <executions>
+ <execution>
+ <id>build-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>local-maven-build/${pkg.name}</repository>
+ <verbose>true</verbose>
+ <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
+ <contextDirectory>${project.build.directory}</contextDirectory>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <profiles>
+ <profile>
+ <id>npm-start</id>
+ <activation>
+ <property>
+ <name>npm-start</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.github.eirslett</groupId>
+ <artifactId>frontend-maven-plugin</artifactId>
+ <version>1.0</version>
+ <configuration>
+ <installDirectory>target</installDirectory>
+ <workingDirectory>${basedir}</workingDirectory>
+ </configuration>
+ <executions>
+ <execution>
+ <id>npm start</id>
+ <goals>
+ <goal>npm</goal>
+ </goals>
+
+ <configuration>
+ <arguments>start</arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+ <repositories>
+ <repository>
+ <id>jenkins</id>
+ <name>Jenkins Repository</name>
+ <url>http://repo.jenkins-ci.org/releases</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ </repositories>
+</project>
msa/web-ui/server.js 129(+129 -0)
diff --git a/msa/web-ui/server.js b/msa/web-ui/server.js
new file mode 100644
index 0000000..46d9bfd
--- /dev/null
+++ b/msa/web-ui/server.js
@@ -0,0 +1,129 @@
+/*
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed 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.
+ */
+
+const config = require('config'),
+ logger = require('./config/logger')('main'),
+ express = require('express'),
+ http = require('http'),
+ httpProxy = require('http-proxy'),
+ path = require('path'),
+ historyApiFallback = require("connect-history-api-fallback");
+
+var server;
+
+(async() => {
+ try {
+ logger.info('Starting ThingsBoard Web UI Microservice...');
+
+ const bindAddress = config.get('server.address');
+ const bindPort = config.get('server.port');
+
+ const thingsboardHost = config.get('thingsboard.host');
+ const thingsboardPort = config.get('thingsboard.port');
+
+ logger.info('Bind address: %s', bindAddress);
+ logger.info('Bind port: %s', bindPort);
+ logger.info('ThingsBoard host: %s', thingsboardHost);
+ logger.info('ThingsBoard port: %s', thingsboardPort);
+
+ var webDir = path.join(__dirname, 'web');
+
+ if (typeof process.env.WEB_FOLDER === 'string') {
+ webDir = path.resolve(process.env.WEB_FOLDER);
+ }
+ logger.info('Web folder: %s', webDir);
+
+ const app = express();
+ server = http.createServer(app);
+
+ const apiProxy = httpProxy.createProxyServer({
+ target: {
+ host: thingsboardHost,
+ port: thingsboardPort
+ }
+ });
+
+ apiProxy.on('error', function (err, req, res) {
+ logger.warn('API proxy error: %s', err.message);
+ res.writeHead(500);
+ if (err.code && err.code === 'ECONNREFUSED') {
+ res.end('Unable to connect to ThingsBoard server.');
+ } else {
+ res.end('Thingsboard server connection error: ' + err.code ? err.code : '');
+ }
+ });
+
+ const root = path.join(webDir, 'public');
+
+ const staticDir = path.join(root, 'static');
+
+ app.all('/api/*', (req, res) => {
+ logger.info(req.method + ' ' + req.originalUrl);
+ apiProxy.web(req, res);
+ });
+
+ app.all('/static/rulenode/*', (req, res) => {
+ apiProxy.web(req, res);
+ });
+
+ app.use(historyApiFallback());
+
+ app.use('/static', express.static(staticDir));
+
+ app.get('*', (req, res) => {
+ apiProxy.web(req, res);
+ });
+
+ server.on('upgrade', (req, socket, head) => {
+ apiProxy.ws(req, socket, head);
+ });
+
+ server.listen(bindPort, bindAddress, (error) => {
+ if (error) {
+ logger.error('Failed to start ThingsBoard Web UI Microservice: %s', e.message);
+ logger.error(error.stack);
+ exit(-1);
+ } else {
+ logger.info('==> 🌎 Listening on port %s.', bindPort);
+ logger.info('Started ThingsBoard Web UI Microservice.');
+ }
+ });
+
+ } catch (e) {
+ logger.error('Failed to start ThingsBoard Web UI Microservice: %s', e.message);
+ logger.error(e.stack);
+ exit(-1);
+ }
+})();
+
+process.on('exit', function () {
+ exit(0);
+});
+
+function exit(status) {
+ logger.info('Exiting with status: %d ...', status);
+ if (server) {
+ logger.info('Stopping HTTP Server...');
+ var _server = server;
+ server = null;
+ _server.close(() => {
+ logger.info('HTTP Server stopped.');
+ process.exit(status);
+ });
+ } else {
+ process.exit(status);
+ }
+}
msa/web-ui/src/main/assembly/windows.xml 75(+75 -0)
diff --git a/msa/web-ui/src/main/assembly/windows.xml b/msa/web-ui/src/main/assembly/windows.xml
new file mode 100644
index 0000000..1211990
--- /dev/null
+++ b/msa/web-ui/src/main/assembly/windows.xml
@@ -0,0 +1,75 @@
+<!--
+
+ Copyright © 2016-2018 The Thingsboard Authors
+
+ Licensed 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.
+
+-->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
+ <id>windows</id>
+
+ <formats>
+ <format>zip</format>
+ </formats>
+
+ <!-- Workaround to create logs directory -->
+ <fileSets>
+ <fileSet>
+ <directory>${pkg.win.dist}</directory>
+ <outputDirectory>logs</outputDirectory>
+ <excludes>
+ <exclude>*/**</exclude>
+ </excludes>
+ </fileSet>
+ <fileSet>
+ <directory>${pkg.win.dist}/conf</directory>
+ <outputDirectory>conf</outputDirectory>
+ <lineEnding>windows</lineEnding>
+ </fileSet>
+ <fileSet>
+ <directory>${project.build.directory}/web</directory>
+ <outputDirectory>web</outputDirectory>
+ </fileSet>
+ </fileSets>
+
+ <files>
+ <file>
+ <source>${pkg.win.dist}/bin/${pkg.name}.exe</source>
+ <outputDirectory>bin</outputDirectory>
+ <destName>${pkg.name}.exe</destName>
+ </file>
+ <file>
+ <source>${pkg.win.dist}/service.exe</source>
+ <outputDirectory/>
+ <destName>${pkg.name}.exe</destName>
+ </file>
+ <file>
+ <source>${pkg.win.dist}/service.xml</source>
+ <outputDirectory/>
+ <destName>${pkg.name}.xml</destName>
+ <lineEnding>windows</lineEnding>
+ </file>
+ <file>
+ <source>${pkg.win.dist}/install.bat</source>
+ <outputDirectory/>
+ <lineEnding>windows</lineEnding>
+ </file>
+ <file>
+ <source>${pkg.win.dist}/uninstall.bat</source>
+ <outputDirectory/>
+ <lineEnding>windows</lineEnding>
+ </file>
+ </files>
+</assembly>
diff --git a/msa/web-ui/src/main/filters/unix.properties b/msa/web-ui/src/main/filters/unix.properties
new file mode 100644
index 0000000..8967278
--- /dev/null
+++ b/msa/web-ui/src/main/filters/unix.properties
@@ -0,0 +1 @@
+pkg.logFolder=${pkg.unixLogFolder}
\ No newline at end of file
diff --git a/msa/web-ui/src/main/filters/windows.properties b/msa/web-ui/src/main/filters/windows.properties
new file mode 100644
index 0000000..a6e48d9
--- /dev/null
+++ b/msa/web-ui/src/main/filters/windows.properties
@@ -0,0 +1,2 @@
+pkg.logFolder=${BASE}\\logs
+pkg.winWrapperLogFolder=%BASE%\\logs
diff --git a/msa/web-ui/src/main/scripts/control/deb/postinst b/msa/web-ui/src/main/scripts/control/deb/postinst
new file mode 100644
index 0000000..4c48c55
--- /dev/null
+++ b/msa/web-ui/src/main/scripts/control/deb/postinst
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+chown -R ${pkg.user}: ${pkg.logFolder}
+chown -R ${pkg.user}: ${pkg.installFolder}
+# update-rc.d ${pkg.name} defaults
+
diff --git a/msa/web-ui/src/main/scripts/control/deb/postrm b/msa/web-ui/src/main/scripts/control/deb/postrm
new file mode 100644
index 0000000..6186580
--- /dev/null
+++ b/msa/web-ui/src/main/scripts/control/deb/postrm
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+update-rc.d -f ${pkg.name} remove
diff --git a/msa/web-ui/src/main/scripts/control/deb/preinst b/msa/web-ui/src/main/scripts/control/deb/preinst
new file mode 100644
index 0000000..d2ebea4
--- /dev/null
+++ b/msa/web-ui/src/main/scripts/control/deb/preinst
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+if ! getent group ${pkg.user} >/dev/null; then
+ addgroup --system ${pkg.user}
+fi
+
+if ! getent passwd ${pkg.user} >/dev/null; then
+ adduser --quiet \
+ --system \
+ --ingroup ${pkg.user} \
+ --quiet \
+ --disabled-login \
+ --disabled-password \
+ --home ${pkg.installFolder} \
+ --no-create-home \
+ -gecos "Thingsboard application" \
+ ${pkg.user}
+fi
diff --git a/msa/web-ui/src/main/scripts/control/deb/prerm b/msa/web-ui/src/main/scripts/control/deb/prerm
new file mode 100644
index 0000000..898d3ef
--- /dev/null
+++ b/msa/web-ui/src/main/scripts/control/deb/prerm
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ -e /var/run/${pkg.name}/${pkg.name}.pid ]; then
+ service ${pkg.name} stop
+fi
diff --git a/msa/web-ui/src/main/scripts/control/rpm/postinst b/msa/web-ui/src/main/scripts/control/rpm/postinst
new file mode 100644
index 0000000..d8021e2
--- /dev/null
+++ b/msa/web-ui/src/main/scripts/control/rpm/postinst
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+chown -R ${pkg.user}: ${pkg.logFolder}
+chown -R ${pkg.user}: ${pkg.installFolder}
+
+if [ $1 -eq 1 ] ; then
+ # Initial installation
+ systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || :
+fi
diff --git a/msa/web-ui/src/main/scripts/control/rpm/postrm b/msa/web-ui/src/main/scripts/control/rpm/postrm
new file mode 100644
index 0000000..8e1f8a2
--- /dev/null
+++ b/msa/web-ui/src/main/scripts/control/rpm/postrm
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+if [ $1 -ge 1 ] ; then
+ # Package upgrade, not uninstall
+ systemctl try-restart ${pkg.name}.service >/dev/null 2>&1 || :
+fi
diff --git a/msa/web-ui/src/main/scripts/control/rpm/preinst b/msa/web-ui/src/main/scripts/control/rpm/preinst
new file mode 100644
index 0000000..db6306e
--- /dev/null
+++ b/msa/web-ui/src/main/scripts/control/rpm/preinst
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+getent group ${pkg.user} >/dev/null || groupadd -r ${pkg.user}
+getent passwd ${pkg.user} >/dev/null || \
+useradd -d ${pkg.installFolder} -g ${pkg.user} -M -r ${pkg.user} -s /sbin/nologin \
+-c "Thingsboard application"
diff --git a/msa/web-ui/src/main/scripts/control/rpm/prerm b/msa/web-ui/src/main/scripts/control/rpm/prerm
new file mode 100644
index 0000000..accb487
--- /dev/null
+++ b/msa/web-ui/src/main/scripts/control/rpm/prerm
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+if [ $1 -eq 0 ] ; then
+ # Package removal, not upgrade
+ systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :
+fi
diff --git a/msa/web-ui/src/main/scripts/control/tb-web-ui.service b/msa/web-ui/src/main/scripts/control/tb-web-ui.service
new file mode 100644
index 0000000..f542dd0
--- /dev/null
+++ b/msa/web-ui/src/main/scripts/control/tb-web-ui.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=${pkg.name}
+After=syslog.target
+
+[Service]
+User=${pkg.user}
+ExecStart=${pkg.installFolder}/init/${pkg.name}
+SuccessExitStatus=143
+
+[Install]
+WantedBy=multi-user.target
msa/web-ui/src/main/scripts/init/tb-web-ui 233(+233 -0)
diff --git a/msa/web-ui/src/main/scripts/init/tb-web-ui b/msa/web-ui/src/main/scripts/init/tb-web-ui
new file mode 100644
index 0000000..d0d3f95
--- /dev/null
+++ b/msa/web-ui/src/main/scripts/init/tb-web-ui
@@ -0,0 +1,233 @@
+#!/bin/bash
+#
+
+
+### BEGIN INIT INFO
+# Provides: tb-web-ui
+# Required-Start: $remote_fs $syslog $network
+# Required-Stop: $remote_fs $syslog $network
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: ${project.name}
+# Description: ${project.description}
+# chkconfig: 2345 99 01
+### END INIT INFO
+
+[[ -n "$DEBUG" ]] && set -x
+
+# Initialize variables that cannot be provided by a .conf file
+WORKING_DIR="$(pwd)"
+# shellcheck disable=SC2153
+
+mainfile=${pkg.installFolder}/bin/${pkg.name}
+configfile=${pkg.name}.conf
+
+# Follow symlinks to find the real script and detect init.d script
+cd "$(dirname "$0")" || exit 1
+[[ -z "$initfile" ]] && initfile=$(pwd)/$(basename "$0")
+while [[ -L "$initfile" ]]; do
+ [[ "$initfile" =~ init\.d ]] && init_script=$(basename "$initfile")
+ initfile=$(readlink "$initfile")
+ cd "$(dirname "$initfile")" || exit 1
+ initfile=$(pwd)/$(basename "$initfile")
+done
+initfolder="$( (cd "$(dirname "initfile")" && pwd -P) )"
+cd "$WORKING_DIR" || exit 1
+
+# Initialize CONF_FOLDER location
+[[ -z "$CONF_FOLDER" ]] && CONF_FOLDER="${pkg.installFolder}/conf"
+
+# shellcheck source=/dev/null
+[[ -r "${CONF_FOLDER}/${configfile}" ]] && source "${CONF_FOLDER}/${configfile}"
+
+# Initialize PID/LOG locations if they weren't provided by the config file
+[[ -z "$PID_FOLDER" ]] && PID_FOLDER="/var/run"
+[[ -z "$LOG_FOLDER" ]] && LOG_FOLDER="${pkg.unixLogFolder}"
+! [[ "$PID_FOLDER" == /* ]] && PID_FOLDER="$(dirname "$mainfile")"/"$PID_FOLDER"
+! [[ "$LOG_FOLDER" == /* ]] && LOG_FOLDER="$(dirname "$mainfile")"/"$LOG_FOLDER"
+! [[ -x "$PID_FOLDER" ]] && PID_FOLDER="/tmp"
+! [[ -x "$LOG_FOLDER" ]] && LOG_FOLDER="/tmp"
+
+# Set up defaults
+[[ -z "$MODE" ]] && MODE="auto" # modes are "auto", "service" or "run"
+[[ -z "$USE_START_STOP_DAEMON" ]] && USE_START_STOP_DAEMON="true"
+
+# Create an identity for log/pid files
+if [[ -z "$identity" ]]; then
+ if [[ -n "$init_script" ]]; then
+ identity="${init_script}"
+ else
+ identity=$(basename "${initfile%.*}")_${initfolder//\//}
+ fi
+fi
+
+# Initialize log file name if not provided by the config file
+[[ -z "$LOG_FILENAME" ]] && LOG_FILENAME="${identity}.log"
+
+# ANSI Colors
+echoRed() { echo $'\e[0;31m'"$1"$'\e[0m'; }
+echoGreen() { echo $'\e[0;32m'"$1"$'\e[0m'; }
+echoYellow() { echo $'\e[0;33m'"$1"$'\e[0m'; }
+
+# Utility functions
+checkPermissions() {
+ touch "$pid_file" &> /dev/null || { echoRed "Operation not permitted (cannot access pid file)"; return 4; }
+ touch "$log_file" &> /dev/null || { echoRed "Operation not permitted (cannot access log file)"; return 4; }
+}
+
+isRunning() {
+ ps -p "$1" &> /dev/null
+}
+
+await_file() {
+ end=$(date +%s)
+ let "end+=10"
+ while [[ ! -s "$1" ]]
+ do
+ now=$(date +%s)
+ if [[ $now -ge $end ]]; then
+ break
+ fi
+ sleep 1
+ done
+}
+
+# Determine the script mode
+action="run"
+if [[ "$MODE" == "auto" && -n "$init_script" ]] || [[ "$MODE" == "service" ]]; then
+ action="$1"
+ shift
+fi
+
+# Build the pid and log filenames
+if [[ "$identity" == "$init_script" ]] || [[ "$identity" == "$APP_NAME" ]]; then
+ PID_FOLDER="$PID_FOLDER/${identity}"
+ pid_subfolder=$PID_FOLDER
+fi
+pid_file="$PID_FOLDER/${identity}.pid"
+log_file="$LOG_FOLDER/$LOG_FILENAME"
+
+# Determine the user to run as if we are root
+# shellcheck disable=SC2012
+[[ $(id -u) == "0" ]] && run_user=$(ls -ld "$mainfile" | awk '{print $3}')
+
+arguments=($RUN_ARGS "$@")
+
+# Action functions
+start() {
+ if [[ -f "$pid_file" ]]; then
+ pid=$(cat "$pid_file")
+ isRunning "$pid" && { echoYellow "Already running [$pid]"; return 0; }
+ fi
+ do_start "$@"
+}
+
+do_start() {
+ working_dir=$(dirname "$mainfile")
+ pushd "$working_dir" > /dev/null
+ mkdir -p "$PID_FOLDER" &> /dev/null
+ if [[ -n "$run_user" ]]; then
+ checkPermissions || return $?
+ if [[ -z "$pid_subfolder" ]]; then
+ chown "$run_user" "$pid_subfolder"
+ fi
+ chown "$run_user" "$pid_file"
+ chown "$run_user" "$log_file"
+ if [ $USE_START_STOP_DAEMON = true ] && type start-stop-daemon > /dev/null 2>&1; then
+ start-stop-daemon --start --quiet \
+ --chuid "$run_user" \
+ --name "$identity" \
+ --make-pidfile --pidfile "$pid_file" \
+ --background --no-close \
+ --startas "$mainfile" \
+ --chdir "$working_dir" \
+ -- "${arguments[@]}" \
+ >> "$log_file" 2>&1
+ await_file "$pid_file"
+ else
+ su -s /bin/sh -c "$mainfile $(printf "\"%s\" " "${arguments[@]}") >> \"$log_file\" 2>&1 & echo \$!" "$run_user" > "$pid_file"
+ fi
+ pid=$(cat "$pid_file")
+ else
+ checkPermissions || return $?
+ "$mainfile" "${arguments[@]}" >> "$log_file" 2>&1 &
+ pid=$!
+ disown $pid
+ echo "$pid" > "$pid_file"
+ fi
+ [[ -z $pid ]] && { echoRed "Failed to start"; return 1; }
+ echoGreen "Started [$pid]"
+}
+
+stop() {
+ working_dir=$(dirname "$mainfile")
+ pushd "$working_dir" > /dev/null
+ [[ -f $pid_file ]] || { echoYellow "Not running (pidfile not found)"; return 0; }
+ pid=$(cat "$pid_file")
+ isRunning "$pid" || { echoYellow "Not running (process ${pid}). Removing stale pid file."; rm -f "$pid_file"; return 0; }
+ do_stop "$pid" "$pid_file"
+}
+
+do_stop() {
+ kill -2 "$1" &> /dev/null || { echoRed "Unable to kill process $1"; return 1; }
+ for i in $(seq 1 60); do
+ isRunning "$1" || { echoGreen "Stopped [$1]"; rm -f "$2"; return 0; }
+ [[ $i -eq 30 ]] && kill -9 "$1" &> /dev/null
+ sleep 1
+ done
+ echoRed "Unable to kill process $1";
+ return 1;
+}
+
+restart() {
+ stop && start
+}
+
+orce_reload() {
+ working_dir=$(dirname "$mainfile")
+ pushd "$working_dir" > /dev/null
+ [[ -f $pid_file ]] || { echoRed "Not running (pidfile not found)"; return 7; }
+ pid=$(cat "$pid_file")
+ rm -f "$pid_file"
+ isRunning "$pid" || { echoRed "Not running (process ${pid} not found)"; return 7; }
+ do_stop "$pid" "$pid_file"
+ do_start
+}
+
+status() {
+ working_dir=$(dirname "$mainfile")
+ pushd "$working_dir" > /dev/null
+ [[ -f "$pid_file" ]] || { echoRed "Not running"; return 3; }
+ pid=$(cat "$pid_file")
+ isRunning "$pid" || { echoRed "Not running (process ${pid} not found)"; return 1; }
+ echoGreen "Running [$pid]"
+ return 0
+}
+
+run() {
+ pushd "$(dirname "$mainfile")" > /dev/null
+ "$mainfile" "${arguments[@]}"
+ result=$?
+ popd > /dev/null
+ return "$result"
+}
+
+# Call the appropriate action function
+case "$action" in
+start)
+ start "$@"; exit $?;;
+stop)
+ stop "$@"; exit $?;;
+restart)
+ restart "$@"; exit $?;;
+force-reload)
+ force_reload "$@"; exit $?;;
+status)
+ status "$@"; exit $?;;
+run)
+ run "$@"; exit $?;;
+*)
+ echo "Usage: $0 {start|stop|restart|force-reload|status|run}"; exit 1;
+esac
+
+exit 0
diff --git a/msa/web-ui/src/main/scripts/windows/install.bat b/msa/web-ui/src/main/scripts/windows/install.bat
new file mode 100644
index 0000000..4da5542
--- /dev/null
+++ b/msa/web-ui/src/main/scripts/windows/install.bat
@@ -0,0 +1,31 @@
+@REM
+@REM Copyright © 2016-2018 The Thingsboard Authors
+@REM
+@REM Licensed under the Apache License, Version 2.0 (the "License");
+@REM you may not use this file except in compliance with the License.
+@REM You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing, software
+@REM distributed under the License is distributed on an "AS IS" BASIS,
+@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@REM See the License for the specific language governing permissions and
+@REM limitations under the License.
+@REM
+
+@ECHO OFF
+
+setlocal ENABLEEXTENSIONS
+
+@ECHO Installing ${pkg.name} ...
+
+SET BASE=%~dp0
+
+%BASE%${pkg.name}.exe install
+
+@ECHO ${pkg.name} installed successfully!
+
+GOTO END
+
+:END
diff --git a/msa/web-ui/src/main/scripts/windows/service.xml b/msa/web-ui/src/main/scripts/windows/service.xml
new file mode 100644
index 0000000..e512aad
--- /dev/null
+++ b/msa/web-ui/src/main/scripts/windows/service.xml
@@ -0,0 +1,30 @@
+<!--
+
+ Copyright © 2016-2018 The Thingsboard Authors
+
+ Licensed 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.
+
+-->
+<service>
+ <id>${pkg.name}</id>
+ <name>${project.name}</name>
+ <description>${project.description}</description>
+ <workingdirectory>%BASE%\bin</workingdirectory>
+ <logpath>${pkg.winWrapperLogFolder}</logpath>
+ <logmode>rotate</logmode>
+ <env name="NODE_CONFIG_DIR" value="%BASE%\conf" />
+ <env name="LOG_FOLDER" value="${pkg.winWrapperLogFolder}" />
+ <env name="NODE_ENV" value="production" />
+ <env name="WEB_FOLDER" value="%BASE%\web" />
+ <executable>%BASE%\bin\${pkg.name}.exe</executable>
+</service>
diff --git a/msa/web-ui/src/main/scripts/windows/uninstall.bat b/msa/web-ui/src/main/scripts/windows/uninstall.bat
new file mode 100644
index 0000000..7061d2a
--- /dev/null
+++ b/msa/web-ui/src/main/scripts/windows/uninstall.bat
@@ -0,0 +1,25 @@
+@REM
+@REM Copyright © 2016-2018 The Thingsboard Authors
+@REM
+@REM Licensed under the Apache License, Version 2.0 (the "License");
+@REM you may not use this file except in compliance with the License.
+@REM You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing, software
+@REM distributed under the License is distributed on an "AS IS" BASIS,
+@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@REM See the License for the specific language governing permissions and
+@REM limitations under the License.
+@REM
+
+@ECHO OFF
+
+@ECHO Stopping ${pkg.name} ...
+net stop ${pkg.name}
+
+@ECHO Uninstalling ${pkg.name} ...
+%~dp0${pkg.name}.exe uninstall
+
+@ECHO DONE.
\ No newline at end of file
ui/package.json 2(+1 -1)
diff --git a/ui/package.json b/ui/package.json
index e730c39..417948e 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -2,7 +2,7 @@
"name": "thingsboard",
"private": true,
"version": "2.2.0",
- "description": "Thingsboard UI",
+ "description": "ThingsBoard UI",
"licenses": [
{
"type": "Apache-2.0",