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.PostConstruct;
|
||||||
import javax.annotation.PreDestroy;
|
import javax.annotation.PreDestroy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -73,6 +74,7 @@ import java.util.concurrent.Executors;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@ -347,16 +349,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
|
|||||||
try {
|
try {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
myTenantStates.values().forEach(state -> {
|
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();
|
TenantId tenantId = state.getTenantId();
|
||||||
state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth());
|
state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth());
|
||||||
ToUsageStatsServiceMsg.Builder msg = ToUsageStatsServiceMsg.newBuilder();
|
saveNewCounts(state, Arrays.asList(ApiUsageRecordKey.values()));
|
||||||
msg.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
|
updateTenantState(state, tenantProfileCache.get(tenantId));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} finally {
|
} 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) {
|
private TenantApiUsageState getOrFetchState(TenantId tenantId) {
|
||||||
TenantApiUsageState tenantState = myTenantStates.get(tenantId);
|
TenantApiUsageState tenantState = myTenantStates.get(tenantId);
|
||||||
if (tenantState == null) {
|
if (tenantState == null) {
|
||||||
@ -377,6 +382,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
|
|||||||
}
|
}
|
||||||
TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
|
TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
|
||||||
tenantState = new TenantApiUsageState(tenantProfile, dbStateEntity);
|
tenantState = new TenantApiUsageState(tenantProfile, dbStateEntity);
|
||||||
|
List<ApiUsageRecordKey> newCounts = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
List<TsKvEntry> dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getId()).get();
|
List<TsKvEntry> dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getId()).get();
|
||||||
for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) {
|
for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) {
|
||||||
@ -385,7 +391,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
|
|||||||
for (TsKvEntry tsKvEntry : dbValues) {
|
for (TsKvEntry tsKvEntry : dbValues) {
|
||||||
if (tsKvEntry.getKey().equals(key.getApiCountKey())) {
|
if (tsKvEntry.getKey().equals(key.getApiCountKey())) {
|
||||||
cycleEntryFound = true;
|
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)) {
|
} else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) {
|
||||||
hourlyEntryFound = true;
|
hourlyEntryFound = true;
|
||||||
tenantState.putHourly(key, tsKvEntry.getTs() == tenantState.getCurrentHourTs() ? tsKvEntry.getLongValue().get() : 0L);
|
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);
|
log.debug("[{}] Initialized state: {}", tenantId, dbStateEntity);
|
||||||
myTenantStates.put(tenantId, tenantState);
|
myTenantStates.put(tenantId, tenantState);
|
||||||
|
saveNewCounts(tenantState, newCounts);
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
log.warn("[{}] Failed to fetch api usage state from db.", tenantId, 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.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalTime;
|
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.time.temporal.TemporalAdjuster;
|
import java.time.temporal.TemporalAdjuster;
|
||||||
import java.time.temporal.TemporalAdjusters;
|
import java.time.temporal.TemporalAdjusters;
|
||||||
import java.time.temporal.TemporalUnit;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
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.ChronoField.DAY_OF_MONTH;
|
||||||
import static java.time.temporal.ChronoUnit.MONTHS;
|
import static java.time.temporal.ChronoUnit.MONTHS;
|
||||||
|
|
||||||
public class SchedulerUtils {
|
public class SchedulerUtils {
|
||||||
|
|
||||||
private final static ZoneId UTC = ZoneId.of("UTC");
|
|
||||||
private static final ConcurrentMap<String, ZoneId> tzMap = new ConcurrentHashMap<>();
|
private static final ConcurrentMap<String, ZoneId> tzMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public static ZoneId getZoneId(String tz) {
|
public static ZoneId getZoneId(String tz) {
|
||||||
@ -44,7 +41,7 @@ public class SchedulerUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static long getStartOfCurrentHour(ZoneId zoneId) {
|
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() {
|
public static long getStartOfCurrentMonth() {
|
||||||
@ -52,7 +49,7 @@ public class SchedulerUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static long getStartOfCurrentMonth(ZoneId zoneId) {
|
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() {
|
public static long getStartOfNextMonth() {
|
||||||
@ -60,7 +57,7 @@ public class SchedulerUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static long getStartOfNextMonth(ZoneId zoneId) {
|
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() {
|
public static long getStartOfNextNextMonth() {
|
||||||
@ -68,7 +65,7 @@ public class SchedulerUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static long getStartOfNextNextMonth(ZoneId zoneId) {
|
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() {
|
public static TemporalAdjuster firstDayOfNextNextMonth() {
|
||||||
|
|||||||
@ -17,4 +17,4 @@
|
|||||||
-->
|
-->
|
||||||
<!--The content below is only a placeholder and can be replaced.-->
|
<!--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(
|
this.matIconRegistry.addSvgIconLiteral(
|
||||||
'alpha-e-circle-outline',
|
'alpha-e-circle-outline',
|
||||||
this.domSanitizer.bypassSecurityTrustHtml(
|
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 ' +
|
'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>'
|
'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() {
|
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 type SubscriptionMessageSeverity = 'info' | 'warn' | 'error' | 'success';
|
||||||
|
|
||||||
export interface SubscriptionMessage {
|
export interface SubscriptionMessage {
|
||||||
severity: SubscriptionMessageSeverity,
|
severity: SubscriptionMessageSeverity;
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -90,7 +90,7 @@
|
|||||||
matTooltipPosition="above"
|
matTooltipPosition="above"
|
||||||
class="mat-subheading-2 title">
|
class="mat-subheading-2 title">
|
||||||
<mat-icon *ngIf="widget.showTitleIcon" [ngStyle]="widget.titleIconStyle">{{widget.titleIcon}}</mat-icon>
|
<mat-icon *ngIf="widget.showTitleIcon" [ngStyle]="widget.titleIconStyle">{{widget.titleIcon}}</mat-icon>
|
||||||
{{widget.title}}
|
{{widget.customTranslatedTitle}}
|
||||||
</span>
|
</span>
|
||||||
<tb-timewindow *ngIf="widget.hasTimewindow"
|
<tb-timewindow *ngIf="widget.hasTimewindow"
|
||||||
#timewindowComponent
|
#timewindowComponent
|
||||||
|
|||||||
@ -54,6 +54,7 @@ import { MatMenuTrigger } from '@angular/material/menu';
|
|||||||
import { SafeStyle } from '@angular/platform-browser';
|
import { SafeStyle } from '@angular/platform-browser';
|
||||||
import { distinct } from 'rxjs/operators';
|
import { distinct } from 'rxjs/operators';
|
||||||
import { ResizeObserver } from '@juggle/resize-observer';
|
import { ResizeObserver } from '@juggle/resize-observer';
|
||||||
|
import { UtilsService } from '@core/services/utils.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tb-dashboard',
|
selector: 'tb-dashboard',
|
||||||
@ -168,6 +169,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
|
|||||||
private gridsterResize$: ResizeObserver;
|
private gridsterResize$: ResizeObserver;
|
||||||
|
|
||||||
constructor(protected store: Store<AppState>,
|
constructor(protected store: Store<AppState>,
|
||||||
|
public utils: UtilsService,
|
||||||
private timeService: TimeService,
|
private timeService: TimeService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private breakpointObserver: BreakpointObserver,
|
private breakpointObserver: BreakpointObserver,
|
||||||
|
|||||||
@ -17,28 +17,28 @@
|
|||||||
-->
|
-->
|
||||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="filterPredicateValueFormGroup">
|
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="filterPredicateValueFormGroup">
|
||||||
<div fxFlex fxLayout="column" [fxShow]="!dynamicMode">
|
<div fxFlex fxLayout="column" [fxShow]="!dynamicMode">
|
||||||
<div fxFlex fxLayout="column" [ngSwitch]="valueType">
|
<div fxLayout="column" [ngSwitch]="valueType">
|
||||||
<ng-template [ngSwitchCase]="valueTypeEnum.STRING">
|
<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>
|
<mat-label></mat-label>
|
||||||
<input matInput formControlName="defaultValue" placeholder="{{'filter.value' | translate}}">
|
<input matInput formControlName="defaultValue" placeholder="{{'filter.value' | translate}}">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC">
|
<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>
|
<mat-label></mat-label>
|
||||||
<input required type="number" matInput formControlName="defaultValue"
|
<input required type="number" matInput formControlName="defaultValue"
|
||||||
placeholder="{{'filter.value' | translate}}">
|
placeholder="{{'filter.value' | translate}}">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
|
<ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
|
||||||
<tb-datetime fxFlex formControlName="defaultValue"
|
<tb-datetime formControlName="defaultValue"
|
||||||
dateText="filter.date"
|
dateText="filter.date"
|
||||||
timeText="filter.time"
|
timeText="filter.time"
|
||||||
required [showLabel]="false"></tb-datetime>
|
required [showLabel]="false"></tb-datetime>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN">
|
<ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN">
|
||||||
<mat-checkbox fxFlex formControlName="defaultValue">
|
<mat-checkbox formControlName="defaultValue">
|
||||||
{{ (filterPredicateValueFormGroup.get('defaultValue').value ? 'value.true' : 'value.false') | translate }}
|
{{ (filterPredicateValueFormGroup.get('defaultValue').value ? 'value.true' : 'value.false') | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -46,9 +46,9 @@
|
|||||||
<div class="tb-hint" translate>filter.default-value</div>
|
<div class="tb-hint" translate>filter.default-value</div>
|
||||||
</div>
|
</div>
|
||||||
<div fxFlex fxLayout="column" [fxShow]="dynamicMode">
|
<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">
|
<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-label></mat-label>
|
||||||
<mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}">
|
<mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}">
|
||||||
<mat-option [value]="null">
|
<mat-option [value]="null">
|
||||||
@ -62,7 +62,7 @@
|
|||||||
<div class="tb-hint" translate>filter.dynamic-source-type</div>
|
<div class="tb-hint" translate>filter.dynamic-source-type</div>
|
||||||
</div>
|
</div>
|
||||||
<div fxFlex fxLayout="column">
|
<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-label></mat-label>
|
||||||
<input matInput formControlName="sourceAttribute" placeholder="{{'filter.source-attribute' | translate}}">
|
<input matInput formControlName="sourceAttribute" placeholder="{{'filter.source-attribute' | translate}}">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|||||||
@ -27,10 +27,10 @@
|
|||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
<div mat-dialog-content>
|
<div mat-dialog-content>
|
||||||
<fieldset [disabled]="isLoading$ | async" fxLayout="column">
|
<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 }}
|
{{ 'filter.editable' | translate }}
|
||||||
</mat-checkbox>
|
</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-form-field fxFlex class="mat-block">
|
||||||
<mat-label translate>filter.display-label</mat-label>
|
<mat-label translate>filter.display-label</mat-label>
|
||||||
<input matInput formControlName="label">
|
<input matInput formControlName="label">
|
||||||
@ -39,7 +39,7 @@
|
|||||||
{{ 'filter.autogenerated-label' | translate }}
|
{{ 'filter.autogenerated-label' | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<mat-form-field fxFlex class="mat-block">
|
<mat-form-field class="mat-block">
|
||||||
<mat-label translate>filter.order-priority</mat-label>
|
<mat-label translate>filter.order-priority</mat-label>
|
||||||
<input matInput type="number" formControlName="order">
|
<input matInput type="number" formControlName="order">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|||||||
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
<div fxLayout="column" class="mat-content mat-padding">
|
<div fxLayout="column" class="mat-content mat-padding">
|
||||||
<div fxLayout="column" *ngFor="let filter of filtersInfo | keyvalue; let last = last">
|
<div *ngFor="let filter of filtersInfo | keyvalue; let last = last">
|
||||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
|
<div fxLayout="row" fxLayoutAlign="start center">
|
||||||
<mat-label fxFlex>{{filter.value.filter}}</mat-label>
|
<mat-label fxFlex>{{filter.value.filter}}</mat-label>
|
||||||
<button mat-icon-button color="primary"
|
<button mat-icon-button color="primary"
|
||||||
style="min-width: 40px;"
|
style="min-width: 40px;"
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
<span *ngIf="$index > 0" translate>filter.operation.and</span>
|
<span *ngIf="$index > 0" translate>filter.operation.and</span>
|
||||||
</div>
|
</div>
|
||||||
<div fxLayout="column" fxFlex="92">
|
<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>{{ keyFilterControl.value.key.key }}</div>
|
||||||
<div fxFlex translate>{{ entityKeyTypeTranslations.get(keyFilterControl.value.key.type) }}</div>
|
<div fxFlex translate>{{ entityKeyTypeTranslations.get(keyFilterControl.value.key.type) }}</div>
|
||||||
<button mat-icon-button color="primary"
|
<button mat-icon-button color="primary"
|
||||||
|
|||||||
@ -29,37 +29,33 @@
|
|||||||
</mat-progress-bar>
|
</mat-progress-bar>
|
||||||
<div mat-dialog-content>
|
<div mat-dialog-content>
|
||||||
<fieldset [disabled]="isLoading$ | async">
|
<fieldset [disabled]="isLoading$ | async">
|
||||||
<div fxFlex fxLayout="column">
|
<div formArrayName="userInputs"
|
||||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center"
|
*ngFor="let userInputControl of userInputsFormArray().controls; let $index = index">
|
||||||
formArrayName="userInputs"
|
<div [ngSwitch]="userInputControl.get('valueType').value">
|
||||||
*ngFor="let userInputControl of userInputsFormArray().controls; let $index = index">
|
<ng-template [ngSwitchCase]="valueTypeEnum.STRING">
|
||||||
<div fxFlex fxLayout="column"
|
<mat-form-field class="mat-block">
|
||||||
[ngSwitch]="userInputControl.get('valueType').value">
|
<mat-label>{{ userInputControl.get('label').value }}</mat-label>
|
||||||
<ng-template [ngSwitchCase]="valueTypeEnum.STRING">
|
<input matInput [formControl]="userInputControl.get('value')">
|
||||||
<mat-form-field fxFlex class="mat-block">
|
</mat-form-field>
|
||||||
<mat-label>{{ userInputControl.get('label').value }}</mat-label>
|
</ng-template>
|
||||||
<input matInput [formControl]="userInputControl.get('value')">
|
<ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC">
|
||||||
</mat-form-field>
|
<mat-form-field class="mat-block">
|
||||||
</ng-template>
|
<mat-label>{{ userInputControl.get('label').value }}</mat-label>
|
||||||
<ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC">
|
<input required type="number" matInput [formControl]="userInputControl.get('value')">
|
||||||
<mat-form-field fxFlex class="mat-block">
|
</mat-form-field>
|
||||||
<mat-label>{{ userInputControl.get('label').value }}</mat-label>
|
</ng-template>
|
||||||
<input required type="number" matInput [formControl]="userInputControl.get('value')">
|
<ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
|
||||||
</mat-form-field>
|
<label class="tb-title no-padding tb-required">{{ userInputControl.get('label').value }}</label>
|
||||||
</ng-template>
|
<tb-datetime [formControl]="userInputControl.get('value')"
|
||||||
<ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
|
dateText="filter.date"
|
||||||
<label class="tb-title no-padding tb-required">{{ userInputControl.get('label').value }}</label>
|
timeText="filter.time"
|
||||||
<tb-datetime fxFlex [formControl]="userInputControl.get('value')"
|
required [showLabel]="false"></tb-datetime>
|
||||||
dateText="filter.date"
|
</ng-template>
|
||||||
timeText="filter.time"
|
<ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN">
|
||||||
required [showLabel]="false"></tb-datetime>
|
<mat-checkbox labelPosition="before" [formControl]="userInputControl.get('value')">
|
||||||
</ng-template>
|
{{ userInputControl.get('label').value }}
|
||||||
<ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN">
|
</mat-checkbox>
|
||||||
<mat-checkbox labelPosition="before" fxFlex [formControl]="userInputControl.get('value')">
|
</ng-template>
|
||||||
{{ userInputControl.get('label').value }}
|
|
||||||
</mat-checkbox>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@ -51,7 +51,7 @@
|
|||||||
<span translate fxLayoutAlign="center center" style="margin: 16px 0"
|
<span translate fxLayoutAlign="center center" style="margin: 16px 0"
|
||||||
class="tb-prompt required">device-profile.add-create-alarm-rule-prompt</span>
|
class="tb-prompt required">device-profile.add-create-alarm-rule-prompt</span>
|
||||||
</div>
|
</div>
|
||||||
<div fxLayout="row" *ngIf="!disabled">
|
<div *ngIf="!disabled">
|
||||||
<button mat-stroked-button color="primary"
|
<button mat-stroked-button color="primary"
|
||||||
type="button"
|
type="button"
|
||||||
(click)="addCreateAlarmRule()"
|
(click)="addCreateAlarmRule()"
|
||||||
|
|||||||
@ -83,9 +83,9 @@
|
|||||||
<tb-create-alarm-rules formControlName="createRules" style="padding-bottom: 16px;">
|
<tb-create-alarm-rules formControlName="createRules" style="padding-bottom: 16px;">
|
||||||
</tb-create-alarm-rules>
|
</tb-create-alarm-rules>
|
||||||
<div translate class="tb-small" style="padding-bottom: 8px;">device-profile.clear-alarm-rule</div>
|
<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"
|
[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">
|
<div class="clear-alarm-rule" fxFlex fxLayout="row">
|
||||||
<tb-alarm-rule formControlName="clearRule" fxFlex>
|
<tb-alarm-rule formControlName="clearRule" fxFlex>
|
||||||
</tb-alarm-rule>
|
</tb-alarm-rule>
|
||||||
@ -103,8 +103,7 @@
|
|||||||
<span translate fxLayoutAlign="center center" style="margin: 16px 0"
|
<span translate fxLayoutAlign="center center" style="margin: 16px 0"
|
||||||
class="tb-prompt">device-profile.no-clear-alarm-rule</span>
|
class="tb-prompt">device-profile.no-clear-alarm-rule</span>
|
||||||
</div>
|
</div>
|
||||||
<div fxLayout="row" *ngIf="!disabled"
|
<div *ngIf="!disabled" [fxShow]="!alarmFormGroup.get('clearRule').value">
|
||||||
[fxShow]="!alarmFormGroup.get('clearRule').value">
|
|
||||||
<button mat-stroked-button color="primary"
|
<button mat-stroked-button color="primary"
|
||||||
type="button"
|
type="button"
|
||||||
(click)="addClearAlarmRule()"
|
(click)="addClearAlarmRule()"
|
||||||
|
|||||||
@ -30,8 +30,7 @@
|
|||||||
<span translate fxLayoutAlign="center center"
|
<span translate fxLayoutAlign="center center"
|
||||||
class="tb-prompt">device-profile.no-alarm-rules</span>
|
class="tb-prompt">device-profile.no-alarm-rules</span>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!disabled" fxFlex fxLayout="row" fxLayoutAlign="start center"
|
<div *ngIf="!disabled" style="padding-top: 16px;">
|
||||||
style="padding-top: 16px;">
|
|
||||||
<button mat-raised-button color="primary"
|
<button mat-raised-button color="primary"
|
||||||
type="button"
|
type="button"
|
||||||
(click)="addAlarm()"
|
(click)="addAlarm()"
|
||||||
|
|||||||
@ -39,7 +39,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mat-padding" fxLayout="column">
|
<div [ngClass]="{'mat-padding': !standalone}" fxLayout="column">
|
||||||
<form [formGroup]="entityForm">
|
<form [formGroup]="entityForm">
|
||||||
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
|
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
|
||||||
<mat-form-field class="mat-block">
|
<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 { IterableDiffer, KeyValueDiffer } from '@angular/core';
|
||||||
import { IAliasController, IStateController } from '@app/core/api/widget-api.models';
|
import { IAliasController, IStateController } from '@app/core/api/widget-api.models';
|
||||||
import { enumerable } from '@shared/decorators/enumerable';
|
import { enumerable } from '@shared/decorators/enumerable';
|
||||||
|
import { UtilsService } from '@core/services/utils.service';
|
||||||
|
|
||||||
export interface WidgetsData {
|
export interface WidgetsData {
|
||||||
widgets: Array<Widget>;
|
widgets: Array<Widget>;
|
||||||
@ -56,6 +57,7 @@ export interface DashboardCallbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IDashboardComponent {
|
export interface IDashboardComponent {
|
||||||
|
utils: UtilsService;
|
||||||
gridsterOpts: GridsterConfig;
|
gridsterOpts: GridsterConfig;
|
||||||
gridster: GridsterComponent;
|
gridster: GridsterComponent;
|
||||||
dashboardWidgets: DashboardWidgets;
|
dashboardWidgets: DashboardWidgets;
|
||||||
@ -295,6 +297,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
|
|||||||
margin: string;
|
margin: string;
|
||||||
|
|
||||||
title: string;
|
title: string;
|
||||||
|
customTranslatedTitle: string;
|
||||||
titleTooltip: string;
|
titleTooltip: string;
|
||||||
showTitle: boolean;
|
showTitle: boolean;
|
||||||
titleStyle: {[klass: string]: any};
|
titleStyle: {[klass: string]: any};
|
||||||
@ -358,8 +361,10 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
|
|||||||
|
|
||||||
this.title = isDefined(this.widgetContext.widgetTitle)
|
this.title = isDefined(this.widgetContext.widgetTitle)
|
||||||
&& this.widgetContext.widgetTitle.length ? this.widgetContext.widgetTitle : this.widget.config.title;
|
&& 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.titleTooltip = isDefined(this.widgetContext.widgetTitleTooltip)
|
||||||
&& this.widgetContext.widgetTitleTooltip.length ? this.widgetContext.widgetTitleTooltip : this.widget.config.titleTooltip;
|
&& 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.showTitle = isDefined(this.widget.config.showTitle) ? this.widget.config.showTitle : true;
|
||||||
this.titleStyle = this.widget.config.titleStyle ? this.widget.config.titleStyle : {};
|
this.titleStyle = this.widget.config.titleStyle ? this.widget.config.titleStyle : {};
|
||||||
|
|
||||||
|
|||||||
@ -61,8 +61,8 @@
|
|||||||
<div [formGroupName]="n" fxLayout="row" fxLayoutGap="8px">
|
<div [formGroupName]="n" fxLayout="row" fxLayoutGap="8px">
|
||||||
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
|
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
|
||||||
<div fxLayout="column" fxFlex.sm="60" fxFlex.gt-sm="50">
|
<div fxLayout="column" fxFlex.sm="60" fxFlex.gt-sm="50">
|
||||||
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
|
<div fxLayout="row" fxLayout.xs="column" fxLayout.md="column" fxLayoutGap="8px" fxLayoutGap.md="0px">
|
||||||
<mat-form-field fxFlex="30" fxFlex.xs class="mat-block">
|
<mat-form-field fxFlex="30" fxFlex.md fxFlex.xs class="mat-block">
|
||||||
<mat-label translate>admin.oauth2.protocol</mat-label>
|
<mat-label translate>admin.oauth2.protocol</mat-label>
|
||||||
<mat-select formControlName="scheme">
|
<mat-select formControlName="scheme">
|
||||||
<mat-option *ngFor="let protocol of protocols" [value]="protocol">
|
<mat-option *ngFor="let protocol of protocols" [value]="protocol">
|
||||||
|
|||||||
@ -18,7 +18,12 @@
|
|||||||
:host {
|
:host {
|
||||||
mat-card.settings-card {
|
mat-card.settings-card {
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
@media #{$mat-gt-sm} {
|
|
||||||
|
@media #{$mat-md} {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$mat-gt-md} {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -21,48 +21,46 @@
|
|||||||
{{'rulechain.open-rulechain' | translate }}
|
{{'rulechain.open-rulechain' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mat-padding" fxLayout="column">
|
<form [formGroup]="ruleNodeFormGroup" class="mat-padding">
|
||||||
<form [formGroup]="ruleNodeFormGroup">
|
<fieldset [disabled]="(isLoading$ | async) || !isEdit || isReadOnly">
|
||||||
<fieldset [disabled]="(isLoading$ | async) || !isEdit || isReadOnly">
|
<section *ngIf="ruleNode.component.type !== ruleNodeType.RULE_CHAIN">
|
||||||
<section *ngIf="ruleNode.component.type !== ruleNodeType.RULE_CHAIN">
|
<section fxLayout="column" fxLayout.gt-sm="row">
|
||||||
<section fxLayout="column" fxLayout.gt-sm="row">
|
<mat-form-field fxFlex class="mat-block">
|
||||||
<mat-form-field fxFlex class="mat-block">
|
<mat-label translate>rulenode.name</mat-label>
|
||||||
<mat-label translate>rulenode.name</mat-label>
|
<input matInput formControlName="name" required>
|
||||||
<input matInput formControlName="name" required>
|
<mat-error *ngIf="ruleNodeFormGroup.get('name').hasError('required')
|
||||||
<mat-error *ngIf="ruleNodeFormGroup.get('name').hasError('required')
|
|| ruleNodeFormGroup.get('name').hasError('pattern')">
|
||||||
|| ruleNodeFormGroup.get('name').hasError('pattern')">
|
{{ 'rulenode.name-required' | translate }}
|
||||||
{{ 'rulenode.name-required' | translate }}
|
</mat-error>
|
||||||
</mat-error>
|
</mat-form-field>
|
||||||
</mat-form-field>
|
<mat-checkbox formControlName="debugMode">
|
||||||
<mat-checkbox formControlName="debugMode">
|
{{ 'rulenode.debug-mode' | translate }}
|
||||||
{{ 'rulenode.debug-mode' | translate }}
|
</mat-checkbox>
|
||||||
</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>
|
|
||||||
</section>
|
</section>
|
||||||
<section *ngIf="ruleNode.component.type === ruleNodeType.RULE_CHAIN">
|
<tb-rule-node-config #ruleNodeConfigComponent
|
||||||
<tb-entity-autocomplete required
|
formControlName="configuration"
|
||||||
[excludeEntityIds]="[ruleChainId]"
|
[ruleNodeId]="ruleNode.ruleNodeId?.id"
|
||||||
[entityType]="entityType.RULE_CHAIN"
|
[nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition">
|
||||||
formControlName="targetRuleChainId">
|
</tb-rule-node-config>
|
||||||
</tb-entity-autocomplete>
|
<div formGroupName="additionalInfo" fxLayout="column">
|
||||||
<div formGroupName="additionalInfo" fxLayout="column">
|
<mat-form-field class="mat-block">
|
||||||
<mat-form-field class="mat-block">
|
<mat-label translate>rulenode.description</mat-label>
|
||||||
<mat-label translate>rulenode.description</mat-label>
|
<textarea matInput formControlName="description" rows="2"></textarea>
|
||||||
<textarea matInput formControlName="description" rows="2"></textarea>
|
</mat-form-field>
|
||||||
</mat-form-field>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
<section *ngIf="ruleNode.component.type === ruleNodeType.RULE_CHAIN">
|
||||||
</fieldset>
|
<tb-entity-autocomplete required
|
||||||
</form>
|
[excludeEntityIds]="[ruleChainId]"
|
||||||
</div>
|
[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 {
|
.tb-login-content {
|
||||||
margin-top: 36px;
|
margin-top: 36px;
|
||||||
margin-bottom: 76px;
|
margin-bottom: 76px;
|
||||||
background-color: rgb(250, 250, 250);
|
background-color: #eee;
|
||||||
.tb-login-form {
|
.tb-login-form {
|
||||||
@media #{$mat-gt-xs} {
|
@media #{$mat-gt-xs} {
|
||||||
width: 550px !important;
|
width: 550px !important;
|
||||||
|
|||||||
@ -433,7 +433,57 @@
|
|||||||
"no-telemetry-text": "No telemetry found"
|
"no-telemetry-text": "No telemetry found"
|
||||||
},
|
},
|
||||||
"api-usage": {
|
"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-log": {
|
||||||
"audit": "Audit",
|
"audit": "Audit",
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" style="width: 100%;">
|
<html lang="en" style="width: 100%; height: 100%;">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>ThingsBoard</title>
|
<title>ThingsBoard</title>
|
||||||
@ -24,8 +24,82 @@
|
|||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="thingsboard.ico">
|
<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>
|
</head>
|
||||||
<body class="tb-default">
|
<body class="tb-default">
|
||||||
<tb-root></tb-root>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -41,7 +41,7 @@ body, html {
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: rgb(250,250,250);
|
background-color: #eee;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user