Browse Source

Merge pull request #14366 from thingsboard/rc

Rc
pull/14369/head
Viacheslav Klimov 11 months ago
committed by GitHub
parent
commit
91373fb016
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      application/src/main/resources/thingsboard.yml
  2. 63
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java
  3. 2
      transport/snmp/src/main/resources/tb-snmp-transport.yml
  4. 17
      ui-ngx/src/app/modules/home/components/github-badge/github-badge.component.html
  5. 23
      ui-ngx/src/app/modules/home/components/github-badge/github-badge.component.scss
  6. 57
      ui-ngx/src/app/modules/home/components/github-badge/github-badge.component.ts
  7. 1
      ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html
  8. 16
      ui-ngx/src/app/shared/components/entity/entity-list.component.ts
  9. 3
      ui-ngx/src/styles.scss

2
application/src/main/resources/thingsboard.yml

@ -1318,6 +1318,8 @@ transport:
ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}" ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}"
# Thread pool size for scheduler that executes device querying tasks # Thread pool size for scheduler that executes device querying tasks
scheduler_thread_pool_size: "${SNMP_SCHEDULER_THREAD_POOL_SIZE:4}" scheduler_thread_pool_size: "${SNMP_SCHEDULER_THREAD_POOL_SIZE:4}"
# Maximum number of retry attempts for a single SNMP devices batch during bootstrap.
batch_retries: "${SNMP_BOOTSTRAP_RETRIES:8}"
stats: stats:
# Enable/Disable the collection of transport statistics # Enable/Disable the collection of transport statistics
enabled: "${TB_TRANSPORT_STATS_ENABLED:true}" enabled: "${TB_TRANSPORT_STATS_ENABLED:true}"

63
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java

@ -15,11 +15,14 @@
*/ */
package org.thingsboard.server.transport.snmp; package org.thingsboard.server.transport.snmp;
import jakarta.annotation.PreDestroy;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
@ -53,9 +56,12 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@TbSnmpTransportComponent @TbSnmpTransportComponent
@Component @Component
@ -72,30 +78,52 @@ public class SnmpTransportContext extends TransportContext {
private final SnmpAuthService snmpAuthService; private final SnmpAuthService snmpAuthService;
private final Map<DeviceId, DeviceSessionContext> sessions = new ConcurrentHashMap<>(); private final Map<DeviceId, DeviceSessionContext> sessions = new ConcurrentHashMap<>();
private final Collection<DeviceId> allSnmpDevicesIds = new ConcurrentLinkedDeque<>(); private final Set<DeviceId> allSnmpDevicesIds = ConcurrentHashMap.newKeySet();
private final ExecutorService snmpExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("snmp-bootstrap"));
@Value("${transport.snmp.batch_retries}")
private int snmpBootstrapBatchRetries;
@AfterStartUp(order = AfterStartUp.AFTER_TRANSPORT_SERVICE) @AfterStartUp(order = AfterStartUp.AFTER_TRANSPORT_SERVICE)
public void fetchDevicesAndEstablishSessions() { public void fetchDevicesAndEstablishSessions() {
log.info("Initializing SNMP devices sessions"); snmpExecutor.execute(this::bootstrapWithRetries);
}
private void bootstrapWithRetries() {
log.info("Initializing SNMP devices sessions");
int batchIndex = 0; int batchIndex = 0;
int batchSize = 512; int batchSize = 512;
boolean nextBatchExists = true; boolean nextBatchExists = true;
while (nextBatchExists) { while (nextBatchExists) {
TransportProtos.GetSnmpDevicesResponseMsg snmpDevicesResponse = protoEntityService.getSnmpDevicesIds(batchIndex, batchSize); for (int attempt = 1; attempt <= snmpBootstrapBatchRetries; attempt++) {
snmpDevicesResponse.getIdsList().stream() try {
.map(id -> new DeviceId(UUID.fromString(id))) TransportProtos.GetSnmpDevicesResponseMsg snmpDevicesResponse = protoEntityService.getSnmpDevicesIds(batchIndex, batchSize);
.peek(allSnmpDevicesIds::add) snmpDevicesResponse.getIdsList().stream()
.filter(deviceId -> balancingService.isManagedByCurrentTransport(deviceId.getId())) .map(id -> new DeviceId(UUID.fromString(id)))
.map(protoEntityService::getDeviceById) .peek(allSnmpDevicesIds::add)
.forEach(device -> getExecutor().execute(() -> establishDeviceSession(device))); .filter(deviceId -> balancingService.isManagedByCurrentTransport(deviceId.getId()))
.map(protoEntityService::getDeviceById)
nextBatchExists = snmpDevicesResponse.getHasNextPage(); .forEach(device -> getExecutor().execute(() -> establishDeviceSession(device)));
batchIndex++; nextBatchExists = snmpDevicesResponse.getHasNextPage();
batchIndex++;
break;
} catch (Exception e) {
if (attempt >= snmpBootstrapBatchRetries) {
log.error("SNMP bootstrap: batch {} failed after {} attempts.", batchIndex, attempt, e);
return;
}
log.warn("SNMP bootstrap: batch {} attempt {}/{} failed.", batchIndex, attempt, snmpBootstrapBatchRetries, e);
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException ex) {
log.warn("SNMP bootstrap interrupted. Stopping bootstrap task.");
return;
}
}
}
} }
log.debug("Found SNMP devices ids: {}", allSnmpDevicesIds);
log.debug("Found all SNMP devices ids: {}", allSnmpDevicesIds);
} }
private void establishDeviceSession(Device device) { private void establishDeviceSession(Device device) {
@ -300,4 +328,9 @@ public class SnmpTransportContext extends TransportContext {
return sessions.values(); return sessions.values();
} }
@PreDestroy
public void destroy() {
snmpExecutor.shutdown();
}
} }

