Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard

pull/1589/head
ShvaykaD 8 years ago
parent
commit
58530f8ce9
  1. 2
      application/src/main/data/json/system/widget_bundles/maps.json
  2. 37
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
  3. 19
      application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java
  4. 10
      application/src/main/resources/thingsboard.yml
  5. 2
      k8s/postgres.yml
  6. 8
      pom.xml
  7. 3
      ui/src/app/admin/admin.controller.js
  8. 4
      ui/src/app/admin/general-settings.tpl.html
  9. 4
      ui/src/app/admin/outgoing-mail-settings.tpl.html
  10. 23
      ui/src/app/admin/settings-card.scss
  11. 1
      ui/src/app/alarm/alarm-details-dialog.controller.js
  12. 1
      ui/src/app/audit/audit-log-details-dialog.controller.js
  13. 2
      ui/src/app/common/utils.service.js
  14. 2
      ui/src/app/components/contact.directive.js
  15. 1
      ui/src/app/components/js-func.directive.js
  16. 1
      ui/src/app/components/json-content.directive.js
  17. 1
      ui/src/app/components/json-object-edit.directive.js
  18. 1
      ui/src/app/components/react/json-form-ace-editor.jsx
  19. 1
      ui/src/app/entity/relation/relation-dialog.controller.js
  20. 1
      ui/src/app/event/event-content-dialog.controller.js
  21. 1
      ui/src/app/extension/extensions-forms/extension-form-http.directive.js
  22. 1
      ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js
  23. 1
      ui/src/app/extension/extensions-forms/extension-form-opc.directive.js
  24. 3
      ui/src/app/locale/locale.constant-de_DE.json
  25. 6
      ui/src/app/locale/locale.constant-en_US.json
  26. 3
      ui/src/app/locale/locale.constant-es_ES.json
  27. 3
      ui/src/app/locale/locale.constant-fa_IR.json
  28. 3
      ui/src/app/locale/locale.constant-fr_FR.json
  29. 3
      ui/src/app/locale/locale.constant-it_IT.json
  30. 19
      ui/src/app/locale/locale.constant-ja_JA.json
  31. 3
      ui/src/app/locale/locale.constant-ko_KR.json
  32. 6
      ui/src/app/locale/locale.constant-ru_RU.json
  33. 19
      ui/src/app/locale/locale.constant-tr_TR.json
  34. 2183
      ui/src/app/locale/locale.constant-uk_UA.json
  35. 15
      ui/src/app/locale/locale.constant-zh_CN.json
  36. 16
      ui/src/app/rulechain/rulechain.controller.js
  37. 9
      ui/src/app/rulechain/rulechain.tpl.html
  38. 10
      ui/src/app/widget/lib/flot-widget.js
  39. 151
      ui/src/app/widget/lib/tripAnimation/trip-animation-widget.js
  40. 162
      ui/src/app/widget/lib/tripAnimation/trip-animation-widget.scss
  41. 59
      ui/src/app/widget/lib/tripAnimation/trip-animation-widget.tpl.html
  42. 1
      ui/src/app/widget/widget-editor.controller.js

2
application/src/main/data/json/system/widget_bundles/maps.json

File diff suppressed because one or more lines are too long

37
application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java

