Merge branch 'rc' of github.com:thingsboard/thingsboard into cf-fixes

This commit is contained in:
IrynaMatveieva 2025-03-19 15:08:33 +02:00
commit 5ee6d41f77
23 changed files with 147 additions and 76 deletions

View File

@ -30,13 +30,12 @@ import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgHeaders;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.state.KafkaQueueStateService;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
import org.thingsboard.server.queue.common.state.KafkaQueueStateService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.queue.kafka.TbKafkaProducerTemplate;
@ -59,7 +58,6 @@ public class KafkaCalculatedFieldStateService extends AbstractCalculatedFieldSta
private final TbRuleEngineQueueFactory queueFactory;
private final PartitionService partitionService;
private final TbQueueAdmin queueAdmin;
@Value("${queue.calculated_fields.poll_interval:25}")
private long pollInterval;
@ -94,7 +92,7 @@ public class KafkaCalculatedFieldStateService extends AbstractCalculatedFieldSta
}
})
.consumerCreator((config, partitionId) -> queueFactory.createCalculatedFieldStateConsumer())
.queueAdmin(queueAdmin)
.queueAdmin(queueFactory.getCalculatedFieldQueueAdmin())
.consumerExecutor(eventConsumer.getConsumerExecutor())
.scheduler(eventConsumer.getScheduler())
.taskExecutor(eventConsumer.getTaskExecutor())

View File

@ -42,7 +42,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldLinke
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldNotificationMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
@ -81,7 +80,6 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
private long packProcessingTimeout;
private final TbRuleEngineQueueFactory queueFactory;
private final TbQueueAdmin queueAdmin;
private final CalculatedFieldStateService stateService;
public DefaultTbCalculatedFieldConsumerService(TbRuleEngineQueueFactory tbQueueFactory,
@ -94,12 +92,10 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
ApplicationEventPublisher eventPublisher,
JwtSettingsService jwtSettingsService,
CalculatedFieldCache calculatedFieldCache,
TbQueueAdmin queueAdmin,
CalculatedFieldStateService stateService) {
super(actorContext, tenantProfileCache, deviceProfileCache, assetProfileCache, calculatedFieldCache, apiUsageStateService, partitionService,
eventPublisher, jwtSettingsService);
this.queueFactory = tbQueueFactory;
this.queueAdmin = queueAdmin;
this.stateService = stateService;
}
@ -114,7 +110,7 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
.pollInterval(pollInterval)
.msgPackProcessor(this::processMsgs)
.consumerCreator((config, partitionId) -> queueFactory.createToCalculatedFieldMsgConsumer())
.queueAdmin(queueAdmin)
.queueAdmin(queueFactory.getCalculatedFieldQueueAdmin())
.consumerExecutor(consumersExecutor)
.scheduler(scheduler)
.taskExecutor(mgmtExecutor)

View File

@ -121,14 +121,14 @@ public class AssetProfileEdgeTest extends AbstractEdgeTest {
Assert.assertNull(assetProfile.getDefaultRuleChainId());
Assert.assertEquals(edgeRuleChainId, assetProfile.getDefaultEdgeRuleChainId());
// delete profile
edgeImitator.expectMessageAmount(1);
// delete profile and delete relation messages
edgeImitator.expectMessageAmount(2);
doDelete("/api/assetProfile/" + assetProfile.getUuidId())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetProfileUpdateMsg);
AssetProfileUpdateMsg assetProfileUpdateMsg = (AssetProfileUpdateMsg) latestMessage;
Optional<AssetProfileUpdateMsg> assetDeleteMsgOpt = edgeImitator.findMessageByType(AssetProfileUpdateMsg.class);
Assert.assertTrue(assetDeleteMsgOpt.isPresent());
AssetProfileUpdateMsg assetProfileUpdateMsg = assetDeleteMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType());
Assert.assertEquals(assetProfile.getUuidId().getMostSignificantBits(), assetProfileUpdateMsg.getIdMSB());
Assert.assertEquals(assetProfile.getUuidId().getLeastSignificantBits(), assetProfileUpdateMsg.getIdLSB());

View File