2
transport/snmp/src/main/resources/tb-snmp-transport.yml

@ -151,6 +151,8 @@ transport:
ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}" ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}"
# Thread pool size for scheduler that executes device querying tasks # Thread pool size for scheduler that executes device querying tasks
scheduler_thread_pool_size: "${SNMP_SCHEDULER_THREAD_POOL_SIZE:4}" scheduler_thread_pool_size: "${SNMP_SCHEDULER_THREAD_POOL_SIZE:4}"
# Maximum number of retry attempts for a single SNMP devices batch during bootstrap.
batch_retries: "${SNMP_BOOTSTRAP_RETRIES:8}"
sessions: sessions:
# Session inactivity timeout is a global configuration parameter that defines how long the device transport session will be opened after the last message arrives from the device. # Session inactivity timeout is a global configuration parameter that defines how long the device transport session will be opened after the last message arrives from the device.
# The parameter value is in milliseconds. # The parameter value is in milliseconds.

17
ui-ngx/src/app/modules/home/components/github-badge/github-badge.component.html

@ -17,10 +17,19 @@
--> -->
@if (githubStar > 0) { @if (githubStar > 0) {
<section class="flex size-full items-center gap-4"> <section class="flex size-full items-center gap-4">
<a mat-stroked-button href="https://github.com/thingsboard/thingsboard" target="_blank"> <div class="group relative">
<tb-icon class="tb-mat-20" matButtonIcon>mdi:github</tb-icon> <a mat-stroked-button href="https://github.com/thingsboard/thingsboard" target="_blank">
<div class="button-label flex gap-1">GitHub<tb-icon class="tb-mat-14 !mr-0">star</tb-icon>{{ githubStar | number }}</div> <tb-icon class="tb-mat-20" matButtonIcon>mdi:github</tb-icon>
</a> <div class="button-label flex items-center gap-2">Star<mat-divider vertical class="h-full"></mat-divider>{{ githubStar | number }}</div>
</a>
<button
mat-icon-button
(click)="hideGithubStar($event)"
type="button"
class="tb-mat-16 absolute -right-2 -top-2 opacity-0 transition-opacity duration-150 ease-in-out group-hover:opacity-100">
<mat-icon class="scale-75">close</mat-icon>
</button>
</div>
<mat-divider vertical class="h-full"></mat-divider> <mat-divider vertical class="h-full"></mat-divider>
</section> </section>
} }

23
ui-ngx/src/app/modules/home/components/github-badge/github-badge.component.scss

@ -20,16 +20,31 @@
--mat-outlined-button-horizontal-padding: 8px; --mat-outlined-button-horizontal-padding: 8px;
--mdc-outlined-button-container-height: 30px; --mdc-outlined-button-container-height: 30px;
--mdc-outlined-button-label-text-size: 12px; --mdc-outlined-button-label-text-size: 12px;
--mat-outlined-button-hover-state-layer-opacity: 0;
--mat-icon-button-hover-state-layer-opacity: 0;
.mdc-button { .group {
background-color: rgba(255, 255, 255, 0.12); .mdc-button {
background-color: rgba(255, 255, 255, 0.12);
}
&:hover { &:hover {
background-color: rgba(255, 255, 255, 0.20); .mdc-button {
background-color: rgba(255, 255, 255, 0.20);
}
}
.mdc-icon-button {
background-color: #162C41;
&:hover {
background-color: #DD2C00;
}
} }
} }
.button-label { .button-label {
margin-top: 1px; height: 28px;
--mat-divider-color: var(--mdc-outlined-button-label-text-color);
} }
} }

57
ui-ngx/src/app/modules/home/components/github-badge/github-badge.component.ts

