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

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

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

2
k8s/postgres.yml

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

8
pom.xml

@ -50,7 +50,7 @@
<commons-validator.version>1.5.0</commons-validator.version>
<commons-io.version>2.5</commons-io.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>
<scala.version>2.11</scala.version>
<akka.version>2.4.2</akka.version>
@ -59,12 +59,12 @@
<velocity.version>1.7</velocity.version>
<velocity-tools.version>2.0</velocity-tools.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>
<grpc.version>1.16.1</grpc.version>
<grpc.version>1.19.0</grpc.version>
<lombok.version>1.16.18</lombok.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>
<rabbitmq.version>4.8.0</rabbitmq.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
* limitations under the License.
*/
import './settings-card.scss';
/*@ngInject*/
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.
-->
<div layout="row" width="100%" layout-wrap>
<md-card flex-gt-sm="60" flex="100" style="height: 100%;">
<div>
<md-card class="settings-card">
<md-card-title>
<md-card-title-text>
<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.
-->
<div layout="row" width="100%" layout-wrap tb-help="'outgoingMailSettings'" help-container-id="help-container">
<md-card flex-gt-sm="60" flex="100" style="height: 100%;">
<div tb-help="'outgoingMailSettings'" help-container-id="help-container">
<md-card class="settings-card">
<md-card-title>
<md-card-title-text layout="row">
<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.
*/
import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/json';
import 'brace/theme/github';
import beautify from 'js-beautify';

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

@ -15,6 +15,7 @@
*/
import $ from 'jquery';
import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/java';
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);
} else if (variableName === 'aliasName') {
label = label.split(variable).join(datasource.aliasName);
} else if (variableName === 'entityDescription') {
label = label.split(variable).join(datasource.entityDescription);
}
match = varsRegex.exec(pattern);
}

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

