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">
|
<table class="table table-hover nowrap-cells">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th style="width:55px;">
|
||||||
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
|
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
|
||||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||||
<label for="select_all"></label>
|
<label for="select_all"></label>
|
||||||
</span>
|
</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')">
|
<a ng-click="$ctrl.changeOrderBy('Name')">
|
||||||
Name
|
Name
|
||||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
|
<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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<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}">
|
<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))"
|
||||||
<td>
|
ng-class="{active: item.Checked}" network-row-content item="item" parent-ctrl="$ctrl" allow-checkbox="true">
|
||||||
<span class="md-checkbox" ng-if="!$ctrl.offlineMode">
|
</tr>
|
||||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" ng-disabled="$ctrl.disableRemove(item)"/>
|
<tr dir-paginate-end ng-show="item.Expanded" ng-repeat="it in item.Subs" style="background: #d5e8f3;"
|
||||||
<label for="select_{{ $index }}"></label>
|
network-row-content item="it" parent-ctrl="$ctrl">
|
||||||
</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>
|
</tr>
|
||||||
<tr ng-if="!$ctrl.dataset">
|
<tr ng-if="!$ctrl.dataset">
|
||||||
<td colspan="9" class="text-center text-muted">Loading...</td>
|
<td colspan="9" class="text-center text-muted">Loading...</td>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import _ from 'lodash-es';
|
||||||
|
|
||||||
angular.module('portainer.docker')
|
angular.module('portainer.docker')
|
||||||
.controller('NetworksDatatableController', ['$scope', '$controller', 'PREDEFINED_NETWORKS', 'DatatableService',
|
.controller('NetworksDatatableController', ['$scope', '$controller', 'PREDEFINED_NETWORKS', 'DatatableService',
|
||||||
function ($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);
|
return PREDEFINED_NETWORKS.includes(item.Name);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.state = Object.assign(this.state, {
|
||||||
|
expandedItems: []
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not allow PREDEFINED_NETWORKS to be selected
|
* Do not allow PREDEFINED_NETWORKS to be selected
|
||||||
*/
|
*/
|
||||||
|
@ -47,5 +53,26 @@ angular.module('portainer.docker')
|
||||||
}
|
}
|
||||||
this.onSettingsRepeaterChange();
|
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')
|
angular.module('portainer.docker')
|
||||||
.controller('NetworksController', ['$scope', '$state', 'NetworkService', 'Notifications', 'HttpRequestHelper', 'EndpointProvider',
|
.controller('NetworksController', ['$q', '$scope', '$state', 'NetworkService', 'Notifications', 'HttpRequestHelper', 'EndpointProvider', 'AgentService',
|
||||||
function ($scope, $state, NetworkService, Notifications, HttpRequestHelper, EndpointProvider) {
|
function ($q, $scope, $state, NetworkService, Notifications, HttpRequestHelper, EndpointProvider, AgentService) {
|
||||||
|
|
||||||
$scope.removeAction = function (selectedItems) {
|
$scope.removeAction = function (selectedItems) {
|
||||||
var actionCount = selectedItems.length;
|
var actionCount = selectedItems.length;
|
||||||
|
@ -28,13 +30,43 @@ function ($scope, $state, NetworkService, Notifications, HttpRequestHelper, Endp
|
||||||
|
|
||||||
$scope.getNetworks = getNetworks;
|
$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() {
|
function getNetworks() {
|
||||||
NetworkService.networks(true, true, true)
|
const req = {
|
||||||
.then(function success(data) {
|
networks: NetworkService.networks(true, true, true)
|
||||||
$scope.networks = data;
|
};
|
||||||
|
|
||||||
|
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();
|
$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 = [];
|
$scope.networks = [];
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve networks');
|
Notifications.error('Failure', err, 'Unable to retrieve networks');
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue