This commit is contained in:
lsyer 2021-01-17 14:58:07 +08:00
commit 0f24e00fea
18 changed files with 129 additions and 61 deletions

File diff suppressed because one or more lines are too long

View File

@ -93,7 +93,6 @@ public class ThingsboardInstallService {
} else if ("3.0.1-cassandra".equals(upgradeFromVersion)) { } else if ("3.0.1-cassandra".equals(upgradeFromVersion)) {
log.info("Migrating ThingsBoard latest timeseries data from cassandra to SQL database ..."); log.info("Migrating ThingsBoard latest timeseries data from cassandra to SQL database ...");
latestMigrateService.migrate(); latestMigrateService.migrate();
log.info("Updating system data...");
} else { } else {
switch (upgradeFromVersion) { switch (upgradeFromVersion) {
case "1.2.3": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion case "1.2.3": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion
@ -182,13 +181,12 @@ public class ThingsboardInstallService {
} }
databaseEntitiesUpgradeService.upgradeDatabase("3.1.1"); databaseEntitiesUpgradeService.upgradeDatabase("3.1.1");
dataUpdateService.updateData("3.1.1"); dataUpdateService.updateData("3.1.1");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
systemDataLoaderService.createOAuth2Templates(); systemDataLoaderService.createOAuth2Templates();
break;
case "3.2.0": case "3.2.0":
log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ..."); log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.2.0"); databaseEntitiesUpgradeService.upgradeDatabase("3.2.0");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break; break;
default: default:
throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion); throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);

View File