@ -283,7 +283,7 @@ function Contact($compile, $templateCache) {
"Austria": "[0-9]{4}",
"Belgium": "[0-9]{4}",
"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}",
"Faroe Islands": "[0-9]{3,4}",
"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 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import $ from 'jquery';
import thingsboardToast from '../services/toast';
import thingsboardUtils from '../common/utils.service';

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

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

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

@ -16,6 +16,7 @@
import './json-object-edit.scss';
import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/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 FlatButton from 'material-ui/FlatButton';
import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/theme/github';
import fixAceEditor from './../ace-editor-fix';

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

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

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

@ -15,6 +15,7 @@
*/
import $ from 'jquery';
import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/java';
import 'brace/theme/github';
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.
*/
import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import 'brace/mode/json';
import 'brace/theme/github';

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

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

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

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

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

@ -1577,7 +1577,8 @@
"es_ES": "Spanisch",
"ja_JA": "Japanisch",
"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.",
"output": "Output",
"test": "Test",
"help": "Help"
"help": "Help",
"reset-debug-mode": "Reset debug mode in all nodes"
},
"tenant": {
"tenant": "Tenant",
@ -1583,7 +1584,8 @@
"es_ES": "Spanish",
"ja_JA": "Japanese",
"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",
"ja_JA": "Japonés",
"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": "اسپانيولي",
"ja_JA": "ژاپني",
"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",
"ja_JA": "Japonaise",
"tr_TR": "Turc",
"fa_IR": "Persane"
"fa_IR": "Persane",
"uk_UA": "Ukrainien"
}
},
"layout": {

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

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

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

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

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

@ -1288,7 +1288,8 @@
"metadata-required": "Метаданные объекта не могут быть пустыми.",
"output": "Выход",
"test": "Протестировать",
"help": "Помощь"
"help": "Помощь",
"reset-debug-mode": "Сбросить режим отладки во всех правилах"
},
"tenant": {
"tenant": "Владелец",
@ -1575,7 +1576,8 @@
"tr_TR": "Турецкий",
"fr_FR": "Французский",
"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"
},
"timeinterval": {
"seconds-interval": "{ seconds, select, 1 {1 saniye} other {# saniye} }",
"minutes-interval": "{ minutes, select, 1 {1 dakika} other {# dakika} }",
"hours-interval": "{ hours, select, 1 {1 saat} other {# saat} }",
"days-interval": "{ days, select, 1 {1 gün} other {# gün} }",
"seconds-interval": "{ seconds, plural, 1 {1 saniye} other {# saniye} }",
"minutes-interval": "{ minutes, plural, 1 {1 dakika} other {# dakika} }",
"hours-interval": "{ hours, plural, 1 {1 saat} other {# saat} }",
"days-interval": "{ days, plural, 1 {1 gün} other {# gün} }",
"days": "Gün",
"hours": "Saat",
"minutes": "Dakika",
@ -1296,10 +1296,10 @@
"advanced": "İleri düzey"
},
"timewindow": {
"days": "{ days, select, 1 { gün } other {# gün } }",
"hours": "{ hours, select, 0 { saat } 1 {1 saat } other {# saat } }",
"minutes": "{ minutes, select, 0 { dakika } 1 {1 dakika } other {# dakika } }",
"seconds": "{ seconds, select, 0 { saniye } 1 {1 saniye } other {# saniye } }",
"days": "{ days, plural, 1 { gün } other {# gün } }",
"hours": "{ hours, plural, 0 { saat } 1 {1 saat } other {# saat } }",
"minutes": "{ minutes, plural, 0 { dakika } 1 {1 dakika } other {# dakika } }",
"seconds": "{ seconds, plural, 0 { saniye } 1 {1 saniye } other {# saniye } }",
"realtime": "Gerçek zaman",
"history": "Tarih",
"last-prefix": "son",
@ -1542,7 +1542,8 @@
"es_ES": "İspanyol",
"ja_JA": "Japonca",
"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": "管理凭据",
"delete": "删除设备",
"assign-devices": "分配设备",
"assign-devices-text": "将{count,select,1 {1 设备} other {# 设备}}分配给客户",
"assign-devices-text": "将{count,plural,1 {1 设备} other {# 设备}}分配给客户",
"delete-devices": "删除设备",
"unassign-from-customer": "取消分配客户",
"unassign-devices": "取消分配设备",
"unassign-devices-action-title": "从客户处取消分配{count,select,1 {1 设备} other {# 设备}}",
"unassign-devices-action-title": "从客户处取消分配{count,plural,1 {1 设备} other {# 设备}}",
"assign-new-device": "分配新设备",
"make-public-device-title": "您确定要将设备 '{{deviceName}}' 设为公开吗?",
"make-public-device-text": "确认后,设备及其所有数据将被设为公开并可被其他人访问。",
@ -602,13 +602,13 @@
"view-credentials": "查看凭据",
"delete-device-title": "您确定要删除设备的{{deviceName}}吗?",
"delete-device-text": "小心!确认后设备及其所有相关数据将不可恢复。",
"delete-devices-title": "您确定要删除{count,select,1 {1 设备} other {# 设备}} 吗?",
"delete-devices-action-title": "删除 {count,select,1 {1 设备} other {# 设备}}",
"delete-devices-title": "您确定要删除{count,plural,1 {1 设备} other {# 设备}} 吗?",
"delete-devices-action-title": "删除 {count,plural,1 {1 设备} other {# 设备}}",
"delete-devices-text": "小心!确认后所有选定的设备将被删除,所有相关数据将不可恢复。",
"unassign-device-title": "您确定要取消分配设备 '{{deviceName}}'?",
"unassign-device-text": "确认后,设备将被取消分配,客户将无法访问。",
"unassign-device": "取消分配设备",
"unassign-devices-title": "您确定要取消分配{count,select,1 {1 设备} other {# 设备}} 吗?",
"unassign-devices-title": "您确定要取消分配{count,plural,1 {1 设备} other {# 设备}} 吗?",
"unassign-devices-text": "确认后,所有选定的设备将被取消分配,并且客户将无法访问。",
"device-credentials": "设备凭据",
"credentials-type": "凭据类型",
@ -1176,7 +1176,7 @@
"tenant-details": "租客详情",
"delete-tenant-title": "您确定要删除租户'{{tenantTitle}}'吗?",
"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-text": "小心!确认后,所有选定的租户将被删除,所有相关数据将不可恢复。",
"title": "标题",
@ -1446,7 +1446,8 @@
"it_IT": "意大利",
"ja_JA": "日本",
"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.deleteSelected = deleteSelected;
vm.isDebugModeEnabled = isDebugModeEnabled;
vm.resetDebugModeInAllNodes = resetDebugModeInAllNodes;
vm.triggerResize = triggerResize;
vm.openRuleChainContextMenu = openRuleChainContextMenu;
@ -1342,6 +1345,19 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
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() {
var w = angular.element($window);
w.triggerHandler('resize');

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

@ -223,6 +223,15 @@
</md-tooltip>
<ng-md-icon icon="delete"></ng-md-icon>
</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)"
class="tb-btn-footer md-accent md-hue-2 md-fab"
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');
if (tbFlot.ctx.tooltipIndividual) {
var seriesHoverInfo = hoverInfo.seriesHover[seriesIndex];
if (seriesHoverInfo) {
content += seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex);
var found = hoverInfo.seriesHover.filter((seriesHover) => {
return seriesHover.index === seriesIndex;
});
if (found && found.length) {
content += seriesInfoDivFromInfo(found[0], seriesIndex);
}
} else {
var seriesDiv = $('<div></div>');
@ -161,7 +163,7 @@ export default class TbFlot {
if (i == hoverInfo.seriesHover.length) {
break;
}
seriesHoverInfo = hoverInfo.seriesHover[i];
var seriesHoverInfo = hoverInfo.seriesHover[i];
columnContent += seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex);
}
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 TbOpenStreetMap from '../openstreet-map';
import L from 'leaflet';
//import tinycolor from 'tinycolor2';
import tinycolor from "tinycolor2";
import {fillPatternWithActions, isNumber, padValue, processPattern} from "../widget-utils";
//import {fillPatternWithActions, isNumber, padValue, processPattern, fillPattern} from "../widget-utils";
(function () {
// save these original methods before they are overwritten
@ -121,20 +119,17 @@ function tripAnimationWidget() {
}
/*@ngInject*/
function tripAnimationController($document, $scope, $http, $timeout, $filter) {
function tripAnimationController($document, $scope, $http, $timeout, $filter, $sce) {
let vm = this;
//const varsRegex = /\$\{([^\}]*)\}/g;
//let icon;
vm.initBounds = true;
vm.markers = [];
vm.index = 0;
vm.dsIndex = 0;
vm.isPlaying = false;
vm.minTime = 0;
vm.maxTime = 0;
vm.isPLaying = false;
vm.isPlaying = false;
vm.trackingLine = {
"type": "FeatureCollection",
features: []
@ -163,7 +158,7 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
function initializeCallbacks() {
vm.self.onDataUpdated = function () {
createUpdatePath();
createUpdatePath(true);
};
vm.self.onResize = function () {
@ -192,26 +187,54 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
}
vm.playMove = function (play) {
if (play && vm.isPLaying) return;
if (play || vm.isPLaying) vm.isPLaying = true;
if (vm.isPLaying) {
if (vm.index + 1 > vm.maxTime) return;
vm.index++;
vm.trips.forEach(function (trip) {
moveMarker(trip);
});
if (play && vm.isPlaying) return;
if (play || vm.isPlaying) vm.isPlaying = true;
if (vm.isPlaying) {
moveInc(1);
vm.timeout = $timeout(function () {
vm.playMove();
}, 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.isPLaying = false;
$timeout.cancel(vm.timeout);
if (vm.isPlaying) {
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() {
vm.trips.forEach(function (value) {
moveMarker(value);
@ -233,7 +256,7 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
vm.utils = vm.self.ctx.$scope.$injector.get('utils');
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;
if (vm.ctx.settings.defaultZoomLevel) {
if (vm.ctx.settings.defaultZoomLevel > 0 && vm.ctx.settings.defaultZoomLevel < 21) {
@ -257,6 +280,8 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
let staticSettings = {};
vm.staticSettings = staticSettings;
//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.latKeyName = vm.ctx.settings.latKeyName || "latitude";
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.showLabel = vm.ctx.settings.showLabel || 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.tooltipOpacity = vm.ctx.settings.tooltipOpacity || 1;
staticSettings.tooltipColor = vm.ctx.settings.tooltipColor ? tinycolor(vm.ctx.settings.tooltipColor).toHexString() : "#ffffff";
staticSettings.tooltipFontColor = vm.ctx.settings.tooltipFontColor ? tinycolor(vm.ctx.settings.tooltipFontColor).toHexString() : "#000000";
staticSettings.tooltipPattern = vm.ctx.settings.tooltipPattern || "<span style=\"font-size: 26px; color: #666; font-weight: bold;\">${entityName}</span>\n" +
"<br/>\n" +
"<span style=\"font-size: 12px; color: #666; font-weight: bold;\">Time:</span><span style=\"font-size: 12px;\"> ${formattedTs}</span>\n" +
"<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.pathWeight = vm.ctx.settings.strokeWeight || 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.color = calculateColor(trip);
trip.settings.strokeWeight = vm.staticSettings.pathWeight;
trip.settings.strokeOpacity = vm.staticSettings.pathOpacity;
trip.settings.pointColor = vm.staticSettings.pointColor;
trip.settings.pointSize = vm.staticSettings.pointSize;
trip.settings.labelText = calculateLabel(trip);
trip.settings.tooltipText = calculateTooltip(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) {
@ -464,7 +500,7 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
return icon;
}
function createUpdatePath() {
function createUpdatePath(apply) {
if (vm.trips && vm.map) {
vm.trips.forEach(function (trip) {
if (trip.marker) {
@ -486,7 +522,7 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
}
let normalizedTimeRange = createNormalizedTime(vm.data, 1000);
createNormalizedTrips(normalizedTimeRange, vm.datasources);
createTripsOnMap();
createTripsOnMap(apply);
if (vm.initBounds && !vm.initTrips) {
vm.trips.forEach(function (trip) {
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);
}
}
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) {
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.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;
}
@ -611,27 +654,43 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
}
el.latLngs.push(data.latLng);
});
addAngleForTip(el);
addAngleForTrip(el);
})
}
function addAngleForTip(trip) {
function addAngleForTrip(trip) {
if (trip.timeRange && trip.timeRange.length > 0) {
trip.timeRange.forEach(function (point, index) {
let nextPoint, prevPoint;
nextPoint = index === (trip.timeRange.length - 1) ? trip.timeRange[index] : 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]);
point.h += vm.staticSettings.rotationAngle;
let nextLatLng = {
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) {
vm.trips.forEach(function (trip) {
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) {
trip.points = [];
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)) {
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.setIcon(vm.staticSettings.icon);
trip.marker.setRotationOrigin('center center');
// trip.marker.addTo(vm.map.map);
trip.marker.on('click', function () {
showHideTooltip(trip);
});
trip.marker.on('click', function () {
showHideTooltip(trip);
});
trip.marker.addTo(vm.map.map);
moveMarker(trip);
}
}
@ -675,10 +734,10 @@ function tripAnimationController($document, $scope, $http, $timeout, $filter) {
trip.marker.setZIndexOffset(1000);
trip.marker.setIcon(vm.staticSettings.icon);
trip.marker.setRotationOrigin('center center');
trip.marker.on('click', function () {
showHideTooltip(trip);
});
trip.marker.addTo(vm.map.map);
trip.marker.on('click', function () {
showHideTooltip(trip);
});
trip.marker.update();
}

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

@ -14,93 +14,123 @@
* limitations under the License.
*/
.heat-map-widget {
.trip-animation-widget {
position: relative;
width: 100%;
height: 100%;
font-size: 16px;
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 {
min-width: auto;
.trip-animation-label-container {
height: 24px;
}
}
.heat-map-tooltip {
position: absolute;
top: 47px;
right: 0;
z-index: 2;
padding: 10px;
background-color: #fff;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
transition: .3s ease-in-out;
&-hidden {
transform: translateX(100%);
}
}
.trip-animation-container {
position: relative;
z-index: 1;
flex: 1;
width: 100%;
.heat-map-title {
padding: 10px;
}
#trip-animation-map {
z-index: 1;
width: 100%;
height: 100%;
.heat-map-control-panel {
position: absolute;
bottom: 0;
z-index: 2;
box-sizing: border-box;
width: 100%;
padding-right: 70px;
padding-left: 20px;
background: rgba(0, 0, 0, .3);
.pointsLayerMarkerIcon {
border-radius: 50%;
}
}
md-slider-container {
button {
max-width: none;
.trip-animation-info-panel {
position: absolute;
top: 0;
right: 0;
z-index: 2;
pointer-events: none;
ng-md-icon {
width: 36px;
height: 36px;
.md-button {
top: 0;
left: 0;
width: 32px;
min-width: 32px;
height: 32px;
min-height: 32px;
padding: 0 0 2px;
margin: 2px;
line-height: 24px;
svg {
width: inherit;
height: inherit;
ng-md-icon {
width: 24px;
height: 24px;
svg {
width: inherit;
height: inherit;
}
}
}
}
.panel-timer {
max-width: none;
font-size: 20px;
font-weight: 600;
.trip-animation-tooltip {
position: absolute;
top: 38px;
right: 0;
z-index: 2;
padding: 10px;
background-color: #fff;
transition: .3s ease-in-out;
&-hidden {
transform: translateX(110%);
}
}
}
}
.heat-map-container {
position: relative;
z-index: 1;
flex: 1;
width: 100%;
}
.trip-animation-control-panel {
position: relative;
box-sizing: border-box;
width: 100%;
padding-left: 10px;
#heat-map {
z-index: 1;
width: 100%;
height: 100%;
md-slider-container {
md-slider {
min-width: 80px;
}
button.md-button.md-icon-button {
width: 44px;
min-width: 44px;
height: 44px;
min-height: 44px;
margin: 0;
line-height: 28px;
.pointsLayerMarkerIcon {
border-radius: 50%;
md-icon {
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.
-->
<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'}">
<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}}
</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-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-button aria-label="Play" ng-click="vm.playMove(true)" ng-disabled="vm.isPlaying">
<ng-md-icon icon="play_circle_outline"></ng-md-icon>
<md-button class="md-icon-button" aria-label="Next" ng-click="vm.moveNext()">
<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-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>
</md-select>
<md-button aria-label="Stop playing" ng-click="vm.stopPlay()">
<ng-md-icon icon="pause_circle_outline"></ng-md-icon>
<md-button class="md-icon-button" aria-label="Stop playing" ng-click="vm.stopPlay()" ng-disabled="!vm.isPlaying">
<md-icon class="material-icons" ng-style="{'color': vm.isPlaying ? vm.staticSettings.buttonColor : vm.staticSettings.disabledButtonColor}">
pause_circle_outline
</md-icon>
</md-button>
<div class="panel-timer">{{vm.trips[vm.activeTripIndex].timeRange[vm.index].ts | date:'medium'}}
</div>
</md-slider-container>
</div>
<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 class="panel-timer">{{vm.trips[vm.activeTripIndex].timeRange[vm.index].ts | date:'medium'}}
</div>
</div>
</div>
</div>

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

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

Loading…
Cancel
Save