From 4bf57ebf20d83aa922f95aae80b5dba3e36aadfa Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 2 Oct 2018 17:42:09 +0300 Subject: [PATCH] ThingsBoard Web UI Microservice. --- msa/docker/docker-compose.yml | 13 +- msa/docker/tb-web-ui.env | 9 + msa/pom.xml | 1 + msa/web-ui/.gitignore | 31 ++ msa/web-ui/build.gradle | 125 ++++++ .../config/custom-environment-variables.yml | 30 ++ msa/web-ui/config/default.yml | 30 ++ msa/web-ui/config/logger.js | 59 +++ msa/web-ui/config/tb-web-ui.conf | 20 + msa/web-ui/docker/Dockerfile | 26 ++ msa/web-ui/docker/start-web-ui.sh | 29 ++ msa/web-ui/install.js | 42 ++ msa/web-ui/package.json | 38 ++ msa/web-ui/pom.xml | 372 ++++++++++++++++++ msa/web-ui/server.js | 129 ++++++ msa/web-ui/src/main/assembly/windows.xml | 75 ++++ msa/web-ui/src/main/filters/unix.properties | 1 + .../src/main/filters/windows.properties | 2 + .../src/main/scripts/control/deb/postinst | 6 + .../src/main/scripts/control/deb/postrm | 3 + .../src/main/scripts/control/deb/preinst | 18 + msa/web-ui/src/main/scripts/control/deb/prerm | 5 + .../src/main/scripts/control/rpm/postinst | 9 + .../src/main/scripts/control/rpm/postrm | 6 + .../src/main/scripts/control/rpm/preinst | 6 + msa/web-ui/src/main/scripts/control/rpm/prerm | 6 + .../main/scripts/control/tb-web-ui.service | 11 + msa/web-ui/src/main/scripts/init/tb-web-ui | 233 +++++++++++ .../src/main/scripts/windows/install.bat | 31 ++ .../src/main/scripts/windows/service.xml | 30 ++ .../src/main/scripts/windows/uninstall.bat | 25 ++ ui/package.json | 2 +- 32 files changed, 1421 insertions(+), 2 deletions(-) create mode 100644 msa/docker/tb-web-ui.env create mode 100644 msa/web-ui/.gitignore create mode 100644 msa/web-ui/build.gradle create mode 100644 msa/web-ui/config/custom-environment-variables.yml create mode 100644 msa/web-ui/config/default.yml create mode 100644 msa/web-ui/config/logger.js create mode 100644 msa/web-ui/config/tb-web-ui.conf create mode 100644 msa/web-ui/docker/Dockerfile create mode 100755 msa/web-ui/docker/start-web-ui.sh create mode 100644 msa/web-ui/install.js create mode 100644 msa/web-ui/package.json create mode 100644 msa/web-ui/pom.xml create mode 100644 msa/web-ui/server.js create mode 100644 msa/web-ui/src/main/assembly/windows.xml create mode 100644 msa/web-ui/src/main/filters/unix.properties create mode 100644 msa/web-ui/src/main/filters/windows.properties create mode 100644 msa/web-ui/src/main/scripts/control/deb/postinst create mode 100644 msa/web-ui/src/main/scripts/control/deb/postrm create mode 100644 msa/web-ui/src/main/scripts/control/deb/preinst create mode 100644 msa/web-ui/src/main/scripts/control/deb/prerm create mode 100644 msa/web-ui/src/main/scripts/control/rpm/postinst create mode 100644 msa/web-ui/src/main/scripts/control/rpm/postrm create mode 100644 msa/web-ui/src/main/scripts/control/rpm/preinst create mode 100644 msa/web-ui/src/main/scripts/control/rpm/prerm create mode 100644 msa/web-ui/src/main/scripts/control/tb-web-ui.service create mode 100644 msa/web-ui/src/main/scripts/init/tb-web-ui create mode 100644 msa/web-ui/src/main/scripts/windows/install.bat create mode 100644 msa/web-ui/src/main/scripts/windows/service.xml create mode 100644 msa/web-ui/src/main/scripts/windows/uninstall.bat diff --git a/msa/docker/docker-compose.yml b/msa/docker/docker-compose.yml index a077c0ee1a..727936c2f6 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 diff --git a/msa/docker/tb-web-ui.env b/msa/docker/tb-web-ui.env new file mode 100644 index 0000000000..8d6157bfc8 --- /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 diff --git a/msa/pom.xml b/msa/pom.xml index 85d45a1f76..3241d0cf51 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -36,6 +36,7 @@ js-executor + web-ui diff --git a/msa/web-ui/.gitignore b/msa/web-ui/.gitignore new file mode 100644 index 0000000000..c5cfd3d322 --- /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 diff --git a/msa/web-ui/build.gradle b/msa/web-ui/build.gradle new file mode 100644 index 0000000000..7372c0a819 --- /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 0000000000..9472a50571 --- /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" diff --git a/msa/web-ui/config/default.yml b/msa/web-ui/config/default.yml new file mode 100644 index 0000000000..cf27a14015 --- /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" diff --git a/msa/web-ui/config/logger.js b/msa/web-ui/config/logger.js new file mode 100644 index 0000000000..695b453763 --- /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 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 0000000000..d002038d40 --- /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 diff --git a/msa/web-ui/docker/Dockerfile b/msa/web-ui/docker/Dockerfile new file mode 100644 index 0000000000..7a75cc3f1f --- /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"] 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 0000000000..af7c686a11 --- /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" diff --git a/msa/web-ui/install.js b/msa/web-ui/install.js new file mode 100644 index 0000000000..63e73dbe56 --- /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); +} diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json new file mode 100644 index 0000000000..f30f2fc3a6 --- /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" + } +} diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml new file mode 100644 index 0000000000..54c3bb45f5 --- /dev/null +++ b/msa/web-ui/pom.xml @@ -0,0 +1,372 @@ + + + 4.0.0 + + org.thingsboard + 2.2.0-SNAPSHOT + msa + + org.thingsboard.msa + web-ui + pom + + ThingsBoard Web UI Microservice + https://thingsboard.io + Service for hosting ThingsBoard Web UI + + + UTF-8 + ${basedir}/../.. + tb-web-ui + thingsboard + /var/log/${pkg.name} + /usr/share/${pkg.name} + ${project.build.directory}/package/linux + ${project.build.directory}/package/windows + true + + + + + org.thingsboard + ui + ${project.version} + jar + provided + + + com.sun.winsw + winsw + bin + exe + provided + + + + + + + com.github.eirslett + frontend-maven-plugin + 1.0 + + target + ${basedir} + + + + install node and npm + + install-node-and-npm + + + v8.11.3 + 5.6.0 + + + + npm install + + npm + + + install + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + extract-web-ui + + unpack + + + + + org.thingsboard + ui + jar + false + ${project.build.directory}/web + + + + + + copy-winsw-service + package + + copy + + + + + com.sun.winsw + winsw + bin + exe + service.exe + + + ${pkg.win.dist} + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-linux-conf + process-resources + + copy-resources + + + ${pkg.linux.dist}/conf + + + config + true + + + + src/main/filters/unix.properties + + + + + copy-linux-init + process-resources + + copy-resources + + + ${pkg.linux.dist}/init + + + src/main/scripts/init + true + + + + src/main/filters/unix.properties + + + + + copy-win-conf + process-resources + + copy-resources + + + ${pkg.win.dist}/conf + + + config + + tb-web-ui.conf + + true + + + + src/main/filters/windows.properties + + + + + copy-control + process-resources + + copy-resources + + + ${project.build.directory}/control + + + src/main/scripts/control + true + + + + src/main/filters/unix.properties + + + + + copy-windows-control + process-resources + + copy-resources + + + ${pkg.win.dist} + + + src/main/scripts/windows + true + + + + src/main/filters/windows.properties + + + + + copy-docker-config + process-resources + + copy-resources + + + ${project.build.directory} + + + docker + true + + + + + + + + org.fortasoft + gradle-maven-plugin + + + build + buildDeb + buildRpm + + + -PprojectBuildDir=${project.build.directory} + -PprojectVersion=${project.version} + -PpkgName=${pkg.name} + -PpkgUser=${pkg.user} + -PpkgInstallFolder=${pkg.installFolder} + -PpkgLogFolder=${pkg.unixLogFolder} + + + + + package + + invoke + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.0.0 + + ${pkg.name} + + src/main/assembly/windows.xml + + + + + assembly + package + + single + + + + + + com.spotify + dockerfile-maven-plugin + 1.4.4 + + + build-docker-image + pre-integration-test + + build + + + + + ${dockerfile.skip} + local-maven-build/${pkg.name} + true + false + ${project.build.directory} + + + + + + + npm-start + + + npm-start + + + + + + com.github.eirslett + frontend-maven-plugin + 1.0 + + target + ${basedir} + + + + npm start + + npm + + + + start + + + + + + + + + + + jenkins + Jenkins Repository + http://repo.jenkins-ci.org/releases + + false + + + + diff --git a/msa/web-ui/server.js b/msa/web-ui/server.js new file mode 100644 index 0000000000..46d9bfd7a9 --- /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); + } +} 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 0000000000..12119906c1 --- /dev/null +++ b/msa/web-ui/src/main/assembly/windows.xml @@ -0,0 +1,75 @@ + + + windows + + + zip + + + + + + ${pkg.win.dist} + logs + + */** + + + + ${pkg.win.dist}/conf + conf + windows + + + ${project.build.directory}/web + web + + + + + + ${pkg.win.dist}/bin/${pkg.name}.exe + bin + ${pkg.name}.exe + + + ${pkg.win.dist}/service.exe + + ${pkg.name}.exe + + + ${pkg.win.dist}/service.xml + + ${pkg.name}.xml + windows + + + ${pkg.win.dist}/install.bat + + windows + + + ${pkg.win.dist}/uninstall.bat + + windows + + + 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 0000000000..8967278673 --- /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 0000000000..a6e48d91ba --- /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 0000000000..4c48c5508a --- /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 0000000000..61865803c3 --- /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 0000000000..d2ebea46d7 --- /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 0000000000..898d3efd5c --- /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 0000000000..d8021e2dd9 --- /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 0000000000..8e1f8a2048 --- /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 0000000000..db6306e4ac --- /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 0000000000..accb487b8e --- /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 0000000000..f542dd0f92 --- /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 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 0000000000..d0d3f95fc9 --- /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 0000000000..4da55425bb --- /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 0000000000..e512aad163 --- /dev/null +++ b/msa/web-ui/src/main/scripts/windows/service.xml @@ -0,0 +1,30 @@ + + + ${pkg.name} + ${project.name} + ${project.description} + %BASE%\bin + ${pkg.winWrapperLogFolder} + rotate + + + + + %BASE%\bin\${pkg.name}.exe + 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 0000000000..7061d2a052 --- /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 diff --git a/ui/package.json b/ui/package.json index e730c39302..417948e1c9 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",