9 changed files with 685 additions and 1 deletions
@ -0,0 +1,93 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
md-list.tb-edge-downlinks-table { |
|||
padding: 0; |
|||
|
|||
md-list-item { |
|||
padding: 0; |
|||
} |
|||
|
|||
.tb-row { |
|||
height: 48px; |
|||
padding: 0; |
|||
overflow: hidden; |
|||
|
|||
.tb-cell { |
|||
text-overflow: ellipsis; |
|||
|
|||
&.tb-scroll { |
|||
overflow-x: auto; |
|||
overflow-y: hidden; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
&.tb-nowrap { |
|||
white-space: nowrap; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.tb-row:hover { |
|||
background-color: #eee; |
|||
} |
|||
|
|||
.tb-header:hover { |
|||
background: none; |
|||
} |
|||
|
|||
.tb-header { |
|||
.tb-cell { |
|||
font-size: 12px; |
|||
font-weight: 700; |
|||
color: rgba(0, 0, 0, .54); |
|||
white-space: nowrap; |
|||
background: none; |
|||
} |
|||
} |
|||
|
|||
.tb-cell { |
|||
&:first-child { |
|||
padding-left: 14px; |
|||
} |
|||
|
|||
&:last-child { |
|||
padding-right: 14px; |
|||
} |
|||
padding: 0 6px; |
|||
margin: auto 0; |
|||
overflow: hidden; |
|||
font-size: 13px; |
|||
color: rgba(0, 0, 0, .87); |
|||
text-align: left; |
|||
vertical-align: middle; |
|||
|
|||
.md-button { |
|||
padding: 0; |
|||
margin: 0; |
|||
} |
|||
} |
|||
|
|||
.tb-cell.tb-number { |
|||
text-align: right; |
|||
} |
|||
} |
|||
|
|||
#tb-edge-downlinks-content { |
|||
width: 100%; |
|||
min-width: 400px; |
|||
height: 100%; |
|||
min-height: 50px; |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
/* |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import $ from 'jquery'; |
|||
import 'brace/ext/language_tools'; |
|||
import 'brace/ext/searchbox'; |
|||
import 'brace/mode/java'; |
|||
import 'brace/theme/github'; |
|||
import beautify from 'js-beautify'; |
|||
|
|||
/* eslint-disable angular/angularelement */ |
|||
|
|||
const js_beautify = beautify.js; |
|||
|
|||
/*@ngInject*/ |
|||
export default function EdgeDownlinksContentDialogController($mdDialog, types, content, contentType, title, showingCallback) { |
|||
|
|||
var vm = this; |
|||
|
|||
showingCallback.onShowing = function(scope, element) { |
|||
updateEditorSize(element); |
|||
} |
|||
|
|||
vm.content = content; |
|||
vm.title = title; |
|||
|
|||
var mode; |
|||
if (contentType) { |
|||
mode = types.contentType[contentType].code; |
|||
if (contentType == types.contentType.JSON.value && vm.content) { |
|||
vm.content = js_beautify(vm.content, {indent_size: 4}); |
|||
} |
|||
} else { |
|||
mode = 'java'; |
|||
} |
|||
|
|||
vm.contentOptions = { |
|||
useWrapMode: false, |
|||
mode: mode, |
|||
showGutter: false, |
|||
showPrintMargin: false, |
|||
theme: 'github', |
|||
advanced: { |
|||
enableSnippets: false, |
|||
enableBasicAutocompletion: false, |
|||
enableLiveAutocompletion: false |
|||
}, |
|||
onLoad: function (_ace) { |
|||
vm.editor = _ace; |
|||
} |
|||
}; |
|||
|
|||
function updateEditorSize(element) { |
|||
var newHeight = 400; |
|||
var newWidth = 600; |
|||
if (vm.content && vm.content.length > 0) { |
|||
var lines = vm.content.split('\n'); |
|||
newHeight = 16 * lines.length + 16; |
|||
var maxLineLength = 0; |
|||
for (var i = 0; i < lines.length; i++) { |
|||
var line = lines[i].replace(/\t/g, ' ').replace(/\n/g, ''); |
|||
var lineLength = line.length; |
|||
maxLineLength = Math.max(maxLineLength, lineLength); |
|||
} |
|||
newWidth = 8 * maxLineLength + 16; |
|||
} |
|||
$('#tb-edge-downlinks-content', element).height(newHeight.toString() + "px") |
|||
.width(newWidth.toString() + "px"); |
|||
vm.editor.resize(); |
|||
} |
|||
|
|||
vm.close = close; |
|||
|
|||
function close () { |
|||
$mdDialog.hide(); |
|||
} |
|||
|
|||
} |
|||
|
|||
/* eslint-enable angular/angularelement */ |
|||
@ -0,0 +1,42 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2020 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<md-dialog aria-label="{{ vm.title | translate }}"> |
|||
<md-toolbar> |
|||
<div class="md-toolbar-tools"> |
|||
<h2 translate>{{ vm.title }}</h2> |
|||
<span flex></span> |
|||
<md-button class="md-icon-button" ng-click="vm.close()"> |
|||
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> |
|||
</md-button> |
|||
</div> |
|||
</md-toolbar> |
|||
<md-dialog-content> |
|||
<div class="md-dialog-content"> |
|||
<div flex id="tb-edge-downlinks-content" readonly |
|||
ui-ace="vm.contentOptions" |
|||
ng-model="vm.content"> |
|||
</div> |
|||
</div> |
|||
</md-dialog-content> |
|||
<md-dialog-actions layout="row"> |
|||
<span flex></span> |
|||
<md-button ng-disabled="$root.loading" ng-click="vm.close()" style="margin-right:20px;">{{ 'action.close' | |
|||
translate }} |
|||
</md-button> |
|||
</md-dialog-actions> |
|||
</md-dialog> |
|||
@ -0,0 +1,39 @@ |
|||
/* |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import edgeDownlinksHeaderTemplate from './edge-downlinks-header.tpl.html' |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
/*@ngInject*/ |
|||
export default function EdgeDownlinksHeaderDirective($compile, $templateCache) { |
|||
|
|||
var linker = function (scope, element) { |
|||
|
|||
var template = edgeDownlinksHeaderTemplate; |
|||
|
|||
element.html($templateCache.get(template)); |
|||
$compile(element.contents())(scope); |
|||
} |
|||
|
|||
return { |
|||
restrict: "A", |
|||
replace: false, |
|||
link: linker, |
|||
scope: false |
|||
}; |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
/* |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import edgeDownlinksContentTemplate from './edge-downlinks-content-dialog.tpl.html'; |
|||
import edgeDownlinlsRowTemplate from './edge-downlinks-row.tpl.html'; |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
/*@ngInject*/ |
|||
export default function EdgeDownlinksRowDirective($compile, $templateCache, $mdDialog, $document, $translate, |
|||
types, utils, toast, entityService, ruleChainService) { |
|||
|
|||
var linker = function (scope, element, attrs) { |
|||
|
|||
var template = edgeDownlinlsRowTemplate; |
|||
|
|||
element.html($templateCache.get(template)); |
|||
$compile(element.contents())(scope); |
|||
|
|||
scope.types = types; |
|||
scope.downlink = attrs.downlink; |
|||
|
|||
scope.showEdgeEntityContent = function($event, title, contentType) { |
|||
var onShowingCallback = { |
|||
onShowing: function(){} |
|||
} |
|||
if (!contentType) { |
|||
contentType = null; |
|||
} |
|||
var content = ''; |
|||
switch(scope.downlink.type) { |
|||
case types.edgeEventType.relation: |
|||
content = angular.toJson(scope.downlink.body); |
|||
showDialog(); |
|||
break; |
|||
case types.edgeEventType.ruleChainMetaData: |
|||
content = ruleChainService.getRuleChainMetaData(scope.downlink.entityId, {ignoreErrors: true}).then( |
|||
function success(info) { |
|||
showDialog(); |
|||
return angular.toJson(info); |
|||
}, function fail() { |
|||
showError(); |
|||
}); |
|||
break; |
|||
default: |
|||
content = entityService.getEntity(scope.downlink.type, scope.downlink.entityId, {ignoreErrors: true}).then( |
|||
function success(info) { |
|||
showDialog(); |
|||
return angular.toJson(info); |
|||
}, function fail() { |
|||
showError(); |
|||
}); |
|||
break; |
|||
} |
|||
function showDialog() { |
|||
$mdDialog.show({ |
|||
controller: 'EdgeDownlinksContentDialogController', |
|||
controllerAs: 'vm', |
|||
templateUrl: edgeDownlinksContentTemplate, |
|||
locals: {content: content, title: title, contentType: contentType, showingCallback: onShowingCallback}, |
|||
parent: angular.element($document[0].body), |
|||
fullscreen: true, |
|||
targetEvent: $event, |
|||
multiple: true, |
|||
onShowing: function(scope, element) { |
|||
onShowingCallback.onShowing(scope, element); |
|||
} |
|||
}); |
|||
} |
|||
function showError() { |
|||
toast.showError($translate.instant('edge.load-entity-error')); |
|||
} |
|||
} |
|||
|
|||
scope.checkEdgeDownlinksType = function (type) { |
|||
return !(type === types.edgeEventType.widgetType || |
|||
type === types.edgeEventType.adminSettings || |
|||
type === types.edgeEventType.widgetsBundle ); |
|||
} |
|||
|
|||
scope.checkTooltip = function($event) { |
|||
var el = $event.target; |
|||
var $el = angular.element(el); |
|||
if(el.offsetWidth < el.scrollWidth && !$el.attr('title')){ |
|||
$el.attr('title', $el.text()); |
|||
} |
|||
} |
|||
|
|||
$compile(element.contents())(scope); |
|||
|
|||
scope.updateStatus = function(downlinkCreatedTime) { |
|||
var status; |
|||
if (downlinkCreatedTime < scope.queueStartTs) { |
|||
status = $translate.instant(types.edgeEventStatus.DEPLOYED.name); |
|||
scope.statusColor = types.edgeEventStatus.DEPLOYED.color; |
|||
} else { |
|||
status = $translate.instant(types.edgeEventStatus.PENDING.name); |
|||
scope.statusColor = types.edgeEventStatus.PENDING.color; |
|||
} |
|||
return status; |
|||
} |
|||
} |
|||
|
|||
return { |
|||
restrict: "A", |
|||
replace: false, |
|||
link: linker, |
|||
scope: false |
|||
}; |
|||
} |
|||
@ -0,0 +1,230 @@ |
|||
/* |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
import './downlink.scss'; |
|||
|
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import edgeDownlinksTableTemplate from './edge-downlinks-table.tpl.html'; |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
/*@ngInject*/ |
|||
export default function EdgeDownlinksDirective($compile, $templateCache, $rootScope, $translate, types, edgeService, attributeService) { |
|||
|
|||
var linker = function (scope, element) { |
|||
|
|||
var template = $templateCache.get(edgeDownlinksTableTemplate); |
|||
element.html(template); |
|||
|
|||
var pageSize = 20; |
|||
var startTime = 0; |
|||
var endTime = 0; |
|||
|
|||
scope.timewindow = { |
|||
history: { |
|||
timewindowMs: 24 * 60 * 60 * 1000 // 1 day
|
|||
} |
|||
} |
|||
|
|||
scope.topIndex = 0; |
|||
|
|||
scope.theDownlinks = { |
|||
getItemAtIndex: function (index) { |
|||
if (index > scope.downlinks.data.length) { |
|||
scope.theDownlinks.fetchMoreItems_(index); |
|||
return null; |
|||
} |
|||
var item = scope.downlinks.data[index]; |
|||
if (item) { |
|||
item.indexNumber = index + 1; |
|||
} |
|||
return item; |
|||
}, |
|||
|
|||
getLength: function () { |
|||
if (scope.downlinks.hasNext) { |
|||
return scope.downlinks.data.length + scope.downlinks.nextPageLink.limit; |
|||
} else { |
|||
return scope.downlinks.data.length; |
|||
} |
|||
}, |
|||
|
|||
fetchMoreItems_: function () { |
|||
if (scope.downlinks.hasNext && !scope.downlinks.pending) { |
|||
if (scope.entityType && scope.entityId && scope.tenantId) { |
|||
scope.loadEdgeInfo(); |
|||
scope.downlinks.pending = true; |
|||
edgeService.getEdgeEvents(scope.entityId, scope.downlinks.nextPageLink).then( |
|||
function success(downlinks) { |
|||
scope.downlinks.data = scope.downlinks.data.concat(prepareEdgeDownlinksData(downlinks.data)); |
|||
scope.downlinks.nextPageLink = downlinks.nextPageLink; |
|||
scope.downlinks.hasNext = downlinks.hasNext; |
|||
if (scope.downlinks.hasNext) { |
|||
scope.downlinks.nextPageLink.limit = pageSize; |
|||
} |
|||
scope.downlinks.pending = false; |
|||
}, |
|||
function fail() { |
|||
scope.downlinks.hasNext = false; |
|||
scope.downlinks.pending = false; |
|||
}); |
|||
} else { |
|||
scope.downlinks.hasNext = false; |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
scope.$watch("entityId", function(newVal, prevVal) { |
|||
if (newVal && !angular.equals(newVal, prevVal)) { |
|||
scope.resetFilter(); |
|||
scope.reload(); |
|||
} |
|||
}); |
|||
|
|||
scope.$watch("timewindow", function(newVal, prevVal) { |
|||
if (newVal && !angular.equals(newVal, prevVal)) { |
|||
scope.reload(); |
|||
} |
|||
}, true); |
|||
|
|||
scope.resetFilter = function() { |
|||
scope.timewindow = { |
|||
history: { |
|||
timewindowMs: 24 * 60 * 60 * 1000 // 1 day
|
|||
} |
|||
}; |
|||
} |
|||
|
|||
scope.updateTimeWindowRange = function() { |
|||
if (scope.timewindow.history.timewindowMs) { |
|||
var currentTime = (new Date).getTime(); |
|||
startTime = currentTime - scope.timewindow.history.timewindowMs; |
|||
endTime = currentTime; |
|||
} else { |
|||
startTime = scope.timewindow.history.fixedTimewindow.startTimeMs; |
|||
endTime = scope.timewindow.history.fixedTimewindow.endTimeMs; |
|||
} |
|||
} |
|||
|
|||
scope.reload = function() { |
|||
scope.topIndex = 0; |
|||
scope.selected = []; |
|||
scope.updateTimeWindowRange(); |
|||
scope.downlinks = { |
|||
data: [], |
|||
nextPageLink: { |
|||
limit: pageSize, |
|||
startTime: startTime, |
|||
endTime: endTime |
|||
}, |
|||
hasNext: true, |
|||
pending: false |
|||
}; |
|||
scope.theDownlinks.getItemAtIndex(pageSize); |
|||
} |
|||
|
|||
scope.noData = function() { |
|||
return scope.downlinks.data.length == 0 && !scope.downlinks.hasNext; |
|||
} |
|||
|
|||
scope.hasData = function() { |
|||
return scope.downlinks.data.length > 0; |
|||
} |
|||
|
|||
scope.loading = function() { |
|||
return $rootScope.loading; |
|||
} |
|||
|
|||
scope.hasScroll = function() { |
|||
var repeatContainer = scope.repeatContainer[0]; |
|||
if (repeatContainer) { |
|||
var scrollElement = repeatContainer.children[0]; |
|||
if (scrollElement) { |
|||
return scrollElement.scrollHeight > scrollElement.clientHeight; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
scope.subscriptionId = null; |
|||
|
|||
scope.loadEdgeInfo = function() { |
|||
attributeService.getEntityAttributesValues( |
|||
scope.entityType, |
|||
scope.entityId, |
|||
types.attributesScope.server.value, |
|||
types.edgeAttributeKeys.queueStartTs, |
|||
null).then( |
|||
function success(attributes) { |
|||
attributes.length > 0 ? scope.onEdgeAttributesUpdate(attributes) : scope.queueStartTs = 0; |
|||
}); |
|||
scope.checkSubscription(); |
|||
} |
|||
|
|||
scope.onEdgeAttributesUpdate = function(attributes) { |
|||
let edgeAttributes = attributes.reduce(function (map, attribute) { |
|||
map[attribute.key] = attribute; |
|||
return map; |
|||
}, {}); |
|||
if (edgeAttributes.queueStartTs) { |
|||
scope.queueStartTs = edgeAttributes.queueStartTs.lastUpdateTs; |
|||
} |
|||
} |
|||
|
|||
scope.checkSubscription = function() { |
|||
var newSubscriptionId = null; |
|||
if (scope.entityId && scope.entityType && types.attributesScope.server.value) { |
|||
newSubscriptionId = |
|||
attributeService.subscribeForEntityAttributes(scope.entityType, scope.entityId, types.attributesScope.server.value); |
|||
} |
|||
if (scope.subscriptionId && scope.subscriptionId != newSubscriptionId) { |
|||
attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); |
|||
} |
|||
scope.subscriptionId = newSubscriptionId; |
|||
} |
|||
|
|||
scope.$on('$destroy', function () { |
|||
if (scope.subscriptionId) { |
|||
attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); |
|||
} |
|||
}); |
|||
|
|||
scope.reload(); |
|||
|
|||
$compile(element.contents())(scope); |
|||
} |
|||
function prepareEdgeDownlinksData(data) { |
|||
|
|||
data.forEach( |
|||
edgeDownlink => { |
|||
edgeDownlink.edgeEventActionText = $translate.instant(types.edgeEventActionTypeTranslations[edgeDownlink.action].name); |
|||
edgeDownlink.edgeEventTypeText = $translate.instant(types.edgeEventTypeTranslations[edgeDownlink.type].name); |
|||
} |
|||
); |
|||
return data; |
|||
} |
|||
|
|||
return { |
|||
restrict: "E", |
|||
link: linker, |
|||
scope: { |
|||
entityType: '=', |
|||
entityId: '=', |
|||
tenantId: '=' |
|||
} |
|||
}; |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2020 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<md-content flex class="md-padding tb-absolute-fill" layout="column"> |
|||
<section layout="row"> |
|||
<tb-timewindow flex ng-model="timewindow" history-only as-button="true"></tb-timewindow> |
|||
<md-button ng-disabled="$root.loading" |
|||
class="md-icon-button" ng-click="reload()"> |
|||
<md-icon>refresh</md-icon> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'action.refresh' | translate }} |
|||
</md-tooltip> |
|||
</md-button> |
|||
</section> |
|||
<md-list flex layout="column" class="md-whiteframe-z1 tb-edge-downlinks-table"> |
|||
<md-list class="tb-row tb-header" layout="row" layout-align="start center" tb-edge-downlinks-header> |
|||
</md-list> |
|||
<md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading" |
|||
ng-show="$root.loading"></md-progress-linear> |
|||
<md-divider></md-divider> |
|||
<span translate layout-align="center center" |
|||
style="margin-top: 25px;" |
|||
class="tb-prompt" ng-show="noData()">edge.no-downlinks-prompt</span> |
|||
<md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer"> |
|||
<md-list-item md-virtual-repeat="downlink in theDownlinks" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}"> |
|||
<md-list class="tb-row" flex layout="row" layout-align="start center" tb-edge-downlinks-row downlink="{{downlink}}"> |
|||
</md-list> |
|||
<md-divider flex></md-divider> |
|||
</md-list-item> |
|||
</md-virtual-repeat-container> |
|||
</md-list> |
|||
</md-content> |
|||
Loading…
Reference in new issue