diff --git a/traffic_portal/app/src/app.js b/traffic_portal/app/src/app.js index ddf01c5093..7575d3c2cf 100644 --- a/traffic_portal/app/src/app.js +++ b/traffic_portal/app/src/app.js @@ -24,8 +24,11 @@ var App = function($urlRouterProvider) { $urlRouterProvider.otherwise('/'); }; + App.$inject = ['$urlRouterProvider']; +agGrid.initialiseAgGridWithAngular1(angular); + var trafficPortal = angular.module('trafficPortal', [ 'config', 'ngAnimate', @@ -42,6 +45,7 @@ var trafficPortal = angular.module('trafficPortal', [ 'angular-loading-bar', 'moment-picker', 'jsonFormatter', + 'agGrid', // public modules require('./modules/public').name, @@ -509,5 +513,3 @@ trafficPortal.factory('authInterceptor', function ($rootScope, $q, $window, $loc trafficPortal.config(function ($httpProvider) { $httpProvider.interceptors.push('authInterceptor'); }); - - diff --git a/traffic_portal/app/src/common/modules/table/_table.scss b/traffic_portal/app/src/common/modules/table/_table.scss index 1c39b488a3..e6a28683e8 100644 --- a/traffic_portal/app/src/common/modules/table/_table.scss +++ b/traffic_portal/app/src/common/modules/table/_table.scss @@ -107,3 +107,74 @@ th.center, td.center { .dt-button.btn-link { text-decoration: underline; } + +/* Table context menus */ +menu[type="contextmenu"] { + display: block; + position: fixed; + background-color: white; + padding: 0; + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + border-radius: 3px; + + ul { + display: block; + clear: both; + font-weight: normal; + line-height: 1.428571429; + white-space: nowrap; + padding: 0; + margin: 0; + + li { + display: block; + clear: both; + + &:hover { + color: #262626; + background-color: #f5f5f5; + } + + a, button { + color: #333333; + text-decoration: none; + padding: 7px 20px; + display: block; + clear: both; + } + + button { + background: transparent; + border: none; + width: 100%; + text-align: left; + + &[disabled] { + color: gray; + background-color: #f5f5f5; + } + } + } + + hr.divider { + margin: 0; + } + } +} + +div.dropdown button.menu-item-button { + color: #333333; + width: 100%; + background: transparent; + border: none; + text-align: inherit; + display: block; + clear: both; + padding: 3px 20px; + margin: 0; + + &:hover { + color: #262626; + background-color: #f5f5f5; + } +} diff --git a/traffic_portal/app/src/common/modules/table/cacheGroupServers/TableCacheGroupServersController.js b/traffic_portal/app/src/common/modules/table/cacheGroupServers/TableCacheGroupServersController.js index 7ce6742fde..8ea50a2f08 100644 --- a/traffic_portal/app/src/common/modules/table/cacheGroupServers/TableCacheGroupServersController.js +++ b/traffic_portal/app/src/common/modules/table/cacheGroupServers/TableCacheGroupServersController.js @@ -19,8 +19,8 @@ var TableCacheGroupsServersController = function(cacheGroup, servers, $controller, $scope, $state, $uibModal, cacheGroupService) { - // extends the TableServersController to inherit common methods - angular.extend(this, $controller('TableServersController', { servers: servers, $scope: $scope })); + // extends the TableParentServersController to inherit common methods + angular.extend(this, $controller('TableParentServersController', { servers: servers, $scope: $scope })); let cacheGroupServersTable; diff --git a/traffic_portal/app/src/common/modules/table/cdnServers/TableCDNServersController.js b/traffic_portal/app/src/common/modules/table/cdnServers/TableCDNServersController.js index f0b8df7cc3..b6a4dca883 100644 --- a/traffic_portal/app/src/common/modules/table/cdnServers/TableCDNServersController.js +++ b/traffic_portal/app/src/common/modules/table/cdnServers/TableCDNServersController.js @@ -19,8 +19,8 @@ var TableCDNServersController = function(cdn, servers, $controller, $scope) { - // extends the TableServersController to inherit common methods - angular.extend(this, $controller('TableServersController', { servers: servers, $scope: $scope })); + // extends the TableParentServersController to inherit common methods + angular.extend(this, $controller('TableParentServersController', { servers: servers, $scope: $scope })); let cdnServersTable; diff --git a/traffic_portal/app/src/common/modules/table/deliveryServiceServers/TableDeliveryServiceServersController.js b/traffic_portal/app/src/common/modules/table/deliveryServiceServers/TableDeliveryServiceServersController.js index b343657792..5d0c0944fb 100644 --- a/traffic_portal/app/src/common/modules/table/deliveryServiceServers/TableDeliveryServiceServersController.js +++ b/traffic_portal/app/src/common/modules/table/deliveryServiceServers/TableDeliveryServiceServersController.js @@ -19,8 +19,8 @@ var TableDeliveryServiceServersController = function(deliveryService, servers, $controller, $scope, $uibModal, deliveryServiceService, serverUtils) { - // extends the TableServersController to inherit common methods - angular.extend(this, $controller('TableServersController', { servers: servers, $scope: $scope })); + // extends the TableParentServersController to inherit common methods + angular.extend(this, $controller('TableParentServersController', { servers: servers, $scope: $scope })); let dsServersTable; diff --git a/traffic_portal/app/src/common/modules/table/physLocationServers/TablePhysLocationServersController.js b/traffic_portal/app/src/common/modules/table/physLocationServers/TablePhysLocationServersController.js index 497eb6c7b3..f329796c57 100644 --- a/traffic_portal/app/src/common/modules/table/physLocationServers/TablePhysLocationServersController.js +++ b/traffic_portal/app/src/common/modules/table/physLocationServers/TablePhysLocationServersController.js @@ -19,8 +19,8 @@ var TablePhysLocationServersController = function(physLocation, servers, $controller, $scope) { - // extends the TableServersController to inherit common methods - angular.extend(this, $controller('TableServersController', { servers: servers, $scope: $scope })); + // extends the TableParentServersController to inherit common methods + angular.extend(this, $controller('TableParentServersController', { servers: servers, $scope: $scope })); let physLocServersTable; diff --git a/traffic_portal/app/src/common/modules/table/profileServers/TableProfileServersController.js b/traffic_portal/app/src/common/modules/table/profileServers/TableProfileServersController.js index dbc2cf72e7..eff77f2189 100644 --- a/traffic_portal/app/src/common/modules/table/profileServers/TableProfileServersController.js +++ b/traffic_portal/app/src/common/modules/table/profileServers/TableProfileServersController.js @@ -19,8 +19,8 @@ var TableProfileServersController = function(profile, servers, $controller, $scope) { - // extends the TableServersController to inherit common methods - angular.extend(this, $controller('TableServersController', { servers: servers, $scope: $scope })); + // extends the TableParentServersController to inherit common methods + angular.extend(this, $controller('TableParentServersController', { servers: servers, $scope: $scope })); let profileServersTable; diff --git a/traffic_portal/app/src/common/modules/table/serverCapabilityServers/TableServerCapabilityServersController.js b/traffic_portal/app/src/common/modules/table/serverCapabilityServers/TableServerCapabilityServersController.js index a6dad82617..1afca893e7 100644 --- a/traffic_portal/app/src/common/modules/table/serverCapabilityServers/TableServerCapabilityServersController.js +++ b/traffic_portal/app/src/common/modules/table/serverCapabilityServers/TableServerCapabilityServersController.js @@ -19,8 +19,8 @@ var TableServerCapabilityServersController = function(serverCapability, servers, $scope, $state, $controller, $uibModal, $window, locationUtils, serverService, messageModel) { - // extends the TableServersController to inherit common methods - angular.extend(this, $controller('TableServersController', { servers: servers, $scope: $scope })); + // extends the TableParentServersController to inherit common methods + angular.extend(this, $controller('TableParentServersController', { servers: servers, $scope: $scope })); var removeCapability = function(serverId) { serverService.removeServerCapability(serverId, serverCapability.name) diff --git a/traffic_portal/app/src/common/modules/table/servers/TableParentServersController.js b/traffic_portal/app/src/common/modules/table/servers/TableParentServersController.js new file mode 100644 index 0000000000..5fb0adc192 --- /dev/null +++ b/traffic_portal/app/src/common/modules/table/servers/TableParentServersController.js @@ -0,0 +1,421 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +var TableParentServersController = function(servers, $scope, $state, $uibModal, $window, dateUtils, locationUtils, serverUtils, cdnService, serverService, statusService, propertiesModel, messageModel) { + + let serversTable; + + var getStatuses = function() { + statusService.getStatuses() + .then(function(result) { + $scope.statuses = result; + }); + }; + + var queueServerUpdates = function(server) { + serverService.queueServerUpdates(server.id) + .then( + function() { + $scope.refresh(); + } + ); + }; + + var clearServerUpdates = function(server) { + serverService.clearServerUpdates(server.id) + .then( + function() { + $scope.refresh(); + } + ); + }; + + var queueCDNServerUpdates = function(cdnId) { + cdnService.queueServerUpdates(cdnId) + .then( + function() { + $scope.refresh(); + } + ); + }; + + var clearCDNServerUpdates = function(cdnId) { + cdnService.clearServerUpdates(cdnId) + .then( + function() { + $scope.refresh(); + } + ); + }; + + var confirmDelete = function(server) { + var params = { + title: 'Delete Server: ' + server.hostName, + key: server.hostName + }; + var modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/delete/dialog.delete.tpl.html', + controller: 'DialogDeleteController', + size: 'md', + resolve: { + params: function () { + return params; + } + } + }); + modalInstance.result.then(function() { + deleteServer(server); + }, function () { + // do nothing + }); + }; + + var deleteServer = function(server) { + serverService.deleteServer(server.id) + .then(function(result) { + messageModel.setMessages(result.alerts, false); + $scope.refresh(); + }); + }; + + var confirmStatusUpdate = function(server) { + var modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/select/status/dialog.select.status.tpl.html', + controller: 'DialogSelectStatusController', + size: 'md', + resolve: { + server: function() { + return server; + }, + statuses: function() { + return $scope.statuses; + } + } + }); + modalInstance.result.then(function(status) { + updateStatus(status, server); + }, function () { + // do nothing + }); + }; + + var updateStatus = function(status, server) { + serverService.updateStatus(server.id, { status: status.id, offlineReason: status.offlineReason }) + .then( + function(result) { + messageModel.setMessages(result.data.alerts, false); + $scope.refresh(); + }, + function(fault) { + messageModel.setMessages(fault.data.alerts, false); + } + ); + }; + + $scope.servers = servers; + + $scope.columns = [ + { "name": "Cache Group", "visible": true, "searchable": true }, + { "name": "CDN", "visible": true, "searchable": true }, + { "name": "Domain", "visible": true, "searchable": true }, + { "name": "Host", "visible": true, "searchable": true }, + { "name": "HTTPS Port", "visible": false, "searchable": false }, + { "name": "ID", "visible": false, "searchable": false }, + { "name": "ILO IP Address", "visible": true, "searchable": true }, + { "name": "ILO IP Gateway", "visible": false, "searchable": false }, + { "name": "ILO IP Netmask", "visible": false, "searchable": false }, + { "name": "ILO Username", "visible": false, "searchable": false }, + { "name": "Interface Name", "visible": false, "searchable": false }, + { "name": "IPv6 Address", "visible": true, "searchable": true }, + { "name": "IPv6 Gateway", "visible": false, "searchable": false }, + { "name": "Last Updated", "visible": false, "searchable": false }, + { "name": "Mgmt IP Address", "visible": false, "searchable": false }, + { "name": "Mgmt IP Gateway", "visible": false, "searchable": false }, + { "name": "Mgmt IP Netmask", "visible": false, "searchable": false }, + { "name": "Network Gateway", "visible": false, "searchable": false }, + { "name": "Network IP", "visible": true, "searchable": true }, + { "name": "Network MTU", "visible": false, "searchable": false }, + { "name": "Network Subnet", "visible": false, "searchable": false }, + { "name": "Offline Reason", "visible": false, "searchable": false }, + { "name": "Phys Location", "visible": true, "searchable": true }, + { "name": "Profile", "visible": true, "searchable": true }, + { "name": "Rack", "visible": false, "searchable": false }, + { "name": "Reval Pending", "visible": false, "searchable": false }, + { "name": "Router Hostname", "visible": false, "searchable": false }, + { "name": "Router Port Name", "visible": false, "searchable": false }, + { "name": "Status", "visible": true, "searchable": true }, + { "name": "TCP Port", "visible": false, "searchable": false }, + { "name": "Type", "visible": true, "searchable": true }, + { "name": "Update Pending", "visible": true, "searchable": true } + ]; + + $scope.contextMenuItems = [ + { + text: 'Open in New Tab', + click: function ($itemScope) { + $window.open('/#!/servers/' + $itemScope.s.id, '_blank'); + } + }, + null, // Divider + { + text: 'Navigate to Server FQDN', + click: function ($itemScope) { + $window.open('http://' + $itemScope.s.hostName + '.' + $itemScope.s.domainName, '_blank'); + } + }, + null, // Divider + { + text: 'Edit', + click: function ($itemScope) { + $scope.editServer($itemScope.s.id); + } + }, + { + text: 'Delete', + click: function ($itemScope) { + confirmDelete($itemScope.s); + } + }, + null, // Divider + { + text: 'Update Status', + click: function ($itemScope) { + confirmStatusUpdate($itemScope.s); + } + }, + { + text: 'Queue Server Updates', + displayed: function ($itemScope) { + return serverUtils.isCache($itemScope.s) && !$itemScope.s.updPending; + }, + click: function ($itemScope) { + queueServerUpdates($itemScope.s); + } + }, + { + text: 'Clear Server Updates', + displayed: function ($itemScope) { + return serverUtils.isCache($itemScope.s) && $itemScope.s.updPending; + }, + click: function ($itemScope) { + clearServerUpdates($itemScope.s); + } + }, + { + text: 'Show Charts', + displayed: function () { + return propertiesModel.properties.servers.charts.show; + }, + hasBottomDivider: function () { + return true; + }, + hasTopDivider: function () { + return true; + }, + click: function ($itemScope) { + $window.open(propertiesModel.properties.servers.charts.baseUrl + $itemScope.s.hostName, '_blank'); + } + }, + { + text: 'Manage Capabilities', + displayed: function ($itemScope) { + return serverUtils.isCache($itemScope.s); + }, + hasTopDivider: function () { + return true; + }, + click: function ($itemScope) { + locationUtils.navigateToPath('/servers/' + $itemScope.s.id + '/capabilities'); + } + }, + { + text: 'Manage Delivery Services', + displayed: function ($itemScope) { + return serverUtils.isEdge($itemScope.s) || serverUtils.isOrigin($itemScope.s); + }, + hasTopDivider: function ($itemScope) { + return !serverUtils.isCache($itemScope.s); + }, + click: function ($itemScope) { + locationUtils.navigateToPath('/servers/' + $itemScope.s.id + '/delivery-services'); + } + }, + { + text: 'View Config Files', + displayed: function ($itemScope) { + return serverUtils.isCache($itemScope.s); + }, + click: function ($itemScope) { + locationUtils.navigateToPath('/servers/' + $itemScope.s.id + '/config-files'); + } + } + ]; + + $scope.editServer = function(id) { + locationUtils.navigateToPath('/servers/' + id); + }; + + $scope.createServer = function() { + locationUtils.navigateToPath('/servers/new'); + }; + + $scope.confirmCDNQueueServerUpdates = function(cdn) { + var params; + if (cdn) { + params = { + title: 'Queue Server Updates: ' + cdn.name, + message: 'Are you sure you want to queue server updates for all ' + cdn.name + ' servers?' + }; + var modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/confirm/dialog.confirm.tpl.html', + controller: 'DialogConfirmController', + size: 'md', + resolve: { + params: function () { + return params; + } + } + }); + modalInstance.result.then(function() { + queueCDNServerUpdates(cdn.id); + }, function () { + // do nothing + }); + } else { + params = { + title: 'Queue Server Updates', + message: "Please select a CDN" + }; + var modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/select/dialog.select.tpl.html', + controller: 'DialogSelectController', + size: 'md', + resolve: { + params: function () { + return params; + }, + collection: function(cdnService) { + return cdnService.getCDNs(); + } + } + }); + modalInstance.result.then(function(cdn) { + queueCDNServerUpdates(cdn.id); + }, function () { + // do nothing + }); + } + }; + + $scope.confirmCDNClearServerUpdates = function(cdn) { + var params; + if (cdn) { + params = { + title: 'Clear Server Updates: ' + cdn.name, + message: 'Are you sure you want to clear server updates for all ' + cdn.name + ' servers?' + }; + var modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/confirm/dialog.confirm.tpl.html', + controller: 'DialogConfirmController', + size: 'md', + resolve: { + params: function () { + return params; + } + } + }); + modalInstance.result.then(function() { + clearCDNServerUpdates(cdn.id); + }, function () { + // do nothing + }); + + + } else { + params = { + title: 'Clear Server Updates', + message: "Please select a CDN" + }; + var modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/select/dialog.select.tpl.html', + controller: 'DialogSelectController', + size: 'md', + resolve: { + params: function () { + return params; + }, + collection: function(cdnService) { + return cdnService.getCDNs(); + } + } + }); + modalInstance.result.then(function(cdn) { + clearCDNServerUpdates(cdn.id); + }, function () { + // do nothing + }); + } + }; + + $scope.refresh = function() { + $state.reload(); // reloads all the resolves for the view + }; + + $scope.toggleVisibility = function(colName) { + const col = serversTable.column(colName + ':name'); + col.visible(!col.visible()); + serversTable.rows().invalidate().draw(); + }; + + $scope.ssh = serverUtils.ssh; + + $scope.isOffline = serverUtils.isOffline; + + $scope.offlineReason = serverUtils.offlineReason; + + $scope.getRelativeTime = dateUtils.getRelativeTime; + + $scope.navigateToPath = locationUtils.navigateToPath; + + var init = function () { + getStatuses(); + }; + init(); + + angular.element(document).ready(function () { + serversTable = $('#serversTable').DataTable({ + "lengthMenu": [[25, 50, 100, -1], [25, 50, 100, "All"]], + "iDisplayLength": 25, + "aaSorting": [], + "columns": $scope.columns, + "initComplete": function(settings, json) { + try { + // need to create the show/hide column checkboxes and bind to the current visibility + $scope.columns = JSON.parse(localStorage.getItem('DataTables_serversTable_/')).columns; + } catch (e) { + console.error("Failure to retrieve required column info from localStorage (key=DataTables_serversTable_/):", e); + } + } + }); + }); + +}; + +TableParentServersController.$inject = ['servers', '$scope', '$state', '$uibModal', '$window', 'dateUtils', 'locationUtils', 'serverUtils', 'cdnService', 'serverService', 'statusService', 'propertiesModel', 'messageModel']; +module.exports = TableParentServersController; diff --git a/traffic_portal/app/src/common/modules/table/servers/TableServersController.js b/traffic_portal/app/src/common/modules/table/servers/TableServersController.js index 4073abeaf8..10ad816f42 100644 --- a/traffic_portal/app/src/common/modules/table/servers/TableServersController.js +++ b/traffic_portal/app/src/common/modules/table/servers/TableServersController.js @@ -17,405 +17,600 @@ * under the License. */ -var TableServersController = function(servers, $scope, $state, $uibModal, $window, dateUtils, locationUtils, serverUtils, cdnService, serverService, statusService, propertiesModel, messageModel) { - - let serversTable; - - var getStatuses = function() { - statusService.getStatuses() - .then(function(result) { - $scope.statuses = result; - }); - }; - - var queueServerUpdates = function(server) { - serverService.queueServerUpdates(server.id) - .then( - function() { - $scope.refresh(); - } - ); - }; - - var clearServerUpdates = function(server) { - serverService.clearServerUpdates(server.id) - .then( - function() { - $scope.refresh(); - } - ); - }; - - var queueCDNServerUpdates = function(cdnId) { - cdnService.queueServerUpdates(cdnId) - .then( - function() { - $scope.refresh(); - } - ); - }; - - var clearCDNServerUpdates = function(cdnId) { - cdnService.clearServerUpdates(cdnId) - .then( - function() { - $scope.refresh(); - } - ); - }; - - var confirmDelete = function(server) { - var params = { - title: 'Delete Server: ' + server.hostName, - key: server.hostName - }; - var modalInstance = $uibModal.open({ - templateUrl: 'common/modules/dialog/delete/dialog.delete.tpl.html', - controller: 'DialogDeleteController', - size: 'md', - resolve: { - params: function () { - return params; - } - } - }); - modalInstance.result.then(function() { - deleteServer(server); - }, function () { - // do nothing - }); - }; - - var deleteServer = function(server) { - serverService.deleteServer(server.id) - .then(function(result) { - messageModel.setMessages(result.alerts, false); - $scope.refresh(); - }); - }; - - var confirmStatusUpdate = function(server) { - var modalInstance = $uibModal.open({ - templateUrl: 'common/modules/dialog/select/status/dialog.select.status.tpl.html', - controller: 'DialogSelectStatusController', - size: 'md', - resolve: { - server: function() { - return server; - }, - statuses: function() { - return $scope.statuses; - } - } - }); - modalInstance.result.then(function(status) { - updateStatus(status, server); - }, function () { - // do nothing - }); - }; - - var updateStatus = function(status, server) { - serverService.updateStatus(server.id, { status: status.id, offlineReason: status.offlineReason }) - .then( - function(result) { - messageModel.setMessages(result.data.alerts, false); - $scope.refresh(); - }, - function(fault) { - messageModel.setMessages(fault.data.alerts, false); - } - ); - }; - - $scope.servers = servers; - - $scope.columns = [ - { "name": "Cache Group", "visible": true, "searchable": true }, - { "name": "CDN", "visible": true, "searchable": true }, - { "name": "Domain", "visible": true, "searchable": true }, - { "name": "Host", "visible": true, "searchable": true }, - { "name": "HTTPS Port", "visible": false, "searchable": false }, - { "name": "ID", "visible": false, "searchable": false }, - { "name": "ILO IP Address", "visible": true, "searchable": true }, - { "name": "ILO IP Gateway", "visible": false, "searchable": false }, - { "name": "ILO IP Netmask", "visible": false, "searchable": false }, - { "name": "ILO Username", "visible": false, "searchable": false }, - { "name": "Interface Name", "visible": false, "searchable": false }, - { "name": "IPv6 Address", "visible": true, "searchable": true }, - { "name": "IPv6 Gateway", "visible": false, "searchable": false }, - { "name": "Last Updated", "visible": false, "searchable": false }, - { "name": "Mgmt IP Address", "visible": false, "searchable": false }, - { "name": "Mgmt IP Gateway", "visible": false, "searchable": false }, - { "name": "Mgmt IP Netmask", "visible": false, "searchable": false }, - { "name": "Network Gateway", "visible": false, "searchable": false }, - { "name": "Network IP", "visible": true, "searchable": true }, - { "name": "Network MTU", "visible": false, "searchable": false }, - { "name": "Network Subnet", "visible": false, "searchable": false }, - { "name": "Offline Reason", "visible": false, "searchable": false }, - { "name": "Phys Location", "visible": true, "searchable": true }, - { "name": "Profile", "visible": true, "searchable": true }, - { "name": "Rack", "visible": false, "searchable": false }, - { "name": "Reval Pending", "visible": false, "searchable": false }, - { "name": "Router Hostname", "visible": false, "searchable": false }, - { "name": "Router Port Name", "visible": false, "searchable": false }, - { "name": "Status", "visible": true, "searchable": true }, - { "name": "TCP Port", "visible": false, "searchable": false }, - { "name": "Type", "visible": true, "searchable": true }, - { "name": "Update Pending", "visible": true, "searchable": true } - ]; - - $scope.contextMenuItems = [ - { - text: 'Open in New Tab', - click: function ($itemScope) { - $window.open('/#!/servers/' + $itemScope.s.id, '_blank'); - } - }, - null, // Divider - { - text: 'Navigate to Server FQDN', - click: function ($itemScope) { - $window.open('http://' + $itemScope.s.hostName + '.' + $itemScope.s.domainName, '_blank'); - } - }, - null, // Divider - { - text: 'Edit', - click: function ($itemScope) { - $scope.editServer($itemScope.s.id); - } - }, - { - text: 'Delete', - click: function ($itemScope) { - confirmDelete($itemScope.s); - } - }, - null, // Divider - { - text: 'Update Status', - click: function ($itemScope) { - confirmStatusUpdate($itemScope.s); - } - }, - { - text: 'Queue Server Updates', - displayed: function ($itemScope) { - return serverUtils.isCache($itemScope.s) && !$itemScope.s.updPending; - }, - click: function ($itemScope) { - queueServerUpdates($itemScope.s); - } - }, - { - text: 'Clear Server Updates', - displayed: function ($itemScope) { - return serverUtils.isCache($itemScope.s) && $itemScope.s.updPending; - }, - click: function ($itemScope) { - clearServerUpdates($itemScope.s); - } - }, - { - text: 'Show Charts', - displayed: function () { - return propertiesModel.properties.servers.charts.show; - }, - hasBottomDivider: function () { - return true; - }, - hasTopDivider: function () { - return true; - }, - click: function ($itemScope) { - $window.open(propertiesModel.properties.servers.charts.baseUrl + $itemScope.s.hostName, '_blank'); - } - }, - { - text: 'Manage Capabilities', - displayed: function ($itemScope) { - return serverUtils.isCache($itemScope.s); - }, - hasTopDivider: function () { - return true; - }, - click: function ($itemScope) { - locationUtils.navigateToPath('/servers/' + $itemScope.s.id + '/capabilities'); - } - }, - { - text: 'Manage Delivery Services', - displayed: function ($itemScope) { - return serverUtils.isEdge($itemScope.s) || serverUtils.isOrigin($itemScope.s); - }, - hasTopDivider: function ($itemScope) { - return !serverUtils.isCache($itemScope.s); - }, - click: function ($itemScope) { - locationUtils.navigateToPath('/servers/' + $itemScope.s.id + '/delivery-services'); - } - }, - { - text: 'View Config Files', - displayed: function ($itemScope) { - return serverUtils.isCache($itemScope.s); - }, - click: function ($itemScope) { - locationUtils.navigateToPath('/servers/' + $itemScope.s.id + '/config-files'); - } - } - ]; - - $scope.editServer = function(id) { - locationUtils.navigateToPath('/servers/' + id); - }; - - $scope.createServer = function() { - locationUtils.navigateToPath('/servers/new'); - }; - - $scope.confirmCDNQueueServerUpdates = function(cdn) { - var params; - if (cdn) { - params = { - title: 'Queue Server Updates: ' + cdn.name, - message: 'Are you sure you want to queue server updates for all ' + cdn.name + ' servers?' - }; - var modalInstance = $uibModal.open({ - templateUrl: 'common/modules/dialog/confirm/dialog.confirm.tpl.html', - controller: 'DialogConfirmController', - size: 'md', - resolve: { - params: function () { - return params; - } - } - }); - modalInstance.result.then(function() { - queueCDNServerUpdates(cdn.id); - }, function () { - // do nothing - }); - } else { - params = { - title: 'Queue Server Updates', - message: "Please select a CDN" - }; - var modalInstance = $uibModal.open({ - templateUrl: 'common/modules/dialog/select/dialog.select.tpl.html', - controller: 'DialogSelectController', - size: 'md', - resolve: { - params: function () { - return params; - }, - collection: function(cdnService) { - return cdnService.getCDNs(); - } - } - }); - modalInstance.result.then(function(cdn) { - queueCDNServerUpdates(cdn.id); - }, function () { - // do nothing - }); - } - }; - - $scope.confirmCDNClearServerUpdates = function(cdn) { - var params; - if (cdn) { - params = { - title: 'Clear Server Updates: ' + cdn.name, - message: 'Are you sure you want to clear server updates for all ' + cdn.name + ' servers?' - }; - var modalInstance = $uibModal.open({ - templateUrl: 'common/modules/dialog/confirm/dialog.confirm.tpl.html', - controller: 'DialogConfirmController', - size: 'md', - resolve: { - params: function () { - return params; - } - } - }); - modalInstance.result.then(function() { - clearCDNServerUpdates(cdn.id); - }, function () { - // do nothing - }); - - - } else { - params = { - title: 'Clear Server Updates', - message: "Please select a CDN" - }; - var modalInstance = $uibModal.open({ - templateUrl: 'common/modules/dialog/select/dialog.select.tpl.html', - controller: 'DialogSelectController', - size: 'md', - resolve: { - params: function () { - return params; - }, - collection: function(cdnService) { - return cdnService.getCDNs(); - } - } - }); - modalInstance.result.then(function(cdn) { - clearCDNServerUpdates(cdn.id); - }, function () { - // do nothing - }); - } - }; - - $scope.refresh = function() { - $state.reload(); // reloads all the resolves for the view - }; - - $scope.toggleVisibility = function(colName) { - const col = serversTable.column(colName + ':name'); - col.visible(!col.visible()); - serversTable.rows().invalidate().draw(); - }; - - $scope.ssh = serverUtils.ssh; - - $scope.isOffline = serverUtils.isOffline; - - $scope.offlineReason = serverUtils.offlineReason; - - $scope.getRelativeTime = dateUtils.getRelativeTime; - - $scope.navigateToPath = locationUtils.navigateToPath; - - var init = function () { - getStatuses(); - }; - init(); - - angular.element(document).ready(function () { - serversTable = $('#serversTable').DataTable({ - "lengthMenu": [[25, 50, 100, -1], [25, 50, 100, "All"]], - "iDisplayLength": 25, - "aaSorting": [], - "columns": $scope.columns, - "initComplete": function(settings, json) { - try { - // need to create the show/hide column checkboxes and bind to the current visibility - $scope.columns = JSON.parse(localStorage.getItem('DataTables_serversTable_/')).columns; - } catch (e) { - console.error("Failure to retrieve required column info from localStorage (key=DataTables_serversTable_/):", e); - } - } - }); - }); +var TableServersController = function(servers, $scope, $state, $uibModal, $window, dateUtils, locationUtils, serverUtils, cdnService, serverService, statusService, propertiesModel, messageModel, userModel, $document) { + /**** Table cell formatters/renderers ****/ + // browserify can't handle classes... + function SSHCellRenderer() {} + SSHCellRenderer.prototype.init = function(params) { + this.eGui = document.createElement("A"); + this.eGui.href = "ssh://" + userModel.user.username + "@" + params.value; + this.eGui.setAttribute("target", "_blank"); + this.eGui.textContent = params.value; + }; + SSHCellRenderer.prototype.getGui = function() {return this.eGui;}; + + function UpdateCellRenderer() {} + UpdateCellRenderer.prototype.init = function(params) { + this.eGui = document.createElement("I"); + this.eGui.setAttribute("aria-hidden", "true"); + this.eGui.setAttribute("title", String(params.value)); + this.eGui.classList.add("fa", "fa-lg"); + if (params.value) { + this.eGui.classList.add("fa-clock-o"); + } else { + this.eGui.classList.add("fa-check"); + } + } + UpdateCellRenderer.prototype.getGui = function() {return this.eGui;}; + + /** + * Gets text with which to file a status tooltip. + * @returns {string | undefined} The offline reason if the server is offline, otherwise nothing. + */ + function offlineReasonTooltip(params) { + if (!params.value || !serverUtils.isOffline(params.value)) { + return; + } + return params.data.offlineReason; + } + + /** + * Formats the contents of a 'lastUpdated' column cell as "relative to now". + */ + function dateCellFormatter(params) { + return dateUtils.getRelativeTime(params.value); + } + + + /**** Constants, scope data, etc. ****/ + + /** The columns of the ag-grid table */ + const columns = [ + { + headerName: "Cache Group", + field: "cachegroup", + hide: false, + }, + { + headerName: "CDN", + field: "cdnName", + hide: false, + }, + { + headerName: "Domain", + field: "domainName", + hide: false, + }, + { + headerName: "Host", + field: "hostName", + hide: false, + }, + { + headerName: "HTTPS Port", + field: "httpsPort", + hide: true, + filter: "agNumberColumnFilter" + }, + { + headerName: "ID", + field: "id", + hide: true, + filter: "agNumberColumnFilter" + }, + { + headerName: "ILO IP Address", + field: "iloIpAddress", + hide: true, + cellRenderer: "sshCellRenderer", + onCellClicked: null + }, + { + headerName: "ILO IP Gateway", + field: "iloIpGateway", + hide: true, + cellRenderer: "sshCellRenderer", + onCellClicked: null + }, + { + headerName: "ILO IP Netmask", + field: "iloIpNetmask", + hide: true, + }, + { + headerName: "ILO Username", + field: "iloUsername", + hide: true, + }, + { + headerName: "Interface Name", + field: "interfaceName", + hide: true, + }, + { + headerName: "IPv6 Address", + field: "ipv6Address", + hide: false, + }, + { + headerName: "IPv6 Gateway", + field: "ipv6Gateway", + hide: true, + }, + { + headerName: "Last Updated", + field: "lastUpdated", + hide: true, + filter: "agDateColumnFilter", + valueFormatter: dateCellFormatter + }, + { + headerName: "Mgmt IP Address", + field: "mgmtIpAddress", + hide: true, + }, + { + headerName: "Mgmt IP Gateway", + field: "mgmtIpGateway", + hide: true, + filter: true, + cellRenderer: "sshCellRenderer", + onCellClicked: null + }, + { + headerName: "Mgmt IP Netmask", + field: "mgmtIpNetmask", + hide: true, + filter: true, + cellRenderer: "sshCellRenderer", + onCellClicked: null + }, + { + headerName: "Network Gateway", + field: "ipGateway", + hide: true, + filter: true, + cellRenderer: "sshCellRenderer", + onCellClicked: null + }, + { + headerName: "Network IP", + field: "ipAddress", + hide: false, + filter: true, + cellRenderer: "sshCellRenderer", + onCellClicked: null + }, + { + headerName: "Network MTU", + field: "interfaceMtu", + hide: true, + filter: "agNumberColumnFilter" + }, + { + headerName: "Network Subnet", + field: "ipNetmask", + hide: true, + }, + { + headerName: "Offline Reason", + field: "offlineReason", + hide: true, + }, + { + headerName: "Phys Location", + field: "physLocation", + hide: true, + }, + { + headerName: "Profile", + field: "profile", + hide: false, + }, + { + headerName: "Rack", + field: "rack", + hide: true, + }, + { + headerName: "Reval Pending", + field: "revalPending", + hide: true, + filter: true, + cellRenderer: "updateCellRenderer" + }, + { + headerName: "Router Hostname", + field: "routerHostName", + hide: true, + }, + { + headerName: "Router Port Name", + field: "routerPortName", + hide: true, + }, + { + headerName: "Status", + field: "status", + hide: false, + tooltip: offlineReasonTooltip + }, + { + headerName: "TCP Port", + field: "tcpPort", + hide: true, + }, + { + headerName: "Type", + field: "type", + hide: false, + }, + { + headerName: "Update Pending", + field: "updPending", + hide: false, + filter: true, + cellRenderer: "updateCellRenderer" + } + ]; + + /** All of the statuses (populated on init). */ + let statuses = []; + + /** All of the servers - lastUpdated fields converted to actual Dates. */ + $scope.servers = servers.map(function(x){x.lastUpdated = x.lastUpdated ? new Date(x.lastUpdated.replace("+00", "Z")) : x.lastUpdated;}); + + /** The base URL to use for constructing links to server charts. */ + $scope.chartsBase = propertiesModel.properties.servers.charts.baseUrl; + + /** The currently selected server - at the moment only used by the context menu */ + $scope.server = { + hostName: "", + domainName: "", + id: -1 + }; + + /** Options, configuration, data and callbacks for the ag-grid table. */ + $scope.gridOptions = { + components: { + sshCellRenderer: SSHCellRenderer, + updateCellRenderer: UpdateCellRenderer + }, + columnDefs: columns, + defaultColDef: { + filter: true, + onCellClicked: function(params) { + locationUtils.navigateToPath('/servers/' + params.data.id); + // Event is outside the digest cycle, so we need to trigger one. + $scope.$apply(); + }, + sortable: true, + resizable: true + }, + rowData: servers, + pagination: true, + rowBuffer: 0, + onColumnResized: function(params) { + localStorage.setItem("servers_table_columns", JSON.stringify($scope.gridOptions.columnApi.getColumnState())); + }, + tooltipShowDelay: 500, + allowContextMenuWithControlKey: true, + preventDefaultOnContextMenu: true, + onCellContextMenu: function(params) { + $scope.showMenu = true; + $scope.menuStyle.left = String(params.event.pageX) + "px"; + $scope.menuStyle.top = String(params.event.pageY) + "px"; + $scope.server = params.data; + $scope.$apply(); + }, + colResizeDefault: "shift" + }; + + /** These three functions are used by the context menu to determine what functionality to provide for a server. */ + $scope.isCache = serverUtils.isCache; + $scope.isEdge = serverUtils.isEdge; + $scope.isOrigin = serverUtils.isOrigin; + + /** Used by the context menu to determine whether or not to include links to server charts. */ + $scope.showCharts = propertiesModel.properties.servers.charts.show; + + /** This is used to position the context menu under the cursor. */ + $scope.menuStyle = { + left: 0, + top: 0, + }; + + /** Controls whether or not the context menu is visible. */ + $scope.showMenu = false; + + + /**** Miscellaneous scope functions ****/ + + /** Reloads all 'resolve'd data for the view. */ + $scope.refresh = function() { + $state.reload(); + }; + + /** Toggles the visibility of a column that has the ID provided as 'col'. */ + $scope.toggleVisibility = function(col) { + const visible = $scope.gridOptions.columnApi.getColumn(col).isVisible(); + $scope.gridOptions.columnApi.setColumnVisible(col, !visible); + }; + + /** Downloads the table as a CSV */ + $scope.exportCSV = function() { + // TODO: figure out how to reconcile clicking on a server taking you to it + // with row selection exports. + const params = { + allColumns: true, + fileName: "servers.csv", + }; + $scope.gridOptions.api.exportDataAsCsv(params); + } + + /**** Context menu functions ****/ + + $scope.queueServerUpdates = function(server, event) { + event.stopPropagation(); + serverService.queueServerUpdates(server.id).then($scope.refresh); + }; + + $scope.clearServerUpdates = function(server, event) { + event.stopPropagation(); + serverService.clearServerUpdates(server.id).then($scope.refresh); + }; + + $scope.confirmDelete = function(server, event) { + event.stopPropagation(); + + const params = { + title: 'Delete Server: ' + server.hostName, + key: server.hostName + }; + const modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/delete/dialog.delete.tpl.html', + controller: 'DialogDeleteController', + size: 'md', + resolve: { + params: function () { + return params; + } + } + }); + modalInstance.result.then( + function() { + serverService.deleteServer(server.id).then( + function(result) { + messageModel.setMessages(result.alerts, false); + $scope.refresh(); + }, + function(err) { + // TODO: use template strings once the build can handle them. + console.error("Error deleting server", server.hostName + "." + server.domainName, "(#" + String(server.id) + "):", err); + } + ); + }, + function() { + // This is just a cancel event from closing the dialog, do nothing. + } + ); + }; + + /** + * updateStatus sets the status of the given server to the given status value. + * + * @param {{id: number, offlineReason?: string}} status The numeric ID of the status to set along with a reason why it was set offline, if applicable. + * @param {{id: number}} server The server (or at least its numeric ID) which will have its status set. + */ + function updateStatus(status, server) { + const params = { + status: status.id, + offlineReason: status.offlineReason + }; + + serverService.updateStatus(server.id, params).then( + function(result) { + messageModel.setMessages(result.data.alerts, false); + $scope.refresh(); + }, + function(fault) { + messageModel.setMessages(fault.data.alerts, false); + } + ); + }; + + $scope.confirmStatusUpdate = function(server, event) { + event.stopPropagation(); + + const modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/select/status/dialog.select.status.tpl.html', + controller: 'DialogSelectStatusController', + size: 'md', + resolve: { + server: function() { + return server; + }, + statuses: function() { + return statuses; + } + } + }); + modalInstance.result.then( + function(status) { + updateStatus(status, server); + }, + function () { + // this is just a cancel event from closing the dialog, do nothing + } + ); + }; + + $scope.confirmCDNQueueServerUpdates = function(cdn) { + let params; + if (cdn) { + params = { + title: 'Queue Server Updates: ' + cdn.name, + message: 'Are you sure you want to queue server updates for all ' + cdn.name + ' servers?' + }; + const modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/confirm/dialog.confirm.tpl.html', + controller: 'DialogConfirmController', + size: 'md', + resolve: { + params: function () { + return params; + } + } + }); + modalInstance.result.then(function() { + cdnService.queueServerUpdates(cdn.id).then($scope.refresh); + }, function () { + // this is just a cancel event from closing the dialog, do nothing + }); + } else { + params = { + title: 'Queue Server Updates', + message: "Please select a CDN" + }; + const modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/select/dialog.select.tpl.html', + controller: 'DialogSelectController', + size: 'md', + resolve: { + params: function () { + return params; + }, + collection: function(cdnService) { + return cdnService.getCDNs(); + } + } + }); + modalInstance.result.then(function(cdn) { + cdnService.queueServerUpdates(cdn.id).then($scope.refresh); + }, function () { + // do nothing + }); + } + }; + + $scope.confirmCDNClearServerUpdates = function(cdn) { + let params; + if (cdn) { + params = { + title: 'Clear Server Updates: ' + cdn.name, + message: 'Are you sure you want to clear server updates for all ' + cdn.name + ' servers?' + }; + const modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/confirm/dialog.confirm.tpl.html', + controller: 'DialogConfirmController', + size: 'md', + resolve: { + params: function () { + return params; + } + } + }); + modalInstance.result.then(function() { + cdnService.clearServerUpdates(cdn.id).then($scope.refresh); + }, function () { + // do nothing + }); + + + } else { + params = { + title: 'Clear Server Updates', + message: "Please select a CDN" + }; + const modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/select/dialog.select.tpl.html', + controller: 'DialogSelectController', + size: 'md', + resolve: { + params: function () { + return params; + }, + collection: function(cdnService) { + return cdnService.getCDNs(); + } + } + }); + modalInstance.result.then(function(cdn) { + cdnService.clearServerUpdates(cdn.id).then($scope.refresh); + }, function () { + // do nothing + }); + } + }; + + + /**** Initialization code, including loading user columns from localstorage ****/ + angular.element(document).ready(function () { + try { + // need to create the show/hide column checkboxes and bind to the current visibility + const colstates = JSON.parse(localStorage.getItem("servers_table_columns")); + if (colstates) { + if (!$scope.gridOptions.columnApi.setColumnState(colstates)) { + console.error("Failed to load stored column state: one or more columns not found"); + } + } else { + $scope.gridOptions.api.sizeColumnsToFit(); + } + } catch (e) { + console.error("Failure to retrieve required column info from localStorage (key=servers_table_columns):", e); + } + + try { + const filterState = JSON.parse(localStorage.getItem("servers_table_filters")); + $scope.gridOptions.api.setFilterModel(filterState); + } catch (e) { + console.error("Failure to load stored filter state:", e); + } + + $scope.gridOptions.api.addEventListener("filterChanged", function() { + localStorage.setItem("servers_table_filters", JSON.stringify($scope.gridOptions.api.getFilterModel())); + }); + + try { + const sortState = JSON.parse(localStorage.getItem("servers_table_sort")); + $scope.gridOptions.api.setSortModel(sortState); + } catch (e) { + console.error("Failure to load stored sort state:", e); + } + + $scope.gridOptions.api.addEventListener("sortChanged", function() { + localStorage.setItem("servers_table_sort", JSON.stringify($scope.gridOptions.api.getSortModel())); + }); + + $scope.gridOptions.api.addEventListener("columnMoved", function() { + localStorage.setItem("servers_table_columns", JSON.stringify($scope.gridOptions.columnApi.getColumnState())); + }); + + $scope.gridOptions.api.addEventListener("columnVisible", function() { + $scope.gridOptions.api.sizeColumnsToFit(); + try { + colStates = $scope.gridOptions.columnApi.getColumnState(); + localStorage.setItem("servers_table_columns", JSON.stringify(colStates)); + } catch (e) { + console.error("Failed to store column defs to local storage:", e); + } + }); + + // clicks outside the context menu will hide it + $document.bind("click", function(e) { + $scope.showMenu = false; + e.stopPropagation(); + $scope.$apply(); + }); + + statusService.getStatuses().then( + function(result) { + statuses = result; + } + ); + }); }; -TableServersController.$inject = ['servers', '$scope', '$state', '$uibModal', '$window', 'dateUtils', 'locationUtils', 'serverUtils', 'cdnService', 'serverService', 'statusService', 'propertiesModel', 'messageModel']; +TableServersController.$inject = ['servers', '$scope', '$state', '$uibModal', '$window', 'dateUtils', 'locationUtils', 'serverUtils', 'cdnService', 'serverService', 'statusService', 'propertiesModel', 'messageModel', "userModel", "$document"]; module.exports = TableServersController; diff --git a/traffic_portal/app/src/common/modules/table/servers/index.js b/traffic_portal/app/src/common/modules/table/servers/index.js index e955714d6e..2b5d9b5900 100644 --- a/traffic_portal/app/src/common/modules/table/servers/index.js +++ b/traffic_portal/app/src/common/modules/table/servers/index.js @@ -18,4 +18,5 @@ */ module.exports = angular.module('trafficPortal.table.servers', []) - .controller('TableServersController', require('./TableServersController')); + .controller('TableServersController', require('./TableServersController')) + .controller('TableParentServersController', require('./TableParentServersController')); diff --git a/traffic_portal/app/src/common/modules/table/servers/table.servers.tpl.html b/traffic_portal/app/src/common/modules/table/servers/table.servers.tpl.html index 9c3dce476f..4d36373aa8 100644 --- a/traffic_portal/app/src/common/modules/table/servers/table.servers.tpl.html +++ b/traffic_portal/app/src/common/modules/table/servers/table.servers.tpl.html @@ -23,7 +23,7 @@
| Cache Group | -CDN | -Domain | -Host | -HTTPS Port | -ID | -ILO IP Address | -ILO IP Gateway | -ILO IP Netmask | -ILO Username | -Interface Name | -IPv6 Address | -IPv6 Gateway | -Last Updated | -Mgmt IP Address | -Mgmt IP Gateway | -Mgmt IP Netmask | -Network Gateway | -Network IP | -Network MTU | -Network Subnet | -Offline Reason | -Phys Location | -Profile | -Rack | -Reval Pending | -Router Hostname | -Router Port Name | -Status | -TCP Port | -Type | -Update Pending | -
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{::s.cachegroup}} | -{{::s.cdnName}} | -{{::s.domainName}} | -{{::s.hostName}} | -{{::s.httpsPort}} | -{{::s.id}} | -{{::s.iloIpAddress}} | -{{::s.iloIpGateway}} | -{{::s.iloIpNetmask}} | -{{::s.iloUsername}} | -{{::s.interfaceName}} | -{{::s.ip6Address}} | -{{::s.ip6Gateway}} | -{{::getRelativeTime(s.lastUpdated)}} | -{{::s.mgmtIpAddress}} | -{{::s.mgmtIpGateway}} | -{{::s.mgmtIpNetmask}} | -{{::s.ipGateway}} | -{{::s.ipAddress}} | -{{::s.interfaceMtu}} | -{{::s.ipNetmask}} | -{{::s.offlineReason}} | -{{::s.physLocation}} | -{{::s.profile}} | -{{::s.rack}} | -- - - | -{{::s.routerHostName}} | -{{::s.routerPortName}} | -- {{::s.status}} - {{::s.status}} - | -{{::s.tcpPort}} | -{{::s.type}} | -- - - | -