diff --git a/app/docker/models/network.js b/app/docker/models/network.js index e3beb5e1f..32c145efa 100644 --- a/app/docker/models/network.js +++ b/app/docker/models/network.js @@ -10,6 +10,7 @@ export function NetworkViewModel(data) { this.IPAM = data.IPAM; this.Containers = data.Containers; this.Options = data.Options; + this.Ingress = data.Ingress; this.Labels = data.Labels; if (this.Labels && this.Labels['com.docker.compose.project']) { diff --git a/app/docker/services/networkService.js b/app/docker/services/networkService.js index 3804aaeed..ad056ae48 100644 --- a/app/docker/services/networkService.js +++ b/app/docker/services/networkService.js @@ -41,7 +41,6 @@ angular.module('portainer.docker').factory('NetworkService', [ Network.query({ filters: filters }) .$promise.then(function success(data) { var networks = data; - var filteredNetworks = networks .filter(function (network) { if (localNetworks && network.Scope === 'local') { diff --git a/app/docker/views/services/edit/includes/networks.html b/app/docker/views/services/edit/includes/networks.html index 40f4fed2d..aa3060c10 100644 --- a/app/docker/views/services/edit/includes/networks.html +++ b/app/docker/views/services/edit/includes/networks.html @@ -1,26 +1,74 @@
- - + + + +

This service is not connected to any networks.

- + + + - + - + + + +
Name ID IP addressActions
- {{ network.NetworkID }} + + {{ network.Name }} {{ network.Addr }} + {{ network.Id }} + + {{ network.Addr }} + + + + +
+ + +
diff --git a/app/docker/views/services/edit/serviceController.js b/app/docker/views/services/edit/serviceController.js index 2e5907ecf..ef6bd3f8f 100644 --- a/app/docker/views/services/edit/serviceController.js +++ b/app/docker/views/services/edit/serviceController.js @@ -17,6 +17,7 @@ require('./includes/servicelabels.html'); require('./includes/tasks.html'); require('./includes/updateconfig.html'); +import _ from 'lodash-es'; import { PorImageRegistryModel } from 'Docker/models/porImageRegistry'; angular.module('portainer.docker').controller('ServiceController', [ @@ -51,6 +52,7 @@ angular.module('portainer.docker').controller('ServiceController', [ 'EndpointProvider', 'clipboard', 'WebhookHelper', + 'NetworkService', function ( $q, $scope, @@ -82,7 +84,8 @@ angular.module('portainer.docker').controller('ServiceController', [ WebhookService, EndpointProvider, clipboard, - WebhookHelper + WebhookHelper, + NetworkService ) { $scope.state = { updateInProgress: false, @@ -210,6 +213,25 @@ angular.module('portainer.docker').controller('ServiceController', [ $scope.updateMount = function updateMount(service) { updateServiceArray(service, 'ServiceMounts', service.ServiceMounts); }; + + $scope.addNetwork = function addNetwork(service) { + if (!service.Networks) { + service.Networks = []; + } + service.Networks.push({ Editable: true }); + }; + + $scope.removeNetwork = function removeNetwork(service, index) { + var removedElement = service.Networks.splice(index, 1); + if (removedElement && removedElement.length && removedElement[0].Id) { + updateServiceArray(service, 'Networks', service.Networks); + } + }; + + $scope.updateNetwork = function updateNetwork(service) { + updateServiceArray(service, 'Networks', service.Networks); + }; + $scope.addPlacementConstraint = function addPlacementConstraint(service) { service.ServiceConstraints.push({ key: '', operator: '==', value: '' }); updateServiceArray(service, 'ServiceConstraints', service.ServiceConstraints); @@ -370,6 +392,14 @@ angular.module('portainer.docker').controller('ServiceController', [ config.TaskTemplate.ContainerSpec.Image = service.Image; } + if ($scope.hasChanges(service, ['Networks'])) { + config.Networks = _.map( + _.filter(service.Networks, (item) => item.Id && item.Editable), + (item) => ({ Target: item.Id }) + ); + config.TaskTemplate.Networks = config.Networks; + } + config.TaskTemplate.ContainerSpec.Secrets = service.ServiceSecrets ? service.ServiceSecrets.map(SecretHelper.secretConfig) : []; config.TaskTemplate.ContainerSpec.Configs = service.ServiceConfigs ? service.ServiceConfigs.map(ConfigHelper.configConfig) : []; config.TaskTemplate.ContainerSpec.Hosts = service.Hosts ? ServiceHelper.translateHostnameIPToHostsEntries(service.Hosts) : []; @@ -629,11 +659,12 @@ angular.module('portainer.docker').controller('ServiceController', [ configs: apiVersion >= 1.3 ? ConfigService.configs() : [], availableImages: ImageService.images(), availableLoggingDrivers: PluginService.loggingPlugins(apiVersion < 1.25), + availableNetworks: NetworkService.networks(true, true, apiVersion >= 1.25), settings: SettingsService.publicSettings(), webhooks: WebhookService.webhooks(service.Id, EndpointProvider.endpointID()), }); }) - .then(function success(data) { + .then(async function success(data) { $scope.nodes = data.nodes; $scope.configs = data.configs; $scope.secrets = data.secrets; @@ -642,6 +673,36 @@ angular.module('portainer.docker').controller('ServiceController', [ $scope.availableVolumes = data.volumes; $scope.allowBindMounts = data.settings.AllowBindMountsForRegularUsers; $scope.isAdmin = Authentication.isAdmin(); + $scope.availableNetworks = data.availableNetworks; + $scope.swarmNetworks = _.filter($scope.availableNetworks, (network) => network.Scope === 'swarm'); + + const serviceNetworks = _.uniqBy(_.concat($scope.service.Model.Spec.Networks || [], $scope.service.Model.Spec.TaskTemplate.Networks || []), 'Target'); + const networks = _.filter( + _.map(serviceNetworks, ({ Target }) => _.find(data.availableNetworks, { Id: Target })), + Boolean + ); + + if (_.some($scope.service.Ports, (port) => port.PublishMode === 'ingress')) { + const ingressNetwork = _.find($scope.availableNetworks, (network) => network.Ingress); + if (ingressNetwork) { + networks.unshift(ingressNetwork); + } + } + + $scope.service.Networks = await Promise.all( + _.map(networks, async (item) => { + let addr = ''; + if (item.IPAM.Config.length) { + addr = item.IPAM.Config[0].Subnet; + } else { + const network = await NetworkService.network(item.Id); + addr = (network && network.IPAM && network.IPAM.Config && network.IPAM.Config.length && network.IPAM.Config[0].Subnet) || ''; + } + return { Id: item.Id, Name: item.Name, Addr: addr, Editable: !item.Ingress }; + }) + ); + + originalService.Networks = angular.copy($scope.service.Networks); if (data.webhooks.length > 0) { var webhook = data.webhooks[0]; @@ -699,6 +760,11 @@ angular.module('portainer.docker').controller('ServiceController', [ previousServiceValues.push(name); } + $scope.filterNetworks = filterNetworks; + function filterNetworks(networks, current) { + return networks.filter((network) => !network.Ingress && (network.Id === current.Id || $scope.service.Networks.every((serviceNetwork) => network.Id !== serviceNetwork.Id))); + } + function updateServiceArray(service, name) { previousServiceValues.push(name); service.hasChanges = true;