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,
|
VolumesTab,
|
||||||
volumesTabUtils,
|
volumesTabUtils,
|
||||||
} from '@/react/docker/containers/CreateView/VolumesTab';
|
} from '@/react/docker/containers/CreateView/VolumesTab';
|
||||||
|
import {
|
||||||
|
networkTabUtils,
|
||||||
|
NetworkTab,
|
||||||
|
type NetworkTabValues,
|
||||||
|
} from '@/react/docker/containers/CreateView/NetworkTab';
|
||||||
|
|
||||||
const ngModule = angular
|
const ngModule = angular
|
||||||
.module('portainer.docker.react.components.containers', [])
|
.module('portainer.docker.react.components.containers', [])
|
||||||
|
@ -57,3 +62,11 @@ withFormValidation(
|
||||||
['allowBindMounts'],
|
['allowBindMounts'],
|
||||||
volumesTabUtils.validation
|
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 { commandsTabUtils } from '@/react/docker/containers/CreateView/CommandsTab';
|
||||||
import { volumesTabUtils } from '@/react/docker/containers/CreateView/VolumesTab';
|
import { volumesTabUtils } from '@/react/docker/containers/CreateView/VolumesTab';
|
||||||
|
import { networkTabUtils } from '@/react/docker/containers/CreateView/NetworkTab';
|
||||||
import { ContainerCapabilities, ContainerCapability } from '@/docker/models/containerCapabilities';
|
import { ContainerCapabilities, ContainerCapability } from '@/docker/models/containerCapabilities';
|
||||||
import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
|
import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
|
||||||
import { ContainerDetailsViewModel } from '@/docker/models/container';
|
import { ContainerDetailsViewModel } from '@/docker/models/container';
|
||||||
|
|
||||||
import './createcontainer.css';
|
import './createcontainer.css';
|
||||||
import { envVarsTabUtils } from '@/react/docker/containers/CreateView/EnvVarsTab';
|
import { envVarsTabUtils } from '@/react/docker/containers/CreateView/EnvVarsTab';
|
||||||
|
import { getContainers } from '@/react/docker/containers/queries/containers';
|
||||||
|
|
||||||
angular.module('portainer.docker').controller('CreateContainerController', [
|
angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
'$q',
|
'$q',
|
||||||
|
@ -74,7 +76,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
selectedGPUs: ['all'],
|
selectedGPUs: ['all'],
|
||||||
capabilities: ['compute', 'utility'],
|
capabilities: ['compute', 'utility'],
|
||||||
},
|
},
|
||||||
NetworkContainer: null,
|
|
||||||
Labels: [],
|
Labels: [],
|
||||||
ExtraHosts: [],
|
ExtraHosts: [],
|
||||||
MacAddress: '',
|
MacAddress: '',
|
||||||
|
@ -94,10 +95,9 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
commands: commandsTabUtils.getDefaultViewModel(),
|
commands: commandsTabUtils.getDefaultViewModel(),
|
||||||
envVars: envVarsTabUtils.getDefaultViewModel(),
|
envVars: envVarsTabUtils.getDefaultViewModel(),
|
||||||
volumes: volumesTabUtils.getDefaultViewModel(),
|
volumes: volumesTabUtils.getDefaultViewModel(),
|
||||||
|
network: networkTabUtils.getDefaultViewModel(),
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.extraNetworks = {};
|
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
formValidationError: '',
|
formValidationError: '',
|
||||||
actionInProgress: false,
|
actionInProgress: false,
|
||||||
|
@ -132,6 +132,11 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
$scope.formValues.volumes = volumes;
|
$scope.formValues.volumes = volumes;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
$scope.onNetworkChange = function (network) {
|
||||||
|
return $scope.$evalAsync(() => {
|
||||||
|
$scope.formValues.network = network;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function onAlwaysPullChange(checked) {
|
function onAlwaysPullChange(checked) {
|
||||||
return $scope.$evalAsync(() => {
|
return $scope.$evalAsync(() => {
|
||||||
|
@ -280,50 +285,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
config.HostConfig.PortBindings = bindings;
|
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) {
|
function prepareLabels(config) {
|
||||||
var labels = {};
|
var labels = {};
|
||||||
$scope.formValues.Labels.forEach(function (label) {
|
$scope.formValues.Labels.forEach(function (label) {
|
||||||
|
@ -439,8 +400,8 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
config = commandsTabUtils.toRequest(config, $scope.formValues.commands);
|
config = commandsTabUtils.toRequest(config, $scope.formValues.commands);
|
||||||
config = envVarsTabUtils.toRequest(config, $scope.formValues.envVars);
|
config = envVarsTabUtils.toRequest(config, $scope.formValues.envVars);
|
||||||
config = volumesTabUtils.toRequest(config, $scope.formValues.volumes);
|
config = volumesTabUtils.toRequest(config, $scope.formValues.volumes);
|
||||||
|
config = networkTabUtils.toRequest(config, $scope.formValues.network, $scope.fromContainer.Id);
|
||||||
|
|
||||||
prepareNetworkConfig(config);
|
|
||||||
prepareImageConfig(config);
|
prepareImageConfig(config);
|
||||||
preparePortBindings(config);
|
preparePortBindings(config);
|
||||||
prepareLabels(config);
|
prepareLabels(config);
|
||||||
|
@ -457,70 +418,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
$scope.config.HostConfig.PortBindings = bindings;
|
$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() {
|
function loadFromContainerLabels() {
|
||||||
for (var l in $scope.config.Labels) {
|
for (var l in $scope.config.Labels) {
|
||||||
if ({}.hasOwnProperty.call($scope.config.Labels, l)) {
|
if ({}.hasOwnProperty.call($scope.config.Labels, l)) {
|
||||||
|
@ -640,16 +537,14 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
|
|
||||||
$scope.fromContainer = fromContainer;
|
$scope.fromContainer = fromContainer;
|
||||||
$scope.state.mode = 'duplicate';
|
$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.commands = commandsTabUtils.toViewModel(d);
|
||||||
$scope.formValues.envVars = envVarsTabUtils.toViewModel(d);
|
$scope.formValues.envVars = envVarsTabUtils.toViewModel(d);
|
||||||
$scope.formValues.volumes = volumesTabUtils.toViewModel(d);
|
$scope.formValues.volumes = volumesTabUtils.toViewModel(d);
|
||||||
|
$scope.formValues.network = networkTabUtils.toViewModel(d, $scope.availableNetworks, $scope.runningContainers);
|
||||||
|
|
||||||
loadFromContainerPortBindings(d);
|
loadFromContainerPortBindings(d);
|
||||||
|
|
||||||
loadFromContainerNetworkConfig(d);
|
|
||||||
|
|
||||||
loadFromContainerLabels(d);
|
loadFromContainerLabels(d);
|
||||||
loadFromContainerDevices(d);
|
loadFromContainerDevices(d);
|
||||||
loadFromContainerDeviceRequests(d);
|
loadFromContainerDeviceRequests(d);
|
||||||
|
@ -691,11 +586,8 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve networks');
|
Notifications.error('Failure', err, 'Unable to retrieve networks');
|
||||||
});
|
});
|
||||||
|
getContainers(endpoint.Id)
|
||||||
Container.query(
|
.then((containers) => {
|
||||||
{},
|
|
||||||
function (d) {
|
|
||||||
var containers = d;
|
|
||||||
$scope.runningContainers = containers;
|
$scope.runningContainers = containers;
|
||||||
$scope.gpuUseAll = _.get($scope, 'endpoint.Snapshots[0].GpuUseAll', false);
|
$scope.gpuUseAll = _.get($scope, 'endpoint.Snapshots[0].GpuUseAll', false);
|
||||||
$scope.gpuUseList = _.get($scope, 'endpoint.Snapshots[0].GpuUseList', []);
|
$scope.gpuUseList = _.get($scope, 'endpoint.Snapshots[0].GpuUseList', []);
|
||||||
|
@ -706,11 +598,10 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
$scope.fromContainer = {};
|
$scope.fromContainer = {};
|
||||||
$scope.formValues.capabilities = $scope.areContainerCapabilitiesEnabled ? new ContainerCapabilities() : [];
|
$scope.formValues.capabilities = $scope.areContainerCapabilitiesEnabled ? new ContainerCapabilities() : [];
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
function (e) {
|
.catch((e) => {
|
||||||
Notifications.error('Failure', e, 'Unable to retrieve running containers');
|
Notifications.error('Failure', e, 'Unable to retrieve running containers');
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
SystemService.info()
|
SystemService.info()
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
|
@ -928,11 +819,11 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectToExtraNetworks(newContainerId) {
|
function connectToExtraNetworks(newContainerId) {
|
||||||
if (!$scope.extraNetworks) {
|
if (!$scope.formValues.network.extraNetworks) {
|
||||||
return $q.when();
|
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')) {
|
if (_.has(network, 'Aliases')) {
|
||||||
var aliases = _.filter(network.Aliases, (o) => {
|
var aliases = _.filter(network.Aliases, (o) => {
|
||||||
return !_.startsWith($scope.fromContainer.Id, 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>
|
<li ng-if="areContainerCapabilitiesEnabled" class="interactive"><a data-target="#container-capabilities" data-toggle="tab">Capabilities</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<!-- tab-content -->
|
<!-- tab-content -->
|
||||||
<div class="form-horizontal">
|
<div class="form-horizontal" ng-if="state.containerIsLoaded">
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<!-- tab-command -->
|
<!-- tab-command -->
|
||||||
<div class="tab-pane active" id="command">
|
<div class="tab-pane active" id="command">
|
||||||
|
@ -220,122 +220,10 @@
|
||||||
</docker-create-container-volumes-tab>
|
</docker-create-container-volumes-tab>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- tab-network -->
|
|
||||||
<div class="tab-pane" id="network">
|
<div class="tab-pane" id="network">
|
||||||
<form class="form-horizontal" style="margin-top: 15px">
|
<docker-create-container-network-tab values="formValues.network" on-change="(onNetworkChange)"> </docker-create-container-network-tab>
|
||||||
<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>
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
<!-- !tab-network -->
|
|
||||||
<!-- tab-labels -->
|
<!-- tab-labels -->
|
||||||
<div class="tab-pane" id="labels">
|
<div class="tab-pane" id="labels">
|
||||||
<form class="form-horizontal" style="margin-top: 15px">
|
<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 { urlBuilder } from '../containers.service';
|
||||||
import { DockerContainerResponse } from '../types/response';
|
import { DockerContainerResponse } from '../types/response';
|
||||||
import { parseListViewModel } from '../utils';
|
import { parseListViewModel } from '../utils';
|
||||||
|
import { DockerContainer } from '../types';
|
||||||
|
|
||||||
import { Filters } from './types';
|
import { Filters } from './types';
|
||||||
import { queryKeys } from './query-keys';
|
import { queryKeys } from './query-keys';
|
||||||
|
@ -20,14 +21,15 @@ interface UseContainers {
|
||||||
nodeName?: string;
|
nodeName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useContainers(
|
export function useContainers<T = DockerContainer[]>(
|
||||||
environmentId: EnvironmentId,
|
environmentId: EnvironmentId,
|
||||||
{
|
{
|
||||||
autoRefreshRate,
|
autoRefreshRate,
|
||||||
|
select,
|
||||||
...params
|
...params
|
||||||
}: UseContainers & {
|
}: UseContainers & {
|
||||||
autoRefreshRate?: number;
|
autoRefreshRate?: number;
|
||||||
|
select?: (data: DockerContainer[]) => T;
|
||||||
} = {}
|
} = {}
|
||||||
) {
|
) {
|
||||||
return useQuery(
|
return useQuery(
|
||||||
|
@ -38,11 +40,12 @@ export function useContainers(
|
||||||
refetchInterval() {
|
refetchInterval() {
|
||||||
return autoRefreshRate ?? false;
|
return autoRefreshRate ?? false;
|
||||||
},
|
},
|
||||||
|
select,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getContainers(
|
export async function getContainers(
|
||||||
environmentId: EnvironmentId,
|
environmentId: EnvironmentId,
|
||||||
{ all = true, filters, nodeName }: UseContainers = {}
|
{ all = true, filters, nodeName }: UseContainers = {}
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { NetworkId } from '../../networks/types';
|
import { NetworkId } from '../../networks/types';
|
||||||
|
import { ContainerStatus } from '../types';
|
||||||
|
|
||||||
export interface Filters {
|
export interface Filters {
|
||||||
label?: string[];
|
label?: string[];
|
||||||
network?: NetworkId[];
|
network?: NetworkId[];
|
||||||
|
status?: ContainerStatus[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
interface Filters {
|
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> 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
|
// Matches a network's driver
|
||||||
driver?: string[];
|
driver?: string[];
|
||||||
// Matches all or part of a network ID
|
// Matches all or part of a network ID
|
||||||
|
|
Loading…
Reference in New Issue