@ -14,21 +14,66 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component } from '@angular/core'; import { Component, OnDestroy } from '@angular/core';
import { GitHubService } from '@core/http/git-hub.service'; import { GitHubService } from '@core/http/git-hub.service';
import { Store } from '@ngrx/store';
import { selectAuthUser, selectIsAuthenticated } from '@core/auth/auth.selectors';
import { distinctUntilChanged, filter, map, switchMap, take, takeUntil } from 'rxjs/operators';
import { Authority } from '@shared/models/authority.enum';
import { AppState } from '@core/core.state';
import { LocalStorageService } from '@core/local-storage/local-storage.service';
import { Subject } from 'rxjs';
const SETTINGS_KEY = 'HIDE_GITHUB_STAR_BUTTON';
@Component({ @Component({
selector: 'tb-github-badge', selector: 'tb-github-badge',
templateUrl: './github-badge.component.html', templateUrl: './github-badge.component.html',
styleUrl: './github-badge.component.scss' styleUrl: './github-badge.component.scss'
}) })
export class GithubBadgeComponent { export class GithubBadgeComponent implements OnDestroy {
githubStar = 0; githubStar = 0;
constructor(private gitHubService: GitHubService) { private stopWatch$ = new Subject<void>();
this.gitHubService.getGitHubStar().subscribe(star => {
this.githubStar = star; constructor(private gitHubService: GitHubService,
}); private localStorageService: LocalStorageService,
private store: Store<AppState>,) {
const hide = this.localStorageService.getItem(SETTINGS_KEY) ?? false;
if (!hide) {
this.store.select(selectIsAuthenticated).pipe(
filter((data) => data),
switchMap(() => this.store.select(selectAuthUser).pipe(take(1))),
map((authUser) => {
return [Authority.TENANT_ADMIN, Authority.SYS_ADMIN].includes(authUser?.authority ?? Authority.ANONYMOUS)
}),
distinctUntilChanged(),
takeUntil(this.stopWatch$),
).subscribe(value => {
if (value) {
this.gitHubService.getGitHubStar().subscribe(star => {
this.githubStar = star;
});
} else {
this.githubStar = 0
}
});
}
}
hideGithubStar($event: Event) {
$event?.stopPropagation();
this.localStorageService.setItem(SETTINGS_KEY, true);
this.githubStar = 0;
this.stopWatch$.next();
this.stopWatch$.complete();
}
ngOnDestroy() {
this.stopWatch$.next();
this.stopWatch$.complete();
} }
} }

1
ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html

@ -92,6 +92,7 @@
<ng-container *ngSwitchCase="notificationTargetConfigType.USER_LIST"> <ng-container *ngSwitchCase="notificationTargetConfigType.USER_LIST">
<tb-entity-list <tb-entity-list
required required
syncIdsWithDB
formControlName="usersIds" formControlName="usersIds"
[entityType]="entityType.USER" [entityType]="entityType.USER"
labelText="{{ 'user.user-list' | translate }}" labelText="{{ 'user.user-list' | translate }}"

16
ui-ngx/src/app/shared/components/entity/entity-list.component.ts

@ -213,16 +213,18 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, OnChan
this.searchText = ''; this.searchText = '';
if (value != null && value.length > 0) { if (value != null && value.length > 0) {
this.modelValue = [...value]; this.modelValue = [...value];
this.entityService.getEntities(this.entityType, value).subscribe( this.entityService.getEntities(this.entityType, value)
(entities) => { .subscribe(resolvedEntities => {
this.entities = entities; this.entities = resolvedEntities;
this.entityListFormGroup.get('entities').setValue(this.entities); this.entityListFormGroup.get('entities').setValue(this.entities);
if (this.syncIdsWithDB && this.modelValue.length !== entities.length) { if (this.syncIdsWithDB && this.modelValue.length !== this.entities.length) {
this.modelValue = entities.map(entity => entity.id.id); this.modelValue = this.entities.map(entity => entity.id.id);
if (!this.modelValue.length) {
this.modelValue = null;
}
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
} }
} });
);
} else { } else {
this.entities = []; this.entities = [];
this.entityListFormGroup.get('entities').setValue(this.entities); this.entityListFormGroup.get('entities').setValue(this.entities);

3
ui-ngx/src/styles.scss

@ -902,9 +902,6 @@ pre.tb-highlight {
&.tb-mat-12 { &.tb-mat-12 {
@include tb-mat-icon-size(12); @include tb-mat-icon-size(12);
} }
&.tb-mat-14 {
@include tb-mat-icon-size(14);
}
&.tb-mat-16 { &.tb-mat-16 {
@include tb-mat-icon-size(16); @include tb-mat-icon-size(16);
} }

Loading…
Cancel
Save