Browse Source

Merge branch 'develop/3.4' of github.com:thingsboard/thingsboard into lwm2m-client-serialization

pull/5977/head
YevhenBondarenko 5 years ago
parent
commit
50b096c35b
  1. 6
      application/src/main/data/json/system/widget_bundles/control_widgets.json
  2. 18
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  3. 7
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  4. 37
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  5. 24
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsg.java
  6. 21
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsgType.java
  7. 38
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketPingMsg.java
  8. 34
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketTextMsg.java
  9. 13
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  10. 16
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  11. 10
      common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java
  12. 2
      msa/tb/pom.xml
  13. 30
      pull_request_template.md
  14. 2
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  15. 5
      ui-ngx/src/app/core/http/public-api.ts
  16. 3
      ui-ngx/src/app/core/ws/telemetry-websocket.service.ts
  17. 113
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
  18. 8
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
  19. 2
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.html
  20. 2
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.ts
  21. 10
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  22. 12
      ui-ngx/src/assets/help/en_US/rulenode/transformation_node_script_fn.md
  23. 16
      ui-ngx/src/assets/locale/locale.constant-en_US.json

6
application/src/main/data/json/system/widget_bundles/control_widgets.json

File diff suppressed because one or more lines are too long

18
application/src/main/data/json/system/widget_bundles/input_widgets.json

File diff suppressed because one or more lines are too long

