mirror of https://github.com/portainer/portainer
				
				
				
			feat(docker/containers): migrate network tab to react [EE-5210] (#10344)
							parent
							
								
									e92f067e42
								
							
						
					
					
						commit
						2b47b84e5e
					
				|  | @ -20,6 +20,11 @@ import { | |||
|   VolumesTab, | ||||
|   volumesTabUtils, | ||||
| } from '@/react/docker/containers/CreateView/VolumesTab'; | ||||
| import { | ||||
|   networkTabUtils, | ||||
|   NetworkTab, | ||||
|   type NetworkTabValues, | ||||
| } from '@/react/docker/containers/CreateView/NetworkTab'; | ||||
| 
 | ||||
| const ngModule = angular | ||||
|   .module('portainer.docker.react.components.containers', []) | ||||
|  | @ -57,3 +62,11 @@ withFormValidation( | |||
|   ['allowBindMounts'], | ||||
|   volumesTabUtils.validation | ||||
| ); | ||||
| 
 | ||||
| withFormValidation<ComponentProps<typeof NetworkTab>, NetworkTabValues>( | ||||
|   ngModule, | ||||
|   withUIRouter(withReactQuery(NetworkTab)), | ||||
|   'dockerCreateContainerNetworkTab', | ||||
|   [], | ||||
|   networkTabUtils.validation | ||||
| ); | ||||
|  |  | |||
|  | @ -8,12 +8,14 @@ import { buildConfirmButton } from '@@/modals/utils'; | |||
| 
 | ||||
| import { commandsTabUtils } from '@/react/docker/containers/CreateView/CommandsTab'; | ||||
| import { volumesTabUtils } from '@/react/docker/containers/CreateView/VolumesTab'; | ||||
| import { networkTabUtils } from '@/react/docker/containers/CreateView/NetworkTab'; | ||||
| import { ContainerCapabilities, ContainerCapability } from '@/docker/models/containerCapabilities'; | ||||
| import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel'; | ||||
| import { ContainerDetailsViewModel } from '@/docker/models/container'; | ||||
| 
 | ||||
| import './createcontainer.css'; | ||||
| import { envVarsTabUtils } from '@/react/docker/containers/CreateView/EnvVarsTab'; | ||||
| import { getContainers } from '@/react/docker/containers/queries/containers'; | ||||
| 
 | ||||
| angular.module('portainer.docker').controller('CreateContainerController', [ | ||||
|   '$q', | ||||
|  | @ -74,7 +76,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [ | |||
|         selectedGPUs: ['all'], | ||||
|         capabilities: ['compute', 'utility'], | ||||
|       }, | ||||
|       NetworkContainer: null, | ||||
|       Labels: [], | ||||
|       ExtraHosts: [], | ||||
|       MacAddress: '', | ||||
|  | @ -94,10 +95,9 @@ angular.module('portainer.docker').controller('CreateContainerController', [ | |||
|       commands: commandsTabUtils.getDefaultViewModel(), | ||||
|       envVars: envVarsTabUtils.getDefaultViewModel(), | ||||
|       volumes: volumesTabUtils.getDefaultViewModel(), | ||||
|       network: networkTabUtils.getDefaultViewModel(), | ||||
|     }; | ||||
| 
 | ||||
|     $scope.extraNetworks = {}; | ||||
| 
 | ||||
|     $scope.state = { | ||||
|       formValidationError: '', | ||||
|       actionInProgress: false, | ||||
|  | @ -132,6 +132,11 @@ angular.module('portainer.docker').controller('CreateContainerController', [ | |||
|         $scope.formValues.volumes = volumes; | ||||
|       }); | ||||
|     }; | ||||
|     $scope.onNetworkChange = function (network) { | ||||
|       return $scope.$evalAsync(() => { | ||||
|         $scope.formValues.network = network; | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
|     function onAlwaysPullChange(checked) { | ||||
|       return $scope.$evalAsync(() => { | ||||
|  | @ -280,50 +285,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [ | |||
|       config.HostConfig.PortBindings = bindings; | ||||
|     } | ||||
| 
 | ||||
|     function prepareNetworkConfig(config) { | ||||
|       var mode = config.HostConfig.NetworkMode; | ||||
|       var container = $scope.formValues.NetworkContainer; | ||||
|       var containerName = container; | ||||
|       if (container && typeof container === 'object') { | ||||
|         containerName = container.Names[0]; | ||||
|       } | ||||
|       var networkMode = mode; | ||||
|       if (containerName) { | ||||
|         networkMode += ':' + containerName; | ||||
|         config.Hostname = ''; | ||||
|       } | ||||
|       config.HostConfig.NetworkMode = networkMode; | ||||
|       config.MacAddress = $scope.formValues.MacAddress; | ||||
| 
 | ||||
|       config.NetworkingConfig.EndpointsConfig[networkMode] = { | ||||
|         IPAMConfig: { | ||||
|           IPv4Address: $scope.formValues.IPv4, | ||||
|           IPv6Address: $scope.formValues.IPv6, | ||||
|         }, | ||||
|       }; | ||||
| 
 | ||||
|       if (networkMode && _.get($scope.config.NetworkingConfig.EndpointsConfig[networkMode], 'Aliases')) { | ||||
|         var aliases = $scope.config.NetworkingConfig.EndpointsConfig[networkMode].Aliases; | ||||
|         config.NetworkingConfig.EndpointsConfig[networkMode].Aliases = _.filter(aliases, (o) => { | ||||
|           return !_.startsWith($scope.fromContainer.Id, o); | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       var dnsServers = []; | ||||
|       if ($scope.formValues.DnsPrimary) { | ||||
|         dnsServers.push($scope.formValues.DnsPrimary); | ||||
|       } | ||||
|       if ($scope.formValues.DnsSecondary) { | ||||
|         dnsServers.push($scope.formValues.DnsSecondary); | ||||
|       } | ||||
|       config.HostConfig.Dns = dnsServers; | ||||
| 
 | ||||
|       config.HostConfig.ExtraHosts = _.map( | ||||
|         _.filter($scope.formValues.ExtraHosts, (v) => v.value), | ||||
|         'value' | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     function prepareLabels(config) { | ||||
|       var labels = {}; | ||||
|       $scope.formValues.Labels.forEach(function (label) { | ||||
|  | @ -439,8 +400,8 @@ angular.module('portainer.docker').controller('CreateContainerController', [ | |||
|       config = commandsTabUtils.toRequest(config, $scope.formValues.commands); | ||||
|       config = envVarsTabUtils.toRequest(config, $scope.formValues.envVars); | ||||
|       config = volumesTabUtils.toRequest(config, $scope.formValues.volumes); | ||||
|       config = networkTabUtils.toRequest(config, $scope.formValues.network, $scope.fromContainer.Id); | ||||
| 
 | ||||
|       prepareNetworkConfig(config); | ||||
|       prepareImageConfig(config); | ||||
|       preparePortBindings(config); | ||||
|       prepareLabels(config); | ||||
|  | @ -457,70 +418,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [ | |||
|       $scope.config.HostConfig.PortBindings = bindings; | ||||
|     } | ||||
| 
 | ||||
|     $scope.resetNetworkConfig = function () { | ||||
|       $scope.config.NetworkingConfig = { | ||||
|         EndpointsConfig: {}, | ||||
|       }; | ||||
|     }; | ||||
| 
 | ||||
|     function loadFromContainerNetworkConfig(d) { | ||||
|       $scope.config.NetworkingConfig = { | ||||
|         EndpointsConfig: {}, | ||||
|       }; | ||||
|       var networkMode = d.HostConfig.NetworkMode; | ||||
|       if (networkMode === 'default') { | ||||
|         $scope.config.HostConfig.NetworkMode = 'bridge'; | ||||
|         if (!_.find($scope.availableNetworks, { Name: 'bridge' })) { | ||||
|           $scope.config.HostConfig.NetworkMode = 'nat'; | ||||
|         } | ||||
|       } | ||||
|       if ($scope.config.HostConfig.NetworkMode.indexOf('container:') === 0) { | ||||
|         var netContainer = $scope.config.HostConfig.NetworkMode.split(/^container:/)[1]; | ||||
|         $scope.config.HostConfig.NetworkMode = 'container'; | ||||
|         for (var c in $scope.runningContainers) { | ||||
|           if ($scope.runningContainers[c].Id == netContainer) { | ||||
|             $scope.formValues.NetworkContainer = $scope.runningContainers[c]; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       $scope.fromContainerMultipleNetworks = Object.keys(d.NetworkSettings.Networks).length >= 2; | ||||
|       if (d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode]) { | ||||
|         if (d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode].IPAMConfig) { | ||||
|           if (d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode].IPAMConfig.IPv4Address) { | ||||
|             $scope.formValues.IPv4 = d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode].IPAMConfig.IPv4Address; | ||||
|           } | ||||
|           if (d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode].IPAMConfig.IPv6Address) { | ||||
|             $scope.formValues.IPv6 = d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode].IPAMConfig.IPv6Address; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       $scope.config.NetworkingConfig.EndpointsConfig[$scope.config.HostConfig.NetworkMode] = d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode]; | ||||
|       if (Object.keys(d.NetworkSettings.Networks).length > 1) { | ||||
|         var firstNetwork = d.NetworkSettings.Networks[Object.keys(d.NetworkSettings.Networks)[0]]; | ||||
|         $scope.config.NetworkingConfig.EndpointsConfig[$scope.config.HostConfig.NetworkMode] = firstNetwork; | ||||
|         $scope.extraNetworks = angular.copy(d.NetworkSettings.Networks); | ||||
|         delete $scope.extraNetworks[Object.keys(d.NetworkSettings.Networks)[0]]; | ||||
|       } | ||||
|       $scope.formValues.MacAddress = d.Config.MacAddress; | ||||
| 
 | ||||
|       if (d.HostConfig.Dns && d.HostConfig.Dns[0]) { | ||||
|         $scope.formValues.DnsPrimary = d.HostConfig.Dns[0]; | ||||
|         if (d.HostConfig.Dns[1]) { | ||||
|           $scope.formValues.DnsSecondary = d.HostConfig.Dns[1]; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       // ExtraHosts
 | ||||
|       if ($scope.config.HostConfig.ExtraHosts) { | ||||
|         var extraHosts = $scope.config.HostConfig.ExtraHosts; | ||||
|         for (var i = 0; i < extraHosts.length; i++) { | ||||
|           var host = extraHosts[i]; | ||||
|           $scope.formValues.ExtraHosts.push({ value: host }); | ||||
|         } | ||||
|         $scope.config.HostConfig.ExtraHosts = []; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     function loadFromContainerLabels() { | ||||
|       for (var l in $scope.config.Labels) { | ||||
|         if ({}.hasOwnProperty.call($scope.config.Labels, l)) { | ||||
|  | @ -640,16 +537,14 @@ angular.module('portainer.docker').controller('CreateContainerController', [ | |||
| 
 | ||||
|           $scope.fromContainer = fromContainer; | ||||
|           $scope.state.mode = 'duplicate'; | ||||
|           $scope.config = ContainerHelper.configFromContainer(fromContainer.Model); | ||||
|           $scope.config = ContainerHelper.configFromContainer(angular.copy(d)); | ||||
| 
 | ||||
|           $scope.formValues.commands = commandsTabUtils.toViewModel(d); | ||||
|           $scope.formValues.envVars = envVarsTabUtils.toViewModel(d); | ||||
|           $scope.formValues.volumes = volumesTabUtils.toViewModel(d); | ||||
|           $scope.formValues.network = networkTabUtils.toViewModel(d, $scope.availableNetworks, $scope.runningContainers); | ||||
| 
 | ||||
|           loadFromContainerPortBindings(d); | ||||
| 
 | ||||
|           loadFromContainerNetworkConfig(d); | ||||
| 
 | ||||
|           loadFromContainerLabels(d); | ||||
|           loadFromContainerDevices(d); | ||||
|           loadFromContainerDeviceRequests(d); | ||||
|  | @ -691,11 +586,8 @@ angular.module('portainer.docker').controller('CreateContainerController', [ | |||
|         .catch(function error(err) { | ||||
|           Notifications.error('Failure', err, 'Unable to retrieve networks'); | ||||
|         }); | ||||
| 
 | ||||
|       Container.query( | ||||
|         {}, | ||||
|         function (d) { | ||||
|           var containers = d; | ||||
|       getContainers(endpoint.Id) | ||||
|         .then((containers) => { | ||||
|           $scope.runningContainers = containers; | ||||
|           $scope.gpuUseAll = _.get($scope, 'endpoint.Snapshots[0].GpuUseAll', false); | ||||
|           $scope.gpuUseList = _.get($scope, 'endpoint.Snapshots[0].GpuUseList', []); | ||||
|  | @ -706,11 +598,10 @@ angular.module('portainer.docker').controller('CreateContainerController', [ | |||
|             $scope.fromContainer = {}; | ||||
|             $scope.formValues.capabilities = $scope.areContainerCapabilitiesEnabled ? new ContainerCapabilities() : []; | ||||
|           } | ||||
|         }, | ||||
|         function (e) { | ||||
|         }) | ||||
|         .catch((e) => { | ||||
|           Notifications.error('Failure', e, 'Unable to retrieve running containers'); | ||||
|         } | ||||
|       ); | ||||
|         }); | ||||
| 
 | ||||
|       SystemService.info() | ||||
|         .then(function success(data) { | ||||
|  | @ -928,11 +819,11 @@ angular.module('portainer.docker').controller('CreateContainerController', [ | |||
|       } | ||||
| 
 | ||||
|       function connectToExtraNetworks(newContainerId) { | ||||
|         if (!$scope.extraNetworks) { | ||||
|         if (!$scope.formValues.network.extraNetworks) { | ||||
|           return $q.when(); | ||||
|         } | ||||
| 
 | ||||
|         var connectionPromises = _.forOwn($scope.extraNetworks, function (network, networkName) { | ||||
|         var connectionPromises = _.forOwn($scope.formValues.network.extraNetworks, function (network, networkName) { | ||||
|           if (_.has(network, 'Aliases')) { | ||||
|             var aliases = _.filter(network.Aliases, (o) => { | ||||
|               return !_.startsWith($scope.fromContainer.Id, o); | ||||
|  |  | |||
|  | @ -202,7 +202,7 @@ | |||
|           <li ng-if="areContainerCapabilitiesEnabled" class="interactive"><a data-target="#container-capabilities" data-toggle="tab">Capabilities</a></li> | ||||
|         </ul> | ||||
|         <!-- tab-content --> | ||||
|         <div class="form-horizontal"> | ||||
|         <div class="form-horizontal" ng-if="state.containerIsLoaded"> | ||||
|           <div class="tab-content"> | ||||
|             <!-- tab-command --> | ||||
|             <div class="tab-pane active" id="command"> | ||||
|  | @ -220,122 +220,10 @@ | |||
|               </docker-create-container-volumes-tab> | ||||
|             </div> | ||||
| 
 | ||||
|             <!-- tab-network --> | ||||
|             <div class="tab-pane" id="network"> | ||||
|               <form class="form-horizontal" style="margin-top: 15px"> | ||||
|                 <div class="form-group" ng-if="globalNetworkCount === 0 && applicationState.endpoint.mode.provider !== 'DOCKER_SWARM_MODE'"> | ||||
|                   <div class="col-sm-12"> | ||||
|                     <span class="small text-muted">You don't have any shared networks. Head over to the <a ui-sref="docker.networks">networks view</a> to create one.</span> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <!-- network-input --> | ||||
|                 <div class="form-group"> | ||||
|                   <label for="container_network" class="col-sm-2 col-lg-1 control-label text-left">Network</label> | ||||
|                   <div class="col-sm-9"> | ||||
|                     <select | ||||
|                       class="form-control" | ||||
|                       ng-options="net.Name as net.Name for net in availableNetworks" | ||||
|                       ng-model="config.HostConfig.NetworkMode" | ||||
|                       id="container_network" | ||||
|                       ng-change="resetNetworkConfig()" | ||||
|                     > | ||||
|                       <option selected disabled hidden value="">Select a network</option> | ||||
|                     </select> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <!-- !network-input --> | ||||
|                 <!-- container-name-input --> | ||||
|                 <div class="form-group" ng-if="config.HostConfig.NetworkMode == 'container'"> | ||||
|                   <label for="container_network_container" class="col-sm-2 col-lg-1 control-label text-left">Container</label> | ||||
|                   <div class="col-sm-9"> | ||||
|                     <select ng-options="container|containername for container in runningContainers" class="form-control" ng-model="formValues.NetworkContainer"> | ||||
|                       <option selected disabled hidden value="">Select a container</option> | ||||
|                     </select> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <!-- !container-name-input --> | ||||
|                 <!-- hostname-input --> | ||||
|                 <div class="form-group"> | ||||
|                   <label for="container_hostname" class="col-sm-2 col-lg-1 control-label text-left">Hostname</label> | ||||
|                   <div class="col-sm-9"> | ||||
|                     <input type="text" class="form-control" ng-model="config.Hostname" id="container_hostname" placeholder="e.g. web01" /> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <!-- !hostname-input --> | ||||
|                 <!-- domainname-input --> | ||||
|                 <div class="form-group"> | ||||
|                   <label for="container_domainname" class="col-sm-2 col-lg-1 control-label text-left">Domain Name</label> | ||||
|                   <div class="col-sm-9"> | ||||
|                     <input type="text" class="form-control" ng-model="config.Domainname" id="container_domainname" placeholder="e.g. example.com" /> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <!-- !domainname --> | ||||
|                 <!-- mac-address-input --> | ||||
|                 <div class="form-group"> | ||||
|                   <label for="container_macaddress" class="col-sm-2 col-lg-1 control-label text-left">Mac Address</label> | ||||
|                   <div class="col-sm-9"> | ||||
|                     <input type="text" class="form-control" ng-model="formValues.MacAddress" id="container_macaddress" placeholder="e.g. 12-34-56-78-9a-bc" /> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <!-- !mac-address-input --> | ||||
|                 <!-- ipv4-input --> | ||||
|                 <div class="form-group"> | ||||
|                   <label for="container_ipv4" class="col-sm-2 col-lg-1 control-label text-left">IPv4 Address</label> | ||||
|                   <div class="col-sm-9"> | ||||
|                     <input type="text" class="form-control" ng-model="formValues.IPv4" id="container_ipv4" placeholder="e.g. 172.20.0.7" /> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <!-- !ipv4-input --> | ||||
|                 <!-- ipv6-input --> | ||||
|                 <div class="form-group"> | ||||
|                   <label for="container_ipv6" class="col-sm-2 col-lg-1 control-label text-left">IPv6 Address</label> | ||||
|                   <div class="col-sm-9"> | ||||
|                     <input type="text" class="form-control" ng-model="formValues.IPv6" id="container_ipv6" placeholder="e.g. a:b:c:d::1234" /> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <!-- !ipv6-input --> | ||||
|                 <!-- dns-primary-input --> | ||||
|                 <div class="form-group"> | ||||
|                   <label for="container_dns_primary" class="col-sm-2 col-lg-1 control-label text-left">Primary DNS Server</label> | ||||
|                   <div class="col-sm-9"> | ||||
|                     <input type="text" class="form-control" ng-model="formValues.DnsPrimary" id="container_dns_primary" placeholder="e.g. 1.1.1.1, 2606:4700:4700::1111" /> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <!-- !dns-primary-input --> | ||||
|                 <!-- dns-secondary-input --> | ||||
|                 <div class="form-group"> | ||||
|                   <label for="container_dns_secondary" class="col-sm-2 col-lg-1 control-label text-left">Secondary DNS Server</label> | ||||
|                   <div class="col-sm-9"> | ||||
|                     <input type="text" class="form-control" ng-model="formValues.DnsSecondary" id="container_dns_secondary" placeholder="e.g. 1.0.0.1, 2606:4700:4700::1001" /> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 <!-- !dns-secondary-input --> | ||||
|                 <!-- extra-hosts-variables --> | ||||
|                 <div class="form-group"> | ||||
|                   <div class="col-sm-12" style="margin-top: 5px"> | ||||
|                     <label class="control-label text-left">Hosts file entries</label> | ||||
|                     <span class="label label-default interactive" style="margin-left: 10px" ng-click="addExtraHost()"> | ||||
|                       <pr-icon icon="'plus'" mode="'alt'"></pr-icon> add additional entry | ||||
|                     </span> | ||||
|                   </div> | ||||
|                   <!-- extra-hosts-input-list --> | ||||
|                   <div class="col-sm-12 form-inline" style="margin-top: 10px"> | ||||
|                     <div ng-repeat="variable in formValues.ExtraHosts" style="margin-top: 2px"> | ||||
|                       <div class="input-group col-sm-5 input-group-sm"> | ||||
|                         <span class="input-group-addon">value</span> | ||||
|                         <input type="text" class="form-control" ng-model="variable.value" placeholder="e.g. host:IP" /> | ||||
|                       </div> | ||||
|                       <button class="btn btn-light" type="button" ng-click="removeExtraHost($index)"> | ||||
|                         <pr-icon icon="'trash-2'" class-name="'icon-secondary icon-md'"></pr-icon> | ||||
|                       </button> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                   <!-- !extra-hosts-input-list --> | ||||
|                 </div> | ||||
|                 <!-- !extra-hosts-variables --> | ||||
|               </form> | ||||
|               <docker-create-container-network-tab values="formValues.network" on-change="(onNetworkChange)"> </docker-create-container-network-tab> | ||||
|             </div> | ||||
|             <!-- !tab-network --> | ||||
| 
 | ||||
|             <!-- tab-labels --> | ||||
|             <div class="tab-pane" id="labels"> | ||||
|               <form class="form-horizontal" style="margin-top: 15px"> | ||||
|  |  | |||
|  | @ -0,0 +1,36 @@ | |||
| import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; | ||||
| 
 | ||||
| import { Option, PortainerSelect } from '@@/form-components/PortainerSelect'; | ||||
| 
 | ||||
| import { useContainers } from '../../queries/containers'; | ||||
| import { ContainerStatus } from '../../types'; | ||||
| 
 | ||||
| export function ContainerSelector({ | ||||
|   onChange, | ||||
|   value, | ||||
| }: { | ||||
|   value: string; | ||||
|   onChange: (value: string) => void; | ||||
| }) { | ||||
|   const environmentId = useEnvironmentId(); | ||||
| 
 | ||||
|   const containersQuery = useContainers<Array<Option<string>>>(environmentId, { | ||||
|     filters: { status: [ContainerStatus.Running] }, | ||||
|     select(containers) { | ||||
|       return containers.map((n) => { | ||||
|         const name = n.Names[0]; | ||||
| 
 | ||||
|         return { label: name, value: name }; | ||||
|       }); | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   return ( | ||||
|     <PortainerSelect | ||||
|       value={value} | ||||
|       onChange={onChange} | ||||
|       options={containersQuery.data || []} | ||||
|       isLoading={containersQuery.isLoading} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,139 @@ | |||
| import { FormikErrors } from 'formik'; | ||||
| import { useState } from 'react'; | ||||
| 
 | ||||
| import { FormControl } from '@@/form-components/FormControl'; | ||||
| import { Input } from '@@/form-components/Input'; | ||||
| import { InputList, ItemProps } from '@@/form-components/InputList'; | ||||
| import { InputGroup } from '@@/form-components/InputGroup'; | ||||
| import { FormError } from '@@/form-components/FormError'; | ||||
| 
 | ||||
| import { NetworkSelector } from '../../components/NetworkSelector'; | ||||
| 
 | ||||
| import { CONTAINER_MODE, Values } from './types'; | ||||
| import { ContainerSelector } from './ContainerSelector'; | ||||
| 
 | ||||
| export function NetworkTab({ | ||||
|   values: initialValues, | ||||
|   onChange, | ||||
|   errors, | ||||
| }: { | ||||
|   values: Values; | ||||
|   onChange(values: Values): void; | ||||
|   errors?: FormikErrors<Values>; | ||||
| }) { | ||||
|   const [values, setControlledValues] = useState(initialValues); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="mt-3"> | ||||
|       <FormControl label="Network" errors={errors?.networkMode}> | ||||
|         <NetworkSelector | ||||
|           value={values.networkMode} | ||||
|           additionalOptions={[{ label: 'Container', value: CONTAINER_MODE }]} | ||||
|           onChange={(networkMode) => handleChange({ networkMode })} | ||||
|         /> | ||||
|       </FormControl> | ||||
| 
 | ||||
|       {values.networkMode === CONTAINER_MODE && ( | ||||
|         <FormControl label="Container" errors={errors?.container}> | ||||
|           <ContainerSelector | ||||
|             value={values.container} | ||||
|             onChange={(container) => handleChange({ container })} | ||||
|           /> | ||||
|         </FormControl> | ||||
|       )} | ||||
| 
 | ||||
|       <FormControl label="Hostname" errors={errors?.hostname}> | ||||
|         <Input | ||||
|           value={values.hostname} | ||||
|           onChange={(e) => handleChange({ hostname: e.target.value })} | ||||
|           placeholder="e.g. web01" | ||||
|         /> | ||||
|       </FormControl> | ||||
| 
 | ||||
|       <FormControl label="Domain Name" errors={errors?.domain}> | ||||
|         <Input | ||||
|           value={values.domain} | ||||
|           onChange={(e) => handleChange({ domain: e.target.value })} | ||||
|           placeholder="e.g. example.com" | ||||
|         /> | ||||
|       </FormControl> | ||||
| 
 | ||||
|       <FormControl label="MAC Address" errors={errors?.macAddress}> | ||||
|         <Input | ||||
|           value={values.macAddress} | ||||
|           onChange={(e) => handleChange({ macAddress: e.target.value })} | ||||
|           placeholder="e.g. 12-34-56-78-9a-bc" | ||||
|         /> | ||||
|       </FormControl> | ||||
| 
 | ||||
|       <FormControl label="IPv4 Address" errors={errors?.ipv4Address}> | ||||
|         <Input | ||||
|           value={values.ipv4Address} | ||||
|           onChange={(e) => handleChange({ ipv4Address: e.target.value })} | ||||
|           placeholder="e.g. 172.20.0.7" | ||||
|         /> | ||||
|       </FormControl> | ||||
| 
 | ||||
|       <FormControl label="IPv6 Address" errors={errors?.ipv6Address}> | ||||
|         <Input | ||||
|           value={values.ipv6Address} | ||||
|           onChange={(e) => handleChange({ ipv6Address: e.target.value })} | ||||
|           placeholder="e.g. a:b:c:d::1234" | ||||
|         /> | ||||
|       </FormControl> | ||||
| 
 | ||||
|       <FormControl label="Primary DNS Server" errors={errors?.primaryDns}> | ||||
|         <Input | ||||
|           value={values.primaryDns} | ||||
|           onChange={(e) => handleChange({ primaryDns: e.target.value })} | ||||
|           placeholder="e.g. 1.1.1.1, 2606:4700:4700::1111" | ||||
|         /> | ||||
|       </FormControl> | ||||
| 
 | ||||
|       <FormControl label="Secondary DNS Server" errors={errors?.secondaryDns}> | ||||
|         <Input | ||||
|           value={values.secondaryDns} | ||||
|           onChange={(e) => handleChange({ secondaryDns: e.target.value })} | ||||
|           placeholder="e.g. 1.0.0.1, 2606:4700:4700::1001" | ||||
|         /> | ||||
|       </FormControl> | ||||
| 
 | ||||
|       <InputList | ||||
|         label="Hosts file entries" | ||||
|         value={values.hostsFileEntries} | ||||
|         onChange={(hostsFileEntries) => handleChange({ hostsFileEntries })} | ||||
|         errors={errors?.hostsFileEntries} | ||||
|         item={HostsFileEntryItem} | ||||
|       /> | ||||
|     </div> | ||||
|   ); | ||||
| 
 | ||||
|   function handleChange(newValues: Partial<Values>) { | ||||
|     onChange({ ...values, ...newValues }); | ||||
|     setControlledValues((values) => ({ ...values, ...newValues })); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function HostsFileEntryItem({ | ||||
|   item, | ||||
|   onChange, | ||||
|   disabled, | ||||
|   error, | ||||
|   readOnly, | ||||
| }: ItemProps<string>) { | ||||
|   return ( | ||||
|     <div> | ||||
|       <InputGroup> | ||||
|         <InputGroup.Addon>value</InputGroup.Addon> | ||||
|         <Input | ||||
|           value={item} | ||||
|           onChange={(e) => onChange(e.target.value)} | ||||
|           disabled={disabled} | ||||
|           readOnly={readOnly} | ||||
|         /> | ||||
|       </InputGroup> | ||||
| 
 | ||||
|       {error && <FormError>{error}</FormError>} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
|  | @ -0,0 +1,14 @@ | |||
| import { validation } from './validation'; | ||||
| import { toRequest } from './toRequest'; | ||||
| import { toViewModel, getDefaultViewModel } from './toViewModel'; | ||||
| 
 | ||||
| export { NetworkTab } from './NetworkTab'; | ||||
| 
 | ||||
| export { type Values as NetworkTabValues } from './types'; | ||||
| 
 | ||||
| export const networkTabUtils = { | ||||
|   toRequest, | ||||
|   toViewModel, | ||||
|   validation, | ||||
|   getDefaultViewModel, | ||||
| }; | ||||
|  | @ -0,0 +1,42 @@ | |||
| import { CreateContainerRequest } from '../types'; | ||||
| 
 | ||||
| import { CONTAINER_MODE, Values } from './types'; | ||||
| 
 | ||||
| export function toRequest( | ||||
|   oldConfig: CreateContainerRequest, | ||||
|   values: Values, | ||||
|   fromContainerId: string | ||||
| ): CreateContainerRequest { | ||||
|   let mode = values.networkMode; | ||||
|   let hostName = values.hostname; | ||||
|   if (mode === CONTAINER_MODE && values.container) { | ||||
|     mode += `:${values.container}`; | ||||
|     hostName = ''; | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     ...oldConfig, | ||||
|     Hostname: hostName, | ||||
|     MacAddress: values.macAddress, | ||||
|     HostConfig: { | ||||
|       ...oldConfig.HostConfig, | ||||
|       NetworkMode: mode, | ||||
|       Dns: [values.primaryDns, values.secondaryDns].filter((d) => d), | ||||
|       ExtraHosts: values.hostsFileEntries, | ||||
|     }, | ||||
|     NetworkingConfig: { | ||||
|       ...oldConfig.NetworkingConfig, | ||||
|       EndpointsConfig: { | ||||
|         [mode]: { | ||||
|           IPAMConfig: { | ||||
|             IPv4Address: values.ipv4Address, | ||||
|             IPv6Address: values.ipv6Address, | ||||
|           }, | ||||
|           Aliases: oldConfig.NetworkingConfig.EndpointsConfig?.[ | ||||
|             mode | ||||
|           ]?.Aliases?.filter((al) => !fromContainerId.startsWith(al)), | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|   }; | ||||
| } | ||||
|  | @ -0,0 +1,102 @@ | |||
| import { DockerNetwork } from '@/react/docker/networks/types'; | ||||
| 
 | ||||
| import { ContainerJSON } from '../../queries/container'; | ||||
| import { DockerContainer } from '../../types'; | ||||
| 
 | ||||
| import { CONTAINER_MODE, Values } from './types'; | ||||
| 
 | ||||
| export function getDefaultViewModel(hasBridgeNetwork: boolean) { | ||||
|   return { | ||||
|     networkMode: hasBridgeNetwork ? 'bridge' : 'nat', | ||||
|     hostname: '', | ||||
|     domain: '', | ||||
|     macAddress: '', | ||||
|     ipv4Address: '', | ||||
|     ipv6Address: '', | ||||
|     primaryDns: '', | ||||
|     secondaryDns: '', | ||||
|     hostsFileEntries: [], | ||||
|     container: '', | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function toViewModel( | ||||
|   config: ContainerJSON, | ||||
|   networks: Array<DockerNetwork>, | ||||
|   runningContainers: Array<DockerContainer> = [] | ||||
| ): Values { | ||||
|   const dns = config.HostConfig?.Dns; | ||||
|   const [primaryDns = '', secondaryDns = ''] = dns || []; | ||||
| 
 | ||||
|   const hostsFileEntries = config.HostConfig?.ExtraHosts || []; | ||||
| 
 | ||||
|   const [networkMode, container = ''] = getNetworkMode( | ||||
|     config, | ||||
|     networks, | ||||
|     runningContainers | ||||
|   ); | ||||
| 
 | ||||
|   const networkSettings = config.NetworkSettings?.Networks?.[networkMode]; | ||||
|   let ipv4Address = ''; | ||||
|   let ipv6Address = ''; | ||||
|   if (networkSettings && networkSettings.IPAMConfig) { | ||||
|     ipv4Address = networkSettings.IPAMConfig.IPv4Address || ''; | ||||
|     ipv6Address = networkSettings.IPAMConfig.IPv6Address || ''; | ||||
|   } | ||||
| 
 | ||||
|   const macAddress = networkSettings?.MacAddress || ''; | ||||
| 
 | ||||
|   return { | ||||
|     networkMode, | ||||
|     hostname: config.Config?.Hostname || '', | ||||
|     domain: config.Config?.Domainname || '', | ||||
|     macAddress, | ||||
|     ipv4Address, | ||||
|     ipv6Address, | ||||
|     primaryDns, | ||||
|     secondaryDns, | ||||
|     hostsFileEntries, | ||||
|     container, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function getNetworkMode( | ||||
|   config: ContainerJSON, | ||||
|   networks: Array<DockerNetwork>, | ||||
|   runningContainers: Array<DockerContainer> = [] | ||||
| ) { | ||||
|   let networkMode = config.HostConfig?.NetworkMode || ''; | ||||
|   if (!networkMode) { | ||||
|     const networks = Object.keys(config.NetworkSettings?.Networks || {}); | ||||
|     if (networks.length > 0) { | ||||
|       [networkMode] = networks; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (networkMode.startsWith('container:')) { | ||||
|     const networkContainerId = networkMode.split(/^container:/)[1]; | ||||
|     const container = | ||||
|       runningContainers.find((c) => c.Id === networkContainerId)?.Names[0] || | ||||
|       ''; | ||||
|     return [CONTAINER_MODE, container] as const; | ||||
|   } | ||||
| 
 | ||||
|   const networkNames = networks.map((n) => n.Name); | ||||
| 
 | ||||
|   if (networkNames.includes(networkMode)) { | ||||
|     return [networkMode] as const; | ||||
|   } | ||||
| 
 | ||||
|   if ( | ||||
|     networkNames.includes('bridge') && | ||||
|     (!networkMode || networkMode === 'default' || networkMode === 'bridge') | ||||
|   ) { | ||||
|     return ['bridge'] as const; | ||||
|   } | ||||
| 
 | ||||
|   if (networkNames.includes('nat')) { | ||||
|     return ['nat'] as const; | ||||
|   } | ||||
| 
 | ||||
|   return [networks[0].Name] as const; | ||||
| } | ||||
|  | @ -0,0 +1,14 @@ | |||
| export const CONTAINER_MODE = 'container'; | ||||
| 
 | ||||
| export interface Values { | ||||
|   networkMode: string; | ||||
|   hostname: string; | ||||
|   domain: string; | ||||
|   macAddress: string; | ||||
|   ipv4Address: string; | ||||
|   ipv6Address: string; | ||||
|   primaryDns: string; | ||||
|   secondaryDns: string; | ||||
|   hostsFileEntries: Array<string>; | ||||
|   container: string; | ||||
| } | ||||
|  | @ -0,0 +1,23 @@ | |||
| import { array, object, SchemaOf, string } from 'yup'; | ||||
| 
 | ||||
| import { Values } from './types'; | ||||
| 
 | ||||
| export function validation(): SchemaOf<Values> { | ||||
|   return object({ | ||||
|     networkMode: string().default(''), | ||||
|     hostname: string().default(''), | ||||
|     domain: string().default(''), | ||||
|     macAddress: string().default(''), | ||||
|     ipv4Address: string().default(''), | ||||
|     ipv6Address: string().default(''), | ||||
|     primaryDns: string().default(''), | ||||
|     secondaryDns: string().default(''), | ||||
|     hostsFileEntries: array(string().required('Entry is required')).default([]), | ||||
|     container: string() | ||||
|       .default('') | ||||
|       .when('network', { | ||||
|         is: 'container', | ||||
|         then: string().required('Container is required'), | ||||
|       }), | ||||
|   }); | ||||
| } | ||||
|  | @ -10,6 +10,7 @@ import { withGlobalError } from '@/react-tools/react-query'; | |||
| import { urlBuilder } from '../containers.service'; | ||||
| import { DockerContainerResponse } from '../types/response'; | ||||
| import { parseListViewModel } from '../utils'; | ||||
| import { DockerContainer } from '../types'; | ||||
| 
 | ||||
| import { Filters } from './types'; | ||||
| import { queryKeys } from './query-keys'; | ||||
|  | @ -20,14 +21,15 @@ interface UseContainers { | |||
|   nodeName?: string; | ||||
| } | ||||
| 
 | ||||
| export function useContainers( | ||||
| export function useContainers<T = DockerContainer[]>( | ||||
|   environmentId: EnvironmentId, | ||||
|   { | ||||
|     autoRefreshRate, | ||||
| 
 | ||||
|     select, | ||||
|     ...params | ||||
|   }: UseContainers & { | ||||
|     autoRefreshRate?: number; | ||||
|     select?: (data: DockerContainer[]) => T; | ||||
|   } = {} | ||||
| ) { | ||||
|   return useQuery( | ||||
|  | @ -38,11 +40,12 @@ export function useContainers( | |||
|       refetchInterval() { | ||||
|         return autoRefreshRate ?? false; | ||||
|       }, | ||||
|       select, | ||||
|     } | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| async function getContainers( | ||||
| export async function getContainers( | ||||
|   environmentId: EnvironmentId, | ||||
|   { all = true, filters, nodeName }: UseContainers = {} | ||||
| ) { | ||||
|  |  | |||
|  | @ -1,6 +1,8 @@ | |||
| import { NetworkId } from '../../networks/types'; | ||||
| import { ContainerStatus } from '../types'; | ||||
| 
 | ||||
| export interface Filters { | ||||
|   label?: string[]; | ||||
|   network?: NetworkId[]; | ||||
|   status?: ContainerStatus[]; | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| interface Filters { | ||||
|   /* dangling=<boolean> When set to true (or 1), returns all networks that are not in use by a container. When set to false (or 0), only networks that are in use by one or more containers are returned. */ | ||||
|   dangling?: boolean[]; | ||||
|   dangling?: [boolean]; | ||||
|   // Matches a network's driver
 | ||||
|   driver?: string[]; | ||||
|   // Matches all or part of a network ID
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Chaim Lev-Ari
						Chaim Lev-Ari