@ -327,14 +327,14 @@ public class DeviceProfileEdgeTest extends AbstractEdgeTest {
Assert.assertNotNull(deviceProfile);
Assert.assertEquals("Device Profile On Edge", deviceProfile.getName());
// delete profile
edgeImitator.expectMessageAmount(1);
// delete profile and delete relation messages
edgeImitator.expectMessageAmount(2);
doDelete("/api/deviceProfile/" + deviceProfile.getUuidId())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg);
DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage;
Optional<DeviceProfileUpdateMsg> deviceDeleteMsgOpt = edgeImitator.findMessageByType(DeviceProfileUpdateMsg.class);
Assert.assertTrue(deviceDeleteMsgOpt.isPresent());
DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceDeleteMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType());
Assert.assertEquals(deviceProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB());
Assert.assertEquals(deviceProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB());

View File

@ -53,7 +53,6 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.EdqsEventMsg;
import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueHandler;
import org.thingsboard.server.queue.TbQueueResponseTemplate;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
@ -92,7 +91,6 @@ public class EdqsProcessor implements TbQueueHandler<TbProtoQueueMsg<ToEdqsMsg>,
private final EdqsPartitionService partitionService;
private final ConfigurableApplicationContext applicationContext;
private final EdqsStateService stateService;
private final TbQueueAdmin queueAdmin;
private PartitionedQueueConsumerManager<TbProtoQueueMsg<ToEdqsMsg>> eventConsumer;
private TbQueueResponseTemplate<TbProtoQueueMsg<ToEdqsMsg>, TbProtoQueueMsg<FromEdqsMsg>> responseTemplate;
@ -143,7 +141,7 @@ public class EdqsProcessor implements TbQueueHandler<TbProtoQueueMsg<ToEdqsMsg>,
consumer.commit();
})
.consumerCreator((config, partitionId) -> queueFactory.createEdqsMsgConsumer(EdqsQueue.EVENTS))
.queueAdmin(queueAdmin)
.queueAdmin(queueFactory.getEdqsQueueAdmin())
.consumerExecutor(consumersExecutor)
.taskExecutor(taskExecutor)
.scheduler(scheduler)

View File

@ -30,7 +30,6 @@ import org.thingsboard.server.edqs.processor.EdqsProducer;
import org.thingsboard.server.edqs.util.VersionsStore;
import org.thingsboard.server.gen.transport.TransportProtos.EdqsEventMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
import org.thingsboard.server.queue.common.consumer.QueueConsumerManager;
@ -56,7 +55,6 @@ public class KafkaEdqsStateService implements EdqsStateService {
private final EdqsConfig config;
private final EdqsPartitionService partitionService;
private final EdqsQueueFactory queueFactory;
private final TbQueueAdmin queueAdmin;
private final TopicService topicService;
@Autowired @Lazy
private EdqsProcessor edqsProcessor;
@ -92,7 +90,7 @@ public class KafkaEdqsStateService implements EdqsStateService {
consumer.commit();
})
.consumerCreator((config, partitionId) -> queueFactory.createEdqsMsgConsumer(EdqsQueue.STATE))
.queueAdmin(queueAdmin)
.queueAdmin(queueFactory.getEdqsQueueAdmin())
.consumerExecutor(eventConsumer.getConsumerExecutor())
.taskExecutor(eventConsumer.getTaskExecutor())
.scheduler(eventConsumer.getScheduler())

View File

@ -17,6 +17,7 @@ package org.thingsboard.server.queue.edqs;
import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueResponseTemplate;
@ -32,4 +33,6 @@ public interface EdqsQueueFactory {
TbQueueResponseTemplate<TbProtoQueueMsg<ToEdqsMsg>, TbProtoQueueMsg<FromEdqsMsg>> createEdqsResponseTemplate();
TbQueueAdmin getEdqsQueueAdmin();
}

View File

@ -22,6 +22,7 @@ import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueResponseTemplate;
@ -39,6 +40,7 @@ public class InMemoryEdqsQueueFactory implements EdqsQueueFactory {
private final InMemoryStorage storage;
private final EdqsConfig edqsConfig;
private final StatsFactory statsFactory;
private final TbQueueAdmin queueAdmin;
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToEdqsMsg>> createEdqsMsgConsumer(EdqsQueue queue) {
@ -76,4 +78,9 @@ public class InMemoryEdqsQueueFactory implements EdqsQueueFactory {
.build();
}
@Override
public TbQueueAdmin getEdqsQueueAdmin() {
return queueAdmin;
}
}

View File

@ -22,6 +22,7 @@ import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueResponseTemplate;
@ -126,4 +127,9 @@ public class KafkaEdqsQueueFactory implements EdqsQueueFactory {
.build();
}
@Override
public TbQueueAdmin getEdqsQueueAdmin() {
return edqsEventsAdmin;
}
}