@ -52,6 +52,8 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED; import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED;
@ -96,11 +98,13 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
@Lazy @Lazy
private ClusterRoutingService routingService; private ClusterRoutingService routingService;
private ExecutorService reconnectExecutorService;
private CuratorFramework client; private CuratorFramework client;
private PathChildrenCache cache; private PathChildrenCache cache;
private String nodePath; private String nodePath;
private volatile boolean stopped = false; private volatile boolean stopped = true;
@PostConstruct @PostConstruct
public void init() { public void init() {
@ -110,9 +114,15 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms")); Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms"));
Assert.notNull(zkSessionTimeout, MiscUtils.missingProperty("zk.session_timeout_ms")); Assert.notNull(zkSessionTimeout, MiscUtils.missingProperty("zk.session_timeout_ms"));
reconnectExecutorService = Executors.newSingleThreadExecutor();
log.info("Initializing discovery service using ZK connect string: {}", zkUrl); log.info("Initializing discovery service using ZK connect string: {}", zkUrl);
zkNodesDir = zkDir + "/nodes"; zkNodesDir = zkDir + "/nodes";
initZkClient();
}
private void initZkClient() {
try { try {
client = CuratorFrameworkFactory.newClient(zkUrl, zkSessionTimeout, zkConnectionTimeout, new RetryForever(zkRetryInterval)); client = CuratorFrameworkFactory.newClient(zkUrl, zkSessionTimeout, zkConnectionTimeout, new RetryForever(zkRetryInterval));
client.start(); client.start();
@ -120,6 +130,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
cache = new PathChildrenCache(client, zkNodesDir, true); cache = new PathChildrenCache(client, zkNodesDir, true);
cache.getListenable().addListener(this); cache.getListenable().addListener(this);
cache.start(); cache.start();
stopped = false;
log.info("ZK client connected");
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to connect to ZK: {}", e.getMessage(), e); log.error("Failed to connect to ZK: {}", e.getMessage(), e);
CloseableUtils.closeQuietly(cache); CloseableUtils.closeQuietly(cache);
@ -128,12 +140,20 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
} }
} }
@PreDestroy private void destroyZkClient() {
public void destroy() {
stopped = true; stopped = true;
unpublishCurrentServer(); try {
unpublishCurrentServer();
} catch (Exception e) {}
CloseableUtils.closeQuietly(cache); CloseableUtils.closeQuietly(cache);
CloseableUtils.closeQuietly(client); CloseableUtils.closeQuietly(client);
log.info("ZK client disconnected");
}
@PreDestroy
public void destroy() {
destroyZkClient();
reconnectExecutorService.shutdownNow();
log.info("Stopped discovery service"); log.info("Stopped discovery service");
} }
@ -180,20 +200,21 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
return (client, newState) -> { return (client, newState) -> {
log.info("[{}:{}] ZK state changed: {}", self.getHost(), self.getPort(), newState); log.info("[{}:{}] ZK state changed: {}", self.getHost(), self.getPort(), newState);
if (newState == ConnectionState.LOST) { if (newState == ConnectionState.LOST) {
reconnect(); reconnectExecutorService.submit(this::reconnect);
} }
}; };
} }
private boolean reconnectInProgress = false; private volatile boolean reconnectInProgress = false;
private synchronized void reconnect() { private synchronized void reconnect() {
if (!reconnectInProgress) { if (!reconnectInProgress) {
reconnectInProgress = true; reconnectInProgress = true;
try { try {
client.blockUntilConnected(); destroyZkClient();
initZkClient();
publishCurrentServer(); publishCurrentServer();
} catch (InterruptedException e) { } catch (Exception e) {
log.error("Failed to reconnect to ZK: {}", e.getMessage(), e); log.error("Failed to reconnect to ZK: {}", e.getMessage(), e);
} finally { } finally {
reconnectInProgress = false; reconnectInProgress = false;

19
application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java

@ -95,15 +95,24 @@ public final class GrpcSession implements Closeable {
} }
public void sendMsg(ClusterAPIProtos.ClusterMessage msg) { public void sendMsg(ClusterAPIProtos.ClusterMessage msg) {
outputStream.onNext(msg); if (connected) {
} try {
outputStream.onNext(msg);
public void onError(Throwable t) { } catch (Throwable t) {
outputStream.onError(t); try {
outputStream.onError(t);
} catch (Throwable t2) {
}
listener.onError(GrpcSession.this, t);
}
} else {
log.warn("[{}] Failed to send message due to closed session!", sessionId);
}
} }
@Override @Override
public void close() { public void close() {
connected = false;
try { try {
outputStream.onCompleted(); outputStream.onCompleted();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {

10
application/src/main/resources/thingsboard.yml

@ -273,23 +273,17 @@ updates:
spring.mvc.cors: spring.mvc.cors:
mappings: mappings:
# Intercept path # Intercept path
"/api/auth/**": "[/api/**]":
#Comma-separated list of origins to allow. '*' allows all origins. When not set,CORS support is disabled. #Comma-separated list of origins to allow. '*' allows all origins. When not set,CORS support is disabled.
allowed-origins: "*" allowed-origins: "*"
#Comma-separated list of methods to allow. '*' allows all methods. #Comma-separated list of methods to allow. '*' allows all methods.
allowed-methods: "POST,GET,OPTIONS" allowed-methods: "*"
#Comma-separated list of headers to allow in a request. '*' allows all headers. #Comma-separated list of headers to allow in a request. '*' allows all headers.
allowed-headers: "*" allowed-headers: "*"
#How long, in seconds, the response from a pre-flight request can be cached by clients. #How long, in seconds, the response from a pre-flight request can be cached by clients.
max-age: "1800" max-age: "1800"
#Set whether credentials are supported. When not set, credentials are not supported. #Set whether credentials are supported. When not set, credentials are not supported.
allow-credentials: "true" allow-credentials: "true"
"/api/v1/**":
allowed-origins: "*"
allowed-methods: "*"
allowed-headers: "*"
max-age: "1800"
allow-credentials: "true"
# spring serve gzip compressed static resources # spring serve gzip compressed static resources
spring.resources.chain: spring.resources.chain:

2
k8s/postgres.yml

@ -55,6 +55,8 @@ spec:
env: env:
- name: POSTGRES_DB - name: POSTGRES_DB
value: "thingsboard" value: "thingsboard"
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts: volumeMounts:
- mountPath: /var/lib/postgresql/data - mountPath: /var/lib/postgresql/data
name: postgres-data name: postgres-data

8
pom.xml

@ -50,7 +50,7 @@
<commons-validator.version>1.5.0</commons-validator.version> <commons-validator.version>1.5.0</commons-validator.version>
<commons-io.version>2.5</commons-io.version> <commons-io.version>2.5</commons-io.version>
<commons-csv.version>1.4</commons-csv.version> <commons-csv.version>1.4</commons-csv.version>
<jackson.version>2.9.7</jackson.version> <jackson.version>2.9.8</jackson.version>
<json-schema-validator.version>2.2.6</json-schema-validator.version> <json-schema-validator.version>2.2.6</json-schema-validator.version>
<scala.version>2.11</scala.version> <scala.version>2.11</scala.version>
<akka.version>2.4.2</akka.version> <akka.version>2.4.2</akka.version>
@ -59,12 +59,12 @@
<velocity.version>1.7</velocity.version> <velocity.version>1.7</velocity.version>
<velocity-tools.version>2.0</velocity-tools.version> <velocity-tools.version>2.0</velocity-tools.version>
<mail.version>1.4.3</mail.version> <mail.version>1.4.3</mail.version>
<curator.version>4.0.1</curator.version> <curator.version>4.2.0</curator.version>
<protobuf.version>3.6.1</protobuf.version> <protobuf.version>3.6.1</protobuf.version>
<grpc.version>1.16.1</grpc.version> <grpc.version>1.19.0</grpc.version>
<lombok.version>1.16.18</lombok.version> <lombok.version>1.16.18</lombok.version>
<paho.client.version>1.1.0</paho.client.version> <paho.client.version>1.1.0</paho.client.version>
<netty.version>4.1.30.Final</netty.version> <netty.version>4.1.34.Final</netty.version>
<os-maven-plugin.version>1.5.0</os-maven-plugin.version> <os-maven-plugin.version>1.5.0</os-maven-plugin.version>
<rabbitmq.version>4.8.0</rabbitmq.version> <rabbitmq.version>4.8.0</rabbitmq.version>
<surfire.version>2.19.1</surfire.version> <surfire.version>2.19.1</surfire.version>

3
ui/src/app/admin/admin.controller.js

@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import './settings-card.scss';
/*@ngInject*/ /*@ngInject*/
export default function AdminController(adminService, toast, $scope, $rootScope, $state, $translate) { export default function AdminController(adminService, toast, $scope, $rootScope, $state, $translate) {

4
ui/src/app/admin/general-settings.tpl.html

@ -15,8 +15,8 @@
limitations under the License. limitations under the License.
--> -->
<div layout="row" width="100%" layout-wrap> <div>
<md-card flex-gt-sm="60" flex="100" style="height: 100%;"> <md-card class="settings-card">
<md-card-title> <md-card-title>
<md-card-title-text> <md-card-title-text>
<span translate class="md-headline">admin.general-settings</span> <span translate class="md-headline">admin.general-settings</span>

4
ui/src/app/admin/outgoing-mail-settings.tpl.html

@ -15,8 +15,8 @@
limitations under the License. limitations under the License.
--> -->
<div layout="row" width="100%" layout-wrap tb-help="'outgoingMailSettings'" help-container-id="help-container"> <div tb-help="'outgoingMailSettings'" help-container-id="help-container">
<md-card flex-gt-sm="60" flex="100" style="height: 100%;"> <md-card class="settings-card">
<md-card-title> <md-card-title>
<md-card-title-text layout="row"> <md-card-title-text layout="row">
<span translate class="md-headline">admin.outgoing-mail-settings</span> <span translate class="md-headline">admin.outgoing-mail-settings</span>

23
ui/src/app/admin/settings-card.scss

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2019 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 "../../scss/constants";
md-card.settings-card {
@media (min-width: $layout-breakpoint-sm) {
width: 60%;
}
}

1
ui/src/app/alarm/alarm-details-dialog.controller.js

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import 'brace/ext/language_tools'; import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/json'; import 'brace/mode/json';
import 'brace/theme/github'; import 'brace/theme/github';
import beautify from 'js-beautify'; import beautify from 'js-beautify';

1
ui/src/app/audit/audit-log-details-dialog.controller.js

@ -15,6 +15,7 @@
*/ */
import $ from 'jquery'; import $ from 'jquery';
import 'brace/ext/language_tools'; import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/java'; import 'brace/mode/java';
import 'brace/theme/github'; import 'brace/theme/github';

2
ui/src/app/common/utils.service.js

@ -499,6 +499,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
label = label.split(variable).join(datasource.entityName); label = label.split(variable).join(datasource.entityName);
} else if (variableName === 'aliasName') { } else if (variableName === 'aliasName') {
label = label.split(variable).join(datasource.aliasName); label = label.split(variable).join(datasource.aliasName);
} else if (variableName === 'entityDescription') {
label = label.split(variable).join(datasource.entityDescription);
} }
match = varsRegex.exec(pattern); match = varsRegex.exec(pattern);
} }

2
ui/src/app/components/contact.directive.js

@ -283,7 +283,7 @@ function Contact($compile, $templateCache) {
"Austria": "[0-9]{4}", "Austria": "[0-9]{4}",
"Belgium": "[0-9]{4}", "Belgium": "[0-9]{4}",
"Brazil": "[0-9]{5}[\\-]?[0-9]{3}", "Brazil": "[0-9]{5}[\\-]?[0-9]{3}",
"Canada": "[A-Za-z][0-9][A-Za-z] [0-9][A-Za-z][0-9]", "Canada": "^(?!.*[DFIOQU])[A-VXY][0-9][A-Z][ -]?[0-9][A-Z][0-9]$",
"Denmark": "[0-9]{3,4}", "Denmark": "[0-9]{3,4}",
"Faroe Islands": "[0-9]{3,4}", "Faroe Islands": "[0-9]{3,4}",
"Netherlands": "[1-9][0-9]{3}\\s?[a-zA-Z]{2}", "Netherlands": "[1-9][0-9]{3}\\s?[a-zA-Z]{2}",

1
ui/src/app/components/js-func.directive.js

@ -17,6 +17,7 @@ import './js-func.scss';
import ace from 'brace'; import ace from 'brace';
import 'brace/ext/language_tools'; import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import $ from 'jquery'; import $ from 'jquery';
import thingsboardToast from '../services/toast'; import thingsboardToast from '../services/toast';
import thingsboardUtils from '../common/utils.service'; import thingsboardUtils from '../common/utils.service';

1
ui/src/app/components/json-content.directive.js

@ -16,6 +16,7 @@
import './json-content.scss'; import './json-content.scss';
import 'brace/ext/language_tools'; import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/json'; import 'brace/mode/json';
import 'brace/mode/text'; import 'brace/mode/text';
import 'brace/snippets/json'; import 'brace/snippets/json';

1
ui/src/app/components/json-object-edit.directive.js

@ -16,6 +16,7 @@
import './json-object-edit.scss'; import './json-object-edit.scss';
import 'brace/ext/language_tools'; import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/json'; import 'brace/mode/json';
import 'brace/snippets/json'; import 'brace/snippets/json';

1
ui/src/app/components/react/json-form-ace-editor.jsx

@ -21,6 +21,7 @@ import reactCSS from 'reactcss';
import AceEditor from 'react-ace'; import AceEditor from 'react-ace';
import FlatButton from 'material-ui/FlatButton'; import FlatButton from 'material-ui/FlatButton';
import 'brace/ext/language_tools'; import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/theme/github'; import 'brace/theme/github';
import fixAceEditor from './../ace-editor-fix'; import fixAceEditor from './../ace-editor-fix';

1
ui/src/app/entity/relation/relation-dialog.controller.js

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import 'brace/ext/language_tools'; import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/json'; import 'brace/mode/json';
import 'brace/theme/github'; import 'brace/theme/github';
import beautify from 'js-beautify'; import beautify from 'js-beautify';

1
ui/src/app/event/event-content-dialog.controller.js

@ -15,6 +15,7 @@
*/ */
import $ from 'jquery'; import $ from 'jquery';
import 'brace/ext/language_tools'; import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/java'; import 'brace/mode/java';
import 'brace/theme/github'; import 'brace/theme/github';
import beautify from 'js-beautify'; import beautify from 'js-beautify';

1
ui/src/app/extension/extensions-forms/extension-form-http.directive.js

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import 'brace/ext/language_tools'; import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/json'; import 'brace/mode/json';
import 'brace/theme/github'; import 'brace/theme/github';

1
ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import 'brace/ext/language_tools'; import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/json'; import 'brace/mode/json';
import 'brace/theme/github'; import 'brace/theme/github';

1
ui/src/app/extension/extensions-forms/extension-form-opc.directive.js

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import 'brace/ext/language_tools'; import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/json'; import 'brace/mode/json';
import 'brace/theme/github'; import 'brace/theme/github';

3
ui/src/app/locale/locale.constant-de_DE.json

@ -1577,7 +1577,8 @@
"es_ES": "Spanisch", "es_ES": "Spanisch",
"ja_JA": "Japanisch", "ja_JA": "Japanisch",
"tr_TR": "Türkisch", "tr_TR": "Türkisch",
"fa_IR": "Persisch" "fa_IR": "Persisch",
"uk_UA": "Ukrainisch"
} }
} }
} }

6
ui/src/app/locale/locale.constant-en_US.json

@ -1295,7 +1295,8 @@
"metadata-required": "Metadata entries can't be empty.", "metadata-required": "Metadata entries can't be empty.",
"output": "Output", "output": "Output",
"test": "Test", "test": "Test",
"help": "Help" "help": "Help",
"reset-debug-mode": "Reset debug mode in all nodes"
}, },
"tenant": { "tenant": {
"tenant": "Tenant", "tenant": "Tenant",
@ -1583,7 +1584,8 @@
"es_ES": "Spanish", "es_ES": "Spanish",
"ja_JA": "Japanese", "ja_JA": "Japanese",
"tr_TR": "Turkish", "tr_TR": "Turkish",
"fa_IR": "Persian" "fa_IR": "Persian",
"uk_UA": "Ukrainian"
} }
} }
} }

