Commit c2bd4117e440da4c1a2ff982e31ae7ae45e23ef2
Merge branch 'master' of https://github.com/thingsboard/thingsboard
Showing
18 changed files
with
210 additions
and
107 deletions
1 | +/** | |
2 | + * Copyright © 2016-2020 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.queue.util; | |
17 | + | |
18 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | |
19 | + | |
20 | +import java.lang.annotation.Retention; | |
21 | +import java.lang.annotation.RetentionPolicy; | |
22 | + | |
23 | +@Retention(RetentionPolicy.RUNTIME) | |
24 | +@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") | |
25 | +public @interface TbTransportComponent { | |
26 | +} | ... | ... |
... | ... | @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; |
20 | 20 | import org.thingsboard.server.common.data.id.DeviceProfileId; |
21 | 21 | import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; |
22 | 22 | import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; |
23 | +import org.thingsboard.server.common.transport.limits.TransportRateLimitType; | |
23 | 24 | import org.thingsboard.server.gen.transport.TransportProtos; |
24 | 25 | import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; |
25 | 26 | import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; |
... | ... | @@ -69,6 +70,8 @@ public interface TransportService { |
69 | 70 | |
70 | 71 | boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback); |
71 | 72 | |
73 | + boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints, TransportRateLimitType... limits); | |
74 | + | |
72 | 75 | void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback); |
73 | 76 | |
74 | 77 | void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback); | ... | ... |
... | ... | @@ -23,11 +23,13 @@ import org.thingsboard.server.common.data.id.DeviceId; |
23 | 23 | import org.thingsboard.server.common.data.id.TenantId; |
24 | 24 | import org.thingsboard.server.common.transport.TransportTenantProfileCache; |
25 | 25 | import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; |
26 | +import org.thingsboard.server.queue.util.TbTransportComponent; | |
26 | 27 | |
27 | 28 | import java.util.concurrent.ConcurrentHashMap; |
28 | 29 | import java.util.concurrent.ConcurrentMap; |
29 | 30 | |
30 | 31 | @Service |
32 | +@TbTransportComponent | |
31 | 33 | @Slf4j |
32 | 34 | public class DefaultTransportRateLimitService implements TransportRateLimitService { |
33 | 35 | |
... | ... | @@ -43,23 +45,21 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi |
43 | 45 | } |
44 | 46 | |
45 | 47 | @Override |
46 | - public TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limitType) { | |
47 | - TransportRateLimit[] limits = perTenantLimits.get(tenantId); | |
48 | - if (limits == null) { | |
49 | - limits = fetchProfileAndInit(tenantId); | |
50 | - perTenantLimits.put(tenantId, limits); | |
51 | - } | |
52 | - return limits[limitType.ordinal()]; | |
53 | - } | |
54 | - | |
55 | - @Override | |
56 | - public TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limitType) { | |
57 | - TransportRateLimit[] limits = perDeviceLimits.get(deviceId); | |
58 | - if (limits == null) { | |
59 | - limits = fetchProfileAndInit(tenantId); | |
60 | - perDeviceLimits.put(deviceId, limits); | |
48 | + public TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits) { | |
49 | + TransportRateLimit[] tenantLimits = getTenantRateLimits(tenantId); | |
50 | + TransportRateLimit[] deviceLimits = getDeviceRateLimits(tenantId, deviceId); | |
51 | + for (TransportRateLimitType limitType : limits) { | |
52 | + TransportRateLimit rateLimit; | |
53 | + if (limitType.isTenantLevel()) { | |
54 | + rateLimit = tenantLimits[limitType.ordinal()]; | |
55 | + } else { | |
56 | + rateLimit = deviceLimits[limitType.ordinal()]; | |
57 | + } | |
58 | + if (!rateLimit.tryConsume(limitType.isMessageLevel() ? 1L : dataPoints)) { | |
59 | + return limitType; | |
60 | + } | |
61 | 61 | } |
62 | - return limits[limitType.ordinal()]; | |
62 | + return null; | |
63 | 63 | } |
64 | 64 | |
65 | 65 | @Override |
... | ... | @@ -75,7 +75,17 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi |
75 | 75 | mergeLimits(tenantId, fetchProfileAndInit(tenantId)); |
76 | 76 | } |
77 | 77 | |
78 | - public void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { | |
78 | + @Override | |
79 | + public void remove(TenantId tenantId) { | |
80 | + perTenantLimits.remove(tenantId); | |
81 | + } | |
82 | + | |
83 | + @Override | |
84 | + public void remove(DeviceId deviceId) { | |
85 | + perDeviceLimits.remove(deviceId); | |
86 | + } | |
87 | + | |
88 | + private void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { | |
79 | 89 | TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId); |
80 | 90 | if (oldRateLimits == null) { |
81 | 91 | perTenantLimits.put(tenantId, newRateLimits); |
... | ... | @@ -90,16 +100,6 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi |
90 | 100 | } |
91 | 101 | } |
92 | 102 | |
93 | - @Override | |
94 | - public void remove(TenantId tenantId) { | |
95 | - perTenantLimits.remove(tenantId); | |
96 | - } | |
97 | - | |
98 | - @Override | |
99 | - public void remove(DeviceId deviceId) { | |
100 | - perDeviceLimits.remove(deviceId); | |
101 | - } | |
102 | - | |
103 | 103 | private TransportRateLimit[] fetchProfileAndInit(TenantId tenantId) { |
104 | 104 | return perTenantLimits.computeIfAbsent(tenantId, tmp -> createTransportRateLimits(tenantProfileCache.get(tenantId))); |
105 | 105 | } |
... | ... | @@ -112,4 +112,22 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi |
112 | 112 | } |
113 | 113 | return rateLimits; |
114 | 114 | } |
115 | + | |
116 | + private TransportRateLimit[] getTenantRateLimits(TenantId tenantId) { | |
117 | + TransportRateLimit[] limits = perTenantLimits.get(tenantId); | |
118 | + if (limits == null) { | |
119 | + limits = fetchProfileAndInit(tenantId); | |
120 | + perTenantLimits.put(tenantId, limits); | |
121 | + } | |
122 | + return limits; | |
123 | + } | |
124 | + | |
125 | + private TransportRateLimit[] getDeviceRateLimits(TenantId tenantId, DeviceId deviceId) { | |
126 | + TransportRateLimit[] limits = perDeviceLimits.get(deviceId); | |
127 | + if (limits == null) { | |
128 | + limits = fetchProfileAndInit(tenantId); | |
129 | + perDeviceLimits.put(deviceId, limits); | |
130 | + } | |
131 | + return limits; | |
132 | + } | |
115 | 133 | } | ... | ... |
... | ... | @@ -21,9 +21,7 @@ import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult |
21 | 21 | |
22 | 22 | public interface TransportRateLimitService { |
23 | 23 | |
24 | - TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limit); | |
25 | - | |
26 | - TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limit); | |
24 | + TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits); | |
27 | 25 | |
28 | 26 | void update(TenantProfileUpdateResult update); |
29 | 27 | ... | ... |
... | ... | @@ -19,15 +19,29 @@ import lombok.Getter; |
19 | 19 | |
20 | 20 | public enum TransportRateLimitType { |
21 | 21 | |
22 | - TENANT_MAX_MSGS("transport.tenant.max.msg"), | |
23 | - TENANT_MAX_DATA_POINTS("transport.tenant.max.dataPoints"), | |
24 | - DEVICE_MAX_MSGS("transport.device.max.msg"), | |
25 | - DEVICE_MAX_DATA_POINTS("transport.device.max.dataPoints"); | |
22 | + TENANT_MAX_MSGS("transport.tenant.msg", true, true), | |
23 | + TENANT_TELEMETRY_MSGS("transport.tenant.telemetry", true, true), | |
24 | + TENANT_MAX_DATA_POINTS("transport.tenant.dataPoints", true, false), | |
25 | + DEVICE_MAX_MSGS("transport.device.msg", false, true), | |
26 | + DEVICE_TELEMETRY_MSGS("transport.device.telemetry", false, true), | |
27 | + DEVICE_MAX_DATA_POINTS("transport.device.dataPoints", false, false); | |
26 | 28 | |
27 | 29 | @Getter |
28 | 30 | private final String configurationKey; |
31 | + @Getter | |
32 | + private final boolean tenantLevel; | |
33 | + @Getter | |
34 | + private final boolean deviceLevel; | |
35 | + @Getter | |
36 | + private final boolean messageLevel; | |
37 | + @Getter | |
38 | + private final boolean dataPointLevel; | |
29 | 39 | |
30 | - TransportRateLimitType(String configurationKey) { | |
40 | + TransportRateLimitType(String configurationKey, boolean tenantLevel, boolean messageLevel) { | |
31 | 41 | this.configurationKey = configurationKey; |
42 | + this.tenantLevel = tenantLevel; | |
43 | + this.deviceLevel = !tenantLevel; | |
44 | + this.messageLevel = messageLevel; | |
45 | + this.dataPointLevel = !messageLevel; | |
32 | 46 | } |
33 | 47 | } | ... | ... |
... | ... | @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.DeviceProfile; |
23 | 23 | import org.thingsboard.server.common.data.id.DeviceProfileId; |
24 | 24 | import org.thingsboard.server.common.transport.TransportDeviceProfileCache; |
25 | 25 | import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; |
26 | +import org.thingsboard.server.queue.util.TbTransportComponent; | |
26 | 27 | |
27 | 28 | import java.util.Optional; |
28 | 29 | import java.util.concurrent.ConcurrentHashMap; |
... | ... | @@ -30,7 +31,7 @@ import java.util.concurrent.ConcurrentMap; |
30 | 31 | |
31 | 32 | @Slf4j |
32 | 33 | @Component |
33 | -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") | |
34 | +@TbTransportComponent | |
34 | 35 | public class DefaultTransportDeviceProfileCache implements TransportDeviceProfileCache { |
35 | 36 | |
36 | 37 | private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfiles = new ConcurrentHashMap<>(); | ... | ... |
... | ... | @@ -79,6 +79,7 @@ import org.thingsboard.server.queue.discovery.PartitionService; |
79 | 79 | import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
80 | 80 | import org.thingsboard.server.queue.provider.TbQueueProducerProvider; |
81 | 81 | import org.thingsboard.server.queue.provider.TbTransportQueueFactory; |
82 | +import org.thingsboard.server.queue.util.TbTransportComponent; | |
82 | 83 | |
83 | 84 | import javax.annotation.PostConstruct; |
84 | 85 | import javax.annotation.PreDestroy; |
... | ... | @@ -103,7 +104,7 @@ import java.util.concurrent.atomic.AtomicInteger; |
103 | 104 | */ |
104 | 105 | @Slf4j |
105 | 106 | @Service |
106 | -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") | |
107 | +@TbTransportComponent | |
107 | 108 | public class DefaultTransportService implements TransportService { |
108 | 109 | |
109 | 110 | @Value("${transport.sessions.inactivity_timeout}") |
... | ... | @@ -363,7 +364,11 @@ public class DefaultTransportService implements TransportService { |
363 | 364 | |
364 | 365 | @Override |
365 | 366 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback<Void> callback) { |
366 | - if (checkLimits(sessionInfo, msg, callback)) { | |
367 | + int dataPoints = 0; | |
368 | + for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { | |
369 | + dataPoints += tsKv.getKvCount(); | |
370 | + } | |
371 | + if (checkLimits(sessionInfo, msg, callback, dataPoints, TELEMETRY)) { | |
367 | 372 | reportActivityInternal(sessionInfo); |
368 | 373 | TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); |
369 | 374 | DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); |
... | ... | @@ -384,7 +389,7 @@ public class DefaultTransportService implements TransportService { |
384 | 389 | |
385 | 390 | @Override |
386 | 391 | public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback) { |
387 | - if (checkLimits(sessionInfo, msg, callback)) { | |
392 | + if (checkLimits(sessionInfo, msg, callback, msg.getKvCount(), TELEMETRY)) { | |
388 | 393 | reportActivityInternal(sessionInfo); |
389 | 394 | TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); |
390 | 395 | DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); |
... | ... | @@ -574,37 +579,34 @@ public class DefaultTransportService implements TransportService { |
574 | 579 | sessions.remove(toSessionId(sessionInfo)); |
575 | 580 | } |
576 | 581 | |
582 | + private TransportRateLimitType[] DEFAULT = new TransportRateLimitType[]{TransportRateLimitType.TENANT_MAX_MSGS, TransportRateLimitType.DEVICE_MAX_MSGS}; | |
583 | + private TransportRateLimitType[] TELEMETRY = TransportRateLimitType.values(); | |
584 | + | |
577 | 585 | @Override |
578 | 586 | public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback) { |
587 | + return checkLimits(sessionInfo, msg, callback, 0, DEFAULT); | |
588 | + } | |
589 | + | |
590 | + @Override | |
591 | + public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints, TransportRateLimitType... limits) { | |
579 | 592 | if (log.isTraceEnabled()) { |
580 | 593 | log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); |
581 | 594 | } |
582 | 595 | TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); |
583 | - | |
584 | - TransportRateLimit tenantRateLimit = rateLimitService.getRateLimit(tenantId, TransportRateLimitType.TENANT_MAX_MSGS); | |
585 | - | |
586 | - if (!tenantRateLimit.tryConsume()) { | |
587 | - if (callback != null) { | |
588 | - callback.onError(new TbRateLimitsException(EntityType.TENANT)); | |
589 | - } | |
590 | - if (log.isTraceEnabled()) { | |
591 | - log.trace("[{}][{}] Tenant level rate limit detected: {}", toSessionId(sessionInfo), tenantId, msg); | |
592 | - } | |
593 | - return false; | |
594 | - } | |
595 | 596 | DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); |
596 | - TransportRateLimit deviceRateLimit = rateLimitService.getRateLimit(tenantId, deviceId, TransportRateLimitType.DEVICE_MAX_MSGS); | |
597 | - if (!deviceRateLimit.tryConsume()) { | |
597 | + | |
598 | + TransportRateLimitType limit = rateLimitService.checkLimits(tenantId, deviceId, 0, limits); | |
599 | + if (limit == null) { | |
600 | + return true; | |
601 | + } else { | |
598 | 602 | if (callback != null) { |
599 | - callback.onError(new TbRateLimitsException(EntityType.DEVICE)); | |
603 | + callback.onError(new TbRateLimitsException(limit.isTenantLevel() ? EntityType.TENANT : EntityType.DEVICE)); | |
600 | 604 | } |
601 | 605 | if (log.isTraceEnabled()) { |
602 | - log.trace("[{}][{}] Device level rate limit detected: {}", toSessionId(sessionInfo), deviceId, msg); | |
606 | + log.trace("[{}][{}] {} rateLimit detected: {}", toSessionId(sessionInfo), tenantId, limit, msg); | |
603 | 607 | } |
604 | 608 | return false; |
605 | 609 | } |
606 | - | |
607 | - return true; | |
608 | 610 | } |
609 | 611 | |
610 | 612 | protected void processToTransportMsg(TransportProtos.ToTransportMsg toSessionMsg) { | ... | ... |
... | ... | @@ -33,6 +33,7 @@ import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; |
33 | 33 | import org.thingsboard.server.gen.transport.TransportProtos; |
34 | 34 | import org.thingsboard.server.queue.discovery.TenantRoutingInfo; |
35 | 35 | import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; |
36 | +import org.thingsboard.server.queue.util.TbTransportComponent; | |
36 | 37 | |
37 | 38 | import java.util.Collections; |
38 | 39 | import java.util.Optional; |
... | ... | @@ -43,7 +44,7 @@ import java.util.concurrent.locks.Lock; |
43 | 44 | import java.util.concurrent.locks.ReentrantLock; |
44 | 45 | |
45 | 46 | @Component |
46 | -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") | |
47 | +@TbTransportComponent | |
47 | 48 | @Slf4j |
48 | 49 | public class DefaultTransportTenantProfileCache implements TransportTenantProfileCache { |
49 | 50 | ... | ... |
... | ... | @@ -91,6 +91,12 @@ |
91 | 91 | </mat-error> |
92 | 92 | </mat-form-field> |
93 | 93 | </div> |
94 | + <div fxLayout="column" fxLayoutAlign="flex-end start"> | |
95 | + <tb-rule-chain-autocomplete | |
96 | + labelText="device-profile.default-rule-chain" | |
97 | + formControlName="defaultRuleChainId"> | |
98 | + </tb-rule-chain-autocomplete> | |
99 | + </div> | |
94 | 100 | </div> |
95 | 101 | <mat-checkbox formControlName="gateway" style="padding-bottom: 16px;"> |
96 | 102 | {{ 'device.is-gateway' | translate }} | ... | ... |
... | ... | @@ -42,6 +42,7 @@ import { ErrorStateMatcher } from '@angular/material/core'; |
42 | 42 | import { StepperSelectionEvent } from '@angular/cdk/stepper'; |
43 | 43 | import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; |
44 | 44 | import { MediaBreakpoints } from '@shared/models/constants'; |
45 | +import { RuleChainId } from '@shared/models/id/rule-chain-id'; | |
45 | 46 | |
46 | 47 | @Component({ |
47 | 48 | selector: 'tb-device-wizard', |
... | ... | @@ -103,6 +104,7 @@ export class DeviceWizardDialogComponent extends |
103 | 104 | addProfileType: [0], |
104 | 105 | deviceProfileId: [null, Validators.required], |
105 | 106 | newDeviceProfileTitle: [{value: null, disabled: true}], |
107 | + defaultRuleChainId: [{value: null, disabled: true}], | |
106 | 108 | description: [''] |
107 | 109 | } |
108 | 110 | ); |
... | ... | @@ -114,6 +116,7 @@ export class DeviceWizardDialogComponent extends |
114 | 116 | this.deviceWizardFormGroup.get('deviceProfileId').enable(); |
115 | 117 | this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null); |
116 | 118 | this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable(); |
119 | + this.deviceWizardFormGroup.get('defaultRuleChainId').disable(); | |
117 | 120 | this.deviceWizardFormGroup.updateValueAndValidity(); |
118 | 121 | this.createProfile = false; |
119 | 122 | this.createTransportConfiguration = false; |
... | ... | @@ -122,6 +125,7 @@ export class DeviceWizardDialogComponent extends |
122 | 125 | this.deviceWizardFormGroup.get('deviceProfileId').disable(); |
123 | 126 | this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); |
124 | 127 | this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable(); |
128 | + this.deviceWizardFormGroup.get('defaultRuleChainId').enable(); | |
125 | 129 | this.deviceWizardFormGroup.updateValueAndValidity(); |
126 | 130 | this.createProfile = true; |
127 | 131 | this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value && |
... | ... | @@ -274,6 +278,9 @@ export class DeviceWizardDialogComponent extends |
274 | 278 | provisionConfiguration: deviceProvisionConfiguration |
275 | 279 | } |
276 | 280 | }; |
281 | + if (this.deviceWizardFormGroup.get('defaultRuleChainId').value) { | |
282 | + deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceWizardFormGroup.get('defaultRuleChainId').value); | |
283 | + } | |
277 | 284 | return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( |
278 | 285 | map(profile => profile.id), |
279 | 286 | tap((profileId) => { | ... | ... |
... | ... | @@ -142,6 +142,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha |
142 | 142 | tenantNamePattern = {value: null, disabled: true}; |
143 | 143 | } |
144 | 144 | const basicGroup = this.fb.group({ |
145 | + emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required], | |
145 | 146 | firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''], |
146 | 147 | lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''], |
147 | 148 | tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN], |
... | ... | @@ -151,11 +152,6 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha |
151 | 152 | alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false] |
152 | 153 | }); |
153 | 154 | |
154 | - if (MapperConfigType.GITHUB !== type) { | |
155 | - basicGroup.addControl('emailAttributeKey', | |
156 | - this.fb.control( mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required)); | |
157 | - } | |
158 | - | |
159 | 155 | this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => { |
160 | 156 | if (domain === 'CUSTOM') { |
161 | 157 | basicGroup.get('tenantNamePattern').enable(); |
... | ... | @@ -358,11 +354,15 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha |
358 | 354 | mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); |
359 | 355 | } else { |
360 | 356 | mapperConfig.removeControl('custom'); |
361 | - if (mapperConfig.get('basic')) { | |
362 | - mapperConfig.setControl('basic', this.formBasicGroup(type, predefinedValue?.basic)); | |
363 | - } else { | |
357 | + if (!mapperConfig.get('basic')) { | |
364 | 358 | mapperConfig.addControl('basic', this.formBasicGroup(type, predefinedValue?.basic)); |
365 | 359 | } |
360 | + if (type === MapperConfigType.GITHUB) { | |
361 | + mapperConfig.get('basic.emailAttributeKey').disable(); | |
362 | + mapperConfig.get('basic.emailAttributeKey').patchValue(null, {emitEvent: false}); | |
363 | + } else { | |
364 | + mapperConfig.get('basic.emailAttributeKey').enable(); | |
365 | + } | |
366 | 366 | } |
367 | 367 | } |
368 | 368 | ... | ... |
... | ... | @@ -97,10 +97,6 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte |
97 | 97 | ngOnInit() { |
98 | 98 | this.entitySelectFormGroup.get('entityType').valueChanges.subscribe( |
99 | 99 | (value) => { |
100 | - if(value === AliasEntityType.CURRENT_TENANT || value === AliasEntityType.CURRENT_USER || | |
101 | - value === AliasEntityType.CURRENT_USER_OWNER) { | |
102 | - this.modelValue.id = NULL_UUID; | |
103 | - } | |
104 | 100 | this.updateView(value, this.modelValue.id); |
105 | 101 | } |
106 | 102 | ); |
... | ... | @@ -140,20 +136,23 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte |
140 | 136 | } |
141 | 137 | |
142 | 138 | updateView(entityType: EntityType | AliasEntityType | null, entityId: string | null) { |
143 | - if (this.modelValue.entityType !== entityType || | |
144 | - this.modelValue.id !== entityId) { | |
145 | - this.modelValue = { | |
146 | - entityType, | |
147 | - id: this.modelValue.entityType !== entityType ? null : entityId | |
148 | - }; | |
149 | - if (this.modelValue.entityType && (this.modelValue.id || | |
150 | - this.modelValue.entityType === AliasEntityType.CURRENT_TENANT || | |
151 | - this.modelValue.entityType === AliasEntityType.CURRENT_USER || | |
152 | - this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER)) { | |
153 | - this.propagateChange(this.modelValue); | |
154 | - } else { | |
155 | - this.propagateChange(null); | |
156 | - } | |
139 | + if (this.modelValue.entityType !== entityType || this.modelValue.id !== entityId) { | |
140 | + this.modelValue = { | |
141 | + entityType, | |
142 | + id: this.modelValue.entityType !== entityType ? null : entityId | |
143 | + }; | |
144 | + | |
145 | + if (this.modelValue.entityType === AliasEntityType.CURRENT_TENANT | |
146 | + || this.modelValue.entityType === AliasEntityType.CURRENT_USER | |
147 | + || this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER) { | |
148 | + this.modelValue.id = NULL_UUID; | |
149 | + } | |
150 | + | |
151 | + if (this.modelValue.entityType && this.modelValue.id) { | |
152 | + this.propagateChange(this.modelValue); | |
153 | + } else { | |
154 | + this.propagateChange(null); | |
155 | + } | |
157 | 156 | } |
158 | 157 | } |
159 | 158 | } | ... | ... |
... | ... | @@ -36,7 +36,6 @@ import { TranslateService } from '@ngx-translate/core'; |
36 | 36 | import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; |
37 | 37 | import { ResizeObserver } from '@juggle/resize-observer'; |
38 | 38 | import { TbEditorCompleter } from '@shared/models/ace/completion.models'; |
39 | -import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models'; | |
40 | 39 | |
41 | 40 | @Component({ |
42 | 41 | selector: 'tb-js-func', |
... | ... | @@ -64,6 +63,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, |
64 | 63 | private jsEditor: ace.Ace.Editor; |
65 | 64 | private editorsResizeCaf: CancelAnimationFrame; |
66 | 65 | private editorResize$: ResizeObserver; |
66 | + private ignoreChange = false; | |
67 | 67 | |
68 | 68 | toastTargetId = `jsFuncEditor-${guid()}`; |
69 | 69 | |
... | ... | @@ -154,8 +154,10 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, |
154 | 154 | this.jsEditor.session.setUseWrapMode(true); |
155 | 155 | this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1); |
156 | 156 | this.jsEditor.on('change', () => { |
157 | - this.cleanupJsErrors(); | |
158 | - this.updateView(); | |
157 | + if (!this.ignoreChange) { | |
158 | + this.cleanupJsErrors(); | |
159 | + this.updateView(); | |
160 | + } | |
159 | 161 | }); |
160 | 162 | if (this.editorCompleter) { |
161 | 163 | this.jsEditor.completers = [this.editorCompleter, ...(this.jsEditor.completers || [])]; |
... | ... | @@ -332,7 +334,9 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, |
332 | 334 | writeValue(value: string): void { |
333 | 335 | this.modelValue = value; |
334 | 336 | if (this.jsEditor) { |
337 | + this.ignoreChange = true; | |
335 | 338 | this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1); |
339 | + this.ignoreChange = false; | |
336 | 340 | } |
337 | 341 | } |
338 | 342 | ... | ... |
... | ... | @@ -61,6 +61,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid |
61 | 61 | private jsonEditor: ace.Ace.Editor; |
62 | 62 | private editorsResizeCaf: CancelAnimationFrame; |
63 | 63 | private editorResize$: ResizeObserver; |
64 | + private ignoreChange = false; | |
64 | 65 | |
65 | 66 | toastTargetId = `jsonContentEditor-${guid()}`; |
66 | 67 | |
... | ... | @@ -140,8 +141,13 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid |
140 | 141 | this.jsonEditor.session.setUseWrapMode(true); |
141 | 142 | this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1); |
142 | 143 | this.jsonEditor.on('change', () => { |
143 | - this.cleanupJsonErrors(); | |
144 | - this.updateView(); | |
144 | + if (!this.ignoreChange) { | |
145 | + this.cleanupJsonErrors(); | |
146 | + this.updateView(); | |
147 | + } | |
148 | + }); | |
149 | + this.jsonEditor.on('blur', () => { | |
150 | + this.contentValid = !this.validateContent || this.doValidate(true); | |
145 | 151 | }); |
146 | 152 | this.editorResize$ = new ResizeObserver(() => { |
147 | 153 | this.onAceEditorResize(); |
... | ... | @@ -210,34 +216,36 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid |
210 | 216 | this.cleanupJsonErrors(); |
211 | 217 | this.contentValid = true; |
212 | 218 | this.propagateChange(this.contentBody); |
213 | - this.contentValid = this.doValidate(); | |
219 | + this.contentValid = this.doValidate(true); | |
214 | 220 | this.propagateChange(this.contentBody); |
215 | 221 | } |
216 | 222 | } |
217 | 223 | |
218 | - private doValidate(): boolean { | |
224 | + private doValidate(showErrorToast = false): boolean { | |
219 | 225 | try { |
220 | 226 | if (this.validateContent && this.contentType === ContentType.JSON) { |
221 | 227 | JSON.parse(this.contentBody); |
222 | 228 | } |
223 | 229 | return true; |
224 | 230 | } catch (ex) { |
225 | - let errorInfo = 'Error:'; | |
226 | - if (ex.name) { | |
227 | - errorInfo += ' ' + ex.name + ':'; | |
228 | - } | |
229 | - if (ex.message) { | |
230 | - errorInfo += ' ' + ex.message; | |
231 | + if (showErrorToast) { | |
232 | + let errorInfo = 'Error:'; | |
233 | + if (ex.name) { | |
234 | + errorInfo += ' ' + ex.name + ':'; | |
235 | + } | |
236 | + if (ex.message) { | |
237 | + errorInfo += ' ' + ex.message; | |
238 | + } | |
239 | + this.store.dispatch(new ActionNotificationShow( | |
240 | + { | |
241 | + message: errorInfo, | |
242 | + type: 'error', | |
243 | + target: this.toastTargetId, | |
244 | + verticalPosition: 'bottom', | |
245 | + horizontalPosition: 'left' | |
246 | + })); | |
247 | + this.errorShowed = true; | |
231 | 248 | } |
232 | - this.store.dispatch(new ActionNotificationShow( | |
233 | - { | |
234 | - message: errorInfo, | |
235 | - type: 'error', | |
236 | - target: this.toastTargetId, | |
237 | - verticalPosition: 'bottom', | |
238 | - horizontalPosition: 'left' | |
239 | - })); | |
240 | - this.errorShowed = true; | |
241 | 249 | return false; |
242 | 250 | } |
243 | 251 | } |
... | ... | @@ -256,8 +264,9 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid |
256 | 264 | this.contentBody = value; |
257 | 265 | this.contentValid = true; |
258 | 266 | if (this.jsonEditor) { |
267 | + this.ignoreChange = true; | |
259 | 268 | this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1); |
260 | - // this.jsonEditor. | |
269 | + this.ignoreChange = false; | |
261 | 270 | } |
262 | 271 | } |
263 | 272 | ... | ... |