Commit 409fb1f2bc9577a2cb829941b552b76eedff07b1

Authored by ShvaykaD
2 parents a583f9fa 87991dea

Merge branch 'master' of github.com:thingsboard/thingsboard

Showing 25 changed files with 353 additions and 209 deletions
@@ -61,6 +61,7 @@ import org.thingsboard.server.service.telemetry.InternalTelemetryService; @@ -61,6 +61,7 @@ import org.thingsboard.server.service.telemetry.InternalTelemetryService;
61 import javax.annotation.PostConstruct; 61 import javax.annotation.PostConstruct;
62 import javax.annotation.PreDestroy; 62 import javax.annotation.PreDestroy;
63 import java.util.ArrayList; 63 import java.util.ArrayList;
  64 +import java.util.Arrays;
64 import java.util.HashSet; 65 import java.util.HashSet;
65 import java.util.List; 66 import java.util.List;
66 import java.util.Map; 67 import java.util.Map;
@@ -73,6 +74,7 @@ import java.util.concurrent.Executors; @@ -73,6 +74,7 @@ import java.util.concurrent.Executors;
73 import java.util.concurrent.TimeUnit; 74 import java.util.concurrent.TimeUnit;
74 import java.util.concurrent.locks.Lock; 75 import java.util.concurrent.locks.Lock;
75 import java.util.concurrent.locks.ReentrantLock; 76 import java.util.concurrent.locks.ReentrantLock;
  77 +import java.util.stream.Collectors;
76 78
77 @Slf4j 79 @Slf4j
78 @Service 80 @Service
@@ -347,16 +349,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @@ -347,16 +349,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
347 try { 349 try {
348 long now = System.currentTimeMillis(); 350 long now = System.currentTimeMillis();
349 myTenantStates.values().forEach(state -> { 351 myTenantStates.values().forEach(state -> {
350 - if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) { 352 + if ((state.getNextCycleTs() < now) && (now - state.getNextCycleTs() < TimeUnit.HOURS.toMillis(1))) {
351 TenantId tenantId = state.getTenantId(); 353 TenantId tenantId = state.getTenantId();
352 state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); 354 state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth());
353 - ToUsageStatsServiceMsg.Builder msg = ToUsageStatsServiceMsg.newBuilder();  
354 - msg.setTenantIdMSB(tenantId.getId().getMostSignificantBits());  
355 - msg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());  
356 - for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) {  
357 - msg.addValues(UsageStatsKVProto.newBuilder().setKey(key.name()).setValue(0).build());  
358 - }  
359 - process(new TbProtoQueueMsg<>(UUID.randomUUID(), msg.build()), TbCallback.EMPTY); 355 + saveNewCounts(state, Arrays.asList(ApiUsageRecordKey.values()));
  356 + updateTenantState(state, tenantProfileCache.get(tenantId));
360 } 357 }
361 }); 358 });
362 } finally { 359 } finally {
@@ -364,6 +361,14 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @@ -364,6 +361,14 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
364 } 361 }
365 } 362 }
366 363
  364 + private void saveNewCounts(TenantApiUsageState state, List<ApiUsageRecordKey> keys) {
  365 + List<TsKvEntry> counts = keys.stream()
  366 + .map(key -> new BasicTsKvEntry(state.getCurrentCycleTs(), new LongDataEntry(key.getApiCountKey(), 0L)))
  367 + .collect(Collectors.toList());
  368 +
  369 + tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), counts, VOID_CALLBACK);
  370 + }
  371 +
367 private TenantApiUsageState getOrFetchState(TenantId tenantId) { 372 private TenantApiUsageState getOrFetchState(TenantId tenantId) {
368 TenantApiUsageState tenantState = myTenantStates.get(tenantId); 373 TenantApiUsageState tenantState = myTenantStates.get(tenantId);
369 if (tenantState == null) { 374 if (tenantState == null) {
@@ -377,6 +382,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @@ -377,6 +382,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
377 } 382 }
378 TenantProfile tenantProfile = tenantProfileCache.get(tenantId); 383 TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
379 tenantState = new TenantApiUsageState(tenantProfile, dbStateEntity); 384 tenantState = new TenantApiUsageState(tenantProfile, dbStateEntity);
  385 + List<ApiUsageRecordKey> newCounts = new ArrayList<>();
380 try { 386 try {
381 List<TsKvEntry> dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getId()).get(); 387 List<TsKvEntry> dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getId()).get();
382 for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { 388 for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) {
@@ -385,7 +391,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @@ -385,7 +391,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
385 for (TsKvEntry tsKvEntry : dbValues) { 391 for (TsKvEntry tsKvEntry : dbValues) {
386 if (tsKvEntry.getKey().equals(key.getApiCountKey())) { 392 if (tsKvEntry.getKey().equals(key.getApiCountKey())) {
387 cycleEntryFound = true; 393 cycleEntryFound = true;
388 - tenantState.put(key, tsKvEntry.getTs() == tenantState.getCurrentCycleTs() ? tsKvEntry.getLongValue().get() : 0L); 394 +
  395 + boolean oldCount = tsKvEntry.getTs() == tenantState.getCurrentCycleTs();
  396 + tenantState.put(key, oldCount ? tsKvEntry.getLongValue().get() : 0L);
  397 +
  398 + if (!oldCount) {
  399 + newCounts.add(key);
  400 + }
389 } else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) { 401 } else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) {
390 hourlyEntryFound = true; 402 hourlyEntryFound = true;
391 tenantState.putHourly(key, tsKvEntry.getTs() == tenantState.getCurrentHourTs() ? tsKvEntry.getLongValue().get() : 0L); 403 tenantState.putHourly(key, tsKvEntry.getTs() == tenantState.getCurrentHourTs() ? tsKvEntry.getLongValue().get() : 0L);
@@ -397,6 +409,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @@ -397,6 +409,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
397 } 409 }
398 log.debug("[{}] Initialized state: {}", tenantId, dbStateEntity); 410 log.debug("[{}] Initialized state: {}", tenantId, dbStateEntity);
399 myTenantStates.put(tenantId, tenantState); 411 myTenantStates.put(tenantId, tenantState);
  412 + saveNewCounts(tenantState, newCounts);
400 } catch (InterruptedException | ExecutionException e) { 413 } catch (InterruptedException | ExecutionException e) {
401 log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); 414 log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e);
402 } 415 }
@@ -17,22 +17,19 @@ package org.thingsboard.server.common.msg.tools; @@ -17,22 +17,19 @@ package org.thingsboard.server.common.msg.tools;
17 17
18 import java.time.LocalDate; 18 import java.time.LocalDate;
19 import java.time.LocalDateTime; 19 import java.time.LocalDateTime;
20 -import java.time.LocalTime;  
21 import java.time.ZoneId; 20 import java.time.ZoneId;
22 -import java.time.ZoneOffset;  
23 import java.time.temporal.ChronoUnit; 21 import java.time.temporal.ChronoUnit;
24 import java.time.temporal.TemporalAdjuster; 22 import java.time.temporal.TemporalAdjuster;
25 import java.time.temporal.TemporalAdjusters; 23 import java.time.temporal.TemporalAdjusters;
26 -import java.time.temporal.TemporalUnit;  
27 import java.util.concurrent.ConcurrentHashMap; 24 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.ConcurrentMap; 25 import java.util.concurrent.ConcurrentMap;
29 26
  27 +import static java.time.ZoneOffset.UTC;
