1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-03 04:45:21 +02:00

fix(edge/templates): fix issues [EE-6328] (#10656)

This commit is contained in:
Chaim Lev-Ari 2023-11-27 09:56:15 +02:00 committed by GitHub
parent 140ac5d17c
commit 76bcdfa2b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 181 additions and 91 deletions

View file

@ -16,9 +16,9 @@ export function RadioGroup<T extends string | number = string>({
return (
<div>
{options.map((option) => (
<span
<label
key={option.value}
className="col-sm-3 col-lg-2 control-label !p-0 text-left"
className="col-sm-3 col-lg-2 control-label !p-0 text-left font-normal"
>
<input
type="radio"
@ -29,7 +29,7 @@ export function RadioGroup<T extends string | number = string>({
style={{ margin: '0 4px 0 0' }}
/>
{option.label}
</span>
</label>
))}
</div>
);

View file

@ -1,46 +1,68 @@
import { useState } from 'react';
import { SetStateAction, useEffect, useState } from 'react';
import sanitize from 'sanitize-html';
import { FormikErrors } from 'formik';
import { useCustomTemplates } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplates';
import { CustomTemplate } from '@/react/portainer/templates/custom-templates/types';
import { useCustomTemplateFileMutation } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplateFile';
import {
CustomTemplatesVariablesField,
VariablesFieldValue,
getVariablesFieldDefaultValues,
} from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesField';
import { renderTemplate } from '@/react/portainer/custom-templates/components/utils';
import { FormControl } from '@@/form-components/FormControl';
import { PortainerSelect } from '@@/form-components/PortainerSelect';
export interface Values {
template: CustomTemplate | undefined;
variables: VariablesFieldValue;
}
export function TemplateFieldset({
value: selectedTemplate,
onChange,
onChangeFile,
values: initialValues,
setValues: setInitialValues,
errors,
}: {
value: CustomTemplate | undefined;
onChange: (value?: CustomTemplate) => void;
onChangeFile: (value: string) => void;
errors?: FormikErrors<Values>;
values: Values;
setValues: (values: SetStateAction<Values>) => void;
}) {
const fetchFileMutation = useCustomTemplateFileMutation();
const [templateFile, setTemplateFile] = useState('');
const [values, setControlledValues] = useState(initialValues); // todo remove when all view is in react
useEffect(() => {
if (initialValues.template?.Id !== values.template?.Id) {
setControlledValues(initialValues);
}
}, [initialValues, values.template?.Id]);
const templatesQuery = useCustomTemplates({
select: (templates) =>
templates.filter((template) => template.EdgeTemplate),
});
const [variableValues, setVariableValues] = useState<VariablesFieldValue>([]);
return (
<>
<TemplateSelector
value={selectedTemplate?.Id}
onChange={handleChangeTemplate}
error={errors?.template}
value={values.template?.Id}
onChange={(value) => {
setValues((values) => {
const template = templatesQuery.data?.find(
(template) => template.Id === value
);
return {
...values,
template,
variables: getVariablesFieldDefaultValues(
template?.Variables || []
),
};
});
}}
/>
{selectedTemplate && (
{values.template && (
<>
{selectedTemplate.Note && (
{values.template.Note && (
<div>
<div className="col-sm-12 form-section-title"> Information </div>
<div className="form-group">
@ -49,7 +71,7 @@ export function TemplateFieldset({
className="template-note"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: sanitize(selectedTemplate.Note),
__html: sanitize(values.template.Note),
}}
/>
</div>
@ -59,59 +81,34 @@ export function TemplateFieldset({
<CustomTemplatesVariablesField
onChange={(value) => {
setVariableValues(value);
onChangeFile(
renderTemplate(templateFile, value, selectedTemplate.Variables)
);
setValues((values) => ({
...values,
variables: value,
}));
}}
value={variableValues}
definitions={selectedTemplate.Variables}
value={values.variables}
definitions={values.template.Variables}
errors={errors?.variables}
/>
</>
)}
</>
);
function handleChangeTemplate(templateId: CustomTemplate['Id'] | undefined) {
const selectedTemplate = templatesQuery.data?.find(
(template) => template.Id === templateId
);
if (!selectedTemplate) {
setVariableValues([]);
onChange(undefined);
return;
}
fetchFileMutation.mutate(
{ id: selectedTemplate.Id, git: !!selectedTemplate.GitConfig },
{
onSuccess: (data) => {
setTemplateFile(data);
onChangeFile(
renderTemplate(
data,
getVariablesFieldDefaultValues(selectedTemplate.Variables),
selectedTemplate.Variables
)
);
},
}
);
setVariableValues(
selectedTemplate
? getVariablesFieldDefaultValues(selectedTemplate.Variables)
: []
);
onChange(selectedTemplate);
function setValues(values: SetStateAction<Values>) {
setControlledValues(values);
setInitialValues(values);
}
}
function TemplateSelector({
value,
onChange,
error,
}: {
value: CustomTemplate['Id'] | undefined;
onChange: (value: CustomTemplate['Id'] | undefined) => void;
error?: string;
}) {
const templatesQuery = useCustomTemplates({
select: (templates) =>
@ -123,7 +120,7 @@ function TemplateSelector({
}
return (
<FormControl label="Template" inputId="stack_template">
<FormControl label="Template" inputId="stack_template" errors={error}>
<PortainerSelect
placeholder="Select an Edge stack template"
value={value}

View file

@ -62,7 +62,6 @@ export function PrivateRegistryFieldsetWrapper({
const registries = await dryRunMutation.mutateAsync(values);
if (registries.length === 0) {
onChange(undefined);
return;
}

View file

@ -40,6 +40,12 @@ export function PrivateRegistryFieldset({
const tooltipMessage =
'This allows you to provide credentials when using a private registry that requires authentication';
useEffect(() => {
if (isActive) {
setChecked(isActive);
}
}, [isActive]);
useEffect(() => {
if (checked) {
onChange();

View file

@ -15,17 +15,20 @@ export function useParseRegistries() {
});
}
export async function parseRegistries(props: {
export async function parseRegistries({
file,
fileContent,
}: {
file?: File;
fileContent?: string;
}) {
if (!props.file && !props.fileContent) {
if (!file && !fileContent) {
throw new Error('File or fileContent must be provided');
}
let currentFile = props.file;
if (!props.file && props.fileContent) {
currentFile = new File([props.fileContent], 'registries.yml');
let currentFile = file;
if (!file && fileContent) {
currentFile = new File([fileContent], 'registries.yml');
}
try {
const { data } = await axios.post<Array<RegistryId>>(