View File

@ -160,7 +160,7 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
int partition = record.partition();
Long endOffset = endOffsets.get(partition);
if (endOffset == null) {
log.warn("End offset not found for {} [{}]", record.topic(), partition);
log.debug("End offset not found for {} [{}]", record.topic(), partition);
return;
}
log.trace("[{}-{}] Got record offset {}, expected end offset: {}", record.topic(), partition, record.offset(), endOffset - 1);

View File

@ -138,6 +138,11 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
return new InMemoryTbQueueConsumer<>(storage, topicService.buildTopicName(calculatedFieldSettings.getEventTopic()));
}
@Override
public TbQueueAdmin getCalculatedFieldQueueAdmin() {
return queueAdmin;
}
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() {
return new InMemoryTbQueueProducer<>(storage, topicService.buildTopicName(calculatedFieldSettings.getEventTopic()));

View File

@ -526,6 +526,11 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
return consumerBuilder.build();
}
@Override
public TbQueueAdmin getCalculatedFieldQueueAdmin() {
return cfAdmin;
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() {
TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> requestBuilder = TbKafkaProducerTemplate.builder();

View File

@ -321,6 +321,11 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
return consumerBuilder.build();
}
@Override
public TbQueueAdmin getCalculatedFieldQueueAdmin() {
return cfAdmin;
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() {
TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> requestBuilder = TbKafkaProducerTemplate.builder();

View File

@ -29,6 +29,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateSer
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueRequestTemplate;
@ -122,6 +123,8 @@ public interface TbRuleEngineQueueFactory extends TbUsageStatsClientQueueFactory
TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgConsumer();
TbQueueAdmin getCalculatedFieldQueueAdmin();
TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer();
TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgConsumer();

View File

@ -195,7 +195,9 @@ public class BaseTimeseriesService implements TimeseriesService {
}
if (saveLatest) {
latestFutures.add(Futures.transform(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry), version -> {
if (version != null) {
edqsService.onUpdate(tenantId, ObjectType.LATEST_TS_KV, new LatestTsKv(entityId, tsKvEntry, version));
}
return version;
}, MoreExecutors.directExecutor()));
}

View File

@ -15,17 +15,15 @@
limitations under the License.
-->
<div class="flex max-w-sm flex-col gap-3 p-2">
<div class="flex w-96 flex-col gap-3 p-2">
<div class="tb-form-panel-title" translate>debug-settings.label</div>
@if (debugLimitsConfiguration) {
<div class="hint-container">
<div class="tb-form-hint tb-primary-fill tb-flex center">
@if (debugLimitsConfiguration) {
{{ 'debug-settings.hint.main-limited' | translate: { entity: entityLabel ?? ('debug-settings.entity' | translate), msg: maxMessagesCount, time: (maxTimeFrameDuration | milliSecondsToTimeString: true : true) } }}
} @else {
{{ 'debug-settings.hint.main' | translate }}
</div>
</div>
}
</div>
</div>
<div class="flex flex-col gap-3">
<mat-slide-toggle class="mat-slide" [formControl]="onFailuresControl">
<div tb-hint-tooltip-icon="{{ 'debug-settings.hint.on-failure' | translate }}">
@ -33,12 +31,12 @@
</div>
</mat-slide-toggle>
<div class="align-center flex justify-between">
<mat-slide-toggle class="mat-slide" [formControl]="debugAllControl">
<mat-slide-toggle class="mat-slide" [formControl]="debugAllControl" (change)="debugAllControl.markAsTouched()">
<div tb-hint-tooltip-icon="{{ 'debug-settings.hint.all-messages' | translate }}">
{{ 'debug-settings.all-messages' | translate: { time: (isDebugAllActive$ | async) && !allEnabled ? (allEnabledUntil | durationLeft) : (maxDebugModeDuration | milliSecondsToTimeString: true : true) } }}
{{ 'debug-settings.all-messages' | translate: { time: (isDebugAllActive$ | async) && !allEnabled && debugAllControl.untouched ? (allEnabledUntil | durationLeft) : (maxDebugModeDuration | milliSecondsToTimeString: true : true) } }}
</div>
</mat-slide-toggle>
<button mat-icon-button *ngIf="(isDebugAllActive$ | async) && !allEnabled"
<button mat-icon-button *ngIf="(isDebugAllActive$ | async) && !allEnabled && debugAllControl.untouched"
class="tb-mat-20"
matTooltip="{{ 'action.reset' | translate }}"
matTooltipPosition="above"

View File

@ -127,19 +127,23 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
actionSourceId: [this.action.actionSourceId, Validators.required],
columnIndex: [{value: this.checkColumnIndex(this.action.columnIndex), disabled: true}, Validators.required],
name: [this.action.name, [this.validateActionName(), Validators.required]],
buttonType: [isDefinedAndNotNull(this.action.buttonType) ? this.action.buttonType : WidgetHeaderActionButtonType.icon, []],
showIcon: [isDefinedAndNotNull(this.action.showIcon) ? this.action.showIcon : true, []],
buttonType: [{ value: this.action.buttonType ?? WidgetHeaderActionButtonType.icon, disabled: true}, []],
showIcon: [{ value: this.action.showIcon ?? true, disabled: true}, []],
icon: [this.action.icon, Validators.required],
buttonColor: [isDefinedAndNotNull(this.action.buttonColor) ? this.action.buttonColor : 'rgba(0, 0, 0, 0.87)', []],
buttonFillColor: [isDefinedAndNotNull(this.action.buttonFillColor) ? this.action.buttonFillColor : '#3F52DD', []],
buttonBorderColor: [isDefinedAndNotNull(this.action.buttonBorderColor) ? this.action.buttonBorderColor : '#3F52DD', []],
customButtonStyle: [isDefinedAndNotNull(this.action.customButtonStyle) ? this.action.customButtonStyle : null, []],
buttonColor: [{ value: this.action.buttonColor ?? 'rgba(0, 0, 0, 0.87)', disabled: true}, []],
buttonFillColor: [{ value: this.action.buttonFillColor ?? '#3F52DD', disabled: true}, []],
buttonBorderColor: [{ value: this.action.buttonBorderColor ?? '#3F52DD', disabled: true}, []],
customButtonStyle: [{ value: this.action.customButtonStyle ?? {}, disabled: true}, []],
useShowWidgetActionFunction: [this.action.useShowWidgetActionFunction],
showWidgetActionFunction: [this.action.showWidgetActionFunction || 'return true;'],
widgetAction: [actionDescriptorToAction(toWidgetActionDescriptor(this.action)), Validators.required]
});
this.updateShowWidgetActionForm();
if (this.widgetActionFormGroup.get('actionSourceId').value === 'headerButton') {
this.widgetActionFormGroup.get('buttonType').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonColor').enable({emitEvent: false});
this.widgetHeaderButtonValidators();
}
this.widgetActionFormGroup.get('actionSourceId').valueChanges.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe((value) => {
@ -151,6 +155,17 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
} else {
this.widgetActionFormGroup.get('columnIndex').disable();
}
if (value === 'headerButton') {
this.widgetActionFormGroup.get('buttonType').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonColor').enable({emitEvent: false});
this.widgetHeaderButtonValidators();
} else {
this.widgetActionFormGroup.get('buttonType').disable({emitEvent: false});
this.widgetActionFormGroup.get('showIcon').disable({emitEvent: false});
this.widgetActionFormGroup.get('buttonColor').disable({emitEvent: false});
this.widgetActionFormGroup.get('buttonFillColor').disable({emitEvent: false});
this.widgetActionFormGroup.get('buttonBorderColor').disable({emitEvent: false});
}
});
this.widgetActionFormGroup.get('useShowWidgetActionFunction').valueChanges.pipe(
takeUntilDestroyed(this.destroyRef)
@ -185,10 +200,12 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
this.widgetActionFormGroup.get('buttonFillColor').enable({emitEvent: false});
break;
case WidgetHeaderActionButtonType.stroked:
this.widgetActionFormGroup.get('showIcon').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonBorderColor').enable({emitEvent: false});
break;
case WidgetHeaderActionButtonType.flat:
this.widgetActionFormGroup.get('showIcon').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonFillColor').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonBorderColor').enable({emitEvent: false});
break;
case WidgetHeaderActionButtonType.miniFab:
this.widgetActionFormGroup.get('buttonFillColor').enable({emitEvent: false});

View File

@ -109,11 +109,10 @@
[style]="action.customButtonStyle"
[class.!hidden]="isEdit"
[class.mr-2]="!last"
[style.background-color]="action.buttonFillColor"
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
matTooltipPosition="above">
<tb-icon [style.color]="action.buttonColor">{{ action.icon }}</tb-icon>
<tb-icon>{{ action.icon }}</tb-icon>
</button>
}
@case (widgetHeaderActionButtonType.basic) {
@ -123,46 +122,41 @@
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
matTooltipPosition="above">
<tb-icon matButtonIcon *ngIf="action.showIcon" [style.color]="action.buttonColor">{{ action.icon }}</tb-icon>
<span [style.color]="action.buttonColor">{{ action.displayName }}</span>
<tb-icon matButtonIcon *ngIf="action.showIcon">{{ action.icon }}</tb-icon>
<span>{{ action.displayName }}</span>
</button>
}
@case (widgetHeaderActionButtonType.raised) {
<button [class.!hidden]="isEdit" mat-raised-button
[class.mr-2]="!last"
[style]="action.customButtonStyle"
[style.background-color]="action.buttonFillColor"
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
matTooltipPosition="above">
<tb-icon matButtonIcon *ngIf="action.showIcon" [style.color]="action.buttonColor">{{ action.icon }}</tb-icon>
<span [style.color]="action.buttonColor">{{ action.displayName }}</span>
<tb-icon matButtonIcon *ngIf="action.showIcon">{{ action.icon }}</tb-icon>
<span>{{ action.displayName }}</span>
</button>
}
@case (widgetHeaderActionButtonType.stroked) {
<button [class.!hidden]="isEdit" mat-stroked-button
[class.mr-2]="!last"
[style]="action.customButtonStyle"
[style.border-color]="action.buttonBorderColor"
[style.background-color]="action.buttonFillColor"
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
matTooltipPosition="above">
<tb-icon matButtonIcon *ngIf="action.showIcon" [style.color]="action.buttonColor">{{ action.icon }}</tb-icon>
<span [style.color]="action.buttonColor">{{ action.displayName }}</span>
<tb-icon matButtonIcon *ngIf="action.showIcon">{{ action.icon }}</tb-icon>
<span>{{ action.displayName }}</span>
</button>
}
@case (widgetHeaderActionButtonType.flat) {
<button [class.!hidden]="isEdit" mat-flat-button
[class.mr-2]="!last"
[style]="action.customButtonStyle"
[style.background-color]="action.buttonFillColor"
[style.border-color]="action.buttonBorderColor"
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
matTooltipPosition="above">
<tb-icon matButtonIcon *ngIf="action.showIcon" [style.color]="action.buttonColor">{{ action.icon }}</tb-icon>
<span [style.color]="action.buttonColor">{{ action.displayName }}</span>
<tb-icon matButtonIcon *ngIf="action.showIcon">{{ action.icon }}</tb-icon>
<span>{{ action.displayName }}</span>
</button>
}
@default {
@ -172,7 +166,7 @@
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
matTooltipPosition="above">
<tb-icon [style.color]="action.buttonColor">{{ action.icon }}</tb-icon>
<tb-icon>{{ action.icon }}</tb-icon>
</button>
}
}

