Commit 712e756e589d671d1423b3e60a51015e6f7bfd65

Authored by Volodymyr Babak
1 parent ae856411

Refactoring to simplify reading and merge

@@ -21,11 +21,13 @@ export interface SysParamsState { @@ -21,11 +21,13 @@ export interface SysParamsState {
21 allowedDashboardIds: string[]; 21 allowedDashboardIds: string[];
22 edgesSupportEnabled: boolean; 22 edgesSupportEnabled: boolean;
23 } 23 }
  24 +
24 export interface AuthPayload extends SysParamsState { 25 export interface AuthPayload extends SysParamsState {
25 authUser: AuthUser; 26 authUser: AuthUser;
26 userDetails: User; 27 userDetails: User;
27 forceFullscreen: boolean; 28 forceFullscreen: boolean;
28 } 29 }
  30 +
29 export interface AuthState extends AuthPayload { 31 export interface AuthState extends AuthPayload {
30 isAuthenticated: boolean; 32 isAuthenticated: boolean;
31 isUserLoaded: boolean; 33 isUserLoaded: boolean;
@@ -295,9 +295,9 @@ export class RuleChainService { @@ -295,9 +295,9 @@ export class RuleChainService {
295 ); 295 );
296 } 296 }
297 297
298 - public getEdgeRuleChains(edgeId: string, pageLink: PageLink, config?:RequestConfig): Observable<PageData<RuleChain>> { 298 + public getEdgeRuleChains(edgeId: string, pageLink: PageLink, config?: RequestConfig): Observable<PageData<RuleChain>> {
299 return this.http.get<PageData<RuleChain>>(`/api/edge/${edgeId}/ruleChains${pageLink.toQuery()}`, 299 return this.http.get<PageData<RuleChain>>(`/api/edge/${edgeId}/ruleChains${pageLink.toQuery()}`,
300 - defaultHttpOptionsFromConfig(config) ) 300 + defaultHttpOptionsFromConfig(config));
301 } 301 }
302 302
303 public assignRuleChainToEdge(edgeId: string, ruleChainId: string, config?: RequestConfig): Observable<RuleChain> { 303 public assignRuleChainToEdge(edgeId: string, ruleChainId: string, config?: RequestConfig): Observable<RuleChain> {
@@ -26,7 +26,6 @@ import { NULL_UUID } from '@shared/models/id/has-uuid'; @@ -26,7 +26,6 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
26 import { ActionNotificationShow } from '@core/notification/notification.actions'; 26 import { ActionNotificationShow } from '@core/notification/notification.actions';
27 import { generateSecret, guid } from '@core/utils'; 27 import { generateSecret, guid } from '@core/utils';
28 import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; 28 import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
29 -import { WINDOW } from '@core/services/window.service';  
30 29
31 @Component({ 30 @Component({
32 selector: 'tb-edge', 31 selector: 'tb-edge',
@@ -43,15 +42,14 @@ export class EdgeComponent extends EntityComponent<EdgeInfo> { @@ -43,15 +42,14 @@ export class EdgeComponent extends EntityComponent<EdgeInfo> {
43 protected translate: TranslateService, 42 protected translate: TranslateService,
44 @Inject('entity') protected entityValue: EdgeInfo, 43 @Inject('entity') protected entityValue: EdgeInfo,
45 @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<EdgeInfo>, 44 @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<EdgeInfo>,
46 - public fb: FormBuilder,  
47 - @Inject(WINDOW) protected window: Window) { 45 + public fb: FormBuilder) {
48 super(store, fb, entityValue, entitiesTableConfigValue); 46 super(store, fb, entityValue, entitiesTableConfigValue);
49 } 47 }
50 48
51 ngOnInit() { 49 ngOnInit() {
52 this.edgeScope = this.entitiesTableConfig.componentsData.edgeScope; 50 this.edgeScope = this.entitiesTableConfig.componentsData.edgeScope;
53 this.entityForm.patchValue({ 51 this.entityForm.patchValue({
54 - cloudEndpoint: this.window.location.origin 52 + cloudEndpoint: window.location.origin
55 }); 53 });
56 super.ngOnInit(); 54 super.ngOnInit();
57 } 55 }
@@ -94,7 +92,7 @@ export class EdgeComponent extends EntityComponent<EdgeInfo> { @@ -94,7 +92,7 @@ export class EdgeComponent extends EntityComponent<EdgeInfo> {
94 name: entity.name, 92 name: entity.name,
95 type: entity.type, 93 type: entity.type,
96 label: entity.label, 94 label: entity.label,
97 - cloudEndpoint: entity.cloudEndpoint ? entity.cloudEndpoint : this.window.location.origin, 95 + cloudEndpoint: entity.cloudEndpoint ? entity.cloudEndpoint : window.location.origin,
98 edgeLicenseKey: entity.edgeLicenseKey, 96 edgeLicenseKey: entity.edgeLicenseKey,
99 routingKey: entity.routingKey, 97 routingKey: entity.routingKey,
100 secret: entity.secret, 98 secret: entity.secret,
@@ -49,6 +49,7 @@ import { isUndefined } from '@core/utils'; @@ -49,6 +49,7 @@ import { isUndefined } from '@core/utils';
49 import { PageLink } from '@shared/models/page/page-link'; 49 import { PageLink } from '@shared/models/page/page-link';
50 import { Edge } from '@shared/models/edge.models'; 50 import { Edge } from '@shared/models/edge.models';
51 import { mergeMap } from 'rxjs/operators'; 51 import { mergeMap } from 'rxjs/operators';
  52 +import { PageData } from '@shared/models/page/page-data';
52 53
53 @Injectable() 54 @Injectable()
54 export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<RuleChain>> { 55 export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<RuleChain>> {
@@ -83,25 +84,27 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -83,25 +84,27 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
83 } 84 }
84 85
85 resolve(route: ActivatedRouteSnapshot): EntityTableConfig<RuleChain> { 86 resolve(route: ActivatedRouteSnapshot): EntityTableConfig<RuleChain> {
86 - const routeParams = route.params; 87 + const edgeId = route.params?.edgeId;
  88 + const ruleChainScope = route.data?.ruleChainsType ? route.data?.ruleChainsType : 'tenant';
87 this.config.componentsData = { 89 this.config.componentsData = {
88 - ruleChainScope: route.data.ruleChainsType,  
89 - edgeId: routeParams.edgeId 90 + ruleChainScope,
  91 + edgeId
90 }; 92 };
91 - this.config.columns = this.configureEntityTableColumns(this.config.componentsData.ruleChainScope);  
92 - this.configureEntityFunctions(this.config.componentsData.ruleChainScope);  
93 - this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.ruleChainScope);  
94 - this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.ruleChainScope);  
95 - this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.ruleChainScope);  
96 - if (this.config.componentsData.ruleChainScope === 'tenant' || this.config.componentsData.ruleChainScope === 'edges') { 93 + this.config.columns = this.configureEntityTableColumns(ruleChainScope);
  94 + this.config.entitiesFetchFunction = this.configureEntityFunctions(ruleChainScope, edgeId);
  95 + this.config.groupActionDescriptors = this.configureGroupActions(ruleChainScope);
  96 + this.config.addActionDescriptors = this.configureAddActions(ruleChainScope);
  97 + this.config.cellActionDescriptors = this.configureCellActions(ruleChainScope);
  98 + if (ruleChainScope === 'tenant' || ruleChainScope === 'edges') {
97 this.config.entitySelectionEnabled = ruleChain => ruleChain && !ruleChain.root; 99 this.config.entitySelectionEnabled = ruleChain => ruleChain && !ruleChain.root;
98 this.config.deleteEnabled = (ruleChain) => ruleChain && !ruleChain.root; 100 this.config.deleteEnabled = (ruleChain) => ruleChain && !ruleChain.root;
99 this.config.entitiesDeleteEnabled = true; 101 this.config.entitiesDeleteEnabled = true;
100 - } else if (this.config.componentsData.ruleChainScope === 'edge') {  
101 - this.config.entitySelectionEnabled = ruleChain => this.config.componentsData.edge.rootRuleChainId.id != ruleChain.id.id;  
102 - this.edgeService.getEdge(this.config.componentsData.edgeId).subscribe(edge => { 102 + this.config.tableTitle = this.configureTableTitle(ruleChainScope, null);
  103 + } else if (ruleChainScope === 'edge') {
  104 + this.config.entitySelectionEnabled = ruleChain => this.config.componentsData.edge.rootRuleChainId.id !== ruleChain.id.id;
  105 + this.edgeService.getEdge(edgeId).subscribe(edge => {
103 this.config.componentsData.edge = edge; 106 this.config.componentsData.edge = edge;
104 - this.config.tableTitle = edge.name + ': ' + this.translate.instant('edge.rulechains'); 107 + this.config.tableTitle = this.configureTableTitle(ruleChainScope, edge);
105 }); 108 });
106 this.config.entitiesDeleteEnabled = false; 109 this.config.entitiesDeleteEnabled = false;
107 } 110 }
@@ -110,24 +113,23 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -110,24 +113,23 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
110 113
111 configureEntityTableColumns(ruleChainScope: string): Array<EntityColumn<RuleChain>> { 114 configureEntityTableColumns(ruleChainScope: string): Array<EntityColumn<RuleChain>> {
112 const columns: Array<EntityColumn<RuleChain>> = []; 115 const columns: Array<EntityColumn<RuleChain>> = [];
  116 + columns.push(
  117 + new DateEntityTableColumn<RuleChain>('createdTime', 'common.created-time', this.datePipe, '150px'),
  118 + new EntityTableColumn<RuleChain>('name', 'rulechain.name', '100%')
  119 + );
113 if (ruleChainScope === 'tenant' || ruleChainScope === 'edge') { 120 if (ruleChainScope === 'tenant' || ruleChainScope === 'edge') {
114 columns.push( 121 columns.push(
115 - new DateEntityTableColumn<RuleChain>('createdTime', 'common.created-time', this.datePipe, '150px'),  
116 - new EntityTableColumn<RuleChain>('name', 'rulechain.name', '100%'),  
117 new EntityTableColumn<RuleChain>('root', 'rulechain.root', '60px', 122 new EntityTableColumn<RuleChain>('root', 'rulechain.root', '60px',
118 entity => { 123 entity => {
119 if (ruleChainScope === 'edge') { 124 if (ruleChainScope === 'edge') {
120 - return checkBoxCell((this.config.componentsData.edge.rootRuleChainId.id == entity.id.id)); 125 + return checkBoxCell((this.config.componentsData.edge.rootRuleChainId.id === entity.id.id));
121 } else { 126 } else {
122 return checkBoxCell(entity.root); 127 return checkBoxCell(entity.root);
123 } 128 }
124 }) 129 })
125 ); 130 );
126 - }  
127 - if (ruleChainScope === 'edges') { 131 + } else if (ruleChainScope === 'edges') {
128 columns.push( 132 columns.push(
129 - new DateEntityTableColumn<RuleChain>('createdTime', 'common.created-time', this.datePipe, '150px'),  
130 - new EntityTableColumn<RuleChain>('name', 'rulechain.name', '100%'),  
131 new EntityTableColumn<RuleChain>('root', 'rulechain.edge-template-root', '60px', 133 new EntityTableColumn<RuleChain>('root', 'rulechain.edge-template-root', '60px',
132 entity => { 134 entity => {
133 return checkBoxCell(entity.root); 135 return checkBoxCell(entity.root);
@@ -157,7 +159,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -157,7 +159,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
157 isEnabled: () => true, 159 isEnabled: () => true,
158 onAction: ($event) => this.importRuleChain($event) 160 onAction: ($event) => this.importRuleChain($event)
159 } 161 }
160 - ) 162 + );
161 } 163 }
162 if (ruleChainScope === 'edge') { 164 if (ruleChainScope === 'edge') {
163 actions.push( 165 actions.push(
@@ -167,20 +169,28 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -167,20 +169,28 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
167 isEnabled: () => true, 169 isEnabled: () => true,
168 onAction: ($event) => this.addRuleChainsToEdge($event) 170 onAction: ($event) => this.addRuleChainsToEdge($event)
169 } 171 }
170 - ) 172 + );
171 } 173 }
172 return actions; 174 return actions;
173 } 175 }
174 176
175 - configureEntityFunctions(ruleChainScope: string): void { 177 + configureEntityFunctions(ruleChainScope: string, edgeId: string): (pageLink) => Observable<PageData<RuleChain>> {
176 if (ruleChainScope === 'tenant') { 178 if (ruleChainScope === 'tenant') {
177 - this.config.tableTitle = this.translate.instant('rulechain.rulechains');  
178 - this.config.entitiesFetchFunction = pageLink => this.fetchRuleChains(pageLink); 179 + return pageLink => this.fetchRuleChains(pageLink);
179 } else if (ruleChainScope === 'edges') { 180 } else if (ruleChainScope === 'edges') {
180 - this.config.tableTitle = this.translate.instant('edge.rulechain-templates');  
181 - this.config.entitiesFetchFunction = pageLink => this.fetchEdgeRuleChains(pageLink); 181 + return pageLink => this.fetchEdgeRuleChains(pageLink);
182 } else if (ruleChainScope === 'edge') { 182 } else if (ruleChainScope === 'edge') {
183 - this.config.entitiesFetchFunction = pageLink => this.ruleChainService.getEdgeRuleChains(this.config.componentsData.edgeId, pageLink); 183 + return pageLink => this.ruleChainService.getEdgeRuleChains(edgeId, pageLink);
  184 + }
  185 + }
  186 +
  187 + configureTableTitle(ruleChainScope: string, edge: Edge): string {
  188 + if (ruleChainScope === 'tenant') {
  189 + return this.translate.instant('rulechain.rulechains');
  190 + } else if (ruleChainScope === 'edges') {
  191 + return this.translate.instant('edge.rulechain-templates');
  192 + } else if (ruleChainScope === 'edge') {
  193 + return this.config.tableTitle = edge.name + ': ' + this.translate.instant('edge.rulechains');
184 } 194 }
185 } 195 }
186 196
@@ -194,7 +204,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -194,7 +204,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
194 isEnabled: true, 204 isEnabled: true,
195 onAction: ($event, entities) => this.unassignRuleChainsFromEdge($event, entities) 205 onAction: ($event, entities) => this.unassignRuleChainsFromEdge($event, entities)
196 } 206 }
197 - ) 207 + );
198 } 208 }
199 return actions; 209 return actions;
200 } 210 }
@@ -215,7 +225,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -215,7 +225,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
215 isEnabled: () => true, 225 isEnabled: () => true,
216 onAction: ($event, entity) => this.exportRuleChain($event, entity) 226 onAction: ($event, entity) => this.exportRuleChain($event, entity)
217 } 227 }
218 - ) 228 + );
219 if (ruleChainScope === 'tenant') { 229 if (ruleChainScope === 'tenant') {
220 actions.push( 230 actions.push(
221 { 231 {
@@ -224,7 +234,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -224,7 +234,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
224 isEnabled: (entity) => this.isNonRootRuleChain(entity), 234 isEnabled: (entity) => this.isNonRootRuleChain(entity),
225 onAction: ($event, entity) => this.setRootRuleChain($event, entity) 235 onAction: ($event, entity) => this.setRootRuleChain($event, entity)
226 } 236 }
227 - ) 237 + );
228 } 238 }
229 if (ruleChainScope === 'edges') { 239 if (ruleChainScope === 'edges') {
230 actions.push( 240 actions.push(
@@ -246,7 +256,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -246,7 +256,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
246 isEnabled: (entity) => this.isAutoAssignToEdgeRuleChain(entity), 256 isEnabled: (entity) => this.isAutoAssignToEdgeRuleChain(entity),
247 onAction: ($event, entity) => this.unsetAutoAssignToEdgeRuleChain($event, entity) 257 onAction: ($event, entity) => this.unsetAutoAssignToEdgeRuleChain($event, entity)
248 } 258 }
249 - ) 259 + );
250 } 260 }
251 } 261 }
252 if (ruleChainScope === 'edge') { 262 if (ruleChainScope === 'edge') {
@@ -260,10 +270,10 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -260,10 +270,10 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
260 { 270 {
261 name: this.translate.instant('edge.unassign-from-edge'), 271 name: this.translate.instant('edge.unassign-from-edge'),
262 icon: 'assignment_return', 272 icon: 'assignment_return',
263 - isEnabled: (entity) => entity.id.id != this.config.componentsData.edge.rootRuleChainId.id, 273 + isEnabled: (entity) => entity.id.id !== this.config.componentsData.edge.rootRuleChainId.id,
264 onAction: ($event, entity) => this.unassignFromEdge($event, entity) 274 onAction: ($event, entity) => this.unassignFromEdge($event, entity)
265 } 275 }
266 - ) 276 + );
267 } 277 }
268 return actions; 278 return actions;
269 } 279 }
@@ -298,9 +308,9 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -298,9 +308,9 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
298 308
299 saveRuleChain(ruleChain: RuleChain) { 309 saveRuleChain(ruleChain: RuleChain) {
300 if (isUndefined(ruleChain.type)) { 310 if (isUndefined(ruleChain.type)) {
301 - if (this.config.componentsData.ruleChainScope == 'tenant') { 311 + if (this.config.componentsData.ruleChainScope === 'tenant') {
302 ruleChain.type = RuleChainType.CORE; 312 ruleChain.type = RuleChainType.CORE;
303 - } else if (this.config.componentsData.ruleChainScope == 'edges') { 313 + } else if (this.config.componentsData.ruleChainScope === 'edges') {
304 ruleChain.type = RuleChainType.EDGE; 314 ruleChain.type = RuleChainType.EDGE;
305 } else { 315 } else {
306 // safe fallback to default core type 316 // safe fallback to default core type
@@ -335,13 +345,13 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -335,13 +345,13 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
335 this.config.componentsData.edge = edge; 345 this.config.componentsData.edge = edge;
336 this.config.table.updateData(); 346 this.config.table.updateData();
337 } 347 }
338 - ) 348 + );
339 } else { 349 } else {
340 this.ruleChainService.setRootRuleChain(ruleChain.id.id).subscribe( 350 this.ruleChainService.setRootRuleChain(ruleChain.id.id).subscribe(
341 () => { 351 () => {
342 this.config.table.updateData(); 352 this.config.table.updateData();
343 } 353 }
344 - ) 354 + );
345 } 355 }
346 } 356 }
347 } 357 }
@@ -415,14 +425,14 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -415,14 +425,14 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
415 this.edgeService.findMissingToRelatedRuleChains(this.config.componentsData.edgeId).subscribe( 425 this.edgeService.findMissingToRelatedRuleChains(this.config.componentsData.edgeId).subscribe(
416 (missingRuleChains) => { 426 (missingRuleChains) => {
417 if (missingRuleChains && Object.keys(missingRuleChains).length > 0) { 427 if (missingRuleChains && Object.keys(missingRuleChains).length > 0) {
418 - let formattedMissingRuleChains: Array<string> = new Array<string>(); 428 + const formattedMissingRuleChains: Array<string> = new Array<string>();
419 for (const missingRuleChain of Object.keys(missingRuleChains)) { 429 for (const missingRuleChain of Object.keys(missingRuleChains)) {
420 const arrayOfMissingRuleChains = missingRuleChains[missingRuleChain]; 430 const arrayOfMissingRuleChains = missingRuleChains[missingRuleChain];
421 - const tmp = "- '" + missingRuleChain + "': '" + arrayOfMissingRuleChains.join("', ") + "'"; 431 + const tmp = '- \'' + missingRuleChain + '\': \'' + arrayOfMissingRuleChains.join('\', ') + '\'';
422 formattedMissingRuleChains.push(tmp); 432 formattedMissingRuleChains.push(tmp);
423 } 433 }
424 const message = this.translate.instant('edge.missing-related-rule-chains-text', 434 const message = this.translate.instant('edge.missing-related-rule-chains-text',
425 - {missingRuleChains: formattedMissingRuleChains.join("<br>")}); 435 + {missingRuleChains: formattedMissingRuleChains.join('<br>')});
426 this.dialogService.alert(this.translate.instant('edge.missing-related-rule-chains-title'), 436 this.dialogService.alert(this.translate.instant('edge.missing-related-rule-chains-title'),
427 message, this.translate.instant('action.close'), true).subscribe( 437 message, this.translate.instant('action.close'), true).subscribe(
428 () => { 438 () => {
@@ -433,10 +443,10 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -433,10 +443,10 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
433 this.config.table.updateData(); 443 this.config.table.updateData();
434 } 444 }
435 } 445 }
436 - ) 446 + );
437 } 447 }
438 } 448 }
439 - ) 449 + );
440 } 450 }
441 451
442 unassignFromEdge($event: Event, ruleChain: RuleChain) { 452 unassignFromEdge($event: Event, ruleChain: RuleChain) {
@@ -505,7 +515,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -505,7 +515,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
505 () => { 515 () => {
506 this.config.table.updateData(); 516 this.config.table.updateData();
507 } 517 }
508 - ) 518 + );
509 } 519 }
510 } 520 }
511 ); 521 );
@@ -527,7 +537,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -527,7 +537,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
527 () => { 537 () => {
528 this.config.table.updateData(); 538 this.config.table.updateData();
529 } 539 }
530 - ) 540 + );
531 } 541 }
532 } 542 }
533 ); 543 );
@@ -536,7 +546,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig< @@ -536,7 +546,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
536 isNonRootRuleChain(ruleChain: RuleChain) { 546 isNonRootRuleChain(ruleChain: RuleChain) {
537 if (this.config.componentsData.ruleChainScope === 'edge') { 547 if (this.config.componentsData.ruleChainScope === 'edge') {
538 return this.config.componentsData.edge.rootRuleChainId && 548 return this.config.componentsData.edge.rootRuleChainId &&
539 - this.config.componentsData.edge.rootRuleChainId.id != ruleChain.id.id; 549 + this.config.componentsData.edge.rootRuleChainId.id !== ruleChain.id.id;
540 } 550 }
541 return !ruleChain.root; 551 return !ruleChain.root;
542 } 552 }
1 -/**  
2 - * Copyright © 2016-2020 The Thingsboard Authors  
3 - *  
4 - * Licensed under the Apache License, Version 2.0 (the "License");  
5 - * you may not use this file except in compliance with the License.  
6 - * You may obtain a copy of the License at  
7 - *  
8 - * http://www.apache.org/licenses/LICENSE-2.0  
9 - *  
10 - * Unless required by applicable law or agreed to in writing, software  
11 - * distributed under the License is distributed on an "AS IS" BASIS,  
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
13 - * See the License for the specific language governing permissions and  
14 - * limitations under the License.  
15 - */  
16 -md-list.tb-edge-downlinks-table {  
17 - padding: 0;  
18 -  
19 - md-list-item {  
20 - padding: 0;  
21 - }  
22 -  
23 - .tb-row {  
24 - height: 48px;  
25 - padding: 0;  
26 - overflow: hidden;  
27 -  
28 - .tb-cell {  
29 - text-overflow: ellipsis;  
30 -  
31 - &.tb-scroll {  
32 - overflow-x: auto;  
33 - overflow-y: hidden;  
34 - white-space: nowrap;  
35 - }  
36 -  
37 - &.tb-nowrap {  
38 - white-space: nowrap;  
39 - }  
40 - }  
41 - }  
42 -  
43 - .tb-row:hover {  
44 - background-color: #eee;  
45 - }  
46 -  
47 - .tb-header:hover {  
48 - background: none;  
49 - }  
50 -  
51 - .tb-header {  
52 - .tb-cell {  
53 - font-size: 12px;  
54 - font-weight: 700;  
55 - color: rgba(0, 0, 0, .54);  
56 - white-space: nowrap;  
57 - background: none;  
58 - }  
59 - }  
60 -  
61 - .tb-cell {  
62 - &:first-child {  
63 - padding-left: 14px;  
64 - }  
65 -  
66 - &:last-child {  
67 - padding-right: 14px;  
68 - }  
69 - padding: 0 6px;  
70 - margin: auto 0;  
71 - overflow: hidden;  
72 - font-size: 13px;  
73 - color: rgba(0, 0, 0, .87);  
74 - text-align: left;  
75 - vertical-align: middle;  
76 -  
77 - .md-button {  
78 - padding: 0;  
79 - margin: 0;  
80 - }  
81 - }  
82 -  
83 - .tb-cell.tb-number {  
84 - text-align: right;  
85 - }  
86 -}  
87 -  
88 -#tb-edge-downlinks-content {  
89 - width: 100%;  
90 - min-width: 400px;  
91 - height: 100%;  
92 - min-height: 50px;  
93 -}  
1 -/*  
2 - * Copyright © 2016-2020 The Thingsboard Authors  
3 - *  
4 - * Licensed under the Apache License, Version 2.0 (the "License");  
5 - * you may not use this file except in compliance with the License.  
6 - * You may obtain a copy of the License at  
7 - *  
8 - * http://www.apache.org/licenses/LICENSE-2.0  
9 - *  
10 - * Unless required by applicable law or agreed to in writing, software  
11 - * distributed under the License is distributed on an "AS IS" BASIS,  
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
13 - * See the License for the specific language governing permissions and  
14 - * limitations under the License.  
15 - */  
16 -import $ from 'jquery';  
17 -import 'brace/ext/language_tools';  
18 -import 'brace/ext/searchbox';  
19 -import 'brace/mode/java';  
20 -import 'brace/theme/github';  
21 -import beautify from 'js-beautify';  
22 -  
23 -/* eslint-disable angular/angularelement */  
24 -  
25 -const js_beautify = beautify.js;  
26 -  
27 -/*@ngInject*/  
28 -export default function EdgeDownlinksContentDialogController($mdDialog, types, content, contentType, title, showingCallback) {  
29 -  
30 - var vm = this;  
31 -  
32 - showingCallback.onShowing = function(scope, element) {  
33 - updateEditorSize(element);  
34 - }  
35 -  
36 - vm.content = content;  
37 - vm.title = title;  
38 -  
39 - var mode;  
40 - if (contentType) {  
41 - mode = types.contentType[contentType].code;  
42 - if (contentType == types.contentType.JSON.value && vm.content) {  
43 - vm.content = js_beautify(vm.content, {indent_size: 4});  
44 - }  
45 - } else {  
46 - mode = 'java';  
47 - }  
48 -  
49 - vm.contentOptions = {  
50 - useWrapMode: false,  
51 - mode: mode,  
52 - showGutter: false,  
53 - showPrintMargin: false,  
54 - theme: 'github',  
55 - advanced: {  
56 - enableSnippets: false,  
57 - enableBasicAutocompletion: false,  
58 - enableLiveAutocompletion: false  
59 - },  
60 - onLoad: function (_ace) {  
61 - vm.editor = _ace;  
62 - }  
63 - };  
64 -  
65 - function updateEditorSize(element) {  
66 - var newHeight = 400;  
67 - var newWidth = 600;  
68 - if (vm.content && vm.content.length > 0) {  
69 - var lines = vm.content.split('\n');  
70 - newHeight = 16 * lines.length + 16;  
71 - var maxLineLength = 0;  
72 - for (var i = 0; i < lines.length; i++) {  
73 - var line = lines[i].replace(/\t/g, ' ').replace(/\n/g, '');  
74 - var lineLength = line.length;  
75 - maxLineLength = Math.max(maxLineLength, lineLength);  
76 - }  
77 - newWidth = 8 * maxLineLength + 16;  
78 - }  
79 - $('#tb-edge-downlinks-content', element).height(newHeight.toString() + "px")  
80 - .width(newWidth.toString() + "px");  
81 - vm.editor.resize();  
82 - }  
83 -  
84 - vm.close = close;  
85 -  
86 - function close () {  
87 - $mdDialog.hide();  
88 - }  
89 -  
90 -}  
91 -  
92 -/* eslint-enable angular/angularelement */  
1 -<!--  
2 -  
3 - Copyright © 2016-2020 The Thingsboard Authors  
4 -  
5 - Licensed under the Apache License, Version 2.0 (the "License");  
6 - you may not use this file except in compliance with the License.  
7 - You may obtain a copy of the License at  
8 -  
9 - http://www.apache.org/licenses/LICENSE-2.0  
10 -  
11 - Unless required by applicable law or agreed to in writing, software  
12 - distributed under the License is distributed on an "AS IS" BASIS,  
13 - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
14 - See the License for the specific language governing permissions and  
15 - limitations under the License.  
16 -  
17 --->  
18 -<md-dialog aria-label="{{ vm.title | translate }}">  
19 - <md-toolbar>  
20 - <div class="md-toolbar-tools">  
21 - <h2 translate>{{ vm.title }}</h2>  
22 - <span flex></span>  
23 - <md-button class="md-icon-button" ng-click="vm.close()">  
24 - <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>  
25 - </md-button>  
26 - </div>  
27 - </md-toolbar>  
28 - <md-dialog-content>  
29 - <div class="md-dialog-content">  
30 - <div flex id="tb-edge-downlinks-content" readonly  
31 - ui-ace="vm.contentOptions"  
32 - ng-model="vm.content">  
33 - </div>  
34 - </div>  
35 - </md-dialog-content>  
36 - <md-dialog-actions layout="row">  
37 - <span flex></span>  
38 - <md-button ng-disabled="$root.loading" ng-click="vm.close()" style="margin-right:20px;">{{ 'action.close' |  
39 - translate }}  
40 - </md-button>  
41 - </md-dialog-actions>  
42 -</md-dialog>  
1 -/*  
2 - * Copyright © 2016-2020 The Thingsboard Authors  
3 - *  
4 - * Licensed under the Apache License, Version 2.0 (the "License");  
5 - * you may not use this file except in compliance with the License.  
6 - * You may obtain a copy of the License at  
7 - *  
8 - * http://www.apache.org/licenses/LICENSE-2.0  
9 - *  
10 - * Unless required by applicable law or agreed to in writing, software  
11 - * distributed under the License is distributed on an "AS IS" BASIS,  
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
13 - * See the License for the specific language governing permissions and  
14 - * limitations under the License.  
15 - */  
16 -/* eslint-disable import/no-unresolved, import/default */  
17 -  
18 -import edgeDownlinksHeaderTemplate from './edge-downlinks-header.tpl.html'  
19 -  
20 -/* eslint-enable import/no-unresolved, import/default */  
21 -  
22 -/*@ngInject*/  
23 -export default function EdgeDownlinksHeaderDirective($compile, $templateCache) {  
24 -  
25 - var linker = function (scope, element) {  
26 -  
27 - var template = edgeDownlinksHeaderTemplate;  
28 -  
29 - element.html($templateCache.get(template));  
30 - $compile(element.contents())(scope);  
31 - }  
32 -  
33 - return {  
34 - restrict: "A",  
35 - replace: false,  
36 - link: linker,  
37 - scope: false  
38 - };  
39 -}  
1 -/*  
2 - * Copyright © 2016-2020 The Thingsboard Authors  
3 - *  
4 - * Licensed under the Apache License, Version 2.0 (the "License");  
5 - * you may not use this file except in compliance with the License.  
6 - * You may obtain a copy of the License at  
7 - *  
8 - * http://www.apache.org/licenses/LICENSE-2.0  
9 - *  
10 - * Unless required by applicable law or agreed to in writing, software  
11 - * distributed under the License is distributed on an "AS IS" BASIS,  
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
13 - * See the License for the specific language governing permissions and  
14 - * limitations under the License.  
15 - */  
16 -/* eslint-disable import/no-unresolved, import/default */  
17 -  
18 -import edgeDownlinksContentTemplate from './edge-downlinks-content-dialog.tpl.html';  
19 -import edgeDownlinlsRowTemplate from './edge-downlinks-row.tpl.html';  
20 -  
21 -/* eslint-enable import/no-unresolved, import/default */  
22 -  
23 -/*@ngInject*/  
24 -export default function EdgeDownlinksRowDirective($compile, $templateCache, $mdDialog, $document, $translate,  
25 - types, utils, toast, entityService, ruleChainService) {  
26 -  
27 - var linker = function (scope, element, attrs) {  
28 -  
29 - var template = edgeDownlinlsRowTemplate;  
30 -  
31 - element.html($templateCache.get(template));  
32 - $compile(element.contents())(scope);  
33 -  
34 - scope.types = types;  
35 - scope.downlink = attrs.downlink;  
36 -  
37 - scope.showEdgeEntityContent = function($event, title, contentType) {  
38 - var onShowingCallback = {  
39 - onShowing: function(){}  
40 - }  
41 - if (!contentType) {  
42 - contentType = null;  
43 - }  
44 - var content = '';  
45 - switch(scope.downlink.type) {  
46 - case types.edgeEventType.relation:  
47 - content = angular.toJson(scope.downlink.body);  
48 - showDialog();  
49 - break;  
50 - case types.edgeEventType.ruleChainMetaData:  
51 - content = ruleChainService.getRuleChainMetaData(scope.downlink.entityId, {ignoreErrors: true}).then(  
52 - function success(info) {  
53 - showDialog();  
54 - return angular.toJson(info);  
55 - }, function fail() {  
56 - showError();  
57 - });  
58 - break;  
59 - default:  
60 - content = entityService.getEntity(scope.downlink.type, scope.downlink.entityId, {ignoreErrors: true}).then(  
61 - function success(info) {  
62 - showDialog();  
63 - return angular.toJson(info);  
64 - }, function fail() {  
65 - showError();  
66 - });  
67 - break;  
68 - }  
69 - function showDialog() {  
70 - $mdDialog.show({  
71 - controller: 'EdgeDownlinksContentDialogController',  
72 - controllerAs: 'vm',  
73 - templateUrl: edgeDownlinksContentTemplate,  
74 - locals: {content: content, title: title, contentType: contentType, showingCallback: onShowingCallback},  
75 - parent: angular.element($document[0].body),  
76 - fullscreen: true,  
77 - targetEvent: $event,  
78 - multiple: true,  
79 - onShowing: function(scope, element) {  
80 - onShowingCallback.onShowing(scope, element);  
81 - }  
82 - });  
83 - }  
84 - function showError() {  
85 - toast.showError($translate.instant('edge.load-entity-error'));  
86 - }  
87 - }  
88 -  
89 - scope.checkEdgeDownlinksType = function (type) {  
90 - return !(type === types.edgeEventType.widgetType ||  
91 - type === types.edgeEventType.adminSettings ||  
92 - type === types.edgeEventType.widgetsBundle );  
93 - }  
94 -  
95 - scope.checkTooltip = function($event) {  
96 - var el = $event.target;  
97 - var $el = angular.element(el);  
98 - if(el.offsetWidth < el.scrollWidth && !$el.attr('title')){  
99 - $el.attr('title', $el.text());  
100 - }  
101 - }  
102 -  
103 - $compile(element.contents())(scope);  
104 -  
105 - scope.updateStatus = function(downlinkCreatedTime) {  
106 - var status;  
107 - if (downlinkCreatedTime < scope.queueStartTs) {  
108 - status = $translate.instant(types.edgeEventStatus.DEPLOYED.name);  
109 - scope.statusColor = types.edgeEventStatus.DEPLOYED.color;  
110 - } else {  
111 - status = $translate.instant(types.edgeEventStatus.PENDING.name);  
112 - scope.statusColor = types.edgeEventStatus.PENDING.color;  
113 - }  
114 - return status;  
115 - }  
116 - }  
117 -  
118 - return {  
119 - restrict: "A",  
120 - replace: false,  
121 - link: linker,  
122 - scope: false  
123 - };  
124 -}  
1 -/*  
2 - * Copyright © 2016-2020 The Thingsboard Authors  
3 - *  
4 - * Licensed under the Apache License, Version 2.0 (the "License");  
5 - * you may not use this file except in compliance with the License.  
6 - * You may obtain a copy of the License at  
7 - *  
8 - * http://www.apache.org/licenses/LICENSE-2.0  
9 - *  
10 - * Unless required by applicable law or agreed to in writing, software  
11 - * distributed under the License is distributed on an "AS IS" BASIS,  
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
13 - * See the License for the specific language governing permissions and  
14 - * limitations under the License.  
15 - */  
16 -import './downlink.scss';  
17 -  
18 -/* eslint-disable import/no-unresolved, import/default */  
19 -  
20 -import edgeDownlinksTableTemplate from './edge-downlinks-table.tpl.html';  
21 -  
22 -/* eslint-enable import/no-unresolved, import/default */  
23 -  
24 -/*@ngInject*/  
25 -export default function EdgeDownlinksDirective($compile, $templateCache, $rootScope, $translate, types, edgeService, attributeService) {  
26 -  
27 - var linker = function (scope, element) {  
28 -  
29 - var template = $templateCache.get(edgeDownlinksTableTemplate);  
30 - element.html(template);  
31 -  
32 - var pageSize = 20;  
33 - var startTime = 0;  
34 - var endTime = 0;  
35 -  
36 - scope.timewindow = {  
37 - history: {  
38 - timewindowMs: 24 * 60 * 60 * 1000 // 1 day  
39 - }  
40 - }  
41 -  
42 - scope.topIndex = 0;  
43 -  
44 - scope.theDownlinks = {  
45 - getItemAtIndex: function (index) {  
46 - if (index > scope.downlinks.data.length) {  
47 - scope.theDownlinks.fetchMoreItems_(index);  
48 - return null;  
49 - }  
50 - var item = scope.downlinks.data[index];  
51 - if (item) {  
52 - item.indexNumber = index + 1;  
53 - }  
54 - return item;  
55 - },  
56 -  
57 - getLength: function () {  
58 - if (scope.downlinks.hasNext) {  
59 - return scope.downlinks.data.length + scope.downlinks.nextPageLink.limit;  
60 - } else {  
61 - return scope.downlinks.data.length;  
62 - }  
63 - },  
64 -  
65 - fetchMoreItems_: function () {  
66 - if (scope.downlinks.hasNext && !scope.downlinks.pending) {  
67 - if (scope.entityType && scope.entityId && scope.tenantId) {  
68 - scope.loadEdgeInfo();  
69 - scope.downlinks.pending = true;  
70 - edgeService.getEdgeEvents(scope.entityId, scope.downlinks.nextPageLink).then(  
71 - function success(downlinks) {  
72 - scope.downlinks.data = scope.downlinks.data.concat(prepareEdgeDownlinksData(downlinks.data));  
73 - scope.downlinks.nextPageLink = downlinks.nextPageLink;  
74 - scope.downlinks.hasNext = downlinks.hasNext;  
75 - if (scope.downlinks.hasNext) {  
76 - scope.downlinks.nextPageLink.limit = pageSize;  
77 - }  
78 - scope.downlinks.pending = false;  
79 - },  
80 - function fail() {  
81 - scope.downlinks.hasNext = false;  
82 - scope.downlinks.pending = false;  
83 - });  
84 - } else {  
85 - scope.downlinks.hasNext = false;  
86 - }  
87 - }  
88 - }  
89 - };  
90 -  
91 - scope.$watch("entityId", function(newVal, prevVal) {  
92 - if (newVal && !angular.equals(newVal, prevVal)) {  
93 - scope.resetFilter();  
94 - scope.reload();  
95 - }  
96 - });  
97 -  
98 - scope.$watch("timewindow", function(newVal, prevVal) {  
99 - if (newVal && !angular.equals(newVal, prevVal)) {  
100 - scope.reload();  
101 - }  
102 - }, true);  
103 -  
104 - scope.resetFilter = function() {  
105 - scope.timewindow = {  
106 - history: {  
107 - timewindowMs: 24 * 60 * 60 * 1000 // 1 day  
108 - }  
109 - };  
110 - }  
111 -  
112 - scope.updateTimeWindowRange = function() {  
113 - if (scope.timewindow.history.timewindowMs) {  
114 - var currentTime = (new Date).getTime();  
115 - startTime = currentTime - scope.timewindow.history.timewindowMs;  
116 - endTime = currentTime;  
117 - } else {  
118 - startTime = scope.timewindow.history.fixedTimewindow.startTimeMs;  
119 - endTime = scope.timewindow.history.fixedTimewindow.endTimeMs;  
120 - }  
121 - }  
122 -  
123 - scope.reload = function() {  
124 - scope.topIndex = 0;  
125 - scope.selected = [];  
126 - scope.updateTimeWindowRange();  
127 - scope.downlinks = {  
128 - data: [],  
129 - nextPageLink: {  
130 - limit: pageSize,  
131 - startTime: startTime,  
132 - endTime: endTime  
133 - },  
134 - hasNext: true,  
135 - pending: false  
136 - };  
137 - scope.theDownlinks.getItemAtIndex(pageSize);  
138 - }  
139 -  
140 - scope.noData = function() {  
141 - return scope.downlinks.data.length == 0 && !scope.downlinks.hasNext;  
142 - }  
143 -  
144 - scope.hasData = function() {  
145 - return scope.downlinks.data.length > 0;  
146 - }  
147 -  
148 - scope.loading = function() {  
149 - return $rootScope.loading;  
150 - }  
151 -  
152 - scope.hasScroll = function() {  
153 - var repeatContainer = scope.repeatContainer[0];  
154 - if (repeatContainer) {  
155 - var scrollElement = repeatContainer.children[0];  
156 - if (scrollElement) {  
157 - return scrollElement.scrollHeight > scrollElement.clientHeight;  
158 - }  
159 - }  
160 - return false;  
161 - }  
162 -  
163 - scope.subscriptionId = null;  
164 -  
165 - scope.loadEdgeInfo = function() {  
166 - attributeService.getEntityAttributesValues(  
167 - scope.entityType,  
168 - scope.entityId,  
169 - types.attributesScope.server.value,  
170 - types.edgeAttributeKeys.queueStartTs,  
171 - null).then(  
172 - function success(attributes) {  
173 - attributes.length > 0 ? scope.onEdgeAttributesUpdate(attributes) : scope.queueStartTs = 0;  
174 - });  
175 - scope.checkSubscription();  
176 - }  
177 -  
178 - scope.onEdgeAttributesUpdate = function(attributes) {  
179 - let edgeAttributes = attributes.reduce(function (map, attribute) {  
180 - map[attribute.key] = attribute;  
181 - return map;  
182 - }, {});  
183 - if (edgeAttributes.queueStartTs) {  
184 - scope.queueStartTs = edgeAttributes.queueStartTs.lastUpdateTs;  
185 - }  
186 - }  
187 -  
188 - scope.checkSubscription = function() {  
189 - var newSubscriptionId = null;  
190 - if (scope.entityId && scope.entityType && types.attributesScope.server.value) {  
191 - newSubscriptionId =  
192 - attributeService.subscribeForEntityAttributes(scope.entityType, scope.entityId, types.attributesScope.server.value);  
193 - }  
194 - if (scope.subscriptionId && scope.subscriptionId != newSubscriptionId) {  
195 - attributeService.unsubscribeForEntityAttributes(scope.subscriptionId);  
196 - }  
197 - scope.subscriptionId = newSubscriptionId;  
198 - }  
199 -  
200 - scope.$on('$destroy', function () {  
201 - if (scope.subscriptionId) {  
202 - attributeService.unsubscribeForEntityAttributes(scope.subscriptionId);  
203 - }  
204 - });  
205 -  
206 - scope.reload();  
207 -  
208 - $compile(element.contents())(scope);  
209 - }  
210 - function prepareEdgeDownlinksData(data) {  
211 -  
212 - data.forEach(  
213 - edgeDownlink => {  
214 - edgeDownlink.edgeEventActionText = $translate.instant(types.edgeEventActionTypeTranslations[edgeDownlink.action].name);  
215 - edgeDownlink.edgeEventTypeText = $translate.instant(types.edgeEventTypeTranslations[edgeDownlink.type].name);  
216 - }  
217 - );  
218 - return data;  
219 - }  
220 -  
221 - return {  
222 - restrict: "E",  
223 - link: linker,  
224 - scope: {  
225 - entityType: '=',  
226 - entityId: '=',  
227 - tenantId: '='  
228 - }  
229 - };  
230 -}  
1 -<!--  
2 -  
3 - Copyright © 2016-2020 The Thingsboard Authors  
4 -  
5 - Licensed under the Apache License, Version 2.0 (the "License");  
6 - you may not use this file except in compliance with the License.  
7 - You may obtain a copy of the License at  
8 -  
9 - http://www.apache.org/licenses/LICENSE-2.0  
10 -  
11 - Unless required by applicable law or agreed to in writing, software  
12 - distributed under the License is distributed on an "AS IS" BASIS,  
13 - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
14 - See the License for the specific language governing permissions and  
15 - limitations under the License.  
16 -  
17 --->  
18 -<md-content flex class="md-padding tb-absolute-fill" layout="column">  
19 - <section layout="row">  
20 - <tb-timewindow flex ng-model="timewindow" history-only as-button="true"></tb-timewindow>  
21 - <md-button ng-disabled="$root.loading"  
22 - class="md-icon-button" ng-click="reload()">  
23 - <md-icon>refresh</md-icon>  
24 - <md-tooltip md-direction="top">  
25 - {{ 'action.refresh' | translate }}  
26 - </md-tooltip>  
27 - </md-button>  
28 - </section>  
29 - <md-list flex layout="column" class="md-whiteframe-z1 tb-edge-downlinks-table">  
30 - <md-list class="tb-row tb-header" layout="row" layout-align="start center" tb-edge-downlinks-header>  
31 - </md-list>  
32 - <md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading"  
33 - ng-show="$root.loading"></md-progress-linear>  
34 - <md-divider></md-divider>  
35 - <span translate layout-align="center center"  
36 - style="margin-top: 25px;"  
37 - class="tb-prompt" ng-show="noData()">edge.no-downlinks-prompt</span>  
38 - <md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer">  
39 - <md-list-item md-virtual-repeat="downlink in theDownlinks" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}">  
40 - <md-list class="tb-row" flex layout="row" layout-align="start center" tb-edge-downlinks-row downlink="{{downlink}}">  
41 - </md-list>  
42 - <md-divider flex></md-divider>  
43 - </md-list-item>  
44 - </md-virtual-repeat-container>  
45 - </md-list>  
46 -</md-content>