diff options
author | 2017-08-18 10:09:01 -0400 | |
---|---|---|
committer | 2017-08-18 10:10:04 -0400 | |
commit | e8c966373f25bc769fae4512bccc44903bf7309b (patch) | |
tree | 577435a3d5668ca6608009a9af5e8b4dd9175abd /oom-app-overlay/src/main/webapp/app/oom/consul | |
parent | a6214cfb46570297c71fea29ee68014892a2484b (diff) |
Rename to OOM (no more ecd)
- Change to OOM Dashboard (no more ECOMP Controller)
- Rework some exception handling
Issue: CCSDK-61
Change-Id: Id5379e8b272b651373c79f46b20da1d32de013ed
Signed-off-by: Christopher Lott (cl778h) <clott@research.att.com>
Diffstat (limited to 'oom-app-overlay/src/main/webapp/app/oom/consul')
12 files changed, 1454 insertions, 0 deletions
diff --git a/oom-app-overlay/src/main/webapp/app/oom/consul/datacenter-health-service.js b/oom-app-overlay/src/main/webapp/app/oom/consul/datacenter-health-service.js new file mode 100644 index 0000000..66f934e --- /dev/null +++ b/oom-app-overlay/src/main/webapp/app/oom/consul/datacenter-health-service.js @@ -0,0 +1,51 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.factory('DatacenterHealthService', function ($http, $q, $log) { + return { + /** + * Gets one page of objects. + * @param {Number} pageNum - page number; e.g., 1 + * @param {Number} viewPerPage - number of items per page; e.g., 25 + * @return {JSON} Response object from remote side + */ + getDatacentersHealth: function(pageNum,viewPerPage) { + // cache control for IE + var cc = "&cc=" + new Date().getTime().toString(); + return $http({ + method: 'GET', + url: 'healthServices/datacenters?pageNum=' + pageNum + '&viewPerPage=' + viewPerPage + cc, + cache: false, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('DatacentersService.getDatacentersHealth: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('DatacenterService.getDatacentersHealth failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + }; +}); diff --git a/oom-app-overlay/src/main/webapp/app/oom/consul/datacenter-table-controller.js b/oom-app-overlay/src/main/webapp/app/oom/consul/datacenter-table-controller.js new file mode 100644 index 0000000..f1c8820 --- /dev/null +++ b/oom-app-overlay/src/main/webapp/app/oom/consul/datacenter-table-controller.js @@ -0,0 +1,103 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.controller('datacenterTableController', function($scope, $log, $modal, DatacenterHealthService) { + + 'use strict'; + + // this object holds all app data and functions + $scope.ecdapp = {}; + // models for controls on screen + $scope.ecdapp.tableData = []; + $scope.ecdapp.currentPageNum = 1; + $scope.ecdapp.totalPages = 1; + $scope.ecdapp.viewPerPage = 100; + $scope.ecdapp.viewPerPageOptions = [ { + index : 0, + value : 50 + }, { + index : 1, + value : 100 + }, { + index : 2, + value : 500 + }, { + index : 3, + value : 1000 + }, { + index : 4, + value : 2500 + } ]; + // other + $scope.ecdapp.errMsg = null; + $scope.ecdapp.isDataLoading = true; + $scope.ecdapp.isRequestFailed = false; + + /** + * Answers an array of the specified size - makes Angular iteration easy. + */ + $scope.ecdapp.buildArraySizeN = function(num) { + // $log.debug("buildArraySizeN: invoked with " + num); + return new Array(num); + } + + /** + * Loads the table. Interprets the remote controller's response and copies + * to scope variables. The response is either list to be assigned to + * tableData, or an error to be shown. + */ + $scope.ecdapp.loadTable = function() { + $scope.ecdapp.isDataLoading = true; + DatacenterHealthService.getDatacentersHealth( + $scope.ecdapp.currentPageNum, $scope.ecdapp.viewPerPage).then( + function(jsonObj) { + if (jsonObj.error) { + $log.error("datacentersController.loadTable failed: " + + jsonObj.error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = jsonObj.error; + $scope.ecdapp.tableData = []; + } else { + // $log.debug("datacentersController.loadTable + // succeeded, size " + jsonObj.data.length); + $scope.ecdapp.isRequestFailed = false; + $scope.ecdapp.errMsg = null; + $scope.ecdapp.totalPages = jsonObj.totalPages; + $scope.ecdapp.tableData = jsonObj.items; + } + $scope.ecdapp.isDataLoading = false; + }, + function(error) { + $log.error("datacentersController.loadTable failed: " + + error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = error; + $scope.ecdapp.tableData = []; + $scope.ecdapp.isDataLoading = false; + }); + }; + + // Populate the table on load. Note that the b2b selector code + // sets the page-number value, and the change event calls load table. + // Do not call this here to avoid double load: + // $scope.ecdapp.loadTable(); + +}); diff --git a/oom-app-overlay/src/main/webapp/app/oom/consul/datacenter_table.html b/oom-app-overlay/src/main/webapp/app/oom/consul/datacenter_table.html new file mode 100644 index 0000000..73a1b82 --- /dev/null +++ b/oom-app-overlay/src/main/webapp/app/oom/consul/datacenter_table.html @@ -0,0 +1,71 @@ +<div id="page-content"> + + <h1 class="heading-page" id="consul_datacenters">Monitored Datacenters</h1> + + <!-- show progress indicator --> + <div ng-show="ecdapp.isDataLoading"> + <div class="span" style="margin-bottom:20px;"> + <i class="icon-primary-spinner small" role="img" aria-label="Please wait while the content loads"></i> + Please wait while the content loads. + </div> + </div> + + <div ng-hide="ecdapp.isDataLoading"> + + <div id="button-search-row"> + <div style="float:right;"> + <div class="form-field form-field__small"> + <input + type="text" + placeholder="Search Datacenters" + ng-model="ecdapp.searchString"/> + </div> + </div> + </div> + + <div ng-show="ecdapp.isRequestFailed"> + <span class="ecd-error-message">{{ecdapp.errMsg}}</span> + </div> + + <div ng-hide="ecdapp.isRequestFailed"> + + <div + b2b-table + id="consul-datacenters-table" + class="b2b-table-div" + table-data="ecdapp.tableData" + search-string="ecdapp.searchString" + current-page="ecdapp.currentPageIgnored" + next-sort="ecdapp.nextSortIgnored"> + + <table> + + <thead b2b-table-row type="header"> + <tr id="th-header-row"> + <th b2b-table-header key="name">Name</th> + </tr> + </thead> + + <tbody b2b-table-row type="body" row-repeat="rowData in ecdapp.tableData"> + <tr id="tr-rowData"> + <td b2b-table-body + ng-bind="rowData.name"/> + </tr> + </tbody> + </table> + </div> + + <div b2b-pagination="" total-pages="ecdapp.totalPages" + current-page="ecdapp.currentPageNum" + click-handler="pageChangeHandler" role="navigation"></div> + + </div> + <!-- failed --> + + <div style="height: 10px;"> + <!-- space between page number and black footer --> + </div> + + </div><!-- loading --> + +</div><!-- page content --> diff --git a/oom-app-overlay/src/main/webapp/app/oom/consul/node-health-service.js b/oom-app-overlay/src/main/webapp/app/oom/consul/node-health-service.js new file mode 100644 index 0000000..3f3c8f6 --- /dev/null +++ b/oom-app-overlay/src/main/webapp/app/oom/consul/node-health-service.js @@ -0,0 +1,78 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.factory('NodeHealthService', function ($http, $q, $log) { + return { + /** + * Gets one page of objects. + * @param {Number} pageNum - page number; e.g., 1 + * @param {Number} viewPerPage - number of items per page; e.g., 25 + * @return {JSON} Response object from remote side + */ + getNodesHealth: function(pageNum,viewPerPage) { + // cache control for IE + let cc = "&cc=" + new Date().getTime().toString(); + let url = 'healthservices/nodes?pageNum=' + pageNum + '&viewPerPage=' + viewPerPage + cc; + return $http({ + method: 'GET', + url: url, + cache: false, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('nodeHealthService.getNodesHealth: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('nodeHealthService.getNodesHealth failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + /** + * Gets all services on the specified node. + * @param {nodeName} node name (not the UUID) + * @return {JSON} Response object from remote side + */ + getNodeServicesHealth: function(nodeName) { + // cache control for IE + let cc = "?cc=" + new Date().getTime().toString(); + let url = 'healthservices/nodes/' + nodeName + cc; + return $http({ + method: 'GET', + url: url, + cache: false, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('serviceHealthService.getNodeServicesHealth: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('serviceHealthService.getNodeServicesHealth failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + }; +}); diff --git a/oom-app-overlay/src/main/webapp/app/oom/consul/node-services-controller.js b/oom-app-overlay/src/main/webapp/app/oom/consul/node-services-controller.js new file mode 100644 index 0000000..b82ba40 --- /dev/null +++ b/oom-app-overlay/src/main/webapp/app/oom/consul/node-services-controller.js @@ -0,0 +1,77 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.controller('nodeServicesCtrl', function($scope, $log, message, NodeHealthService) { + + 'use strict'; + + // Controls logging in this controller + var debug = false; + + if (debug) + $log.debug('nodeServicesCtrl: message is ' + JSON.stringify(message)); + + // this object holds all app data and functions + $scope.ecdapp = {}; + $scope.ecdapp.label = 'Services on Node ' + message.node; + + /** + * Loads the table of services for the specified node. + */ + $scope.ecdapp.loadTable = function(nodeName) { + $scope.ecdapp.isDataLoading = true; + if (debug) + $log.debug('nodeServicesCtrl: loading data for ' + nodeName); + NodeHealthService.getNodeServicesHealth(nodeName).then( + function(jsonObj) { + if (debug) + $log.debug('nodeServicesCtrl: response is ' + JSON.stringify(jsonObj)); + if (jsonObj.error) { + $log.error("nodeServicesCtrl.loadTable failed: " + + jsonObj.error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = jsonObj.error; + $scope.ecdapp.tableData = []; + } else { + // $log.debug("serviceHealthController.loadTable + // succeeded, size " + jsonObj.data.length); + $scope.ecdapp.isRequestFailed = false; + $scope.ecdapp.errMsg = null; + $scope.ecdapp.tableData = jsonObj; + } + $scope.ecdapp.isDataLoading = false; + }, + function(error) { + $log.error("nodeServicesCtrl.loadTable failed: " + + error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = error; + $scope.ecdapp.tableData = []; + $scope.ecdapp.isDataLoading = false; + }); + }; + + // Show services for the requested node + if (debug) + $log.debug('nodeServicesCtrl: requesting services for node ' + message.node); + $scope.ecdapp.loadTable(message.node); + +}); diff --git a/oom-app-overlay/src/main/webapp/app/oom/consul/node-table-controller.js b/oom-app-overlay/src/main/webapp/app/oom/consul/node-table-controller.js new file mode 100644 index 0000000..61ececb --- /dev/null +++ b/oom-app-overlay/src/main/webapp/app/oom/consul/node-table-controller.js @@ -0,0 +1,118 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.controller('nodeTableController', function($scope, $log, $modal, NodeHealthService) { + + 'use strict'; + + // this object holds all app data and functions + $scope.ecdapp = {}; + // models for controls on screen + $scope.ecdapp.tableData = []; + $scope.ecdapp.currentPageNum = 1; + $scope.ecdapp.viewPerPage = 10; + // other + $scope.ecdapp.errMsg = null; + $scope.ecdapp.isDataLoading = true; + $scope.ecdapp.isRequestFailed = false; + + /** + * Answers an array of the specified size - makes Angular iteration easy. + */ + $scope.ecdapp.buildArraySizeN = function(num) { + // $log.debug("buildArraySizeN: invoked with " + num); + return new Array(num); + } + + /** + * Loads the table. Interprets the remote controller's response and copies + * to scope variables. The response is either list to be assigned to + * tableData, or an error to be shown. + */ + $scope.ecdapp.loadTable = function() { + $scope.ecdapp.isDataLoading = true; + NodeHealthService.getNodesHealth($scope.ecdapp.currentPageNum, + $scope.ecdapp.viewPerPage).then( + function(jsonObj) { + if (jsonObj.error) { + $log.error("nodeHealthController.loadTable failed: " + + jsonObj.error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = jsonObj.error; + $scope.ecdapp.tableData = []; + } else { + // $log.debug("nodeHealthController.loadTable succeeded, + // size " + jsonObj.data.length); + $scope.ecdapp.isRequestFailed = false; + $scope.ecdapp.errMsg = null; + $scope.ecdapp.totalPages = jsonObj.totalPages; + $scope.ecdapp.tableData = jsonObj.items; + } + $scope.ecdapp.isDataLoading = false; + }, + function(error) { + $log.error("nodeHealthController.loadTable failed: " + + error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = error; + $scope.ecdapp.tableData = []; + $scope.ecdapp.isDataLoading = false; + }); + }; + + /** + * Invoked at first page load AND when user clicks on the B2B pagination + * control. + */ + $scope.pageChangeHandler = function(page) { + // console.log('pageChangeHandler: current is ' + + // $scope.ecdapp.currentPageNum + ' new is ' + page); + $scope.ecdapp.currentPageNum = page; + $scope.ecdapp.loadTable(); + } + + + /** + * Shows a modal pop-up with services on this node. + */ + $scope.ecdapp.viewServicesModalPopup = function(nodeInfo) { + var modalInstance = $modal.open({ + templateUrl : 'node_services_popup.html', + controller : 'nodeServicesCtrl', + windowClass: 'modal-docked', + sizeClass: 'modal-large', + resolve : { + message : function() { + return nodeInfo ; + } + } + }); + modalInstance.result.then(function(response) { + // No response expected. + }); + }; + + // Populate the table on load. Note that the b2b selector code + // sets the page-number value, and the change event calls load table. + // Do not call this here to avoid double load: + // $scope.ecdapp.loadTable(); + +}); diff --git a/oom-app-overlay/src/main/webapp/app/oom/consul/node_popups.html b/oom-app-overlay/src/main/webapp/app/oom/consul/node_popups.html new file mode 100644 index 0000000..63b5e5f --- /dev/null +++ b/oom-app-overlay/src/main/webapp/app/oom/consul/node_popups.html @@ -0,0 +1,67 @@ +<script type="text/ng-template" id="node_services_popup.html"> + + <div class="b2b-modal-header ng-scope"> + <h2 id="myModalLabel" modal-title="">{{ecdapp.label}}</h2> + <div class="corner-button in"> + <button type="button" class="close" aria-label="Close" + ng-click="$dismiss('cancel')"></button> + </div> + </div> + + <!-- show progress indicator --> + <div ng-show="ecdapp.isDataLoading"> + <div class="span" style="margin-bottom: 20px;"> + <i class="icon-primary-spinner small" role="img" + aria-label="Please wait while the content loads"></i> Please wait + while the content loads. + </div> + </div> + + <div ng-hide="ecdapp.isDataLoading"> + + <div ng-show="ecdapp.isRequestFailed"> + <span class="ecd-error-message">{{ecdapp.errMsg}}</span> + </div> + + <div ng-hide="ecdapp.isRequestFailed"> + + <div b2b-table id="consul-node-services-table" class="b2b-table-div" + table-data="ecdapp.tableData" search-string="ecdapp.searchStringIgnored" + current-page="ecdapp.currentPageIgnored" next-sort="ecdapp.nextSortIgnored"> + + <table> + <thead b2b-table-row type="header"> + <tr id="th-header-row"> + <th b2b-table-header key="serviceName">Service Name</th> + <th b2b-table-header key="serviceID">Service ID</th> + <th b2b-table-header key="status">Status</th> + <th b2b-table-header key="notes">Notes</th> + <th b2b-table-header key="output">Output</th> + </tr> + </thead> + <tbody b2b-table-row type="body" + row-repeat="rowData in ecdapp.tableData"> + <tr id="tr-rowData"> + <td b2b-table-body ng-bind="rowData.serviceName" /> + <td b2b-table-body ng-bind="rowData.serviceID" /> + <td b2b-table-body ng-bind="rowData.status" /> + <td b2b-table-body ng-bind="rowData.notes" /> + <td b2b-table-body ng-bind="rowData.output" /> + </tr> + </tbody> + </table> + + </div> <!--table--> + </div> <!--failed--> + </div> <!--hide--> + + <div class="b2b-modal-footer ng-scope ng-isolate-scope"> + <div class="cta-button-group in"> + <button class="btn btn-alt btn-small" type="button" + ng-click="$dismiss('cancel')"> + Close + </button> + </div> + </div> + +</script> diff --git a/oom-app-overlay/src/main/webapp/app/oom/consul/node_table.html b/oom-app-overlay/src/main/webapp/app/oom/consul/node_table.html new file mode 100644 index 0000000..231d331 --- /dev/null +++ b/oom-app-overlay/src/main/webapp/app/oom/consul/node_table.html @@ -0,0 +1,78 @@ +<div id="page-content"> + + <h1 class="heading-page" id="consul_nodes">Monitored Nodes</h1> + + <!-- show progress indicator --> + <div ng-show="ecdapp.isDataLoading"> + <div class="span" style="margin-bottom: 20px;"> + <i class="icon-primary-spinner small" role="img" + aria-label="Please wait while the content loads"></i> Please wait + while the content loads. + </div> + </div> + + <div ng-hide="ecdapp.isDataLoading"> + + <div id="button-search-row"> + <div style="float: right;"> + <div class="form-field form-field__small"> + <input type="text" placeholder="Search Nodes" + ng-model="ecdapp.searchString" /> + </div> + </div> + </div> + + <div ng-show="ecdapp.isRequestFailed"> + <span class="ecd-error-message">{{ecdapp.errMsg}}</span> + </div> + + <div ng-hide="ecdapp.isRequestFailed"> + + <div b2b-table id="consul-nodes-table" class="b2b-table-div" + table-data="ecdapp.tableData" search-string="ecdapp.searchString" + current-page="ecdapp.currentPageIgnored" + next-sort="ecdapp.nextSortIgnored"> + + <table> + + <thead b2b-table-row type="header"> + <tr id="th-header-row"> + <th b2b-table-header key="node">Node Name</th> + <th b2b-table-header key="id">Node ID</th> + <th b2b-table-header key="address">Address</th> + <th b2b-table-header sortable="false"><i class="icon-controls-gear ecd-icon-display"></i></th> + </tr> + </thead> + + <tbody b2b-table-row type="body" + row-repeat="rowData in ecdapp.tableData"> + <tr id="tr-rowData"> + <td b2b-table-body ng-bind="rowData.node" /> + <td b2b-table-body ng-bind="rowData.id" /> + <td b2b-table-body ng-bind="rowData.address" /> + <td b2b-table-body> + <div ng-click="ecdapp.viewServicesModalPopup(rowData);"> + <a href="" title="View node services" class="icon-people-preview ecd-icon-action"></a> + </div> + </td> + </tr> + </tbody> + </table> + </div> + + <div b2b-pagination="" total-pages="ecdapp.totalPages" + current-page="ecdapp.currentPageNum" + click-handler="pageChangeHandler" role="navigation"></div> + + </div> + <!-- failed --> + + <div style="height: 10px;"> + <!-- space between page number and black footer --> + </div> + + </div> + <!-- loading --> + +</div> +<!-- page content --> diff --git a/oom-app-overlay/src/main/webapp/app/oom/consul/service-controllers.js b/oom-app-overlay/src/main/webapp/app/oom/consul/service-controllers.js new file mode 100644 index 0000000..339c436 --- /dev/null +++ b/oom-app-overlay/src/main/webapp/app/oom/consul/service-controllers.js @@ -0,0 +1,366 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.controller('serviceHealthTableController', function($scope, $log, $modal, modalService, ServiceHealthService) { + + 'use strict'; + + // this object holds all app data and functions + $scope.ecdapp = {}; + // models for controls on screen + $scope.ecdapp.tableData = []; + $scope.ecdapp.currentPageNum = 1; + $scope.ecdapp.viewPerPage = 100; + // other + $scope.ecdapp.errMsg = null; + $scope.ecdapp.isDataLoading = true; + $scope.ecdapp.isRequestFailed = false; + + /** + * Loads the table. Interprets the remote controller's response and copies + * to scope variables. The response is either list to be assigned to + * tableData, or an error to be shown. + */ + $scope.ecdapp.loadTable = function() { + $scope.ecdapp.isDataLoading = true; + ServiceHealthService.getServicesHealth($scope.ecdapp.currentPageNum, + $scope.ecdapp.viewPerPage).then( + function(jsonObj) { + if (jsonObj.error) { + $log.error("serviceHealthTableController.loadTable failed: " + + jsonObj.error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = jsonObj.error; + $scope.ecdapp.tableData = []; + } else { + // $log.debug("serviceHealthController.loadTable + // succeeded, size " + jsonObj.data.length); + $scope.ecdapp.isRequestFailed = false; + $scope.ecdapp.errMsg = null; + $scope.ecdapp.totalPages = jsonObj.totalPages; + $scope.ecdapp.tableData = jsonObj.items; + } + $scope.ecdapp.isDataLoading = false; + }, + function(error) { + $log.error("serviceHealthTableController.loadTable failed: " + + error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = error; + $scope.ecdapp.tableData = []; + $scope.ecdapp.isDataLoading = false; + }); + }; + + /** + * Invoked at first page load AND when user clicks on the B2B pagination + * control. + */ + $scope.pageChangeHandler = function(page) { + // console.log('pageChangeHandler: current is ' + + // $scope.ecdapp.currentPageNum + ' new is ' + page); + $scope.ecdapp.currentPageNum = page; + $scope.ecdapp.loadTable(); + } + + /** + * Shows a modal pop-up to register a service. + */ + $scope.ecdapp.registerServiceModalPopup = function() { + var modalInstance = $modal.open({ + templateUrl : 'service_register_popup.html', + controller : 'serviceRegisterCtrl', + windowClass: 'modal-docked', + sizeClass: 'modal-medium', + resolve : { + message : function() { + return { /* no data */ } ; + } + } + }); + modalInstance.result.then(function(response) { + // $log.debug('addFeedModalPopup: response: ' + JSON.stringify(response)); + if (response == null) { + // $log.debug('user closed dialog'); + } + else { + if (response.error != null) { + $log.error('uploadBlueprintModalPopup failed: ' + response.error); + alert('Failed to upload blueprint:\n' + response.error); + } + else { + // success, get the updated list. + $scope.ecdapp.loadTable() + } + } + }); + }; + + /** + * Shows a modal pop-up with service health history. + */ + $scope.ecdapp.viewHealthHistoryModalPopup = function(service) { + var modalInstance = $modal.open({ + templateUrl : 'service_history_popup.html', + controller : 'serviceHistoryCtlr', + windowClass: 'modal-docked', + sizeClass: 'modal-large', + resolve : { + message : function() { + return service; + } + } + }); + modalInstance.result.then(function(response) { + // $log.debug('addFeedModalPopup: response: ' + JSON.stringify(response)); + if (response == null) { + // $log.debug('user closed dialog'); + } + else { + if (response.error != null) { + $log.error('uploadBlueprintModalPopup failed: ' + response.error); + alert('Failed to upload blueprint:\n' + response.error); + } + else { + // success, get the updated list. + $scope.ecdapp.loadTable() + } + } + }); + }; + + /** + * Shows a modal pop-up to confirm deregistration. + * On successful completion, updates the table. + */ + $scope.ecdapp.deregisterServiceModalPopup = function(service) { + modalService.popupConfirmWin("Confirm", "Deregister service '" + + service.serviceName + "'?", function() { + ServiceHealthService.deregisterService(service.serviceName).then( + function(response) { + if (response && response.error) { + // $log.error('deleteBlueprint failed: ' + response.error); + alert('Failed to deregister service:\n' + response.error); + } + else { + // No response body on success. + $scope.ecdapp.loadTable(); + } + }, + function(error) { + $log.error('ServiceHealthService.deregisterService failed: ' + error); + alert('Failed to deregister service:\n' + error); + }); + }) + }; + + // Populate the table on load. Note that the b2b selector code + // sets the page-number value, and the change event calls load table. + // Do not call this here to avoid double load: + // $scope.ecdapp.loadTable(); + +}); + + +appDS2.controller('serviceRegisterCtrl', function($scope, $log, message, ServiceHealthService) { + + 'use strict'; + + // this object holds all app data and functions + $scope.ecdapp = {}; + + $scope.ecdapp.label = 'Register Service'; + // Model the data simply here. + // Build the complex request later. + $scope.ecdapp.registerRequest = + { + service_id : '', + service_name : '', + service_address : '', + service_port : '', + check_endpoint: '', + check_interval: '', + check_description: '', + check_name: '' + }; + + /** + * Validates content of user-editable fields. + * Uses the list in message.feedList + * Returns null if all is well, + * a descriptive error message otherwise. + */ + $scope.ecdapp.validateRequest = function(request) { + if (request == null) + return "No data found.\nPlease enter some values."; + if (request.service_id == null || request.service_id.trim() == '') + return "Service ID is required.\nPlease enter a value."; + if (request.service_name == null || request.service_name.trim() == '') + return "Service name is required.\nPlease enter a value."; + if (request.service_address == null || request.service_address.trim() == '') + return "Service address is required.\nPlease enter a value."; + if (request.service_port == null || request.service_port.trim() == '') + return "Service port is required.\nPlease enter a value."; + if (request.check_endpoint == null || request.check_endpoint.trim() == '') + return "Check endpoint URL is required.\nPlease enter a value."; + if (request.check_interval == null || request.check_interval.trim() == '') + return "Check interval is required.\nPlease enter a value."; + // description and name are optional (I think) + return null; + } + + $scope.ecdapp.registerService = function(edit_req) { + var validateMsg = $scope.ecdapp.validateRequest(edit_req); + if (validateMsg != null) { + alert('Invalid registration request:\n' + validateMsg); + return; + } + // Build the complex request + let request = { + services : [ + { + id : edit_req.service_id, + name : edit_req.service_name, + address : edit_req.service_address, + port : edit_req.service_port, + tags: [ ], + checks : [ + { + endpoint : edit_req.check_endoint, + interval : edit_req.check_interval, + description : edit_req.description, + name : edit_req.check_name + } + ] + } + ] + }; + + ServiceHealthService.registerService(request) + .then(function(response) { + if (response.error) + alert('Failed to register service:\n' + response.error); + }, + function (error) { + $log.error('serviceRegisterCtrl: error while registering: ' + error); + alert('Server rejected registration request:\n' + error); + } + ); + + }; + +}); + + +appDS2.controller('serviceHistoryCtlr', function($scope, $log, $modal, message, ServiceHealthService) { + + 'use strict'; + + // Controls logging in this controller + var debug = false; + if (debug) + $log.debug('serviceHistoryCtlr: message: ' + JSON.stringify(message)); + + // this object holds all app data and functions + $scope.ecdapp = {}; + // For convenience + $scope.ecdapp.serviceName = message.serviceName; + $scope.ecdapp.label = 'History for Service ' + $scope.ecdapp.serviceName; + + // models for controls on screen + $scope.ecdapp.startDateTime = ''; + $scope.ecdapp.endDateTime = ''; + // data fetched from backend + $scope.ecdapp.tableData = []; + // progress and error handling + $scope.ecdapp.isDataLoading = false; + $scope.ecdapp.isRequestFailed = false; + $scope.ecdapp.errMsg = null; + + /** + * Loads the table. Interprets the remote controller's response and copies + * to scope variables. The response is either list to be assigned to + * tableData, or an error to be shown. + */ + $scope.ecdapp.showHistory = function() { + // Validate the entries in the date fields. + // If user types, it's a STRING (not a Date). + if ($scope.ecdapp.startDateTime == null) { + alert("Please enter a start date."); + return; + } + let startDT = Date.parse($scope.ecdapp.startDateTime); + if (isNaN(startDT)) { + alert("Failed to parse start date."); + return; + } + else { + // Use the parsed version. + $scope.ecdapp.startDateTime = new Date(startDT); + } + if ($scope.ecdapp.endDateTime == null) { + alert("Please enter an end date."); + return; + } + let endDT = Date.parse($scope.ecdapp.endDateTime); + if (isNaN(endDT)) { + alert("Failed to parse end date."); + return; + } + else { + $scope.ecdapp.endDateTime = new Date(endDT); + } + if ($scope.ecdapp.startDateTime.getTime() >= $scope.ecdapp.endDateTime.getTime()) { + alert("The start date must be before the end date, please correct."); + return; + } + $scope.ecdapp.isDataLoading = true; + ServiceHealthService.getServiceHealthHistory( + $scope.ecdapp.serviceName, + $scope.ecdapp.startDateTime.toISOString(), + $scope.ecdapp.endDateTime.toISOString() ).then( + function(jsonObj) { + if (jsonObj.error) { + $log.error("serviceHistoryCtlr.getServiceHealthHistory reported error: " + + jsonObj.error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = jsonObj.error; + $scope.ecdapp.tableData = []; + } else { + if (debug) + $log.debug("serviceHistoryCtlr.getServiceHealthHistory succeeded, size " + jsonObj.length); + $scope.ecdapp.isRequestFailed = false; + $scope.ecdapp.errMsg = null; + $scope.ecdapp.tableData = jsonObj; + } + $scope.ecdapp.isDataLoading = false; + }, + function(error) { + $log.error("serviceHistoryCtlr.getServiceHealthHistory failed: " + + error); + $scope.ecdapp.isRequestFailed = true; + $scope.ecdapp.errMsg = error; + $scope.ecdapp.tableData = []; + $scope.ecdapp.isDataLoading = false; + }); + }; + +}); diff --git a/oom-app-overlay/src/main/webapp/app/oom/consul/service-health-service.js b/oom-app-overlay/src/main/webapp/app/oom/consul/service-health-service.js new file mode 100644 index 0000000..010cc11 --- /dev/null +++ b/oom-app-overlay/src/main/webapp/app/oom/consul/service-health-service.js @@ -0,0 +1,145 @@ +/******************************************************************************* + * =============LICENSE_START========================================================= + * + * ================================================================================= + * Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + *******************************************************************************/ +appDS2.factory('ServiceHealthService', function ($http, $q, $log) { + return { + + /** + * Gets information on all services. + */ + getServices: function() { + let cc = "?cc=" + new Date().getTime().toString(); + let url = 'healthservices/services' + cc; + return $http({ + method: 'GET', + url: url, + cache: false, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('serviceHealthService.getServices: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('serviceHealthService.getServices failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + /** + * Gets one page of objects. + * @param {Number} pageNum - page number; e.g., 1 + * @param {Number} viewPerPage - number of items per page; e.g., 25 + * @return {JSON} Response object from remote side + */ + getServicesHealth: function(pageNum, viewPerPage) { + // cache control for IE + let cc = "&cc=" + new Date().getTime().toString(); + let url = 'healthservices/serviceshealth?pageNum=' + pageNum + '&viewPerPage=' + viewPerPage + cc; + return $http({ + method: 'GET', + url: url, + cache: false, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('serviceHealthService.getServicesHealth: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('serviceHealthService.getServicesHealth failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + /** + * Gets history of the specified service between the specified date-times. + */ + getServiceHealthHistory: function(serviceName, startDateTime, endDateTime) { + // encode everything + let enService = encodeURIComponent(serviceName); + let enStart = encodeURIComponent(startDateTime); + let enEnd = encodeURIComponent(endDateTime); + let url = 'healthservices/svchist/' + enService + '?start=' + enStart + '&end=' + enEnd; + return $http({ + method: 'GET', + url: url, + cache: false, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('serviceHealthService.getServiceHealthHistory: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('serviceHealthService.getServiceHealthHistory failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + /** + * Registers a service with Consul for health monitoring. + */ + registerService: function(request) { + let url = 'healthservices/register'; + return $http({ + method: 'POST', + url: url, + data: request, + responseType: 'json' + }).then(function(response) { + if (response.data == null || typeof response.data != 'object') + return $q.reject('ServiceHealthService.registerService: response.data null or not object'); + else + return response.data; + }, + function(error) { + $log.error('ServiceHealthService.registerService failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + /** + * Deregisters a service from Consul. + */ + deregisterService: function(serviceName) { + let url = 'healthservices/deregister/' + serviceName; + return $http({ + method: 'POST', + url: url, + responseType: 'json' + }).then(function(response) { + // This is called on response code 200..299. + // On success, response.data is null. + // On failure, response.data has an error message. + return response.data; + }, + function(error) { + $log.error('ServiceHealthService.deregisterService failed: ' + JSON.stringify(error)); + return $q.reject(error.statusText); + }); + }, + + }; +}); diff --git a/oom-app-overlay/src/main/webapp/app/oom/consul/service_health_table.html b/oom-app-overlay/src/main/webapp/app/oom/consul/service_health_table.html new file mode 100644 index 0000000..b6a4557 --- /dev/null +++ b/oom-app-overlay/src/main/webapp/app/oom/consul/service_health_table.html @@ -0,0 +1,93 @@ +<div id="page-content"> + + <h1 class="heading-page" id="consul_services">Monitored Services</h1> + + <!-- show progress indicator --> + <div ng-show="ecdapp.isDataLoading"> + <div class="span" style="margin-bottom: 20px;"> + <i class="icon-primary-spinner small" role="img" + aria-label="Please wait while the content loads"></i> Please wait + while the content loads. + </div> + </div> + + <div ng-hide="ecdapp.isDataLoading"> + + <div id="button-search-row"> + <button class="btn btn-alt btn-small" + type="submit" + ng-click="ecdapp.registerServiceModalPopup();"> + Register Service... + </button> + <div style="float: right;"> + <div class="form-field form-field__small"> + <input type="text" placeholder="Search Services" + ng-model="ecdapp.searchString" /> + </div> + </div> + </div> + + <div ng-show="ecdapp.isRequestFailed"> + <span class="ecd-error-message">{{ecdapp.errMsg}}</span> + </div> + + <div ng-hide="ecdapp.isRequestFailed"> + + <div b2b-table id="consul-services-table" class="b2b-table-div" + table-data="ecdapp.tableData" search-string="ecdapp.searchString" + current-page="ecdapp.currentPageIgnored" + next-sort="ecdapp.nextSortIgnored"> + + <table> + + <thead b2b-table-row type="header"> + <tr id="th-header-row"> + <th b2b-table-header key="serviceName">Service Name</th> + <th b2b-table-header key="serviceID">Service ID</th> + <th b2b-table-header key="node">Node Name</th> + <th b2b-table-header key="status">Status</th> + <th b2b-table-header key="notes">Notes</th> + <th b2b-table-header key="output">Output</th> + <th b2b-table-header sortable="false"><i class="icon-controls-gear ecd-icon-display"></i></th> + </tr> + </thead> + + <tbody b2b-table-row type="body" + row-repeat="rowData in ecdapp.tableData"> + <tr id="tr-rowData"> + <td b2b-table-body ng-bind="rowData.serviceName" /> + <td b2b-table-body ng-bind="rowData.serviceID" /> + <td b2b-table-body ng-bind="rowData.node" /> + <td b2b-table-body ng-bind="rowData.status" /> + <td b2b-table-body ng-bind="rowData.notes" /> + <td b2b-table-body ng-bind="rowData.output" /> + <td b2b-table-body> + <div ng-click="ecdapp.viewHealthHistoryModalPopup(rowData);"> + <a href="" title="View history" class="icon-datanetwork-history ecd-icon-action"></a> + </div> + <div ng-click="ecdapp.deregisterServiceModalPopup(rowData);"> + <a href="" title="Deregister service" class="icon-misc-trash ecd-icon-action"></a> + </div> + </td> + </tr> + </tbody> + </table> + </div> + + <div b2b-pagination="" total-pages="ecdapp.totalPages" + current-page="ecdapp.currentPageNum" + click-handler="pageChangeHandler" role="navigation"></div> + + + </div> + <!-- error --> + + <div style="height: 10px;"> + <!-- space between page number and black footer --> + </div> + + </div> + <!-- loading --> + +</div> +<!-- page content --> diff --git a/oom-app-overlay/src/main/webapp/app/oom/consul/service_popups.html b/oom-app-overlay/src/main/webapp/app/oom/consul/service_popups.html new file mode 100644 index 0000000..8fd92b3 --- /dev/null +++ b/oom-app-overlay/src/main/webapp/app/oom/consul/service_popups.html @@ -0,0 +1,207 @@ +<script type="text/ng-template" id="service_register_popup.html"> + + <div class="b2b-modal-header ng-scope"> + <h2 id="myModalLabel" modal-title="">{{ecdapp.label}}</h2> + <div class="corner-button in"> + <button type="button" class="close" aria-label="Close" + ng-click="$dismiss('cancel')"></button> + </div> + </div> + + <div class="b2b-modal-body ng-scope ng-isolate-scope" tabindex="0" + role="region" aria-label="Modal body content"> + + <div class="row-nowrap"> + <div class="span12"> + <div class="form-row"> + <label for="serviceName">*Service Name</label> + <div class="field-group"> + <input id="serviceName" class="span12" type="text" data-ng-model="ecdapp.editRequest.service_name" autofocus /> + </div> + </div> + </div> + <div class="span12"> + <div class="form-row"> + <label for="checkEndpoint">*Endpoint URL</label> + <div class="field-group"> + <input id="checkEndpoint" class="span12" type="text" data-ng-model="ecdapp.editRequest.check_endpoint"/> + </div> + </div> + </div> + </div> + + <div class="row-nowrap"> + <div class="span12"> + <div class="form-row"> + <label for="serviceId">*Service ID</label> + <div class="field-group"> + <input id="serviceId" class="span12" type="text" data-ng-model="ecdapp.editRequest.service_id"/> + </div> + </div> + </div> + <div class="span12"> + <div class="form-row"> + <label for="checkInterval">*Interval</label> + <div class="field-group"> + <input id="checkInterval" class="span12" type="text" data-ng-model="ecdapp.editRequest.check_interval"/> + </div> + </div> + </div> + </div> + + <div class="row-nowrap"> + <div class="span12"> + <div class="form-row"> + <label for="serviceAddress">*Address</label> + <div class="field-group"> + <input id="serviceAddress" class="span12" type="text" data-ng-model="ecdapp.editRequest.service_address"/> + </div> + </div> + </div> + <div class="span12"> + <div class="form-row"> + <label for="checkDescription">Description</label> + <div class="field-group"> + <input id="checkDescription" class="span12" type="text" data-ng-model="ecdapp.editRequest.check_description"/> + </div> + </div> + </div> + </div> + + <div class="row-nowrap"> + <div class="span12"> + <div class="form-row"> + <label for="servicePort">*Port</label> + <div class="field-group"> + <input id="servicePort" class="span12" type="text" data-ng-model="ecdapp.editRequest.service_port"/> + </div> + </div> + </div> + <div class="span12"> + <div class="form-row"> + <label for="checkName">Name</label> + <div class="field-group"> + <input id="checkName" class="span12" type="text" data-ng-model="ecdapp.editRequest.check_name"/> + </div> + </div> + </div> + </div> + + </div> + + <div class="b2b-modal-footer ng-scope ng-isolate-scope"> + <div class="cta-button-group in"> + <button class="btn btn-alt btn-small" type="button" + ng-click="ecdapp.registerService(ecdapp.editRequest);"> + Save + </button> + <button class="btn btn-small" type="button" + ng-click="$dismiss('cancel')"> + Cancel + </button> + </div> + </div> + +</script> + +<script type="text/ng-template" id="service_history_popup.html"> + + <div class="b2b-modal-header ng-scope"> + <h2 id="myModalLabel" modal-title="">{{ecdapp.label}}</h2> + <div class="corner-button in"> + <button type="button" class="close" aria-label="Close" + ng-click="$dismiss('cancel')"></button> + </div> + </div> + + <div class="b2b-modal-body ng-scope ng-isolate-scope" tabindex="0" + role="region" aria-label="Modal body content"> + + <div class="row-nowrap"> + <div class="span12"> + <div class="form-row"> + <label for="startDateTime">*Start Date</label> + <div class="field-group"> + <div class="datepicker-container" > + <input id="startDateTime" name="startDateTime" class="span12" type="text" ng-model="ecdapp.startDateTime" b2b-datepicker> + </div> + </div> + </div> + </div> + <div class="span12"> + <div class="form-row"> + <label for="endDateTime">*End Date</label> + <div class="field-group"> + <div class="datepicker-container" > + <input id="endDateTime" name="endDateTime" class="span12" type="text" ng-model="ecdapp.endDateTime" b2b-datepicker> + </div> + </div> + </div> + </div> + <div class="span12"> + <div class="form-row"> + <label for="submit"> </label> + <div class="field-group"> + <button id="submit" + class="btn btn-alt btn-small" + type="submit" + ng-disabled="ecdapp.startDateTime === '' || ecdapp.endDateTime === '' " + ng-click="ecdapp.showHistory();"> + Search + </button> + </div> + </div> + </div> + </div> + + <!-- show progress indicator --> + <div ng-show="ecdapp.isDataLoading"> + <div class="span" style="margin-bottom: 20px;"> + <i class="icon-primary-spinner small" role="img" + aria-label="Please wait while the content loads"></i> Please wait + while the content loads. + </div> + </div> + + <div ng-show="ecdapp.isRequestFailed"> + <span class="ecd-error-message">{{ecdapp.errMsg}}</span> + </div> + + <div ng-hide="ecdapp.isRequestFailed"> + <div b2b-table id="consul-services-history-table" class="b2b-table-div" + table-data="ecdapp.tableData" search-string="ecdapp.searchString" + current-page="ecdapp.currentPageIgnored" + next-sort="ecdapp.nextSortIgnored"> + <table> + <thead b2b-table-row type="header"> + <tr id="th-header-row"> + <th b2b-table-header key="status">Status</th> + <th b2b-table-header key="output">Output</th> + <th b2b-table-header key="notes">Date</th> + </tr> + </thead> + + <tbody b2b-table-row type="body" + row-repeat="rowData in ecdapp.tableData"> + <tr id="tr-rowData"> + <td b2b-table-body ng-bind="rowData.status" /> + <td b2b-table-body ng-bind="rowData.output" /> + <td b2b-table-body ng-bind="rowData.date" /> + </tr> + </tbody> + </table> + </div> + </div> <!--hide--> + + </div> + + <div class="b2b-modal-footer ng-scope ng-isolate-scope"> + <div class="cta-button-group in"> + <button class="btn btn-alt btn-small" type="button" + ng-click="$dismiss();"> + Close + </button> + </div> + </div> + +</script> |