3
ui/src/app/locale/locale.constant-es_ES.json

@ -1577,7 +1577,8 @@
"es_ES": "Español", "es_ES": "Español",
"ja_JA": "Japonés", "ja_JA": "Japonés",
"tr_TR": "Turco", "tr_TR": "Turco",
"fa_IR": "Persa" "fa_IR": "Persa",
"uk_UA": "Ucraniano"
} }
} }
} }

3
ui/src/app/locale/locale.constant-fa_IR.json

@ -1576,7 +1576,8 @@
"es_ES": "اسپانيولي", "es_ES": "اسپانيولي",
"ja_JA": "ژاپني", "ja_JA": "ژاپني",
"tr_TR": "ترکي", "tr_TR": "ترکي",
"fa_IR": "فارسي" "fa_IR": "فارسي",
"uk_UA": "اوکراین"
} }
} }
} }

3
ui/src/app/locale/locale.constant-fr_FR.json

@ -1014,7 +1014,8 @@
"zh_CN": "Chinois", "zh_CN": "Chinois",
"ja_JA": "Japonaise", "ja_JA": "Japonaise",
"tr_TR": "Turc", "tr_TR": "Turc",
"fa_IR": "Persane" "fa_IR": "Persane",
"uk_UA": "Ukrainien"
} }
}, },
"layout": { "layout": {

3
ui/src/app/locale/locale.constant-it_IT.json

@ -1582,7 +1582,8 @@
"es_ES": "Spagnolo", "es_ES": "Spagnolo",
"ja_JA": "Giapponese", "ja_JA": "Giapponese",
"tr_TR": "Turco", "tr_TR": "Turco",
"fa_IR": "Persiana" "fa_IR": "Persiana",
"uk_UA": "Ucraino"
} }
} }
} }

19
ui/src/app/locale/locale.constant-ja_JA.json

@ -1205,10 +1205,10 @@
"tenant-required": "テナントが必要です" "tenant-required": "テナントが必要です"
}, },
"timeinterval": { "timeinterval": {
"seconds-interval": "{ seconds, select, 1 {1 second} other {# seconds} }", "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",
"minutes-interval": "{ minutes, select, 1 {1 minute} other {# minutes} }", "minutes-interval": "{ minutes, plural, 1 {1 minute} other {# minutes} }",
"hours-interval": "{ hours, select, 1 {1 hour} other {# hours} }", "hours-interval": "{ hours, plural, 1 {1 hour} other {# hours} }",
"days-interval": "{ days, select, 1 {1 day} other {# days} }", "days-interval": "{ days, plural, 1 {1 day} other {# days} }",
"days": "日々", "days": "日々",
"hours": "時間", "hours": "時間",
"minutes": "分", "minutes": "分",
@ -1216,10 +1216,10 @@
"advanced": "上級" "advanced": "上級"
}, },
"timewindow": { "timewindow": {
"days": "{ days, select, 1 { day } other {# days } }", "days": "{ days, plural, 1 { day } other {# days } }",
"hours": "{ hours, select, 0 { hour } 1 {1 hour } other {# hours } }", "hours": "{ hours, plural, 0 { hour } 1 {1 hour } other {# hours } }",
"minutes": "{ minutes, select, 0 { minute } 1 {1 minute } other {# minutes } }", "minutes": "{ minutes, plural, 0 { minute } 1 {1 minute } other {# minutes } }",
"seconds": "{ seconds, select, 0 { second } 1 {1 second } other {# seconds } }", "seconds": "{ seconds, plural, 0 { second } 1 {1 second } other {# seconds } }",
"realtime": "リアルタイム", "realtime": "リアルタイム",
"history": "歴史", "history": "歴史",
"last-prefix": "最終", "last-prefix": "最終",
@ -1460,7 +1460,8 @@
"es_ES": "スペイン語", "es_ES": "スペイン語",
"ja_JA": "日本語", "ja_JA": "日本語",
"tr_TR": "トルコ語", "tr_TR": "トルコ語",
"fa_IR": "ペルシャ語" "fa_IR": "ペルシャ語",
"uk_UA": "ウクライナ語"
} }
} }
} }

