Merge branch 'master' into fix_bug_lwm2m_redis
This commit is contained in:
		
						commit
						8828798639
					
				@ -2,15 +2,15 @@
 | 
			
		||||
  "widgetsBundle": {
 | 
			
		||||
    "alias": "status_indicators",
 | 
			
		||||
    "title": "Status indicators",
 | 
			
		||||
    "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAjVBMVEUAAADu7u7u7u7g4OD///9c35Dw8PCt78fz/ffW9+PHx8dw457r+/Hj4+Pl+u6QkJDZ+OWsrKzM9tzf+er4/vuE56xYWFi6urp0dHTV1dXS9uCZ67qCgoKF56tm4Zeenp7C89VmZmZKSkq48c7A89Wj7cFLS0s9PT08PDyF56zi+euP6bN65aWZ67lZWVkXV4nvAAAABHRSTlMA799f7FlksgAABdJJREFUeNrt3I12mjAYBuBt/YAkJCJgCVQEFLX+7v4vbwHGQEnbeQwOXF7XVjjuvDxiCN04fHv5bvaaHy/fRB7Q8gN6znfheESLCT3HFI4HtGiIhlTREA0ZJSQi5ZJ6iKSlV8i7u4qAAYTiUfwEUzVE3qIeQkKWOku+4zvixEuydZcsVA2Rt6iGxCR1VhCIx9YJiANhylVD5C2qIUu+5StYsZ9sSVxRQXagGiJvUQvhbgSMc+CMRSYnplgKVUPkLb0ffmOmGiJv0fOIhmjIVTREQ54FMquiGiJv6REy8aocFEPkLdPeILM373dQj5CZVweph8wmvojXZCoWD0ghpGl5u2qhCiEIT5Ak04lSCJ3SWTfIPyiDzKazzJKE+kghZPZG90Y3J+9AVUHoJDNkOc6mCiET+iptSdBUFWSCLGnFAt4UQnxkyIM1REM0RENuhXjShvn4ICCbEHM0QgjCksD4IDSRNeTjgzzNYNcQDflHEJNzgDRIwYwD3h+EcAAeugBOyPuBLAMHyNYMiBuznQnAUrMHSBiEwHaRaFlGZYtr3grJv4CA64AbgpOGHLYMII7cVD2kqOCixQldCCIAJ0rTGyHobHezOHQgTkggEJDIiVk/ELeGkKLFYTdCQJ5rSMxDt9wjwGLH7W+PxBUEmHh2G4Tu7W5O/gXEXDkrRt7jFUC5151eIOYqfWdkm+5AJCVuqGKMHBsIY+KLmNW3MsRUCmlaXAIQ1cOcMz2PaIiGaMgzQ6gta7DGBwFEu0Ej/J0drHk3Gzo+SCaf2ccHeZrBriEaoiGfQuizQCZY2jBHU4WQA32VQ6gyCOCZJ/1vhSlVCEFT6YlQktOJMgiaTii6Dp1gtVc+THw0k2SCVV5UQyeSzEApBJA/lYTqC88uoyEaoiHtlu/Qc34IxyNaXn6Yveb7i3A8okVHR+fT6DsM/GX0HQZuylPN7BqiIWU05P+CmBGDCJowAvK4d0DUtsghP93A5GAWq8QXA+KUz0hafC/Xln9CCMw7IEpb5JClG5kBC+LQWa4ix4mLimWcxgHZ8jhO3SAgcfwe7cjWCW6E9N9yoRbvVcBTFjrEIakTFBVxGBGxaO6cpSsSQP24BdJfixyyIksSkNAtK1Z8VVYUa7gDK0JckSV5hy27A6K4RQ5hnAABnsYRi5hYMKPq6jvOouInEyHuFqKIALkR0n+L2VlVXwsnDQ9dFYffr1v0PNJEQzREQzREQzSkjI9F6PghuVEGK4Egf51v5kU21hqjh0JeK4h1N8TPj7ZxmWRv4ZFB8CYx5EmO2WggyGoUUsucjgFC58bX2eOhQySMxLbtpEuhg4ZYSVuw2KwP9fYi7FmLC8+cDhaC7ZZig5HsINB6iZ0NFJK3LprD8FEO56TZKUgtBPlY5E4I2v/ZGRaCz0Iz+89OoUohdjX87oJQW/o2y4OsP2isEIKNKndBascJX5+jFNfLW5aH0YX7XEvQsCC+UWVzMbL3RjunuQdNst9DJRsWBFUfefzFSUoyx9cfRjwsCFgXQxfvO4buQRcVH6/z0MYIeFaO6ve6YUgptPk7GAYHaZInl9ttn2z7krJBUGe4kPbuOG3WtF6NrUV7pwwe4tvNvEg/ngmTTBUEW0WoaghNPj+Raii5Ggj9ve9VQ46SsxQ5xVYBaTa+H8iJwsehxzFAMkPk3NodyMsty8q9Ns0qG4YNgWxxyqEOtezmQDXH7RdZMHBIK/6+cy9FaDIaCNoY3czp6CDUlp+e+CODtB2JnbQW/HFBvKt/g8B5LdvcD0G5JZI9BLKu58XOTGjdD5kbZbxHQFCx1QvaHf42vRfSbONjBvvmnHcnmfOGwsggH2a8ELS2MvoEkPIXFNsbPaSeUPyxQ7J6+hg75GxUWYwdkhtVjmOHHIwq2dghYFVDZPRHLYDDcXH0nmAe6WFmt1qfVvTabC9Kyuf2xYuGDAFvLYKhE7ougtovGv9FNc9zmZOGaIiG3Bl9h4EB3mHgF3NeY+W3xB1xAAAAAElFTkSuQmCC",
 | 
			
		||||
    "image": "tb-image:c3RhdHVzX2luZGljYXRvcnNfc3lzdGVtX2J1bmRsZV9pbWFnZS5wbmc=:IlN0YXR1cyBpbmRpY2F0b3JzIiBzeXN0ZW0gYnVuZGxlIGltYWdl;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAjVBMVEUAAADu7u7u7u7g4OD///9c35Dw8PCt78fz/ffW9+PHx8dw457r+/Hj4+Pl+u6QkJDZ+OWsrKzM9tzf+er4/vuE56xYWFi6urp0dHTV1dXS9uCZ67qCgoKF56tm4Zeenp7C89VmZmZKSkq48c7A89Wj7cFLS0s9PT08PDyF56zi+euP6bN65aWZ67lZWVkXV4nvAAAABHRSTlMA799f7FlksgAABdJJREFUeNrt3I12mjAYBuBt/YAkJCJgCVQEFLX+7v4vbwHGQEnbeQwOXF7XVjjuvDxiCN04fHv5bvaaHy/fRB7Q8gN6znfheESLCT3HFI4HtGiIhlTREA0ZJSQi5ZJ6iKSlV8i7u4qAAYTiUfwEUzVE3qIeQkKWOku+4zvixEuydZcsVA2Rt6iGxCR1VhCIx9YJiANhylVD5C2qIUu+5StYsZ9sSVxRQXagGiJvUQvhbgSMc+CMRSYnplgKVUPkLb0ffmOmGiJv0fOIhmjIVTREQ54FMquiGiJv6REy8aocFEPkLdPeILM373dQj5CZVweph8wmvojXZCoWD0ghpGl5u2qhCiEIT5Ak04lSCJ3SWTfIPyiDzKazzJKE+kghZPZG90Y3J+9AVUHoJDNkOc6mCiET+iptSdBUFWSCLGnFAt4UQnxkyIM1REM0RENuhXjShvn4ICCbEHM0QgjCksD4IDSRNeTjgzzNYNcQDflHEJNzgDRIwYwD3h+EcAAeugBOyPuBLAMHyNYMiBuznQnAUrMHSBiEwHaRaFlGZYtr3grJv4CA64AbgpOGHLYMII7cVD2kqOCixQldCCIAJ0rTGyHobHezOHQgTkggEJDIiVk/ELeGkKLFYTdCQJ5rSMxDt9wjwGLH7W+PxBUEmHh2G4Tu7W5O/gXEXDkrRt7jFUC5151eIOYqfWdkm+5AJCVuqGKMHBsIY+KLmNW3MsRUCmlaXAIQ1cOcMz2PaIiGaMgzQ6gta7DGBwFEu0Ej/J0drHk3Gzo+SCaf2ccHeZrBriEaoiGfQuizQCZY2jBHU4WQA32VQ6gyCOCZJ/1vhSlVCEFT6YlQktOJMgiaTii6Dp1gtVc+THw0k2SCVV5UQyeSzEApBJA/lYTqC88uoyEaoiHtlu/Qc34IxyNaXn6Yveb7i3A8okVHR+fT6DsM/GX0HQZuylPN7BqiIWU05P+CmBGDCJowAvK4d0DUtsghP93A5GAWq8QXA+KUz0hafC/Xln9CCMw7IEpb5JClG5kBC+LQWa4ix4mLimWcxgHZ8jhO3SAgcfwe7cjWCW6E9N9yoRbvVcBTFjrEIakTFBVxGBGxaO6cpSsSQP24BdJfixyyIksSkNAtK1Z8VVYUa7gDK0JckSV5hy27A6K4RQ5hnAABnsYRi5hYMKPq6jvOouInEyHuFqKIALkR0n+L2VlVXwsnDQ9dFYffr1v0PNJEQzREQzREQzSkjI9F6PghuVEGK4Egf51v5kU21hqjh0JeK4h1N8TPj7ZxmWRv4ZFB8CYx5EmO2WggyGoUUsucjgFC58bX2eOhQySMxLbtpEuhg4ZYSVuw2KwP9fYi7FmLC8+cDhaC7ZZig5HsINB6iZ0NFJK3LprD8FEO56TZKUgtBPlY5E4I2v/ZGRaCz0Iz+89OoUohdjX87oJQW/o2y4OsP2isEIKNKndBascJX5+jFNfLW5aH0YX7XEvQsCC+UWVzMbL3RjunuQdNst9DJRsWBFUfefzFSUoyx9cfRjwsCFgXQxfvO4buQRcVH6/z0MYIeFaO6ve6YUgptPk7GAYHaZInl9ttn2z7krJBUGe4kPbuOG3WtF6NrUV7pwwe4tvNvEg/ngmTTBUEW0WoaghNPj+Raii5Ggj9ve9VQ46SsxQ5xVYBaTa+H8iJwsehxzFAMkPk3NodyMsty8q9Ns0qG4YNgWxxyqEOtezmQDXH7RdZMHBIK/6+cy9FaDIaCNoY3czp6CDUlp+e+CODtB2JnbQW/HFBvKt/g8B5LdvcD0G5JZI9BLKu58XOTGjdD5kbZbxHQFCx1QvaHf42vRfSbONjBvvmnHcnmfOGwsggH2a8ELS2MvoEkPIXFNsbPaSeUPyxQ7J6+hg75GxUWYwdkhtVjmOHHIwq2dghYFVDZPRHLYDDcXH0nmAe6WFmt1qfVvTabC9Kyuf2xYuGDAFvLYKhE7ougtovGv9FNc9zmZOGaIiG3Bl9h4EB3mHgF3NeY+W3xB1xAAAAAElFTkSuQmCC",
 | 
			
		||||
    "description": "Contains widgets displaying battery level and signal strength.",
 | 
			
		||||
    "order": 9000,
 | 
			
		||||
    "externalId": null,
 | 
			
		||||
    "name": "Status indicators"
 | 
			
		||||
  },
 | 
			
		||||
  "widgetTypeFqns": [
 | 
			
		||||
    "battery_level",
 | 
			
		||||
    "signal_strength",
 | 
			
		||||
    "progress_bar"
 | 
			
		||||
    "progress_bar",
 | 
			
		||||
    "status_widget"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -647,7 +647,7 @@ public class TelemetryController extends BaseController {
 | 
			
		||||
        Map<Long, List<KvEntry>> telemetryRequest;
 | 
			
		||||
        JsonElement telemetryJson;
 | 
			
		||||
        try {
 | 
			
		||||
            telemetryJson = new JsonParser().parse(requestBody);
 | 
			
		||||
            telemetryJson = JsonParser.parseString(requestBody);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            return getImmediateDeferredResult("Unable to parse timeseries payload: Invalid JSON body!", HttpStatus.BAD_REQUEST);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -147,7 +147,7 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest
 | 
			
		||||
    void checkDiffBetweenLatestTsForDevicesAndAsset(List<Device> devices, Asset asset) throws ExecutionException, InterruptedException, TimeoutException {
 | 
			
		||||
        TsKvEntry assetTsKvEntry = getTsKvLatest(asset.getId(), GENERIC_CUMULATIVE_OBJ);
 | 
			
		||||
        Assert.assertTrue(assetTsKvEntry.getJsonValue().isPresent());
 | 
			
		||||
        JsonObject assetJsonObject = new JsonParser().parse(assetTsKvEntry.getJsonValue().get()).getAsJsonObject();
 | 
			
		||||
        JsonObject assetJsonObject = JsonParser.parseString(assetTsKvEntry.getJsonValue().get()).getAsJsonObject();
 | 
			
		||||
        for (Device device : devices) {
 | 
			
		||||
            Long assetValue = assetJsonObject.get(device.getName()).getAsLong();
 | 
			
		||||
            TsKvEntry deviceLatest = getTsKvLatest(device.getId(), TOTALIZER);
 | 
			
		||||
@ -182,7 +182,7 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest
 | 
			
		||||
    JsonObject getJsonObject(String key, long value, Optional<String> tsKvEntryOpt) {
 | 
			
		||||
        JsonObject jsonObject = new JsonObject();
 | 
			
		||||
        if (tsKvEntryOpt.isPresent()) {
 | 
			
		||||
            jsonObject = new JsonParser().parse(tsKvEntryOpt.get()).getAsJsonObject();
 | 
			
		||||
            jsonObject = JsonParser.parseString(tsKvEntryOpt.get()).getAsJsonObject();
 | 
			
		||||
        }
 | 
			
		||||
        jsonObject.addProperty(key, value);
 | 
			
		||||
        return jsonObject;
 | 
			
		||||
 | 
			
		||||
@ -66,7 +66,6 @@ import java.util.stream.Collectors;
 | 
			
		||||
public class JsonConverter {
 | 
			
		||||
 | 
			
		||||
    private static final Gson GSON = new Gson();
 | 
			
		||||
    private static final JsonParser JSON_PARSER = new JsonParser();
 | 
			
		||||
    private static final String CAN_T_PARSE_VALUE = "Can't parse value: ";
 | 
			
		||||
    private static final String DEVICE_PROPERTY = "device";
 | 
			
		||||
 | 
			
		||||
@ -111,7 +110,7 @@ public class JsonConverter {
 | 
			
		||||
    public static ClaimDeviceMsg convertToClaimDeviceProto(DeviceId deviceId, String json) {
 | 
			
		||||
        long durationMs = 0L;
 | 
			
		||||
        if (json != null && !json.isEmpty()) {
 | 
			
		||||
            return convertToClaimDeviceProto(deviceId, JSON_PARSER.parse(json));
 | 
			
		||||
            return convertToClaimDeviceProto(deviceId, JsonParser.parseString(json));
 | 
			
		||||
        }
 | 
			
		||||
        return buildClaimDeviceMsg(deviceId, DataConstants.DEFAULT_SECRET_KEY, durationMs);
 | 
			
		||||
    }
 | 
			
		||||
@ -160,7 +159,7 @@ public class JsonConverter {
 | 
			
		||||
            result.addProperty("id", msg.getRequestId());
 | 
			
		||||
        }
 | 
			
		||||
        result.addProperty("method", msg.getMethodName());
 | 
			
		||||
        result.add("params", JSON_PARSER.parse(msg.getParams()));
 | 
			
		||||
        result.add("params", JsonParser.parseString(msg.getParams()));
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -367,7 +366,7 @@ public class JsonConverter {
 | 
			
		||||
                json.addProperty(name, entry.getLongV());
 | 
			
		||||
                break;
 | 
			
		||||
            case JSON_V:
 | 
			
		||||
                json.add(name, JSON_PARSER.parse(entry.getJsonV()));
 | 
			
		||||
                json.add(name, JsonParser.parseString(entry.getJsonV()));
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -388,7 +387,7 @@ public class JsonConverter {
 | 
			
		||||
                    result.add(de.getKv().getKey(), new JsonPrimitive(de.getKv().getStringV()));
 | 
			
		||||
                    break;
 | 
			
		||||
                case JSON_V:
 | 
			
		||||
                    result.add(de.getKv().getKey(), JSON_PARSER.parse(de.getKv().getJsonV()));
 | 
			
		||||
                    result.add(de.getKv().getKey(), JsonParser.parseString(de.getKv().getJsonV()));
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    throw new IllegalArgumentException("Unsupported data type: " + de.getKv().getType());
 | 
			
		||||
@ -412,7 +411,7 @@ public class JsonConverter {
 | 
			
		||||
                    result.add(de.getKey(), new JsonPrimitive(de.getStrValue().get()));
 | 
			
		||||
                    break;
 | 
			
		||||
                case JSON:
 | 
			
		||||
                    result.add(de.getKey(), JSON_PARSER.parse(de.getJsonValue().get()));
 | 
			
		||||
                    result.add(de.getKey(), JsonParser.parseString(de.getJsonValue().get()));
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    throw new IllegalArgumentException("Unsupported data type: " + de.getDataType());
 | 
			
		||||
@ -422,7 +421,7 @@ public class JsonConverter {
 | 
			
		||||
 | 
			
		||||
    public static JsonElement toJson(TransportProtos.ToServerRpcResponseMsg msg) {
 | 
			
		||||
        if (StringUtils.isEmpty(msg.getError())) {
 | 
			
		||||
            return JSON_PARSER.parse(msg.getPayload());
 | 
			
		||||
            return JsonParser.parseString(msg.getPayload());
 | 
			
		||||
        } else {
 | 
			
		||||
            JsonObject errorMsg = new JsonObject();
 | 
			
		||||
            errorMsg.addProperty("error", msg.getError());
 | 
			
		||||
@ -456,7 +455,7 @@ public class JsonConverter {
 | 
			
		||||
                    result.addProperty("credentialsValue", payload.getCredentialsValue());
 | 
			
		||||
                    break;
 | 
			
		||||
                case MQTT_BASIC:
 | 
			
		||||
                    result.add("credentialsValue", JSON_PARSER.parse(payload.getCredentialsValue()).getAsJsonObject());
 | 
			
		||||
                    result.add("credentialsValue", JsonParser.parseString(payload.getCredentialsValue()).getAsJsonObject());
 | 
			
		||||
                    break;
 | 
			
		||||
                case LWM2M_CREDENTIALS:
 | 
			
		||||
                    break;
 | 
			
		||||
@ -572,7 +571,7 @@ public class JsonConverter {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static JsonElement parse(String json) {
 | 
			
		||||
        return JSON_PARSER.parse(json);
 | 
			
		||||
        return JsonParser.parseString(json);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static <T> T parse(String json, Class<T> clazz) {
 | 
			
		||||
@ -600,7 +599,7 @@ public class JsonConverter {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) {
 | 
			
		||||
        JsonElement jsonElement = JSON_PARSER.parse(json);
 | 
			
		||||
        JsonElement jsonElement = JsonParser.parseString(json);
 | 
			
		||||
        if (jsonElement.isJsonObject()) {
 | 
			
		||||
            return buildProvisionRequestMsg(jsonElement.getAsJsonObject());
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
@ -39,7 +39,6 @@ import java.util.List;
 | 
			
		||||
public class ProtoConverter {
 | 
			
		||||
 | 
			
		||||
    public static final Gson GSON = new Gson();
 | 
			
		||||
    public static final JsonParser JSON_PARSER = new JsonParser();
 | 
			
		||||
 | 
			
		||||
    public static TransportProtos.PostTelemetryMsg convertToTelemetryProto(byte[] payload) throws InvalidProtocolBufferException, IllegalArgumentException {
 | 
			
		||||
        TransportProtos.TsKvListProto protoPayload = TransportProtos.TsKvListProto.parseFrom(payload);
 | 
			
		||||
@ -165,7 +164,7 @@ public class ProtoConverter {
 | 
			
		||||
                    break;
 | 
			
		||||
                case JSON_V:
 | 
			
		||||
                    try {
 | 
			
		||||
                        JSON_PARSER.parse(keyValueProto.getJsonV());
 | 
			
		||||
                        JsonParser.parseString(keyValueProto.getJsonV());
 | 
			
		||||
                    } catch (Exception e) {
 | 
			
		||||
                        throw new IllegalArgumentException("Can't parse value: " + keyValueProto.getJsonV() + " for key: " + key + "!");
 | 
			
		||||
                    }
 | 
			
		||||
@ -184,7 +183,7 @@ public class ProtoConverter {
 | 
			
		||||
        rpcRequestJson.addProperty("requestId", toDeviceRpcRequestMsg.getRequestId());
 | 
			
		||||
        String params = toDeviceRpcRequestMsg.getParams();
 | 
			
		||||
        try {
 | 
			
		||||
            JsonElement paramsElement = JSON_PARSER.parse(params);
 | 
			
		||||
            JsonElement paramsElement = JsonParser.parseString(params);
 | 
			
		||||
            rpcRequestJson.add("params", paramsElement);
 | 
			
		||||
            DynamicMessage dynamicRpcRequest = DynamicProtoUtils.jsonToDynamicMessage(rpcRequestDynamicMessageBuilder, GSON.toJson(rpcRequestJson));
 | 
			
		||||
            return dynamicRpcRequest.toByteArray();
 | 
			
		||||
 | 
			
		||||
@ -26,8 +26,6 @@ import java.util.ArrayList;
 | 
			
		||||
 | 
			
		||||
public class JsonConverterTest {
 | 
			
		||||
 | 
			
		||||
    private final JsonParser JSON_PARSER = new JsonParser();
 | 
			
		||||
 | 
			
		||||
    @BeforeEach
 | 
			
		||||
    public void before() {
 | 
			
		||||
        JsonConverter.setTypeCastEnabled(true);
 | 
			
		||||
@ -35,55 +33,55 @@ public class JsonConverterTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testParseBigDecimalAsLong() {
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1E+1}"), 0L);
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JsonParser.parseString("{\"meterReadingDelta\": 1E+1}"), 0L);
 | 
			
		||||
        Assert.assertEquals(10L, result.get(0L).get(0).getLongValue().get().longValue());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testParseBigDecimalAsDouble() {
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 101E-1}"), 0L);
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JsonParser.parseString("{\"meterReadingDelta\": 101E-1}"), 0L);
 | 
			
		||||
        Assert.assertEquals(10.1, result.get(0L).get(0).getDoubleValue().get(), 0.0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testParseAttributesBigDecimalAsLong() {
 | 
			
		||||
        var result = new ArrayList<>(JsonConverter.convertToAttributes(JSON_PARSER.parse("{\"meterReadingDelta\": 1E1}")));
 | 
			
		||||
        var result = new ArrayList<>(JsonConverter.convertToAttributes(JsonParser.parseString("{\"meterReadingDelta\": 1E1}")));
 | 
			
		||||
        Assert.assertEquals(10L, result.get(0).getLongValue().get().longValue());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testParseAsDoubleWithZero() {
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 42.0}"), 0L);
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JsonParser.parseString("{\"meterReadingDelta\": 42.0}"), 0L);
 | 
			
		||||
        Assert.assertEquals(42.0, result.get(0L).get(0).getDoubleValue().get(), 0.0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testParseAsDouble() {
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1.1}"), 0L);
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JsonParser.parseString("{\"meterReadingDelta\": 1.1}"), 0L);
 | 
			
		||||
        Assert.assertEquals(1.1, result.get(0L).get(0).getDoubleValue().get(), 0.0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testParseAsLong() {
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 11}"), 0L);
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JsonParser.parseString("{\"meterReadingDelta\": 11}"), 0L);
 | 
			
		||||
        Assert.assertEquals(11L, result.get(0L).get(0).getLongValue().get().longValue());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testParseBigDecimalAsStringOutOfLongRange() {
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 9.9701010061400066E19}"), 0L);
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JsonParser.parseString("{\"meterReadingDelta\": 9.9701010061400066E19}"), 0L);
 | 
			
		||||
        Assert.assertEquals("99701010061400066000", result.get(0L).get(0).getStrValue().get());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testParseBigDecimalAsStringOutOfLongRange2() {
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 99701010061400066001}"), 0L);
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JsonParser.parseString("{\"meterReadingDelta\": 99701010061400066001}"), 0L);
 | 
			
		||||
        Assert.assertEquals("99701010061400066001", result.get(0L).get(0).getStrValue().get());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testParseBigDecimalAsStringOutOfLongRange3() {
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1E19}"), 0L);
 | 
			
		||||
        var result = JsonConverter.convertToTelemetry(JsonParser.parseString("{\"meterReadingDelta\": 1E19}"), 0L);
 | 
			
		||||
        Assert.assertEquals("10000000000000000000", result.get(0L).get(0).getStrValue().get());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -91,7 +89,7 @@ public class JsonConverterTest {
 | 
			
		||||
    public void testParseBigDecimalOutOfLongRangeWithoutParsing() {
 | 
			
		||||
        JsonConverter.setTypeCastEnabled(false);
 | 
			
		||||
        Assertions.assertThrows(JsonSyntaxException.class, () -> {
 | 
			
		||||
            JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 89701010051400054084}"), 0L);
 | 
			
		||||
            JsonConverter.convertToTelemetry(JsonParser.parseString("{\"meterReadingDelta\": 89701010051400054084}"), 0L);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -99,7 +97,7 @@ public class JsonConverterTest {
 | 
			
		||||
    public void testParseBigDecimalOutOfLongRangeWithoutParsing2() {
 | 
			
		||||
        JsonConverter.setTypeCastEnabled(false);
 | 
			
		||||
        Assertions.assertThrows(JsonSyntaxException.class, () -> {
 | 
			
		||||
            JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 9.9701010061400066E19}"), 0L);
 | 
			
		||||
            JsonConverter.convertToTelemetry(JsonParser.parseString("{\"meterReadingDelta\": 9.9701010061400066E19}"), 0L);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -45,7 +45,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
 | 
			
		||||
    public TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound, Descriptors.Descriptor telemetryMsgDescriptor) throws AdaptorException {
 | 
			
		||||
        String payload = validatePayload(sessionId, inbound, false);
 | 
			
		||||
        try {
 | 
			
		||||
            return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload));
 | 
			
		||||
            return JsonConverter.convertToTelemetryProto(JsonParser.parseString(payload));
 | 
			
		||||
        } catch (IllegalStateException | JsonSyntaxException ex) {
 | 
			
		||||
            throw new AdaptorException(ex);
 | 
			
		||||
        }
 | 
			
		||||
@ -55,7 +55,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
 | 
			
		||||
    public TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound, Descriptors.Descriptor attributesMsgDescriptor) throws AdaptorException {
 | 
			
		||||
        String payload = validatePayload(sessionId, inbound, false);
 | 
			
		||||
        try {
 | 
			
		||||
            return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload));
 | 
			
		||||
            return JsonConverter.convertToAttributesProto(JsonParser.parseString(payload));
 | 
			
		||||
        } catch (IllegalStateException | JsonSyntaxException ex) {
 | 
			
		||||
            throw new AdaptorException(ex);
 | 
			
		||||
        }
 | 
			
		||||
@ -70,7 +70,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
 | 
			
		||||
    public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound, Descriptors.Descriptor rpcResponseMsgDescriptor) throws AdaptorException {
 | 
			
		||||
        Optional<Integer> requestId = CoapTransportResource.getRequestId(inbound);
 | 
			
		||||
        String payload = validatePayload(sessionId, inbound, false);
 | 
			
		||||
        JsonObject response = new JsonParser().parse(payload).getAsJsonObject();
 | 
			
		||||
        JsonObject response = JsonParser.parseString(payload).getAsJsonObject();
 | 
			
		||||
        return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId.orElseThrow(() -> new AdaptorException("Request id is missing!")))
 | 
			
		||||
                .setPayload(response.toString()).build();
 | 
			
		||||
    }
 | 
			
		||||
@ -78,7 +78,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
 | 
			
		||||
    @Override
 | 
			
		||||
    public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(UUID sessionId, Request inbound) throws AdaptorException {
 | 
			
		||||
        String payload = validatePayload(sessionId, inbound, false);
 | 
			
		||||
        return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), 0);
 | 
			
		||||
        return JsonConverter.convertToServerRpcRequest(JsonParser.parseString(payload), 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 | 
			
		||||
@ -45,7 +45,7 @@ public class ProtoCoapAdaptor implements CoapTransportAdaptor {
 | 
			
		||||
    public TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound, Descriptors.Descriptor telemetryMsgDescriptor) throws AdaptorException {
 | 
			
		||||
        ProtoConverter.validateDescriptor(telemetryMsgDescriptor);
 | 
			
		||||
        try {
 | 
			
		||||
            return JsonConverter.convertToTelemetryProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(inbound.getPayload(), telemetryMsgDescriptor)));
 | 
			
		||||
            return JsonConverter.convertToTelemetryProto(JsonParser.parseString(ProtoConverter.dynamicMsgToJson(inbound.getPayload(), telemetryMsgDescriptor)));
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw new AdaptorException(e);
 | 
			
		||||
        }
 | 
			
		||||
@ -55,7 +55,7 @@ public class ProtoCoapAdaptor implements CoapTransportAdaptor {
 | 
			
		||||
    public TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound, Descriptors.Descriptor attributesMsgDescriptor) throws AdaptorException {
 | 
			
		||||
        ProtoConverter.validateDescriptor(attributesMsgDescriptor);
 | 
			
		||||
        try {
 | 
			
		||||
            return JsonConverter.convertToAttributesProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(inbound.getPayload(), attributesMsgDescriptor)));
 | 
			
		||||
            return JsonConverter.convertToAttributesProto(JsonParser.parseString(ProtoConverter.dynamicMsgToJson(inbound.getPayload(), attributesMsgDescriptor)));
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw new AdaptorException(e);
 | 
			
		||||
        }
 | 
			
		||||
@ -74,7 +74,7 @@ public class ProtoCoapAdaptor implements CoapTransportAdaptor {
 | 
			
		||||
        } else {
 | 
			
		||||
            ProtoConverter.validateDescriptor(rpcResponseMsgDescriptor);
 | 
			
		||||
            try {
 | 
			
		||||
                JsonElement response = new JsonParser().parse(ProtoConverter.dynamicMsgToJson(inbound.getPayload(), rpcResponseMsgDescriptor));
 | 
			
		||||
                JsonElement response = JsonParser.parseString(ProtoConverter.dynamicMsgToJson(inbound.getPayload(), rpcResponseMsgDescriptor));
 | 
			
		||||
                return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId.orElseThrow(() -> new AdaptorException("Request id is missing!")))
 | 
			
		||||
                        .setPayload(response.toString()).build();
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
 | 
			
		||||
@ -185,7 +185,7 @@ public class DeviceApiController implements TbTransportService {
 | 
			
		||||
        transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
 | 
			
		||||
                new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
 | 
			
		||||
                    TransportService transportService = transportContext.getTransportService();
 | 
			
		||||
                    transportService.process(sessionInfo, JsonConverter.convertToAttributesProto(new JsonParser().parse(json)),
 | 
			
		||||
                    transportService.process(sessionInfo, JsonConverter.convertToAttributesProto(JsonParser.parseString(json)),
 | 
			
		||||
                            new HttpOkCallback(responseWriter));
 | 
			
		||||
                }));
 | 
			
		||||
        return responseWriter;
 | 
			
		||||
@ -205,7 +205,7 @@ public class DeviceApiController implements TbTransportService {
 | 
			
		||||
        transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
 | 
			
		||||
                new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
 | 
			
		||||
                    TransportService transportService = transportContext.getTransportService();
 | 
			
		||||
                    transportService.process(sessionInfo, JsonConverter.convertToTelemetryProto(new JsonParser().parse(json)),
 | 
			
		||||
                    transportService.process(sessionInfo, JsonConverter.convertToTelemetryProto(JsonParser.parseString(json)),
 | 
			
		||||
                            new HttpOkCallback(responseWriter));
 | 
			
		||||
                }));
 | 
			
		||||
        return responseWriter;
 | 
			
		||||
@ -301,7 +301,7 @@ public class DeviceApiController implements TbTransportService {
 | 
			
		||||
        DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
 | 
			
		||||
        transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
 | 
			
		||||
                new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
 | 
			
		||||
                    JsonObject request = new JsonParser().parse(json).getAsJsonObject();
 | 
			
		||||
                    JsonObject request = JsonParser.parseString(json).getAsJsonObject();
 | 
			
		||||
                    TransportService transportService = transportContext.getTransportService();
 | 
			
		||||
                    transportService.registerSyncSession(sessionInfo,
 | 
			
		||||
                            new HttpSessionListener(responseWriter, transportContext.getTransportService(), sessionInfo),
 | 
			
		||||
 | 
			
		||||
@ -50,7 +50,6 @@ import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType.
 | 
			
		||||
public class LwM2mTransportServerHelper {
 | 
			
		||||
 | 
			
		||||
    private final LwM2mTransportContext context;
 | 
			
		||||
    private final static JsonParser JSON_PARSER = new JsonParser();
 | 
			
		||||
 | 
			
		||||
    public void sendParametersOnThingsboardAttribute(List<TransportProtos.KeyValueProto> result, SessionInfoProto sessionInfo) {
 | 
			
		||||
        PostAttributeMsg.Builder request = PostAttributeMsg.newBuilder();
 | 
			
		||||
@ -231,7 +230,7 @@ public class LwM2mTransportServerHelper {
 | 
			
		||||
                return kv.getStringV();
 | 
			
		||||
            case JSON_V:
 | 
			
		||||
                try {
 | 
			
		||||
                    return JSON_PARSER.parse(kv.getJsonV());
 | 
			
		||||
                    return JsonParser.parseString(kv.getJsonV());
 | 
			
		||||
                } catch (Exception e) {
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -59,7 +59,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
 | 
			
		||||
    public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
 | 
			
		||||
        String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
 | 
			
		||||
        try {
 | 
			
		||||
            return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload));
 | 
			
		||||
            return JsonConverter.convertToTelemetryProto(JsonParser.parseString(payload));
 | 
			
		||||
        } catch (IllegalStateException | JsonSyntaxException ex) {
 | 
			
		||||
            log.debug("Failed to decode post telemetry request", ex);
 | 
			
		||||
            throw new AdaptorException(ex);
 | 
			
		||||
@ -70,7 +70,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
 | 
			
		||||
    public TransportProtos.PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
 | 
			
		||||
        String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
 | 
			
		||||
        try {
 | 
			
		||||
            return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload));
 | 
			
		||||
            return JsonConverter.convertToAttributesProto(JsonParser.parseString(payload));
 | 
			
		||||
        } catch (IllegalStateException | JsonSyntaxException ex) {
 | 
			
		||||
            log.debug("Failed to decode post attributes request", ex);
 | 
			
		||||
            throw new AdaptorException(ex);
 | 
			
		||||
@ -162,7 +162,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
 | 
			
		||||
    public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
 | 
			
		||||
        String payload = validatePayload(sessionId, payloadData, false);
 | 
			
		||||
        try {
 | 
			
		||||
            return new JsonParser().parse(payload);
 | 
			
		||||
            return JsonParser.parseString(payload);
 | 
			
		||||
        } catch (JsonSyntaxException ex) {
 | 
			
		||||
            log.debug("Payload is in incorrect format: {}", payload);
 | 
			
		||||
            throw new AdaptorException(ex);
 | 
			
		||||
@ -175,7 +175,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
 | 
			
		||||
            TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder();
 | 
			
		||||
            result.setRequestId(getRequestId(topicName, topicBase));
 | 
			
		||||
            String payload = inbound.payload().toString(UTF8);
 | 
			
		||||
            JsonElement requestBody = new JsonParser().parse(payload);
 | 
			
		||||
            JsonElement requestBody = JsonParser.parseString(payload);
 | 
			
		||||
            Set<String> clientKeys = toStringSet(requestBody, "clientKeys");
 | 
			
		||||
            Set<String> sharedKeys = toStringSet(requestBody, "sharedKeys");
 | 
			
		||||
            if (clientKeys != null) {
 | 
			
		||||
@ -208,7 +208,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
 | 
			
		||||
        String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
 | 
			
		||||
        try {
 | 
			
		||||
            int requestId = getRequestId(topicName, topicBase);
 | 
			
		||||
            return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId);
 | 
			
		||||
            return JsonConverter.convertToServerRpcRequest(JsonParser.parseString(payload), requestId);
 | 
			
		||||
        } catch (IllegalStateException | JsonSyntaxException ex) {
 | 
			
		||||
            log.debug("Failed to decode to server rpc request", ex);
 | 
			
		||||
            throw new AdaptorException(ex);
 | 
			
		||||
 | 
			
		||||
@ -50,7 +50,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
 | 
			
		||||
        byte[] bytes = toBytes(inbound.payload());
 | 
			
		||||
        Descriptors.Descriptor telemetryDynamicMsgDescriptor = ProtoConverter.validateDescriptor(deviceSessionCtx.getTelemetryDynamicMsgDescriptor());
 | 
			
		||||
        try {
 | 
			
		||||
            return JsonConverter.convertToTelemetryProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, telemetryDynamicMsgDescriptor)));
 | 
			
		||||
            return JsonConverter.convertToTelemetryProto(JsonParser.parseString(ProtoConverter.dynamicMsgToJson(bytes, telemetryDynamicMsgDescriptor)));
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.debug("Failed to decode post telemetry request", e);
 | 
			
		||||
            throw new AdaptorException(e);
 | 
			
		||||
@ -63,7 +63,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
 | 
			
		||||
        byte[] bytes = toBytes(inbound.payload());
 | 
			
		||||
        Descriptors.Descriptor attributesDynamicMessageDescriptor = ProtoConverter.validateDescriptor(deviceSessionCtx.getAttributesDynamicMessageDescriptor());
 | 
			
		||||
        try {
 | 
			
		||||
            return JsonConverter.convertToAttributesProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, attributesDynamicMessageDescriptor)));
 | 
			
		||||
            return JsonConverter.convertToAttributesProto(JsonParser.parseString(ProtoConverter.dynamicMsgToJson(bytes, attributesDynamicMessageDescriptor)));
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.debug("Failed to decode post attributes request", e);
 | 
			
		||||
            throw new AdaptorException(e);
 | 
			
		||||
@ -102,7 +102,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
 | 
			
		||||
        Descriptors.Descriptor rpcResponseDynamicMessageDescriptor = ProtoConverter.validateDescriptor(deviceSessionCtx.getRpcResponseDynamicMessageDescriptor());
 | 
			
		||||
        try {
 | 
			
		||||
            int requestId = getRequestId(topicName, topicBase);
 | 
			
		||||
            JsonElement response = new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, rpcResponseDynamicMessageDescriptor));
 | 
			
		||||
            JsonElement response = JsonParser.parseString(ProtoConverter.dynamicMsgToJson(bytes, rpcResponseDynamicMessageDescriptor));
 | 
			
		||||
            return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(response.toString()).build();
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.debug("Failed to decode rpc response", e);
 | 
			
		||||
 | 
			
		||||
@ -94,7 +94,7 @@ public class SparkplugNodeSessionHandler extends AbstractGatewaySessionHandler<S
 | 
			
		||||
        byte[] bytes = getBytes(inbound.payload());
 | 
			
		||||
        Descriptors.Descriptor telemetryDynamicMsgDescriptor = ProtoConverter.validateDescriptor(deviceSessionCtx.getTelemetryDynamicMsgDescriptor());
 | 
			
		||||
        try {
 | 
			
		||||
            return JsonConverter.convertToTelemetryProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, telemetryDynamicMsgDescriptor)));
 | 
			
		||||
            return JsonConverter.convertToTelemetryProto(JsonParser.parseString(ProtoConverter.dynamicMsgToJson(bytes, telemetryDynamicMsgDescriptor)));
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.debug("Failed to decode post telemetry request", e);
 | 
			
		||||
            throw new AdaptorException(e);
 | 
			
		||||
 | 
			
		||||
@ -24,8 +24,6 @@ import java.util.List;
 | 
			
		||||
 | 
			
		||||
public class JsonUtils {
 | 
			
		||||
 | 
			
		||||
    private static final JsonParser jsonParser = new JsonParser();
 | 
			
		||||
 | 
			
		||||
    public static JsonObject getJsonObject(List<KeyValueProto> tsKv) {
 | 
			
		||||
        JsonObject json = new JsonObject();
 | 
			
		||||
        for (KeyValueProto kv : tsKv) {
 | 
			
		||||
@ -43,7 +41,7 @@ public class JsonUtils {
 | 
			
		||||
                    json.addProperty(kv.getKey(), kv.getStringV());
 | 
			
		||||
                    break;
 | 
			
		||||
                case JSON_V:
 | 
			
		||||
                    json.add(kv.getKey(), jsonParser.parse(kv.getJsonV()));
 | 
			
		||||
                    json.add(kv.getKey(), JsonParser.parseString(kv.getJsonV()));
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -51,7 +49,7 @@ public class JsonUtils {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static JsonElement parse(String params) {
 | 
			
		||||
        return jsonParser.parse(params);
 | 
			
		||||
        return JsonParser.parseString(params);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -51,6 +51,7 @@ import java.net.InetSocketAddress;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
import static org.eclipse.leshan.client.object.Security.noSec;
 | 
			
		||||
import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
 | 
			
		||||
@ -95,7 +96,7 @@ public class Lwm2mClient extends BaseInstanceEnabler implements Destroyable {
 | 
			
		||||
        LwM2mModel model = new StaticModel(models);
 | 
			
		||||
        ObjectsInitializer initializer = new ObjectsInitializer(model);
 | 
			
		||||
        initializer.setInstancesForObject(SECURITY, security);
 | 
			
		||||
        initializer.setInstancesForObject(SERVER, new Server(123, 300));
 | 
			
		||||
        initializer.setInstancesForObject(SERVER, new Server(123, TimeUnit.MINUTES.toSeconds(60)));
 | 
			
		||||
        initializer.setInstancesForObject(DEVICE, this);
 | 
			
		||||
        initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class);
 | 
			
		||||
        DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
 | 
			
		||||
 | 
			
		||||
@ -72,7 +72,6 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback;
 | 
			
		||||
public class TbSaveToCustomCassandraTableNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
    private static final String TABLE_PREFIX = "cs_tb_";
 | 
			
		||||
    private static final JsonParser parser = new JsonParser();
 | 
			
		||||
    private static final String ENTITY_ID = "$entityId";
 | 
			
		||||
 | 
			
		||||
    private TbSaveToCustomCassandraTableNodeConfiguration config;
 | 
			
		||||
@ -168,7 +167,7 @@ public class TbSaveToCustomCassandraTableNode implements TbNode {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ListenableFuture<Void> save(TbMsg msg, TbContext ctx) {
 | 
			
		||||
        JsonElement data = parser.parse(msg.getData());
 | 
			
		||||
        JsonElement data = JsonParser.parseString(msg.getData());
 | 
			
		||||
        if (!data.isJsonObject()) {
 | 
			
		||||
            throw new IllegalStateException("Invalid message structure, it is not a JSON Object:" + data);
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,6 @@ import org.thingsboard.rule.engine.api.RuleNode;
 | 
			
		||||
import org.thingsboard.rule.engine.api.TbContext;
 | 
			
		||||
import org.thingsboard.rule.engine.api.TbNodeException;
 | 
			
		||||
import org.thingsboard.server.common.data.AttributeScope;
 | 
			
		||||
import org.thingsboard.server.common.data.DataConstants;
 | 
			
		||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
			
		||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
 | 
			
		||||
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
 | 
			
		||||
@ -75,7 +74,6 @@ public class TbGpsGeofencingActionNode extends AbstractGeofencingNode<TbGpsGeofe
 | 
			
		||||
    private static final String REPORT_PRESENCE_STATUS_ON_EACH_MESSAGE = "reportPresenceStatusOnEachMessage";
 | 
			
		||||
    private final Map<EntityId, EntityGeofencingState> entityStates = new HashMap<>();
 | 
			
		||||
    private final Gson gson = new Gson();
 | 
			
		||||
    private final JsonParser parser = new JsonParser();
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
 | 
			
		||||
@ -88,7 +86,7 @@ public class TbGpsGeofencingActionNode extends AbstractGeofencingNode<TbGpsGeofe
 | 
			
		||||
                        .find(ctx.getTenantId(), msg.getOriginator(), AttributeScope.SERVER_SCOPE, ctx.getServiceId())
 | 
			
		||||
                        .get(1, TimeUnit.MINUTES);
 | 
			
		||||
                if (entry.isPresent()) {
 | 
			
		||||
                    JsonObject element = parser.parse(entry.get().getValueAsString()).getAsJsonObject();
 | 
			
		||||
                    JsonObject element = JsonParser.parseString(entry.get().getValueAsString()).getAsJsonObject();
 | 
			
		||||
                    return new EntityGeofencingState(element.get("inside").getAsBoolean(), element.get("stateSwitchTime").getAsLong(), element.get("stayed").getAsBoolean());
 | 
			
		||||
                } else {
 | 
			
		||||
                    return new EntityGeofencingState(false, 0L, false);
 | 
			
		||||
 | 
			
		||||
@ -114,6 +114,9 @@ import { ComparisonKeyRowComponent } from '@home/components/widget/config/basic/
 | 
			
		||||
import {
 | 
			
		||||
  ComparisonKeysTableComponent
 | 
			
		||||
} from '@home/components/widget/config/basic/chart/comparison-keys-table.component';
 | 
			
		||||
import {
 | 
			
		||||
  StatusWidgetBasicConfigComponent
 | 
			
		||||
} from '@home/components/widget/config/basic/indicator/status-widget-basic-config.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@ -151,7 +154,8 @@ import {
 | 
			
		||||
    ToggleButtonBasicConfigComponent,
 | 
			
		||||
    TimeSeriesChartBasicConfigComponent,
 | 
			
		||||
    ComparisonKeyRowComponent,
 | 
			
		||||
    ComparisonKeysTableComponent
 | 
			
		||||
    ComparisonKeysTableComponent,
 | 
			
		||||
    StatusWidgetBasicConfigComponent
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
@ -191,7 +195,8 @@ import {
 | 
			
		||||
    PowerButtonBasicConfigComponent,
 | 
			
		||||
    SliderBasicConfigComponent,
 | 
			
		||||
    ToggleButtonBasicConfigComponent,
 | 
			
		||||
    TimeSeriesChartBasicConfigComponent
 | 
			
		||||
    TimeSeriesChartBasicConfigComponent,
 | 
			
		||||
    StatusWidgetBasicConfigComponent
 | 
			
		||||
  ]
 | 
			
		||||
})
 | 
			
		||||
export class BasicWidgetConfigModule {
 | 
			
		||||
@ -225,5 +230,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
 | 
			
		||||
  'tb-power-button-basic-config': PowerButtonBasicConfigComponent,
 | 
			
		||||
  'tb-slider-basic-config': SliderBasicConfigComponent,
 | 
			
		||||
  'tb-toggle-button-basic-config': ToggleButtonBasicConfigComponent,
 | 
			
		||||
  'tb-time-series-chart-basic-config': TimeSeriesChartBasicConfigComponent
 | 
			
		||||
  'tb-time-series-chart-basic-config': TimeSeriesChartBasicConfigComponent,
 | 
			
		||||
  'tb-status-widget-basic-config': StatusWidgetBasicConfigComponent
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,101 @@
 | 
			
		||||
<!--
 | 
			
		||||
 | 
			
		||||
    Copyright © 2016-2024 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.
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<ng-container [formGroup]="statusWidgetConfigForm">
 | 
			
		||||
  <tb-target-device formControlName="targetDevice"></tb-target-device>
 | 
			
		||||
  <div class="tb-form-panel">
 | 
			
		||||
    <div class="tb-form-panel-title" translate>widgets.status-widget.behavior</div>
 | 
			
		||||
    <div class="tb-form-row">
 | 
			
		||||
      <div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.initial-state-hint' | translate}}" translate>widgets.rpc-state.initial-state</div>
 | 
			
		||||
      <tb-get-value-action-settings fxFlex
 | 
			
		||||
                                    panelTitle="widgets.rpc-state.initial-state"
 | 
			
		||||
                                    [valueType]="valueType.BOOLEAN"
 | 
			
		||||
                                    trueLabel="widgets.rpc-state.on"
 | 
			
		||||
                                    falseLabel="widgets.rpc-state.off"
 | 
			
		||||
                                    stateLabel="widgets.rpc-state.on"
 | 
			
		||||
                                    [aliasController]="aliasController"
 | 
			
		||||
                                    [targetDevice]="targetDevice"
 | 
			
		||||
                                    [widgetType]="widgetType"
 | 
			
		||||
                                    formControlName="initialState"></tb-get-value-action-settings>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="tb-form-row">
 | 
			
		||||
      <div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.disabled-state-hint' | translate}}" translate>widgets.rpc-state.disabled-state</div>
 | 
			
		||||
      <tb-get-value-action-settings fxFlex
 | 
			
		||||
                                    panelTitle="widgets.rpc-state.disabled-state"
 | 
			
		||||
                                    [valueType]="valueType.BOOLEAN"
 | 
			
		||||
                                    stateLabel="widgets.rpc-state.disabled"
 | 
			
		||||
                                    [aliasController]="aliasController"
 | 
			
		||||
                                    [targetDevice]="targetDevice"
 | 
			
		||||
                                    [widgetType]="widgetType"
 | 
			
		||||
                                    formControlName="disabledState"></tb-get-value-action-settings>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="tb-form-panel">
 | 
			
		||||
    <div class="tb-form-panel-title" translate>widget-config.appearance</div>
 | 
			
		||||
    <tb-image-cards-select rowHeight="1:1"
 | 
			
		||||
                           [cols]="{columns: 3,
 | 
			
		||||
                                    breakpoints: {
 | 
			
		||||
                                      'lt-sm': 1,
 | 
			
		||||
                                      'lt-md': 2
 | 
			
		||||
                                    }}"
 | 
			
		||||
                           label="{{ 'widgets.status-widget.layout' | translate }}" formControlName="layout">
 | 
			
		||||
      <tb-image-cards-select-option *ngFor="let layout of statusWidgetLayouts"
 | 
			
		||||
                                    [value]="layout"
 | 
			
		||||
                                    [image]="statusWidgetLayoutImageMap.get(layout)">
 | 
			
		||||
        {{ statusWidgetLayoutTranslationMap.get(layout) | translate }}
 | 
			
		||||
      </tb-image-cards-select-option>
 | 
			
		||||
    </tb-image-cards-select>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="tb-form-panel">
 | 
			
		||||
    <div fxLayout="row" fxLayoutAlign="space-between center">
 | 
			
		||||
      <div class="tb-form-panel-title" translate>widget-config.card-style</div>
 | 
			
		||||
      <tb-toggle-select [(ngModel)]="cardStyleMode"
 | 
			
		||||
                        [ngModelOptions]="{ standalone: true }">
 | 
			
		||||
        <tb-toggle-option value="on">{{ 'widgets.status-widget.on' | translate }}</tb-toggle-option>
 | 
			
		||||
        <tb-toggle-option value="off">{{ 'widgets.status-widget.off' | translate }}</tb-toggle-option>
 | 
			
		||||
      </tb-toggle-select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <tb-status-widget-state-settings
 | 
			
		||||
        *ngIf="cardStyleMode === 'on'"
 | 
			
		||||
        [layout]="statusWidgetConfigForm.get('layout').value"
 | 
			
		||||
        formControlName="onState">
 | 
			
		||||
    </tb-status-widget-state-settings>
 | 
			
		||||
    <tb-status-widget-state-settings
 | 
			
		||||
        *ngIf="cardStyleMode === 'off'"
 | 
			
		||||
        [layout]="statusWidgetConfigForm.get('layout').value"
 | 
			
		||||
        formControlName="offState">
 | 
			
		||||
    </tb-status-widget-state-settings>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="tb-form-panel">
 | 
			
		||||
    <div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
 | 
			
		||||
    <div class="tb-form-row space-between column-lt-md">
 | 
			
		||||
      <div translate>widget-config.show-card-buttons</div>
 | 
			
		||||
      <mat-chip-listbox multiple formControlName="cardButtons">
 | 
			
		||||
        <mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
 | 
			
		||||
      </mat-chip-listbox>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="tb-form-row space-between">
 | 
			
		||||
      <div>{{ 'widget-config.card-border-radius' | translate }}</div>
 | 
			
		||||
      <mat-form-field appearance="outline" subscriptSizing="dynamic">
 | 
			
		||||
        <input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
 | 
			
		||||
      </mat-form-field>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <tb-widget-actions-panel
 | 
			
		||||
      formControlName="actions">
 | 
			
		||||
  </tb-widget-actions-panel>
 | 
			
		||||
</ng-container>
 | 
			
		||||
@ -0,0 +1,119 @@
 | 
			
		||||
///
 | 
			
		||||
/// Copyright © 2016-2024 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 { Component } from '@angular/core';
 | 
			
		||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
 | 
			
		||||
import { Store } from '@ngrx/store';
 | 
			
		||||
import { AppState } from '@core/core.state';
 | 
			
		||||
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
 | 
			
		||||
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
 | 
			
		||||
import { TargetDevice, WidgetConfig, } from '@shared/models/widget.models';
 | 
			
		||||
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
 | 
			
		||||
import { isUndefined } from '@core/utils';
 | 
			
		||||
import { ValueType } from '@shared/models/constants';
 | 
			
		||||
import {
 | 
			
		||||
  statusWidgetDefaultSettings,
 | 
			
		||||
  statusWidgetLayoutImages,
 | 
			
		||||
  statusWidgetLayouts,
 | 
			
		||||
  statusWidgetLayoutTranslations,
 | 
			
		||||
  StatusWidgetSettings
 | 
			
		||||
} from '@home/components/widget/lib/indicator/status-widget.models';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-status-widget-basic-config',
 | 
			
		||||
  templateUrl: './status-widget-basic-config.component.html',
 | 
			
		||||
  styleUrls: ['../basic-config.scss']
 | 
			
		||||
})
 | 
			
		||||
export class StatusWidgetBasicConfigComponent extends BasicWidgetConfigComponent {
 | 
			
		||||
 | 
			
		||||
  get targetDevice(): TargetDevice {
 | 
			
		||||
    return this.statusWidgetConfigForm.get('targetDevice').value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  statusWidgetLayouts = statusWidgetLayouts;
 | 
			
		||||
 | 
			
		||||
  statusWidgetLayoutTranslationMap = statusWidgetLayoutTranslations;
 | 
			
		||||
  statusWidgetLayoutImageMap = statusWidgetLayoutImages;
 | 
			
		||||
 | 
			
		||||
  valueType = ValueType;
 | 
			
		||||
 | 
			
		||||
  statusWidgetConfigForm: UntypedFormGroup;
 | 
			
		||||
 | 
			
		||||
  cardStyleMode = 'on';
 | 
			
		||||
 | 
			
		||||
  constructor(protected store: Store<AppState>,
 | 
			
		||||
              protected widgetConfigComponent: WidgetConfigComponent,
 | 
			
		||||
              private fb: UntypedFormBuilder) {
 | 
			
		||||
    super(store, widgetConfigComponent);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected configForm(): UntypedFormGroup {
 | 
			
		||||
    return this.statusWidgetConfigForm;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected onConfigSet(configData: WidgetConfigComponentData) {
 | 
			
		||||
    const settings: StatusWidgetSettings = {...statusWidgetDefaultSettings, ...(configData.config.settings || {})};
 | 
			
		||||
    this.statusWidgetConfigForm = this.fb.group({
 | 
			
		||||
      targetDevice: [configData.config.targetDevice, []],
 | 
			
		||||
 | 
			
		||||
      initialState: [settings.initialState, []],
 | 
			
		||||
      disabledState: [settings.disabledState, []],
 | 
			
		||||
 | 
			
		||||
      layout: [settings.layout, []],
 | 
			
		||||
 | 
			
		||||
      onState: [settings.onState, []],
 | 
			
		||||
      offState: [settings.offState, []],
 | 
			
		||||
 | 
			
		||||
      cardButtons: [this.getCardButtons(configData.config), []],
 | 
			
		||||
      borderRadius: [configData.config.borderRadius, []],
 | 
			
		||||
 | 
			
		||||
      actions: [configData.config.actions || {}, []]
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected prepareOutputConfig(config: any): WidgetConfigComponentData {
 | 
			
		||||
    this.widgetConfig.config.targetDevice = config.targetDevice;
 | 
			
		||||
 | 
			
		||||
    this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
 | 
			
		||||
 | 
			
		||||
    this.widgetConfig.config.settings.initialState = config.initialState;
 | 
			
		||||
    this.widgetConfig.config.settings.disabledState = config.disabledState;
 | 
			
		||||
 | 
			
		||||
    this.widgetConfig.config.settings.layout = config.layout;
 | 
			
		||||
 | 
			
		||||
    this.widgetConfig.config.settings.onState = config.onState;
 | 
			
		||||
    this.widgetConfig.config.settings.offState = config.offState;
 | 
			
		||||
 | 
			
		||||
    this.setCardButtons(config.cardButtons, this.widgetConfig.config);
 | 
			
		||||
    this.widgetConfig.config.borderRadius = config.borderRadius;
 | 
			
		||||
 | 
			
		||||
    this.widgetConfig.config.actions = config.actions;
 | 
			
		||||
    return this.widgetConfig;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getCardButtons(config: WidgetConfig): string[] {
 | 
			
		||||
    const buttons: string[] = [];
 | 
			
		||||
    if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
 | 
			
		||||
      buttons.push('fullscreen');
 | 
			
		||||
    }
 | 
			
		||||
    return buttons;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private setCardButtons(buttons: string[], config: WidgetConfig) {
 | 
			
		||||
    config.enableFullscreen = buttons.includes('fullscreen');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,33 @@
 | 
			
		||||
<!--
 | 
			
		||||
 | 
			
		||||
    Copyright © 2016-2024 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.
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<div #statusWidgetPanel class="tb-status-widget-panel" [style]="backgroundStyle$ | async">
 | 
			
		||||
  <div class="tb-status-widget-overlay" [style]="overlayStyle" [style.inset]="overlayInset"></div>
 | 
			
		||||
  <div class="tb-status-widget-title-panel">
 | 
			
		||||
    <ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div #statusWidgetContent class="tb-status-widget-content" [class]="this.layout">
 | 
			
		||||
    <div class="tb-status-widget-icon-container">
 | 
			
		||||
      <tb-icon [style]="iconStyle">{{ icon }}</tb-icon>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="tb-status-widget-labels-container">
 | 
			
		||||
      <div *ngIf="showLabel" class="tb-status-widget-label" [style]="labelStyle">{{ label$ | async }}</div>
 | 
			
		||||
      <div *ngIf="showStatus" class="tb-status-widget-status" [style]="statusStyle">{{ status$ | async }}</div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <mat-progress-bar class="tb-action-widget-progress" style="height: 4px;" color="accent" mode="indeterminate" *ngIf="loading$ | async"></mat-progress-bar>
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,99 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2024 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.
 | 
			
		||||
 */
 | 
			
		||||
.tb-status-widget-panel {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  > div:not(.tb-status-widget-overlay), > tb-icon {
 | 
			
		||||
    z-index: 1;
 | 
			
		||||
  }
 | 
			
		||||
  .tb-status-widget-overlay {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    inset: 12px;
 | 
			
		||||
  }
 | 
			
		||||
  > div.tb-status-widget-title-panel {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 12px;
 | 
			
		||||
    left: 12px;
 | 
			
		||||
    right: 12px;
 | 
			
		||||
    z-index: 2;
 | 
			
		||||
  }
 | 
			
		||||
  .tb-status-widget-content {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    padding: 16px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    .tb-status-widget-icon-container {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
    }
 | 
			
		||||
    .tb-status-widget-labels-container {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      .tb-status-widget-label {
 | 
			
		||||
        display: -webkit-box;
 | 
			
		||||
        -webkit-line-clamp: 2;
 | 
			
		||||
        -webkit-box-orient: vertical;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        text-overflow: ellipsis;
 | 
			
		||||
      }
 | 
			
		||||
      .tb-status-widget-status {
 | 
			
		||||
        text-transform: uppercase;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        text-overflow: ellipsis;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    &.default {
 | 
			
		||||
      place-content: flex-start space-between;
 | 
			
		||||
      align-items: flex-start;
 | 
			
		||||
    }
 | 
			
		||||
    &.center {
 | 
			
		||||
      place-content: center flex-start;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      .tb-status-widget-icon-container {
 | 
			
		||||
        flex: 1;
 | 
			
		||||
        place-content: center;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
      }
 | 
			
		||||
      .tb-status-widget-labels-container {
 | 
			
		||||
        flex-direction: column-reverse;
 | 
			
		||||
        place-content: center flex-start;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    &.icon {
 | 
			
		||||
      place-content: center;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      .tb-status-widget-icon-container {
 | 
			
		||||
        flex: 1;
 | 
			
		||||
        place-content: center;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
      }
 | 
			
		||||
      .tb-status-widget-labels-container {
 | 
			
		||||
        display: none;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,242 @@
 | 
			
		||||
///
 | 
			
		||||
/// Copyright © 2016-2024 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 {
 | 
			
		||||
  AfterViewInit,
 | 
			
		||||
  ChangeDetectorRef,
 | 
			
		||||
  Component,
 | 
			
		||||
  ElementRef,
 | 
			
		||||
  OnDestroy,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  Renderer2, ViewChild,
 | 
			
		||||
  ViewEncapsulation
 | 
			
		||||
} from '@angular/core';
 | 
			
		||||
import { BasicActionWidgetComponent } from '@home/components/widget/lib/action/action-widget.models';
 | 
			
		||||
import {
 | 
			
		||||
  statusWidgetDefaultSettings,
 | 
			
		||||
  StatusWidgetLayout,
 | 
			
		||||
  StatusWidgetSettings, StatusWidgetStateSettings
 | 
			
		||||
} from '@home/components/widget/lib/indicator/status-widget.models';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import {
 | 
			
		||||
  backgroundStyle,
 | 
			
		||||
  ComponentStyle,
 | 
			
		||||
  iconStyle,
 | 
			
		||||
  overlayStyle,
 | 
			
		||||
  textStyle
 | 
			
		||||
} from '@shared/models/widget-settings.models';
 | 
			
		||||
import { ResizeObserver } from '@juggle/resize-observer';
 | 
			
		||||
import { ImagePipe } from '@shared/pipe/image.pipe';
 | 
			
		||||
import { DomSanitizer } from '@angular/platform-browser';
 | 
			
		||||
import { UtilsService } from '@core/services/utils.service';
 | 
			
		||||
import { ValueType } from '@shared/models/constants';
 | 
			
		||||
 | 
			
		||||
const initialStatusWidgetSize = 147;
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-status-widget',
 | 
			
		||||
  templateUrl: './status-widget.component.html',
 | 
			
		||||
  styleUrls: ['../action/action-widget.scss', './status-widget.component.scss'],
 | 
			
		||||
  encapsulation: ViewEncapsulation.None
 | 
			
		||||
})
 | 
			
		||||
export class StatusWidgetComponent extends
 | 
			
		||||
  BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
  @ViewChild('statusWidgetPanel', {static: false})
 | 
			
		||||
  statusWidgetPanel: ElementRef<HTMLElement>;
 | 
			
		||||
 | 
			
		||||
  @ViewChild('statusWidgetContent', {static: false})
 | 
			
		||||
  statusWidgetContent: ElementRef<HTMLElement>;
 | 
			
		||||
 | 
			
		||||
  settings: StatusWidgetSettings;
 | 
			
		||||
 | 
			
		||||
  backgroundStyle$: Observable<ComponentStyle>;
 | 
			
		||||
  overlayStyle: ComponentStyle = {};
 | 
			
		||||
 | 
			
		||||
  overlayInset = '12px';
 | 
			
		||||
  borderRadius = '';
 | 
			
		||||
 | 
			
		||||
  layout: StatusWidgetLayout;
 | 
			
		||||
 | 
			
		||||
  showLabel = true;
 | 
			
		||||
  label$: Observable<string>;
 | 
			
		||||
  labelStyle: ComponentStyle = {};
 | 
			
		||||
 | 
			
		||||
  showStatus = true;
 | 
			
		||||
  status$: Observable<string>;
 | 
			
		||||
  statusStyle: ComponentStyle = {};
 | 
			
		||||
 | 
			
		||||
  icon = '';
 | 
			
		||||
  iconStyle: ComponentStyle = {};
 | 
			
		||||
 | 
			
		||||
  private panelResize$: ResizeObserver;
 | 
			
		||||
 | 
			
		||||
  private onLabel$: Observable<string>;
 | 
			
		||||
  private onStatus$: Observable<string>;
 | 
			
		||||
  private onBackground$: Observable<ComponentStyle>;
 | 
			
		||||
  private onBackgroundDisabled$: Observable<ComponentStyle>;
 | 
			
		||||
 | 
			
		||||
  private offLabel$: Observable<string>;
 | 
			
		||||
  private offStatus$: Observable<string>;
 | 
			
		||||
  private offBackground$: Observable<ComponentStyle>;
 | 
			
		||||
  private offBackgroundDisabled$: Observable<ComponentStyle>;
 | 
			
		||||
 | 
			
		||||
  private state = false;
 | 
			
		||||
  private disabled = false;
 | 
			
		||||
  private disabledState = false;
 | 
			
		||||
 | 
			
		||||
  constructor(protected imagePipe: ImagePipe,
 | 
			
		||||
              protected sanitizer: DomSanitizer,
 | 
			
		||||
              private renderer: Renderer2,
 | 
			
		||||
              private utils: UtilsService,
 | 
			
		||||
              protected cd: ChangeDetectorRef,
 | 
			
		||||
              private elementRef: ElementRef) {
 | 
			
		||||
    super(cd);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    super.ngOnInit();
 | 
			
		||||
    this.settings = {...statusWidgetDefaultSettings, ...this.ctx.settings};
 | 
			
		||||
    this.layout = this.settings.layout;
 | 
			
		||||
 | 
			
		||||
    this.onLabel$ = this.ctx.registerLabelPattern(this.settings.onState.label, this.onLabel$);
 | 
			
		||||
    this.onStatus$ = this.ctx.registerLabelPattern(this.settings.onState.status, this.onStatus$);
 | 
			
		||||
    this.onBackground$ = backgroundStyle(this.settings.onState.background, this.imagePipe, this.sanitizer);
 | 
			
		||||
    this.onBackgroundDisabled$ = backgroundStyle(this.settings.onState.backgroundDisabled, this.imagePipe, this.sanitizer);
 | 
			
		||||
 | 
			
		||||
    this.offLabel$ = this.ctx.registerLabelPattern(this.settings.offState.label, this.offLabel$);
 | 
			
		||||
    this.offStatus$ = this.ctx.registerLabelPattern(this.settings.offState.status, this.offStatus$);
 | 
			
		||||
    this.offBackground$ = backgroundStyle(this.settings.offState.background, this.imagePipe, this.sanitizer);
 | 
			
		||||
    this.offBackgroundDisabled$ = backgroundStyle(this.settings.offState.backgroundDisabled, this.imagePipe, this.sanitizer);
 | 
			
		||||
 | 
			
		||||
    const getInitialStateSettings =
 | 
			
		||||
      {...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.initial-state')};
 | 
			
		||||
    this.createValueGetter(getInitialStateSettings, ValueType.BOOLEAN, {
 | 
			
		||||
      next: (value) => this.onState(value)
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const disabledStateSettings =
 | 
			
		||||
      {...this.settings.disabledState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.disabled-state')};
 | 
			
		||||
    this.createValueGetter(disabledStateSettings, ValueType.BOOLEAN, {
 | 
			
		||||
      next: (value) => this.onDisabled(value)
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.loading$.subscribe((loading) => {
 | 
			
		||||
      this.updateDisabledState(loading || this.disabled);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.updateStyle(this.state, this.disabled);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngAfterViewInit(): void {
 | 
			
		||||
    this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'overflow', 'visible');
 | 
			
		||||
    this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'position', 'absolute');
 | 
			
		||||
    this.panelResize$ = new ResizeObserver(() => {
 | 
			
		||||
      this.onResize();
 | 
			
		||||
    });
 | 
			
		||||
    this.panelResize$.observe(this.statusWidgetPanel.nativeElement);
 | 
			
		||||
    if (this.showLabel) {
 | 
			
		||||
      this.panelResize$.observe(this.statusWidgetPanel.nativeElement);
 | 
			
		||||
    }
 | 
			
		||||
    this.onResize();
 | 
			
		||||
    super.ngAfterViewInit();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    if (this.panelResize$) {
 | 
			
		||||
      this.panelResize$.disconnect();
 | 
			
		||||
    }
 | 
			
		||||
    super.ngOnDestroy();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public onInit() {
 | 
			
		||||
    super.onInit();
 | 
			
		||||
    this.borderRadius = this.ctx.$widgetElement.css('borderRadius');
 | 
			
		||||
    this.overlayStyle = {...this.overlayStyle, ...{borderRadius: this.borderRadius}};
 | 
			
		||||
    this.cd.detectChanges();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private onState(value: boolean): void {
 | 
			
		||||
    const newState = !!value;
 | 
			
		||||
    if (this.state !== newState) {
 | 
			
		||||
      this.state = newState;
 | 
			
		||||
      this.updateStyle(this.state, this.disabled || this.disabledState);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private onDisabled(value: boolean): void {
 | 
			
		||||
    const newDisabled = !!value;
 | 
			
		||||
    if (this.disabled !== newDisabled) {
 | 
			
		||||
      this.disabled = newDisabled;
 | 
			
		||||
      this.updateDisabledState(this.disabled);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private updateDisabledState(disabled: boolean) {
 | 
			
		||||
    this.disabledState = disabled;
 | 
			
		||||
    this.updateStyle(this.state, this.disabledState);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private onResize() {
 | 
			
		||||
    const panelWidth = this.statusWidgetPanel.nativeElement.getBoundingClientRect().width;
 | 
			
		||||
    const panelHeight = this.statusWidgetPanel.nativeElement.getBoundingClientRect().height;
 | 
			
		||||
    const targetSize = Math.min(panelWidth, panelHeight);
 | 
			
		||||
    const scale = targetSize / initialStatusWidgetSize;
 | 
			
		||||
    const width = initialStatusWidgetSize;
 | 
			
		||||
    const height = initialStatusWidgetSize;
 | 
			
		||||
    this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'width', width + 'px');
 | 
			
		||||
    this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'height', height + 'px');
 | 
			
		||||
    this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'transform', `scale(${scale})`);
 | 
			
		||||
    this.overlayInset = (Math.floor(12 * scale * 100) / 100) + 'px';
 | 
			
		||||
    this.cd.markForCheck();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private updateStyle(state: boolean, disabled: boolean) {
 | 
			
		||||
    let stateSettings: StatusWidgetStateSettings;
 | 
			
		||||
    if (state) {
 | 
			
		||||
      this.label$ = this.onLabel$;
 | 
			
		||||
      this.status$ = this.onStatus$;
 | 
			
		||||
      this.backgroundStyle$ = disabled ? this.onBackgroundDisabled$ : this.onBackground$;
 | 
			
		||||
      stateSettings = this.settings.onState;
 | 
			
		||||
    } else {
 | 
			
		||||
      this.label$ = this.offLabel$;
 | 
			
		||||
      this.status$ = this.offStatus$;
 | 
			
		||||
      this.backgroundStyle$ = disabled ? this.offBackgroundDisabled$ : this.offBackground$;
 | 
			
		||||
      stateSettings = this.settings.offState;
 | 
			
		||||
    }
 | 
			
		||||
    this.showLabel = stateSettings.showLabel && this.layout !== StatusWidgetLayout.icon;
 | 
			
		||||
    this.showStatus = stateSettings.showStatus && this.layout !== StatusWidgetLayout.icon;
 | 
			
		||||
    this.icon = stateSettings.icon;
 | 
			
		||||
 | 
			
		||||
    const primaryColor = disabled ? stateSettings.primaryColorDisabled : stateSettings.primaryColor;
 | 
			
		||||
    const secondaryColor = disabled ? stateSettings.secondaryColorDisabled : stateSettings.secondaryColor;
 | 
			
		||||
 | 
			
		||||
    this.labelStyle = textStyle(stateSettings.labelFont);
 | 
			
		||||
    this.labelStyle.color = primaryColor;
 | 
			
		||||
 | 
			
		||||
    this.statusStyle = textStyle(stateSettings.statusFont);
 | 
			
		||||
    this.statusStyle.color = secondaryColor;
 | 
			
		||||
 | 
			
		||||
    this.iconStyle = iconStyle(stateSettings.iconSize, stateSettings.iconSizeUnit);
 | 
			
		||||
    this.iconStyle.color = primaryColor;
 | 
			
		||||
 | 
			
		||||
    this.overlayStyle = overlayStyle(disabled ? stateSettings.backgroundDisabled.overlay : stateSettings.background.overlay);
 | 
			
		||||
    if (this.borderRadius) {
 | 
			
		||||
      this.overlayStyle = {...this.overlayStyle, ...{borderRadius: this.borderRadius}};
 | 
			
		||||
    }
 | 
			
		||||
    this.cd.detectChanges();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,204 @@
 | 
			
		||||
///
 | 
			
		||||
/// Copyright © 2016-2024 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 { DataToValueType, GetValueAction, GetValueSettings } from '@shared/models/action-widget-settings.models';
 | 
			
		||||
import { BackgroundSettings, BackgroundType, cssUnit, Font } from '@shared/models/widget-settings.models';
 | 
			
		||||
 | 
			
		||||
export enum StatusWidgetLayout {
 | 
			
		||||
  default = 'default',
 | 
			
		||||
  center = 'center',
 | 
			
		||||
  icon = 'icon'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const statusWidgetLayouts = Object.keys(StatusWidgetLayout) as StatusWidgetLayout[];
 | 
			
		||||
 | 
			
		||||
export const statusWidgetLayoutTranslations = new Map<StatusWidgetLayout, string>(
 | 
			
		||||
  [
 | 
			
		||||
    [StatusWidgetLayout.default, 'widgets.status-widget.layout-default'],
 | 
			
		||||
    [StatusWidgetLayout.center, 'widgets.status-widget.layout-center'],
 | 
			
		||||
    [StatusWidgetLayout.icon, 'widgets.status-widget.layout-icon']
 | 
			
		||||
  ]
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const statusWidgetLayoutImages = new Map<StatusWidgetLayout, string>(
 | 
			
		||||
  [
 | 
			
		||||
    [StatusWidgetLayout.default, 'assets/widget/status-widget/default-layout.svg'],
 | 
			
		||||
    [StatusWidgetLayout.center, 'assets/widget/status-widget/center-layout.svg'],
 | 
			
		||||
    [StatusWidgetLayout.icon, 'assets/widget/status-widget/icon-layout.svg']
 | 
			
		||||
  ]
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export interface StatusWidgetStateSettings {
 | 
			
		||||
  showLabel: boolean;
 | 
			
		||||
  label: string;
 | 
			
		||||
  labelFont: Font;
 | 
			
		||||
  showStatus: boolean;
 | 
			
		||||
  status: string;
 | 
			
		||||
  statusFont: Font;
 | 
			
		||||
  icon: string;
 | 
			
		||||
  iconSize: number;
 | 
			
		||||
  iconSizeUnit: cssUnit;
 | 
			
		||||
  primaryColor: string;
 | 
			
		||||
  secondaryColor: string;
 | 
			
		||||
  background: BackgroundSettings;
 | 
			
		||||
  primaryColorDisabled: string;
 | 
			
		||||
  secondaryColorDisabled: string;
 | 
			
		||||
  backgroundDisabled: BackgroundSettings;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface StatusWidgetSettings {
 | 
			
		||||
  initialState: GetValueSettings<boolean>;
 | 
			
		||||
  disabledState: GetValueSettings<boolean>;
 | 
			
		||||
  layout: StatusWidgetLayout;
 | 
			
		||||
  onState: StatusWidgetStateSettings;
 | 
			
		||||
  offState: StatusWidgetStateSettings;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const statusWidgetDefaultSettings: StatusWidgetSettings = {
 | 
			
		||||
  initialState: {
 | 
			
		||||
    action: GetValueAction.EXECUTE_RPC,
 | 
			
		||||
    defaultValue: false,
 | 
			
		||||
    executeRpc: {
 | 
			
		||||
      method: 'getState',
 | 
			
		||||
      requestTimeout: 5000,
 | 
			
		||||
      requestPersistent: false,
 | 
			
		||||
      persistentPollingInterval: 1000
 | 
			
		||||
    },
 | 
			
		||||
    getAttribute: {
 | 
			
		||||
      key: 'state',
 | 
			
		||||
      scope: null
 | 
			
		||||
    },
 | 
			
		||||
    getTimeSeries: {
 | 
			
		||||
      key: 'state'
 | 
			
		||||
    },
 | 
			
		||||
    dataToValue: {
 | 
			
		||||
      type: DataToValueType.NONE,
 | 
			
		||||
      compareToValue: true,
 | 
			
		||||
      dataToValueFunction: '/* Should return boolean value */\nreturn data;'
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  disabledState: {
 | 
			
		||||
    action: GetValueAction.DO_NOTHING,
 | 
			
		||||
    defaultValue: false,
 | 
			
		||||
    getAttribute: {
 | 
			
		||||
      key: 'state',
 | 
			
		||||
      scope: null
 | 
			
		||||
    },
 | 
			
		||||
    getTimeSeries: {
 | 
			
		||||
      key: 'state'
 | 
			
		||||
    },
 | 
			
		||||
    dataToValue: {
 | 
			
		||||
      type: DataToValueType.NONE,
 | 
			
		||||
      compareToValue: true,
 | 
			
		||||
      dataToValueFunction: '/* Should return boolean value */\nreturn data;'
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  layout: StatusWidgetLayout.default,
 | 
			
		||||
  onState: {
 | 
			
		||||
    showLabel: true,
 | 
			
		||||
    label: 'Window left corner',
 | 
			
		||||
    labelFont: {
 | 
			
		||||
      family: 'Roboto',
 | 
			
		||||
      size: 12,
 | 
			
		||||
      sizeUnit: 'px',
 | 
			
		||||
      style: 'normal',
 | 
			
		||||
      weight: '500',
 | 
			
		||||
      lineHeight: '16px'
 | 
			
		||||
    },
 | 
			
		||||
    showStatus: true,
 | 
			
		||||
    status: 'Opened',
 | 
			
		||||
    statusFont: {
 | 
			
		||||
      family: 'Roboto',
 | 
			
		||||
      size: 10,
 | 
			
		||||
      sizeUnit: 'px',
 | 
			
		||||
      style: 'normal',
 | 
			
		||||
      weight: '500',
 | 
			
		||||
      lineHeight: '20px'
 | 
			
		||||
    },
 | 
			
		||||
    icon: 'mdi:curtains',
 | 
			
		||||
    iconSize: 32,
 | 
			
		||||
    iconSizeUnit: 'px',
 | 
			
		||||
    primaryColor: '#fff',
 | 
			
		||||
    secondaryColor: 'rgba(255, 255, 255, 0.80)',
 | 
			
		||||
    background: {
 | 
			
		||||
      type: BackgroundType.color,
 | 
			
		||||
      color: '#3F52DD',
 | 
			
		||||
      overlay: {
 | 
			
		||||
        enabled: false,
 | 
			
		||||
        color: 'rgba(255,255,255,0.72)',
 | 
			
		||||
        blur: 3
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    primaryColorDisabled: 'rgba(0, 0, 0, 0.38)',
 | 
			
		||||
    secondaryColorDisabled: 'rgba(0, 0, 0, 0.38)',
 | 
			
		||||
    backgroundDisabled: {
 | 
			
		||||
      type: BackgroundType.color,
 | 
			
		||||
      color: '#CACACA',
 | 
			
		||||
      overlay: {
 | 
			
		||||
        enabled: false,
 | 
			
		||||
        color: 'rgba(255,255,255,0.72)',
 | 
			
		||||
        blur: 3
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  offState: {
 | 
			
		||||
    showLabel: true,
 | 
			
		||||
    label: 'Window left corner',
 | 
			
		||||
    labelFont: {
 | 
			
		||||
      family: 'Roboto',
 | 
			
		||||
      size: 12,
 | 
			
		||||
      sizeUnit: 'px',
 | 
			
		||||
      style: 'normal',
 | 
			
		||||
      weight: '500',
 | 
			
		||||
      lineHeight: '16px'
 | 
			
		||||
    },
 | 
			
		||||
    showStatus: true,
 | 
			
		||||
    status: 'Closed',
 | 
			
		||||
    statusFont: {
 | 
			
		||||
      family: 'Roboto',
 | 
			
		||||
      size: 10,
 | 
			
		||||
      sizeUnit: 'px',
 | 
			
		||||
      style: 'normal',
 | 
			
		||||
      weight: '500',
 | 
			
		||||
      lineHeight: '20px'
 | 
			
		||||
    },
 | 
			
		||||
    icon: 'mdi:curtains-closed',
 | 
			
		||||
    iconSize: 32,
 | 
			
		||||
    iconSizeUnit: 'px',
 | 
			
		||||
    primaryColor: 'rgba(0, 0, 0, 0.87)',
 | 
			
		||||
    secondaryColor: 'rgba(0, 0, 0, 0.54)',
 | 
			
		||||
    background: {
 | 
			
		||||
      type: BackgroundType.color,
 | 
			
		||||
      color: '#FFF',
 | 
			
		||||
      overlay: {
 | 
			
		||||
        enabled: false,
 | 
			
		||||
        color: 'rgba(255,255,255,0.72)',
 | 
			
		||||
        blur: 3
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    primaryColorDisabled: 'rgba(0, 0, 0, 0.38)',
 | 
			
		||||
    secondaryColorDisabled: 'rgba(0, 0, 0, 0.38)',
 | 
			
		||||
    backgroundDisabled: {
 | 
			
		||||
      type: BackgroundType.color,
 | 
			
		||||
      color: '#CACACA',
 | 
			
		||||
      overlay: {
 | 
			
		||||
        enabled: false,
 | 
			
		||||
        color: 'rgba(255,255,255,0.72)',
 | 
			
		||||
        blur: 3
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,111 @@
 | 
			
		||||
<!--
 | 
			
		||||
 | 
			
		||||
    Copyright © 2016-2024 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.
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<ng-container [formGroup]="stateSettingsFormGroup">
 | 
			
		||||
  <div *ngIf="layout !== StatusWidgetLayout.icon" class="tb-form-row column-xs">
 | 
			
		||||
    <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showLabel">
 | 
			
		||||
      {{ 'widgets.status-widget.label' | translate }}
 | 
			
		||||
    </mat-slide-toggle>
 | 
			
		||||
    <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
 | 
			
		||||
      <mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
 | 
			
		||||
        <input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}">
 | 
			
		||||
      </mat-form-field>
 | 
			
		||||
      <tb-font-settings formControlName="labelFont"
 | 
			
		||||
                        [previewText]="stateSettingsFormGroup.get('label').value">
 | 
			
		||||
      </tb-font-settings>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div *ngIf="layout !== StatusWidgetLayout.icon" class="tb-form-row column-xs">
 | 
			
		||||
    <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showStatus">
 | 
			
		||||
      {{ 'widgets.status-widget.status' | translate }}
 | 
			
		||||
    </mat-slide-toggle>
 | 
			
		||||
    <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
 | 
			
		||||
      <mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
 | 
			
		||||
        <input matInput formControlName="status" placeholder="{{ 'widget-config.set' | translate }}">
 | 
			
		||||
      </mat-form-field>
 | 
			
		||||
      <tb-font-settings formControlName="statusFont"
 | 
			
		||||
                        [previewText]="stateSettingsFormGroup.get('status').value">
 | 
			
		||||
      </tb-font-settings>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="tb-form-row">
 | 
			
		||||
    <div class="fixed-title-width">
 | 
			
		||||
      {{ 'widgets.status-widget.icon' | translate }}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
 | 
			
		||||
      <mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic">
 | 
			
		||||
        <input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}">
 | 
			
		||||
      </mat-form-field>
 | 
			
		||||
      <tb-css-unit-select fxFlex formControlName="iconSizeUnit"></tb-css-unit-select>
 | 
			
		||||
      <tb-material-icon-select asBoxInput
 | 
			
		||||
                               formControlName="icon">
 | 
			
		||||
      </tb-material-icon-select>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="tb-form-row space-between" [class]="{'column-xs': layout === StatusWidgetLayout.icon, 'column-lt-md': layout !== StatusWidgetLayout.icon}">
 | 
			
		||||
    <div>{{ 'widgets.status-widget.color-palette' | translate }}</div>
 | 
			
		||||
    <div fxLayout="row wrap" fxLayoutAlign="start center" fxLayoutAlign.lt-sm="space-between center"
 | 
			
		||||
         [fxLayoutAlign.lt-md]="layout !== StatusWidgetLayout.icon ? 'space-between center': 'start center'"
 | 
			
		||||
         style="gap: 12px;">
 | 
			
		||||
      <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
 | 
			
		||||
        <div tb-hint-tooltip-icon="{{'widgets.status-widget.primary-color-hint' | translate}}" translate>widgets.status-widget.primary</div>
 | 
			
		||||
        <tb-color-input asBoxInput
 | 
			
		||||
                        formControlName="primaryColor">
 | 
			
		||||
        </tb-color-input>
 | 
			
		||||
      </div>
 | 
			
		||||
      <mat-divider *ngIf="layout !== StatusWidgetLayout.icon" vertical fxHide.lt-md></mat-divider>
 | 
			
		||||
      <div *ngIf="layout !== StatusWidgetLayout.icon" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
 | 
			
		||||
        <div tb-hint-tooltip-icon="{{'widgets.status-widget.secondary-color-hint' | translate}}" translate>widgets.status-widget.secondary</div>
 | 
			
		||||
        <tb-color-input asBoxInput
 | 
			
		||||
                        formControlName="secondaryColor">
 | 
			
		||||
        </tb-color-input>
 | 
			
		||||
      </div>
 | 
			
		||||
      <mat-divider vertical fxHide.lt-sm [fxHide.lt-md]="layout !== StatusWidgetLayout.icon"></mat-divider>
 | 
			
		||||
      <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
 | 
			
		||||
        <div translate>widgets.status-widget.background</div>
 | 
			
		||||
        <tb-background-settings formControlName="background">
 | 
			
		||||
        </tb-background-settings>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="tb-form-row space-between" [class]="{'column-xs': layout === StatusWidgetLayout.icon, 'column-lt-md': layout !== StatusWidgetLayout.icon}">
 | 
			
		||||
    <div>{{ 'widgets.status-widget.disabled-color-palette' | translate }}</div>
 | 
			
		||||
    <div fxLayout="row wrap" fxLayoutAlign="start center" fxLayoutAlign.lt-sm="space-between center"
 | 
			
		||||
         [fxLayoutAlign.lt-md]="layout !== StatusWidgetLayout.icon ? 'space-between center': 'start center'"
 | 
			
		||||
         style="gap: 12px;">
 | 
			
		||||
      <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
 | 
			
		||||
        <div tb-hint-tooltip-icon="{{'widgets.status-widget.primary-color-hint' | translate}}" translate>widgets.status-widget.primary</div>
 | 
			
		||||
        <tb-color-input asBoxInput
 | 
			
		||||
                        formControlName="primaryColorDisabled">
 | 
			
		||||
        </tb-color-input>
 | 
			
		||||
      </div>
 | 
			
		||||
      <mat-divider *ngIf="layout !== StatusWidgetLayout.icon" vertical fxHide.lt-md></mat-divider>
 | 
			
		||||
      <div *ngIf="layout !== StatusWidgetLayout.icon" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
 | 
			
		||||
        <div tb-hint-tooltip-icon="{{'widgets.status-widget.secondary-color-hint' | translate}}" translate>widgets.status-widget.secondary</div>
 | 
			
		||||
        <tb-color-input asBoxInput
 | 
			
		||||
                        formControlName="secondaryColorDisabled">
 | 
			
		||||
        </tb-color-input>
 | 
			
		||||
      </div>
 | 
			
		||||
      <mat-divider vertical fxHide.lt-sm [fxHide.lt-md]="layout !== StatusWidgetLayout.icon"></mat-divider>
 | 
			
		||||
      <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
 | 
			
		||||
        <div translate>widgets.status-widget.background</div>
 | 
			
		||||
        <tb-background-settings formControlName="backgroundDisabled">
 | 
			
		||||
        </tb-background-settings>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-container>
 | 
			
		||||
@ -0,0 +1,158 @@
 | 
			
		||||
///
 | 
			
		||||
/// Copyright © 2016-2024 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 { Component, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
 | 
			
		||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
 | 
			
		||||
import { merge } from 'rxjs';
 | 
			
		||||
import {
 | 
			
		||||
  StatusWidgetLayout,
 | 
			
		||||
  StatusWidgetStateSettings
 | 
			
		||||
} from '@home/components/widget/lib/indicator/status-widget.models';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-status-widget-state-settings',
 | 
			
		||||
  templateUrl: './status-widget-state-settings.component.html',
 | 
			
		||||
  styleUrls: ['./../../widget-settings.scss'],
 | 
			
		||||
  providers: [
 | 
			
		||||
    {
 | 
			
		||||
      provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
      useExisting: forwardRef(() => StatusWidgetStateSettingsComponent),
 | 
			
		||||
      multi: true
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
})
 | 
			
		||||
export class StatusWidgetStateSettingsComponent implements OnInit, OnChanges, ControlValueAccessor {
 | 
			
		||||
 | 
			
		||||
  StatusWidgetLayout = StatusWidgetLayout;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  disabled: boolean;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  layout: StatusWidgetLayout;
 | 
			
		||||
 | 
			
		||||
  private modelValue: StatusWidgetStateSettings;
 | 
			
		||||
 | 
			
		||||
  private propagateChange = null;
 | 
			
		||||
 | 
			
		||||
  public stateSettingsFormGroup: UntypedFormGroup;
 | 
			
		||||
 | 
			
		||||
  constructor(private fb: UntypedFormBuilder) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.stateSettingsFormGroup = this.fb.group({
 | 
			
		||||
      showLabel: [null, []],
 | 
			
		||||
      label: [null, []],
 | 
			
		||||
      labelFont: [null, []],
 | 
			
		||||
      showStatus: [null, []],
 | 
			
		||||
      status: [null, []],
 | 
			
		||||
      statusFont: [null, []],
 | 
			
		||||
      icon: [null, []],
 | 
			
		||||
      iconSize: [null, []],
 | 
			
		||||
      iconSizeUnit: [null, []],
 | 
			
		||||
      primaryColor: [null, []],
 | 
			
		||||
      secondaryColor: [null, []],
 | 
			
		||||
      background: [null, []],
 | 
			
		||||
      primaryColorDisabled: [null, []],
 | 
			
		||||
      secondaryColorDisabled: [null, []],
 | 
			
		||||
      backgroundDisabled: [null, []]
 | 
			
		||||
    });
 | 
			
		||||
    this.stateSettingsFormGroup.valueChanges.subscribe(() => {
 | 
			
		||||
      this.updateModel();
 | 
			
		||||
    });
 | 
			
		||||
    merge(this.stateSettingsFormGroup.get('showLabel').valueChanges,
 | 
			
		||||
      this.stateSettingsFormGroup.get('showStatus').valueChanges)
 | 
			
		||||
    .subscribe(() => {
 | 
			
		||||
      this.updateValidators();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges(changes: SimpleChanges): void {
 | 
			
		||||
    for (const propName of Object.keys(changes)) {
 | 
			
		||||
      const change = changes[propName];
 | 
			
		||||
      if (!change.firstChange && change.currentValue !== change.previousValue) {
 | 
			
		||||
        if (['layout'].includes(propName)) {
 | 
			
		||||
          this.updateValidators();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  registerOnChange(fn: any): void {
 | 
			
		||||
    this.propagateChange = fn;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  registerOnTouched(_fn: any): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDisabledState(isDisabled: boolean): void {
 | 
			
		||||
    this.disabled = isDisabled;
 | 
			
		||||
    if (isDisabled) {
 | 
			
		||||
      this.stateSettingsFormGroup.disable({emitEvent: false});
 | 
			
		||||
    } else {
 | 
			
		||||
      this.stateSettingsFormGroup.enable({emitEvent: false});
 | 
			
		||||
      this.updateValidators();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  writeValue(value: StatusWidgetStateSettings): void {
 | 
			
		||||
    this.modelValue = value;
 | 
			
		||||
    this.stateSettingsFormGroup.patchValue(
 | 
			
		||||
      value, {emitEvent: false}
 | 
			
		||||
    );
 | 
			
		||||
    this.updateValidators();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private updateValidators() {
 | 
			
		||||
    if (this.layout === StatusWidgetLayout.icon) {
 | 
			
		||||
      this.stateSettingsFormGroup.get('showLabel').disable({emitEvent: false});
 | 
			
		||||
      this.stateSettingsFormGroup.get('label').disable({emitEvent: false});
 | 
			
		||||
      this.stateSettingsFormGroup.get('labelFont').disable({emitEvent: false});
 | 
			
		||||
      this.stateSettingsFormGroup.get('showStatus').disable({emitEvent: false});
 | 
			
		||||
      this.stateSettingsFormGroup.get('status').disable({emitEvent: false});
 | 
			
		||||
      this.stateSettingsFormGroup.get('statusFont').disable({emitEvent: false});
 | 
			
		||||
      this.stateSettingsFormGroup.get('secondaryColor').disable({emitEvent: false});
 | 
			
		||||
      this.stateSettingsFormGroup.get('secondaryColorDisabled').disable({emitEvent: false});
 | 
			
		||||
    } else {
 | 
			
		||||
      this.stateSettingsFormGroup.get('showLabel').enable({emitEvent: false});
 | 
			
		||||
      this.stateSettingsFormGroup.get('showStatus').enable({emitEvent: false});
 | 
			
		||||
      this.stateSettingsFormGroup.get('secondaryColor').enable({emitEvent: false});
 | 
			
		||||
      this.stateSettingsFormGroup.get('secondaryColorDisabled').enable({emitEvent: false});
 | 
			
		||||
      const showLabel: boolean = this.stateSettingsFormGroup.get('showLabel').value;
 | 
			
		||||
      const showStatus: boolean = this.stateSettingsFormGroup.get('showStatus').value;
 | 
			
		||||
      if (showLabel) {
 | 
			
		||||
        this.stateSettingsFormGroup.get('label').enable({emitEvent: false});
 | 
			
		||||
        this.stateSettingsFormGroup.get('labelFont').enable({emitEvent: false});
 | 
			
		||||
      } else {
 | 
			
		||||
        this.stateSettingsFormGroup.get('label').disable({emitEvent: false});
 | 
			
		||||
        this.stateSettingsFormGroup.get('labelFont').disable({emitEvent: false});
 | 
			
		||||
      }
 | 
			
		||||
      if (showStatus) {
 | 
			
		||||
        this.stateSettingsFormGroup.get('status').enable({emitEvent: false});
 | 
			
		||||
        this.stateSettingsFormGroup.get('statusFont').enable({emitEvent: false});
 | 
			
		||||
      } else {
 | 
			
		||||
        this.stateSettingsFormGroup.get('status').disable({emitEvent: false});
 | 
			
		||||
        this.stateSettingsFormGroup.get('statusFont').disable({emitEvent: false});
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private updateModel() {
 | 
			
		||||
    this.modelValue = this.stateSettingsFormGroup.getRawValue();
 | 
			
		||||
    this.propagateChange(this.modelValue);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -145,6 +145,9 @@ import {
 | 
			
		||||
import {
 | 
			
		||||
  TimeSeriesChartGridSettingsComponent
 | 
			
		||||
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-grid-settings.component';
 | 
			
		||||
import {
 | 
			
		||||
  StatusWidgetStateSettingsComponent
 | 
			
		||||
} from '@home/components/widget/lib/settings/common/indicator/status-widget-state-settings.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@ -198,6 +201,7 @@ import {
 | 
			
		||||
    TimeSeriesChartStatesPanelComponent,
 | 
			
		||||
    TimeSeriesChartStateRowComponent,
 | 
			
		||||
    TimeSeriesChartGridSettingsComponent,
 | 
			
		||||
    StatusWidgetStateSettingsComponent,
 | 
			
		||||
    DataKeyInputComponent,
 | 
			
		||||
    EntityAliasInputComponent
 | 
			
		||||
  ],
 | 
			
		||||
@ -257,6 +261,7 @@ import {
 | 
			
		||||
    TimeSeriesChartStatesPanelComponent,
 | 
			
		||||
    TimeSeriesChartStateRowComponent,
 | 
			
		||||
    TimeSeriesChartGridSettingsComponent,
 | 
			
		||||
    StatusWidgetStateSettingsComponent,
 | 
			
		||||
    DataKeyInputComponent,
 | 
			
		||||
    EntityAliasInputComponent
 | 
			
		||||
  ],
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,82 @@
 | 
			
		||||
<!--
 | 
			
		||||
 | 
			
		||||
    Copyright © 2016-2024 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.
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<ng-container [formGroup]="statusWidgetSettingsForm">
 | 
			
		||||
  <div class="tb-form-panel">
 | 
			
		||||
    <div class="tb-form-panel-title" translate>widgets.status-widget.behavior</div>
 | 
			
		||||
    <div class="tb-form-row">
 | 
			
		||||
      <div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.initial-state-hint' | translate}}" translate>widgets.rpc-state.initial-state</div>
 | 
			
		||||
      <tb-get-value-action-settings fxFlex
 | 
			
		||||
                                    panelTitle="widgets.rpc-state.initial-state"
 | 
			
		||||
                                    [valueType]="valueType.BOOLEAN"
 | 
			
		||||
                                    trueLabel="widgets.rpc-state.on"
 | 
			
		||||
                                    falseLabel="widgets.rpc-state.off"
 | 
			
		||||
                                    stateLabel="widgets.rpc-state.on"
 | 
			
		||||
                                    [aliasController]="aliasController"
 | 
			
		||||
                                    [targetDevice]="targetDevice"
 | 
			
		||||
                                    [widgetType]="widgetType"
 | 
			
		||||
                                    formControlName="initialState"></tb-get-value-action-settings>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="tb-form-row">
 | 
			
		||||
      <div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.disabled-state-hint' | translate}}" translate>widgets.rpc-state.disabled-state</div>
 | 
			
		||||
      <tb-get-value-action-settings fxFlex
 | 
			
		||||
                                    panelTitle="widgets.rpc-state.disabled-state"
 | 
			
		||||
                                    [valueType]="valueType.BOOLEAN"
 | 
			
		||||
                                    stateLabel="widgets.rpc-state.disabled"
 | 
			
		||||
                                    [aliasController]="aliasController"
 | 
			
		||||
                                    [targetDevice]="targetDevice"
 | 
			
		||||
                                    [widgetType]="widgetType"
 | 
			
		||||
                                    formControlName="disabledState"></tb-get-value-action-settings>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="tb-form-panel">
 | 
			
		||||
    <div class="tb-form-panel-title" translate>widget-config.appearance</div>
 | 
			
		||||
    <tb-image-cards-select rowHeight="1:1"
 | 
			
		||||
                           [cols]="{columns: 3,
 | 
			
		||||
                                    breakpoints: {
 | 
			
		||||
                                      'lt-sm': 1,
 | 
			
		||||
                                      'lt-md': 2
 | 
			
		||||
                                    }}"
 | 
			
		||||
                           label="{{ 'widgets.status-widget.layout' | translate }}" formControlName="layout">
 | 
			
		||||
      <tb-image-cards-select-option *ngFor="let layout of statusWidgetLayouts"
 | 
			
		||||
                                    [value]="layout"
 | 
			
		||||
                                    [image]="statusWidgetLayoutImageMap.get(layout)">
 | 
			
		||||
        {{ statusWidgetLayoutTranslationMap.get(layout) | translate }}
 | 
			
		||||
      </tb-image-cards-select-option>
 | 
			
		||||
    </tb-image-cards-select>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="tb-form-panel">
 | 
			
		||||
    <div fxLayout="row" fxLayoutAlign="space-between center">
 | 
			
		||||
      <div class="tb-form-panel-title" translate>widget-config.card-style</div>
 | 
			
		||||
      <tb-toggle-select [(ngModel)]="cardStyleMode"
 | 
			
		||||
                        [ngModelOptions]="{ standalone: true }">
 | 
			
		||||
        <tb-toggle-option value="on">{{ 'widgets.status-widget.on' | translate }}</tb-toggle-option>
 | 
			
		||||
        <tb-toggle-option value="off">{{ 'widgets.status-widget.off' | translate }}</tb-toggle-option>
 | 
			
		||||
      </tb-toggle-select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <tb-status-widget-state-settings
 | 
			
		||||
        *ngIf="cardStyleMode === 'on'"
 | 
			
		||||
        [layout]="statusWidgetSettingsForm.get('layout').value"
 | 
			
		||||
        formControlName="onState">
 | 
			
		||||
    </tb-status-widget-state-settings>
 | 
			
		||||
    <tb-status-widget-state-settings
 | 
			
		||||
        *ngIf="cardStyleMode === 'off'"
 | 
			
		||||
        [layout]="statusWidgetSettingsForm.get('layout').value"
 | 
			
		||||
        formControlName="offState">
 | 
			
		||||
    </tb-status-widget-state-settings>
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-container>
 | 
			
		||||
@ -0,0 +1,79 @@
 | 
			
		||||
///
 | 
			
		||||
/// Copyright © 2016-2024 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 { Component, Injector } from '@angular/core';
 | 
			
		||||
import { TargetDevice, WidgetSettings, WidgetSettingsComponent, widgetType } from '@shared/models/widget.models';
 | 
			
		||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
 | 
			
		||||
import { Store } from '@ngrx/store';
 | 
			
		||||
import { AppState } from '@core/core.state';
 | 
			
		||||
import {
 | 
			
		||||
  statusWidgetDefaultSettings,
 | 
			
		||||
  statusWidgetLayoutImages,
 | 
			
		||||
  statusWidgetLayouts,
 | 
			
		||||
  statusWidgetLayoutTranslations
 | 
			
		||||
} from '@home/components/widget/lib/indicator/status-widget.models';
 | 
			
		||||
import { ValueType } from '@shared/models/constants';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-status-widget-settings',
 | 
			
		||||
  templateUrl: './status-widget-settings.component.html',
 | 
			
		||||
  styleUrls: ['./../widget-settings.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class StatusWidgetSettingsComponent extends WidgetSettingsComponent {
 | 
			
		||||
 | 
			
		||||
  get targetDevice(): TargetDevice {
 | 
			
		||||
    return this.widgetConfig?.config?.targetDevice;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get widgetType(): widgetType {
 | 
			
		||||
    return this.widgetConfig?.widgetType;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  statusWidgetLayouts = statusWidgetLayouts;
 | 
			
		||||
 | 
			
		||||
  statusWidgetLayoutTranslationMap = statusWidgetLayoutTranslations;
 | 
			
		||||
  statusWidgetLayoutImageMap = statusWidgetLayoutImages;
 | 
			
		||||
 | 
			
		||||
  valueType = ValueType;
 | 
			
		||||
 | 
			
		||||
  statusWidgetSettingsForm: UntypedFormGroup;
 | 
			
		||||
 | 
			
		||||
  cardStyleMode = 'on';
 | 
			
		||||
 | 
			
		||||
  constructor(protected store: Store<AppState>,
 | 
			
		||||
              private $injector: Injector,
 | 
			
		||||
              private fb: UntypedFormBuilder) {
 | 
			
		||||
    super(store);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected settingsForm(): UntypedFormGroup {
 | 
			
		||||
    return this.statusWidgetSettingsForm;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected defaultSettings(): WidgetSettings {
 | 
			
		||||
    return {...statusWidgetDefaultSettings};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected onSettingsSet(settings: WidgetSettings) {
 | 
			
		||||
    this.statusWidgetSettingsForm = this.fb.group({
 | 
			
		||||
      initialState: [settings.initialState, []],
 | 
			
		||||
      disabledState: [settings.disabledState, []],
 | 
			
		||||
      layout: [settings.layout, []],
 | 
			
		||||
      onState: [settings.onState, []],
 | 
			
		||||
      offState: [settings.offState, []]
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -342,6 +342,9 @@ import {
 | 
			
		||||
import {
 | 
			
		||||
  TimeSeriesChartWidgetSettingsComponent
 | 
			
		||||
} from '@home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component';
 | 
			
		||||
import {
 | 
			
		||||
  StatusWidgetSettingsComponent
 | 
			
		||||
} from '@home/components/widget/lib/settings/indicator/status-widget-settings.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@ -464,7 +467,8 @@ import {
 | 
			
		||||
    TimeSeriesChartKeySettingsComponent,
 | 
			
		||||
    TimeSeriesChartLineSettingsComponent,
 | 
			
		||||
    TimeSeriesChartBarSettingsComponent,
 | 
			
		||||
    TimeSeriesChartWidgetSettingsComponent
 | 
			
		||||
    TimeSeriesChartWidgetSettingsComponent,
 | 
			
		||||
    StatusWidgetSettingsComponent
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
@ -592,7 +596,8 @@ import {
 | 
			
		||||
    TimeSeriesChartKeySettingsComponent,
 | 
			
		||||
    TimeSeriesChartLineSettingsComponent,
 | 
			
		||||
    TimeSeriesChartBarSettingsComponent,
 | 
			
		||||
    TimeSeriesChartWidgetSettingsComponent
 | 
			
		||||
    TimeSeriesChartWidgetSettingsComponent,
 | 
			
		||||
    StatusWidgetSettingsComponent
 | 
			
		||||
  ]
 | 
			
		||||
})
 | 
			
		||||
export class WidgetSettingsModule {
 | 
			
		||||
@ -685,5 +690,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
 | 
			
		||||
  'tb-slider-widget-settings': SliderWidgetSettingsComponent,
 | 
			
		||||
  'tb-toggle-button-widget-settings': ToggleButtonWidgetSettingsComponent,
 | 
			
		||||
  'tb-time-series-chart-key-settings': TimeSeriesChartKeySettingsComponent,
 | 
			
		||||
  'tb-time-series-chart-widget-settings': TimeSeriesChartWidgetSettingsComponent
 | 
			
		||||
  'tb-time-series-chart-widget-settings': TimeSeriesChartWidgetSettingsComponent,
 | 
			
		||||
  'tb-status-widget-settings': StatusWidgetSettingsComponent
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -86,6 +86,7 @@ import { PowerButtonWidgetComponent } from '@home/components/widget/lib/rpc/powe
 | 
			
		||||
import { SliderWidgetComponent } from '@home/components/widget/lib/rpc/slider-widget.component';
 | 
			
		||||
import { ToggleButtonWidgetComponent } from '@home/components/widget/lib/button/toggle-button-widget.component';
 | 
			
		||||
import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/chart/time-series-chart-widget.component';
 | 
			
		||||
import { StatusWidgetComponent } from '@home/components/widget/lib/indicator/status-widget.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations:
 | 
			
		||||
@ -138,7 +139,8 @@ import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/char
 | 
			
		||||
      PowerButtonWidgetComponent,
 | 
			
		||||
      SliderWidgetComponent,
 | 
			
		||||
      ToggleButtonWidgetComponent,
 | 
			
		||||
      TimeSeriesChartWidgetComponent
 | 
			
		||||
      TimeSeriesChartWidgetComponent,
 | 
			
		||||
      StatusWidgetComponent
 | 
			
		||||
    ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
@ -195,7 +197,8 @@ import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/char
 | 
			
		||||
        PowerButtonWidgetComponent,
 | 
			
		||||
        SliderWidgetComponent,
 | 
			
		||||
        ToggleButtonWidgetComponent,
 | 
			
		||||
        TimeSeriesChartWidgetComponent
 | 
			
		||||
        TimeSeriesChartWidgetComponent,
 | 
			
		||||
        StatusWidgetComponent
 | 
			
		||||
    ],
 | 
			
		||||
  providers: [
 | 
			
		||||
    {provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8263
									
								
								ui-ngx/src/assets/locale/locale.constant-ar_AR.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8263
									
								
								ui-ngx/src/assets/locale/locale.constant-ar_AR.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -5461,6 +5461,25 @@
 | 
			
		||||
            "signal-strength-card-style": "Signal strength card style",
 | 
			
		||||
            "no-signal-rssi-value": "\"No signal\" rssi value"
 | 
			
		||||
        },
 | 
			
		||||
        "status-widget": {
 | 
			
		||||
            "behavior": "Behavior",
 | 
			
		||||
            "layout": "Layout",
 | 
			
		||||
            "layout-default": "Default",
 | 
			
		||||
            "layout-center": "Center",
 | 
			
		||||
            "layout-icon": "Icon",
 | 
			
		||||
            "on": "On",
 | 
			
		||||
            "off": "Off",
 | 
			
		||||
            "label": "Label",
 | 
			
		||||
            "status": "Status",
 | 
			
		||||
            "icon": "Icon",
 | 
			
		||||
            "color-palette": "Color palette",
 | 
			
		||||
            "disabled-color-palette": "Disabled color palette",
 | 
			
		||||
            "primary": "Primary",
 | 
			
		||||
            "primary-color-hint": "Color of icon and label",
 | 
			
		||||
            "secondary": "Secondary",
 | 
			
		||||
            "secondary-color-hint": "Color of status",
 | 
			
		||||
            "background": "Background"
 | 
			
		||||
        },
 | 
			
		||||
        "chart": {
 | 
			
		||||
            "common-settings": "Common settings",
 | 
			
		||||
            "enable-stacking-mode": "Enable stacking mode",
 | 
			
		||||
@ -7262,6 +7281,7 @@
 | 
			
		||||
    "language": {
 | 
			
		||||
        "language": "Language",
 | 
			
		||||
        "locales": {
 | 
			
		||||
            "ar_AR": "اَلْعَرَبِيَّةُ",
 | 
			
		||||
            "ca_ES": "Catalan",
 | 
			
		||||
            "cs_CZ": "Česky",
 | 
			
		||||
            "da_DK": "Dansk",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								ui-ngx/src/assets/widget/status-widget/center-layout.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								ui-ngx/src/assets/widget/status-widget/center-layout.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 After Width: | Height: | Size: 19 KiB  | 
							
								
								
									
										20
									
								
								ui-ngx/src/assets/widget/status-widget/default-layout.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								ui-ngx/src/assets/widget/status-widget/default-layout.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 After Width: | Height: | Size: 19 KiB  | 
							
								
								
									
										18
									
								
								ui-ngx/src/assets/widget/status-widget/icon-layout.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								ui-ngx/src/assets/widget/status-widget/icon-layout.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
<svg width="141" height="140" viewBox="0 0 141 140" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
<g filter="url(#filter0_d_5952_143941)">
 | 
			
		||||
<rect x="8.66675" y="4" width="124" height="124" rx="4" fill="#3F52DD"/>
 | 
			
		||||
<path d="M83.0387 55.8776H58.2949V53.6282H83.0387V55.8776ZM59.4196 77.2472H63.9185C63.9185 73.8731 61.6691 71.6236 61.6691 71.6236C68.4174 67.1248 69.5421 57.0023 69.5421 57.0023H59.4196V77.2472ZM81.914 57.0023H71.7915C71.7915 57.0023 72.9162 67.1248 79.6645 71.6236C79.6645 71.6236 77.4151 73.8731 77.4151 77.2472H81.914V57.0023Z" fill="white"/>
 | 
			
		||||
</g>
 | 
			
		||||
<defs>
 | 
			
		||||
<filter id="filter0_d_5952_143941" x="0.666748" y="0" width="140" height="140" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
 | 
			
		||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
 | 
			
		||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
 | 
			
		||||
<feOffset dy="4"/>
 | 
			
		||||
<feGaussianBlur stdDeviation="4"/>
 | 
			
		||||
<feComposite in2="hardAlpha" operator="out"/>
 | 
			
		||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
 | 
			
		||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5952_143941"/>
 | 
			
		||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5952_143941" result="shape"/>
 | 
			
		||||
</filter>
 | 
			
		||||
</defs>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.3 KiB  | 
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user