Merge branch 'master' of github.com:thingsboard/thingsboard
This commit is contained in:
commit
409fb1f2bc
@ -61,6 +61,7 @@ import org.thingsboard.server.service.telemetry.InternalTelemetryService;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -73,6 +74,7 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@ -347,16 +349,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
myTenantStates.values().forEach(state -> {
|
||||
if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) {
|
||||
if ((state.getNextCycleTs() < now) && (now - state.getNextCycleTs() < TimeUnit.HOURS.toMillis(1))) {
|
||||
TenantId tenantId = state.getTenantId();
|
||||
state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth());
|
||||
ToUsageStatsServiceMsg.Builder msg = ToUsageStatsServiceMsg.newBuilder();
|
||||
msg.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
|
||||
msg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
|
||||
for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) {
|
||||
msg.addValues(UsageStatsKVProto.newBuilder().setKey(key.name()).setValue(0).build());
|
||||
}
|
||||
process(new TbProtoQueueMsg<>(UUID.randomUUID(), msg.build()), TbCallback.EMPTY);
|
||||
saveNewCounts(state, Arrays.asList(ApiUsageRecordKey.values()));
|
||||
updateTenantState(state, tenantProfileCache.get(tenantId));
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
@ -364,6 +361,14 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
|
||||
}
|
||||
}
|
||||
|
||||
private void saveNewCounts(TenantApiUsageState state, List<ApiUsageRecordKey> keys) {
|
||||
List<TsKvEntry> counts = keys.stream()
|
||||
.map(key -> new BasicTsKvEntry(state.getCurrentCycleTs(), new LongDataEntry(key.getApiCountKey(), 0L)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), counts, VOID_CALLBACK);
|
||||
}
|
||||
|
||||
private TenantApiUsageState getOrFetchState(TenantId tenantId) {
|
||||
TenantApiUsageState tenantState = myTenantStates.get(tenantId);
|
||||
if (tenantState == null) {
|
||||
@ -377,6 +382,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
|
||||
}
|
||||
TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
|
||||
tenantState = new TenantApiUsageState(tenantProfile, dbStateEntity);
|
||||
List<ApiUsageRecordKey> newCounts = new ArrayList<>();
|
||||
try {
|
||||
List<TsKvEntry> dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getId()).get();
|
||||
for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) {
|
||||
@ -385,7 +391,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
|
||||
for (TsKvEntry tsKvEntry : dbValues) {
|
||||
if (tsKvEntry.getKey().equals(key.getApiCountKey())) {
|
||||
cycleEntryFound = true;
|
||||
tenantState.put(key, tsKvEntry.getTs() == tenantState.getCurrentCycleTs() ? tsKvEntry.getLongValue().get() : 0L);
|
||||
|
||||
boolean oldCount = tsKvEntry.getTs() == tenantState.getCurrentCycleTs();
|
||||
tenantState.put(key, oldCount ? tsKvEntry.getLongValue().get() : 0L);
|
||||
|
||||
if (!oldCount) {
|
||||
newCounts.add(key);
|
||||
}
|
||||
} else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) {
|
||||
hourlyEntryFound = true;
|
||||
tenantState.putHourly(key, tsKvEntry.getTs() == tenantState.getCurrentHourTs() ? tsKvEntry.getLongValue().get() : 0L);
|
||||
@ -397,6 +409,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
|
||||
}
|
||||
log.debug("[{}] Initialized state: {}", tenantId, dbStateEntity);
|
||||
myTenantStates.put(tenantId, tenantState);
|
||||
saveNewCounts(tenantState, newCounts);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e);
|
||||
}
|
||||
|
||||
@ -17,22 +17,19 @@ package org.thingsboard.server.common.msg.tools;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.TemporalAdjuster;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.time.temporal.TemporalUnit;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import static java.time.ZoneOffset.UTC;
|
||||
import static java.time.temporal.ChronoField.DAY_OF_MONTH;
|
||||
import static java.time.temporal.ChronoUnit.MONTHS;
|
||||
|
||||
public class SchedulerUtils {
|
||||
|
||||
private final static ZoneId UTC = ZoneId.of("UTC");
|
||||
private static final ConcurrentMap<String, ZoneId> tzMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static ZoneId getZoneId(String tz) {
|
||||
@ -44,7 +41,7 @@ public class SchedulerUtils {
|
||||
}
|
||||
|
||||
public static long getStartOfCurrentHour(ZoneId zoneId) {
|
||||
return LocalDateTime.now(ZoneOffset.UTC).atZone(zoneId).truncatedTo(ChronoUnit.HOURS).toInstant().toEpochMilli();
|
||||
return LocalDateTime.now(UTC).atZone(zoneId).truncatedTo(ChronoUnit.HOURS).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
public static long getStartOfCurrentMonth() {
|
||||
@ -52,7 +49,7 @@ public class SchedulerUtils {
|
||||
}
|
||||
|
||||
public static long getStartOfCurrentMonth(ZoneId zoneId) {
|
||||
return LocalDate.now().withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli();
|
||||
return LocalDate.now(UTC).withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
public static long getStartOfNextMonth() {
|
||||
@ -60,7 +57,7 @@ public class SchedulerUtils {
|
||||
}
|
||||
|
||||
public static long getStartOfNextMonth(ZoneId zoneId) {
|
||||
return LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli();
|
||||
return LocalDate.now(UTC).with(TemporalAdjusters.firstDayOfNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
public static long getStartOfNextNextMonth() {
|
||||
@ -68,7 +65,7 @@ public class SchedulerUtils {
|
||||
}
|
||||
|
||||
public static long getStartOfNextNextMonth(ZoneId zoneId) {
|
||||
return LocalDate.now().with(firstDayOfNextNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli();
|
||||
return LocalDate.now(UTC).with(firstDayOfNextNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
public static TemporalAdjuster firstDayOfNextNextMonth() {
|
||||
|
||||
@ -17,4 +17,4 @@
|
||||
-->
|
||||
<!--The content below is only a placeholder and can be replaced.-->
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
<router-outlet (activate)="onActivateComponent($event)"></router-outlet>
|
||||
|
||||
@ -62,7 +62,7 @@ export class AppComponent implements OnInit {
|
||||
this.matIconRegistry.addSvgIconLiteral(
|
||||
'alpha-e-circle-outline',
|
||||
this.domSanitizer.bypassSecurityTrustHtml(
|
||||
'<svg viewBox="0 0 24 24"><path d="M9,7H15V9H11V11H15V13H11V15H15V17H9V7M12,2A10,10 0 0,'+
|
||||
'<svg viewBox="0 0 24 24"><path d="M9,7H15V9H11V11H15V13H11V15H15V17H9V7M12,2A10,10 0 0,' +
|
||||
'1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 ' +
|
||||
'0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" /></svg>'
|
||||
)
|
||||
@ -124,5 +124,11 @@ export class AppComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
onActivateComponent($event: any) {
|
||||
const loadingElement = $('div#tb-loading-spinner');
|
||||
if (loadingElement.length) {
|
||||
loadingElement.remove();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -198,7 +198,7 @@ export class WidgetSubscriptionContext {
|
||||
export type SubscriptionMessageSeverity = 'info' | 'warn' | 'error' | 'success';
|
||||
|
||||
export interface SubscriptionMessage {
|
||||
severity: SubscriptionMessageSeverity,
|
||||
severity: SubscriptionMessageSeverity;
|
||||
message: string;
|
||||
}
|
||||
|
||||
|
||||
@ -90,7 +90,7 @@
|
||||
matTooltipPosition="above"
|
||||
class="mat-subheading-2 title">
|
||||
<mat-icon *ngIf="widget.showTitleIcon" [ngStyle]="widget.titleIconStyle">{{widget.titleIcon}}</mat-icon>
|
||||
{{widget.title}}
|
||||
{{widget.customTranslatedTitle}}
|
||||
</span>
|
||||
<tb-timewindow *ngIf="widget.hasTimewindow"
|
||||
#timewindowComponent
|
||||
|
||||
@ -54,6 +54,7 @@ import { MatMenuTrigger } from '@angular/material/menu';
|
||||
import { SafeStyle } from '@angular/platform-browser';
|
||||
import { distinct } from 'rxjs/operators';
|
||||
import { ResizeObserver } from '@juggle/resize-observer';
|
||||
import { UtilsService } from '@core/services/utils.service';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-dashboard',
|
||||
@ -168,6 +169,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
|
||||
private gridsterResize$: ResizeObserver;
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
public utils: UtilsService,
|
||||
private timeService: TimeService,
|
||||
private dialogService: DialogService,
|
||||
private breakpointObserver: BreakpointObserver,
|
||||
|
||||
@ -17,28 +17,28 @@
|
||||
-->
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="filterPredicateValueFormGroup">
|
||||
<div fxFlex fxLayout="column" [fxShow]="!dynamicMode">
|
||||
<div fxFlex fxLayout="column" [ngSwitch]="valueType">
|
||||
<div fxLayout="column" [ngSwitch]="valueType">
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.STRING">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
|
||||
<mat-label></mat-label>
|
||||
<input matInput formControlName="defaultValue" placeholder="{{'filter.value' | translate}}">
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
|
||||
<mat-label></mat-label>
|
||||
<input required type="number" matInput formControlName="defaultValue"
|
||||
placeholder="{{'filter.value' | translate}}">
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
|
||||
<tb-datetime fxFlex formControlName="defaultValue"
|
||||
<tb-datetime formControlName="defaultValue"
|
||||
dateText="filter.date"
|
||||
timeText="filter.time"
|
||||
required [showLabel]="false"></tb-datetime>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN">
|
||||
<mat-checkbox fxFlex formControlName="defaultValue">
|
||||
<mat-checkbox formControlName="defaultValue">
|
||||
{{ (filterPredicateValueFormGroup.get('defaultValue').value ? 'value.true' : 'value.false') | translate }}
|
||||
</mat-checkbox>
|
||||
</ng-template>
|
||||
@ -46,9 +46,9 @@
|
||||
<div class="tb-hint" translate>filter.default-value</div>
|
||||
</div>
|
||||
<div fxFlex fxLayout="column" [fxShow]="dynamicMode">
|
||||
<div fxFlex formGroupName="dynamicValue" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<div formGroupName="dynamicValue" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<div fxFlex fxLayout="column">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
|
||||
<mat-label></mat-label>
|
||||
<mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}">
|
||||
<mat-option [value]="null">
|
||||
@ -62,7 +62,7 @@
|
||||
<div class="tb-hint" translate>filter.dynamic-source-type</div>
|
||||
</div>
|
||||
<div fxFlex fxLayout="column">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
|
||||
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
|
||||
<mat-label></mat-label>
|
||||
<input matInput formControlName="sourceAttribute" placeholder="{{'filter.source-attribute' | translate}}">
|
||||
</mat-form-field>
|
||||
|
||||
@ -27,10 +27,10 @@
|
||||
</mat-toolbar>
|
||||
<div mat-dialog-content>
|
||||
<fieldset [disabled]="isLoading$ | async" fxLayout="column">
|
||||
<mat-checkbox fxFlex formControlName="editable" style="margin-bottom: 16px;">
|
||||
<mat-checkbox formControlName="editable" style="margin-bottom: 16px;">
|
||||
{{ 'filter.editable' | translate }}
|
||||
</mat-checkbox>
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>filter.display-label</mat-label>
|
||||
<input matInput formControlName="label">
|
||||
@ -39,7 +39,7 @@
|
||||
{{ 'filter.autogenerated-label' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>filter.order-priority</mat-label>
|
||||
<input matInput type="number" formControlName="order">
|
||||
</mat-form-field>
|
||||
|
||||
@ -16,8 +16,8 @@
|
||||
|
||||
-->
|
||||
<div fxLayout="column" class="mat-content mat-padding">
|
||||
<div fxLayout="column" *ngFor="let filter of filtersInfo | keyvalue; let last = last">
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
|
||||
<div *ngFor="let filter of filtersInfo | keyvalue; let last = last">
|
||||
<div fxLayout="row" fxLayoutAlign="start center">
|
||||
<mat-label fxFlex>{{filter.value.filter}}</mat-label>
|
||||
<button mat-icon-button color="primary"
|
||||
style="min-width: 40px;"
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
<span *ngIf="$index > 0" translate>filter.operation.and</span>
|
||||
</div>
|
||||
<div fxLayout="column" fxFlex="92">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" fxFlex>
|
||||
<div fxLayout="row" fxLayoutAlign="start center">
|
||||
<div fxFlex>{{ keyFilterControl.value.key.key }}</div>
|
||||
<div fxFlex translate>{{ entityKeyTypeTranslations.get(keyFilterControl.value.key.type) }}</div>
|
||||
<button mat-icon-button color="primary"
|
||||
|
||||
@ -29,37 +29,33 @@
|
||||
</mat-progress-bar>
|
||||
<div mat-dialog-content>
|
||||
<fieldset [disabled]="isLoading$ | async">
|
||||
<div fxFlex fxLayout="column">
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center"
|
||||
formArrayName="userInputs"
|
||||
*ngFor="let userInputControl of userInputsFormArray().controls; let $index = index">
|
||||
<div fxFlex fxLayout="column"
|
||||
[ngSwitch]="userInputControl.get('valueType').value">
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.STRING">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label>{{ userInputControl.get('label').value }}</mat-label>
|
||||
<input matInput [formControl]="userInputControl.get('value')">
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label>{{ userInputControl.get('label').value }}</mat-label>
|
||||
<input required type="number" matInput [formControl]="userInputControl.get('value')">
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
|
||||
<label class="tb-title no-padding tb-required">{{ userInputControl.get('label').value }}</label>
|
||||
<tb-datetime fxFlex [formControl]="userInputControl.get('value')"
|
||||
dateText="filter.date"
|
||||
timeText="filter.time"
|
||||
required [showLabel]="false"></tb-datetime>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN">
|
||||
<mat-checkbox labelPosition="before" fxFlex [formControl]="userInputControl.get('value')">
|
||||
{{ userInputControl.get('label').value }}
|
||||
</mat-checkbox>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div formArrayName="userInputs"
|
||||
*ngFor="let userInputControl of userInputsFormArray().controls; let $index = index">
|
||||
<div [ngSwitch]="userInputControl.get('valueType').value">
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.STRING">
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label>{{ userInputControl.get('label').value }}</mat-label>
|
||||
<input matInput [formControl]="userInputControl.get('value')">
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC">
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label>{{ userInputControl.get('label').value }}</mat-label>
|
||||
<input required type="number" matInput [formControl]="userInputControl.get('value')">
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
|
||||
<label class="tb-title no-padding tb-required">{{ userInputControl.get('label').value }}</label>
|
||||
<tb-datetime [formControl]="userInputControl.get('value')"
|
||||
dateText="filter.date"
|
||||
timeText="filter.time"
|
||||
required [showLabel]="false"></tb-datetime>
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN">
|
||||
<mat-checkbox labelPosition="before" [formControl]="userInputControl.get('value')">
|
||||
{{ userInputControl.get('label').value }}
|
||||
</mat-checkbox>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@ -51,7 +51,7 @@
|
||||
<span translate fxLayoutAlign="center center" style="margin: 16px 0"
|
||||
class="tb-prompt required">device-profile.add-create-alarm-rule-prompt</span>
|
||||
</div>
|
||||
<div fxLayout="row" *ngIf="!disabled">
|
||||
<div *ngIf="!disabled">
|
||||
<button mat-stroked-button color="primary"
|
||||
type="button"
|
||||
(click)="addCreateAlarmRule()"
|
||||
|
||||
@ -83,9 +83,9 @@
|
||||
<tb-create-alarm-rules formControlName="createRules" style="padding-bottom: 16px;">
|
||||
</tb-create-alarm-rules>
|
||||
<div translate class="tb-small" style="padding-bottom: 8px;">device-profile.clear-alarm-rule</div>
|
||||
<div fxFlex fxLayout="row"
|
||||
<div fxLayout="row" fxLayoutGap="8px;" fxLayoutAlign="start center"
|
||||
[fxShow]="alarmFormGroup.get('clearRule').value"
|
||||
fxLayoutGap="8px;" fxLayoutAlign="start center" style="padding-bottom: 8px;">
|
||||
style="padding-bottom: 8px;">
|
||||
<div class="clear-alarm-rule" fxFlex fxLayout="row">
|
||||
<tb-alarm-rule formControlName="clearRule" fxFlex>
|
||||
</tb-alarm-rule>
|
||||
@ -103,8 +103,7 @@
|
||||
<span translate fxLayoutAlign="center center" style="margin: 16px 0"
|
||||
class="tb-prompt">device-profile.no-clear-alarm-rule</span>
|
||||
</div>
|
||||
<div fxLayout="row" *ngIf="!disabled"
|
||||
[fxShow]="!alarmFormGroup.get('clearRule').value">
|
||||
<div *ngIf="!disabled" [fxShow]="!alarmFormGroup.get('clearRule').value">
|
||||
<button mat-stroked-button color="primary"
|
||||
type="button"
|
||||
(click)="addClearAlarmRule()"
|
||||
|
||||
@ -30,8 +30,7 @@
|
||||
<span translate fxLayoutAlign="center center"
|
||||
class="tb-prompt">device-profile.no-alarm-rules</span>
|
||||
</div>
|
||||
<div *ngIf="!disabled" fxFlex fxLayout="row" fxLayoutAlign="start center"
|
||||
style="padding-top: 16px;">
|
||||
<div *ngIf="!disabled" style="padding-top: 16px;">
|
||||
<button mat-raised-button color="primary"
|
||||
type="button"
|
||||
(click)="addAlarm()"
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mat-padding" fxLayout="column">
|
||||
<div [ngClass]="{'mat-padding': !standalone}" fxLayout="column">
|
||||
<form [formGroup]="entityForm">
|
||||
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
|
||||
<mat-form-field class="mat-block">
|
||||
|
||||
@ -24,6 +24,7 @@ import { guid, isDefined, isEqual, isUndefined } from '@app/core/utils';
|
||||
import { IterableDiffer, KeyValueDiffer } from '@angular/core';
|
||||
import { IAliasController, IStateController } from '@app/core/api/widget-api.models';
|
||||
import { enumerable } from '@shared/decorators/enumerable';
|
||||
import { UtilsService } from '@core/services/utils.service';
|
||||
|
||||
export interface WidgetsData {
|
||||
widgets: Array<Widget>;
|
||||
@ -56,6 +57,7 @@ export interface DashboardCallbacks {
|
||||
}
|
||||
|
||||
export interface IDashboardComponent {
|
||||
utils: UtilsService;
|
||||
gridsterOpts: GridsterConfig;
|
||||
gridster: GridsterComponent;
|
||||
dashboardWidgets: DashboardWidgets;
|
||||
@ -295,6 +297,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
|
||||
margin: string;
|
||||
|
||||
title: string;
|
||||
customTranslatedTitle: string;
|
||||
titleTooltip: string;
|
||||
showTitle: boolean;
|
||||
titleStyle: {[klass: string]: any};
|
||||
@ -358,8 +361,10 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
|
||||
|
||||
this.title = isDefined(this.widgetContext.widgetTitle)
|
||||
&& this.widgetContext.widgetTitle.length ? this.widgetContext.widgetTitle : this.widget.config.title;
|
||||
this.customTranslatedTitle = this.dashboard.utils.customTranslation(this.title, this.title);
|
||||
this.titleTooltip = isDefined(this.widgetContext.widgetTitleTooltip)
|
||||
&& this.widgetContext.widgetTitleTooltip.length ? this.widgetContext.widgetTitleTooltip : this.widget.config.titleTooltip;
|
||||
this.titleTooltip = this.dashboard.utils.customTranslation(this.titleTooltip, this.titleTooltip);
|
||||
this.showTitle = isDefined(this.widget.config.showTitle) ? this.widget.config.showTitle : true;
|
||||
this.titleStyle = this.widget.config.titleStyle ? this.widget.config.titleStyle : {};
|
||||
|
||||
|
||||
@ -61,8 +61,8 @@
|
||||
<div [formGroupName]="n" fxLayout="row" fxLayoutGap="8px">
|
||||
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
|
||||
<div fxLayout="column" fxFlex.sm="60" fxFlex.gt-sm="50">
|
||||
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
|
||||
<mat-form-field fxFlex="30" fxFlex.xs class="mat-block">
|
||||
<div fxLayout="row" fxLayout.xs="column" fxLayout.md="column" fxLayoutGap="8px" fxLayoutGap.md="0px">
|
||||
<mat-form-field fxFlex="30" fxFlex.md fxFlex.xs class="mat-block">
|
||||
<mat-label translate>admin.oauth2.protocol</mat-label>
|
||||
<mat-select formControlName="scheme">
|
||||
<mat-option *ngFor="let protocol of protocols" [value]="protocol">
|
||||
|
||||
@ -18,7 +18,12 @@
|
||||
:host {
|
||||
mat-card.settings-card {
|
||||
margin: 8px;
|
||||
@media #{$mat-gt-sm} {
|
||||
|
||||
@media #{$mat-md} {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
@media #{$mat-gt-md} {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -21,48 +21,46 @@
|
||||
{{'rulechain.open-rulechain' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mat-padding" fxLayout="column">
|
||||
<form [formGroup]="ruleNodeFormGroup">
|
||||
<fieldset [disabled]="(isLoading$ | async) || !isEdit || isReadOnly">
|
||||
<section *ngIf="ruleNode.component.type !== ruleNodeType.RULE_CHAIN">
|
||||
<section fxLayout="column" fxLayout.gt-sm="row">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>rulenode.name</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
<mat-error *ngIf="ruleNodeFormGroup.get('name').hasError('required')
|
||||
|| ruleNodeFormGroup.get('name').hasError('pattern')">
|
||||
{{ 'rulenode.name-required' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-checkbox formControlName="debugMode">
|
||||
{{ 'rulenode.debug-mode' | translate }}
|
||||
</mat-checkbox>
|
||||
</section>
|
||||
<tb-rule-node-config #ruleNodeConfigComponent
|
||||
formControlName="configuration"
|
||||
[ruleNodeId]="ruleNode.ruleNodeId?.id"
|
||||
[nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition">
|
||||
</tb-rule-node-config>
|
||||
<div formGroupName="additionalInfo" fxLayout="column">
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>rulenode.description</mat-label>
|
||||
<textarea matInput formControlName="description" rows="2"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<form [formGroup]="ruleNodeFormGroup" class="mat-padding">
|
||||
<fieldset [disabled]="(isLoading$ | async) || !isEdit || isReadOnly">
|
||||
<section *ngIf="ruleNode.component.type !== ruleNodeType.RULE_CHAIN">
|
||||
<section fxLayout="column" fxLayout.gt-sm="row">
|
||||
<mat-form-field fxFlex class="mat-block">
|
||||
<mat-label translate>rulenode.name</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
<mat-error *ngIf="ruleNodeFormGroup.get('name').hasError('required')
|
||||
|| ruleNodeFormGroup.get('name').hasError('pattern')">
|
||||
{{ 'rulenode.name-required' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-checkbox formControlName="debugMode">
|
||||
{{ 'rulenode.debug-mode' | translate }}
|
||||
</mat-checkbox>
|
||||
</section>
|
||||
<section *ngIf="ruleNode.component.type === ruleNodeType.RULE_CHAIN">
|
||||
<tb-entity-autocomplete required
|
||||
[excludeEntityIds]="[ruleChainId]"
|
||||
[entityType]="entityType.RULE_CHAIN"
|
||||
formControlName="targetRuleChainId">
|
||||
</tb-entity-autocomplete>
|
||||
<div formGroupName="additionalInfo" fxLayout="column">
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>rulenode.description</mat-label>
|
||||
<textarea matInput formControlName="description" rows="2"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</section>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<tb-rule-node-config #ruleNodeConfigComponent
|
||||
formControlName="configuration"
|
||||
[ruleNodeId]="ruleNode.ruleNodeId?.id"
|
||||
[nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition">
|
||||
</tb-rule-node-config>
|
||||
<div formGroupName="additionalInfo" fxLayout="column">
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>rulenode.description</mat-label>
|
||||
<textarea matInput formControlName="description" rows="2"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</section>
|
||||
<section *ngIf="ruleNode.component.type === ruleNodeType.RULE_CHAIN">
|
||||
<tb-entity-autocomplete required
|
||||
[excludeEntityIds]="[ruleChainId]"
|
||||
[entityType]="entityType.RULE_CHAIN"
|
||||
formControlName="targetRuleChainId">
|
||||
</tb-entity-autocomplete>
|
||||
<div formGroupName="additionalInfo" fxLayout="column">
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>rulenode.description</mat-label>
|
||||
<textarea matInput formControlName="description" rows="2"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</section>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
.tb-login-content {
|
||||
margin-top: 36px;
|
||||
margin-bottom: 76px;
|
||||
background-color: rgb(250, 250, 250);
|
||||
background-color: #eee;
|
||||
.tb-login-form {
|
||||
@media #{$mat-gt-xs} {
|
||||
width: 550px !important;
|
||||
|
||||
@ -433,7 +433,57 @@
|
||||
"no-telemetry-text": "No telemetry found"
|
||||
},
|
||||
"api-usage": {
|
||||
"api-usage": "Api Usage"
|
||||
"api-usage": "Api Usage",
|
||||
"data-points": "Data points",
|
||||
"data-points-storage-days": "Data points storage days",
|
||||
"email": "Email",
|
||||
"email-messages": "Email messages",
|
||||
"email-messages-daily-activity": "Email messages daily activity",
|
||||
"email-messages-hourly-activity": "Email messages hourly activity",
|
||||
"email-messages-monthly-activity": "Email messages monthly activity",
|
||||
"exceptions": "Exceptions",
|
||||
"executions": "Executions",
|
||||
"javascript": "JavaScript",
|
||||
"javascript-executions": "JavaScript executions",
|
||||
"javascript-functions": "JavaScript functions",
|
||||
"javascript-functions-daily-activity": "JavaScript functions daily activity",
|
||||
"javascript-functions-hourly-activity": "JavaScript functions hourly activity",
|
||||
"javascript-functions-monthly-activity": "JavaScript functions monthly activity",
|
||||
"latest-error": "Latest Error",
|
||||
"messages": "Messages",
|
||||
"permanent-failures": "${entityName} Permanent Failures",
|
||||
"permanent-timeouts": "${entityName} Permanent Timeouts",
|
||||
"processing-failures": "${entityName} Processing Failures",
|
||||
"processing-failures-and-timeouts": "Processing Failures and Timeouts",
|
||||
"processing-timeouts": "${entityName} Processing Timeouts",
|
||||
"queue-stats": "Queue Stats",
|
||||
"rule-chain": "Rule Chain",
|
||||
"rule-engine": "Rule Engine",
|
||||
"rule-engine-daily-activity": "Rule Engine daily activity",
|
||||
"rule-engine-executions": "Rule Engine executions",
|
||||
"rule-engine-hourly-activity": "Rule Engine hourly activity",
|
||||
"rule-engine-monthly-activity": "Rule Engine monthly activity",
|
||||
"rule-engine-statistics": "Rule Engine Statistics",
|
||||
"rule-node": "Rule Node",
|
||||
"sms": "SMS",
|
||||
"sms-messages": "SMS messages",
|
||||
"sms-messages-daily-activity": "SMS messages daily activity",
|
||||
"sms-messages-hourly-activity": "SMS messages hourly activity",
|
||||
"sms-messages-monthly-activity": "SMS messages monthly activity",
|
||||
"successful": "${entityName} Successful",
|
||||
"telemetry": "Telemetry",
|
||||
"telemetry-persistence": "Telemetry persistence",
|
||||
"telemetry-persistence-daily-activity": "Telemetry persistence daily activity",
|
||||
"telemetry-persistence-hourly-activity": "Telemetry persistence hourly activity",
|
||||
"telemetry-persistence-monthly-activity": "Telemetry persistence monthly activity",
|
||||
"transport": "Transport",
|
||||
"transport-daily-activity": "Transport daily activity",
|
||||
"transport-data-points": "Transport data points",
|
||||
"transport-hourly-activity": "Transport hourly activity",
|
||||
"transport-messages": "Transport messages",
|
||||
"transport-monthly-activity": "Transport monthly activity",
|
||||
"view-details": "View details",
|
||||
"view-statistics": "View statistics"
|
||||
},
|
||||
"audit-log": {
|
||||
"audit": "Audit",
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
-->
|
||||
<!doctype html>
|
||||
<html lang="en" style="width: 100%;">
|
||||
<html lang="en" style="width: 100%; height: 100%;">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>ThingsBoard</title>
|
||||
@ -24,8 +24,82 @@
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="thingsboard.ico">
|
||||
<style type="text/css">
|
||||
|
||||
body, html {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.tb-loading-spinner {
|
||||
margin: auto;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 136px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tb-loading-spinner > div {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 10px;
|
||||
background-color: rgb(43,160,199);
|
||||
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
-webkit-animation: tb-bouncedelay 1.4s infinite ease-in-out both;
|
||||
-moz-animation: tb-bouncedelay 1.4s infinite ease-in-out both;
|
||||
animation: tb-bouncedelay 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
.tb-loading-spinner .tb-bounce1 {
|
||||
-webkit-animation-delay: -0.32s;
|
||||
-moz-animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.tb-loading-spinner .tb-bounce2 {
|
||||
-webkit-animation-delay: -0.16s;
|
||||
-moz-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes tb-bouncedelay {
|
||||
0%, 80%, 100% { -webkit-transform: scale(0) }
|
||||
40% { -webkit-transform: scale(1.0) }
|
||||
}
|
||||
|
||||
@-moz-keyframes tb-bouncedelay {
|
||||
0%, 80%, 100% { -moz-transform: scale(0) }
|
||||
40% { -moz-transform: scale(1.0) }
|
||||
}
|
||||
|
||||
@keyframes tb-bouncedelay {
|
||||
0%, 80%, 100% {
|
||||
-webkit-transform: scale(0);
|
||||
-moz-transform: scale(0);
|
||||
transform: scale(0);
|
||||
} 40% {
|
||||
-webkit-transform: scale(1.0);
|
||||
-moz-transform: scale(1.0);
|
||||
transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body class="tb-default">
|
||||
<tb-root></tb-root>
|
||||
<div id="tb-loading-spinner" class="tb-loading-spinner">
|
||||
<div class="tb-bounce1"></div>
|
||||
<div class="tb-bounce2"></div>
|
||||
<div class="tb-bounce3"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -41,7 +41,7 @@ body, html {
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: rgb(250,250,250);
|
||||
background-color: #eee;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user