mirror of https://github.com/portainer/portainer
fix(edge-stack): sync CE code with EE EE-6163 (#10437)
parent
a0dbabcc5f
commit
66ca73f98b
@ -0,0 +1,251 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
GitFormModel,
|
||||
RelativePathModel,
|
||||
} from '@/react/portainer/gitops/types';
|
||||
import { PathSelector } from '@/react/portainer/gitops/ComposePathField/PathSelector';
|
||||
import { dummyGitForm } from '@/react/portainer/gitops/RelativePathFieldset/utils';
|
||||
import { useValidation } from '@/react/portainer/gitops/RelativePathFieldset/useValidation';
|
||||
|
||||
import { SwitchField } from '@@/form-components/SwitchField';
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
import { Input, Select } from '@@/form-components/Input';
|
||||
|
||||
interface Props {
|
||||
value: RelativePathModel;
|
||||
gitModel?: GitFormModel;
|
||||
onChange?: (value: Partial<RelativePathModel>) => void;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
export function RelativePathFieldset({
|
||||
value,
|
||||
gitModel,
|
||||
onChange,
|
||||
readonly,
|
||||
}: Props) {
|
||||
const innerOnChange = useCallback(
|
||||
(value: Partial<RelativePathModel>) => onChange && onChange(value),
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const { errors } = useValidation(value);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<TextTip color="blue">
|
||||
'Gitops Edge Configuration' requires relative path volumes
|
||||
to be enabled first, as it uses this feature as the base mechanism.
|
||||
</TextTip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
name="EnableRelativePaths"
|
||||
label="Enable relative path volumes"
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
tooltip="Enabling this means you can specify relative path volumes in your Compose files, with Portainer pulling the content from your git repository to the environment the stack is deployed to."
|
||||
disabled={readonly}
|
||||
checked={value.SupportRelativePath}
|
||||
onChange={(value) => innerOnChange({ SupportRelativePath: value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{value.SupportRelativePath && (
|
||||
<>
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<TextTip color="blue">
|
||||
For relative path volumes use with Docker Swarm, you must have a
|
||||
network filesystem which all of your nodes can access.
|
||||
</TextTip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<FormControl
|
||||
label="Local filesystem path"
|
||||
errors={errors.FilesystemPath}
|
||||
>
|
||||
<Input
|
||||
name="FilesystemPath"
|
||||
placeholder="/mnt"
|
||||
disabled={readonly}
|
||||
value={value.FilesystemPath}
|
||||
onChange={(e) =>
|
||||
innerOnChange({ FilesystemPath: e.target.value })
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<TextTip color="blue">
|
||||
When enabled, corresponding Edge ID will be passed through as an
|
||||
environment variable: PORTAINER_EDGE_ID.
|
||||
</TextTip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
name="EnablePerDeviceConfigs"
|
||||
label="GitOps Edge configurations"
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
tooltip="By enabling the GitOps Edge Configurations feature, you gain the ability to define relative path volumes in your configuration files. Portainer will then automatically fetch the content from your git repository by matching the folder name or file name with the Portainer Edge ID, and apply it to the environment where the stack is deployed"
|
||||
disabled={readonly}
|
||||
checked={!!value.SupportPerDeviceConfigs}
|
||||
onChange={(value) =>
|
||||
innerOnChange({ SupportPerDeviceConfigs: value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{value.SupportPerDeviceConfigs && (
|
||||
<>
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<TextTip color="blue">
|
||||
Specify the directory name where your configuration will be
|
||||
located. This will allow you to manage device configuration
|
||||
settings with a Git repo as your template.
|
||||
</TextTip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<FormControl
|
||||
label="Directory"
|
||||
errors={errors.PerDeviceConfigsPath}
|
||||
>
|
||||
<PathSelector
|
||||
value={value.PerDeviceConfigsPath || ''}
|
||||
onChange={(value) =>
|
||||
innerOnChange({ PerDeviceConfigsPath: value })
|
||||
}
|
||||
placeholder="config"
|
||||
model={gitModel || dummyGitForm}
|
||||
readOnly={readonly}
|
||||
dirOnly
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<TextTip color="blue">
|
||||
Select which rule to use when matching configuration with
|
||||
Portainer Edge ID either on a per-device basis or group-wide
|
||||
with an Edge Group. Only configurations that match the
|
||||
selected rule will be accessible through their corresponding
|
||||
paths. Deployments that rely on accessing the configuration
|
||||
may experience errors.
|
||||
</TextTip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<FormControl label="Device matching rule">
|
||||
<Select
|
||||
value={value.PerDeviceConfigsMatchType}
|
||||
onChange={(e) =>
|
||||
innerOnChange({
|
||||
PerDeviceConfigsMatchType: e.target.value,
|
||||
})
|
||||
}
|
||||
options={[
|
||||
{
|
||||
label: '',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
label: 'Match file name with Portainer Edge ID',
|
||||
value: 'file',
|
||||
},
|
||||
{
|
||||
label: 'Match folder name with Portainer Edge ID',
|
||||
value: 'dir',
|
||||
},
|
||||
]}
|
||||
disabled={readonly}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<FormControl label="Group matching rule">
|
||||
<Select
|
||||
value={value.PerDeviceConfigsGroupMatchType}
|
||||
onChange={(e) =>
|
||||
innerOnChange({
|
||||
PerDeviceConfigsGroupMatchType: e.target.value,
|
||||
})
|
||||
}
|
||||
options={[
|
||||
{
|
||||
label: '',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
label: 'Match file name with Edge Group',
|
||||
value: 'file',
|
||||
},
|
||||
{
|
||||
label: 'Match folder name with Edge Group',
|
||||
value: 'dir',
|
||||
},
|
||||
]}
|
||||
disabled={readonly}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<TextTip color="blue">
|
||||
<div>
|
||||
You can use it as an environment variable with an image:{' '}
|
||||
<code>myapp:${PORTAINER_EDGE_ID}</code> or{' '}
|
||||
<code>myapp:${PORTAINER_EDGE_GROUP}</code>. You
|
||||
can also use it with the relative path for volumes:{' '}
|
||||
<code>
|
||||
./config/${PORTAINER_EDGE_ID}:/myapp/config
|
||||
</code>{' '}
|
||||
or{' '}
|
||||
<code>
|
||||
./config/${PORTAINER_EDGE_GROUP}:/myapp/groupconfig
|
||||
</code>
|
||||
. More documentation can be found{' '}
|
||||
<a href="https://docs.portainer.io/user/edge/stacks/add#gitops-edge-configurations">
|
||||
here
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</TextTip>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FormikErrors, yupToFormErrors } from 'formik';
|
||||
|
||||
import { RelativePathModel } from '@/react/portainer/gitops/types';
|
||||
import { relativePathValidation } from '@/react/portainer/gitops/RelativePathFieldset/validation';
|
||||
|
||||
export function useValidation(value: RelativePathModel) {
|
||||
const [errors, setErrors] = useState<FormikErrors<RelativePathModel>>({});
|
||||
|
||||
useEffect(() => {
|
||||
async function valide() {
|
||||
try {
|
||||
await relativePathValidation().validate(value, {
|
||||
strict: true,
|
||||
abortEarly: false,
|
||||
});
|
||||
setErrors({});
|
||||
} catch (error) {
|
||||
setErrors(yupToFormErrors(error));
|
||||
}
|
||||
}
|
||||
|
||||
valide();
|
||||
}, [value]);
|
||||
|
||||
return { errors };
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import { EdgeStack } from '@/react/edge/edge-stacks/types';
|
||||
|
||||
import { GitFormModel, RelativePathModel } from '../types';
|
||||
|
||||
export function parseRelativePathResponse(stack: EdgeStack): RelativePathModel {
|
||||
return {
|
||||
SupportRelativePath: stack.SupportRelativePath,
|
||||
FilesystemPath: stack.FilesystemPath,
|
||||
SupportPerDeviceConfigs: stack.SupportPerDeviceConfigs,
|
||||
PerDeviceConfigsMatchType: stack.PerDeviceConfigsMatchType,
|
||||
PerDeviceConfigsGroupMatchType: stack.PerDeviceConfigsGroupMatchType,
|
||||
PerDeviceConfigsPath: stack.PerDeviceConfigsPath,
|
||||
};
|
||||
}
|
||||
|
||||
export const dummyGitForm: GitFormModel = {
|
||||
RepositoryURL: '',
|
||||
RepositoryURLValid: false,
|
||||
RepositoryAuthentication: false,
|
||||
RepositoryUsername: '',
|
||||
RepositoryPassword: '',
|
||||
AdditionalFiles: [],
|
||||
RepositoryReferenceName: '',
|
||||
ComposeFilePathInRepository: '',
|
||||
NewCredentialName: '',
|
||||
SaveCredential: false,
|
||||
TLSSkipVerify: false,
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
import { boolean, object, SchemaOf, string } from 'yup';
|
||||
|
||||
import { RelativePathModel } from '@/react/portainer/gitops/types';
|
||||
|
||||
export function relativePathValidation(): SchemaOf<RelativePathModel> {
|
||||
return object({
|
||||
SupportRelativePath: boolean().default(false),
|
||||
FilesystemPath: string()
|
||||
.when(['SupportRelativePath'], {
|
||||
is: true,
|
||||
then: string().required('Local filesystem path is required'),
|
||||
})
|
||||
.default(''),
|
||||
SupportPerDeviceConfigs: boolean().default(false),
|
||||
PerDeviceConfigsPath: string()
|
||||
.when(['SupportPerDeviceConfigs'], {
|
||||
is: true,
|
||||
then: string().required('Directory is required'),
|
||||
})
|
||||
.default(''),
|
||||
PerDeviceConfigsMatchType: string().oneOf(['', 'file', 'dir']),
|
||||
PerDeviceConfigsGroupMatchType: string().oneOf(['', 'file', 'dir']),
|
||||
});
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import angular from 'angular';
|
||||
|
||||
import { r2a } from '@/react-tools/react2angular';
|
||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
||||
import { withReactQuery } from '@/react-tools/withReactQuery';
|
||||
import { PathSelector } from '@/react/portainer/gitops/ComposePathField/PathSelector';
|
||||
import { RelativePathFieldset } from '@/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset';
|
||||
|
||||
export const ngModule = angular
|
||||
.module('portainer.app.react.gitops', [])
|
||||
.component(
|
||||
'pathSelector',
|
||||
r2a(withUIRouter(withReactQuery(PathSelector)), [
|
||||
'value',
|
||||
'onChange',
|
||||
'placeholder',
|
||||
'model',
|
||||
'dirOnly',
|
||||
'readOnly',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'relativePathFieldset',
|
||||
r2a(withUIRouter(withReactQuery(RelativePathFieldset)), [
|
||||
'value',
|
||||
'gitModel',
|
||||
'onChange',
|
||||
'readonly',
|
||||
])
|
||||
);
|
||||
|
||||
export const gitopsModule = ngModule.name;
|
Loading…
Reference in new issue