feat(networks): group networks for swarm endpoints (#3028)
* feat(networks): group networks for swarm endpoints * fix(networks): display error on networks with 1 subpull/3086/head
parent
552c897b3b
commit
c12ce5a5c7
|
@ -0,0 +1,27 @@
|
|||
<td ng-if="allowCheckbox">
|
||||
<span class="md-checkbox" ng-if="!parentCtrl.offlineMode">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="parentCtrl.selectItem(item)" ng-disabled="parentCtrl.disableRemove(item)"/>
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ng-if="parentCtrl.itemCanExpand(item)" ng-click="parentCtrl.expandItem(item, !item.Expanded)"><i ng-class="{ 'fas fa-angle-down': item.Expanded, 'fas fa-angle-right': !item.Expanded }" class="space-right" aria-hidden="true"></i></a>
|
||||
</td>
|
||||
<td ng-if="!allowCheckbox"></td>
|
||||
<td>
|
||||
<a ng-if="!parentCtrl.offlineMode" ui-sref="docker.networks.network({ id: item.Id, nodeName: item.NodeName })" title="{{ item.Name }}">{{ item.Name | truncate:40 }}</a>
|
||||
<span ng-if="parentCtrl.offlineMode">{{ item.Name | truncate:40 }}</span>
|
||||
</td>
|
||||
<td>{{ item.StackName ? item.StackName : '-' }}</td>
|
||||
<td>{{ item.Scope }}</td>
|
||||
<td>{{ item.Driver }}</td>
|
||||
<td>{{ item.Attachable }}</td>
|
||||
<td>{{ item.Internal }}</td>
|
||||
<td>{{ item.IPAM.Driver }}</td>
|
||||
<td>{{ item.IPAM.Config[0].Subnet ? item.IPAM.Config[0].Subnet : '-' }}</td>
|
||||
<td>{{ item.IPAM.Config[0].Gateway ? item.IPAM.Config[0].Gateway : '-' }}</td>
|
||||
<td ng-if="parentCtrl.showHostColumn">{{ item.NodeName ? item.NodeName : '-' }}</td>
|
||||
<td ng-if="parentCtrl.showOwnershipColumn">
|
||||
<span>
|
||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }}
|
||||
</span>
|
||||
</td>
|
|
@ -0,0 +1,15 @@
|
|||
angular.module('portainer.docker')
|
||||
.directive('networkRowContent', [function networkRowContent() {
|
||||
var directive = {
|
||||
templateUrl: './networkRowContent.html',
|
||||
restrict: 'A',
|
||||
transclude: true,
|
||||
scope: {
|
||||
item: '<',
|
||||
parentCtrl: '<',
|
||||
allowCheckbox: '<',
|
||||
allowExpand: '<'
|
||||
}
|
||||
};
|
||||
return directive;
|
||||
}]);
|
|
@ -61,11 +61,16 @@
|
|||
<table class="table table-hover nowrap-cells">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<th style="width:55px;">
|
||||
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<a ng-click="$ctrl.expandAll()" ng-if="$ctrl.hasExpandableItems()">
|
||||
<i ng-class="{ 'fas fa-angle-down': $ctrl.state.expandAll, 'fas fa-angle-right': !$ctrl.state.expandAll }" aria-hidden="true"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Name')">
|
||||
Name
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
|
||||
|
@ -145,30 +150,11 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" ng-disabled="$ctrl.disableRemove(item)"/>
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ng-if="!$ctrl.offlineMode" ui-sref="docker.networks.network({ id: item.Id, nodeName: item.NodeName })" title="{{ item.Name }}">{{ item.Name | truncate:40 }}</a>
|
||||
<span ng-if="$ctrl.offlineMode">{{ item.Name | truncate:40 }}</span>
|
||||
</td>
|
||||
<td>{{ item.StackName ? item.StackName : '-' }}</td>
|
||||
<td>{{ item.Scope }}</td>
|
||||
<td>{{ item.Driver }}</td>
|
||||
<td>{{ item.Attachable }}</td>
|
||||
<td>{{ item.Internal }}</td>
|
||||
<td>{{ item.IPAM.Driver }}</td>
|
||||
<td>{{ item.IPAM.Config[0].Subnet ? item.IPAM.Config[0].Subnet : '-' }}</td>
|
||||
<td>{{ item.IPAM.Config[0].Gateway ? item.IPAM.Config[0].Gateway : '-' }}</td>
|
||||
<td ng-if="$ctrl.showHostColumn">{{ item.NodeName ? item.NodeName : '-' }}</td>
|
||||
<td ng-if="$ctrl.showOwnershipColumn">
|
||||
<span>
|
||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }}
|
||||
</span>
|
||||
</td>
|
||||
<tr dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
|
||||
ng-class="{active: item.Checked}" network-row-content item="item" parent-ctrl="$ctrl" allow-checkbox="true">
|
||||
</tr>
|
||||
<tr dir-paginate-end ng-show="item.Expanded" ng-repeat="it in item.Subs" style="background: #d5e8f3;"
|
||||
network-row-content item="it" parent-ctrl="$ctrl">
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="9" class="text-center text-muted">Loading...</td>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
angular.module('portainer.docker')
|
||||
.controller('NetworksDatatableController', ['$scope', '$controller', 'PREDEFINED_NETWORKS', 'DatatableService',
|
||||
function ($scope, $controller, PREDEFINED_NETWORKS, DatatableService) {
|
||||
|
@ -8,6 +10,10 @@ angular.module('portainer.docker')
|
|||
return PREDEFINED_NETWORKS.includes(item.Name);
|
||||
};
|
||||
|
||||
this.state = Object.assign(this.state, {
|
||||
expandedItems: []
|
||||
})
|
||||
|
||||
/**
|
||||
* Do not allow PREDEFINED_NETWORKS to be selected
|
||||
*/
|
||||
|
@ -47,5 +53,26 @@ angular.module('portainer.docker')
|
|||
}
|
||||
this.onSettingsRepeaterChange();
|
||||
};
|
||||
|
||||
this.expandItem = function(item, expanded) {
|
||||
item.Expanded = expanded;
|
||||
};
|
||||
|
||||
this.itemCanExpand = function(item) {
|
||||
return item.Subs.length > 0;
|
||||
}
|
||||
|
||||
this.hasExpandableItems = function() {
|
||||
return _.filter(this.state.filteredDataSet, (item) => this.itemCanExpand(item)).length;
|
||||
};
|
||||
|
||||
this.expandAll = function() {
|
||||
this.state.expandAll = !this.state.expandAll;
|
||||
_.forEach(this.state.filteredDataSet, (item) => {
|
||||
if (this.itemCanExpand(item)) {
|
||||
this.expandItem(item, this.state.expandAll);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
]);
|
|
@ -1,6 +1,8 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
angular.module('portainer.docker')
|
||||
.controller('NetworksController', ['$scope', '$state', 'NetworkService', 'Notifications', 'HttpRequestHelper', 'EndpointProvider',
|
||||
function ($scope, $state, NetworkService, Notifications, HttpRequestHelper, EndpointProvider) {
|
||||
.controller('NetworksController', ['$q', '$scope', '$state', 'NetworkService', 'Notifications', 'HttpRequestHelper', 'EndpointProvider', 'AgentService',
|
||||
function ($q, $scope, $state, NetworkService, Notifications, HttpRequestHelper, EndpointProvider, AgentService) {
|
||||
|
||||
$scope.removeAction = function (selectedItems) {
|
||||
var actionCount = selectedItems.length;
|
||||
|
@ -28,13 +30,43 @@ function ($scope, $state, NetworkService, Notifications, HttpRequestHelper, Endp
|
|||
|
||||
$scope.getNetworks = getNetworks;
|
||||
|
||||
function groupSwarmNetworksManagerNodesFirst(networks, agents) {
|
||||
const getRole = (item) => _.find(agents, (agent) => agent.NodeName === item.NodeName).NodeRole;
|
||||
|
||||
const nonSwarmNetworks = _.remove(networks, (item) => item.Scope !== 'swarm')
|
||||
const grouped = _.toArray(_.groupBy(networks, (item) => item.Id));
|
||||
const sorted = _.map(grouped, (arr) => _.sortBy(arr, (item) => getRole(item)));
|
||||
const arr = _.map(sorted, (a) => {
|
||||
const item = a[0];
|
||||
for (let i = 1; i < a.length; i++) {
|
||||
item.Subs.push(a[i]);
|
||||
}
|
||||
return item;
|
||||
});
|
||||
const res = _.concat(arr, ...nonSwarmNetworks);
|
||||
return res;
|
||||
}
|
||||
|
||||
function getNetworks() {
|
||||
NetworkService.networks(true, true, true)
|
||||
.then(function success(data) {
|
||||
$scope.networks = data;
|
||||
const req = {
|
||||
networks: NetworkService.networks(true, true, true)
|
||||
};
|
||||
|
||||
if ($scope.applicationState.endpoint.mode.agentProxy && $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') {
|
||||
req.agents = AgentService.agents();
|
||||
}
|
||||
|
||||
$q.all(req)
|
||||
.then((data) => {
|
||||
$scope.offlineMode = EndpointProvider.offlineMode();
|
||||
const networks = _.forEach(data.networks, (item) => item.Subs = []);
|
||||
if ($scope.applicationState.endpoint.mode.agentProxy && $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') {
|
||||
$scope.networks = groupSwarmNetworksManagerNodesFirst(data.networks, data.agents);
|
||||
} else {
|
||||
$scope.networks = networks;
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
.catch((err) => {
|
||||
$scope.networks = [];
|
||||
Notifications.error('Failure', err, 'Unable to retrieve networks');
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue