mirror of https://github.com/portainer/portainer
feat(containers): migrate labels tab to react [EE-5212] (#10348)
parent
b4b44e6fa4
commit
7acde18930
|
@ -38,6 +38,10 @@ import {
|
|||
RestartPolicyTab,
|
||||
restartPolicyTabUtils,
|
||||
} from '@/react/docker/containers/CreateView/RestartPolicyTab';
|
||||
import {
|
||||
LabelsTab,
|
||||
labelsTabUtils,
|
||||
} from '@/react/docker/containers/CreateView/LabelsTab';
|
||||
|
||||
const ngModule = angular
|
||||
.module('portainer.docker.react.components.containers', [])
|
||||
|
@ -114,3 +118,11 @@ withFormValidation(
|
|||
[],
|
||||
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 { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { ContainerDetailsViewModel } from '@/docker/models/container';
|
||||
import { labelsTabUtils } from '@/react/docker/containers/CreateView/LabelsTab';
|
||||
|
||||
import './createcontainer.css';
|
||||
import { envVarsTabUtils } from '@/react/docker/containers/CreateView/EnvVarsTab';
|
||||
|
@ -77,7 +78,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
selectedGPUs: ['all'],
|
||||
capabilities: ['compute', 'utility'],
|
||||
},
|
||||
Labels: [],
|
||||
ExtraHosts: [],
|
||||
MacAddress: '',
|
||||
IPv4: '',
|
||||
|
@ -95,6 +95,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
resources: resourcesTabUtils.getDefaultViewModel(),
|
||||
capabilities: capabilitiesTabUtils.getDefaultViewModel(),
|
||||
restartPolicy: restartPolicyTabUtils.getDefaultViewModel(),
|
||||
labels: labelsTabUtils.getDefaultViewModel(),
|
||||
};
|
||||
|
||||
$scope.state = {
|
||||
|
@ -136,6 +137,11 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
$scope.formValues.network = network;
|
||||
});
|
||||
};
|
||||
$scope.onLabelsChange = function (labels) {
|
||||
return $scope.$evalAsync(() => {
|
||||
$scope.formValues.labels = labels;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.onResourcesChange = function (resources) {
|
||||
return $scope.$evalAsync(() => {
|
||||
|
@ -250,14 +256,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
$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.formValues.ExtraHosts.push({ value: '' });
|
||||
};
|
||||
|
@ -302,20 +300,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
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() {
|
||||
var config = angular.copy($scope.config);
|
||||
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 = capabilitiesTabUtils.toRequest(config, $scope.formValues.capabilities);
|
||||
config = restartPolicyTabUtils.toRequest(config, $scope.formValues.restartPolicy);
|
||||
config = labelsTabUtils.toRequest(config, $scope.formValues.labels);
|
||||
|
||||
prepareImageConfig(config);
|
||||
preparePortBindings(config);
|
||||
prepareLabels(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
function loadFromContainerPortBindings() {
|
||||
|
@ -337,14 +320,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
$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() {
|
||||
RegistryService.retrievePorRegistryModelFromRepository($scope.config.Image, endpoint.Id)
|
||||
.then((model) => {
|
||||
|
@ -381,11 +356,11 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
$scope.formValues.network = networkTabUtils.toViewModel(d, $scope.availableNetworks, $scope.runningContainers);
|
||||
$scope.formValues.resources = resourcesTabUtils.toViewModel(d);
|
||||
$scope.formValues.capabilities = capabilitiesTabUtils.toViewModel(d);
|
||||
$scope.formValues.labels = labelsTabUtils.toViewModel(d);
|
||||
|
||||
$scope.formValues.restartPolicy = restartPolicyTabUtils.toViewModel(d);
|
||||
|
||||
loadFromContainerPortBindings(d);
|
||||
loadFromContainerLabels(d);
|
||||
loadFromContainerImageConfig(d);
|
||||
})
|
||||
.then(() => {
|
||||
|
|
|
@ -201,13 +201,12 @@
|
|||
<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>
|
||||
</ul>
|
||||
<!-- tab-content -->
|
||||
<div class="form-horizontal" ng-if="state.containerIsLoaded">
|
||||
<!-- tab-content -->
|
||||
<div class="tab-content">
|
||||
<!-- tab-command -->
|
||||
<div class="tab-pane active" id="command">
|
||||
<docker-create-container-commands-tab
|
||||
ng-if="state.containerIsLoaded"
|
||||
values="formValues.commands"
|
||||
api-version="applicationState.endpoint.apiVersion"
|
||||
on-change="(handleCommandsChange)"
|
||||
|
@ -224,37 +223,10 @@
|
|||
<docker-create-container-network-tab values="formValues.network" on-change="(onNetworkChange)"> </docker-create-container-network-tab>
|
||||
</div>
|
||||
|
||||
<!-- tab-labels -->
|
||||
<div class="tab-pane" id="labels">
|
||||
<form class="form-horizontal" style="margin-top: 15px">
|
||||
<!-- 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>
|
||||
<docker-create-container-labels-tab values="formValues.labels" on-change="(onLabelsChange)"></docker-create-container-labels-tab>
|
||||
</div>
|
||||
<!-- !tab-labels -->
|
||||
|
||||
<!-- tab-env -->
|
||||
<div class="tab-pane" id="env">
|
||||
<docker-create-container-env-vars-tab
|
||||
|
|
|
@ -10,8 +10,8 @@ import { validateForm } from '@@/form-components/validate-form';
|
|||
import { ArrayError } from '@@/form-components/InputList/InputList';
|
||||
|
||||
interface FormFieldProps<TValue> {
|
||||
onChange(values: TValue): void;
|
||||
values: TValue;
|
||||
onChange(values: TValue): void; // update the values for the entire form object used in yup validation, not just one input.
|
||||
values: TValue; // current values
|
||||
errors?: FormikErrors<TValue> | ArrayError<TValue>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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…
Reference in New Issue