View File

@ -44,6 +44,7 @@ import {
widgetActionSources,
WidgetActionType,
WidgetComparisonSettings,
WidgetHeaderActionButtonType,
WidgetMobileActionDescriptor,
WidgetMobileActionType,
WidgetResource,
@ -301,10 +302,13 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
buttonType: descriptor.buttonType,
showIcon: descriptor.showIcon,
icon: descriptor.icon,
buttonColor: descriptor.buttonColor,
buttonFillColor: descriptor.buttonFillColor,
buttonBorderColor: descriptor.buttonBorderColor,
customButtonStyle: descriptor.customButtonStyle,
customButtonStyle: this.headerButtonStyle(
descriptor.buttonType,
descriptor.customButtonStyle,
descriptor.buttonColor,
descriptor.buttonFillColor,
descriptor.buttonBorderColor
),
descriptor,
useShowWidgetHeaderActionFunction,
showWidgetHeaderActionFunction,
@ -359,6 +363,39 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
}
}
headerButtonStyle(buttonType: WidgetHeaderActionButtonType = WidgetHeaderActionButtonType.icon,
customButtonStyle:{[key: string]: string},
buttonColor: string = 'rgba(0,0,0,0.87)',
backgroundColor: string,
borderColor: string) {
const buttonStyle = {};
switch (buttonType) {
case WidgetHeaderActionButtonType.basic:
buttonStyle['--mdc-text-button-label-text-color'] = buttonColor;
break;
case WidgetHeaderActionButtonType.raised:
buttonStyle['--mdc-protected-button-label-text-color'] = buttonColor;
buttonStyle['--mdc-protected-button-container-color'] = backgroundColor;
break;
case WidgetHeaderActionButtonType.stroked:
buttonStyle['--mdc-outlined-button-label-text-color'] = buttonColor;
buttonStyle['--mdc-outlined-button-outline-color'] = borderColor;
break;
case WidgetHeaderActionButtonType.flat:
buttonStyle['--mdc-filled-button-label-text-color'] = buttonColor;
buttonStyle['--mdc-filled-button-container-color'] = backgroundColor;
break;
case WidgetHeaderActionButtonType.miniFab:
buttonStyle['--mat-fab-small-foreground-color'] = buttonColor;
buttonStyle['--mdc-fab-small-container-color'] = backgroundColor;
break;
default:
buttonStyle['--mat-icon-color'] = buttonColor;
break;
}
return {...buttonStyle, ...customButtonStyle};
}
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];