3
ui/src/app/locale/locale.constant-ko_KR.json

@ -1336,7 +1336,8 @@
"it_IT": "이탈리아 사람", "it_IT": "이탈리아 사람",
"ja_JA": "일본어", "ja_JA": "일본어",
"tr_TR": "터키어", "tr_TR": "터키어",
"fa_IR": "페르시아 인" "fa_IR": "페르시아 인",
"uk_UA": "우크라이나의"
} }
} }
} }

6
ui/src/app/locale/locale.constant-ru_RU.json

@ -1288,7 +1288,8 @@
"metadata-required": "Метаданные объекта не могут быть пустыми.", "metadata-required": "Метаданные объекта не могут быть пустыми.",
"output": "Выход", "output": "Выход",
"test": "Протестировать", "test": "Протестировать",
"help": "Помощь" "help": "Помощь",
"reset-debug-mode": "Сбросить режим отладки во всех правилах"
}, },
"tenant": { "tenant": {
"tenant": "Владелец", "tenant": "Владелец",
@ -1575,7 +1576,8 @@
"tr_TR": "Турецкий", "tr_TR": "Турецкий",
"fr_FR": "Французский", "fr_FR": "Французский",
"ja_JA": "Японский", "ja_JA": "Японский",
"fa_IR": "Персидский" "fa_IR": "Персидский",
"uk_UA": "Украинский"
} }
} }
} }

19
ui/src/app/locale/locale.constant-tr_TR.json

@ -1285,10 +1285,10 @@
"tenant-required": "Tenant gerekli" "tenant-required": "Tenant gerekli"
}, },
"timeinterval": { "timeinterval": {
"seconds-interval": "{ seconds, select, 1 {1 saniye} other {# saniye} }", "seconds-interval": "{ seconds, plural, 1 {1 saniye} other {# saniye} }",
"minutes-interval": "{ minutes, select, 1 {1 dakika} other {# dakika} }", "minutes-interval": "{ minutes, plural, 1 {1 dakika} other {# dakika} }",
"hours-interval": "{ hours, select, 1 {1 saat} other {# saat} }", "hours-interval": "{ hours, plural, 1 {1 saat} other {# saat} }",
"days-interval": "{ days, select, 1 {1 gün} other {# gün} }", "days-interval": "{ days, plural, 1 {1 gün} other {# gün} }",
"days": "Gün", "days": "Gün",
"hours": "Saat", "hours": "Saat",
"minutes": "Dakika", "minutes": "Dakika",
@ -1296,10 +1296,10 @@
"advanced": "İleri düzey" "advanced": "İleri düzey"
}, },
"timewindow": { "timewindow": {
"days": "{ days, select, 1 { gün } other {# gün } }", "days": "{ days, plural, 1 { gün } other {# gün } }",
"hours": "{ hours, select, 0 { saat } 1 {1 saat } other {# saat } }", "hours": "{ hours, plural, 0 { saat } 1 {1 saat } other {# saat } }",
"minutes": "{ minutes, select, 0 { dakika } 1 {1 dakika } other {# dakika } }", "minutes": "{ minutes, plural, 0 { dakika } 1 {1 dakika } other {# dakika } }",
"seconds": "{ seconds, select, 0 { saniye } 1 {1 saniye } other {# saniye } }", "seconds": "{ seconds, plural, 0 { saniye } 1 {1 saniye } other {# saniye } }",
"realtime": "Gerçek zaman", "realtime": "Gerçek zaman",
"history": "Tarih", "history": "Tarih",
"last-prefix": "son", "last-prefix": "son",
@ -1542,7 +1542,8 @@
"es_ES": "İspanyol", "es_ES": "İspanyol",
"ja_JA": "Japonca", "ja_JA": "Japonca",
"tr_TR": "Türkçe", "tr_TR": "Türkçe",
"fa_IR": "Farsça" "fa_IR": "Farsça",
"uk_UA": "Ukrayna"
} }
} }
} }

2183
ui/src/app/locale/locale.constant-uk_UA.json

File diff suppressed because it is too large

15
ui/src/app/locale/locale.constant-zh_CN.json

@ -589,11 +589,11 @@
"manage-credentials": "管理凭据", "manage-credentials": "管理凭据",
"delete": "删除设备", "delete": "删除设备",
"assign-devices": "分配设备", "assign-devices": "分配设备",
"assign-devices-text": "将{count,select,1 {1 设备} other {# 设备}}分配给客户", "assign-devices-text": "将{count,plural,1 {1 设备} other {# 设备}}分配给客户",
"delete-devices": "删除设备", "delete-devices": "删除设备",
"unassign-from-customer": "取消分配客户", "unassign-from-customer": "取消分配客户",
"unassign-devices": "取消分配设备", "unassign-devices": "取消分配设备",
"unassign-devices-action-title": "从客户处取消分配{count,select,1 {1 设备} other {# 设备}}", "unassign-devices-action-title": "从客户处取消分配{count,plural,1 {1 设备} other {# 设备}}",
"assign-new-device": "分配新设备", "assign-new-device": "分配新设备",
"make-public-device-title": "您确定要将设备 '{{deviceName}}' 设为公开吗?", "make-public-device-title": "您确定要将设备 '{{deviceName}}' 设为公开吗?",
"make-public-device-text": "确认后,设备及其所有数据将被设为公开并可被其他人访问。", "make-public-device-text": "确认后,设备及其所有数据将被设为公开并可被其他人访问。",
@ -602,13 +602,13 @@
"view-credentials": "查看凭据", "view-credentials": "查看凭据",
"delete-device-title": "您确定要删除设备的{{deviceName}}吗?", "delete-device-title": "您确定要删除设备的{{deviceName}}吗?",
"delete-device-text": "小心!确认后设备及其所有相关数据将不可恢复。", "delete-device-text": "小心!确认后设备及其所有相关数据将不可恢复。",
"delete-devices-title": "您确定要删除{count,select,1 {1 设备} other {# 设备}} 吗?", "delete-devices-title": "您确定要删除{count,plural,1 {1 设备} other {# 设备}} 吗?",
"delete-devices-action-title": "删除 {count,select,1 {1 设备} other {# 设备}}", "delete-devices-action-title": "删除 {count,plural,1 {1 设备} other {# 设备}}",
"delete-devices-text": "小心!确认后所有选定的设备将被删除,所有相关数据将不可恢复。", "delete-devices-text": "小心!确认后所有选定的设备将被删除,所有相关数据将不可恢复。",
"unassign-device-title": "您确定要取消分配设备 '{{deviceName}}'?", "unassign-device-title": "您确定要取消分配设备 '{{deviceName}}'?",
"unassign-device-text": "确认后,设备将被取消分配,客户将无法访问。", "unassign-device-text": "确认后,设备将被取消分配,客户将无法访问。",
"unassign-device": "取消分配设备", "unassign-device": "取消分配设备",
"unassign-devices-title": "您确定要取消分配{count,select,1 {1 设备} other {# 设备}} 吗?", "unassign-devices-title": "您确定要取消分配{count,plural,1 {1 设备} other {# 设备}} 吗?",
"unassign-devices-text": "确认后,所有选定的设备将被取消分配,并且客户将无法访问。", "unassign-devices-text": "确认后,所有选定的设备将被取消分配,并且客户将无法访问。",
"device-credentials": "设备凭据", "device-credentials": "设备凭据",
"credentials-type": "凭据类型", "credentials-type": "凭据类型",
@ -1176,7 +1176,7 @@
"tenant-details": "租客详情", "tenant-details": "租客详情",
"delete-tenant-title": "您确定要删除租户'{{tenantTitle}}'吗?", "delete-tenant-title": "您确定要删除租户'{{tenantTitle}}'吗?",
"delete-tenant-text": "小心!确认后,租户和所有相关数据将不可恢复。", "delete-tenant-text": "小心!确认后,租户和所有相关数据将不可恢复。",
"delete-tenants-title": "您确定要删除 {count,select,1 {1 租户} other {# 租户}} 吗?", "delete-tenants-title": "您确定要删除 {count,plural,1 {1 租户} other {# 租户}} 吗?",
"delete-tenants-action-title": "删除 { count, plural, 1 {1 租户} other {# 租户} }", "delete-tenants-action-title": "删除 { count, plural, 1 {1 租户} other {# 租户} }",
"delete-tenants-text": "小心!确认后,所有选定的租户将被删除,所有相关数据将不可恢复。", "delete-tenants-text": "小心!确认后,所有选定的租户将被删除,所有相关数据将不可恢复。",
"title": "标题", "title": "标题",
@ -1446,7 +1446,8 @@
"it_IT": "意大利", "it_IT": "意大利",
"ja_JA": "日本", "ja_JA": "日本",
"tr_TR": "土耳其", "tr_TR": "土耳其",
"fa_IR": "波斯语" "fa_IR": "波斯语",
"uk_UA": "乌克兰"
} }
} }
} }