7
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -38,7 +38,6 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
import org.thingsboard.server.common.data.ClaimRequest;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
@ -875,9 +874,6 @@ public class DeviceController extends BaseController {
Device savedDevice = checkNotNull(deviceService.assignDeviceToEdge(getCurrentUser().getTenantId(), deviceId, edgeId));
tbClusterService.pushMsgToCore(new DeviceEdgeUpdateMsg(savedDevice.getTenantId(),
savedDevice.getId(), edgeId), null);
logEntityAction(deviceId, savedDevice,
savedDevice.getCustomerId(),
ActionType.ASSIGNED_TO_EDGE, null, strDeviceId, strEdgeId, edge.getName());
@ -918,9 +914,6 @@ public class DeviceController extends BaseController {
Device savedDevice = checkNotNull(deviceService.unassignDeviceFromEdge(getCurrentUser().getTenantId(), deviceId, edgeId));
tbClusterService.pushMsgToCore(new DeviceEdgeUpdateMsg(savedDevice.getTenantId(),
savedDevice.getId(), null), null);
logEntityAction(deviceId, device,
device.getCustomerId(),
ActionType.UNASSIGNED_FROM_EDGE, null, strDeviceId, strEdgeId, edge.getName());

37
application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java

@ -67,7 +67,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
private static final ConcurrentMap<String, SessionMetaData> internalSessionMap = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, String> externalSessionMap = new ConcurrentHashMap<>();
private static final ByteBuffer PING_MSG = ByteBuffer.wrap(new byte[]{});
@Autowired
private TelemetryWebSocketService webSocketService;
@ -216,7 +216,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
private final TelemetryWebSocketSessionRef sessionRef;
private volatile boolean isSending = false;
private final Queue<String> msgQueue;
private final Queue<TbWebSocketMsg<?>> msgQueue;
private volatile long lastActivityTime;
@ -237,7 +237,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
log.warn("[{}] Closing session due to ping timeout", session.getId());
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
} else if (timeSinceLastActivity >= pingTimeout / NUMBER_OF_PING_ATTEMPTS) {
this.asyncRemote.sendPing(PING_MSG);
sendMsg(TbWebSocketPingMsg.INSTANCE);
}
} catch (Exception e) {
log.trace("[{}] Failed to send ping msg", session.getId(), e);
@ -258,6 +258,10 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
synchronized void sendMsg(String msg) {
sendMsg(new TbWebSocketTextMsg(msg));
}
synchronized void sendMsg(TbWebSocketMsg<?> msg) {
if (isSending) {
try {
msgQueue.add(msg);
@ -275,9 +279,16 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
}
private void sendMsgInternal(String msg) {
private void sendMsgInternal(TbWebSocketMsg<?> msg) {
try {
this.asyncRemote.sendText(msg, this);
if (TbWebSocketMsgType.TEXT.equals(msg.getType())) {
TbWebSocketTextMsg textMsg = (TbWebSocketTextMsg) msg;
this.asyncRemote.sendText(textMsg.getMsg(), this);
} else {
TbWebSocketPingMsg pingMsg = (TbWebSocketPingMsg) msg;
this.asyncRemote.sendPing(pingMsg.getMsg());
processNextMsg();
}
} catch (Exception e) {
log.trace("[{}] Failed to send msg", session.getId(), e);
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
@ -290,12 +301,16 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
log.trace("[{}] Failed to send msg", session.getId(), result.getException());
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
} else {
String msg = msgQueue.poll();
if (msg != null) {
sendMsgInternal(msg);
} else {
isSending = false;
}
processNextMsg();
}
}
private void processNextMsg() {
TbWebSocketMsg<?> msg = msgQueue.poll();
if (msg != null) {
sendMsgInternal(msg);
} else {
isSending = false;
}
}
}

24
application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsg.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2022 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.
*/
package org.thingsboard.server.controller.plugin;
public interface TbWebSocketMsg<T> {
TbWebSocketMsgType getType();
T getMsg();
}

21
application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsgType.java

@ -0,0 +1,21 @@
/**
* Copyright © 2016-2022 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.
*/
package org.thingsboard.server.controller.plugin;
public enum TbWebSocketMsgType {
PING, TEXT
}

38
application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketPingMsg.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2022 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.
*/
package org.thingsboard.server.controller.plugin;
import lombok.RequiredArgsConstructor;
import java.nio.ByteBuffer;
@RequiredArgsConstructor
public class TbWebSocketPingMsg implements TbWebSocketMsg<ByteBuffer> {
public static TbWebSocketPingMsg INSTANCE = new TbWebSocketPingMsg();
private static final ByteBuffer PING_MSG = ByteBuffer.wrap(new byte[]{});
@Override
public TbWebSocketMsgType getType() {
return TbWebSocketMsgType.PING;
}
@Override
public ByteBuffer getMsg() {
return PING_MSG;
}
}

34
application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketTextMsg.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2022 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.
*/
package org.thingsboard.server.controller.plugin;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class TbWebSocketTextMsg implements TbWebSocketMsg<String> {
private final String value;
@Override
public TbWebSocketMsgType getType() {
return TbWebSocketMsgType.TEXT;
}
@Override
public String getMsg() {
return value;
}
}

13
application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java

@ -17,13 +17,14 @@ package org.thingsboard.server.service.install.update;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Objects;
import java.util.Optional;
@RequiredArgsConstructor
@Service
@ -32,6 +33,8 @@ import java.util.Objects;
public class DefaultCacheCleanupService implements CacheCleanupService {
private final CacheManager cacheManager;
private final Optional<RedisTemplate<String, Object>> redisTemplate;
/**
* Cleanup caches that can not deserialize anymore due to schema upgrade or data update using sql scripts.
@ -59,9 +62,7 @@ public class DefaultCacheCleanupService implements CacheCleanupService {
break;
case "3.3.2":
log.info("Clear cache to upgrade from version 3.3.2 to 3.3.3 ...");
clearCacheByName("devices");
clearCacheByName("deviceProfiles");
clearCacheByName("tenantProfiles");
clearAll();
break;
default:
//Do nothing, since cache cleanup is optional.
@ -78,4 +79,8 @@ public class DefaultCacheCleanupService implements CacheCleanupService {
cache.clear();
}
void clearAll() {
redisTemplate.ifPresent(rt -> rt.execute(connection ->
connection.execute("FLUSHALL"), false));
}
}

16
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EdgeUtils;
@ -455,6 +456,21 @@ public class DefaultTbClusterService implements TbClusterService {
TransportProtos.EdgeNotificationMsgProto msg = builder.build();
log.trace("[{}] sending notification to edge service {}", tenantId.getId(), msg);
pushMsgToCore(tenantId, entityId != null ? entityId : tenantId, TransportProtos.ToCoreMsg.newBuilder().setEdgeNotificationMsg(msg).build(), null);
if (entityId != null && EntityType.DEVICE.equals(entityId.getEntityType())) {
pushDeviceUpdateMessage(tenantId, edgeId, entityId, action);
}
}
private void pushDeviceUpdateMessage(TenantId tenantId, EdgeId edgeId, EntityId entityId, EdgeEventActionType action) {
log.trace("{} Going to send edge update notification for device actor, device id {}, edge id {}", tenantId, entityId, edgeId);
switch (action) {
case ASSIGNED_TO_EDGE:
pushMsgToCore(new DeviceEdgeUpdateMsg(tenantId, new DeviceId(entityId.getId()), edgeId), null);
break;
case UNASSIGNED_FROM_EDGE:
pushMsgToCore(new DeviceEdgeUpdateMsg(tenantId, new DeviceId(entityId.getId()), null), null);
break;
}
}
}

10
common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java

@ -19,6 +19,8 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
@ -27,6 +29,9 @@ public class EdgeId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@JsonIgnore
static final ConcurrentReferenceHashMap<UUID, EdgeId> edges = new ConcurrentReferenceHashMap<>(16, ReferenceType.SOFT);
@JsonCreator
public EdgeId(@JsonProperty("id") UUID id) {
super(id);
@ -41,4 +46,9 @@ public class EdgeId extends UUIDBased implements EntityId {
public EntityType getEntityType() {
return EntityType.EDGE;
}
@JsonCreator
public static EdgeId fromUUID(@JsonProperty("id") UUID id) {
return edges.computeIfAbsent(id, EdgeId::new);
}
}

2
msa/tb/pom.xml

@ -39,7 +39,7 @@
<tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name>
<tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<pkg.upgradeVersion>3.3.1</pkg.upgradeVersion>
<pkg.upgradeVersion>3.3.3</pkg.upgradeVersion>
</properties>
<dependencies>

30
pull_request_template.md

@ -0,0 +1,30 @@
## Pull Request description
Put your PR description here instead of this sentence.
## General checklist
- [ ] You have reviewed the guidelines [document](https://docs.google.com/document/d/1wqcOafLx5hth8SAg4dqV_LV3un3m5WYR8RdTJ4MbbUM/edit?usp=sharing).
- [ ] PR name contains fix version. For example, "[3.3.4] Hotfix of some UI component" or "[3.4] New Super Feature".
- [ ] Description references specific [issue](https://github.com/thingsboard/thingsboard/issues).
- [ ] Description contains human-readable scope of changes.
- [ ] Description contains brief notes about what needs to be added to the documentation.
- [ ] No merge conficts, commented blocks of code, code formatting issues.
- [ ] Changes are backward compatible or upgrade script is provided.
- [ ] Similar PR is opened for PE version to simplify merge. Required for internal contributors only.
## Front-End feature checklist
- [ ] Screenshots with affected component(s) are added. The best option is to provide 2 screens: before and after changes;
- [ ] If you change widget or other API, ensure it is backward-compatible or upgrade script is present.
- [ ] Ensure new API is documented [here](https://github.com/thingsboard/thingsboard-ui-help)
## Back-End feature checklist
- [ ] Added corresponding unit and/or integration test(s). Provide written explanation in the PR description if you have failed to add tests.
- [ ] If new dependency was added: the dependency tree is checked for conflicts.
- [ ] If new service was added: the service is marked with corresponding @TbCoreComponent, @TbRuleEngineComponent, @TbTransportComponent, etc.
- [ ] If new REST API was added: the RestClient.java was updated, issue for [Python REST client](https://github.com/thingsboard/thingsboard-python-rest-client) is created.

2
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

5
ui-ngx/src/app/core/http/public-api.ts

@ -23,6 +23,7 @@ export * from './component-descriptor.service';
export * from './customer.service';
export * from './dashboard.service';
export * from './device.service';
export * from './device-profile.service';
export * from './entity.service';
export * from './edge.service';
export * from './entity-relation.service';
@ -30,8 +31,12 @@ export * from './entity-view.service';
export * from './event.service';
export * from './http-utils';
export * from './oauth2.service';
export * from './ota-package.service';
export * from './queue.service';
export * from './resource.service';
export * from './rule-chain.service';
export * from './tenant.service';
export * from './tenant-profile.service';
export * from './ui-settings.service';
export * from './user.service';
export * from './widget.service';

3
ui-ngx/src/app/core/ws/telemetry-websocket.service.ts

@ -332,7 +332,8 @@ export class TelemetryWebsocketService implements TelemetryService {
}
private onClose(closeEvent: CloseEvent) {
if (closeEvent && closeEvent.code > 1001 && closeEvent.code !== 1006 && closeEvent.code !== 1012) {
if (closeEvent && closeEvent.code > 1001 && closeEvent.code !== 1006
&& closeEvent.code !== 1011 && closeEvent.code !== 1012 && closeEvent.code !== 4500) {
this.showWsError(closeEvent.code, closeEvent.reason);
}
this.isOpening = false;

113
ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts

@ -89,6 +89,7 @@ export default abstract class LeafletMap {
saveLocation: (e: FormattedData, values: {[key: string]: any}) => Observable<any>;
saveMarkerLocation: (e: FormattedData, lat?: number, lng?: number) => Observable<any>;
savePolygonLocation: (e: FormattedData, coordinates?: Array<any>) => Observable<any>;
translateService: TranslateService;
protected constructor(public ctx: WidgetContext,
public $container: HTMLElement,
@ -96,6 +97,7 @@ export default abstract class LeafletMap {
this.options = options;
this.editPolygons = options.showPolygon && options.editablePolygon;
L.Icon.Default.imagePath = '/';
this.translateService = this.ctx.$injector.get(TranslateService);
}
public initSettings(options: MapSettings) {
@ -171,6 +173,38 @@ export default abstract class LeafletMap {
if (data !== null) {
this.selectedEntity = data;
this.toggleDrawMode(type);
let tooltipText;
let customTranslation;
switch (type) {
case 'tbMarker':
tooltipText = this.translateService.instant('widgets.maps.tooltips.placeMarker', {entityName: data.entityName});
// @ts-ignore
this.map.pm.Draw.tbMarker._hintMarker.setTooltipContent(tooltipText);
break;
case 'tbRectangle':
tooltipText = this.translateService.instant('widgets.maps.tooltips.firstVertex', {entityName: data.entityName});
// @ts-ignore
this.map.pm.Draw.tbRectangle._hintMarker.setTooltipContent(tooltipText);
customTranslation = {
tooltips: {
finishRect: this.translateService.instant('widgets.maps.tooltips.finishRect', {entityName: data.entityName})
}
};
this.map.pm.setLang('en', customTranslation, 'en');
break;
case 'tbPolygon':
tooltipText = this.translateService.instant('widgets.maps.tooltips.firstVertex', {entityName: data.entityName});
// @ts-ignore
this.map.pm.Draw.tbPolygon._hintMarker.setTooltipContent(tooltipText);
customTranslation = {
tooltips: {
continueLine: this.translateService.instant('widgets.maps.tooltips.continueLine', {entityName: data.entityName}),
finishPoly: this.translateService.instant('widgets.maps.tooltips.finishPoly', {entityName: data.entityName})
}
};
this.map.pm.setLang('en', customTranslation);
break;
}
} else {
// @ts-ignore
this.map.pm.Toolbar.toggleButton(type, false);
@ -231,8 +265,7 @@ export default abstract class LeafletMap {
});
}
const translateService = this.ctx.$injector.get(TranslateService);
this.map.pm.setLang('en', translateService.instant('widgets.maps'), 'en');
this.map.pm.setLang('en', this.translateService.instant('widgets.maps'), 'en');
if (!this.options.hideAllControlButton) {
this.map.pm.addControls({
position: 'topleft',
@ -494,16 +527,72 @@ export default abstract class LeafletMap {
}
this.updateMarkers(formattedData, false);
this.updateBoundsInternal();
if (this.options.draggableMarker && !this.options.hideDrawControlButton) {
const foundEntityWithoutLocation = formattedData.some(mData => !this.convertPosition(mData));
this.map.pm.Toolbar.setButtonDisabled('tbMarker', !foundEntityWithoutLocation);
this.datasources = formattedData;
}
if (this.editPolygons && !this.options.hideDrawControlButton) {
const foundEntityWithoutPolygon = formattedData.some(pData => !this.isValidPolygonPosition(pData));
this.map.pm.Toolbar.setButtonDisabled('tbPolygon', !foundEntityWithoutPolygon);
this.map.pm.Toolbar.setButtonDisabled('tbRectangle', !foundEntityWithoutPolygon);
this.datasources = formattedData;
if (this.options.draggableMarker || this.editPolygons) {
let foundEntityWithoutLocation = false;
let foundEntityWithLocation = false;
let foundEntityWithoutPolygon = false;
let foundEntityWithPolygon = false;
if (this.options.draggableMarker && !this.options.hideDrawControlButton && !this.options.hideAllControlButton) {
for (const mData of formattedData) {
const position = this.convertPosition(mData);
if (!position) {
foundEntityWithoutLocation = true;
} else if (!!position) {
foundEntityWithLocation = true;
}
if (foundEntityWithoutLocation && foundEntityWithLocation) {
break;
}
}
this.map.pm.Toolbar.setButtonDisabled('tbMarker', !foundEntityWithoutLocation);
this.datasources = formattedData;
}
if (this.editPolygons && !this.options.hideDrawControlButton && !this.options.hideAllControlButton) {
for (const pData of formattedData) {
const isValidPolygon = this.isValidPolygonPosition(pData);
if (!isValidPolygon) {
foundEntityWithoutPolygon = true;
} else if (isValidPolygon) {
foundEntityWithPolygon = true;
}
if (foundEntityWithoutPolygon && foundEntityWithPolygon) {
break;
}
}
this.map.pm.Toolbar.setButtonDisabled('tbPolygon', !foundEntityWithoutPolygon);
this.map.pm.Toolbar.setButtonDisabled('tbRectangle', !foundEntityWithoutPolygon);
this.datasources = formattedData;
}
if (!this.options.hideRemoveControlButton && !this.options.hideAllControlButton) {
const disabledButton = !foundEntityWithLocation && !foundEntityWithPolygon;
if (disabledButton && this.map.pm.globalRemovalModeEnabled()) {
this.map.pm.toggleGlobalRemovalMode();
}
this.map.pm.Toolbar.setButtonDisabled('removalMode', disabledButton);
}
if (!this.options.hideEditControlButton && !this.options.hideAllControlButton) {
const disabledButton = !foundEntityWithLocation && !foundEntityWithPolygon;
if (disabledButton) {
// @ts-ignore
this.map.pm.Toolbar.toggleButton('dragMode', false);
}
if (this.editPolygons && !foundEntityWithPolygon) {
// @ts-ignore
this.map.pm.Toolbar.toggleButton('editMode', false);
// @ts-ignore
this.map.pm.Toolbar.toggleButton('cutPolygon', false);
// @ts-ignore
this.map.pm.Toolbar.toggleButton('rotateMode', false);
}
this.map.pm.Toolbar.setButtonDisabled('dragMode', disabledButton);
if (this.editPolygons) {
this.map.pm.Toolbar.setButtonDisabled('editMode', !foundEntityWithPolygon);
this.map.pm.Toolbar.setButtonDisabled('cutPolygon', !foundEntityWithPolygon);
this.map.pm.Toolbar.setButtonDisabled('rotateMode', !foundEntityWithPolygon);
}
}
}
} else {
this.updatePending = true;

8
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts

@ -164,6 +164,7 @@ export class ImageMap extends LeafletMap {
const southWest = this.pointToLatLng(-padding, h + padding);
const northEast = this.pointToLatLng(w + padding, -padding);
const maxBounds = new L.LatLngBounds(southWest, northEast);
(this.map as any)._enforcingBounds = true;
this.map.setMaxBounds(maxBounds);
if (lastCenterPos) {
lastCenterPos.x *= w;
@ -171,6 +172,7 @@ export class ImageMap extends LeafletMap {
const center = this.pointToLatLng(lastCenterPos.x, lastCenterPos.y);
this.map.panTo(center, { animate: false });
}
(this.map as any)._enforcingBounds = false;
}
onResize(updateImage?: boolean) {
@ -195,7 +197,9 @@ export class ImageMap extends LeafletMap {
lastCenterPos.x /= prevWidth;
lastCenterPos.y /= prevHeight;
this.updateBounds(updateImage, lastCenterPos);
this.map.invalidateSize(true);
(this.map as any)._enforcingBounds = true;
this.map.invalidateSize(false);
(this.map as any)._enforcingBounds = false;
this.updateMarkers(this.markersData);
if (this.options.draggableMarker && this.addMarkers.length) {
this.addMarkers.forEach((marker) => {
@ -266,7 +270,7 @@ export class ImageMap extends LeafletMap {
return L.CRS.Simple.pointToLatLng({ x, y } as L.PointExpression, maxZoom - 1);
}
latLngToPoint(latLng: LatLngLiteral) {
latLngToPoint(latLng: LatLngLiteral): L.Point {
return L.CRS.Simple.latLngToPoint(latLng, maxZoom - 1);
}

2
ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.html

@ -107,7 +107,7 @@
<mat-row *matRowDef="let column; columns: displayedColumns"
[fxShow]="!persistentDatasource.dataLoading"></mat-row>
</table>
<span [fxShow]="(persistentDatasource.isEmpty() | async) && !persistentDatasource.dataLoading"
<span [fxShow]="(persistentDatasource.isEmpty() | async) && !persistentDatasource.dataLoading && hasData"
fxLayoutAlign="center center"
class="no-data-found">{{ noDataDisplayMessageText }}</span>
<span [fxShow]="persistentDatasource.dataLoading"

2
ui-ngx/src/app/modules/home/components/widget/lib/rpc/persistent-table.component.ts

@ -134,6 +134,7 @@ export class PersistentTableComponent extends PageComponent implements OnInit {
public actionCellButtonAction: PersistentTableWidgetActionDescriptor[] = [];
public displayedColumns: string[];
public hidePageSize = false;
hasData = false;
constructor(protected store: Store<AppState>,
private elementRef: ElementRef,
@ -154,6 +155,7 @@ export class PersistentTableComponent extends PageComponent implements OnInit {
this.widgetConfig = this.ctx.widgetConfig;
this.subscription = this.ctx.defaultSubscription;
this.initializeConfig();
this.hasData = this.ctx.defaultSubscription.hasResolvedData;
this.ctx.updateWidgetParams();
if (this.displayPagination) {
this.widgetResize$ = new ResizeObserver(() => {

10
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -1068,7 +1068,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
switch (type) {
case WidgetActionType.openDashboardState:
case WidgetActionType.updateDashboardState:
let targetDashboardStateId = descriptor.targetDashboardStateId;
const params = deepClone(this.widgetContext.stateController.getStateParams());
updateEntityParams(params, targetEntityParamName, targetEntityId, entityName, entityLabel);
if (type === WidgetActionType.openDashboardState) {
@ -1080,20 +1079,19 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.openDashboardStateInSeparateDialog(descriptor.targetDashboardStateId, params, descriptor.dialogTitle,
descriptor.dialogHideDashboardToolbar, descriptor.dialogWidth, descriptor.dialogHeight);
} else {
this.widgetContext.stateController.openState(targetDashboardStateId, params, descriptor.openRightLayout);
this.widgetContext.stateController.openState(descriptor.targetDashboardStateId, params, descriptor.openRightLayout);
}
} else {
this.widgetContext.stateController.updateState(targetDashboardStateId, params, descriptor.openRightLayout);
this.widgetContext.stateController.updateState(descriptor.targetDashboardStateId, params, descriptor.openRightLayout);
}
break;
case WidgetActionType.openDashboard:
const targetDashboardId = descriptor.targetDashboardId;
targetDashboardStateId = descriptor.targetDashboardStateId;
const stateObject: StateObject = {};
stateObject.params = {};
updateEntityParams(stateObject.params, targetEntityParamName, targetEntityId, entityName, entityLabel);
if (targetDashboardStateId) {
stateObject.id = targetDashboardStateId;
if (descriptor.targetDashboardStateId) {
stateObject.id = descriptor.targetDashboardStateId;
}
const state = objToBase64URI([ stateObject ]);
const isSinglePage = this.route.snapshot.data.singlePageMode;

12
ui-ngx/src/assets/help/en_US/rulenode/transformation_node_script_fn.md

@ -66,6 +66,18 @@ return {msg: msg, metadata: metadata, msgType: newType};
{:copy-code}
```
* Replace the incoming message with **two** new messages that contain only one field - sum or difference of properties in the original message:
```javascript
var sum = msg.a + msg.b;
var diff = msg.a - msg.b;
return [
{msg: {sum: sum}, metadata: metadata, msgType: msgType},
{msg: {difference: diff}, metadata: metadata, msgType: msgType}
];
```
<br>
You can see real life example, how to use this node in those tutorials:

16
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3322,12 +3322,12 @@
"select-entity": "Select entity",
"select-entity-hint": "Hint: after selection click at the map to set position",
"tooltips": {
"placeMarker": "Click to place marker",
"firstVertex": "Click to place first point",
"continueLine": "Click to continue drawing",
"placeMarker": "Click to place '{{entityName}}' entity",
"firstVertex": "Polygon for '{{entityName}}': click to place first point",
"continueLine": "Polygon for '{{entityName}}': click to continue drawing",
"finishLine": "Click any existing marker to finish",
"finishPoly": "Click first marker to finish and save",
"finishRect": "Click to finish and save",
"finishPoly": "Polygon for '{{entityName}}': click first marker to finish and save",
"finishRect": "Polygon for '{{entityName}}': click to finish and save",
"startCircle": "Click to place circle center",
"finishCircle": "Click to finish circle",
"placeCircleMarker": "Click to place circle marker"
@ -3338,10 +3338,10 @@
"removeLastVertex": "Remove last point"
},
"buttonTitles": {
"drawMarkerButton": "Create marker",
"drawMarkerButton": "Place entity",
"drawPolyButton": "Create polygon",
"drawLineButton": "Create Polyline",
"drawCircleButton": "Create Circle",
"drawLineButton": "Create polyline",
"drawCircleButton": "Create circle",
"drawRectButton": "Create rectangle",
"editButton": "Edit mode",
"dragButton": "Drag-drop mode",

Loading…
Cancel
Save