30 import static java.time.temporal.ChronoField.DAY_OF_MONTH; 28 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
31 import static java.time.temporal.ChronoUnit.MONTHS; 29 import static java.time.temporal.ChronoUnit.MONTHS;
32 30
33 public class SchedulerUtils { 31 public class SchedulerUtils {
34 32
35 - private final static ZoneId UTC = ZoneId.of("UTC");  
36 private static final ConcurrentMap<String, ZoneId> tzMap = new ConcurrentHashMap<>(); 33 private static final ConcurrentMap<String, ZoneId> tzMap = new ConcurrentHashMap<>();
37 34
38 public static ZoneId getZoneId(String tz) { 35 public static ZoneId getZoneId(String tz) {
@@ -44,7 +41,7 @@ public class SchedulerUtils { @@ -44,7 +41,7 @@ public class SchedulerUtils {
44 } 41 }
45 42
46 public static long getStartOfCurrentHour(ZoneId zoneId) { 43 public static long getStartOfCurrentHour(ZoneId zoneId) {
47 - return LocalDateTime.now(ZoneOffset.UTC).atZone(zoneId).truncatedTo(ChronoUnit.HOURS).toInstant().toEpochMilli(); 44 + return LocalDateTime.now(UTC).atZone(zoneId).truncatedTo(ChronoUnit.HOURS).toInstant().toEpochMilli();
48 } 45 }
49 46
50 public static long getStartOfCurrentMonth() { 47 public static long getStartOfCurrentMonth() {
@@ -52,7 +49,7 @@ public class SchedulerUtils { @@ -52,7 +49,7 @@ public class SchedulerUtils {
52 } 49 }
53 50
54 public static long getStartOfCurrentMonth(ZoneId zoneId) { 51 public static long getStartOfCurrentMonth(ZoneId zoneId) {
55 - return LocalDate.now().withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli(); 52 + return LocalDate.now(UTC).withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli();
56 } 53 }
57 54
58 public static long getStartOfNextMonth() { 55 public static long getStartOfNextMonth() {
@@ -60,7 +57,7 @@ public class SchedulerUtils { @@ -60,7 +57,7 @@ public class SchedulerUtils {
60 } 57 }
61 58
62 public static long getStartOfNextMonth(ZoneId zoneId) { 59 public static long getStartOfNextMonth(ZoneId zoneId) {
63 - return LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); 60 + return LocalDate.now(UTC).with(TemporalAdjusters.firstDayOfNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli();
64 } 61 }
65 62
66 public static long getStartOfNextNextMonth() { 63 public static long getStartOfNextNextMonth() {
@@ -68,7 +65,7 @@ public class SchedulerUtils { @@ -68,7 +65,7 @@ public class SchedulerUtils {
68 } 65 }
69 66
70 public static long getStartOfNextNextMonth(ZoneId zoneId) { 67 public static long getStartOfNextNextMonth(ZoneId zoneId) {
71 - return LocalDate.now().with(firstDayOfNextNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); 68 + return LocalDate.now(UTC).with(firstDayOfNextNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli();
72 } 69 }
73 70
74 public static TemporalAdjuster firstDayOfNextNextMonth() { 71 public static TemporalAdjuster firstDayOfNextNextMonth() {
@@ -17,4 +17,4 @@ @@ -17,4 +17,4 @@
17 --> 17 -->
18 <!--The content below is only a placeholder and can be replaced.--> 18 <!--The content below is only a placeholder and can be replaced.-->
19 19
20 -<router-outlet></router-outlet> 20 +<router-outlet (activate)="onActivateComponent($event)"></router-outlet>
@@ -62,7 +62,7 @@ export class AppComponent implements OnInit { @@ -62,7 +62,7 @@ export class AppComponent implements OnInit {
62 this.matIconRegistry.addSvgIconLiteral( 62 this.matIconRegistry.addSvgIconLiteral(
63 'alpha-e-circle-outline', 63 'alpha-e-circle-outline',
64 this.domSanitizer.bypassSecurityTrustHtml( 64 this.domSanitizer.bypassSecurityTrustHtml(
65 - '<svg viewBox="0 0 24 24"><path d="M9,7H15V9H11V11H15V13H11V15H15V17H9V7M12,2A10,10 0 0,'+ 65 + '<svg viewBox="0 0 24 24"><path d="M9,7H15V9H11V11H15V13H11V15H15V17H9V7M12,2A10,10 0 0,' +
66 '1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 ' + 66 '1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 ' +
67 '0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" /></svg>' 67 '0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" /></svg>'
68 ) 68 )
@@ -124,5 +124,11 @@ export class AppComponent implements OnInit { @@ -124,5 +124,11 @@ export class AppComponent implements OnInit {
124 ngOnInit() { 124 ngOnInit() {
125 } 125 }
126 126
127 -} 127 + onActivateComponent($event: any) {
  128 + const loadingElement = $('div#tb-loading-spinner');
  129 + if (loadingElement.length) {
  130 + loadingElement.remove();
  131 + }
  132 + }
128 133
  134 +}
@@ -198,7 +198,7 @@ export class WidgetSubscriptionContext { @@ -198,7 +198,7 @@ export class WidgetSubscriptionContext {
198 export type SubscriptionMessageSeverity = 'info' | 'warn' | 'error' | 'success'; 198 export type SubscriptionMessageSeverity = 'info' | 'warn' | 'error' | 'success';
199 199
200 export interface SubscriptionMessage { 200 export interface SubscriptionMessage {
201 - severity: SubscriptionMessageSeverity, 201 + severity: SubscriptionMessageSeverity;
202 message: string; 202 message: string;
203 } 203 }
204 204
@@ -90,7 +90,7 @@ @@ -90,7 +90,7 @@
90 matTooltipPosition="above" 90 matTooltipPosition="above"
91 class="mat-subheading-2 title"> 91 class="mat-subheading-2 title">
92 <mat-icon *ngIf="widget.showTitleIcon" [ngStyle]="widget.titleIconStyle">{{widget.titleIcon}}</mat-icon> 92 <mat-icon *ngIf="widget.showTitleIcon" [ngStyle]="widget.titleIconStyle">{{widget.titleIcon}}</mat-icon>
93 - {{widget.title}} 93 + {{widget.customTranslatedTitle}}
94 </span> 94 </span>
95 <tb-timewindow *ngIf="widget.hasTimewindow" 95 <tb-timewindow *ngIf="widget.hasTimewindow"
96 #timewindowComponent 96 #timewindowComponent
@@ -54,6 +54,7 @@ import { MatMenuTrigger } from '@angular/material/menu'; @@ -54,6 +54,7 @@ import { MatMenuTrigger } from '@angular/material/menu';
54 import { SafeStyle } from '@angular/platform-browser'; 54 import { SafeStyle } from '@angular/platform-browser';
55 import { distinct } from 'rxjs/operators'; 55 import { distinct } from 'rxjs/operators';
56 import { ResizeObserver } from '@juggle/resize-observer'; 56 import { ResizeObserver } from '@juggle/resize-observer';
  57 +import { UtilsService } from '@core/services/utils.service';
57 58
58 @Component({ 59 @Component({
59 selector: 'tb-dashboard', 60 selector: 'tb-dashboard',
@@ -168,6 +169,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo @@ -168,6 +169,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
168 private gridsterResize$: ResizeObserver; 169 private gridsterResize$: ResizeObserver;
169 170
170 constructor(protected store: Store<AppState>, 171 constructor(protected store: Store<AppState>,
  172 + public utils: UtilsService,
171 private timeService: TimeService, 173 private timeService: TimeService,
172 private dialogService: DialogService, 174 private dialogService: DialogService,
173 private breakpointObserver: BreakpointObserver, 175 private breakpointObserver: BreakpointObserver,
@@ -17,28 +17,28 @@ @@ -17,28 +17,28 @@
17 --> 17 -->
18 <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="filterPredicateValueFormGroup"> 18 <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="filterPredicateValueFormGroup">
19 <div fxFlex fxLayout="column" [fxShow]="!dynamicMode"> 19 <div fxFlex fxLayout="column" [fxShow]="!dynamicMode">
20 - <div fxFlex fxLayout="column" [ngSwitch]="valueType"> 20 + <div fxLayout="column" [ngSwitch]="valueType">
21 <ng-template [ngSwitchCase]="valueTypeEnum.STRING"> 21 <ng-template [ngSwitchCase]="valueTypeEnum.STRING">
22 - <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block"> 22 + <mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
23 <mat-label></mat-label> 23 <mat-label></mat-label>
24 <input matInput formControlName="defaultValue" placeholder="{{'filter.value' | translate}}"> 24 <input matInput formControlName="defaultValue" placeholder="{{'filter.value' | translate}}">
25 </mat-form-field> 25 </mat-form-field>
26 </ng-template> 26 </ng-template>
27 <ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC"> 27 <ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC">
28 - <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block"> 28 + <mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
29 <mat-label></mat-label> 29 <mat-label></mat-label>
30 <input required type="number" matInput formControlName="defaultValue" 30 <input required type="number" matInput formControlName="defaultValue"
31 placeholder="{{'filter.value' | translate}}"> 31 placeholder="{{'filter.value' | translate}}">
32 </mat-form-field> 32 </mat-form-field>
33 </ng-template> 33 </ng-template>
34 <ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME"> 34 <ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
35 - <tb-datetime fxFlex formControlName="defaultValue" 35 + <tb-datetime formControlName="defaultValue"
36 dateText="filter.date" 36 dateText="filter.date"
37 timeText="filter.time" 37 timeText="filter.time"
38 required [showLabel]="false"></tb-datetime> 38 required [showLabel]="false"></tb-datetime>
39 </ng-template> 39 </ng-template>
40 <ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN"> 40 <ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN">
41 - <mat-checkbox fxFlex formControlName="defaultValue"> 41 + <mat-checkbox formControlName="defaultValue">
42 {{ (filterPredicateValueFormGroup.get('defaultValue').value ? 'value.true' : 'value.false') | translate }} 42 {{ (filterPredicateValueFormGroup.get('defaultValue').value ? 'value.true' : 'value.false') | translate }}
43 </mat-checkbox> 43 </mat-checkbox>
44 </ng-template> 44 </ng-template>
@@ -46,9 +46,9 @@ @@ -46,9 +46,9 @@
46 <div class="tb-hint" translate>filter.default-value</div> 46 <div class="tb-hint" translate>filter.default-value</div>
47 </div> 47 </div>
48 <div fxFlex fxLayout="column" [fxShow]="dynamicMode"> 48 <div fxFlex fxLayout="column" [fxShow]="dynamicMode">
49 - <div fxFlex formGroupName="dynamicValue" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> 49 + <div formGroupName="dynamicValue" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
50 <div fxFlex fxLayout="column"> 50 <div fxFlex fxLayout="column">
51 - <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block"> 51 + <mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
52 <mat-label></mat-label> 52 <mat-label></mat-label>
53 <mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}"> 53 <mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}">
54 <mat-option [value]="null"> 54 <mat-option [value]="null">
@@ -62,7 +62,7 @@ @@ -62,7 +62,7 @@
62 <div class="tb-hint" translate>filter.dynamic-source-type</div> 62 <div class="tb-hint" translate>filter.dynamic-source-type</div>
63 </div> 63 </div>
64 <div fxFlex fxLayout="column"> 64 <div fxFlex fxLayout="column">
65 - <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block"> 65 + <mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
66 <mat-label></mat-label> 66 <mat-label></mat-label>
67 <input matInput formControlName="sourceAttribute" placeholder="{{'filter.source-attribute' | translate}}"> 67 <input matInput formControlName="sourceAttribute" placeholder="{{'filter.source-attribute' | translate}}">
68 </mat-form-field> 68 </mat-form-field>
@@ -27,10 +27,10 @@ @@ -27,10 +27,10 @@
27 </mat-toolbar> 27 </mat-toolbar>
28 <div mat-dialog-content> 28 <div mat-dialog-content>
29 <fieldset [disabled]="isLoading$ | async" fxLayout="column"> 29 <fieldset [disabled]="isLoading$ | async" fxLayout="column">
30 - <mat-checkbox fxFlex formControlName="editable" style="margin-bottom: 16px;"> 30 + <mat-checkbox formControlName="editable" style="margin-bottom: 16px;">
31 {{ 'filter.editable' | translate }} 31 {{ 'filter.editable' | translate }}
32 </mat-checkbox> 32 </mat-checkbox>
33 - <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> 33 + <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
34 <mat-form-field fxFlex class="mat-block"> 34 <mat-form-field fxFlex class="mat-block">
35 <mat-label translate>filter.display-label</mat-label> 35 <mat-label translate>filter.display-label</mat-label>
36 <input matInput formControlName="label"> 36 <input matInput formControlName="label">
@@ -39,7 +39,7 @@ @@ -39,7 +39,7 @@
39 {{ 'filter.autogenerated-label' | translate }} 39 {{ 'filter.autogenerated-label' | translate }}
40 </mat-checkbox> 40 </mat-checkbox>
41 </div> 41 </div>
42 - <mat-form-field fxFlex class="mat-block"> 42 + <mat-form-field class="mat-block">
43 <mat-label translate>filter.order-priority</mat-label> 43 <mat-label translate>filter.order-priority</mat-label>
44 <input matInput type="number" formControlName="order"> 44 <input matInput type="number" formControlName="order">
45 </mat-form-field> 45 </mat-form-field>
@@ -16,8 +16,8 @@ @@ -16,8 +16,8 @@
16 16
17 --> 17 -->
18 <div fxLayout="column" class="mat-content mat-padding"> 18 <div fxLayout="column" class="mat-content mat-padding">
19 - <div fxLayout="column" *ngFor="let filter of filtersInfo | keyvalue; let last = last">  
20 - <div fxFlex fxLayout="row" fxLayoutAlign="start center"> 19 + <div *ngFor="let filter of filtersInfo | keyvalue; let last = last">
  20 + <div fxLayout="row" fxLayoutAlign="start center">
21 <mat-label fxFlex>{{filter.value.filter}}</mat-label> 21 <mat-label fxFlex>{{filter.value.filter}}</mat-label>
22 <button mat-icon-button color="primary" 22 <button mat-icon-button color="primary"
23 style="min-width: 40px;" 23 style="min-width: 40px;"
@@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
41 <span *ngIf="$index > 0" translate>filter.operation.and</span> 41 <span *ngIf="$index > 0" translate>filter.operation.and</span>
42 </div> 42 </div>
43 <div fxLayout="column" fxFlex="92"> 43 <div fxLayout="column" fxFlex="92">
44 - <div fxLayout="row" fxLayoutAlign="start center" fxFlex> 44 + <div fxLayout="row" fxLayoutAlign="start center">
45 <div fxFlex>{{ keyFilterControl.value.key.key }}</div> 45 <div fxFlex>{{ keyFilterControl.value.key.key }}</div>
46 <div fxFlex translate>{{ entityKeyTypeTranslations.get(keyFilterControl.value.key.type) }}</div> 46 <div fxFlex translate>{{ entityKeyTypeTranslations.get(keyFilterControl.value.key.type) }}</div>
47 <button mat-icon-button color="primary" 47 <button mat-icon-button color="primary"
@@ -29,37 +29,33 @@ @@ -29,37 +29,33 @@
29 </mat-progress-bar> 29 </mat-progress-bar>
30 <div mat-dialog-content> 30 <div mat-dialog-content>
31 <fieldset [disabled]="isLoading$ | async"> 31 <fieldset [disabled]="isLoading$ | async">
32 - <div fxFlex fxLayout="column">  
33 - <div fxFlex fxLayout="row" fxLayoutAlign="start center"  
34 - formArrayName="userInputs"  
35 - *ngFor="let userInputControl of userInputsFormArray().controls; let $index = index">  
36 - <div fxFlex fxLayout="column"  
37 - [ngSwitch]="userInputControl.get('valueType').value">  
38 - <ng-template [ngSwitchCase]="valueTypeEnum.STRING">  
39 - <mat-form-field fxFlex class="mat-block">  
40 - <mat-label>{{ userInputControl.get('label').value }}</mat-label>  
41 - <input matInput [formControl]="userInputControl.get('value')">  
42 - </mat-form-field>  
43 - </ng-template>  
44 - <ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC">  
45 - <mat-form-field fxFlex class="mat-block">  
46 - <mat-label>{{ userInputControl.get('label').value }}</mat-label>  
47 - <input required type="number" matInput [formControl]="userInputControl.get('value')">  
48 - </mat-form-field>  
49 - </ng-template>  
50 - <ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">  
51 - <label class="tb-title no-padding tb-required">{{ userInputControl.get('label').value }}</label>  
52 - <tb-datetime fxFlex [formControl]="userInputControl.get('value')"  
53 - dateText="filter.date"  
54 - timeText="filter.time"  
55 - required [showLabel]="false"></tb-datetime>  
56 - </ng-template>  
57 - <ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN">  
58 - <mat-checkbox labelPosition="before" fxFlex [formControl]="userInputControl.get('value')">  
59 - {{ userInputControl.get('label').value }}  
60 - </mat-checkbox>  
61 - </ng-template>  
62 - </div> 32 + <div formArrayName="userInputs"
  33 + *ngFor="let userInputControl of userInputsFormArray().controls; let $index = index">
  34 + <div [ngSwitch]="userInputControl.get('valueType').value">
  35 + <ng-template [ngSwitchCase]="valueTypeEnum.STRING">
  36 + <mat-form-field class="mat-block">
  37 + <mat-label>{{ userInputControl.get('label').value }}</mat-label>
  38 + <input matInput [formControl]="userInputControl.get('value')">
  39 + </mat-form-field>
  40 + </ng-template>
  41 + <ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC">
  42 + <mat-form-field class="mat-block">
  43 + <mat-label>{{ userInputControl.get('label').value }}</mat-label>
  44 + <input required type="number" matInput [formControl]="userInputControl.get('value')">
  45 + </mat-form-field>
  46 + </ng-template>
  47 + <ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
  48 + <label class="tb-title no-padding tb-required">{{ userInputControl.get('label').value }}</label>
  49 + <tb-datetime [formControl]="userInputControl.get('value')"
  50 + dateText="filter.date"
  51 + timeText="filter.time"
  52 + required [showLabel]="false"></tb-datetime>
  53 + </ng-template>
  54 + <ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN">
  55 + <mat-checkbox labelPosition="before" [formControl]="userInputControl.get('value')">
  56 + {{ userInputControl.get('label').value }}
  57 + </mat-checkbox>
  58 + </ng-template>
63 </div> 59 </div>
64 </div> 60 </div>
65 </fieldset> 61 </fieldset>
@@ -51,7 +51,7 @@ @@ -51,7 +51,7 @@
51 <span translate fxLayoutAlign="center center" style="margin: 16px 0" 51 <span translate fxLayoutAlign="center center" style="margin: 16px 0"
52 class="tb-prompt required">device-profile.add-create-alarm-rule-prompt</span> 52 class="tb-prompt required">device-profile.add-create-alarm-rule-prompt</span>
53 </div> 53 </div>
54 - <div fxLayout="row" *ngIf="!disabled"> 54 + <div *ngIf="!disabled">
55 <button mat-stroked-button color="primary" 55 <button mat-stroked-button color="primary"
56 type="button" 56 type="button"
57 (click)="addCreateAlarmRule()" 57 (click)="addCreateAlarmRule()"
@@ -83,9 +83,9 @@ @@ -83,9 +83,9 @@
83 <tb-create-alarm-rules formControlName="createRules" style="padding-bottom: 16px;"> 83 <tb-create-alarm-rules formControlName="createRules" style="padding-bottom: 16px;">
84 </tb-create-alarm-rules> 84 </tb-create-alarm-rules>
85 <div translate class="tb-small" style="padding-bottom: 8px;">device-profile.clear-alarm-rule</div> 85 <div translate class="tb-small" style="padding-bottom: 8px;">device-profile.clear-alarm-rule</div>
86 - <div fxFlex fxLayout="row" 86 + <div fxLayout="row" fxLayoutGap="8px;" fxLayoutAlign="start center"
87 [fxShow]="alarmFormGroup.get('clearRule').value" 87 [fxShow]="alarmFormGroup.get('clearRule').value"
88 - fxLayoutGap="8px;" fxLayoutAlign="start center" style="padding-bottom: 8px;"> 88 + style="padding-bottom: 8px;">
89 <div class="clear-alarm-rule" fxFlex fxLayout="row"> 89 <div class="clear-alarm-rule" fxFlex fxLayout="row">
90 <tb-alarm-rule formControlName="clearRule" fxFlex> 90 <tb-alarm-rule formControlName="clearRule" fxFlex>
91 </tb-alarm-rule> 91 </tb-alarm-rule>
@@ -103,8 +103,7 @@ @@ -103,8 +103,7 @@
103 <span translate fxLayoutAlign="center center" style="margin: 16px 0" 103 <span translate fxLayoutAlign="center center" style="margin: 16px 0"
104 class="tb-prompt">device-profile.no-clear-alarm-rule</span> 104 class="tb-prompt">device-profile.no-clear-alarm-rule</span>
105 </div> 105 </div>
106 - <div fxLayout="row" *ngIf="!disabled"  
107 - [fxShow]="!alarmFormGroup.get('clearRule').value"> 106 + <div *ngIf="!disabled" [fxShow]="!alarmFormGroup.get('clearRule').value">
108 <button mat-stroked-button color="primary" 107 <button mat-stroked-button color="primary"
109 type="button" 108 type="button"
110 (click)="addClearAlarmRule()" 109 (click)="addClearAlarmRule()"
@@ -30,8 +30,7 @@ @@ -30,8 +30,7 @@
30 <span translate fxLayoutAlign="center center" 30 <span translate fxLayoutAlign="center center"
31 class="tb-prompt">device-profile.no-alarm-rules</span> 31 class="tb-prompt">device-profile.no-alarm-rules</span>
32 </div> 32 </div>
33 - <div *ngIf="!disabled" fxFlex fxLayout="row" fxLayoutAlign="start center"  
34 - style="padding-top: 16px;"> 33 + <div *ngIf="!disabled" style="padding-top: 16px;">
35 <button mat-raised-button color="primary" 34 <button mat-raised-button color="primary"
36 type="button" 35 type="button"
37 (click)="addAlarm()" 36 (click)="addAlarm()"
@@ -39,7 +39,7 @@ @@ -39,7 +39,7 @@
39 </button> 39 </button>
40 </div> 40 </div>
41 </div> 41 </div>
42 -<div class="mat-padding" fxLayout="column"> 42 +<div [ngClass]="{'mat-padding': !standalone}" fxLayout="column">
43 <form [formGroup]="entityForm"> 43 <form [formGroup]="entityForm">
44 <fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;"> 44 <fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
45 <mat-form-field class="mat-block"> 45 <mat-form-field class="mat-block">
@@ -24,6 +24,7 @@ import { guid, isDefined, isEqual, isUndefined } from '@app/core/utils'; @@ -24,6 +24,7 @@ import { guid, isDefined, isEqual, isUndefined } from '@app/core/utils';
24 import { IterableDiffer, KeyValueDiffer } from '@angular/core'; 24 import { IterableDiffer, KeyValueDiffer } from '@angular/core';
25 import { IAliasController, IStateController } from '@app/core/api/widget-api.models'; 25 import { IAliasController, IStateController } from '@app/core/api/widget-api.models';
26 import { enumerable } from '@shared/decorators/enumerable'; 26 import { enumerable } from '@shared/decorators/enumerable';
  27 +import { UtilsService } from '@core/services/utils.service';
27 28
28 export interface WidgetsData { 29 export interface WidgetsData {
29 widgets: Array<Widget>; 30 widgets: Array<Widget>;
@@ -56,6 +57,7 @@ export interface DashboardCallbacks { @@ -56,6 +57,7 @@ export interface DashboardCallbacks {
56 } 57 }
57 58
58 export interface IDashboardComponent { 59 export interface IDashboardComponent {
  60 + utils: UtilsService;
59 gridsterOpts: GridsterConfig; 61 gridsterOpts: GridsterConfig;
60 gridster: GridsterComponent; 62 gridster: GridsterComponent;
61 dashboardWidgets: DashboardWidgets; 63 dashboardWidgets: DashboardWidgets;
@@ -295,6 +297,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { @@ -295,6 +297,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
295 margin: string; 297 margin: string;
296 298
297 title: string; 299 title: string;
  300 + customTranslatedTitle: string;
298 titleTooltip: string; 301 titleTooltip: string;
299 showTitle: boolean; 302 showTitle: boolean;
300 titleStyle: {[klass: string]: any}; 303 titleStyle: {[klass: string]: any};
@@ -358,8 +361,10 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { @@ -358,8 +361,10 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
358 361
359 this.title = isDefined(this.widgetContext.widgetTitle) 362 this.title = isDefined(this.widgetContext.widgetTitle)
360 && this.widgetContext.widgetTitle.length ? this.widgetContext.widgetTitle : this.widget.config.title; 363 && this.widgetContext.widgetTitle.length ? this.widgetContext.widgetTitle : this.widget.config.title;
  364 + this.customTranslatedTitle = this.dashboard.utils.customTranslation(this.title, this.title);
361 this.titleTooltip = isDefined(this.widgetContext.widgetTitleTooltip) 365 this.titleTooltip = isDefined(this.widgetContext.widgetTitleTooltip)
362 && this.widgetContext.widgetTitleTooltip.length ? this.widgetContext.widgetTitleTooltip : this.widget.config.titleTooltip; 366 && this.widgetContext.widgetTitleTooltip.length ? this.widgetContext.widgetTitleTooltip : this.widget.config.titleTooltip;
  367 + this.titleTooltip = this.dashboard.utils.customTranslation(this.titleTooltip, this.titleTooltip);
363 this.showTitle = isDefined(this.widget.config.showTitle) ? this.widget.config.showTitle : true; 368 this.showTitle = isDefined(this.widget.config.showTitle) ? this.widget.config.showTitle : true;
364 this.titleStyle = this.widget.config.titleStyle ? this.widget.config.titleStyle : {}; 369 this.titleStyle = this.widget.config.titleStyle ? this.widget.config.titleStyle : {};
365 370
@@ -61,8 +61,8 @@ @@ -61,8 +61,8 @@
61 <div [formGroupName]="n" fxLayout="row" fxLayoutGap="8px"> 61 <div [formGroupName]="n" fxLayout="row" fxLayoutGap="8px">
62 <div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px"> 62 <div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
63 <div fxLayout="column" fxFlex.sm="60" fxFlex.gt-sm="50"> 63 <div fxLayout="column" fxFlex.sm="60" fxFlex.gt-sm="50">
64 - <div fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">  
65 - <mat-form-field fxFlex="30" fxFlex.xs class="mat-block"> 64 + <div fxLayout="row" fxLayout.xs="column" fxLayout.md="column" fxLayoutGap="8px" fxLayoutGap.md="0px">
  65 + <mat-form-field fxFlex="30" fxFlex.md fxFlex.xs class="mat-block">
66 <mat-label translate>admin.oauth2.protocol</mat-label> 66 <mat-label translate>admin.oauth2.protocol</mat-label>
67 <mat-select formControlName="scheme"> 67 <mat-select formControlName="scheme">
68 <mat-option *ngFor="let protocol of protocols" [value]="protocol"> 68 <mat-option *ngFor="let protocol of protocols" [value]="protocol">
@@ -18,7 +18,12 @@ @@ -18,7 +18,12 @@
18 :host { 18 :host {
19 mat-card.settings-card { 19 mat-card.settings-card {
20 margin: 8px; 20 margin: 8px;
21 - @media #{$mat-gt-sm} { 21 +
  22 + @media #{$mat-md} {
  23 + width: 80%;
  24 + }
  25 +
  26 + @media #{$mat-gt-md} {
22 width: 60%; 27 width: 60%;
23 } 28 }
24 } 29 }
@@ -95,7 +95,7 @@ @@ -95,7 +95,7 @@
95 "decimals": null, 95 "decimals": null,
96 "funcBody": null, 96 "funcBody": null,
97 "usePostProcessing": true, 97 "usePostProcessing": true,
98 - "postFuncBody": "return \"JavaScript\";" 98 + "postFuncBody": "return \"{i18n:api-usage.javascript}\";"
99 }, 99 },
100 { 100 {
101 "name": "jsExecutionApiState", 101 "name": "jsExecutionApiState",
@@ -108,7 +108,7 @@ @@ -108,7 +108,7 @@
108 "decimals": null, 108 "decimals": null,
109 "funcBody": null, 109 "funcBody": null,
110 "usePostProcessing": true, 110 "usePostProcessing": true,
111 - "postFuncBody": "return \"Executions\";" 111 + "postFuncBody": "return \"{i18n:api-usage.executions}\";"
112 } 112 }
113 ] 113 ]
114 } 114 }
@@ -123,8 +123,8 @@ @@ -123,8 +123,8 @@
123 "color": "#666666", 123 "color": "#666666",
124 "padding": "0", 124 "padding": "0",
125 "settings": { 125 "settings": {
126 - "cardHtml": "<div class='card ${apiStateClass}'>\n <img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" onload=\"initializeCard_${cardId:0}(event);\">\n <script type=\"text/javascript\">\n function initializeCard_${cardId:0}(e) {\n \n function toShortNumber(number) {\n var rounder = Math.pow(10, 1);\n var powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n \n var key = '';\n \n for (var i = 0; i < powers.length; i++) {\n var reduced = number / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = powers[i].key;\n break;\n }\n }\n return number + key;\n }\n \n var imgTag = e.target;\n var parentTag = imgTag.parentNode;\n var count = \"${count:0}\";\n var limit = \"${limit:0}\";\n count = count.length > 0 ? parseInt(count, 10) : 0;\n limit = limit.length > 0 ? parseInt(limit, 10) : 0;\n var percentElement = $('#api-usage-percent', parentTag);\n var valueElement = $('#api-usage-value', parentTag);\n var barElement = $('#api-usage-bar', parentTag);\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n barElement.width(percent + '%');\n percent = percent.toFixed(2);\n percentElement.text(percent + '%');\n valueElement.text(toShortNumber(count) + ' / ' + toShortNumber(limit));\n } else {\n barElement.width('0%');\n percentElement.text('');\n valueElement.text(toShortNumber(count) + ' / unlimited');\n }\n }\n </script>\n <div class='content'>\n <div class='column'>\n <div class='title-row'>\n <div class='title'>${title}</div>\n <div class='state'>${apiState}</div>\n </div> \n <div class='bar-container'>\n <div class=\"unit\">${unit}</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar\"></div>\n </div> \n <div class='bar-labels'>\n <div id=\"api-usage-percent\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value\"></div>\n </div>\n </div> \n </div>\n </div>\n <div role=\"separator\" class=\"mat-divider mat-divider-horizontal\" aria-orientation=\"horizontal\"></div>\n <div class='action-row'>\n <button id=\"javascript_functions_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">View details</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n </div> \n</div>",  
127 - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" 126 + "cardHtml": "<div class='card ${apiStateClass}'>\n <img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" onload=\"initializeCard_${cardId:0}(event);\">\n <script type=\"text/javascript\">\n function initializeCard_${cardId:0}(e) {\n \n function toShortNumber(number) {\n var rounder = Math.pow(10, 1);\n var powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n \n var key = '';\n \n for (var i = 0; i < powers.length; i++) {\n var reduced = number / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = powers[i].key;\n break;\n }\n }\n return number + key;\n }\n \n var imgTag = e.target;\n var parentTag = imgTag.parentNode;\n var count = \"${count:0}\";\n var limit = \"${limit:0}\";\n count = count.length > 0 ? parseInt(count, 10) : 0;\n limit = limit.length > 0 ? parseInt(limit, 10) : 0;\n var percentElement = $('#api-usage-percent', parentTag);\n var valueElement = $('#api-usage-value', parentTag);\n var barElement = $('#api-usage-bar', parentTag);\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n barElement.width(percent + '%');\n percent = percent.toFixed(2);\n percentElement.text(percent + '%');\n valueElement.text(toShortNumber(count) + ' / ' + toShortNumber(limit));\n } else {\n barElement.width('0%');\n percentElement.text('');\n valueElement.text(toShortNumber(count) + ' / ∞');\n }\n }\n </script>\n <div class='content'>\n <div class='column'>\n <div class='title-row'>\n <div class='title'>${title}</div>\n <div class='state'>${apiState}</div>\n </div> \n <div class='bar-container'>\n <div class=\"unit\">${unit}</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar\"></div>\n </div> \n <div class='bar-labels'>\n <div id=\"api-usage-percent\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value\"></div>\n </div>\n </div> \n </div>\n </div>\n <div role=\"separator\" class=\"mat-divider mat-divider-horizontal\" aria-orientation=\"horizontal\"></div>\n <div class='action-row'>\n <button id=\"javascript_functions_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">{i18n:api-usage.view-details}</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n </div> \n</div>",
  127 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n"
128 }, 128 },
129 "title": "JavaScript functions", 129 "title": "JavaScript functions",
130 "dropShadow": true, 130 "dropShadow": true,
@@ -253,7 +253,7 @@ @@ -253,7 +253,7 @@
253 "decimals": null, 253 "decimals": null,
254 "funcBody": null, 254 "funcBody": null,
255 "usePostProcessing": true, 255 "usePostProcessing": true,
256 - "postFuncBody": "return \"Telemetry\";" 256 + "postFuncBody": "return \"{i18n:api-usage.telemetry}\";"
257 }, 257 },
258 { 258 {
259 "name": "dbApiState", 259 "name": "dbApiState",
@@ -266,7 +266,7 @@ @@ -266,7 +266,7 @@
266 "decimals": null, 266 "decimals": null,
267 "funcBody": null, 267 "funcBody": null,
268 "usePostProcessing": true, 268 "usePostProcessing": true,
269 - "postFuncBody": "return \"Data points storage days\";" 269 + "postFuncBody": "return \"{i18n:api-usage.data-points-storage-days}\";"
270 } 270 }
271 ] 271 ]
272 } 272 }
@@ -281,8 +281,8 @@ @@ -281,8 +281,8 @@
281 "color": "#666666", 281 "color": "#666666",
282 "padding": "0", 282 "padding": "0",
283 "settings": { 283 "settings": {
284 - "cardHtml": "<div class='card ${apiStateClass}'>\n <img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" onload=\"initializeCard_${cardId:0}(event);\">\n <script type=\"text/javascript\">\n function initializeCard_${cardId:0}(e) {\n \n function toShortNumber(number) {\n var rounder = Math.pow(10, 1);\n var powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n \n var key = '';\n \n for (var i = 0; i < powers.length; i++) {\n var reduced = number / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = powers[i].key;\n break;\n }\n }\n return number + key;\n }\n \n var imgTag = e.target;\n var parentTag = imgTag.parentNode;\n var count = \"${count:0}\";\n var limit = \"${limit:0}\";\n count = count.length > 0 ? parseInt(count, 10) : 0;\n limit = limit.length > 0 ? parseInt(limit, 10) : 0;\n var percentElement = $('#api-usage-percent', parentTag);\n var valueElement = $('#api-usage-value', parentTag);\n var barElement = $('#api-usage-bar', parentTag);\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n barElement.width(percent + '%');\n percent = percent.toFixed(2);\n percentElement.text(percent + '%');\n valueElement.text(toShortNumber(count) + ' / ' + toShortNumber(limit));\n } else {\n barElement.width('0%');\n percentElement.text('');\n valueElement.text(toShortNumber(count) + ' / unlimited');\n }\n }\n </script>\n <div class='content'>\n <div class='column'>\n <div class='title-row'>\n <div class='title'>${title}</div>\n <div class='state'>${apiState}</div>\n </div> \n <div class='bar-container'>\n <div class=\"unit\">${unit}</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar\"></div>\n </div> \n <div class='bar-labels'>\n <div id=\"api-usage-percent\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value\"></div>\n </div>\n </div> \n </div>\n </div>\n <div role=\"separator\" class=\"mat-divider mat-divider-horizontal\" aria-orientation=\"horizontal\"></div>\n <div class='action-row'>\n <button id=\"telemetry_persistence_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">View details</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n </div> \n</div>",  
285 - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" 284 + "cardHtml": "<div class='card ${apiStateClass}'>\n <img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" onload=\"initializeCard_${cardId:0}(event);\">\n <script type=\"text/javascript\">\n function initializeCard_${cardId:0}(e) {\n \n function toShortNumber(number) {\n var rounder = Math.pow(10, 1);\n var powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n \n var key = '';\n \n for (var i = 0; i < powers.length; i++) {\n var reduced = number / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = powers[i].key;\n break;\n }\n }\n return number + key;\n }\n \n var imgTag = e.target;\n var parentTag = imgTag.parentNode;\n var count = \"${count:0}\";\n var limit = \"${limit:0}\";\n count = count.length > 0 ? parseInt(count, 10) : 0;\n limit = limit.length > 0 ? parseInt(limit, 10) : 0;\n var percentElement = $('#api-usage-percent', parentTag);\n var valueElement = $('#api-usage-value', parentTag);\n var barElement = $('#api-usage-bar', parentTag);\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n barElement.width(percent + '%');\n percent = percent.toFixed(2);\n percentElement.text(percent + '%');\n valueElement.text(toShortNumber(count) + ' / ' + toShortNumber(limit));\n } else {\n barElement.width('0%');\n percentElement.text('');\n valueElement.text(toShortNumber(count) + ' / ∞');\n }\n }\n </script>\n <div class='content'>\n <div class='column'>\n <div class='title-row'>\n <div class='title'>${title}</div>\n <div class='state'>${apiState}</div>\n </div> \n <div class='bar-container'>\n <div class=\"unit\">${unit}</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar\"></div>\n </div> \n <div class='bar-labels'>\n <div id=\"api-usage-percent\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value\"></div>\n </div>\n </div> \n </div>\n </div>\n <div role=\"separator\" class=\"mat-divider mat-divider-horizontal\" aria-orientation=\"horizontal\"></div>\n <div class='action-row'>\n <button id=\"telemetry_persistence_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">{i18n:api-usage.view-details}</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n </div> \n</div>",
  285 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n"
286 }, 286 },
287 "title": "Telemetry persistence", 287 "title": "Telemetry persistence",
288 "dropShadow": true, 288 "dropShadow": true,
@@ -411,7 +411,7 @@ @@ -411,7 +411,7 @@
411 "decimals": null, 411 "decimals": null,
412 "funcBody": null, 412 "funcBody": null,
413 "usePostProcessing": true, 413 "usePostProcessing": true,
414 - "postFuncBody": "return \"Rule Engine\";" 414 + "postFuncBody": "return \"{i18n:api-usage.rule-engine}\";"
415 }, 415 },
416 { 416 {
417 "name": "ruleEngineApiState", 417 "name": "ruleEngineApiState",
@@ -424,7 +424,7 @@ @@ -424,7 +424,7 @@
424 "decimals": null, 424 "decimals": null,
425 "funcBody": null, 425 "funcBody": null,
426 "usePostProcessing": true, 426 "usePostProcessing": true,
427 - "postFuncBody": "return \"Executions\";" 427 + "postFuncBody": "return \"{i18n:api-usage.executions}\";"
428 } 428 }
429 ] 429 ]
430 } 430 }
@@ -439,8 +439,8 @@ @@ -439,8 +439,8 @@
439 "color": "#666666", 439 "color": "#666666",
440 "padding": "0", 440 "padding": "0",
441 "settings": { 441 "settings": {
442 - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n",  
443 - "cardHtml": "<div class='card ${apiStateClass}'>\n <img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" onload=\"initializeCard_${cardId:0}(event);\">\n <script type=\"text/javascript\">\n function initializeCard_${cardId:0}(e) {\n \n function toShortNumber(number) {\n var rounder = Math.pow(10, 1);\n var powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n \n var key = '';\n \n for (var i = 0; i < powers.length; i++) {\n var reduced = number / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = powers[i].key;\n break;\n }\n }\n return number + key;\n }\n \n var imgTag = e.target;\n var parentTag = imgTag.parentNode;\n var count = \"${count:0}\";\n var limit = \"${limit:0}\";\n count = count.length > 0 ? parseInt(count, 10) : 0;\n limit = limit.length > 0 ? parseInt(limit, 10) : 0;\n var percentElement = $('#api-usage-percent', parentTag);\n var valueElement = $('#api-usage-value', parentTag);\n var barElement = $('#api-usage-bar', parentTag);\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n barElement.width(percent + '%');\n percent = percent.toFixed(2);\n percentElement.text(percent + '%');\n valueElement.text(toShortNumber(count) + ' / ' + toShortNumber(limit));\n } else {\n barElement.width('0%');\n percentElement.text('');\n valueElement.text(toShortNumber(count) + ' / unlimited');\n }\n }\n </script>\n <div class='content'>\n <div class='column'>\n <div class='title-row'>\n <div class='title'>${title}</div>\n <div class='state'>${apiState}</div>\n </div> \n <div class='bar-container'>\n <div class=\"unit\">${unit}</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar\"></div>\n </div> \n <div class='bar-labels'>\n <div id=\"api-usage-percent\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value\"></div>\n </div>\n </div> \n </div>\n </div>\n <div role=\"separator\" class=\"mat-divider mat-divider-horizontal\" aria-orientation=\"horizontal\"></div>\n <div class='action-row'>\n <button id=\"rule_engine_statistics_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">View statistics</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n <button id=\"rule_engine_execution_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">View details</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n </div> \n</div>" 442 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n",
  443 + "cardHtml": "<div class='card ${apiStateClass}'>\n <img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" onload=\"initializeCard_${cardId:0}(event);\">\n <script type=\"text/javascript\">\n function initializeCard_${cardId:0}(e) {\n \n function toShortNumber(number) {\n var rounder = Math.pow(10, 1);\n var powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n \n var key = '';\n \n for (var i = 0; i < powers.length; i++) {\n var reduced = number / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = powers[i].key;\n break;\n }\n }\n return number + key;\n }\n \n var imgTag = e.target;\n var parentTag = imgTag.parentNode;\n var count = \"${count:0}\";\n var limit = \"${limit:0}\";\n count = count.length > 0 ? parseInt(count, 10) : 0;\n limit = limit.length > 0 ? parseInt(limit, 10) : 0;\n var percentElement = $('#api-usage-percent', parentTag);\n var valueElement = $('#api-usage-value', parentTag);\n var barElement = $('#api-usage-bar', parentTag);\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n barElement.width(percent + '%');\n percent = percent.toFixed(2);\n percentElement.text(percent + '%');\n valueElement.text(toShortNumber(count) + ' / ' + toShortNumber(limit));\n } else {\n barElement.width('0%');\n percentElement.text('');\n valueElement.text(toShortNumber(count) + ' / ∞');\n }\n }\n </script>\n <div class='content'>\n <div class='column'>\n <div class='title-row'>\n <div class='title'>${title}</div>\n <div class='state'>${apiState}</div>\n </div> \n <div class='bar-container'>\n <div class=\"unit\">${unit}</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar\"></div>\n </div> \n <div class='bar-labels'>\n <div id=\"api-usage-percent\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value\"></div>\n </div>\n </div> \n </div>\n </div>\n <div role=\"separator\" class=\"mat-divider mat-divider-horizontal\" aria-orientation=\"horizontal\"></div>\n <div class='action-row'>\n <button id=\"rule_engine_statistics_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">{i18n:api-usage.view-statistics}</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n <button id=\"rule_engine_execution_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">{i18n:api-usage.view-details}</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n </div> \n</div>"
444 }, 444 },
445 "title": "Rule Engine execution", 445 "title": "Rule Engine execution",
446 "dropShadow": true, 446 "dropShadow": true,
@@ -605,7 +605,7 @@ @@ -605,7 +605,7 @@
605 "decimals": null, 605 "decimals": null,
606 "funcBody": null, 606 "funcBody": null,
607 "usePostProcessing": true, 607 "usePostProcessing": true,
608 - "postFuncBody": "return \"Transport\";" 608 + "postFuncBody": "return \"{i18n:api-usage.transport}\";"
609 } 609 }
610 ] 610 ]
611 } 611 }
@@ -620,8 +620,8 @@ @@ -620,8 +620,8 @@
620 "color": "#666666", 620 "color": "#666666",
621 "padding": "0", 621 "padding": "0",
622 "settings": { 622 "settings": {
623 - "cardHtml": "<div class='card ${apiStateClass}'>\n <img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" onload=\"initializeCard_${cardId:0}(event);\">\n <script type=\"text/javascript\">\n function initializeCard_${cardId:0}(e) {\n \n function toShortNumber(number) {\n var rounder = Math.pow(10, 1);\n var powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n \n var key = '';\n \n for (var i = 0; i < powers.length; i++) {\n var reduced = number / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = powers[i].key;\n break;\n }\n }\n return number + key;\n }\n \n var imgTag = e.target;\n var parentTag = imgTag.parentNode;\n var count = \"${count:0}\";\n var limit = \"${limit:0}\";\n count = count.length > 0 ? parseInt(count, 10) : 0;\n limit = limit.length > 0 ? parseInt(limit, 10) : 0;\n var percentElement = $('#api-usage-percent', parentTag);\n var valueElement = $('#api-usage-value', parentTag);\n var barElement = $('#api-usage-bar', parentTag);\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n barElement.width(percent + '%');\n percent = percent.toFixed(2);\n percentElement.text(percent + '%');\n valueElement.text(toShortNumber(count) + ' / ' + toShortNumber(limit));\n } else {\n barElement.width('0%');\n percentElement.text('');\n valueElement.text(toShortNumber(count) + ' / unlimited');\n }\n var pointsCount = \"${pointsCount:0}\";\n var pointsLimit = \"${pointsLimit:0}\";\n pointsCount = pointsCount.length > 0 ? parseInt(pointsCount, 10) : 0;\n pointsLimit = pointsLimit.length > 0 ? parseInt(pointsLimit, 10) : 0;\n var pointsPercentElement = $('#api-usage-percent2', parentTag);\n var pointsValueElement = $('#api-usage-value2', parentTag);\n var pointsBarElement = $('#api-usage-bar2', parentTag);\n if (Number.isFinite(pointsLimit) && pointsLimit > 0) {\n var percent = Math.min(100, ((pointsCount / pointsLimit) * 100));\n pointsBarElement.width(percent + '%');\n percent = percent.toFixed(2);\n pointsPercentElement.text(percent + '%');\n pointsValueElement.text(toShortNumber(pointsCount) + ' / ' + toShortNumber(pointsLimit));\n } else {\n pointsBarElement.width('0%');\n pointsPercentElement.text('');\n pointsValueElement.text(toShortNumber(pointsCount) + ' / unlimited');\n }\n }\n </script>\n <div class='content'>\n <div class='column'>\n <div class='title-row'>\n <div class='title'>\n <span>${title}</span>\n </div>\n <div class='state'>${apiState}</div>\n </div>\n <div class=\"bars-row\">\n <div class=\"bar-column\" style=\"margin-right: 10px;\">\n <div class='bar-container'>\n <div class=\"unit\">Messages</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar\"></div>\n </div>\n <div class='bar-labels'>\n <div id=\"api-usage-percent\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value\"></div>\n </div>\n </div> \n </div>\n <div class=\"bar-column\" style=\"margin-left: 10px;\">\n <div class='bar-container'>\n <div class=\"unit\">Data points</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar2\"></div>\n </div> \n <div class='bar-labels'>\n <div id=\"api-usage-percent2\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value2\"></div>\n </div>\n </div> \n </div>\n </div>\n </div>\n </div>\n <div role=\"separator\" class=\"mat-divider mat-divider-horizontal\" aria-orientation=\"horizontal\"></div>\n <div class='action-row'>\n <button id=\"transport_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">View details</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n </div> \n</div>",  
624 - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 6px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n" 623 + "cardHtml": "<div class='card ${apiStateClass}'>\n <img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" onload=\"initializeCard_${cardId:0}(event);\">\n <script type=\"text/javascript\">\n function initializeCard_${cardId:0}(e) {\n \n function toShortNumber(number) {\n var rounder = Math.pow(10, 1);\n var powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n \n var key = '';\n \n for (var i = 0; i < powers.length; i++) {\n var reduced = number / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = powers[i].key;\n break;\n }\n }\n return number + key;\n }\n \n var imgTag = e.target;\n var parentTag = imgTag.parentNode;\n var count = \"${count:0}\";\n var limit = \"${limit:0}\";\n count = count.length > 0 ? parseInt(count, 10) : 0;\n limit = limit.length > 0 ? parseInt(limit, 10) : 0;\n var percentElement = $('#api-usage-percent', parentTag);\n var valueElement = $('#api-usage-value', parentTag);\n var barElement = $('#api-usage-bar', parentTag);\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n barElement.width(percent + '%');\n percent = percent.toFixed(2);\n percentElement.text(percent + '%');\n valueElement.text(toShortNumber(count) + ' / ' + toShortNumber(limit));\n } else {\n barElement.width('0%');\n percentElement.text('');\n valueElement.text(toShortNumber(count) + ' / ∞');\n }\n var pointsCount = \"${pointsCount:0}\";\n var pointsLimit = \"${pointsLimit:0}\";\n pointsCount = pointsCount.length > 0 ? parseInt(pointsCount, 10) : 0;\n pointsLimit = pointsLimit.length > 0 ? parseInt(pointsLimit, 10) : 0;\n var pointsPercentElement = $('#api-usage-percent2', parentTag);\n var pointsValueElement = $('#api-usage-value2', parentTag);\n var pointsBarElement = $('#api-usage-bar2', parentTag);\n if (Number.isFinite(pointsLimit) && pointsLimit > 0) {\n var percent = Math.min(100, ((pointsCount / pointsLimit) * 100));\n pointsBarElement.width(percent + '%');\n percent = percent.toFixed(2);\n pointsPercentElement.text(percent + '%');\n pointsValueElement.text(toShortNumber(pointsCount) + ' / ' + toShortNumber(pointsLimit));\n } else {\n pointsBarElement.width('0%');\n pointsPercentElement.text('');\n pointsValueElement.text(toShortNumber(pointsCount) + ' / ∞');\n }\n }\n </script>\n <div class='content'>\n <div class='column'>\n <div class='title-row'>\n <div class='title'>\n <span>${title}</span>\n </div>\n <div class='state'>${apiState}</div>\n </div>\n <div class=\"bars-row\">\n <div class=\"bar-column\" style=\"margin-right: 10px;\">\n <div class='bar-container'>\n <div class=\"unit\">{i18n:api-usage.messages}</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar\"></div>\n </div>\n <div class='bar-labels'>\n <div id=\"api-usage-percent\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value\"></div>\n </div>\n </div> \n </div>\n <div class=\"bar-column\" style=\"margin-left: 10px;\">\n <div class='bar-container'>\n <div class=\"unit\">{i18n:api-usage.data-points}</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar2\"></div>\n </div> \n <div class='bar-labels'>\n <div id=\"api-usage-percent2\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value2\"></div>\n </div>\n </div> \n </div>\n </div>\n </div>\n </div>\n <div role=\"separator\" class=\"mat-divider mat-divider-horizontal\" aria-orientation=\"horizontal\"></div>\n <div class='action-row'>\n <button id=\"transport_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">{i18n:api-usage.view-details}</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n </div> \n</div>",
  624 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 6px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n"
625 }, 625 },
626 "title": "Transport", 626 "title": "Transport",
627 "dropShadow": true, 627 "dropShadow": true,
@@ -750,7 +750,7 @@ @@ -750,7 +750,7 @@
750 "decimals": null, 750 "decimals": null,
751 "funcBody": null, 751 "funcBody": null,
752 "usePostProcessing": true, 752 "usePostProcessing": true,
753 - "postFuncBody": "return \"Email\";" 753 + "postFuncBody": "return \"{i18n:api-usage.email}\";"
754 }, 754 },
755 { 755 {
756 "name": "emailApiState", 756 "name": "emailApiState",
@@ -763,7 +763,7 @@ @@ -763,7 +763,7 @@
763 "decimals": null, 763 "decimals": null,
764 "funcBody": null, 764 "funcBody": null,
765 "usePostProcessing": true, 765 "usePostProcessing": true,
766 - "postFuncBody": "return \"Messages\";" 766 + "postFuncBody": "return \"{i18n:api-usage.messages}\";"
767 } 767 }
768 ] 768 ]
769 } 769 }
@@ -778,8 +778,8 @@ @@ -778,8 +778,8 @@
778 "color": "#666666", 778 "color": "#666666",
779 "padding": "0", 779 "padding": "0",
780 "settings": { 780 "settings": {
781 - "cardHtml": "<div class='card ${apiStateClass}'>\n <img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" onload=\"initializeCard_${cardId:0}(event);\">\n <script type=\"text/javascript\">\n function initializeCard_${cardId:0}(e) {\n \n function toShortNumber(number) {\n var rounder = Math.pow(10, 1);\n var powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n \n var key = '';\n \n for (var i = 0; i < powers.length; i++) {\n var reduced = number / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = powers[i].key;\n break;\n }\n }\n return number + key;\n }\n \n var imgTag = e.target;\n var parentTag = imgTag.parentNode;\n var count = \"${count:0}\";\n var limit = \"${limit:0}\";\n count = count.length > 0 ? parseInt(count, 10) : 0;\n limit = limit.length > 0 ? parseInt(limit, 10) : 0;\n var percentElement = $('#api-usage-percent', parentTag);\n var valueElement = $('#api-usage-value', parentTag);\n var barElement = $('#api-usage-bar', parentTag);\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n barElement.width(percent + '%');\n percent = percent.toFixed(2);\n percentElement.text(percent + '%');\n valueElement.text(toShortNumber(count) + ' / ' + toShortNumber(limit));\n } else {\n barElement.width('0%');\n percentElement.text('');\n valueElement.text(toShortNumber(count) + ' / unlimited');\n }\n }\n </script>\n <div class='content'>\n <div class='column'>\n <div class='title-row'>\n <div class='title'>${title}</div>\n <div class='state'>${apiState}</div>\n </div> \n <div class='bar-container'>\n <div class=\"unit\">${unit}</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar\"></div>\n </div> \n <div class='bar-labels'>\n <div id=\"api-usage-percent\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value\"></div>\n </div>\n </div> \n </div>\n </div>\n <div role=\"separator\" class=\"mat-divider mat-divider-horizontal\" aria-orientation=\"horizontal\"></div>\n <div class='action-row'>\n <button id=\"email_messages_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">View details</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n </div> \n</div>",  
782 - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" 781 + "cardHtml": "<div class='card ${apiStateClass}'>\n <img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" onload=\"initializeCard_${cardId:0}(event);\">\n <script type=\"text/javascript\">\n function initializeCard_${cardId:0}(e) {\n \n function toShortNumber(number) {\n var rounder = Math.pow(10, 1);\n var powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n \n var key = '';\n \n for (var i = 0; i < powers.length; i++) {\n var reduced = number / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = powers[i].key;\n break;\n }\n }\n return number + key;\n }\n \n var imgTag = e.target;\n var parentTag = imgTag.parentNode;\n var count = \"${count:0}\";\n var limit = \"${limit:0}\";\n count = count.length > 0 ? parseInt(count, 10) : 0;\n limit = limit.length > 0 ? parseInt(limit, 10) : 0;\n var percentElement = $('#api-usage-percent', parentTag);\n var valueElement = $('#api-usage-value', parentTag);\n var barElement = $('#api-usage-bar', parentTag);\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n barElement.width(percent + '%');\n percent = percent.toFixed(2);\n percentElement.text(percent + '%');\n valueElement.text(toShortNumber(count) + ' / ' + toShortNumber(limit));\n } else {\n barElement.width('0%');\n percentElement.text('');\n valueElement.text(toShortNumber(count) + ' / ∞');\n }\n }\n </script>\n <div class='content'>\n <div class='column'>\n <div class='title-row'>\n <div class='title'>${title}</div>\n <div class='state'>${apiState}</div>\n </div> \n <div class='bar-container'>\n <div class=\"unit\">${unit}</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar\"></div>\n </div> \n <div class='bar-labels'>\n <div id=\"api-usage-percent\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value\"></div>\n </div>\n </div> \n </div>\n </div>\n <div role=\"separator\" class=\"mat-divider mat-divider-horizontal\" aria-orientation=\"horizontal\"></div>\n <div class='action-row'>\n <button id=\"email_messages_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">{i18n:api-usage.view-details}</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n </div> \n</div>",
  782 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n"
783 }, 783 },
784 "title": "Email messages", 784 "title": "Email messages",
785 "dropShadow": true, 785 "dropShadow": true,
@@ -908,7 +908,7 @@ @@ -908,7 +908,7 @@
908 "decimals": null, 908 "decimals": null,
909 "funcBody": null, 909 "funcBody": null,
910 "usePostProcessing": true, 910 "usePostProcessing": true,
911 - "postFuncBody": "return \"SMS\";" 911 + "postFuncBody": "return \"{i18n:api-usage.sms}\";"
912 }, 912 },
913 { 913 {
914 "name": "smsApiState", 914 "name": "smsApiState",
@@ -921,7 +921,7 @@ @@ -921,7 +921,7 @@
921 "decimals": null, 921 "decimals": null,
922 "funcBody": null, 922 "funcBody": null,
923 "usePostProcessing": true, 923 "usePostProcessing": true,
924 - "postFuncBody": "return \"Messages\";" 924 + "postFuncBody": "return \"{i18n:api-usage.messages}\";"
925 } 925 }
926 ] 926 ]
927 } 927 }
@@ -936,8 +936,8 @@ @@ -936,8 +936,8 @@
936 "color": "#666666", 936 "color": "#666666",
937 "padding": "0", 937 "padding": "0",
938 "settings": { 938 "settings": {
939 - "cardHtml": "<div class='card ${apiStateClass}'>\n <img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" onload=\"initializeCard_${cardId:0}(event);\">\n <script type=\"text/javascript\">\n function initializeCard_${cardId:0}(e) {\n \n function toShortNumber(number) {\n var rounder = Math.pow(10, 1);\n var powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n \n var key = '';\n \n for (var i = 0; i < powers.length; i++) {\n var reduced = number / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = powers[i].key;\n break;\n }\n }\n return number + key;\n }\n \n var imgTag = e.target;\n var parentTag = imgTag.parentNode;\n var count = \"${count:0}\";\n var limit = \"${limit:0}\";\n count = count.length > 0 ? parseInt(count, 10) : 0;\n limit = limit.length > 0 ? parseInt(limit, 10) : 0;\n var percentElement = $('#api-usage-percent', parentTag);\n var valueElement = $('#api-usage-value', parentTag);\n var barElement = $('#api-usage-bar', parentTag);\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n barElement.width(percent + '%');\n percent = percent.toFixed(2);\n percentElement.text(percent + '%');\n valueElement.text(toShortNumber(count) + ' / ' + toShortNumber(limit));\n } else {\n barElement.width('0%');\n percentElement.text('');\n valueElement.text(toShortNumber(count) + ' / unlimited');\n }\n }\n </script>\n <div class='content'>\n <div class='column'>\n <div class='title-row'>\n <div class='title'>${title}</div>\n <div class='state'>${apiState}</div>\n </div> \n <div class='bar-container'>\n <div class=\"unit\">${unit}</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar\"></div>\n </div> \n <div class='bar-labels'>\n <div id=\"api-usage-percent\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value\"></div>\n </div>\n </div> \n </div>\n </div>\n <div role=\"separator\" class=\"mat-divider mat-divider-horizontal\" aria-orientation=\"horizontal\"></div>\n <div class='action-row'>\n <button id=\"sms_messages_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">View details</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n </div> \n</div>",  
940 - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" 939 + "cardHtml": "<div class='card ${apiStateClass}'>\n <img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" onload=\"initializeCard_${cardId:0}(event);\">\n <script type=\"text/javascript\">\n function initializeCard_${cardId:0}(e) {\n \n function toShortNumber(number) {\n var rounder = Math.pow(10, 1);\n var powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n ];\n \n var key = '';\n \n for (var i = 0; i < powers.length; i++) {\n var reduced = number / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n number = reduced;\n key = powers[i].key;\n break;\n }\n }\n return number + key;\n }\n \n var imgTag = e.target;\n var parentTag = imgTag.parentNode;\n var count = \"${count:0}\";\n var limit = \"${limit:0}\";\n count = count.length > 0 ? parseInt(count, 10) : 0;\n limit = limit.length > 0 ? parseInt(limit, 10) : 0;\n var percentElement = $('#api-usage-percent', parentTag);\n var valueElement = $('#api-usage-value', parentTag);\n var barElement = $('#api-usage-bar', parentTag);\n if (Number.isFinite(limit) && limit > 0) {\n var percent = Math.min(100, ((count / limit) * 100));\n barElement.width(percent + '%');\n percent = percent.toFixed(2);\n percentElement.text(percent + '%');\n valueElement.text(toShortNumber(count) + ' / ' + toShortNumber(limit));\n } else {\n barElement.width('0%');\n percentElement.text('');\n valueElement.text(toShortNumber(count) + ' / ∞');\n }\n }\n </script>\n <div class='content'>\n <div class='column'>\n <div class='title-row'>\n <div class='title'>${title}</div>\n <div class='state'>${apiState}</div>\n </div> \n <div class='bar-container'>\n <div class=\"unit\">${unit}</div>\n <div class='bar'>\n <div class=\"bar-fill\" id=\"api-usage-bar\"></div>\n </div> \n <div class='bar-labels'>\n <div id=\"api-usage-percent\"></div>\n <div style=\"flex: 1;\"></div>\n <div id=\"api-usage-value\"></div>\n </div>\n </div> \n </div>\n </div>\n <div role=\"separator\" class=\"mat-divider mat-divider-horizontal\" aria-orientation=\"horizontal\"></div>\n <div class='action-row'>\n <button id=\"sms_messages_details\" class=\"mat-focus-indicator mat-button mat-button-base mat-primary\">\n <span class=\"mat-button-wrapper\">{i18n:api-usage.view-details}</span>\n <span class=\"mat-ripple mat-button-ripple\"></span>\n <span class=\"mat-button-focus-overlay\"></span>\n </button>\n </div> \n</div>",
  940 + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n"
941 }, 941 },
942 "title": "SMS messages", 942 "title": "SMS messages",
943 "dropShadow": true, 943 "dropShadow": true,
@@ -993,7 +993,7 @@ @@ -993,7 +993,7 @@
993 { 993 {
994 "name": "ruleEngineExecutionCountHourly", 994 "name": "ruleEngineExecutionCountHourly",
995 "type": "timeseries", 995 "type": "timeseries",
996 - "label": "Rule Engine executions", 996 + "label": "{i18n:api-usage.rule-engine-executions}",
997 "color": "#ab00ff", 997 "color": "#ab00ff",
998 "settings": { 998 "settings": {
999 "excludeFromStacking": false, 999 "excludeFromStacking": false,
@@ -1078,7 +1078,7 @@ @@ -1078,7 +1078,7 @@
1078 "showLabels": true 1078 "showLabels": true
1079 } 1079 }
1080 }, 1080 },
1081 - "title": "Rule Engine hourly activity", 1081 + "title": "{i18n:api-usage.rule-engine-hourly-activity}",
1082 "dropShadow": true, 1082 "dropShadow": true,
1083 "enableFullscreen": true, 1083 "enableFullscreen": true,
1084 "titleStyle": { 1084 "titleStyle": {
@@ -1091,7 +1091,7 @@ @@ -1091,7 +1091,7 @@
1091 "actions": { 1091 "actions": {
1092 "headerButton": [ 1092 "headerButton": [
1093 { 1093 {
1094 - "name": "View statistics", 1094 + "name": "{i18n:api-usage.view-statistics}",
1095 "icon": "show_chart", 1095 "icon": "show_chart",
1096 "type": "openDashboardState", 1096 "type": "openDashboardState",
1097 "targetDashboardStateId": "rule_engine_statistics", 1097 "targetDashboardStateId": "rule_engine_statistics",
@@ -1101,7 +1101,7 @@ @@ -1101,7 +1101,7 @@
1101 "id": "f9f08190-9ed9-d802-5b7a-c57ff84b5648" 1101 "id": "f9f08190-9ed9-d802-5b7a-c57ff84b5648"
1102 }, 1102 },
1103 { 1103 {
1104 - "name": "View details", 1104 + "name": "{i18n:api-usage.view-details}",
1105 "icon": "insert_chart", 1105 "icon": "insert_chart",
1106 "type": "openDashboardState", 1106 "type": "openDashboardState",
1107 "targetDashboardStateId": "rule_engine_execution", 1107 "targetDashboardStateId": "rule_engine_execution",
@@ -1150,7 +1150,7 @@ @@ -1150,7 +1150,7 @@
1150 { 1150 {
1151 "name": "transportMsgCountHourly", 1151 "name": "transportMsgCountHourly",
1152 "type": "timeseries", 1152 "type": "timeseries",
1153 - "label": "Transport messages", 1153 + "label": "{i18n:api-usage.transport-messages}",
1154 "color": "#2196f3", 1154 "color": "#2196f3",
1155 "settings": { 1155 "settings": {
1156 "excludeFromStacking": false, 1156 "excludeFromStacking": false,
@@ -1185,7 +1185,7 @@ @@ -1185,7 +1185,7 @@
1185 { 1185 {
1186 "name": "transportDataPointsCountHourly", 1186 "name": "transportDataPointsCountHourly",
1187 "type": "timeseries", 1187 "type": "timeseries",
1188 - "label": "Transport data points", 1188 + "label": "{i18n:api-usage.transport-data-points}",
1189 "color": "#4caf50", 1189 "color": "#4caf50",
1190 "settings": { 1190 "settings": {
1191 "excludeFromStacking": false, 1191 "excludeFromStacking": false,
@@ -1271,7 +1271,7 @@ @@ -1271,7 +1271,7 @@
1271 }, 1271 },
1272 "tooltipCumulative": false 1272 "tooltipCumulative": false
1273 }, 1273 },
1274 - "title": "Transport hourly activity", 1274 + "title": "{i18n:api-usage.transport-hourly-activity}",
1275 "dropShadow": true, 1275 "dropShadow": true,
1276 "enableFullscreen": true, 1276 "enableFullscreen": true,
1277 "titleStyle": { 1277 "titleStyle": {
@@ -1284,7 +1284,7 @@ @@ -1284,7 +1284,7 @@
1284 "actions": { 1284 "actions": {
1285 "headerButton": [ 1285 "headerButton": [
1286 { 1286 {
1287 - "name": "View details", 1287 + "name": "{i18n:api-usage.view-details}",
1288 "icon": "insert_chart", 1288 "icon": "insert_chart",
1289 "type": "openDashboardState", 1289 "type": "openDashboardState",
1290 "targetDashboardStateId": "transport", 1290 "targetDashboardStateId": "transport",
@@ -1333,7 +1333,7 @@ @@ -1333,7 +1333,7 @@
1333 { 1333 {
1334 "name": "jsExecutionCountHourly", 1334 "name": "jsExecutionCountHourly",
1335 "type": "timeseries", 1335 "type": "timeseries",
1336 - "label": "JavaScript executions", 1336 + "label": "{i18n:api-usage.javascript-executions}",
1337 "color": "#ff9900", 1337 "color": "#ff9900",
1338 "settings": { 1338 "settings": {
1339 "excludeFromStacking": false, 1339 "excludeFromStacking": false,
@@ -1418,7 +1418,7 @@ @@ -1418,7 +1418,7 @@
1418 "showLabels": true 1418 "showLabels": true
1419 } 1419 }
1420 }, 1420 },
1421 - "title": "JavaScript functions hourly activity", 1421 + "title": "{i18n:api-usage.javascript-functions-hourly-activity}",
1422 "dropShadow": true, 1422 "dropShadow": true,
1423 "enableFullscreen": true, 1423 "enableFullscreen": true,
1424 "titleStyle": { 1424 "titleStyle": {
@@ -1431,7 +1431,7 @@ @@ -1431,7 +1431,7 @@
1431 "actions": { 1431 "actions": {
1432 "headerButton": [ 1432 "headerButton": [
1433 { 1433 {
1434 - "name": "View details", 1434 + "name": "{i18n:api-usage.view-details}",
1435 "icon": "insert_chart", 1435 "icon": "insert_chart",
1436 "type": "openDashboardState", 1436 "type": "openDashboardState",
1437 "targetDashboardStateId": "javascript_functions", 1437 "targetDashboardStateId": "javascript_functions",
@@ -1480,7 +1480,7 @@ @@ -1480,7 +1480,7 @@
1480 { 1480 {
1481 "name": "storageDataPointsCountHourly", 1481 "name": "storageDataPointsCountHourly",
1482 "type": "timeseries", 1482 "type": "timeseries",
1483 - "label": "Data points storage days", 1483 + "label": "{i18n:api-usage.data-points-storage-days}",
1484 "color": "#1039ee", 1484 "color": "#1039ee",
1485 "settings": { 1485 "settings": {
1486 "excludeFromStacking": false, 1486 "excludeFromStacking": false,
@@ -1565,7 +1565,7 @@ @@ -1565,7 +1565,7 @@
1565 "showLabels": true 1565 "showLabels": true
1566 } 1566 }
1567 }, 1567 },
1568 - "title": "Telemetry persistence hourly activity", 1568 + "title": "{i18n:api-usage.telemetry-persistence-hourly-activity}",
1569 "dropShadow": true, 1569 "dropShadow": true,
1570 "enableFullscreen": true, 1570 "enableFullscreen": true,
1571 "titleStyle": { 1571 "titleStyle": {
@@ -1578,7 +1578,7 @@ @@ -1578,7 +1578,7 @@
1578 "actions": { 1578 "actions": {
1579 "headerButton": [ 1579 "headerButton": [
1580 { 1580 {
1581 - "name": "View details", 1581 + "name": "{i18n:api-usage.view-details}",
1582 "icon": "insert_chart", 1582 "icon": "insert_chart",
1583 "type": "openDashboardState", 1583 "type": "openDashboardState",
1584 "targetDashboardStateId": "telemetry_persistence", 1584 "targetDashboardStateId": "telemetry_persistence",
@@ -1627,7 +1627,7 @@ @@ -1627,7 +1627,7 @@
1627 { 1627 {
1628 "name": "emailCountHourly", 1628 "name": "emailCountHourly",
1629 "type": "timeseries", 1629 "type": "timeseries",
1630 - "label": "Email messages", 1630 + "label": "{i18n:api-usage.email-messages}",
1631 "color": "#d35a00", 1631 "color": "#d35a00",
1632 "settings": { 1632 "settings": {
1633 "excludeFromStacking": false, 1633 "excludeFromStacking": false,
@@ -1712,7 +1712,7 @@ @@ -1712,7 +1712,7 @@
1712 "showLabels": true 1712 "showLabels": true
1713 } 1713 }
1714 }, 1714 },
1715 - "title": "Email messages hourly activity", 1715 + "title": "{i18n:api-usage.email-messages-hourly-activity}",
1716 "dropShadow": true, 1716 "dropShadow": true,
1717 "enableFullscreen": true, 1717 "enableFullscreen": true,
1718 "titleStyle": { 1718 "titleStyle": {
@@ -1725,7 +1725,7 @@ @@ -1725,7 +1725,7 @@
1725 "actions": { 1725 "actions": {
1726 "headerButton": [ 1726 "headerButton": [
1727 { 1727 {
1728 - "name": "View details", 1728 + "name": "{i18n:api-usage.view-details}",
1729 "icon": "insert_chart", 1729 "icon": "insert_chart",
1730 "type": "openDashboardState", 1730 "type": "openDashboardState",
1731 "targetDashboardStateId": "email_messages", 1731 "targetDashboardStateId": "email_messages",
@@ -1774,7 +1774,7 @@ @@ -1774,7 +1774,7 @@
1774 { 1774 {
1775 "name": "smsCountHourly", 1775 "name": "smsCountHourly",
1776 "type": "timeseries", 1776 "type": "timeseries",
1777 - "label": "SMS messages", 1777 + "label": "{i18n:api-usage.sms-messages}",
1778 "color": "#f36021", 1778 "color": "#f36021",
1779 "settings": { 1779 "settings": {
1780 "excludeFromStacking": false, 1780 "excludeFromStacking": false,
@@ -1859,7 +1859,7 @@ @@ -1859,7 +1859,7 @@
1859 "showLabels": true 1859 "showLabels": true
1860 } 1860 }
1861 }, 1861 },
1862 - "title": "SMS messages hourly activity", 1862 + "title": "{i18n:api-usage.sms-messages-hourly-activity}",
1863 "dropShadow": true, 1863 "dropShadow": true,
1864 "enableFullscreen": true, 1864 "enableFullscreen": true,
1865 "titleStyle": { 1865 "titleStyle": {
@@ -1872,7 +1872,7 @@ @@ -1872,7 +1872,7 @@
1872 "actions": { 1872 "actions": {
1873 "headerButton": [ 1873 "headerButton": [
1874 { 1874 {
1875 - "name": "View details", 1875 + "name": "{i18n:api-usage.view-details}",
1876 "icon": "insert_chart", 1876 "icon": "insert_chart",
1877 "type": "openDashboardState", 1877 "type": "openDashboardState",
1878 "targetDashboardStateId": "sms_messages", 1878 "targetDashboardStateId": "sms_messages",
@@ -1921,7 +1921,7 @@ @@ -1921,7 +1921,7 @@
1921 { 1921 {
1922 "name": "ruleEngineExecutionCountHourly", 1922 "name": "ruleEngineExecutionCountHourly",
1923 "type": "timeseries", 1923 "type": "timeseries",
1924 - "label": "Rule Engine executions", 1924 + "label": "{i18n:api-usage.rule-engine-executions}",
1925 "color": "#ab00ff", 1925 "color": "#ab00ff",
1926 "settings": { 1926 "settings": {
1927 "excludeFromStacking": false, 1927 "excludeFromStacking": false,
@@ -2007,7 +2007,7 @@ @@ -2007,7 +2007,7 @@
2007 "showLabels": true 2007 "showLabels": true
2008 } 2008 }
2009 }, 2009 },
2010 - "title": "Rule Engine daily activity", 2010 + "title": "{i18n:api-usage.rule-engine-daily-activity}",
2011 "dropShadow": true, 2011 "dropShadow": true,
2012 "enableFullscreen": true, 2012 "enableFullscreen": true,
2013 "titleStyle": { 2013 "titleStyle": {
@@ -2056,7 +2056,7 @@ @@ -2056,7 +2056,7 @@
2056 { 2056 {
2057 "name": "ruleEngineExecutionCount", 2057 "name": "ruleEngineExecutionCount",
2058 "type": "timeseries", 2058 "type": "timeseries",
2059 - "label": "Rule Engine executions", 2059 + "label": "{i18n:api-usage.rule-engine-executions}",
2060 "color": "#ab00ff", 2060 "color": "#ab00ff",
2061 "settings": { 2061 "settings": {
2062 "excludeFromStacking": false, 2062 "excludeFromStacking": false,
@@ -2142,7 +2142,7 @@ @@ -2142,7 +2142,7 @@
2142 "showLabels": true 2142 "showLabels": true
2143 } 2143 }
2144 }, 2144 },
2145 - "title": "Rule Engine monthly activity", 2145 + "title": "{i18n:api-usage.rule-engine-monthly-activity}",
2146 "dropShadow": true, 2146 "dropShadow": true,
2147 "enableFullscreen": true, 2147 "enableFullscreen": true,
2148 "titleStyle": { 2148 "titleStyle": {
@@ -2191,7 +2191,7 @@ @@ -2191,7 +2191,7 @@
2191 { 2191 {
2192 "name": "jsExecutionCountHourly", 2192 "name": "jsExecutionCountHourly",
2193 "type": "timeseries", 2193 "type": "timeseries",
2194 - "label": "JavaScript executions", 2194 + "label": "{i18n:api-usage.javascript-executions}",
2195 "color": "#ff9900", 2195 "color": "#ff9900",
2196 "settings": { 2196 "settings": {
2197 "excludeFromStacking": false, 2197 "excludeFromStacking": false,
@@ -2277,7 +2277,7 @@ @@ -2277,7 +2277,7 @@
2277 "showLabels": true 2277 "showLabels": true
2278 } 2278 }
2279 }, 2279 },
2280 - "title": "JavaScript functions daily activity", 2280 + "title": "{i18n:api-usage.javascript-functions-daily-activity}",
2281 "dropShadow": true, 2281 "dropShadow": true,
2282 "enableFullscreen": true, 2282 "enableFullscreen": true,
2283 "titleStyle": { 2283 "titleStyle": {
@@ -2326,7 +2326,7 @@ @@ -2326,7 +2326,7 @@
2326 { 2326 {
2327 "name": "jsExecutionCount", 2327 "name": "jsExecutionCount",
2328 "type": "timeseries", 2328 "type": "timeseries",
2329 - "label": "JavaScript executions", 2329 + "label": "{i18n:api-usage.javascript-executions}",
2330 "color": "#ff9900", 2330 "color": "#ff9900",
2331 "settings": { 2331 "settings": {
2332 "excludeFromStacking": false, 2332 "excludeFromStacking": false,
@@ -2412,7 +2412,7 @@ @@ -2412,7 +2412,7 @@
2412 "showLabels": true 2412 "showLabels": true
2413 } 2413 }
2414 }, 2414 },
2415 - "title": "JavaScript functions monthly activity", 2415 + "title": "{i18n:api-usage.javascript-functions-monthly-activity}",
2416 "dropShadow": true, 2416 "dropShadow": true,
2417 "enableFullscreen": true, 2417 "enableFullscreen": true,
2418 "titleStyle": { 2418 "titleStyle": {
@@ -2461,7 +2461,7 @@ @@ -2461,7 +2461,7 @@
2461 { 2461 {
2462 "name": "transportMsgCountHourly", 2462 "name": "transportMsgCountHourly",
2463 "type": "timeseries", 2463 "type": "timeseries",
2464 - "label": "Transport messages", 2464 + "label": "{i18n:api-usage.transport-messages}",
2465 "color": "#2196f3", 2465 "color": "#2196f3",
2466 "settings": { 2466 "settings": {
2467 "excludeFromStacking": false, 2467 "excludeFromStacking": false,
@@ -2496,7 +2496,7 @@ @@ -2496,7 +2496,7 @@
2496 { 2496 {
2497 "name": "transportDataPointsCountHourly", 2497 "name": "transportDataPointsCountHourly",
2498 "type": "timeseries", 2498 "type": "timeseries",
2499 - "label": "Transport data points", 2499 + "label": "{i18n:api-usage.transport-data-points}",
2500 "color": "#4caf50", 2500 "color": "#4caf50",
2501 "settings": { 2501 "settings": {
2502 "excludeFromStacking": false, 2502 "excludeFromStacking": false,
@@ -2583,7 +2583,7 @@ @@ -2583,7 +2583,7 @@
2583 }, 2583 },
2584 "tooltipCumulative": false 2584 "tooltipCumulative": false
2585 }, 2585 },
2586 - "title": "Transport daily activity", 2586 + "title": "{i18n:api-usage.transport-daily-activity}",
2587 "dropShadow": true, 2587 "dropShadow": true,
2588 "enableFullscreen": true, 2588 "enableFullscreen": true,
2589 "titleStyle": { 2589 "titleStyle": {
@@ -2632,7 +2632,7 @@ @@ -2632,7 +2632,7 @@
2632 { 2632 {
2633 "name": "transportMsgCount", 2633 "name": "transportMsgCount",
2634 "type": "timeseries", 2634 "type": "timeseries",
2635 - "label": "Transport messages", 2635 + "label": "{i18n:api-usage.transport-messages}",
2636 "color": "#2196f3", 2636 "color": "#2196f3",
2637 "settings": { 2637 "settings": {
2638 "excludeFromStacking": false, 2638 "excludeFromStacking": false,
@@ -2667,7 +2667,7 @@ @@ -2667,7 +2667,7 @@
2667 { 2667 {
2668 "name": "transportDataPointsCount", 2668 "name": "transportDataPointsCount",
2669 "type": "timeseries", 2669 "type": "timeseries",
2670 - "label": "Transport data points", 2670 + "label": "{i18n:api-usage.transport-data-points}",
2671 "color": "#4caf50", 2671 "color": "#4caf50",
2672 "settings": { 2672 "settings": {
2673 "excludeFromStacking": false, 2673 "excludeFromStacking": false,
@@ -2754,7 +2754,7 @@ @@ -2754,7 +2754,7 @@
2754 }, 2754 },
2755 "tooltipCumulative": false 2755 "tooltipCumulative": false
2756 }, 2756 },
2757 - "title": "Transport monthly activity", 2757 + "title": "{i18n:api-usage.transport-monthly-activity}",
2758 "dropShadow": true, 2758 "dropShadow": true,
2759 "enableFullscreen": true, 2759 "enableFullscreen": true,
2760 "titleStyle": { 2760 "titleStyle": {
@@ -2803,7 +2803,7 @@ @@ -2803,7 +2803,7 @@
2803 { 2803 {
2804 "name": "storageDataPointsCountHourly", 2804 "name": "storageDataPointsCountHourly",
2805 "type": "timeseries", 2805 "type": "timeseries",
2806 - "label": "Data points storage days", 2806 + "label": "{i18n:api-usage.data-points-storage-days}",
2807 "color": "#1039ee", 2807 "color": "#1039ee",
2808 "settings": { 2808 "settings": {
2809 "excludeFromStacking": false, 2809 "excludeFromStacking": false,
@@ -2889,7 +2889,7 @@ @@ -2889,7 +2889,7 @@
2889 "showLabels": true 2889 "showLabels": true
2890 } 2890 }
2891 }, 2891 },
2892 - "title": "Telemetry persistence daily activity", 2892 + "title": "{i18n:api-usage.telemetry-persistence-daily-activity}",
2893 "dropShadow": true, 2893 "dropShadow": true,
2894 "enableFullscreen": true, 2894 "enableFullscreen": true,
2895 "titleStyle": { 2895 "titleStyle": {
@@ -2938,7 +2938,7 @@ @@ -2938,7 +2938,7 @@
2938 { 2938 {
2939 "name": "storageDataPointsCount", 2939 "name": "storageDataPointsCount",
2940 "type": "timeseries", 2940 "type": "timeseries",
2941 - "label": "Data points storage days", 2941 + "label": "{i18n:api-usage.data-points-storage-days}",
2942 "color": "#1039ee", 2942 "color": "#1039ee",
2943 "settings": { 2943 "settings": {
2944 "excludeFromStacking": false, 2944 "excludeFromStacking": false,
@@ -3024,7 +3024,7 @@ @@ -3024,7 +3024,7 @@
3024 "showLabels": true 3024 "showLabels": true
3025 } 3025 }
3026 }, 3026 },
3027 - "title": "Telemetry persistence monthly activity", 3027 + "title": "{i18n:api-usage.telemetry-persistence-monthly-activity}",
3028 "dropShadow": true, 3028 "dropShadow": true,
3029 "enableFullscreen": true, 3029 "enableFullscreen": true,
3030 "titleStyle": { 3030 "titleStyle": {
@@ -3073,7 +3073,7 @@ @@ -3073,7 +3073,7 @@
3073 { 3073 {
3074 "name": "emailCountHourly", 3074 "name": "emailCountHourly",
3075 "type": "timeseries", 3075 "type": "timeseries",
3076 - "label": "Email messages", 3076 + "label": "{i18n:api-usage.email-messages}",
3077 "color": "#d35a00", 3077 "color": "#d35a00",
3078 "settings": { 3078 "settings": {
3079 "excludeFromStacking": false, 3079 "excludeFromStacking": false,
@@ -3159,7 +3159,7 @@ @@ -3159,7 +3159,7 @@
3159 "showLabels": true 3159 "showLabels": true
3160 } 3160 }
3161 }, 3161 },
3162 - "title": "Email messages daily activity", 3162 + "title": "{i18n:api-usage.email-messages-daily-activity}",
3163 "dropShadow": true, 3163 "dropShadow": true,
3164 "enableFullscreen": true, 3164 "enableFullscreen": true,
3165 "titleStyle": { 3165 "titleStyle": {
@@ -3208,7 +3208,7 @@ @@ -3208,7 +3208,7 @@
3208 { 3208 {
3209 "name": "emailCount", 3209 "name": "emailCount",
3210 "type": "timeseries", 3210 "type": "timeseries",
3211 - "label": "Email messages", 3211 + "label": "{i18n:api-usage.email-messages}",
3212 "color": "#d35a00", 3212 "color": "#d35a00",
3213 "settings": { 3213 "settings": {
3214 "excludeFromStacking": false, 3214 "excludeFromStacking": false,
@@ -3294,7 +3294,7 @@ @@ -3294,7 +3294,7 @@
3294 "showLabels": true 3294 "showLabels": true
3295 } 3295 }
3296 }, 3296 },
3297 - "title": "Email messages monthly activity", 3297 + "title": "{i18n:api-usage.sms-messages-monthly-activity}",
3298 "dropShadow": true, 3298 "dropShadow": true,
3299 "enableFullscreen": true, 3299 "enableFullscreen": true,
3300 "titleStyle": { 3300 "titleStyle": {
@@ -3343,7 +3343,7 @@ @@ -3343,7 +3343,7 @@
3343 { 3343 {
3344 "name": "smsCountHourly", 3344 "name": "smsCountHourly",
3345 "type": "timeseries", 3345 "type": "timeseries",
3346 - "label": "SMS messages", 3346 + "label": "{i18n:api-usage.sms-messages}",
3347 "color": "#f36021", 3347 "color": "#f36021",
3348 "settings": { 3348 "settings": {
3349 "excludeFromStacking": false, 3349 "excludeFromStacking": false,
@@ -3429,7 +3429,7 @@ @@ -3429,7 +3429,7 @@
3429 "showLabels": true 3429 "showLabels": true
3430 } 3430 }
3431 }, 3431 },
3432 - "title": "SMS messages daily activity", 3432 + "title": "{i18n:api-usage.sms-messages-daily-activity}",
3433 "dropShadow": true, 3433 "dropShadow": true,
3434 "enableFullscreen": true, 3434 "enableFullscreen": true,
3435 "titleStyle": { 3435 "titleStyle": {
@@ -3478,7 +3478,7 @@ @@ -3478,7 +3478,7 @@
3478 { 3478 {
3479 "name": "smsCount", 3479 "name": "smsCount",
3480 "type": "timeseries", 3480 "type": "timeseries",
3481 - "label": "SMS messages", 3481 + "label": "{i18n:api-usage.sms-messages}",
3482 "color": "#f36021", 3482 "color": "#f36021",
3483 "settings": { 3483 "settings": {
3484 "excludeFromStacking": false, 3484 "excludeFromStacking": false,
@@ -3564,7 +3564,7 @@ @@ -3564,7 +3564,7 @@
3564 "showLabels": true 3564 "showLabels": true
3565 } 3565 }
3566 }, 3566 },
3567 - "title": "SMS messages monthly activity", 3567 + "title": "{i18n:api-usage.sms-messages-monthly-activity}",
3568 "dropShadow": true, 3568 "dropShadow": true,
3569 "enableFullscreen": true, 3569 "enableFullscreen": true,
3570 "titleStyle": { 3570 "titleStyle": {
@@ -3610,7 +3610,7 @@ @@ -3610,7 +3610,7 @@
3610 { 3610 {
3611 "name": "successfulMsgs", 3611 "name": "successfulMsgs",
3612 "type": "timeseries", 3612 "type": "timeseries",
3613 - "label": "${entityName} Successful", 3613 + "label": "{i18n:api-usage.successful}",
3614 "color": "#4caf50", 3614 "color": "#4caf50",
3615 "settings": { 3615 "settings": {
3616 "excludeFromStacking": false, 3616 "excludeFromStacking": false,
@@ -3640,7 +3640,7 @@ @@ -3640,7 +3640,7 @@
3640 { 3640 {
3641 "name": "failedMsgs", 3641 "name": "failedMsgs",
3642 "type": "timeseries", 3642 "type": "timeseries",
3643 - "label": "${entityName} Permanent Failures", 3643 + "label": "{i18n:api-usage.permanent-failures}",
3644 "color": "#ef5350", 3644 "color": "#ef5350",
3645 "settings": { 3645 "settings": {
3646 "excludeFromStacking": false, 3646 "excludeFromStacking": false,
@@ -3670,7 +3670,7 @@ @@ -3670,7 +3670,7 @@
3670 { 3670 {
3671 "name": "tmpFailed", 3671 "name": "tmpFailed",
3672 "type": "timeseries", 3672 "type": "timeseries",
3673 - "label": "${entityName} Processing Failures", 3673 + "label": "{i18n:api-usage.processing-failures}",
3674 "color": "#ffc107", 3674 "color": "#ffc107",
3675 "settings": { 3675 "settings": {
3676 "excludeFromStacking": false, 3676 "excludeFromStacking": false,
@@ -3794,7 +3794,7 @@ @@ -3794,7 +3794,7 @@
3794 { 3794 {
3795 "name": "timeoutMsgs", 3795 "name": "timeoutMsgs",
3796 "type": "timeseries", 3796 "type": "timeseries",
3797 - "label": "${entityName} Permanent Timeouts", 3797 + "label": "{i18n:api-usage.permanent-timeouts}",
3798 "color": "#4caf50", 3798 "color": "#4caf50",
3799 "settings": { 3799 "settings": {
3800 "excludeFromStacking": false, 3800 "excludeFromStacking": false,
@@ -3824,7 +3824,7 @@ @@ -3824,7 +3824,7 @@
3824 { 3824 {
3825 "name": "tmpTimeout", 3825 "name": "tmpTimeout",
3826 "type": "timeseries", 3826 "type": "timeseries",
3827 - "label": "${entityName} Processing Timeouts", 3827 + "label": "{i18n:api-usage.processing-timeouts}",
3828 "color": "#9c27b0", 3828 "color": "#9c27b0",
3829 "settings": { 3829 "settings": {
3830 "excludeFromStacking": false, 3830 "excludeFromStacking": false,
@@ -4031,7 +4031,7 @@ @@ -4031,7 +4031,7 @@
4031 }, 4031 },
4032 "states": { 4032 "states": {
4033 "default": { 4033 "default": {
4034 - "name": "Api Usage", 4034 + "name": "{i18n:api-usage.api-usage}",
4035 "root": true, 4035 "root": true,
4036 "layouts": { 4036 "layouts": {
4037 "main": { 4037 "main": {
@@ -4130,7 +4130,7 @@ @@ -4130,7 +4130,7 @@
4130 } 4130 }
4131 }, 4131 },
4132 "transport": { 4132 "transport": {
4133 - "name": "Transport", 4133 + "name": "{i18n:api-usage.transport}",
4134 "root": false, 4134 "root": false,
4135 "layouts": { 4135 "layouts": {
4136 "main": { 4136 "main": {
@@ -4163,7 +4163,7 @@ @@ -4163,7 +4163,7 @@
4163 } 4163 }
4164 }, 4164 },
4165 "rule_engine_execution": { 4165 "rule_engine_execution": {
4166 - "name": "Rule Engine execution", 4166 + "name": "{i18n:api-usage.rule-engine-executions}",
4167 "root": false, 4167 "root": false,
4168 "layouts": { 4168 "layouts": {
4169 "main": { 4169 "main": {
@@ -4196,7 +4196,7 @@ @@ -4196,7 +4196,7 @@
4196 } 4196 }
4197 }, 4197 },
4198 "javascript_functions": { 4198 "javascript_functions": {
4199 - "name": "JavaScript functions", 4199 + "name": "{i18n:api-usage.javascript-functions}",
4200 "root": false, 4200 "root": false,
4201 "layouts": { 4201 "layouts": {
4202 "main": { 4202 "main": {
@@ -4229,7 +4229,7 @@ @@ -4229,7 +4229,7 @@
4229 } 4229 }
4230 }, 4230 },
4231 "telemetry_persistence": { 4231 "telemetry_persistence": {
4232 - "name": "Telemetry persistence", 4232 + "name": "{i18n:api-usage.telemetry-persistence}",
4233 "root": false, 4233 "root": false,
4234 "layouts": { 4234 "layouts": {
4235 "main": { 4235 "main": {
@@ -4262,7 +4262,7 @@ @@ -4262,7 +4262,7 @@
4262 } 4262 }
4263 }, 4263 },
4264 "email_messages": { 4264 "email_messages": {
4265 - "name": "Email messages", 4265 + "name": "{i18n:api-usage.email-messages}",
4266 "root": false, 4266 "root": false,
4267 "layouts": { 4267 "layouts": {
4268 "main": { 4268 "main": {
@@ -4295,7 +4295,7 @@ @@ -4295,7 +4295,7 @@
4295 } 4295 }
4296 }, 4296 },
4297 "sms_messages": { 4297 "sms_messages": {
4298 - "name": "SMS messages", 4298 + "name": "{i18n:api-usage.sms-messages}",
4299 "root": false, 4299 "root": false,
4300 "layouts": { 4300 "layouts": {
4301 "main": { 4301 "main": {
@@ -4328,7 +4328,7 @@ @@ -4328,7 +4328,7 @@
4328 } 4328 }
4329 }, 4329 },
4330 "rule_engine_statistics": { 4330 "rule_engine_statistics": {
4331 - "name": "Rule Engine Statistics", 4331 + "name": "{i18n:api-usage.rule-engine-statistics}",
4332 "root": false, 4332 "root": false,
4333 "layouts": { 4333 "layouts": {
4334 "main": { 4334 "main": {
@@ -4417,4 +4417,4 @@ @@ -4417,4 +4417,4 @@
4417 } 4417 }
4418 }, 4418 },
4419 "name": "Api Usage" 4419 "name": "Api Usage"
4420 -}  
  4420 +}
@@ -21,48 +21,46 @@ @@ -21,48 +21,46 @@
21 {{'rulechain.open-rulechain' | translate }} 21 {{'rulechain.open-rulechain' | translate }}
22 </button> 22 </button>
23 </div> 23 </div>
24 -<div class="mat-padding" fxLayout="column">  
25 - <form [formGroup]="ruleNodeFormGroup">  
26 - <fieldset [disabled]="(isLoading$ | async) || !isEdit || isReadOnly">  
27 - <section *ngIf="ruleNode.component.type !== ruleNodeType.RULE_CHAIN">  
28 - <section fxLayout="column" fxLayout.gt-sm="row">  
29 - <mat-form-field fxFlex class="mat-block">  
30 - <mat-label translate>rulenode.name</mat-label>  
31 - <input matInput formControlName="name" required>  
32 - <mat-error *ngIf="ruleNodeFormGroup.get('name').hasError('required')  
33 - || ruleNodeFormGroup.get('name').hasError('pattern')">  
34 - {{ 'rulenode.name-required' | translate }}  
35 - </mat-error>  
36 - </mat-form-field>  
37 - <mat-checkbox formControlName="debugMode">  
38 - {{ 'rulenode.debug-mode' | translate }}  
39 - </mat-checkbox>  
40 - </section>  
41 - <tb-rule-node-config #ruleNodeConfigComponent  
42 - formControlName="configuration"  
43 - [ruleNodeId]="ruleNode.ruleNodeId?.id"  
44 - [nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition">  
45 - </tb-rule-node-config>  
46 - <div formGroupName="additionalInfo" fxLayout="column">  
47 - <mat-form-field class="mat-block">  
48 - <mat-label translate>rulenode.description</mat-label>  
49 - <textarea matInput formControlName="description" rows="2"></textarea>  
50 - </mat-form-field>  
51 - </div> 24 +<form [formGroup]="ruleNodeFormGroup" class="mat-padding">
  25 + <fieldset [disabled]="(isLoading$ | async) || !isEdit || isReadOnly">
  26 + <section *ngIf="ruleNode.component.type !== ruleNodeType.RULE_CHAIN">
  27 + <section fxLayout="column" fxLayout.gt-sm="row">
  28 + <mat-form-field fxFlex class="mat-block">
  29 + <mat-label translate>rulenode.name</mat-label>
  30 + <input matInput formControlName="name" required>
  31 + <mat-error *ngIf="ruleNodeFormGroup.get('name').hasError('required')
  32 + || ruleNodeFormGroup.get('name').hasError('pattern')">
  33 + {{ 'rulenode.name-required' | translate }}
  34 + </mat-error>
  35 + </mat-form-field>
  36 + <mat-checkbox formControlName="debugMode">
  37 + {{ 'rulenode.debug-mode' | translate }}
  38 + </mat-checkbox>
52 </section> 39 </section>
53 - <section *ngIf="ruleNode.component.type === ruleNodeType.RULE_CHAIN">  
54 - <tb-entity-autocomplete required  
55 - [excludeEntityIds]="[ruleChainId]"  
56 - [entityType]="entityType.RULE_CHAIN"  
57 - formControlName="targetRuleChainId">  
58 - </tb-entity-autocomplete>  
59 - <div formGroupName="additionalInfo" fxLayout="column">  
60 - <mat-form-field class="mat-block">  
61 - <mat-label translate>rulenode.description</mat-label>  
62 - <textarea matInput formControlName="description" rows="2"></textarea>  
63 - </mat-form-field>  
64 - </div>  
65 - </section>  
66 - </fieldset>  
67 - </form>  
68 -</div> 40 + <tb-rule-node-config #ruleNodeConfigComponent
  41 + formControlName="configuration"
  42 + [ruleNodeId]="ruleNode.ruleNodeId?.id"
  43 + [nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition">
  44 + </tb-rule-node-config>
  45 + <div formGroupName="additionalInfo" fxLayout="column">
  46 + <mat-form-field class="mat-block">
  47 + <mat-label translate>rulenode.description</mat-label>
  48 + <textarea matInput formControlName="description" rows="2"></textarea>
  49 + </mat-form-field>
  50 + </div>
  51 + </section>
  52 + <section *ngIf="ruleNode.component.type === ruleNodeType.RULE_CHAIN">
  53 + <tb-entity-autocomplete required
  54 + [excludeEntityIds]="[ruleChainId]"
  55 + [entityType]="entityType.RULE_CHAIN"
  56 + formControlName="targetRuleChainId">
  57 + </tb-entity-autocomplete>
  58 + <div formGroupName="additionalInfo" fxLayout="column">
  59 + <mat-form-field class="mat-block">
  60 + <mat-label translate>rulenode.description</mat-label>
  61 + <textarea matInput formControlName="description" rows="2"></textarea>
  62 + </mat-form-field>
  63 + </div>
  64 + </section>
  65 + </fieldset>
  66 +</form>
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 .tb-login-content { 21 .tb-login-content {
22 margin-top: 36px; 22 margin-top: 36px;
23 margin-bottom: 76px; 23 margin-bottom: 76px;
24 - background-color: rgb(250, 250, 250); 24 + background-color: #eee;
25 .tb-login-form { 25 .tb-login-form {
26 @media #{$mat-gt-xs} { 26 @media #{$mat-gt-xs} {
27 width: 550px !important; 27 width: 550px !important;
@@ -433,7 +433,57 @@ @@ -433,7 +433,57 @@
433 "no-telemetry-text": "No telemetry found" 433 "no-telemetry-text": "No telemetry found"
434 }, 434 },
435 "api-usage": { 435 "api-usage": {
436 - "api-usage": "Api Usage" 436 + "api-usage": "Api Usage",
  437 + "data-points": "Data points",
  438 + "data-points-storage-days": "Data points storage days",
  439 + "email": "Email",
  440 + "email-messages": "Email messages",
  441 + "email-messages-daily-activity": "Email messages daily activity",
  442 + "email-messages-hourly-activity": "Email messages hourly activity",
  443 + "email-messages-monthly-activity": "Email messages monthly activity",
  444 + "exceptions": "Exceptions",
  445 + "executions": "Executions",
  446 + "javascript": "JavaScript",
  447 + "javascript-executions": "JavaScript executions",
  448 + "javascript-functions": "JavaScript functions",
  449 + "javascript-functions-daily-activity": "JavaScript functions daily activity",
  450 + "javascript-functions-hourly-activity": "JavaScript functions hourly activity",
  451 + "javascript-functions-monthly-activity": "JavaScript functions monthly activity",
  452 + "latest-error": "Latest Error",
  453 + "messages": "Messages",
  454 + "permanent-failures": "${entityName} Permanent Failures",
  455 + "permanent-timeouts": "${entityName} Permanent Timeouts",
  456 + "processing-failures": "${entityName} Processing Failures",
  457 + "processing-failures-and-timeouts": "Processing Failures and Timeouts",
  458 + "processing-timeouts": "${entityName} Processing Timeouts",
  459 + "queue-stats": "Queue Stats",
  460 + "rule-chain": "Rule Chain",
  461 + "rule-engine": "Rule Engine",
  462 + "rule-engine-daily-activity": "Rule Engine daily activity",
  463 + "rule-engine-executions": "Rule Engine executions",
  464 + "rule-engine-hourly-activity": "Rule Engine hourly activity",
  465 + "rule-engine-monthly-activity": "Rule Engine monthly activity",
  466 + "rule-engine-statistics": "Rule Engine Statistics",
  467 + "rule-node": "Rule Node",
  468 + "sms": "SMS",
  469 + "sms-messages": "SMS messages",
  470 + "sms-messages-daily-activity": "SMS messages daily activity",
  471 + "sms-messages-hourly-activity": "SMS messages hourly activity",
  472 + "sms-messages-monthly-activity": "SMS messages monthly activity",
  473 + "successful": "${entityName} Successful",
  474 + "telemetry": "Telemetry",
  475 + "telemetry-persistence": "Telemetry persistence",
  476 + "telemetry-persistence-daily-activity": "Telemetry persistence daily activity",
  477 + "telemetry-persistence-hourly-activity": "Telemetry persistence hourly activity",
  478 + "telemetry-persistence-monthly-activity": "Telemetry persistence monthly activity",
  479 + "transport": "Transport",
  480 + "transport-daily-activity": "Transport daily activity",
  481 + "transport-data-points": "Transport data points",
  482 + "transport-hourly-activity": "Transport hourly activity",
  483 + "transport-messages": "Transport messages",
  484 + "transport-monthly-activity": "Transport monthly activity",
  485 + "view-details": "View details",
  486 + "view-statistics": "View statistics"
437 }, 487 },
438 "audit-log": { 488 "audit-log": {
439 "audit": "Audit", 489 "audit": "Audit",
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 16
17 --> 17 -->
18 <!doctype html> 18 <!doctype html>
19 -<html lang="en" style="width: 100%;"> 19 +<html lang="en" style="width: 100%; height: 100%;">
20 <head> 20 <head>
21 <meta charset="utf-8"> 21 <meta charset="utf-8">
22 <title>ThingsBoard</title> 22 <title>ThingsBoard</title>
@@ -24,8 +24,82 @@ @@ -24,8 +24,82 @@
24 24
25 <meta name="viewport" content="width=device-width, initial-scale=1"> 25 <meta name="viewport" content="width=device-width, initial-scale=1">
26 <link rel="icon" type="image/x-icon" href="thingsboard.ico"> 26 <link rel="icon" type="image/x-icon" href="thingsboard.ico">
  27 + <style type="text/css">
  28 +
  29 + body, html {
  30 + height: 100%;
  31 + overflow: hidden;
  32 + background-color: #eee;
  33 + }
  34 +
  35 + .tb-loading-spinner {
  36 + margin: auto;
  37 + z-index: 1;
  38 + position: absolute;
  39 + top: 0;
  40 + bottom: 0;
  41 + left: 0;
  42 + right: 0;
  43 + width: 136px;
  44 + height: 30px;
  45 + text-align: center;
  46 + }
  47 +
  48 + .tb-loading-spinner > div {
  49 + width: 30px;
  50 + height: 30px;
  51 + margin-right: 10px;
  52 + background-color: rgb(43,160,199);
  53 +
  54 + border-radius: 100%;
  55 + display: inline-block;
  56 + -webkit-animation: tb-bouncedelay 1.4s infinite ease-in-out both;
  57 + -moz-animation: tb-bouncedelay 1.4s infinite ease-in-out both;
  58 + animation: tb-bouncedelay 1.4s infinite ease-in-out both;
  59 + }
  60 +
  61 + .tb-loading-spinner .tb-bounce1 {
  62 + -webkit-animation-delay: -0.32s;
  63 + -moz-animation-delay: -0.32s;
  64 + animation-delay: -0.32s;
  65 + }
  66 +
  67 + .tb-loading-spinner .tb-bounce2 {
  68 + -webkit-animation-delay: -0.16s;
  69 + -moz-animation-delay: -0.16s;
  70 + animation-delay: -0.16s;
  71 + }
  72 +
  73 + @-webkit-keyframes tb-bouncedelay {
  74 + 0%, 80%, 100% { -webkit-transform: scale(0) }
  75 + 40% { -webkit-transform: scale(1.0) }
  76 + }
  77 +
  78 + @-moz-keyframes tb-bouncedelay {
  79 + 0%, 80%, 100% { -moz-transform: scale(0) }
  80 + 40% { -moz-transform: scale(1.0) }
  81 + }
  82 +
  83 + @keyframes tb-bouncedelay {
  84 + 0%, 80%, 100% {
  85 + -webkit-transform: scale(0);
  86 + -moz-transform: scale(0);
  87 + transform: scale(0);
  88 + } 40% {
  89 + -webkit-transform: scale(1.0);
  90 + -moz-transform: scale(1.0);
  91 + transform: scale(1.0);
  92 + }
  93 + }
  94 +
  95 + </style>
27 </head> 96 </head>
28 <body class="tb-default"> 97 <body class="tb-default">
29 <tb-root></tb-root> 98 <tb-root></tb-root>
  99 + <div id="tb-loading-spinner" class="tb-loading-spinner">
  100 + <div class="tb-bounce1"></div>
  101 + <div class="tb-bounce2"></div>
  102 + <div class="tb-bounce3"></div>
  103 + </div>
30 </body> 104 </body>
31 </html> 105 </html>
@@ -41,7 +41,7 @@ body, html { @@ -41,7 +41,7 @@ body, html {
41 body { 41 body {
42 margin: 0; 42 margin: 0;
43 padding: 0; 43 padding: 0;
44 - background-color: rgb(250,250,250); 44 + background-color: #eee;
45 overflow: hidden; 45 overflow: hidden;
46 } 46 }
47 47