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)) {
log.info("Migrating ThingsBoard latest timeseries data from cassandra to SQL database ...");
latestMigrateService.migrate();
log.info("Updating system data...");
} else {
switch (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");
dataUpdateService.updateData("3.1.1");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
systemDataLoaderService.createOAuth2Templates();
break;
case "3.2.0":
log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.2.0");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break;
default:
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.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import java.io.PrintWriter;
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")
public class AuditLogServiceImpl implements AuditLogService {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
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,
ActionType actionType,
Object... additionalInfo) {
ObjectNode actionData = objectMapper.createObjectNode();
ObjectNode actionData = JacksonUtil.newObjectNode();
switch (actionType) {
case ADDED:
case UPDATED:
@ -168,7 +166,7 @@ public class AuditLogServiceImpl implements AuditLogService {
case RELATIONS_DELETED:
case ASSIGNED_TO_TENANT:
if (entity != null) {
ObjectNode entityNode = objectMapper.valueToTree(entity);
ObjectNode entityNode = (ObjectNode) JacksonUtil.valueToTree(entity);
if (entityId.getEntityType() == EntityType.DASHBOARD) {
entityNode.put("configuration", "");
}
@ -177,7 +175,7 @@ public class AuditLogServiceImpl implements AuditLogService {
if (entityId.getEntityType() == EntityType.RULE_CHAIN) {
RuleChainMetaData ruleChainMetaData = extractParameter(RuleChainMetaData.class, additionalInfo);
if (ruleChainMetaData != null) {
ObjectNode ruleChainMetaDataNode = objectMapper.valueToTree(ruleChainMetaData);
ObjectNode ruleChainMetaDataNode = (ObjectNode) JacksonUtil.valueToTree(ruleChainMetaData);
actionData.set("metadata", ruleChainMetaDataNode);
}
}
@ -194,7 +192,7 @@ public class AuditLogServiceImpl implements AuditLogService {
String scope = extractParameter(String.class, 0, additionalInfo);
List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo);
actionData.put("scope", scope);
ObjectNode attrsNode = objectMapper.createObjectNode();
ObjectNode attrsNode = JacksonUtil.newObjectNode();
if (attributes != null) {
for (AttributeKvEntry attr : attributes) {
attrsNode.put(attr.getKey(), attr.getValueAsString());
@ -225,7 +223,7 @@ public class AuditLogServiceImpl implements AuditLogService {
case CREDENTIALS_UPDATED:
actionData.put("entityId", entityId.toString());
DeviceCredentials deviceCredentials = extractParameter(DeviceCredentials.class, additionalInfo);
actionData.set("credentials", objectMapper.valueToTree(deviceCredentials));
actionData.set("credentials", JacksonUtil.valueToTree(deviceCredentials));
break;
case ASSIGNED_TO_CUSTOMER:
strEntityId = extractParameter(String.class, 0, additionalInfo);
@ -246,7 +244,7 @@ public class AuditLogServiceImpl implements AuditLogService {
case RELATION_ADD_OR_UPDATE:
case RELATION_DELETED:
EntityRelation relation = extractParameter(EntityRelation.class, 0, additionalInfo);
actionData.set("relation", objectMapper.valueToTree(relation));
actionData.set("relation", JacksonUtil.valueToTree(relation));
break;
case LOGIN:
case LOGOUT:
@ -264,7 +262,7 @@ public class AuditLogServiceImpl implements AuditLogService {
case PROVISION_FAILURE:
ProvisionRequest request = extractParameter(ProvisionRequest.class, additionalInfo);
if (request != null) {
actionData.set("provisionRequest", objectMapper.valueToTree(request));
actionData.set("provisionRequest", JacksonUtil.valueToTree(request));
}
break;
case TIMESERIES_UPDATED:
@ -275,7 +273,7 @@ public class AuditLogServiceImpl implements AuditLogService {
updatedTimeseries.stream()
.collect(Collectors.groupingBy(TsKvEntry::getTs))
.forEach((k, v) -> {
ObjectNode element = objectMapper.createObjectNode();
ObjectNode element = JacksonUtil.newObjectNode();
element.put("ts", k);
ObjectNode values = element.putObject("values");
v.forEach(kvEntry -> values.put(kvEntry.getKey(), kvEntry.getValueAsString()));

View File

@ -58,7 +58,7 @@ frontend http-in
acl transport_http_acl path_beg /api/v1/
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 }
@ -76,7 +76,7 @@ frontend https_in
reqadd X-Forwarded-Proto:\ https
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-api-backend if tb_api_acl

View File

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

View File

@ -16,7 +16,7 @@
#
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 "usage: ./client.keygen.sh [-p file]"
echo " -p | --props | --properties file Properties file. default value is ./keygen.properties"
@ -70,6 +70,20 @@ while :
done
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..."
keytool -genkeypair -v \
@ -77,8 +91,8 @@ keytool -genkeypair -v \
-keystore $CLIENT_FILE_PREFIX.jks \
-keypass $CLIENT_KEY_PASSWORD \
-storepass $CLIENT_KEYSTORE_PASSWORD \
-keyalg RSA \
-keysize 2048 \
-keyalg $CLIENT_KEY_ALG \
-keysize $CLIENT_KEY_SIZE\
-validity 9999 \
-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
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) )) \
$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_FILE_PREFIX="mqttserver"
SERVER_KEY_ALG="RSA"
SERVER_KEY_SIZE="2048"
SERVER_KEYSTORE_DIR="/etc/thingsboard/conf"
CLIENT_KEYSTORE_PASSWORD=password
@ -33,4 +35,5 @@ CLIENT_KEY_PASSWORD=password
CLIENT_KEY_ALIAS="clientalias"
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 \
-keypass $SERVER_KEY_PASSWORD \
-storepass $SERVER_KEYSTORE_PASSWORD \
-keyalg RSA \
-keysize 2048 \
-keyalg $SERVER_KEY_ALG \
-keysize $SERVER_KEY_SIZE \
-validity 9999
status=$?

View File

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

View File

@ -112,6 +112,12 @@
{{ 'widget-action.open-right-layout' | translate }}
</mat-checkbox>
</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 ||
widgetActionFormGroup.get('type').value === widgetActionType.updateDashboardState ||
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, [])
);
if (type === WidgetActionType.openDashboard) {
this.actionTypeFormGroup.addControl(
'openNewBrowserTab',
this.fb.control(action ? action.openNewBrowserTab : false, [])
);
this.actionTypeFormGroup.addControl(
'targetDashboardId',
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 { mapProviderSchema, providerSets } from '@home/components/widget/lib/maps/schemes';
import { addCondition, mergeSchemes } from '@core/schema-utils';
import L, {Projection} from "leaflet";
export function getProviderSchema(mapProvider: MapProviders, ignoreImageMap = false) {
const providerSchema = _.cloneDeep(mapProviderSchema);
@ -443,3 +444,21 @@ export function createLoadingDiv(loadingText: string): JQuery<HTMLElement> {
</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, {
FeatureGroup,
Icon,
Icon, LatLng,
LatLngBounds,
LatLngTuple,
markerClusterGroup,
MarkerClusterGroup,
MarkerClusterGroupOptions
MarkerClusterGroupOptions, Projection
} from 'leaflet';
import tinycolor from 'tinycolor2';
import 'leaflet-providers';
@ -46,6 +46,7 @@ import {
createTooltip,
} from '@home/components/widget/lib/maps/maps-utils';
import {
checkLngLat,
createLoadingDiv,
parseArray,
parseData,
@ -79,6 +80,8 @@ export default abstract class LeafletMap {
updatePending = false;
addMarkers: L.Marker[] = [];
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,
public $container: HTMLElement,
@ -206,21 +209,30 @@ export default abstract class LeafletMap {
addPolygonControl() {
if (this.options.showPolygon && this.options.editablePolygon) {
let mousePositionOnMap: L.LatLng[];
let polygonPoints: L.LatLng[];
let addPolygon: L.Control;
let mousePositionOnMap: LatLng;
this.map.on('mousemove', (e: L.LeafletMouseEvent) => {
const polygonOffset = this.options.provider === MapProviders.image ? 10 : 0.01;
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];
mousePositionOnMap = e.latlng;
});
const dragListener = (e: L.DragEndEvent) => {
if (e.type === 'dragend' && mousePositionOnMap) {
const newPolygon = L.polygon(mousePositionOnMap).addTo(this.map);
if (e.type === 'dragend') {
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);
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');
header.appendChild(document.createTextNode('Select entity:'));
header.setAttribute('style', 'font-size: 14px; margin: 8px 0');
@ -414,12 +426,9 @@ export default abstract class LeafletMap {
}).filter(el => !!el);
}
convertToCustomFormat(position: L.LatLng): object {
if (position.lng > 180) {
position.lng = 180;
} else if (position.lng < -180) {
position.lng = -180;
}
convertToCustomFormat(position: L.LatLng, offset = 0): object {
position = checkLngLat(position, this.southWest, this.northEast, offset);
return {
[this.options.latKeyName]: position.lat,
[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')) {
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();
}

View File

@ -19,7 +19,12 @@ import LeafletMap from '../leaflet-map';
import { MapImage, PosFuncton, UnitedMapSettings } from '../map-models';
import { Observable, ReplaySubject } from 'rxjs';
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 { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
@ -132,9 +137,9 @@ export class ImageMap extends LeafletMap {
updateBounds(updateImage?: boolean, lastCenterPos?) {
const w = this.width;
const h = this.height;
let southWest = this.pointToLatLng(0, h);
let northEast = this.pointToLatLng(w, 0);
const bounds = new L.LatLngBounds(southWest, northEast);
this.southWest = this.pointToLatLng(0, h);
this.northEast = this.pointToLatLng(w, 0);
const bounds = new L.LatLngBounds(this.southWest, this.northEast);
if (updateImage && this.imageOverlay) {
this.imageOverlay.remove();
@ -147,8 +152,8 @@ export class ImageMap extends LeafletMap {
this.imageOverlay = L.imageOverlay(this.imageUrl, bounds).addTo(this.map);
}
const padding = 200 * maxZoom;
southWest = this.pointToLatLng(-padding, h + padding);
northEast = this.pointToLatLng(w + padding, -padding);
const southWest = this.pointToLatLng(-padding, h + padding);
const northEast = this.pointToLatLng(w + padding, -padding);
const maxBounds = new L.LatLngBounds(southWest, northEast);
this.map.setMaxBounds(maxBounds);
if (lastCenterPos) {
@ -187,7 +192,7 @@ export class ImageMap extends LeafletMap {
this.updateMarkers(this.markersData);
if (this.options.draggableMarker && this.addMarkers.length) {
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));
});
}
@ -257,11 +262,10 @@ export class ImageMap extends LeafletMap {
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 customX = calculateNewPointCoordinate(point.x, width);
const customY = calculateNewPointCoordinate(point.y, height);
if (customX === 0) {
point.x = 0;
} else if (customX === 1) {
@ -273,7 +277,8 @@ export class ImageMap extends LeafletMap {
} else if (customY === 1) {
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 {
[this.options.xPosKeyName]: customX,

View File

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

View File

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

View File

@ -2257,7 +2257,8 @@
"target-dashboard-state-required": "Target dashboard state is required",
"set-entity-from-widget": "Set entity from widget",
"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": {
"current": "Current bundle",