diff --git a/app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.html b/app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.html new file mode 100644 index 000000000..5b8b3660e --- /dev/null +++ b/app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.html @@ -0,0 +1,106 @@ +
+ + +
+
+ {{ $ctrl.titleText }} +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + Name + + + + + + Role + + + + + + Engine + + + + + + IP Address + + + + + + Status + + + +
+ + + + + {{ item.Hostname }} + {{ item.Hostname }} + {{ item.Role }}{{ item.EngineVersion }}{{ item.Addr }} + {{ item.Status }} +
Loading...
No node available.
+
+ +
+
+
\ No newline at end of file diff --git a/app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.js b/app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.js new file mode 100644 index 000000000..d18e47ab7 --- /dev/null +++ b/app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.js @@ -0,0 +1,15 @@ +angular.module('portainer.docker').component('macvlanNodesDatatable', { + templateUrl: 'app/docker/components/datatables/macvlan-nodes-datatable/macvlanNodesDatatable.html', + controller: 'GenericDatatableController', + bindings: { + titleText: '@', + titleIcon: '@', + dataset: '<', + tableKey: '@', + orderBy: '@', + reverseOrder: '<', + showIpAddressColumn: '<', + accessToNodeDetails: '<', + state: '=' + } +}); diff --git a/app/docker/components/network-macvlan-form/network-macvlan-form.js b/app/docker/components/network-macvlan-form/network-macvlan-form.js new file mode 100644 index 000000000..f9981f1cd --- /dev/null +++ b/app/docker/components/network-macvlan-form/network-macvlan-form.js @@ -0,0 +1,8 @@ +angular.module('portainer.docker').component('networkMacvlanForm', { + templateUrl: 'app/docker/components/network-macvlan-form/networkMacvlanForm.html', + controller: 'NetworkMacvlanFormController', + bindings: { + data: '=', + applicationState: '<' + } +}); \ No newline at end of file diff --git a/app/docker/components/network-macvlan-form/networkMacvlanForm.html b/app/docker/components/network-macvlan-form/networkMacvlanForm.html new file mode 100644 index 000000000..b104bac9d --- /dev/null +++ b/app/docker/components/network-macvlan-form/networkMacvlanForm.html @@ -0,0 +1,108 @@ +
+
+ Macvlan configuration +
+ +
+ + + To create a MACVLAN network you need to create a configuration, then create the network from this configuration. + +
+
+
+
+
+ + +
+
+ + +
+
+
+
+ + + + +
+ +
+ +
+ +
+
+
+
+
+

+ Parent network card must be specified.

+
+
+
+ + +
+
+
+ +
+
+
+
+
+

+ At least one node must be selected.

+
+
+
+
+ +
+ + +
+ + +
+ +
+ +
+
+ +
+
+
+

+ Select a configuration network.