View File

@ -132,7 +132,7 @@ export interface WidgetHeaderAction extends IWidgetAction {
buttonColor?: string;
buttonFillColor?: string;
buttonBorderColor?: string;
customButtonStyle?: string;
customButtonStyle?: {[key: string]: string};
useShowWidgetHeaderActionFunction: boolean;
showWidgetHeaderActionFunction: CompiledTbFunction<ShowWidgetHeaderActionFunction>;
}

View File

@ -45,9 +45,9 @@
<tb-calculated-fields-table [active]="calculatedFieldsTab.isActive" [entityId]="entity.id" [entityName]="entity.name"/>
</mat-tab>
<mat-tab *ngIf="entity" #alarmRules="matTab"
label="{{'device-profile.alarm-rules-with-count' | translate:
{count: this.detailsForm.get('profileData.alarms').value?.length ? this.detailsForm.get('profileData.alarms').value.length : 0}
}}">
label="{{ this.detailsForm.get('profileData.alarms').value?.length
? ('device-profile.alarm-rules-with-count' | translate: { count: this.detailsForm.get('profileData.alarms').value.length })
: 'device-profile.alarm-rules' | translate }}">
<div class="mat-padding" [formGroup]="detailsForm" *ngIf="alarmRules.isActive">
<div formGroupName="profileData">
<tb-device-profile-alarms formControlName="alarms" [deviceProfileId]="entity.id"></tb-device-profile-alarms>

View File

@ -780,7 +780,7 @@ export interface WidgetActionDescriptor extends WidgetAction {
buttonColor?: string;
buttonFillColor?: string;
buttonBorderColor?: string;
customButtonStyle?: string;
customButtonStyle?: {[key: string]: string};
displayName?: string;
useShowWidgetActionFunction?: boolean;
showWidgetActionFunction?: TbFunction;

View File

@ -991,7 +991,7 @@
"type-sms-sent": "SMS sent"
},
"debug-settings": {
"label": "Debug configuration",
"label": "Debug Configuration",
"on-failure": "Failures only (24/7)",
"all-messages": "All messages ({{time}})",
"failures": "Failures",
@ -999,10 +999,9 @@
"rule-node": "rule node",
"calculated-field": "calculated field",
"hint": {
"main": "All node debug messages rate limited with:",
"main-limited": "All {{entity}} debug messages will be rate-limited, with a maximum of {{msg}} messages allowed per {{time}}.",
"on-failure": "Save all failure debug events without time limit.",
"all-messages": "Save all debug events during time limit."
"main-limited": "No more than {{msg}} {{entity}} debug messages per {{time}} will be recorded.",
"on-failure": "Log all debug messages.",
"all-messages": "Log error messages only. "
}
},
"calculated-fields": {