16
ui/src/app/rulechain/rulechain.controller.js

@ -108,6 +108,9 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
vm.objectsSelected = objectsSelected; vm.objectsSelected = objectsSelected;
vm.deleteSelected = deleteSelected; vm.deleteSelected = deleteSelected;
vm.isDebugModeEnabled = isDebugModeEnabled;
vm.resetDebugModeInAllNodes = resetDebugModeInAllNodes;
vm.triggerResize = triggerResize; vm.triggerResize = triggerResize;
vm.openRuleChainContextMenu = openRuleChainContextMenu; vm.openRuleChainContextMenu = openRuleChainContextMenu;
@ -1342,6 +1345,19 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
vm.modelservice.deleteSelected(); vm.modelservice.deleteSelected();
} }
function isDebugModeEnabled() {
var res = $filter('filter')(vm.ruleChainModel.nodes, {debugMode: true});
return (res && res.length);
}
function resetDebugModeInAllNodes() {
vm.ruleChainModel.nodes.forEach((node) => {
if (node.component.type != types.ruleNodeType.INPUT.value && node.component.type != types.ruleNodeType.RULE_CHAIN.value) {
node.debugMode = false;
}
});
}
function triggerResize() { function triggerResize() {
var w = angular.element($window); var w = angular.element($window);
w.triggerHandler('resize'); w.triggerHandler('resize');

9
ui/src/app/rulechain/rulechain.tpl.html

@ -223,6 +223,15 @@
</md-tooltip> </md-tooltip>
<ng-md-icon icon="delete"></ng-md-icon> <ng-md-icon icon="delete"></ng-md-icon>
</md-button> </md-button>
<md-button ng-disabled="$root.loading || !vm.isDebugModeEnabled()"
class="tb-btn-footer md-accent md-hue-2 md-fab"
aria-label="{{ 'rulenode.reset-debug-mode' | translate }}"
ng-click="vm.resetDebugModeInAllNodes()">
<md-tooltip md-direction="top">
{{ 'rulenode.reset-debug-mode' | translate }}
</md-tooltip>
<ng-md-icon icon="bug_report"></ng-md-icon>
</md-button>
<md-button ng-disabled="$root.loading || vm.isInvalid || (!vm.isDirty && !vm.isImport)" <md-button ng-disabled="$root.loading || vm.isInvalid || (!vm.isDirty && !vm.isImport)"
class="tb-btn-footer md-accent md-hue-2 md-fab" class="tb-btn-footer md-accent md-hue-2 md-fab"
aria-label="{{ 'action.apply' | translate }}" aria-label="{{ 'action.apply' | translate }}"

10
ui/src/app/widget/lib/flot-widget.js

@ -137,9 +137,11 @@ export default class TbFlot {
}); });
content += dateDiv.prop('outerHTML'); content += dateDiv.prop('outerHTML');
if (tbFlot.ctx.tooltipIndividual) { if (tbFlot.ctx.tooltipIndividual) {
var seriesHoverInfo = hoverInfo.seriesHover[seriesIndex]; var found = hoverInfo.seriesHover.filter((seriesHover) => {
if (seriesHoverInfo) { return seriesHover.index === seriesIndex;
content += seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex); });
if (found && found.length) {
content += seriesInfoDivFromInfo(found[0], seriesIndex);
} }
} else { } else {
var seriesDiv = $('<div></div>'); var seriesDiv = $('<div></div>');
@ -161,7 +163,7 @@ export default class TbFlot {
if (i == hoverInfo.seriesHover.length) { if (i == hoverInfo.seriesHover.length) {
break; break;
} }
seriesHoverInfo = hoverInfo.seriesHover[i]; var seriesHoverInfo = hoverInfo.seriesHover[i];
columnContent += seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex); columnContent += seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex);
} }
columnDiv.html(columnContent); columnDiv.html(columnContent);

151
ui/src/app/widget/lib/tripAnimation/trip-animation-widget.js