@ -17,7 +17,6 @@ package org.thingsboard.server.dao.audit;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@ -50,6 +49,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
@ -65,8 +65,6 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@ConditionalOnProperty(prefix = "audit-log", value = "enabled", havingValue = "true") @ConditionalOnProperty(prefix = "audit-log", value = "enabled", havingValue = "true")
public class AuditLogServiceImpl implements AuditLogService { public class AuditLogServiceImpl implements AuditLogService {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; private static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
private static final int INSERTS_PER_ENTRY = 3; private static final int INSERTS_PER_ENTRY = 3;
@ -159,7 +157,7 @@ public class AuditLogServiceImpl implements AuditLogService {
private <E extends HasName, I extends EntityId> JsonNode constructActionData(I entityId, E entity, private <E extends HasName, I extends EntityId> JsonNode constructActionData(I entityId, E entity,
ActionType actionType, ActionType actionType,
Object... additionalInfo) { Object... additionalInfo) {
ObjectNode actionData = objectMapper.createObjectNode(); ObjectNode actionData = JacksonUtil.newObjectNode();
switch (actionType) { switch (actionType) {
case ADDED: case ADDED:
case UPDATED: case UPDATED:
@ -168,7 +166,7 @@ public class AuditLogServiceImpl implements AuditLogService {
case RELATIONS_DELETED: case RELATIONS_DELETED:
case ASSIGNED_TO_TENANT: case ASSIGNED_TO_TENANT:
if (entity != null) { if (entity != null) {
ObjectNode entityNode = objectMapper.valueToTree(entity); ObjectNode entityNode = (ObjectNode) JacksonUtil.valueToTree(entity);
if (entityId.getEntityType() == EntityType.DASHBOARD) { if (entityId.getEntityType() == EntityType.DASHBOARD) {
entityNode.put("configuration", ""); entityNode.put("configuration", "");
} }
@ -177,7 +175,7 @@ public class AuditLogServiceImpl implements AuditLogService {
if (entityId.getEntityType() == EntityType.RULE_CHAIN) { if (entityId.getEntityType() == EntityType.RULE_CHAIN) {
RuleChainMetaData ruleChainMetaData = extractParameter(RuleChainMetaData.class, additionalInfo); RuleChainMetaData ruleChainMetaData = extractParameter(RuleChainMetaData.class, additionalInfo);
if (ruleChainMetaData != null) { if (ruleChainMetaData != null) {
ObjectNode ruleChainMetaDataNode = objectMapper.valueToTree(ruleChainMetaData); ObjectNode ruleChainMetaDataNode = (ObjectNode) JacksonUtil.valueToTree(ruleChainMetaData);
actionData.set("metadata", ruleChainMetaDataNode); actionData.set("metadata", ruleChainMetaDataNode);
} }
} }
@ -194,7 +192,7 @@ public class AuditLogServiceImpl implements AuditLogService {
String scope = extractParameter(String.class, 0, additionalInfo); String scope = extractParameter(String.class, 0, additionalInfo);
List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo); List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo);
actionData.put("scope", scope); actionData.put("scope", scope);
ObjectNode attrsNode = objectMapper.createObjectNode(); ObjectNode attrsNode = JacksonUtil.newObjectNode();
if (attributes != null) { if (attributes != null) {
for (AttributeKvEntry attr : attributes) { for (AttributeKvEntry attr : attributes) {
attrsNode.put(attr.getKey(), attr.getValueAsString()); attrsNode.put(attr.getKey(), attr.getValueAsString());
@ -225,7 +223,7 @@ public class AuditLogServiceImpl implements AuditLogService {
case CREDENTIALS_UPDATED: case CREDENTIALS_UPDATED:
actionData.put("entityId", entityId.toString()); actionData.put("entityId", entityId.toString());
DeviceCredentials deviceCredentials = extractParameter(DeviceCredentials.class, additionalInfo); DeviceCredentials deviceCredentials = extractParameter(DeviceCredentials.class, additionalInfo);
actionData.set("credentials", objectMapper.valueToTree(deviceCredentials)); actionData.set("credentials", JacksonUtil.valueToTree(deviceCredentials));
break; break;
case ASSIGNED_TO_CUSTOMER: case ASSIGNED_TO_CUSTOMER:
strEntityId = extractParameter(String.class, 0, additionalInfo); strEntityId = extractParameter(String.class, 0, additionalInfo);
@ -246,7 +244,7 @@ public class AuditLogServiceImpl implements AuditLogService {
case RELATION_ADD_OR_UPDATE: case RELATION_ADD_OR_UPDATE:
case RELATION_DELETED: case RELATION_DELETED:
EntityRelation relation = extractParameter(EntityRelation.class, 0, additionalInfo); EntityRelation relation = extractParameter(EntityRelation.class, 0, additionalInfo);
actionData.set("relation", objectMapper.valueToTree(relation)); actionData.set("relation", JacksonUtil.valueToTree(relation));
break; break;
case LOGIN: case LOGIN:
case LOGOUT: case LOGOUT:
@ -264,7 +262,7 @@ public class AuditLogServiceImpl implements AuditLogService {
case PROVISION_FAILURE: case PROVISION_FAILURE:
ProvisionRequest request = extractParameter(ProvisionRequest.class, additionalInfo); ProvisionRequest request = extractParameter(ProvisionRequest.class, additionalInfo);
if (request != null) { if (request != null) {
actionData.set("provisionRequest", objectMapper.valueToTree(request)); actionData.set("provisionRequest", JacksonUtil.valueToTree(request));
} }
break; break;
case TIMESERIES_UPDATED: case TIMESERIES_UPDATED:
@ -275,7 +273,7 @@ public class AuditLogServiceImpl implements AuditLogService {
updatedTimeseries.stream() updatedTimeseries.stream()
.collect(Collectors.groupingBy(TsKvEntry::getTs)) .collect(Collectors.groupingBy(TsKvEntry::getTs))
.forEach((k, v) -> { .forEach((k, v) -> {
ObjectNode element = objectMapper.createObjectNode(); ObjectNode element = JacksonUtil.newObjectNode();
element.put("ts", k); element.put("ts", k);
ObjectNode values = element.putObject("values"); ObjectNode values = element.putObject("values");
v.forEach(kvEntry -> values.put(kvEntry.getKey(), kvEntry.getValueAsString())); v.forEach(kvEntry -> values.put(kvEntry.getKey(), kvEntry.getValueAsString()));

View File

@ -66,7 +66,7 @@ public class JacksonUtil {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
} }
} }
public static ObjectNode newObjectNode(){ public static ObjectNode newObjectNode(){
return OBJECT_MAPPER.createObjectNode(); return OBJECT_MAPPER.createObjectNode();
} }

View File

@ -58,7 +58,7 @@ frontend http-in
acl transport_http_acl path_beg /api/v1/ acl transport_http_acl path_beg /api/v1/
acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/ acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/
acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ /static/widgets/
redirect scheme https if !letsencrypt_http_acl !transport_http_acl { env(FORCE_HTTPS_REDIRECT) -m str true } redirect scheme https if !letsencrypt_http_acl !transport_http_acl { env(FORCE_HTTPS_REDIRECT) -m str true }
@ -76,7 +76,7 @@ frontend https_in
reqadd X-Forwarded-Proto:\ https reqadd X-Forwarded-Proto:\ https
acl transport_http_acl path_beg /api/v1/ acl transport_http_acl path_beg /api/v1/
acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ /static/widgets/
use_backend tb-http-backend if transport_http_acl use_backend tb-http-backend if transport_http_acl
use_backend tb-api-backend if tb_api_acl use_backend tb-api-backend if tb_api_acl

View File

@ -64,6 +64,7 @@ class ProfileState {
alarmSettings.clear(); alarmSettings.clear();
alarmCreateKeys.clear(); alarmCreateKeys.clear();
alarmClearKeys.clear(); alarmClearKeys.clear();
entityKeys.clear();
if (deviceProfile.getProfileData().getAlarms() != null) { if (deviceProfile.getProfileData().getAlarms() != null) {
alarmSettings.addAll(deviceProfile.getProfileData().getAlarms()); alarmSettings.addAll(deviceProfile.getProfileData().getAlarms());
for (DeviceProfileAlarm alarm : deviceProfile.getProfileData().getAlarms()) { for (DeviceProfileAlarm alarm : deviceProfile.getProfileData().getAlarms()) {

View File

@ -16,7 +16,7 @@
# #
usage() { usage() {
echo "This script generates client public/private rey pair, extracts them to a no-password RSA pem file," echo "This script generates client public/private key pair, extracts them to a no-password pem file,"
echo "and imports server public key to client keystore" echo "and imports server public key to client keystore"
echo "usage: ./client.keygen.sh [-p file]" echo "usage: ./client.keygen.sh [-p file]"
echo " -p | --props | --properties file Properties file. default value is ./keygen.properties" echo " -p | --props | --properties file Properties file. default value is ./keygen.properties"
@ -70,6 +70,20 @@ while :
done done
fi fi
OPENSSL_CMD=""
case $CLIENT_KEY_ALG in
RSA)
OPENSSL_CMD="rsa"
;;
EC)
OPENSSL_CMD="ec"
;;
esac
if [ -z "$OPENSSL_CMD" ]; then
echo "Unexpected CLIENT_KEY_ALG. Exiting."
exit 0
fi
echo "Generating SSL Key Pair..." echo "Generating SSL Key Pair..."
keytool -genkeypair -v \ keytool -genkeypair -v \
@ -77,8 +91,8 @@ keytool -genkeypair -v \
-keystore $CLIENT_FILE_PREFIX.jks \ -keystore $CLIENT_FILE_PREFIX.jks \
-keypass $CLIENT_KEY_PASSWORD \ -keypass $CLIENT_KEY_PASSWORD \
-storepass $CLIENT_KEYSTORE_PASSWORD \ -storepass $CLIENT_KEYSTORE_PASSWORD \
-keyalg RSA \ -keyalg $CLIENT_KEY_ALG \
-keysize 2048 \ -keysize $CLIENT_KEY_SIZE\
-validity 9999 \ -validity 9999 \
-dname "CN=$DOMAIN_SUFFIX, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" -dname "CN=$DOMAIN_SUFFIX, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE"
@ -110,7 +124,7 @@ keytool --importcert \
-noprompt -noprompt
echo "Exporting no-password pem certificate" echo "Exporting no-password pem certificate"
openssl rsa -in $CLIENT_FILE_PREFIX.pem -out $CLIENT_FILE_PREFIX.nopass.pem -passin pass:$CLIENT_KEY_PASSWORD openssl $OPENSSL_CMD -in $CLIENT_FILE_PREFIX.pem -out $CLIENT_FILE_PREFIX.nopass.pem -passin pass:$CLIENT_KEY_PASSWORD
tail -n +$(($(grep -m1 -n -e '-----BEGIN CERTIFICATE' $CLIENT_FILE_PREFIX.pem | cut -d: -f1) )) \ tail -n +$(($(grep -m1 -n -e '-----BEGIN CERTIFICATE' $CLIENT_FILE_PREFIX.pem | cut -d: -f1) )) \
$CLIENT_FILE_PREFIX.pem >> $CLIENT_FILE_PREFIX.nopass.pem $CLIENT_FILE_PREFIX.pem >> $CLIENT_FILE_PREFIX.nopass.pem

View File

@ -26,6 +26,8 @@ SERVER_KEY_PASSWORD=server_key_password
SERVER_KEY_ALIAS="serveralias" SERVER_KEY_ALIAS="serveralias"
SERVER_FILE_PREFIX="mqttserver" SERVER_FILE_PREFIX="mqttserver"
SERVER_KEY_ALG="RSA"
SERVER_KEY_SIZE="2048"
SERVER_KEYSTORE_DIR="/etc/thingsboard/conf" SERVER_KEYSTORE_DIR="/etc/thingsboard/conf"
CLIENT_KEYSTORE_PASSWORD=password CLIENT_KEYSTORE_PASSWORD=password
@ -33,4 +35,5 @@ CLIENT_KEY_PASSWORD=password
CLIENT_KEY_ALIAS="clientalias" CLIENT_KEY_ALIAS="clientalias"
CLIENT_FILE_PREFIX="mqttclient" CLIENT_FILE_PREFIX="mqttclient"
CLIENT_KEY_ALG="RSA"
CLIENT_KEY_SIZE="2048"

View File

@ -92,8 +92,8 @@ keytool -genkeypair -v \
-keystore $SERVER_FILE_PREFIX.jks \ -keystore $SERVER_FILE_PREFIX.jks \
-keypass $SERVER_KEY_PASSWORD \ -keypass $SERVER_KEY_PASSWORD \
-storepass $SERVER_KEYSTORE_PASSWORD \ -storepass $SERVER_KEYSTORE_PASSWORD \
-keyalg RSA \ -keyalg $SERVER_KEY_ALG \
-keysize 2048 \ -keysize $SERVER_KEY_SIZE \
-validity 9999 -validity 9999
status=$? status=$?

View File

@ -26,6 +26,10 @@ const PROXY_CONFIG = {
"target": ruleNodeUiforwardUrl, "target": ruleNodeUiforwardUrl,
"secure": false, "secure": false,
}, },
"/static/widgets": {
"target": forwardUrl,
"secure": false,
},
"/oauth2": { "/oauth2": {
"target": forwardUrl, "target": forwardUrl,
"secure": false, "secure": false,
@ -34,10 +38,6 @@ const PROXY_CONFIG = {
"target": forwardUrl, "target": forwardUrl,
"secure": false, "secure": false,
}, },
"/static": {
"target": forwardUrl,
"secure": false,
},
"/api/ws": { "/api/ws": {
"target": wsForwardUrl, "target": wsForwardUrl,
"ws": true, "ws": true,

View File

@ -112,6 +112,12 @@
{{ 'widget-action.open-right-layout' | translate }} {{ 'widget-action.open-right-layout' | translate }}
</mat-checkbox> </mat-checkbox>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="widgetActionFormGroup.get('type').value === widgetActionType.openDashboard ?
widgetActionFormGroup.get('type').value : ''">
<mat-checkbox formControlName="openNewBrowserTab">
{{ 'widget-action.open-new-browser-tab' | translate }}
</mat-checkbox>
</ng-template>
<ng-template [ngSwitchCase]="widgetActionFormGroup.get('type').value === widgetActionType.openDashboardState || <ng-template [ngSwitchCase]="widgetActionFormGroup.get('type').value === widgetActionType.openDashboardState ||
widgetActionFormGroup.get('type').value === widgetActionType.updateDashboardState || widgetActionFormGroup.get('type').value === widgetActionType.updateDashboardState ||
widgetActionFormGroup.get('type').value === widgetActionType.openDashboard ? widgetActionFormGroup.get('type').value === widgetActionType.openDashboard ?

View File

@ -144,6 +144,10 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
this.fb.control(action ? action.stateEntityParamName : null, []) this.fb.control(action ? action.stateEntityParamName : null, [])
); );
if (type === WidgetActionType.openDashboard) { if (type === WidgetActionType.openDashboard) {
this.actionTypeFormGroup.addControl(
'openNewBrowserTab',
this.fb.control(action ? action.openNewBrowserTab : false, [])
);
this.actionTypeFormGroup.addControl( this.actionTypeFormGroup.addControl(
'targetDashboardId', 'targetDashboardId',
this.fb.control(action ? action.targetDashboardId : null, this.fb.control(action ? action.targetDashboardId : null,

View File

@ -30,6 +30,7 @@ import { Datasource, DatasourceData } from '@shared/models/widget.models';
import _ from 'lodash'; import _ from 'lodash';
import { mapProviderSchema, providerSets } from '@home/components/widget/lib/maps/schemes'; import { mapProviderSchema, providerSets } from '@home/components/widget/lib/maps/schemes';
import { addCondition, mergeSchemes } from '@core/schema-utils'; import { addCondition, mergeSchemes } from '@core/schema-utils';
import L, {Projection} from "leaflet";
export function getProviderSchema(mapProvider: MapProviders, ignoreImageMap = false) { export function getProviderSchema(mapProvider: MapProviders, ignoreImageMap = false) {
const providerSchema = _.cloneDeep(mapProviderSchema); const providerSchema = _.cloneDeep(mapProviderSchema);
@ -443,3 +444,21 @@ export function createLoadingDiv(loadingText: string): JQuery<HTMLElement> {
</div> </div>
`); `);
} }
export function checkLngLat(point: L.LatLng, southWest: L.LatLng, northEast: L.LatLng, offset = 0): L.LatLng {
const maxLngMap = northEast.lng - offset;
const minLngMap = southWest.lng + offset;
const maxLatMap = northEast.lat - offset;
const minLatMap = southWest.lat + offset;
if (point.lng > maxLngMap) {
point.lng = maxLngMap;
} else if (point.lng < minLngMap) {
point.lng = minLngMap;
}
if (point.lat > maxLatMap) {
point.lat = maxLatMap;
} else if (point.lat < minLatMap) {
point.lat = minLatMap;
}
return point;
}

View File

@ -16,12 +16,12 @@
import L, { import L, {
FeatureGroup, FeatureGroup,
Icon, Icon, LatLng,
LatLngBounds, LatLngBounds,
LatLngTuple, LatLngTuple,
markerClusterGroup, markerClusterGroup,
MarkerClusterGroup, MarkerClusterGroup,
MarkerClusterGroupOptions MarkerClusterGroupOptions, Projection
} from 'leaflet'; } from 'leaflet';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import 'leaflet-providers'; import 'leaflet-providers';
@ -46,6 +46,7 @@ import {
createTooltip, createTooltip,
} from '@home/components/widget/lib/maps/maps-utils'; } from '@home/components/widget/lib/maps/maps-utils';
import { import {
checkLngLat,
createLoadingDiv, createLoadingDiv,
parseArray, parseArray,
parseData, parseData,
@ -79,6 +80,8 @@ export default abstract class LeafletMap {
updatePending = false; updatePending = false;
addMarkers: L.Marker[] = []; addMarkers: L.Marker[] = [];
addPolygons: L.Polygon[] = []; addPolygons: L.Polygon[] = [];
southWest = new L.LatLng(-Projection.SphericalMercator['MAX_LATITUDE'], -180);
northEast = new L.LatLng(Projection.SphericalMercator['MAX_LATITUDE'], 180);
protected constructor(public ctx: WidgetContext, protected constructor(public ctx: WidgetContext,
public $container: HTMLElement, public $container: HTMLElement,
@ -206,21 +209,30 @@ export default abstract class LeafletMap {
addPolygonControl() { addPolygonControl() {
if (this.options.showPolygon && this.options.editablePolygon) { if (this.options.showPolygon && this.options.editablePolygon) {
let mousePositionOnMap: L.LatLng[]; let polygonPoints: L.LatLng[];
let addPolygon: L.Control; let addPolygon: L.Control;
let mousePositionOnMap: LatLng;
this.map.on('mousemove', (e: L.LeafletMouseEvent) => { this.map.on('mousemove', (e: L.LeafletMouseEvent) => {
const polygonOffset = this.options.provider === MapProviders.image ? 10 : 0.01; mousePositionOnMap = e.latlng;
const latlng1 = e.latlng;
const latlng2 = L.latLng(e.latlng.lat, e.latlng.lng + polygonOffset);
const latlng3 = L.latLng(e.latlng.lat - polygonOffset, e.latlng.lng);
mousePositionOnMap = [latlng1, latlng2, latlng3];
}); });
const dragListener = (e: L.DragEndEvent) => { const dragListener = (e: L.DragEndEvent) => {
if (e.type === 'dragend' && mousePositionOnMap) { if (e.type === 'dragend') {
const newPolygon = L.polygon(mousePositionOnMap).addTo(this.map); const polygonOffset = this.options.provider === MapProviders.image ? 10 : 0.01;
let convert = this.convertToCustomFormat(mousePositionOnMap,polygonOffset);
mousePositionOnMap.lat = convert[this.options.latKeyName];
mousePositionOnMap.lng = convert[this.options.lngKeyName];
const latlng1 = mousePositionOnMap;
const latlng2 = L.latLng(mousePositionOnMap.lat, mousePositionOnMap.lng + polygonOffset);
const latlng3 = L.latLng(mousePositionOnMap.lat - polygonOffset, mousePositionOnMap.lng);
polygonPoints = [latlng1, latlng2, latlng3];
const newPolygon = L.polygon(polygonPoints).addTo(this.map);
this.addPolygons.push(newPolygon); this.addPolygons.push(newPolygon);
const datasourcesList = document.createElement('div'); const datasourcesList = document.createElement('div');
const customLatLng = {[this.options.polygonKeyName]: this.convertToPolygonFormat(mousePositionOnMap)}; const customLatLng = {[this.options.polygonKeyName]: this.convertToPolygonFormat(polygonPoints)};
const header = document.createElement('p'); const header = document.createElement('p');
header.appendChild(document.createTextNode('Select entity:')); header.appendChild(document.createTextNode('Select entity:'));
header.setAttribute('style', 'font-size: 14px; margin: 8px 0'); header.setAttribute('style', 'font-size: 14px; margin: 8px 0');
@ -414,12 +426,9 @@ export default abstract class LeafletMap {
}).filter(el => !!el); }).filter(el => !!el);
} }
convertToCustomFormat(position: L.LatLng): object { convertToCustomFormat(position: L.LatLng, offset = 0): object {
if (position.lng > 180) { position = checkLngLat(position, this.southWest, this.northEast, offset);
position.lng = 180;
} else if (position.lng < -180) {
position.lng = -180;
}
return { return {
[this.options.latKeyName]: position.lat, [this.options.latKeyName]: position.lat,
[this.options.lngKeyName]: position.lng [this.options.lngKeyName]: position.lng
@ -728,6 +737,11 @@ export default abstract class LeafletMap {
if (e === undefined || (e.type !== 'editable:vertex:dragend' && e.type !== 'editable:vertex:deleted')) { if (e === undefined || (e.type !== 'editable:vertex:dragend' && e.type !== 'editable:vertex:deleted')) {
return; return;
} }
if(this.options.provider !== MapProviders.image) {
for (let key in e.layer._latlngs[0]) {
e.layer._latlngs[0][key] = checkLngLat(e.layer._latlngs[0][key], this.southWest, this.northEast);
}
}
this.savePolygonLocation({ ...data, ...this.convertPolygonToCustomFormat(e.layer._latlngs) }).subscribe(); this.savePolygonLocation({ ...data, ...this.convertPolygonToCustomFormat(e.layer._latlngs) }).subscribe();
} }

View File

@ -19,7 +19,12 @@ import LeafletMap from '../leaflet-map';
import { MapImage, PosFuncton, UnitedMapSettings } from '../map-models'; import { MapImage, PosFuncton, UnitedMapSettings } from '../map-models';
import { Observable, ReplaySubject } from 'rxjs'; import { Observable, ReplaySubject } from 'rxjs';
import { filter, map, mergeMap } from 'rxjs/operators'; import { filter, map, mergeMap } from 'rxjs/operators';
import { aspectCache, calculateNewPointCoordinate, parseFunction } from '@home/components/widget/lib/maps/common-maps-utils'; import {
aspectCache,
calculateNewPointCoordinate,
checkLngLat,
parseFunction
} from '@home/components/widget/lib/maps/common-maps-utils';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models'; import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
@ -132,9 +137,9 @@ export class ImageMap extends LeafletMap {
updateBounds(updateImage?: boolean, lastCenterPos?) { updateBounds(updateImage?: boolean, lastCenterPos?) {
const w = this.width; const w = this.width;
const h = this.height; const h = this.height;
let southWest = this.pointToLatLng(0, h); this.southWest = this.pointToLatLng(0, h);
let northEast = this.pointToLatLng(w, 0); this.northEast = this.pointToLatLng(w, 0);
const bounds = new L.LatLngBounds(southWest, northEast); const bounds = new L.LatLngBounds(this.southWest, this.northEast);
if (updateImage && this.imageOverlay) { if (updateImage && this.imageOverlay) {
this.imageOverlay.remove(); this.imageOverlay.remove();
@ -147,8 +152,8 @@ export class ImageMap extends LeafletMap {
this.imageOverlay = L.imageOverlay(this.imageUrl, bounds).addTo(this.map); this.imageOverlay = L.imageOverlay(this.imageUrl, bounds).addTo(this.map);
} }
const padding = 200 * maxZoom; const padding = 200 * maxZoom;
southWest = this.pointToLatLng(-padding, h + padding); const southWest = this.pointToLatLng(-padding, h + padding);
northEast = this.pointToLatLng(w + padding, -padding); const northEast = this.pointToLatLng(w + padding, -padding);
const maxBounds = new L.LatLngBounds(southWest, northEast); const maxBounds = new L.LatLngBounds(southWest, northEast);
this.map.setMaxBounds(maxBounds); this.map.setMaxBounds(maxBounds);
if (lastCenterPos) { if (lastCenterPos) {
@ -187,7 +192,7 @@ export class ImageMap extends LeafletMap {
this.updateMarkers(this.markersData); this.updateMarkers(this.markersData);
if (this.options.draggableMarker && this.addMarkers.length) { if (this.options.draggableMarker && this.addMarkers.length) {
this.addMarkers.forEach((marker) => { this.addMarkers.forEach((marker) => {
const prevPoint = this.convertToCustomFormat(marker.getLatLng(), prevWidth, prevHeight); const prevPoint = this.convertToCustomFormat(marker.getLatLng(), null, prevWidth, prevHeight);
marker.setLatLng(this.convertPosition(prevPoint)); marker.setLatLng(this.convertPosition(prevPoint));
}); });
} }
@ -257,11 +262,10 @@ export class ImageMap extends LeafletMap {
return L.CRS.Simple.latLngToPoint(latLng, maxZoom - 1); return L.CRS.Simple.latLngToPoint(latLng, maxZoom - 1);
} }
convertToCustomFormat(position: L.LatLng, width = this.width, height = this.height): object { convertToCustomFormat(position: L.LatLng, offset = 0, width = this.width, height = this.height): object {
const point = this.latLngToPoint(position); const point = this.latLngToPoint(position);
const customX = calculateNewPointCoordinate(point.x, width); const customX = calculateNewPointCoordinate(point.x, width);
const customY = calculateNewPointCoordinate(point.y, height); const customY = calculateNewPointCoordinate(point.y, height);
if (customX === 0) { if (customX === 0) {
point.x = 0; point.x = 0;
} else if (customX === 1) { } else if (customX === 1) {
@ -273,7 +277,8 @@ export class ImageMap extends LeafletMap {
} else if (customY === 1) { } else if (customY === 1) {
point.y = height; point.y = height;
} }
const customLatLng = this.pointToLatLng(point.x, point.y);
const customLatLng = checkLngLat(this.pointToLatLng(point.x, point.y), this.southWest, this.northEast, offset);
return { return {
[this.options.xPosKeyName]: customX, [this.options.xPosKeyName]: customX,

View File

@ -1029,7 +1029,11 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
} else { } else {
url = `/dashboards/${targetDashboardId}?state=${state}`; url = `/dashboards/${targetDashboardId}?state=${state}`;
} }
this.router.navigateByUrl(url); if (descriptor.openNewBrowserTab) {
window.open(url, '_blank');
} else {
this.router.navigateByUrl(url);
}
break; break;
case WidgetActionType.custom: case WidgetActionType.custom:
const customFunction = descriptor.customFunction; const customFunction = descriptor.customFunction;

View File

@ -344,6 +344,7 @@ export interface WidgetActionDescriptor extends CustomActionDescriptor {
targetDashboardId?: string; targetDashboardId?: string;
targetDashboardStateId?: string; targetDashboardStateId?: string;
openRightLayout?: boolean; openRightLayout?: boolean;
openNewBrowserTab?: boolean;
setEntityId?: boolean; setEntityId?: boolean;
stateEntityParamName?: string; stateEntityParamName?: string;
} }

View File

@ -2257,7 +2257,8 @@
"target-dashboard-state-required": "Target dashboard state is required", "target-dashboard-state-required": "Target dashboard state is required",
"set-entity-from-widget": "Set entity from widget", "set-entity-from-widget": "Set entity from widget",
"target-dashboard": "Target dashboard", "target-dashboard": "Target dashboard",
"open-right-layout": "Open right dashboard layout (mobile view)" "open-right-layout": "Open right dashboard layout (mobile view)",
"open-new-browser-tab": "Open in a new browser tab"
}, },
"widgets-bundle": { "widgets-bundle": {
"current": "Current bundle", "current": "Current bundle",