+
+
+
+ +
+ +
+
\ No newline at end of file diff --git a/app/docker/components/network-macvlan-form/networkMacvlanFormController.js b/app/docker/components/network-macvlan-form/networkMacvlanFormController.js new file mode 100644 index 000000000..5cf4231fb --- /dev/null +++ b/app/docker/components/network-macvlan-form/networkMacvlanFormController.js @@ -0,0 +1,51 @@ +angular.module('portainer.docker') + .controller('NetworkMacvlanFormController', ['$q', 'NodeService', 'NetworkService', 'Notifications', 'StateManager', 'Authentication', + function ($q, NodeService, NetworkService, Notifications, StateManager, Authentication) { + var ctrl = this; + + ctrl.requiredNodeSelection = function () { + if (ctrl.data.Scope !== 'local' || ctrl.data.DatatableState === undefined) { + return false; + } + return ctrl.data.DatatableState.selectedItemCount === 0; + }; + + ctrl.requiredConfigSelection = function () { + if (ctrl.data.Scope !== 'swarm') { + return false; + } + return !ctrl.data.SelectedNetworkConfig; + }; + + function initComponent() { + if (StateManager.getState().application.authentication) { + var userDetails = Authentication.getUserDetails(); + var isAdmin = userDetails.role === 1 ? true : false; + ctrl.isAdmin = isAdmin; + } + var provider = ctrl.applicationState.endpoint.mode.provider; + var apiVersion = ctrl.applicationState.endpoint.apiVersion; + $q.all({ + nodes: provider !== 'DOCKER_SWARM_MODE' || NodeService.nodes(), + networks: NetworkService.networks( + provider === 'DOCKER_STANDALONE' || provider === 'DOCKER_SWARM_MODE', + false, + provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25 + ) + }) + .then(function success(data) { + if (data.nodes !== true) { + ctrl.nodes = data.nodes; + } + ctrl.availableNetworks = data.networks.filter(function (item) { + return item.ConfigOnly === true; + }); + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve informations for macvlan'); + }); + } + + initComponent(); + } + ]); \ No newline at end of file diff --git a/app/docker/components/network-macvlan-form/networkMacvlanFormModel.js b/app/docker/components/network-macvlan-form/networkMacvlanFormModel.js new file mode 100644 index 000000000..10ed60510 --- /dev/null +++ b/app/docker/components/network-macvlan-form/networkMacvlanFormModel.js @@ -0,0 +1,8 @@ +function MacvlanFormData() { + this.Scope = 'local'; + this.SelectedNetworkConfig = ''; + this.DatatableState = { + selectedItems: [] + }; + this.ParentNetworkCard = ''; +} \ No newline at end of file diff --git a/app/docker/models/network.js b/app/docker/models/network.js index 402d4cd96..744f10061 100644 --- a/app/docker/models/network.js +++ b/app/docker/models/network.js @@ -23,4 +23,7 @@ function NetworkViewModel(data) { this.NodeName = data.Portainer.Agent.NodeName; } } -} + + this.ConfigFrom = data.ConfigFrom; + this.ConfigOnly = data.ConfigOnly; +} \ No newline at end of file diff --git a/app/docker/views/networks/create/createNetworkController.js b/app/docker/views/networks/create/createNetworkController.js index 0f6d3f31c..9f5dd6493 100644 --- a/app/docker/views/networks/create/createNetworkController.js +++ b/app/docker/views/networks/create/createNetworkController.js @@ -1,138 +1,207 @@ angular.module('portainer.docker') -.controller('CreateNetworkController', ['$q', '$scope', '$state', 'PluginService', 'Notifications', 'NetworkService', 'LabelHelper', 'Authentication', 'ResourceControlService', 'FormValidator', 'HttpRequestHelper', -function ($q, $scope, $state, PluginService, Notifications, NetworkService, LabelHelper, Authentication, ResourceControlService, FormValidator, HttpRequestHelper) { + .controller('CreateNetworkController', ['$q', '$scope', '$state', 'PluginService', 'Notifications', 'NetworkService', 'LabelHelper', 'Authentication', 'ResourceControlService', 'FormValidator', 'HttpRequestHelper', + function ($q, $scope, $state, PluginService, Notifications, NetworkService, LabelHelper, Authentication, ResourceControlService, FormValidator, HttpRequestHelper) { - $scope.formValues = { - DriverOptions: [], - Subnet: '', - Gateway: '', - Labels: [], - AccessControlData: new AccessControlFormData(), - NodeName: null - }; + $scope.formValues = { + DriverOptions: [], + Subnet: '', + Gateway: '', + IPRange: '', + AuxAddress: '', + Labels: [], + AccessControlData: new AccessControlFormData(), + NodeName: null, + Macvlan: new MacvlanFormData() + }; - $scope.state = { - formValidationError: '', - actionInProgress: false - }; + $scope.state = { + formValidationError: '', + actionInProgress: false + }; - $scope.availableNetworkDrivers = []; + $scope.availableNetworkDrivers = []; - $scope.config = { - Driver: 'bridge', - CheckDuplicate: true, - Internal: false, - // Force IPAM Driver to 'default', should not be required. - // See: https://github.com/docker/docker/issues/25735 - IPAM: { - Driver: 'default', - Config: [] - }, - Labels: {} - }; + $scope.config = { + Driver: 'bridge', + CheckDuplicate: true, + Internal: false, + Attachable: false, + // Force IPAM Driver to 'default', should not be required. + // See: https://github.com/docker/docker/issues/25735 + IPAM: { + Driver: 'default', + Config: [] + }, + Labels: {} + }; - $scope.addDriverOption = function() { - $scope.formValues.DriverOptions.push({ name: '', value: '' }); - }; + $scope.addDriverOption = function () { + $scope.formValues.DriverOptions.push({ + name: '', + value: '' + }); + }; - $scope.removeDriverOption = function(index) { - $scope.formValues.DriverOptions.splice(index, 1); - }; + $scope.removeDriverOption = function (index) { + $scope.formValues.DriverOptions.splice(index, 1); + }; - $scope.addLabel = function() { - $scope.formValues.Labels.push({ key: '', value: ''}); - }; + $scope.addLabel = function () { + $scope.formValues.Labels.push({ + key: '', + value: '' + }); + }; - $scope.removeLabel = function(index) { - $scope.formValues.Labels.splice(index, 1); - }; + $scope.removeLabel = function (index) { + $scope.formValues.Labels.splice(index, 1); + }; - function prepareIPAMConfiguration(config) { - if ($scope.formValues.Subnet) { - var ipamConfig = {}; - ipamConfig.Subnet = $scope.formValues.Subnet; - if ($scope.formValues.Gateway) { - ipamConfig.Gateway = $scope.formValues.Gateway ; + function prepareIPAMConfiguration(config) { + if ($scope.formValues.Subnet) { + var ipamConfig = {}; + ipamConfig.Subnet = $scope.formValues.Subnet; + if ($scope.formValues.Gateway) { + ipamConfig.Gateway = $scope.formValues.Gateway; + } + if ($scope.formValues.IPRange) { + ipamConfig.IPRange = $scope.formValues.IPRange; + } + if ($scope.formValues.AuxAddress) { + ipamConfig.AuxAddress = $scope.formValues.AuxAddress; + } + config.IPAM.Config.push(ipamConfig); + } } - config.IPAM.Config.push(ipamConfig); + + function prepareDriverOptions(config) { + var options = {}; + $scope.formValues.DriverOptions.forEach(function (option) { + options[option.name] = option.value; + }); + config.Options = options; + } + + function prepareLabelsConfig(config) { + config.Labels = LabelHelper.fromKeyValueToLabelHash($scope.formValues.Labels); + } + + function prepareConfiguration() { + var config = angular.copy($scope.config); + prepareIPAMConfiguration(config); + prepareDriverOptions(config); + prepareLabelsConfig(config); + return config; + } + + function modifyNetworkConfigurationForMacvlanConfigOnly(config) { + config.Internal = null; + config.Attachable = null; + config.ConfigOnly = true; + config.Options.parent = $scope.formValues.Macvlan.ParentNetworkCard; + } + + function modifyNetworkConfigurationForMacvlanConfigFrom(config, selectedNetworkConfig) { + config.ConfigFrom = { + Network: selectedNetworkConfig.Name + }; + config.Scope = 'swarm'; + } + + function validateForm(accessControlData, isAdmin) { + $scope.state.formValidationError = ''; + var error = ''; + error = FormValidator.validateAccessControl(accessControlData, isAdmin); + + if (error) { + $scope.state.formValidationError = error; + return false; + } + return true; + } + + function createNetwork(context) { + HttpRequestHelper.setPortainerAgentTargetHeader(context.nodeName); + + $scope.state.actionInProgress = true; + NetworkService.create(context.networkConfiguration) + .then(function success(data) { + var networkIdentifier = data.Id; + var userId = context.userDetails.ID; + return ResourceControlService.applyResourceControl('network', networkIdentifier, userId, context.accessControlData, []); + }) + .then(function success() { + Notifications.success('Network successfully created'); + if (context.reload) { + $state.go('docker.networks', {}, { + reload: true + }); + } + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'An error occured during network creation'); + }) + .finally(function final() { + $scope.state.actionInProgress = false; + }); + } + + $scope.create = function () { + var networkConfiguration = prepareConfiguration(); + var accessControlData = $scope.formValues.AccessControlData; + var userDetails = Authentication.getUserDetails(); + var isAdmin = userDetails.role === 1; + + if (!validateForm(accessControlData, isAdmin)) { + return; + } + + var creationContext = { + nodeName: $scope.formValues.NodeName, + networkConfiguration: networkConfiguration, + userDetails: userDetails, + accessControlData: accessControlData, + reload: true + }; + + if ($scope.config.Driver === 'macvlan') { + if ($scope.formValues.Macvlan.Scope === 'local') { + modifyNetworkConfigurationForMacvlanConfigOnly(networkConfiguration); + } else if ($scope.formValues.Macvlan.Scope === 'swarm') { + var selectedNetworkConfig = $scope.formValues.Macvlan.SelectedNetworkConfig; + modifyNetworkConfigurationForMacvlanConfigFrom(networkConfiguration, selectedNetworkConfig); + creationContext.nodeName = selectedNetworkConfig.NodeName; + } + } + + if ($scope.config.Driver === 'macvlan' && $scope.formValues.Macvlan.Scope === 'local' && + $scope.applicationState.endpoint.mode.agentProxy && $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') { + var selectedNodes = $scope.formValues.Macvlan.DatatableState.selectedItems; + selectedNodes.forEach(function (node, idx) { + creationContext.nodeName = node.Hostname; + creationContext.reload = idx === selectedNodes.length - 1 ? true : false; + createNetwork(creationContext); + }); + } else { + createNetwork(creationContext); + } + }; + + function initView() { + var apiVersion = $scope.applicationState.endpoint.apiVersion; + + PluginService.networkPlugins(apiVersion < 1.25) + .then(function success(data) { + if ($scope.applicationState.endpoint.mode.provider !== 'DOCKER_SWARM_MODE') { + data.splice(data.indexOf('macvlan'), 1); + } + $scope.availableNetworkDrivers = data; + }) + .catch(function error(err) { + Notifications.error('Failure', err, 'Unable to retrieve network drivers'); + }); + } + + initView(); } - } - - function prepareDriverOptions(config) { - var options = {}; - $scope.formValues.DriverOptions.forEach(function (option) { - options[option.name] = option.value; - }); - config.Options = options; - } - - function prepareLabelsConfig(config) { - config.Labels = LabelHelper.fromKeyValueToLabelHash($scope.formValues.Labels); - } - - function prepareConfiguration() { - var config = angular.copy($scope.config); - prepareIPAMConfiguration(config); - prepareDriverOptions(config); - prepareLabelsConfig(config); - return config; - } - - function validateForm(accessControlData, isAdmin) { - $scope.state.formValidationError = ''; - var error = ''; - error = FormValidator.validateAccessControl(accessControlData, isAdmin); - - if (error) { - $scope.state.formValidationError = error; - return false; - } - return true; - } - - $scope.create = function () { - var networkConfiguration = prepareConfiguration(); - var accessControlData = $scope.formValues.AccessControlData; - var userDetails = Authentication.getUserDetails(); - var isAdmin = userDetails.role === 1; - - if (!validateForm(accessControlData, isAdmin)) { - return; - } - - var nodeName = $scope.formValues.NodeName; - HttpRequestHelper.setPortainerAgentTargetHeader(nodeName); - - $scope.state.actionInProgress = true; - NetworkService.create(networkConfiguration) - .then(function success(data) { - var networkIdentifier = data.Id; - var userId = userDetails.ID; - return ResourceControlService.applyResourceControl('network', networkIdentifier, userId, accessControlData, []); - }) - .then(function success() { - Notifications.success('Network successfully created'); - $state.go('docker.networks', {}, {reload: true}); - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'An error occured during network creation'); - }) - .finally(function final() { - $scope.state.actionInProgress = false; - }); - }; - - function initView() { - var apiVersion = $scope.applicationState.endpoint.apiVersion; - - PluginService.networkPlugins(apiVersion < 1.25) - .then(function success(data){ - $scope.availableNetworkDrivers = data; - }) - .catch(function error(err) { - Notifications.error('Failure', err, 'Unable to retrieve network drivers'); - }); - } - - initView(); -}]); + ]); \ No newline at end of file diff --git a/app/docker/views/networks/create/createnetwork.html b/app/docker/views/networks/create/createnetwork.html index 827f725d3..03711c36c 100644 --- a/app/docker/views/networks/create/createnetwork.html +++ b/app/docker/views/networks/create/createnetwork.html @@ -9,37 +9,22 @@
-
+
- -
+ +
-
- Network configuration -
- -
- -
- -
- -
- -
-
-
Driver configuration
-
+
@@ -77,6 +62,38 @@
+ + + +
+
+ Network configuration +
+ +
+ +
+ +
+ +
+ +
+
+ + +
+ +
+ +
+ +
+ +
+
+ +
Advanced configuration
@@ -108,24 +125,37 @@
-
+
-
-
+ +
+
+ + +
+
+ +
Deployment
- +
@@ -138,7 +168,8 @@
- @@ -151,4 +182,4 @@
-
+
\ No newline at end of file diff --git a/assets/css/app.css b/assets/css/app.css index e13a1e66a..2be345ca2 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -626,6 +626,12 @@ ul.sidebar .sidebar-list .sidebar-sublist a.active { box-shadow: 0 3px 10px -2px rgba(161, 170, 166, 0.5); position: relative; } +.boxselector_wrapper label.boxselector_disabled { + background: #CACACA; + border-color: #787878; + color: #787878; + cursor: not-allowed; +} .boxselector_wrapper input[type="radio"]:checked + label { background: #337ab7;