@ -17,10 +17,8 @@ import './trip-animation-widget.scss';
import template from "./trip-animation-widget.tpl.html"; import template from "./trip-animation-widget.tpl.html";
import TbOpenStreetMap from '../openstreet-map'; import TbOpenStreetMap from '../openstreet-map';
import L from 'leaflet'; import L from 'leaflet';
//import tinycolor from 'tinycolor2';
import tinycolor from "tinycolor2"; import tinycolor from "tinycolor2";
import {fillPatternWithActions, isNumber, padValue, processPattern} from "../widget-utils"; import {fillPatternWithActions, isNumber, padValue, processPattern} from "../widget-utils";
//import {fillPatternWithActions, isNumber, padValue, processPattern, fillPattern} from "../widget-utils";
(function () { (function () {
// save these original methods before they are overwritten // save these original methods before they are overwritten
@ -121,20 +119,17 @@ function tripAnimationWidget() {
} }
/*@ngInject*/ /*@ngInject*/
function tripAnimationController($document, $scope, $http, $timeout, $filter) { function tripAnimationController($document, $scope, $http, $timeout, $filter, $sce) {
let vm = this; let vm = this;
//const varsRegex = /\$\{([^\}]*)\}/g;
//let icon;
vm.initBounds = true; vm.initBounds = true;
vm.markers = []; vm.markers = [];
vm.index = 0; vm.index = 0;
vm.dsIndex = 0; vm.dsIndex = 0;
vm.isPlaying = false;
vm.minTime = 0; vm.minTime = 0;
vm.maxTime = 0; vm.maxTime = 0;
vm.isPLaying = false; vm.isPlaying = false;
vm.trackingLine = { vm.trackingLine = {
"type": "FeatureCollection", "type": "FeatureCollection",
features: [] features: []
@ -163,7 +158,7 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
function initializeCallbacks() { function initializeCallbacks() {
vm.self.onDataUpdated = function () { vm.self.onDataUpdated = function () {
createUpdatePath(); createUpdatePath(true);
}; };
vm.self.onResize = function () { vm.self.onResize = function () {
@ -192,26 +187,54 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
} }
vm.playMove = function (play) { vm.playMove = function (play) {
if (play && vm.isPLaying) return; if (play && vm.isPlaying) return;
if (play || vm.isPLaying) vm.isPLaying = true; if (play || vm.isPlaying) vm.isPlaying = true;
if (vm.isPLaying) { if (vm.isPlaying) {
if (vm.index + 1 > vm.maxTime) return; moveInc(1);
vm.index++;
vm.trips.forEach(function (trip) {
moveMarker(trip);
});
vm.timeout = $timeout(function () { vm.timeout = $timeout(function () {
vm.playMove(); vm.playMove();
}, 1000 / vm.speed) }, 1000 / vm.speed)
} }
}; };
vm.moveNext = function () {
vm.stopPlay();
moveInc(1);
}
vm.movePrev = function () {
vm.stopPlay();
moveInc(-1);
}
vm.moveStart = function () {
vm.stopPlay();
moveToIndex(vm.minTime);
}
vm.moveEnd = function () {
vm.stopPlay();
moveToIndex(vm.maxTime);
}
vm.stopPlay = function () { vm.stopPlay = function () {
vm.isPLaying = false; if (vm.isPlaying) {
$timeout.cancel(vm.timeout); vm.isPlaying = false;
$timeout.cancel(vm.timeout);
}
}; };
function moveInc(inc) {
let newIndex = vm.index + inc;
moveToIndex(newIndex);
}
function moveToIndex(newIndex) {
if (newIndex > vm.maxTime || newIndex < vm.minTime) return;
vm.index = newIndex;
recalculateTrips();
}
function recalculateTrips() { function recalculateTrips() {
vm.trips.forEach(function (value) { vm.trips.forEach(function (value) {
moveMarker(value); moveMarker(value);
@ -233,7 +256,7 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
vm.utils = vm.self.ctx.$scope.$injector.get('utils'); vm.utils = vm.self.ctx.$scope.$injector.get('utils');
vm.showTimestamp = vm.settings.showTimestamp !== false; vm.showTimestamp = vm.settings.showTimestamp !== false;
vm.ctx.$element = angular.element("#heat-map", vm.ctx.$container); vm.ctx.$element = angular.element("#trip-animation-map", vm.ctx.$container);
vm.defaultZoomLevel = 2; vm.defaultZoomLevel = 2;
if (vm.ctx.settings.defaultZoomLevel) { if (vm.ctx.settings.defaultZoomLevel) {
if (vm.ctx.settings.defaultZoomLevel > 0 && vm.ctx.settings.defaultZoomLevel < 21) { if (vm.ctx.settings.defaultZoomLevel > 0 && vm.ctx.settings.defaultZoomLevel < 21) {
@ -257,6 +280,8 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
let staticSettings = {}; let staticSettings = {};
vm.staticSettings = staticSettings; vm.staticSettings = staticSettings;
//Calculate General Settings //Calculate General Settings
staticSettings.buttonColor = tinycolor(vm.widgetConfig.color).setAlpha(0.54).toRgbString();
staticSettings.disabledButtonColor = tinycolor(vm.widgetConfig.color).setAlpha(0.3).toRgbString();
staticSettings.mapProvider = vm.ctx.settings.mapProvider || "OpenStreetMap.Mapnik"; staticSettings.mapProvider = vm.ctx.settings.mapProvider || "OpenStreetMap.Mapnik";
staticSettings.latKeyName = vm.ctx.settings.latKeyName || "latitude"; staticSettings.latKeyName = vm.ctx.settings.latKeyName || "latitude";
staticSettings.lngKeyName = vm.ctx.settings.lngKeyName || "longitude"; staticSettings.lngKeyName = vm.ctx.settings.lngKeyName || "longitude";
@ -268,10 +293,14 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
staticSettings.useLabelFunction = vm.ctx.settings.useLabelFunction || false; staticSettings.useLabelFunction = vm.ctx.settings.useLabelFunction || false;
staticSettings.showLabel = vm.ctx.settings.showLabel || false; staticSettings.showLabel = vm.ctx.settings.showLabel || false;
staticSettings.useTooltipFunction = vm.ctx.settings.useTooltipFunction || false; staticSettings.useTooltipFunction = vm.ctx.settings.useTooltipFunction || false;
staticSettings.tooltipPattern = vm.ctx.settings.tooltipPattern || "<b>${entityName}</b><br/><br/><b>Latitude:</b> ${latitude:7}<br/><b>Longitude:</b> ${longitude:7}<br/><b>Start Time:</b> ${maxTime}<br/><b>End Time:</b> ${minTime}"; staticSettings.tooltipPattern = vm.ctx.settings.tooltipPattern || "<span style=\"font-size: 26px; color: #666; font-weight: bold;\">${entityName}</span>\n" +
staticSettings.tooltipOpacity = vm.ctx.settings.tooltipOpacity || 1; "<br/>\n" +
staticSettings.tooltipColor = vm.ctx.settings.tooltipColor ? tinycolor(vm.ctx.settings.tooltipColor).toHexString() : "#ffffff"; "<span style=\"font-size: 12px; color: #666; font-weight: bold;\">Time:</span><span style=\"font-size: 12px;\"> ${formattedTs}</span>\n" +
staticSettings.tooltipFontColor = vm.ctx.settings.tooltipFontColor ? tinycolor(vm.ctx.settings.tooltipFontColor).toHexString() : "#000000"; "<span style=\"font-size: 12px; color: #666; font-weight: bold;\">Latitude:</span> ${latitude:7}\n" +
"<span style=\"font-size: 12px; color: #666; font-weight: bold;\">Longitude:</span> ${longitude:7}";
staticSettings.tooltipOpacity = angular.isNumber(vm.ctx.settings.tooltipOpacity) ? vm.ctx.settings.tooltipOpacity : 1;
staticSettings.tooltipColor = vm.ctx.settings.tooltipColor ? tinycolor(vm.ctx.settings.tooltipColor).toRgbString() : "#ffffff";
staticSettings.tooltipFontColor = vm.ctx.settings.tooltipFontColor ? tinycolor(vm.ctx.settings.tooltipFontColor).toRgbString() : "#000000";
staticSettings.pathColor = vm.ctx.settings.color ? tinycolor(vm.ctx.settings.color).toHexString() : "#ff6300"; staticSettings.pathColor = vm.ctx.settings.color ? tinycolor(vm.ctx.settings.color).toHexString() : "#ff6300";
staticSettings.pathWeight = vm.ctx.settings.strokeWeight || 1; staticSettings.pathWeight = vm.ctx.settings.strokeWeight || 1;
staticSettings.pathOpacity = vm.ctx.settings.strokeOpacity || 1; staticSettings.pathOpacity = vm.ctx.settings.strokeOpacity || 1;
@ -351,16 +380,23 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
} }
} }
function configureTripSettings(trip) { function configureTripSettings(trip, index, apply) {
trip.settings = {}; trip.settings = {};
trip.settings.color = calculateColor(trip); trip.settings.color = calculateColor(trip);
trip.settings.strokeWeight = vm.staticSettings.pathWeight; trip.settings.strokeWeight = vm.staticSettings.pathWeight;
trip.settings.strokeOpacity = vm.staticSettings.pathOpacity; trip.settings.strokeOpacity = vm.staticSettings.pathOpacity;
trip.settings.pointColor = vm.staticSettings.pointColor; trip.settings.pointColor = vm.staticSettings.pointColor;
trip.settings.pointSize = vm.staticSettings.pointSize; trip.settings.pointSize = vm.staticSettings.pointSize;
trip.settings.labelText = calculateLabel(trip);
trip.settings.tooltipText = calculateTooltip(trip);
trip.settings.icon = calculateIcon(trip); trip.settings.icon = calculateIcon(trip);
if (apply) {
$timeout(() => {
trip.settings.labelText = calculateLabel(trip);
trip.settings.tooltipText = $sce.trustAsHtml(calculateTooltip(trip));
},0,true);
} else {
trip.settings.labelText = calculateLabel(trip);
trip.settings.tooltipText = $sce.trustAsHtml(calculateTooltip(trip));
}
} }
function calculateLabel(trip) { function calculateLabel(trip) {
@ -464,7 +500,7 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
return icon; return icon;
} }
function createUpdatePath() { function createUpdatePath(apply) {
if (vm.trips && vm.map) { if (vm.trips && vm.map) {
vm.trips.forEach(function (trip) { vm.trips.forEach(function (trip) {
if (trip.marker) { if (trip.marker) {
@ -486,7 +522,7 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
} }
let normalizedTimeRange = createNormalizedTime(vm.data, 1000); let normalizedTimeRange = createNormalizedTime(vm.data, 1000);
createNormalizedTrips(normalizedTimeRange, vm.datasources); createNormalizedTrips(normalizedTimeRange, vm.datasources);
createTripsOnMap(); createTripsOnMap(apply);
if (vm.initBounds && !vm.initTrips) { if (vm.initBounds && !vm.initTrips) {
vm.trips.forEach(function (trip) { vm.trips.forEach(function (trip) {
vm.map.extendBounds(vm.map.bounds, trip.polyline); vm.map.extendBounds(vm.map.bounds, trip.polyline);
@ -501,7 +537,6 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
vm.map.fitBounds(vm.map.bounds); vm.map.fitBounds(vm.map.bounds);
} }
} }
function fillPattern(pattern, replaceInfo, currentNormalizedValue) { function fillPattern(pattern, replaceInfo, currentNormalizedValue) {
@ -549,12 +584,20 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
} }
}); });
for (let i = min_time; i < max_time; i += step) { for (let i = min_time; i < max_time; i += step) {
normalizedArray.push({ts: i}) normalizedArray.push({ts: i, formattedTs: $filter('date')(i, 'medium')});
} }
if (normalizedArray[normalizedArray.length - 1] && normalizedArray[normalizedArray.length - 1].ts !== max_time) normalizedArray.push({ts: max_time}); if (normalizedArray[normalizedArray.length - 1] && normalizedArray[normalizedArray.length - 1].ts !== max_time) {
normalizedArray.push({ts: max_time, formattedTs: $filter('date')(max_time, 'medium')});
}
} }
vm.maxTime = normalizedArray.length - 1; vm.maxTime = normalizedArray.length - 1;
vm.minTime = 0; vm.minTime = vm.maxTime > 1 ? 1 : 0;
if (vm.index < vm.minTime) {
vm.index = vm.minTime;
} else if (vm.index > vm.maxTime) {
vm.index = vm.maxTime;
}
return normalizedArray; return normalizedArray;
} }
@ -611,27 +654,43 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
} }
el.latLngs.push(data.latLng); el.latLngs.push(data.latLng);
}); });
addAngleForTip(el); addAngleForTrip(el);
}) })
} }
function addAngleForTip(trip) { function addAngleForTrip(trip) {
if (trip.timeRange && trip.timeRange.length > 0) { if (trip.timeRange && trip.timeRange.length > 0) {
trip.timeRange.forEach(function (point, index) { trip.timeRange.forEach(function (point, index) {
let nextPoint, prevPoint; let nextPoint, prevPoint;
nextPoint = index === (trip.timeRange.length - 1) ? trip.timeRange[index] : trip.timeRange[index + 1]; nextPoint = index === (trip.timeRange.length - 1) ? trip.timeRange[index] : trip.timeRange[index + 1];
prevPoint = index === 0 ? trip.timeRange[0] : trip.timeRange[index - 1]; prevPoint = index === 0 ? trip.timeRange[0] : trip.timeRange[index - 1];
point.h = findAngle(prevPoint[vm.staticSettings.latKeyName], prevPoint[vm.staticSettings.lngKeyName], nextPoint[vm.staticSettings.latKeyName], nextPoint[vm.staticSettings.lngKeyName]); let nextLatLng = {
point.h += vm.staticSettings.rotationAngle; lat: nextPoint[vm.staticSettings.latKeyName],
lng: nextPoint[vm.staticSettings.lngKeyName]
};
let prevLatLng = {
lat: prevPoint[vm.staticSettings.latKeyName],
lng: prevPoint[vm.staticSettings.lngKeyName]
};
if (nextLatLng.lat === prevLatLng.lat && nextLatLng.lng === prevLatLng.lng) {
if (angular.isNumber(prevPoint.h)) {
point.h = prevPoint.h;
} else {
point.h = vm.staticSettings.rotationAngle;
}
} else {
point.h = findAngle(prevLatLng.lat, prevLatLng.lng, nextLatLng.lat, nextLatLng.lng);
point.h += vm.staticSettings.rotationAngle;
}
}); });
} }
} }
function createTripsOnMap() { function createTripsOnMap(apply) {
if (vm.trips.length > 0) { if (vm.trips.length > 0) {
vm.trips.forEach(function (trip) { vm.trips.forEach(function (trip) {
if (trip.timeRange.length > 0 && trip.latLngs.every(el => angular.isDefined(el))) { if (trip.timeRange.length > 0 && trip.latLngs.every(el => angular.isDefined(el))) {
configureTripSettings(trip, vm.index); configureTripSettings(trip, vm.index, apply);
if (vm.staticSettings.showPoints) { if (vm.staticSettings.showPoints) {
trip.points = []; trip.points = [];
trip.latLngs.forEach(function (latLng) { trip.latLngs.forEach(function (latLng) {
@ -648,14 +707,14 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
} }
if (trip.timeRange && trip.timeRange.length && angular.isUndefined(trip.marker)) { if (trip.timeRange && trip.timeRange.length && angular.isUndefined(trip.marker)) {
trip.marker = L.marker(trip.timeRange[vm.index].latLng).addTo(vm.map.map); trip.marker = L.marker(trip.timeRange[vm.index].latLng);
trip.marker.setZIndexOffset(1000); trip.marker.setZIndexOffset(1000);
trip.marker.setIcon(vm.staticSettings.icon); trip.marker.setIcon(vm.staticSettings.icon);
trip.marker.setRotationOrigin('center center'); trip.marker.setRotationOrigin('center center');
// trip.marker.addTo(vm.map.map); trip.marker.on('click', function () {
trip.marker.on('click', function () { showHideTooltip(trip);
showHideTooltip(trip); });
}); trip.marker.addTo(vm.map.map);
moveMarker(trip); moveMarker(trip);
} }
} }
@ -675,10 +734,10 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
trip.marker.setZIndexOffset(1000); trip.marker.setZIndexOffset(1000);
trip.marker.setIcon(vm.staticSettings.icon); trip.marker.setIcon(vm.staticSettings.icon);
trip.marker.setRotationOrigin('center center'); trip.marker.setRotationOrigin('center center');
trip.marker.on('click', function () {
showHideTooltip(trip);
});
trip.marker.addTo(vm.map.map); trip.marker.addTo(vm.map.map);
trip.marker.on('click', function () {
showHideTooltip(trip);
});
trip.marker.update(); trip.marker.update();
} }

162
ui/src/app/widget/lib/tripAnimation/trip-animation-widget.scss

@ -14,93 +14,123 @@
* limitations under the License. * limitations under the License.
*/ */
.heat-map-widget { .trip-animation-widget {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;
}
.heat-map-info-panel {
position: absolute;
top: 0;
right: 0;
z-index: 2;
background-color: rgba(0, 0, 0, .3);
border-bottom-left-radius: 5px;
.md-button { .trip-animation-label-container {
min-width: auto; height: 24px;
} }
}
.heat-map-tooltip { .trip-animation-container {
position: absolute; position: relative;
top: 47px; z-index: 1;
right: 0; flex: 1;
z-index: 2; width: 100%;
padding: 10px;
background-color: #fff;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
transition: .3s ease-in-out;
&-hidden {
transform: translateX(100%);
}
}
.heat-map-title { #trip-animation-map {
padding: 10px; z-index: 1;
} width: 100%;
height: 100%;
.heat-map-control-panel { .pointsLayerMarkerIcon {
position: absolute; border-radius: 50%;
bottom: 0; }
z-index: 2; }
box-sizing: border-box;
width: 100%;
padding-right: 70px;
padding-left: 20px;
background: rgba(0, 0, 0, .3);
md-slider-container { .trip-animation-info-panel {
button { position: absolute;
max-width: none; top: 0;
right: 0;
z-index: 2;
pointer-events: none;
ng-md-icon { .md-button {
width: 36px; top: 0;
height: 36px; left: 0;
width: 32px;
min-width: 32px;
height: 32px;
min-height: 32px;
padding: 0 0 2px;
margin: 2px;
line-height: 24px;
svg { ng-md-icon {
width: inherit; width: 24px;
height: inherit; height: 24px;
svg {
width: inherit;
height: inherit;
}
} }
} }
} }
.panel-timer { .trip-animation-tooltip {
max-width: none; position: absolute;
font-size: 20px; top: 38px;
font-weight: 600; right: 0;
z-index: 2;
padding: 10px;
background-color: #fff;
transition: .3s ease-in-out;
&-hidden {
transform: translateX(110%);
}
} }
} }
}
.heat-map-container { .trip-animation-control-panel {
position: relative; position: relative;
z-index: 1; box-sizing: border-box;
flex: 1; width: 100%;
width: 100%; padding-left: 10px;
}
#heat-map { md-slider-container {
z-index: 1; md-slider {
width: 100%; min-width: 80px;
height: 100%; }
button.md-button.md-icon-button {
width: 44px;
min-width: 44px;
height: 44px;
min-height: 44px;
margin: 0;
line-height: 28px;
.pointsLayerMarkerIcon { md-icon {
border-radius: 50%; width: 28px;
height: 28px;
font-size: 28px;
svg {
width: inherit;
height: inherit;
}
}
}
md-select {
margin: 0;
}
}
.panel-timer {
max-width: none;
padding-right: 250px;
padding-left: 90px;
margin-top: -20px;
font-size: 12px;
font-weight: 500;
text-align: center;
}
} }
} }

59
ui/src/app/widget/lib/tripAnimation/trip-animation-widget.tpl.html

@ -15,40 +15,53 @@
limitations under the License. limitations under the License.
--> -->
<div class="heat-map-widget tracking-widget" layout="column" layout-align="center start" <div class="trip-animation-widget" layout="column" layout-align="center start"
ng-style="{'padding-top' : (!vm.widgetConfig.showTitle && vm.ctx.settings.showLabel) ? '5px' : '0px'}"> ng-style="{'padding-top' : (!vm.widgetConfig.showTitle && vm.ctx.settings.showLabel) ? '5px' : '0px'}">
<div ng-show="vm.ctx.settings.showLabel"> <div class="trip-animation-label-container" ng-show="vm.ctx.settings.showLabel">
{{vm.trips[vm.activeTripIndex].settings.labelText}} {{vm.trips[vm.activeTripIndex].settings.labelText}}
</div> </div>
<div class="heat-map-control-panel" lang="row" layout-align="center start"> <div class="trip-animation-container" layout="column">
<div flex id='trip-animation-map'></div>
<div class="trip-animation-info-panel" layout="row">
<md-button class="md-primary md-fab" aria-label="tooltip" ng-show="vm.staticSettings.displayTooltip" ng-click="vm.showHideTooltip()">
<ng-md-icon icon="info_outline"></ng-md-icon>
</md-button>
</div>
<div class="trip-animation-tooltip md-whiteframe-z4" layout="column" ng-class="!vm.staticSettings.showTooltip ? 'trip-animation-tooltip-hidden':''" ng-bind-html="vm.trips[vm.activeTripIndex].settings.tooltipText"
ng-style="{'background-color': vm.staticSettings.tooltipColor, 'opacity': vm.staticSettings.tooltipOpacity, 'color': vm.staticSettings.tooltipFontColor}">
</div>
</div>
<div class="trip-animation-control-panel">
<md-slider-container> <md-slider-container>
<md-button class="md-icon-button" aria-label="Start" ng-click="vm.moveStart()">
<md-icon class="material-icons" ng-style="{'color': vm.staticSettings.buttonColor}">fast_rewind</md-icon>
</md-button>
<md-button class="md-icon-button" aria-label="Previous" ng-click="vm.movePrev()">
<md-icon class="material-icons" ng-style="{'color': vm.staticSettings.buttonColor}">skip_previous</md-icon>
</md-button>
<md-slider ng-model="vm.index" min="{{vm.minTime}}" max="{{vm.maxTime}}" ng-change="vm.recalculateTrips()"></md-slider> <md-slider ng-model="vm.index" min="{{vm.minTime}}" max="{{vm.maxTime}}" ng-change="vm.recalculateTrips()"></md-slider>
<md-button aria-label="Play" ng-click="vm.playMove(true)" ng-disabled="vm.isPlaying"> <md-button class="md-icon-button" aria-label="Next" ng-click="vm.moveNext()">
<ng-md-icon icon="play_circle_outline"></ng-md-icon> <md-icon class="material-icons" ng-style="{'color': vm.staticSettings.buttonColor}">skip_next</md-icon>
</md-button>
<md-button class="md-icon-button" aria-label="End" ng-click="vm.moveEnd()">
<md-icon class="material-icons" ng-style="{'color': vm.staticSettings.buttonColor}">fast_forward</md-icon>
</md-button>
<md-button class="md-icon-button" aria-label="Play" ng-click="vm.playMove(true)" ng-disabled="vm.isPlaying">
<md-icon class="material-icons" ng-style="{'color': vm.isPlaying ? vm.staticSettings.disabledButtonColor : vm.staticSettings.buttonColor}">
play_circle_outline
</md-icon>
</md-button> </md-button>
<md-select ng-model="vm.speed" aria-label="Speed selector"> <md-select ng-model="vm.speed" aria-label="Speed selector">
<md-option ng-value="speed" flex="1" ng-repeat="speed in vm.speeds track by $index">{{ speed}} <md-option ng-value="speed" flex="1" ng-repeat="speed in vm.speeds track by $index">{{ speed}}
</md-option> </md-option>
</md-select> </md-select>
<md-button aria-label="Stop playing" ng-click="vm.stopPlay()"> <md-button class="md-icon-button" aria-label="Stop playing" ng-click="vm.stopPlay()" ng-disabled="!vm.isPlaying">
<ng-md-icon icon="pause_circle_outline"></ng-md-icon> <md-icon class="material-icons" ng-style="{'color': vm.isPlaying ? vm.staticSettings.buttonColor : vm.staticSettings.disabledButtonColor}">
pause_circle_outline
</md-icon>
</md-button> </md-button>
<div class="panel-timer">{{vm.trips[vm.activeTripIndex].timeRange[vm.index].ts | date:'medium'}}
</div>
</md-slider-container> </md-slider-container>
</div> <div class="panel-timer">{{vm.trips[vm.activeTripIndex].timeRange[vm.index].ts | date:'medium'}}
<div class="heat-map-container" layout="column">
<div flex id='heat-map'></div>
<div class="heat-map-info-panel" layout="row">
<md-button aria-label="tooltip" ng-show="vm.staticSettings.displayTooltip" ng-click="vm.showHideTooltip()">
<ng-md-icon icon="info"></ng-md-icon>
</md-button>
<!--<md-button aria-label="settings" ng-click="vm.openHideSettings()">-->
<!--<ng-md-icon icon="settings_applications"></ng-md-icon>-->
<!--</md-button>-->
</div>
<div class="heat-map-tooltip" class="heat-map-title" layout="column" ng-class="!vm.staticSettings.showTooltip ? 'heat-map-tooltip-hidden':''" ng-bind-html="vm.trips[vm.activeTripIndex].settings.tooltipText"
ng-style="{'background-color': vm.staticSettings.tooltipColor, 'opacity': vm.staticSettings.tooltipOpacity, 'color': vm.staticSettings.tooltipFontColor}">
</div> </div>
</div> </div>
</div> </div>

1
ui/src/app/widget/widget-editor.controller.js

@ -16,6 +16,7 @@
import $ from 'jquery'; import $ from 'jquery';
import ace from 'brace'; import ace from 'brace';
import 'brace/ext/language_tools'; import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/javascript'; import 'brace/mode/javascript';
import 'brace/mode/html'; import 'brace/mode/html';
import 'brace/mode/css'; import 'brace/mode/css';

Loading…
Cancel
Save