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 61 import javax.annotation.PostConstruct;
62 62 import javax.annotation.PreDestroy;
63 63 import java.util.ArrayList;
  64 +import java.util.Arrays;
64 65 import java.util.HashSet;
65 66 import java.util.List;
66 67 import java.util.Map;
... ... @@ -73,6 +74,7 @@ import java.util.concurrent.Executors;
73 74 import java.util.concurrent.TimeUnit;
74 75 import java.util.concurrent.locks.Lock;
75 76 import java.util.concurrent.locks.ReentrantLock;
  77 +import java.util.stream.Collectors;
76 78
77 79 @Slf4j
78 80 @Service
... ... @@ -347,16 +349,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
347 349 try {
348 350 long now = System.currentTimeMillis();
349 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 353 TenantId tenantId = state.getTenantId();
352 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 359 } finally {
... ... @@ -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 372 private TenantApiUsageState getOrFetchState(TenantId tenantId) {
368 373 TenantApiUsageState tenantState = myTenantStates.get(tenantId);
369 374 if (tenantState == null) {
... ... @@ -377,6 +382,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
377 382 }
378 383 TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
379 384 tenantState = new TenantApiUsageState(tenantProfile, dbStateEntity);
  385 + List<ApiUsageRecordKey> newCounts = new ArrayList<>();
380 386 try {
381 387 List<TsKvEntry> dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getId()).get();
382 388 for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) {
... ... @@ -385,7 +391,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
385 391 for (TsKvEntry tsKvEntry : dbValues) {
386 392 if (tsKvEntry.getKey().equals(key.getApiCountKey())) {
387 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 401 } else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) {
390 402 hourlyEntryFound = true;
391 403 tenantState.putHourly(key, tsKvEntry.getTs() == tenantState.getCurrentHourTs() ? tsKvEntry.getLongValue().get() : 0L);
... ... @@ -397,6 +409,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
397 409 }
398 410 log.debug("[{}] Initialized state: {}", tenantId, dbStateEntity);
399 411 myTenantStates.put(tenantId, tenantState);
  412 + saveNewCounts(tenantState, newCounts);
400 413 } catch (InterruptedException | ExecutionException e) {
401 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 17
18 18 import java.time.LocalDate;
19 19 import java.time.LocalDateTime;
20   -import java.time.LocalTime;
21 20 import java.time.ZoneId;
22   -import java.time.ZoneOffset;
23 21 import java.time.temporal.ChronoUnit;
24 22 import java.time.temporal.TemporalAdjuster;
25 23 import java.time.temporal.TemporalAdjusters;
26   -import java.time.temporal.TemporalUnit;
27 24 import java.util.concurrent.ConcurrentHashMap;
28 25 import java.util.concurrent.ConcurrentMap;
29 26
  27 +import static java.time.ZoneOffset.UTC;
30 28 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
31 29 import static java.time.temporal.ChronoUnit.MONTHS;
32 30
33 31 public class SchedulerUtils {
34 32
35   - private final static ZoneId UTC = ZoneId.of("UTC");
36 33 private static final ConcurrentMap<String, ZoneId> tzMap = new ConcurrentHashMap<>();
37 34
38 35 public static ZoneId getZoneId(String tz) {
... ... @@ -44,7 +41,7 @@ public class SchedulerUtils {
44 41 }
45 42
46 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 47 public static long getStartOfCurrentMonth() {
... ... @@ -52,7 +49,7 @@ public class SchedulerUtils {
52 49 }
53 50
54 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 55 public static long getStartOfNextMonth() {
... ... @@ -60,7 +57,7 @@ public class SchedulerUtils {
60 57 }
61 58
62 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 63 public static long getStartOfNextNextMonth() {
... ... @@ -68,7 +65,7 @@ public class SchedulerUtils {
68 65 }
69 66
70 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 71 public static TemporalAdjuster firstDayOfNextNextMonth() {
... ...
... ... @@ -17,4 +17,4 @@
17 17 -->
18 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 62 this.matIconRegistry.addSvgIconLiteral(
63 63 'alpha-e-circle-outline',
64 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 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 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 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 198 export type SubscriptionMessageSeverity = 'info' | 'warn' | 'error' | 'success';
199 199
200 200 export interface SubscriptionMessage {
201   - severity: SubscriptionMessageSeverity,
  201 + severity: SubscriptionMessageSeverity;
202 202 message: string;
203 203 }
204 204
... ...
... ... @@ -90,7 +90,7 @@
90 90 matTooltipPosition="above"
91 91 class="mat-subheading-2 title">
92 92 <mat-icon *ngIf="widget.showTitleIcon" [ngStyle]="widget.titleIconStyle">{{widget.titleIcon}}</mat-icon>
93   - {{widget.title}}
  93 + {{widget.customTranslatedTitle}}
94 94 </span>
95 95 <tb-timewindow *ngIf="widget.hasTimewindow"
96 96 #timewindowComponent
... ...
... ... @@ -54,6 +54,7 @@ import { MatMenuTrigger } from '@angular/material/menu';
54 54 import { SafeStyle } from '@angular/platform-browser';
55 55 import { distinct } from 'rxjs/operators';
56 56 import { ResizeObserver } from '@juggle/resize-observer';
  57 +import { UtilsService } from '@core/services/utils.service';
57 58
58 59 @Component({
59 60 selector: 'tb-dashboard',
... ... @@ -168,6 +169,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
168 169 private gridsterResize$: ResizeObserver;
169 170
170 171 constructor(protected store: Store<AppState>,
  172 + public utils: UtilsService,
171 173 private timeService: TimeService,
172 174 private dialogService: DialogService,
173 175 private breakpointObserver: BreakpointObserver,
... ...
... ... @@ -17,28 +17,28 @@
17 17 -->
18 18 <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="filterPredicateValueFormGroup">
19 19 <div fxFlex fxLayout="column" [fxShow]="!dynamicMode">
20   - <div fxFlex fxLayout="column" [ngSwitch]="valueType">
  20 + <div fxLayout="column" [ngSwitch]="valueType">
21 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 23 <mat-label></mat-label>
24 24 <input matInput formControlName="defaultValue" placeholder="{{'filter.value' | translate}}">
25 25 </mat-form-field>
26 26 </ng-template>
27 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 29 <mat-label></mat-label>
30 30 <input required type="number" matInput formControlName="defaultValue"
31 31 placeholder="{{'filter.value' | translate}}">
32 32 </mat-form-field>
33 33 </ng-template>
34 34 <ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
35   - <tb-datetime fxFlex formControlName="defaultValue"
  35 + <tb-datetime formControlName="defaultValue"
36 36 dateText="filter.date"
37 37 timeText="filter.time"
38 38 required [showLabel]="false"></tb-datetime>
39 39 </ng-template>
40 40 <ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN">
41   - <mat-checkbox fxFlex formControlName="defaultValue">
  41 + <mat-checkbox formControlName="defaultValue">
42 42 {{ (filterPredicateValueFormGroup.get('defaultValue').value ? 'value.true' : 'value.false') | translate }}
43 43 </mat-checkbox>
44 44 </ng-template>
... ... @@ -46,9 +46,9 @@
46 46 <div class="tb-hint" translate>filter.default-value</div>
47 47 </div>
48 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 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 52 <mat-label></mat-label>
53 53 <mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}">
54 54 <mat-option [value]="null">
... ... @@ -62,7 +62,7 @@
62 62 <div class="tb-hint" translate>filter.dynamic-source-type</div>
63 63 </div>
64 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 66 <mat-label></mat-label>
67 67 <input matInput formControlName="sourceAttribute" placeholder="{{'filter.source-attribute' | translate}}">
68 68 </mat-form-field>
... ...
... ... @@ -27,10 +27,10 @@
27 27 </mat-toolbar>
28 28 <div mat-dialog-content>
29 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 31 {{ 'filter.editable' | translate }}
32 32 </mat-checkbox>
33   - <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
  33 + <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
34 34 <mat-form-field fxFlex class="mat-block">
35 35 <mat-label translate>filter.display-label</mat-label>
36 36 <input matInput formControlName="label">
... ... @@ -39,7 +39,7 @@
39 39 {{ 'filter.autogenerated-label' | translate }}
40 40 </mat-checkbox>
41 41 </div>
42   - <mat-form-field fxFlex class="mat-block">
  42 + <mat-form-field class="mat-block">
43 43 <mat-label translate>filter.order-priority</mat-label>
44 44 <input matInput type="number" formControlName="order">
45 45 </mat-form-field>
... ...
... ... @@ -16,8 +16,8 @@
16 16
17 17 -->
18 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 21 <mat-label fxFlex>{{filter.value.filter}}</mat-label>
22 22 <button mat-icon-button color="primary"
23 23 style="min-width: 40px;"
... ...
... ... @@ -41,7 +41,7 @@
41 41 <span *ngIf="$index > 0" translate>filter.operation.and</span>
42 42 </div>
43 43 <div fxLayout="column" fxFlex="92">
44   - <div fxLayout="row" fxLayoutAlign="start center" fxFlex>
  44 + <div fxLayout="row" fxLayoutAlign="start center">
45 45 <div fxFlex>{{ keyFilterControl.value.key.key }}</div>
46 46 <div fxFlex translate>{{ entityKeyTypeTranslations.get(keyFilterControl.value.key.type) }}</div>
47 47 <button mat-icon-button color="primary"
... ...
... ... @@ -29,37 +29,33 @@
29 29 </mat-progress-bar>
30 30 <div mat-dialog-content>
31 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 59 </div>
64 60 </div>
65 61 </fieldset>
... ...
... ... @@ -51,7 +51,7 @@
51 51 <span translate fxLayoutAlign="center center" style="margin: 16px 0"
52 52 class="tb-prompt required">device-profile.add-create-alarm-rule-prompt</span>
53 53 </div>
54   - <div fxLayout="row" *ngIf="!disabled">
  54 + <div *ngIf="!disabled">
55 55 <button mat-stroked-button color="primary"
56 56 type="button"
57 57 (click)="addCreateAlarmRule()"
... ...
... ... @@ -83,9 +83,9 @@
83 83 <tb-create-alarm-rules formControlName="createRules" style="padding-bottom: 16px;">
84 84 </tb-create-alarm-rules>
85 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 87 [fxShow]="alarmFormGroup.get('clearRule').value"
88   - fxLayoutGap="8px;" fxLayoutAlign="start center" style="padding-bottom: 8px;">
  88 + style="padding-bottom: 8px;">
89 89 <div class="clear-alarm-rule" fxFlex fxLayout="row">
90 90 <tb-alarm-rule formControlName="clearRule" fxFlex>
91 91 </tb-alarm-rule>
... ... @@ -103,8 +103,7 @@
103 103 <span translate fxLayoutAlign="center center" style="margin: 16px 0"
104 104 class="tb-prompt">device-profile.no-clear-alarm-rule</span>
105 105 </div>
106   - <div fxLayout="row" *ngIf="!disabled"
107   - [fxShow]="!alarmFormGroup.get('clearRule').value">
  106 + <div *ngIf="!disabled" [fxShow]="!alarmFormGroup.get('clearRule').value">
108 107 <button mat-stroked-button color="primary"
109 108 type="button"
110 109 (click)="addClearAlarmRule()"
... ...
... ... @@ -30,8 +30,7 @@
30 30 <span translate fxLayoutAlign="center center"
31 31 class="tb-prompt">device-profile.no-alarm-rules</span>
32 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 34 <button mat-raised-button color="primary"
36 35 type="button"
37 36 (click)="addAlarm()"
... ...
... ... @@ -39,7 +39,7 @@
39 39 </button>
40 40 </div>
41 41 </div>
42   -<div class="mat-padding" fxLayout="column">
  42 +<div [ngClass]="{'mat-padding': !standalone}" fxLayout="column">
43 43 <form [formGroup]="entityForm">
44 44 <fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;">
45 45 <mat-form-field class="mat-block">
... ...
... ... @@ -24,6 +24,7 @@ import { guid, isDefined, isEqual, isUndefined } from '@app/core/utils';
24 24 import { IterableDiffer, KeyValueDiffer } from '@angular/core';
25 25 import { IAliasController, IStateController } from '@app/core/api/widget-api.models';
26 26 import { enumerable } from '@shared/decorators/enumerable';
  27 +import { UtilsService } from '@core/services/utils.service';
27 28
28 29 export interface WidgetsData {
29 30 widgets: Array<Widget>;
... ... @@ -56,6 +57,7 @@ export interface DashboardCallbacks {
56 57 }
57 58
58 59 export interface IDashboardComponent {
  60 + utils: UtilsService;
59 61 gridsterOpts: GridsterConfig;
60 62 gridster: GridsterComponent;
61 63 dashboardWidgets: DashboardWidgets;
... ... @@ -295,6 +297,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
295 297 margin: string;
296 298
297 299 title: string;
  300 + customTranslatedTitle: string;
298 301 titleTooltip: string;
299 302 showTitle: boolean;
300 303 titleStyle: {[klass: string]: any};
... ... @@ -358,8 +361,10 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
358 361
359 362 this.title = isDefined(this.widgetContext.widgetTitle)
360 363 && this.widgetContext.widgetTitle.length ? this.widgetContext.widgetTitle : this.widget.config.title;
  364 + this.customTranslatedTitle = this.dashboard.utils.customTranslation(this.title, this.title);
361 365 this.titleTooltip = isDefined(this.widgetContext.widgetTitleTooltip)
362 366 && this.widgetContext.widgetTitleTooltip.length ? this.widgetContext.widgetTitleTooltip : this.widget.config.titleTooltip;
  367 + this.titleTooltip = this.dashboard.utils.customTranslation(this.titleTooltip, this.titleTooltip);
363 368 this.showTitle = isDefined(this.widget.config.showTitle) ? this.widget.config.showTitle : true;
364 369 this.titleStyle = this.widget.config.titleStyle ? this.widget.config.titleStyle : {};
365 370
... ...
... ... @@ -61,8 +61,8 @@
61 61 <div [formGroupName]="n" fxLayout="row" fxLayoutGap="8px">
62 62 <div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
63 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 66 <mat-label translate>admin.oauth2.protocol</mat-label>
67 67 <mat-select formControlName="scheme">
68 68 <mat-option *ngFor="let protocol of protocols" [value]="protocol">
... ...
... ... @@ -18,7 +18,12 @@
18 18 :host {
19 19 mat-card.settings-card {
20 20 margin: 8px;
21   - @media #{$mat-gt-sm} {
  21 +
  22 + @media #{$mat-md} {
  23 + width: 80%;
  24 + }
  25 +
  26 + @media #{$mat-gt-md} {
22 27 width: 60%;
23 28 }
24 29 }
... ...
... ... @@ -95,7 +95,7 @@
95 95 "decimals": null,
96 96 "funcBody": null,
97 97 "usePostProcessing": true,
98   - "postFuncBody": "return \"JavaScript\";"
  98 + "postFuncBody": "return \"{i18n:api-usage.javascript}\";"
99 99 },
100 100 {
101 101 "name": "jsExecutionApiState",
... ... @@ -108,7 +108,7 @@
108 108 "decimals": null,
109 109 "funcBody": null,
110 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 123 "color": "#666666",
124 124 "padding": "0",
125 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 129 "title": "JavaScript functions",
130 130 "dropShadow": true,
... ... @@ -253,7 +253,7 @@
253 253 "decimals": null,
254 254 "funcBody": null,
255 255 "usePostProcessing": true,
256   - "postFuncBody": "return \"Telemetry\";"
  256 + "postFuncBody": "return \"{i18n:api-usage.telemetry}\";"
257 257 },
258 258 {
259 259 "name": "dbApiState",
... ... @@ -266,7 +266,7 @@
266 266 "decimals": null,
267 267 "funcBody": null,
268 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 281 "color": "#666666",
282 282 "padding": "0",
283 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 287 "title": "Telemetry persistence",
288 288 "dropShadow": true,
... ... @@ -411,7 +411,7 @@
411 411 "decimals": null,
412 412 "funcBody": null,
413 413 "usePostProcessing": true,
414   - "postFuncBody": "return \"Rule Engine\";"
  414 + "postFuncBody": "return \"{i18n:api-usage.rule-engine}\";"
415 415 },
416 416 {
417 417 "name": "ruleEngineApiState",
... ... @@ -424,7 +424,7 @@
424 424 "decimals": null,
425 425 "funcBody": null,
426 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 439 "color": "#666666",
440 440 "padding": "0",
441 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 445 "title": "Rule Engine execution",
446 446 "dropShadow": true,
... ... @@ -605,7 +605,7 @@
605 605 "decimals": null,
606 606 "funcBody": null,
607 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 620 "color": "#666666",
621 621 "padding": "0",
622 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 626 "title": "Transport",
627 627 "dropShadow": true,
... ... @@ -750,7 +750,7 @@
750 750 "decimals": null,
751 751 "funcBody": null,
752 752 "usePostProcessing": true,
753   - "postFuncBody": "return \"Email\";"
  753 + "postFuncBody": "return \"{i18n:api-usage.email}\";"
754 754 },
755 755 {
756 756 "name": "emailApiState",
... ... @@ -763,7 +763,7 @@
763 763 "decimals": null,
764 764 "funcBody": null,
765 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 778 "color": "#666666",
779 779 "padding": "0",
780 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 784 "title": "Email messages",
785 785 "dropShadow": true,
... ... @@ -908,7 +908,7 @@
908 908 "decimals": null,
909 909 "funcBody": null,
910 910 "usePostProcessing": true,
911   - "postFuncBody": "return \"SMS\";"
  911 + "postFuncBody": "return \"{i18n:api-usage.sms}\";"
912 912 },
913 913 {
914 914 "name": "smsApiState",
... ... @@ -921,7 +921,7 @@
921 921 "decimals": null,
922 922 "funcBody": null,
923 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 936 "color": "#666666",
937 937 "padding": "0",
938 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 942 "title": "SMS messages",
943 943 "dropShadow": true,
... ... @@ -993,7 +993,7 @@
993 993 {
994 994 "name": "ruleEngineExecutionCountHourly",
995 995 "type": "timeseries",
996   - "label": "Rule Engine executions",
  996 + "label": "{i18n:api-usage.rule-engine-executions}",
997 997 "color": "#ab00ff",
998 998 "settings": {
999 999 "excludeFromStacking": false,
... ... @@ -1078,7 +1078,7 @@
1078 1078 "showLabels": true
1079 1079 }
1080 1080 },
1081   - "title": "Rule Engine hourly activity",
  1081 + "title": "{i18n:api-usage.rule-engine-hourly-activity}",
1082 1082 "dropShadow": true,
1083 1083 "enableFullscreen": true,
1084 1084 "titleStyle": {
... ... @@ -1091,7 +1091,7 @@
1091 1091 "actions": {
1092 1092 "headerButton": [
1093 1093 {
1094   - "name": "View statistics",
  1094 + "name": "{i18n:api-usage.view-statistics}",
1095 1095 "icon": "show_chart",
1096 1096 "type": "openDashboardState",
1097 1097 "targetDashboardStateId": "rule_engine_statistics",
... ... @@ -1101,7 +1101,7 @@
1101 1101 "id": "f9f08190-9ed9-d802-5b7a-c57ff84b5648"
1102 1102 },
1103 1103 {
1104   - "name": "View details",
  1104 + "name": "{i18n:api-usage.view-details}",
1105 1105 "icon": "insert_chart",
1106 1106 "type": "openDashboardState",
1107 1107 "targetDashboardStateId": "rule_engine_execution",
... ... @@ -1150,7 +1150,7 @@
1150 1150 {
1151 1151 "name": "transportMsgCountHourly",
1152 1152 "type": "timeseries",
1153   - "label": "Transport messages",
  1153 + "label": "{i18n:api-usage.transport-messages}",
1154 1154 "color": "#2196f3",
1155 1155 "settings": {
1156 1156 "excludeFromStacking": false,
... ... @@ -1185,7 +1185,7 @@
1185 1185 {
1186 1186 "name": "transportDataPointsCountHourly",
1187 1187 "type": "timeseries",
1188   - "label": "Transport data points",
  1188 + "label": "{i18n:api-usage.transport-data-points}",
1189 1189 "color": "#4caf50",
1190 1190 "settings": {
1191 1191 "excludeFromStacking": false,
... ... @@ -1271,7 +1271,7 @@
1271 1271 },
1272 1272 "tooltipCumulative": false
1273 1273 },
1274   - "title": "Transport hourly activity",
  1274 + "title": "{i18n:api-usage.transport-hourly-activity}",
1275 1275 "dropShadow": true,
1276 1276 "enableFullscreen": true,
1277 1277 "titleStyle": {
... ... @@ -1284,7 +1284,7 @@
1284 1284 "actions": {
1285 1285 "headerButton": [
1286 1286 {
1287   - "name": "View details",
  1287 + "name": "{i18n:api-usage.view-details}",
1288 1288 "icon": "insert_chart",
1289 1289 "type": "openDashboardState",
1290 1290 "targetDashboardStateId": "transport",
... ... @@ -1333,7 +1333,7 @@
1333 1333 {
1334 1334 "name": "jsExecutionCountHourly",
1335 1335 "type": "timeseries",
1336   - "label": "JavaScript executions",
  1336 + "label": "{i18n:api-usage.javascript-executions}",
1337 1337 "color": "#ff9900",
1338 1338 "settings": {
1339 1339 "excludeFromStacking": false,
... ... @@ -1418,7 +1418,7 @@
1418 1418 "showLabels": true
1419 1419 }
1420 1420 },
1421   - "title": "JavaScript functions hourly activity",
  1421 + "title": "{i18n:api-usage.javascript-functions-hourly-activity}",
1422 1422 "dropShadow": true,
1423 1423 "enableFullscreen": true,
1424 1424 "titleStyle": {
... ... @@ -1431,7 +1431,7 @@
1431 1431 "actions": {
1432 1432 "headerButton": [
1433 1433 {
1434   - "name": "View details",
  1434 + "name": "{i18n:api-usage.view-details}",
1435 1435 "icon": "insert_chart",
1436 1436 "type": "openDashboardState",
1437 1437 "targetDashboardStateId": "javascript_functions",
... ... @@ -1480,7 +1480,7 @@
1480 1480 {
1481 1481 "name": "storageDataPointsCountHourly",
1482 1482 "type": "timeseries",
1483   - "label": "Data points storage days",
  1483 + "label": "{i18n:api-usage.data-points-storage-days}",
1484 1484 "color": "#1039ee",
1485 1485 "settings": {
1486 1486 "excludeFromStacking": false,
... ... @@ -1565,7 +1565,7 @@
1565 1565 "showLabels": true
1566 1566 }
1567 1567 },
1568   - "title": "Telemetry persistence hourly activity",
  1568 + "title": "{i18n:api-usage.telemetry-persistence-hourly-activity}",
1569 1569 "dropShadow": true,
1570 1570 "enableFullscreen": true,
1571 1571 "titleStyle": {
... ... @@ -1578,7 +1578,7 @@
1578 1578 "actions": {
1579 1579 "headerButton": [
1580 1580 {
1581   - "name": "View details",
  1581 + "name": "{i18n:api-usage.view-details}",
1582 1582 "icon": "insert_chart",
1583 1583 "type": "openDashboardState",
1584 1584 "targetDashboardStateId": "telemetry_persistence",
... ... @@ -1627,7 +1627,7 @@
1627 1627 {
1628 1628 "name": "emailCountHourly",
1629 1629 "type": "timeseries",
1630   - "label": "Email messages",
  1630 + "label": "{i18n:api-usage.email-messages}",
1631 1631 "color": "#d35a00",
1632 1632 "settings": {
1633 1633 "excludeFromStacking": false,
... ... @@ -1712,7 +1712,7 @@
1712 1712 "showLabels": true
1713 1713 }
1714 1714 },
1715   - "title": "Email messages hourly activity",
  1715 + "title": "{i18n:api-usage.email-messages-hourly-activity}",
1716 1716 "dropShadow": true,
1717 1717 "enableFullscreen": true,
1718 1718 "titleStyle": {
... ... @@ -1725,7 +1725,7 @@
1725 1725 "actions": {
1726 1726 "headerButton": [
1727 1727 {
1728   - "name": "View details",
  1728 + "name": "{i18n:api-usage.view-details}",
1729 1729 "icon": "insert_chart",
1730 1730 "type": "openDashboardState",
1731 1731 "targetDashboardStateId": "email_messages",
... ... @@ -1774,7 +1774,7 @@
1774 1774 {
1775 1775 "name": "smsCountHourly",
1776 1776 "type": "timeseries",
1777   - "label": "SMS messages",
  1777 + "label": "{i18n:api-usage.sms-messages}",
1778 1778 "color": "#f36021",
1779 1779 "settings": {
1780 1780 "excludeFromStacking": false,
... ... @@ -1859,7 +1859,7 @@
1859 1859 "showLabels": true
1860 1860 }
1861 1861 },
1862   - "title": "SMS messages hourly activity",
  1862 + "title": "{i18n:api-usage.sms-messages-hourly-activity}",
1863 1863 "dropShadow": true,
1864 1864 "enableFullscreen": true,
1865 1865 "titleStyle": {
... ... @@ -1872,7 +1872,7 @@
1872 1872 "actions": {
1873 1873 "headerButton": [
1874 1874 {
1875   - "name": "View details",
  1875 + "name": "{i18n:api-usage.view-details}",
1876 1876 "icon": "insert_chart",
1877 1877 "type": "openDashboardState",
1878 1878 "targetDashboardStateId": "sms_messages",
... ... @@ -1921,7 +1921,7 @@
1921 1921 {
1922 1922 "name": "ruleEngineExecutionCountHourly",
1923 1923 "type": "timeseries",
1924   - "label": "Rule Engine executions",
  1924 + "label": "{i18n:api-usage.rule-engine-executions}",
1925 1925 "color": "#ab00ff",
1926 1926 "settings": {
1927 1927 "excludeFromStacking": false,
... ... @@ -2007,7 +2007,7 @@
2007 2007 "showLabels": true
2008 2008 }
2009 2009 },
2010   - "title": "Rule Engine daily activity",
  2010 + "title": "{i18n:api-usage.rule-engine-daily-activity}",
2011 2011 "dropShadow": true,
2012 2012 "enableFullscreen": true,
2013 2013 "titleStyle": {
... ... @@ -2056,7 +2056,7 @@
2056 2056 {
2057 2057 "name": "ruleEngineExecutionCount",
2058 2058 "type": "timeseries",
2059   - "label": "Rule Engine executions",
  2059 + "label": "{i18n:api-usage.rule-engine-executions}",
2060 2060 "color": "#ab00ff",
2061 2061 "settings": {
2062 2062 "excludeFromStacking": false,
... ... @@ -2142,7 +2142,7 @@
2142 2142 "showLabels": true
2143 2143 }
2144 2144 },
2145   - "title": "Rule Engine monthly activity",
  2145 + "title": "{i18n:api-usage.rule-engine-monthly-activity}",
2146 2146 "dropShadow": true,
2147 2147 "enableFullscreen": true,
2148 2148 "titleStyle": {
... ... @@ -2191,7 +2191,7 @@
2191 2191 {
2192 2192 "name": "jsExecutionCountHourly",
2193 2193 "type": "timeseries",
2194   - "label": "JavaScript executions",
  2194 + "label": "{i18n:api-usage.javascript-executions}",
2195 2195 "color": "#ff9900",
2196 2196 "settings": {
2197 2197 "excludeFromStacking": false,
... ... @@ -2277,7 +2277,7 @@
2277 2277 "showLabels": true
2278 2278 }
2279 2279 },
2280   - "title": "JavaScript functions daily activity",
  2280 + "title": "{i18n:api-usage.javascript-functions-daily-activity}",
2281 2281 "dropShadow": true,
2282 2282 "enableFullscreen": true,
2283 2283 "titleStyle": {
... ... @@ -2326,7 +2326,7 @@
2326 2326 {
2327 2327 "name": "jsExecutionCount",
2328 2328 "type": "timeseries",
2329   - "label": "JavaScript executions",
  2329 + "label": "{i18n:api-usage.javascript-executions}",
2330 2330 "color": "#ff9900",
2331 2331 "settings": {
2332 2332 "excludeFromStacking": false,
... ... @@ -2412,7 +2412,7 @@
2412 2412 "showLabels": true
2413 2413 }
2414 2414 },
2415   - "title": "JavaScript functions monthly activity",
  2415 + "title": "{i18n:api-usage.javascript-functions-monthly-activity}",
2416 2416 "dropShadow": true,
2417 2417 "enableFullscreen": true,
2418 2418 "titleStyle": {
... ... @@ -2461,7 +2461,7 @@
2461 2461 {
2462 2462 "name": "transportMsgCountHourly",
2463 2463 "type": "timeseries",
2464   - "label": "Transport messages",
  2464 + "label": "{i18n:api-usage.transport-messages}",
2465 2465 "color": "#2196f3",
2466 2466 "settings": {
2467 2467 "excludeFromStacking": false,
... ... @@ -2496,7 +2496,7 @@
2496 2496 {
2497 2497 "name": "transportDataPointsCountHourly",
2498 2498 "type": "timeseries",
2499   - "label": "Transport data points",
  2499 + "label": "{i18n:api-usage.transport-data-points}",
2500 2500 "color": "#4caf50",
2501 2501 "settings": {
2502 2502 "excludeFromStacking": false,
... ... @@ -2583,7 +2583,7 @@
2583 2583 },
2584 2584 "tooltipCumulative": false
2585 2585 },
2586   - "title": "Transport daily activity",
  2586 + "title": "{i18n:api-usage.transport-daily-activity}",
2587 2587 "dropShadow": true,
2588 2588 "enableFullscreen": true,
2589 2589 "titleStyle": {
... ... @@ -2632,7 +2632,7 @@
2632 2632 {
2633 2633 "name": "transportMsgCount",
2634 2634 "type": "timeseries",
2635   - "label": "Transport messages",
  2635 + "label": "{i18n:api-usage.transport-messages}",
2636 2636 "color": "#2196f3",
2637 2637 "settings": {
2638 2638 "excludeFromStacking": false,
... ... @@ -2667,7 +2667,7 @@
2667 2667 {
2668 2668 "name": "transportDataPointsCount",
2669 2669 "type": "timeseries",
2670   - "label": "Transport data points",
  2670 + "label": "{i18n:api-usage.transport-data-points}",
2671 2671 "color": "#4caf50",
2672 2672 "settings": {
2673 2673 "excludeFromStacking": false,
... ... @@ -2754,7 +2754,7 @@
2754 2754 },
2755 2755 "tooltipCumulative": false
2756 2756 },
2757   - "title": "Transport monthly activity",
  2757 + "title": "{i18n:api-usage.transport-monthly-activity}",
2758 2758 "dropShadow": true,
2759 2759 "enableFullscreen": true,
2760 2760 "titleStyle": {
... ... @@ -2803,7 +2803,7 @@
2803 2803 {
2804 2804 "name": "storageDataPointsCountHourly",
2805 2805 "type": "timeseries",
2806   - "label": "Data points storage days",
  2806 + "label": "{i18n:api-usage.data-points-storage-days}",
2807 2807 "color": "#1039ee",
2808 2808 "settings": {
2809 2809 "excludeFromStacking": false,
... ... @@ -2889,7 +2889,7 @@
2889 2889 "showLabels": true
2890 2890 }
2891 2891 },
2892   - "title": "Telemetry persistence daily activity",
  2892 + "title": "{i18n:api-usage.telemetry-persistence-daily-activity}",
2893 2893 "dropShadow": true,
2894 2894 "enableFullscreen": true,
2895 2895 "titleStyle": {
... ... @@ -2938,7 +2938,7 @@
2938 2938 {
2939 2939 "name": "storageDataPointsCount",
2940 2940 "type": "timeseries",
2941   - "label": "Data points storage days",
  2941 + "label": "{i18n:api-usage.data-points-storage-days}",
2942 2942 "color": "#1039ee",
2943 2943 "settings": {
2944 2944 "excludeFromStacking": false,
... ... @@ -3024,7 +3024,7 @@
3024 3024 "showLabels": true
3025 3025 }
3026 3026 },
3027   - "title": "Telemetry persistence monthly activity",
  3027 + "title": "{i18n:api-usage.telemetry-persistence-monthly-activity}",
3028 3028 "dropShadow": true,
3029 3029 "enableFullscreen": true,
3030 3030 "titleStyle": {
... ... @@ -3073,7 +3073,7 @@
3073 3073 {
3074 3074 "name": "emailCountHourly",
3075 3075 "type": "timeseries",
3076   - "label": "Email messages",
  3076 + "label": "{i18n:api-usage.email-messages}",
3077 3077 "color": "#d35a00",
3078 3078 "settings": {
3079 3079 "excludeFromStacking": false,
... ... @@ -3159,7 +3159,7 @@
3159 3159 "showLabels": true
3160 3160 }
3161 3161 },
3162   - "title": "Email messages daily activity",
  3162 + "title": "{i18n:api-usage.email-messages-daily-activity}",
3163 3163 "dropShadow": true,
3164 3164 "enableFullscreen": true,
3165 3165 "titleStyle": {
... ... @@ -3208,7 +3208,7 @@
3208 3208 {
3209 3209 "name": "emailCount",
3210 3210 "type": "timeseries",
3211   - "label": "Email messages",
  3211 + "label": "{i18n:api-usage.email-messages}",
3212 3212 "color": "#d35a00",
3213 3213 "settings": {
3214 3214 "excludeFromStacking": false,
... ... @@ -3294,7 +3294,7 @@
3294 3294 "showLabels": true
3295 3295 }
3296 3296 },
3297   - "title": "Email messages monthly activity",
  3297 + "title": "{i18n:api-usage.sms-messages-monthly-activity}",
3298 3298 "dropShadow": true,
3299 3299 "enableFullscreen": true,
3300 3300 "titleStyle": {
... ... @@ -3343,7 +3343,7 @@
3343 3343 {
3344 3344 "name": "smsCountHourly",
3345 3345 "type": "timeseries",
3346   - "label": "SMS messages",
  3346 + "label": "{i18n:api-usage.sms-messages}",
3347 3347 "color": "#f36021",
3348 3348 "settings": {
3349 3349 "excludeFromStacking": false,
... ... @@ -3429,7 +3429,7 @@
3429 3429 "showLabels": true
3430 3430 }
3431 3431 },
3432   - "title": "SMS messages daily activity",
  3432 + "title": "{i18n:api-usage.sms-messages-daily-activity}",
3433 3433 "dropShadow": true,
3434 3434 "enableFullscreen": true,
3435 3435 "titleStyle": {
... ... @@ -3478,7 +3478,7 @@
3478 3478 {
3479 3479 "name": "smsCount",
3480 3480 "type": "timeseries",
3481   - "label": "SMS messages",
  3481 + "label": "{i18n:api-usage.sms-messages}",
3482 3482 "color": "#f36021",
3483 3483 "settings": {
3484 3484 "excludeFromStacking": false,
... ... @@ -3564,7 +3564,7 @@
3564 3564 "showLabels": true
3565 3565 }
3566 3566 },
3567   - "title": "SMS messages monthly activity",
  3567 + "title": "{i18n:api-usage.sms-messages-monthly-activity}",
3568 3568 "dropShadow": true,
3569 3569 "enableFullscreen": true,
3570 3570 "titleStyle": {
... ... @@ -3610,7 +3610,7 @@
3610 3610 {
3611 3611 "name": "successfulMsgs",
3612 3612 "type": "timeseries",
3613   - "label": "${entityName} Successful",
  3613 + "label": "{i18n:api-usage.successful}",
3614 3614 "color": "#4caf50",
3615 3615 "settings": {
3616 3616 "excludeFromStacking": false,
... ... @@ -3640,7 +3640,7 @@
3640 3640 {
3641 3641 "name": "failedMsgs",
3642 3642 "type": "timeseries",
3643   - "label": "${entityName} Permanent Failures",
  3643 + "label": "{i18n:api-usage.permanent-failures}",
3644 3644 "color": "#ef5350",
3645 3645 "settings": {
3646 3646 "excludeFromStacking": false,
... ... @@ -3670,7 +3670,7 @@
3670 3670 {
3671 3671 "name": "tmpFailed",
3672 3672 "type": "timeseries",
3673   - "label": "${entityName} Processing Failures",
  3673 + "label": "{i18n:api-usage.processing-failures}",
3674 3674 "color": "#ffc107",
3675 3675 "settings": {
3676 3676 "excludeFromStacking": false,
... ... @@ -3794,7 +3794,7 @@
3794 3794 {
3795 3795 "name": "timeoutMsgs",
3796 3796 "type": "timeseries",
3797   - "label": "${entityName} Permanent Timeouts",
  3797 + "label": "{i18n:api-usage.permanent-timeouts}",
3798 3798 "color": "#4caf50",
3799 3799 "settings": {
3800 3800 "excludeFromStacking": false,
... ... @@ -3824,7 +3824,7 @@
3824 3824 {
3825 3825 "name": "tmpTimeout",
3826 3826 "type": "timeseries",
3827   - "label": "${entityName} Processing Timeouts",
  3827 + "label": "{i18n:api-usage.processing-timeouts}",
3828 3828 "color": "#9c27b0",
3829 3829 "settings": {
3830 3830 "excludeFromStacking": false,
... ... @@ -4031,7 +4031,7 @@
4031 4031 },
4032 4032 "states": {
4033 4033 "default": {
4034   - "name": "Api Usage",
  4034 + "name": "{i18n:api-usage.api-usage}",
4035 4035 "root": true,
4036 4036 "layouts": {
4037 4037 "main": {
... ... @@ -4130,7 +4130,7 @@
4130 4130 }
4131 4131 },
4132 4132 "transport": {
4133   - "name": "Transport",
  4133 + "name": "{i18n:api-usage.transport}",
4134 4134 "root": false,
4135 4135 "layouts": {
4136 4136 "main": {
... ... @@ -4163,7 +4163,7 @@
4163 4163 }
4164 4164 },
4165 4165 "rule_engine_execution": {
4166   - "name": "Rule Engine execution",
  4166 + "name": "{i18n:api-usage.rule-engine-executions}",
4167 4167 "root": false,
4168 4168 "layouts": {
4169 4169 "main": {
... ... @@ -4196,7 +4196,7 @@
4196 4196 }
4197 4197 },
4198 4198 "javascript_functions": {
4199   - "name": "JavaScript functions",
  4199 + "name": "{i18n:api-usage.javascript-functions}",
4200 4200 "root": false,
4201 4201 "layouts": {
4202 4202 "main": {
... ... @@ -4229,7 +4229,7 @@
4229 4229 }
4230 4230 },
4231 4231 "telemetry_persistence": {
4232   - "name": "Telemetry persistence",
  4232 + "name": "{i18n:api-usage.telemetry-persistence}",
4233 4233 "root": false,
4234 4234 "layouts": {
4235 4235 "main": {
... ... @@ -4262,7 +4262,7 @@
4262 4262 }
4263 4263 },
4264 4264 "email_messages": {
4265   - "name": "Email messages",
  4265 + "name": "{i18n:api-usage.email-messages}",
4266 4266 "root": false,
4267 4267 "layouts": {
4268 4268 "main": {
... ... @@ -4295,7 +4295,7 @@
4295 4295 }
4296 4296 },
4297 4297 "sms_messages": {
4298   - "name": "SMS messages",
  4298 + "name": "{i18n:api-usage.sms-messages}",
4299 4299 "root": false,
4300 4300 "layouts": {
4301 4301 "main": {
... ... @@ -4328,7 +4328,7 @@
4328 4328 }
4329 4329 },
4330 4330 "rule_engine_statistics": {
4331   - "name": "Rule Engine Statistics",
  4331 + "name": "{i18n:api-usage.rule-engine-statistics}",
4332 4332 "root": false,
4333 4333 "layouts": {
4334 4334 "main": {
... ... @@ -4417,4 +4417,4 @@
4417 4417 }
4418 4418 },
4419 4419 "name": "Api Usage"
4420   -}
\ No newline at end of file
  4420 +}
... ...
... ... @@ -21,48 +21,46 @@
21 21 {{'rulechain.open-rulechain' | translate }}
22 22 </button>
23 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 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 21 .tb-login-content {
22 22 margin-top: 36px;
23 23 margin-bottom: 76px;
24   - background-color: rgb(250, 250, 250);
  24 + background-color: #eee;
25 25 .tb-login-form {
26 26 @media #{$mat-gt-xs} {
27 27 width: 550px !important;
... ...
... ... @@ -433,7 +433,57 @@
433 433 "no-telemetry-text": "No telemetry found"
434 434 },
435 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 488 "audit-log": {
439 489 "audit": "Audit",
... ...
... ... @@ -16,7 +16,7 @@
16 16
17 17 -->
18 18 <!doctype html>
19   -<html lang="en" style="width: 100%;">
  19 +<html lang="en" style="width: 100%; height: 100%;">
20 20 <head>
21 21 <meta charset="utf-8">
22 22 <title>ThingsBoard</title>
... ... @@ -24,8 +24,82 @@
24 24
25 25 <meta name="viewport" content="width=device-width, initial-scale=1">
26 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 96 </head>
28 97 <body class="tb-default">
29 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 104 </body>
31 105 </html>
... ...
... ... @@ -41,7 +41,7 @@ body, html {
41 41 body {
42 42 margin: 0;
43 43 padding: 0;
44   - background-color: rgb(250,250,250);
  44 + background-color: #eee;
45 45 overflow: hidden;
46 46 }
47 47
... ...