mirror of
https://github.com/portainer/portainer.git
synced 2025-07-23 15:29:42 +02:00
feat(containers): migrate labels tab to react [EE-5212] (#10348)
This commit is contained in:
parent
b4b44e6fa4
commit
7acde18930
11 changed files with 159 additions and 67 deletions
|
@ -38,6 +38,10 @@ import {
|
||||||
RestartPolicyTab,
|
RestartPolicyTab,
|
||||||
restartPolicyTabUtils,
|
restartPolicyTabUtils,
|
||||||
} from '@/react/docker/containers/CreateView/RestartPolicyTab';
|
} from '@/react/docker/containers/CreateView/RestartPolicyTab';
|
||||||
|
import {
|
||||||
|
LabelsTab,
|
||||||
|
labelsTabUtils,
|
||||||
|
} from '@/react/docker/containers/CreateView/LabelsTab';
|
||||||
|
|
||||||
const ngModule = angular
|
const ngModule = angular
|
||||||
.module('portainer.docker.react.components.containers', [])
|
.module('portainer.docker.react.components.containers', [])
|
||||||
|
@ -114,3 +118,11 @@ withFormValidation(
|
||||||
[],
|
[],
|
||||||
restartPolicyTabUtils.validation
|
restartPolicyTabUtils.validation
|
||||||
);
|
);
|
||||||
|
|
||||||
|
withFormValidation(
|
||||||
|
ngModule,
|
||||||
|
withUIRouter(withReactQuery(LabelsTab)),
|
||||||
|
'dockerCreateContainerLabelsTab',
|
||||||
|
[],
|
||||||
|
labelsTabUtils.validation
|
||||||
|
);
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { networkTabUtils } from '@/react/docker/containers/CreateView/NetworkTab
|
||||||
import { capabilitiesTabUtils } from '@/react/docker/containers/CreateView/CapabilitiesTab';
|
import { capabilitiesTabUtils } from '@/react/docker/containers/CreateView/CapabilitiesTab';
|
||||||
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 { labelsTabUtils } from '@/react/docker/containers/CreateView/LabelsTab';
|
||||||
|
|
||||||
import './createcontainer.css';
|
import './createcontainer.css';
|
||||||
import { envVarsTabUtils } from '@/react/docker/containers/CreateView/EnvVarsTab';
|
import { envVarsTabUtils } from '@/react/docker/containers/CreateView/EnvVarsTab';
|
||||||
|
@ -77,7 +78,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
selectedGPUs: ['all'],
|
selectedGPUs: ['all'],
|
||||||
capabilities: ['compute', 'utility'],
|
capabilities: ['compute', 'utility'],
|
||||||
},
|
},
|
||||||
Labels: [],
|
|
||||||
ExtraHosts: [],
|
ExtraHosts: [],
|
||||||
MacAddress: '',
|
MacAddress: '',
|
||||||
IPv4: '',
|
IPv4: '',
|
||||||
|
@ -95,6 +95,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
resources: resourcesTabUtils.getDefaultViewModel(),
|
resources: resourcesTabUtils.getDefaultViewModel(),
|
||||||
capabilities: capabilitiesTabUtils.getDefaultViewModel(),
|
capabilities: capabilitiesTabUtils.getDefaultViewModel(),
|
||||||
restartPolicy: restartPolicyTabUtils.getDefaultViewModel(),
|
restartPolicy: restartPolicyTabUtils.getDefaultViewModel(),
|
||||||
|
labels: labelsTabUtils.getDefaultViewModel(),
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
|
@ -136,6 +137,11 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
$scope.formValues.network = network;
|
$scope.formValues.network = network;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
$scope.onLabelsChange = function (labels) {
|
||||||
|
return $scope.$evalAsync(() => {
|
||||||
|
$scope.formValues.labels = labels;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$scope.onResourcesChange = function (resources) {
|
$scope.onResourcesChange = function (resources) {
|
||||||
return $scope.$evalAsync(() => {
|
return $scope.$evalAsync(() => {
|
||||||
|
@ -250,14 +256,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
$scope.config.HostConfig.PortBindings.splice(index, 1);
|
$scope.config.HostConfig.PortBindings.splice(index, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addLabel = function () {
|
|
||||||
$scope.formValues.Labels.push({ name: '', value: '' });
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.removeLabel = function (index) {
|
|
||||||
$scope.formValues.Labels.splice(index, 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.addExtraHost = function () {
|
$scope.addExtraHost = function () {
|
||||||
$scope.formValues.ExtraHosts.push({ value: '' });
|
$scope.formValues.ExtraHosts.push({ value: '' });
|
||||||
};
|
};
|
||||||
|
@ -302,20 +300,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
config.HostConfig.PortBindings = bindings;
|
config.HostConfig.PortBindings = bindings;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareLabels(config) {
|
|
||||||
var labels = {};
|
|
||||||
$scope.formValues.Labels.forEach(function (label) {
|
|
||||||
if (label.name) {
|
|
||||||
if (label.value) {
|
|
||||||
labels[label.name] = label.value;
|
|
||||||
} else {
|
|
||||||
labels[label.name] = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
config.Labels = labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepareConfiguration() {
|
function prepareConfiguration() {
|
||||||
var config = angular.copy($scope.config);
|
var config = angular.copy($scope.config);
|
||||||
config = commandsTabUtils.toRequest(config, $scope.formValues.commands);
|
config = commandsTabUtils.toRequest(config, $scope.formValues.commands);
|
||||||
|
@ -325,11 +309,10 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
config = resourcesTabUtils.toRequest(config, $scope.formValues.resources);
|
config = resourcesTabUtils.toRequest(config, $scope.formValues.resources);
|
||||||
config = capabilitiesTabUtils.toRequest(config, $scope.formValues.capabilities);
|
config = capabilitiesTabUtils.toRequest(config, $scope.formValues.capabilities);
|
||||||
config = restartPolicyTabUtils.toRequest(config, $scope.formValues.restartPolicy);
|
config = restartPolicyTabUtils.toRequest(config, $scope.formValues.restartPolicy);
|
||||||
|
config = labelsTabUtils.toRequest(config, $scope.formValues.labels);
|
||||||
|
|
||||||
prepareImageConfig(config);
|
prepareImageConfig(config);
|
||||||
preparePortBindings(config);
|
preparePortBindings(config);
|
||||||
prepareLabels(config);
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFromContainerPortBindings() {
|
function loadFromContainerPortBindings() {
|
||||||
|
@ -337,14 +320,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
$scope.config.HostConfig.PortBindings = bindings;
|
$scope.config.HostConfig.PortBindings = bindings;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFromContainerLabels() {
|
|
||||||
for (var l in $scope.config.Labels) {
|
|
||||||
if ({}.hasOwnProperty.call($scope.config.Labels, l)) {
|
|
||||||
$scope.formValues.Labels.push({ name: l, value: $scope.config.Labels[l] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadFromContainerImageConfig() {
|
function loadFromContainerImageConfig() {
|
||||||
RegistryService.retrievePorRegistryModelFromRepository($scope.config.Image, endpoint.Id)
|
RegistryService.retrievePorRegistryModelFromRepository($scope.config.Image, endpoint.Id)
|
||||||
.then((model) => {
|
.then((model) => {
|
||||||
|
@ -381,11 +356,11 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
$scope.formValues.network = networkTabUtils.toViewModel(d, $scope.availableNetworks, $scope.runningContainers);
|
$scope.formValues.network = networkTabUtils.toViewModel(d, $scope.availableNetworks, $scope.runningContainers);
|
||||||
$scope.formValues.resources = resourcesTabUtils.toViewModel(d);
|
$scope.formValues.resources = resourcesTabUtils.toViewModel(d);
|
||||||
$scope.formValues.capabilities = capabilitiesTabUtils.toViewModel(d);
|
$scope.formValues.capabilities = capabilitiesTabUtils.toViewModel(d);
|
||||||
|
$scope.formValues.labels = labelsTabUtils.toViewModel(d);
|
||||||
|
|
||||||
$scope.formValues.restartPolicy = restartPolicyTabUtils.toViewModel(d);
|
$scope.formValues.restartPolicy = restartPolicyTabUtils.toViewModel(d);
|
||||||
|
|
||||||
loadFromContainerPortBindings(d);
|
loadFromContainerPortBindings(d);
|
||||||
loadFromContainerLabels(d);
|
|
||||||
loadFromContainerImageConfig(d);
|
loadFromContainerImageConfig(d);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
|
@ -201,13 +201,12 @@
|
||||||
<li class="interactive"><a data-target="#runtime-resources" ng-mouseup="refreshSlider()" data-toggle="tab">Runtime & Resources</a></li>
|
<li class="interactive"><a data-target="#runtime-resources" ng-mouseup="refreshSlider()" data-toggle="tab">Runtime & Resources</a></li>
|
||||||
<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 -->
|
|
||||||
<div class="form-horizontal" ng-if="state.containerIsLoaded">
|
<div class="form-horizontal" ng-if="state.containerIsLoaded">
|
||||||
|
<!-- tab-content -->
|
||||||
<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">
|
||||||
<docker-create-container-commands-tab
|
<docker-create-container-commands-tab
|
||||||
ng-if="state.containerIsLoaded"
|
|
||||||
values="formValues.commands"
|
values="formValues.commands"
|
||||||
api-version="applicationState.endpoint.apiVersion"
|
api-version="applicationState.endpoint.apiVersion"
|
||||||
on-change="(handleCommandsChange)"
|
on-change="(handleCommandsChange)"
|
||||||
|
@ -224,37 +223,10 @@
|
||||||
<docker-create-container-network-tab values="formValues.network" on-change="(onNetworkChange)"> </docker-create-container-network-tab>
|
<docker-create-container-network-tab values="formValues.network" on-change="(onNetworkChange)"> </docker-create-container-network-tab>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- tab-labels -->
|
|
||||||
<div class="tab-pane" id="labels">
|
<div class="tab-pane" id="labels">
|
||||||
<form class="form-horizontal" style="margin-top: 15px">
|
<docker-create-container-labels-tab values="formValues.labels" on-change="(onLabelsChange)"></docker-create-container-labels-tab>
|
||||||
<!-- labels -->
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-12" style="margin-top: 5px">
|
|
||||||
<label class="control-label text-left">Labels</label>
|
|
||||||
<span class="label label-default interactive" style="margin-left: 10px" ng-click="addLabel()"> <pr-icon icon="'plus'" mode="'alt'"></pr-icon> add label </span>
|
|
||||||
</div>
|
|
||||||
<!-- labels-input-list -->
|
|
||||||
<div class="col-sm-12 form-inline" style="margin-top: 10px">
|
|
||||||
<div ng-repeat="label in formValues.Labels" style="margin-top: 2px">
|
|
||||||
<div class="input-group col-sm-5 input-group-sm">
|
|
||||||
<span class="input-group-addon">name</span>
|
|
||||||
<input type="text" class="form-control" ng-model="label.name" placeholder="e.g. com.example.foo" />
|
|
||||||
</div>
|
|
||||||
<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="label.value" placeholder="e.g. bar" />
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-sm btn-light" type="button" ng-click="removeLabel($index)">
|
|
||||||
<pr-icon icon="'trash-2'" class-name="'icon-secondary icon-md'"></pr-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !labels-input-list -->
|
|
||||||
</div>
|
|
||||||
<!-- !labels-->
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- !tab-labels -->
|
|
||||||
<!-- tab-env -->
|
<!-- tab-env -->
|
||||||
<div class="tab-pane" id="env">
|
<div class="tab-pane" id="env">
|
||||||
<docker-create-container-env-vars-tab
|
<docker-create-container-env-vars-tab
|
||||||
|
|
|
@ -10,8 +10,8 @@ import { validateForm } from '@@/form-components/validate-form';
|
||||||
import { ArrayError } from '@@/form-components/InputList/InputList';
|
import { ArrayError } from '@@/form-components/InputList/InputList';
|
||||||
|
|
||||||
interface FormFieldProps<TValue> {
|
interface FormFieldProps<TValue> {
|
||||||
onChange(values: TValue): void;
|
onChange(values: TValue): void; // update the values for the entire form object used in yup validation, not just one input.
|
||||||
values: TValue;
|
values: TValue; // current values
|
||||||
errors?: FormikErrors<TValue> | ArrayError<TValue>;
|
errors?: FormikErrors<TValue> | ArrayError<TValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
31
app/react/docker/containers/CreateView/LabelsTab/Item.tsx
Normal file
31
app/react/docker/containers/CreateView/LabelsTab/Item.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { FormError } from '@@/form-components/FormError';
|
||||||
|
import { InputGroup } from '@@/form-components/InputGroup';
|
||||||
|
import { ItemProps } from '@@/form-components/InputList';
|
||||||
|
|
||||||
|
import { Label } from './types';
|
||||||
|
|
||||||
|
export function Item({ item, onChange, error }: ItemProps<Label>) {
|
||||||
|
return (
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="flex w-full gap-4">
|
||||||
|
<InputGroup className="w-1/2">
|
||||||
|
<InputGroup.Addon>name</InputGroup.Addon>
|
||||||
|
<InputGroup.Input
|
||||||
|
value={item.name}
|
||||||
|
onChange={(e) => onChange({ ...item, name: e.target.value })}
|
||||||
|
placeholder="e.g. com.example.foo"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
<InputGroup className="w-1/2">
|
||||||
|
<InputGroup.Addon>value</InputGroup.Addon>
|
||||||
|
<InputGroup.Input
|
||||||
|
value={item.value}
|
||||||
|
onChange={(e) => onChange({ ...item, value: e.target.value })}
|
||||||
|
placeholder="e.g. bar"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
{error && <FormError>{Object.values(error)[0]}</FormError>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { InputList } from '@@/form-components/InputList';
|
||||||
|
import { ArrayError } from '@@/form-components/InputList/InputList';
|
||||||
|
|
||||||
|
import { Item } from './Item';
|
||||||
|
import { Values } from './types';
|
||||||
|
|
||||||
|
export function LabelsTab({
|
||||||
|
values: initialValues,
|
||||||
|
onChange,
|
||||||
|
errors,
|
||||||
|
}: {
|
||||||
|
values: Values;
|
||||||
|
onChange: (values: Values) => void;
|
||||||
|
errors?: ArrayError<Values>;
|
||||||
|
}) {
|
||||||
|
const [values, setControlledValues] = useState(initialValues);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputList
|
||||||
|
label="Labels"
|
||||||
|
onChange={handleChange}
|
||||||
|
errors={errors}
|
||||||
|
value={values}
|
||||||
|
item={Item}
|
||||||
|
itemBuilder={() => ({ name: '', value: '' })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleChange(values: Values) {
|
||||||
|
setControlledValues(values);
|
||||||
|
onChange(values);
|
||||||
|
}
|
||||||
|
}
|
14
app/react/docker/containers/CreateView/LabelsTab/index.ts
Normal file
14
app/react/docker/containers/CreateView/LabelsTab/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { validation } from './validation';
|
||||||
|
import { toRequest } from './toRequest';
|
||||||
|
import { toViewModel, getDefaultViewModel } from './toViewModel';
|
||||||
|
|
||||||
|
export { LabelsTab } from './LabelsTab';
|
||||||
|
|
||||||
|
export { type Values as LabelsTabValues } from './types';
|
||||||
|
|
||||||
|
export const labelsTabUtils = {
|
||||||
|
toRequest,
|
||||||
|
toViewModel,
|
||||||
|
validation,
|
||||||
|
getDefaultViewModel,
|
||||||
|
};
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { CreateContainerRequest } from '../types';
|
||||||
|
|
||||||
|
import { Values } from './types';
|
||||||
|
|
||||||
|
export function toRequest(
|
||||||
|
oldConfig: CreateContainerRequest,
|
||||||
|
values: Values
|
||||||
|
): CreateContainerRequest {
|
||||||
|
return {
|
||||||
|
...oldConfig,
|
||||||
|
Labels: Object.fromEntries(
|
||||||
|
values
|
||||||
|
.filter((label) => label.name)
|
||||||
|
.map((label) => [label.name, label.value])
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { ContainerJSON } from '../../queries/container';
|
||||||
|
|
||||||
|
import { Values } from './types';
|
||||||
|
|
||||||
|
export function toViewModel(config: ContainerJSON): Values {
|
||||||
|
if (!config || !config.Config || !config.Config.Labels) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(config.Config.Labels).map(([name, value]) => ({
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultViewModel(): Values {
|
||||||
|
return [];
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface Label {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Values = Array<Label>;
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { array, object, SchemaOf, string } from 'yup';
|
||||||
|
|
||||||
|
import { Values } from './types';
|
||||||
|
|
||||||
|
export function validation(): SchemaOf<Values> {
|
||||||
|
return array(
|
||||||
|
object({
|
||||||
|
name: string().required('Name is required'),
|
||||||
|
value: string().default(''),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue