From e43d076269595aadbd9a5bc38cbab3cc49512dab Mon Sep 17 00:00:00 2001
From: Chaim Lev-Ari
Date: Wed, 15 Nov 2023 14:43:18 +0200
Subject: [PATCH] feat(edge/templates): introduce edge specific settings
[EE-6276] (#10609)
---
app/edge/__module.js | 2 +-
.../create-edge-stack-view.controller.js | 19 ++-
app/react-tools/apply-set-state-action.ts | 12 ++
app/react/components/CodeEditor.module.css | 6 +
.../components/RadioGroup/RadioGroup.tsx | 36 +++++
app/react/edge/edge-groups/types.ts | 6 +-
.../EditEdgeStackForm/EditEdgeStackForm.tsx | 53 +++----
.../PrivateRegistryFieldsetWrapper.tsx | 50 +++---
.../edge-stacks/components/PrePullToggle.tsx | 24 +++
.../components/RetryDeployToggle.tsx | 24 +++
.../components/StaggerFieldset.types.ts | 39 +++++
.../useCreateEdgeStack/createStackFromFile.ts | 47 ++++++
.../createStackFromFileContent.ts | 47 ++++++
.../useCreateEdgeStack/createStackFromGit.ts | 74 +++++++++
.../useCreateEdgeStack/useCreateEdgeStack.ts | 135 +++++++++++++++++
.../useCreateEdgeStackFromFileContent.ts | 49 ------
.../queries/useCreateEdgeStackFromGit.ts | 142 ------------------
.../edge-stacks/queries/useParseRegistries.ts | 44 ++++++
app/react/edge/edge-stacks/types.ts | 14 +-
.../templates/AppTemplatesView/DeployForm.tsx | 28 ++--
.../CreateView/CreateTemplateForm.tsx | 2 +
.../CreateView/EdgeSettingsFieldset.tsx | 81 ++++++++++
.../EdgeSettingsFieldset.validation.ts | 19 +++
.../custom-templates/CreateView/InnerForm.tsx | 41 ++++-
.../custom-templates/CreateView/types.ts | 2 +
.../CreateView/useValidation.tsx | 2 +
.../EditView/EditTemplateForm.tsx | 2 +
.../custom-templates/EditView/EditView.tsx | 4 +-
.../custom-templates/EditView/InnerForm.tsx | 42 +++++-
.../custom-templates/EditView/types.ts | 2 +
.../EditView/useValidation.tsx | 3 +
.../gitops/AuthFieldset/AuthFieldset.tsx | 2 +-
app/react/portainer/gitops/GitForm.tsx | 2 +-
.../RelativePathFieldset.tsx | 14 +-
.../gitops/RelativePathFieldset/types.ts | 37 +++++
.../gitops/RelativePathFieldset/validation.ts | 12 +-
app/react/portainer/gitops/types.ts | 16 +-
app/react/portainer/settings/types.ts | 2 +-
.../ListView/CustomTemplatesListItem.tsx | 2 +-
.../queries/useCreateTemplateMutation.ts | 26 +++-
.../queries/useUpdateTemplateMutation.ts | 3 +-
.../templates/custom-templates/types.ts | 37 ++++-
42 files changed, 885 insertions(+), 319 deletions(-)
create mode 100644 app/react-tools/apply-set-state-action.ts
create mode 100644 app/react/components/RadioGroup/RadioGroup.tsx
create mode 100644 app/react/edge/edge-stacks/components/PrePullToggle.tsx
create mode 100644 app/react/edge/edge-stacks/components/RetryDeployToggle.tsx
create mode 100644 app/react/edge/edge-stacks/components/StaggerFieldset.types.ts
create mode 100644 app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromFile.ts
create mode 100644 app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromFileContent.ts
create mode 100644 app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromGit.ts
create mode 100644 app/react/edge/edge-stacks/queries/useCreateEdgeStack/useCreateEdgeStack.ts
delete mode 100644 app/react/edge/edge-stacks/queries/useCreateEdgeStackFromFileContent.ts
delete mode 100644 app/react/edge/edge-stacks/queries/useCreateEdgeStackFromGit.ts
create mode 100644 app/react/edge/edge-stacks/queries/useParseRegistries.ts
create mode 100644 app/react/edge/templates/custom-templates/CreateView/EdgeSettingsFieldset.tsx
create mode 100644 app/react/edge/templates/custom-templates/CreateView/EdgeSettingsFieldset.validation.ts
create mode 100644 app/react/portainer/gitops/RelativePathFieldset/types.ts
diff --git a/app/edge/__module.js b/app/edge/__module.js
index 27b182403..e0b31efdf 100644
--- a/app/edge/__module.js
+++ b/app/edge/__module.js
@@ -181,7 +181,7 @@ angular
$stateRegistryProvider.register({
name: 'edge.templates.custom.edit',
- url: '/:templateId',
+ url: '/:id',
views: {
'content@': {
diff --git a/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.controller.js b/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.controller.js
index 2116bf346..dfae4726b 100644
--- a/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.controller.js
+++ b/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.controller.js
@@ -1,4 +1,4 @@
-import { EditorType } from '@/react/edge/edge-stacks/types';
+import { DeploymentType, EditorType } from '@/react/edge/edge-stacks/types';
import { getValidEditorTypes } from '@/react/edge/edge-stacks/utils';
import { STACK_NAME_VALIDATION_REGEX } from '@/react/constants';
import { confirmWebEditorDiscard } from '@@/modals/confirm';
@@ -8,6 +8,8 @@ import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
import { getCustomTemplate } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplate';
import { notifyError } from '@/portainer/services/notifications';
import { getCustomTemplateFile } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplateFile';
+import { toGitFormModel } from '@/react/portainer/gitops/types';
+import { StackType } from '@/react/common/stacks/types';
export default class CreateEdgeStackViewController {
/* @ngInject */
@@ -71,6 +73,21 @@ export default class CreateEdgeStackViewController {
onChangeTemplate(template) {
return this.$scope.$evalAsync(() => {
this.state.selectedTemplate = template;
+
+ this.formValues = {
+ ...this.formValues,
+ DeploymentType: template.Type === StackType.Kubernetes ? DeploymentType.Kubernetes : DeploymentType.Compose,
+ ...toGitFormModel(template.GitConfig),
+ ...(template.EdgeSettings
+ ? {
+ PrePullImage: template.EdgeSettings.PrePullImage || false,
+ RetryDeploy: template.EdgeSettings.RetryDeploy || false,
+ Registries: template.EdgeSettings.PrivateRegistryId ? [template.EdgeSettings.PrivateRegistryId] : [],
+ SupportRelativePath: template.EdgeSettings.RelativePathSettings.SupportRelativePath || false,
+ FilesystemPath: template.EdgeSettings.RelativePathSettings.FilesystemPath || '',
+ }
+ : {}),
+ };
});
}
diff --git a/app/react-tools/apply-set-state-action.ts b/app/react-tools/apply-set-state-action.ts
new file mode 100644
index 000000000..bf6f0d17f
--- /dev/null
+++ b/app/react-tools/apply-set-state-action.ts
@@ -0,0 +1,12 @@
+import { SetStateAction } from 'react';
+
+export function applySetStateAction(applier: SetStateAction, values?: T) {
+ if (isFunction(applier)) {
+ return values ? applier(values) : undefined;
+ }
+ return applier;
+
+ function isFunction(value: unknown): value is (prevState: T) => T {
+ return typeof value === 'function';
+ }
+}
diff --git a/app/react/components/CodeEditor.module.css b/app/react/components/CodeEditor.module.css
index f9cc51ed5..0fb832a14 100644
--- a/app/react/components/CodeEditor.module.css
+++ b/app/react/components/CodeEditor.module.css
@@ -117,3 +117,9 @@
.root :global(.cm-content[contenteditable='true']) {
min-height: 100%;
}
+
+.root :global(.cm-content[aria-readonly='true']) {
+ @apply bg-gray-3;
+ @apply th-dark:bg-gray-iron-10;
+ @apply th-highcontrast:bg-black;
+}
diff --git a/app/react/components/RadioGroup/RadioGroup.tsx b/app/react/components/RadioGroup/RadioGroup.tsx
new file mode 100644
index 000000000..83da291fc
--- /dev/null
+++ b/app/react/components/RadioGroup/RadioGroup.tsx
@@ -0,0 +1,36 @@
+import { Option } from '@@/form-components/PortainerSelect';
+
+interface Props {
+ options: Array
+ {isTemplateVariablesEnabled && (
+ setFieldValue('Variables', values)}
+ isVariablesNamesFromParent={!isEditorReadonly}
+ errors={errors.Variables}
+ />
+ )}
+
{values.Git && (
<>
)}
- {isTemplateVariablesEnabled && (
- setFieldValue('Variables', values)}
- isVariablesNamesFromParent={!isEditorReadonly}
- errors={errors.Variables}
+ {values.EdgeSettings && (
+
+ setFieldValue(
+ 'EdgeSettings',
+ applySetStateAction(edgeValues, values.EdgeSettings)
+ )
+ }
+ gitConfig={values.Git}
+ fileValues={{
+ fileContent: values.FileContent,
+ }}
+ values={values.EdgeSettings}
+ errors={errors.EdgeSettings as FormikErrors}
+ setFieldError={setFieldError}
/>
)}
diff --git a/app/react/edge/templates/custom-templates/EditView/types.ts b/app/react/edge/templates/custom-templates/EditView/types.ts
index a847979c1..d9c257e1d 100644
--- a/app/react/edge/templates/custom-templates/EditView/types.ts
+++ b/app/react/edge/templates/custom-templates/EditView/types.ts
@@ -3,6 +3,7 @@ import { DefinitionFieldValues } from '@/react/portainer/custom-templates/compon
import { Platform } from '@/react/portainer/templates/types';
import { type Values as CommonFieldsValues } from '@/react/portainer/custom-templates/components/CommonFields';
import { GitFormModel } from '@/react/portainer/gitops/types';
+import { EdgeTemplateSettings } from '@/react/portainer/templates/custom-templates/types';
export interface FormValues extends CommonFieldsValues {
Platform: Platform;
@@ -10,4 +11,5 @@ export interface FormValues extends CommonFieldsValues {
FileContent: string;
Git?: GitFormModel;
Variables: DefinitionFieldValues;
+ EdgeSettings?: EdgeTemplateSettings;
}
diff --git a/app/react/edge/templates/custom-templates/EditView/useValidation.tsx b/app/react/edge/templates/custom-templates/EditView/useValidation.tsx
index 7df3ec116..60dcc2b66 100644
--- a/app/react/edge/templates/custom-templates/EditView/useValidation.tsx
+++ b/app/react/edge/templates/custom-templates/EditView/useValidation.tsx
@@ -11,6 +11,8 @@ import { useCurrentUser } from '@/react/hooks/useUser';
import { useCustomTemplates } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplates';
import { Platform } from '@/react/portainer/templates/types';
+import { edgeFieldsetValidation } from '../CreateView/EdgeSettingsFieldset.validation';
+
export function useValidation(
currentTemplateId: CustomTemplate['Id'],
isGit: boolean
@@ -40,6 +42,7 @@ export function useValidation(
? buildGitValidationSchema(gitCredentialsQuery.data || [])
: mixed(),
Variables: variablesValidation(),
+ EdgeSettings: edgeFieldsetValidation(),
}).concat(
commonFieldsValidation({
templates: customTemplatesQuery.data,
diff --git a/app/react/portainer/gitops/AuthFieldset/AuthFieldset.tsx b/app/react/portainer/gitops/AuthFieldset/AuthFieldset.tsx
index 64fb198d4..099220f56 100644
--- a/app/react/portainer/gitops/AuthFieldset/AuthFieldset.tsx
+++ b/app/react/portainer/gitops/AuthFieldset/AuthFieldset.tsx
@@ -45,7 +45,7 @@ export function AuthFieldset({
label="Authentication"
labelClass="col-sm-3 col-lg-2"
name="authentication"
- checked={value.RepositoryAuthentication}
+ checked={value.RepositoryAuthentication || false}
onChange={(value) =>
handleChange({ RepositoryAuthentication: value })
}
diff --git a/app/react/portainer/gitops/GitForm.tsx b/app/react/portainer/gitops/GitForm.tsx
index 284559d68..5a34d65cd 100644
--- a/app/react/portainer/gitops/GitForm.tsx
+++ b/app/react/portainer/gitops/GitForm.tsx
@@ -113,7 +113,7 @@ export function GitForm({
handleChange({ TLSSkipVerify: value })}
name="TLSSkipVerify"
tooltip="Enabling this will allow skipping TLS validation for any self-signed certificate."
diff --git a/app/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset.tsx b/app/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset.tsx
index f42ee66db..e5acbbe65 100644
--- a/app/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset.tsx
+++ b/app/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset.tsx
@@ -1,9 +1,6 @@
import { useCallback } from 'react';
-import {
- GitFormModel,
- RelativePathModel,
-} from '@/react/portainer/gitops/types';
+import { GitFormModel } 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';
@@ -13,6 +10,8 @@ import { TextTip } from '@@/Tip/TextTip';
import { FormControl } from '@@/form-components/FormControl';
import { Input, Select } from '@@/form-components/Input';
+import { RelativePathModel, getPerDevConfigsFilterType } from './types';
+
interface Props {
value: RelativePathModel;
gitModel?: GitFormModel;
@@ -156,7 +155,9 @@ export function RelativePathFieldset({
value={value.PerDeviceConfigsMatchType}
onChange={(e) =>
innerOnChange({
- PerDeviceConfigsMatchType: e.target.value,
+ PerDeviceConfigsMatchType: getPerDevConfigsFilterType(
+ e.target.value
+ ),
})
}
options={[
@@ -186,7 +187,8 @@ export function RelativePathFieldset({
value={value.PerDeviceConfigsGroupMatchType}
onChange={(e) =>
innerOnChange({
- PerDeviceConfigsGroupMatchType: e.target.value,
+ PerDeviceConfigsGroupMatchType:
+ getPerDevConfigsFilterType(e.target.value),
})
}
options={[
diff --git a/app/react/portainer/gitops/RelativePathFieldset/types.ts b/app/react/portainer/gitops/RelativePathFieldset/types.ts
new file mode 100644
index 000000000..be1a08d99
--- /dev/null
+++ b/app/react/portainer/gitops/RelativePathFieldset/types.ts
@@ -0,0 +1,37 @@
+export function getDefaultRelativePathModel(): RelativePathModel {
+ return {
+ SupportRelativePath: false,
+ FilesystemPath: '',
+ PerDeviceConfigsGroupMatchType: '',
+ PerDeviceConfigsMatchType: '',
+ PerDeviceConfigsPath: '',
+ SupportPerDeviceConfigs: false,
+ };
+}
+
+export interface RelativePathModel {
+ SupportRelativePath: boolean;
+ FilesystemPath: string;
+ SupportPerDeviceConfigs: boolean;
+ PerDeviceConfigsPath: string;
+ PerDeviceConfigsMatchType: PerDevConfigsFilterType;
+ PerDeviceConfigsGroupMatchType: PerDevConfigsFilterType;
+}
+
+export type PerDevConfigsFilterType = 'file' | 'dir' | '';
+
+function isPerDevConfigsFilterType(
+ type: string
+): type is PerDevConfigsFilterType {
+ return ['file', 'dir'].includes(type);
+}
+
+export function getPerDevConfigsFilterType(
+ type: string
+): PerDevConfigsFilterType {
+ if (isPerDevConfigsFilterType(type)) {
+ return type;
+ }
+
+ return '';
+}
diff --git a/app/react/portainer/gitops/RelativePathFieldset/validation.ts b/app/react/portainer/gitops/RelativePathFieldset/validation.ts
index 351e188bf..6f2d16540 100644
--- a/app/react/portainer/gitops/RelativePathFieldset/validation.ts
+++ b/app/react/portainer/gitops/RelativePathFieldset/validation.ts
@@ -1,6 +1,6 @@
-import { boolean, object, SchemaOf, string } from 'yup';
+import { boolean, mixed, object, SchemaOf, string } from 'yup';
-import { RelativePathModel } from '@/react/portainer/gitops/types';
+import { PerDevConfigsFilterType, RelativePathModel } from './types';
export function relativePathValidation(): SchemaOf {
return object({
@@ -18,7 +18,11 @@ export function relativePathValidation(): SchemaOf {
then: string().required('Directory is required'),
})
.default(''),
- PerDeviceConfigsMatchType: string().oneOf(['', 'file', 'dir']),
- PerDeviceConfigsGroupMatchType: string().oneOf(['', 'file', 'dir']),
+ PerDeviceConfigsMatchType: mixed()
+ .oneOf(['', 'file', 'dir'])
+ .default(''),
+ PerDeviceConfigsGroupMatchType: mixed()
+ .oneOf(['', 'file', 'dir'])
+ .default(''),
});
}
diff --git a/app/react/portainer/gitops/types.ts b/app/react/portainer/gitops/types.ts
index 41b0f9eff..72d6552c8 100644
--- a/app/react/portainer/gitops/types.ts
+++ b/app/react/portainer/gitops/types.ts
@@ -1,4 +1,6 @@
export type AutoUpdateMechanism = 'Webhook' | 'Interval';
+export { type RelativePathModel } from './RelativePathFieldset/types';
+
export interface AutoUpdateResponse {
/* Auto update interval */
Interval: string;
@@ -37,7 +39,7 @@ export type AutoUpdateModel = {
};
export type GitCredentialsModel = {
- RepositoryAuthentication: boolean;
+ RepositoryAuthentication?: boolean;
RepositoryUsername?: string;
RepositoryPassword?: string;
RepositoryGitCredentialID?: number;
@@ -54,13 +56,12 @@ export interface GitFormModel extends GitAuthModel {
RepositoryURL: string;
RepositoryURLValid?: boolean;
ComposeFilePathInRepository: string;
- RepositoryAuthentication: boolean;
RepositoryReferenceName?: string;
AdditionalFiles?: string[];
SaveCredential?: boolean;
NewCredentialName?: string;
- TLSSkipVerify: boolean;
+ TLSSkipVerify?: boolean;
/**
* Auto update
@@ -70,15 +71,6 @@ export interface GitFormModel extends GitAuthModel {
AutoUpdate?: AutoUpdateModel;
}
-export interface RelativePathModel {
- SupportRelativePath: boolean;
- FilesystemPath?: string;
- SupportPerDeviceConfigs?: boolean;
- PerDeviceConfigsPath?: string;
- PerDeviceConfigsMatchType?: string;
- PerDeviceConfigsGroupMatchType?: string;
-}
-
export function toGitFormModel(response?: RepoConfigResponse): GitFormModel {
if (!response) {
return {
diff --git a/app/react/portainer/settings/types.ts b/app/react/portainer/settings/types.ts
index f2523259c..6f5126855 100644
--- a/app/react/portainer/settings/types.ts
+++ b/app/react/portainer/settings/types.ts
@@ -41,7 +41,7 @@ export interface LDAPSettings {
export interface Pair {
name: string;
- value: string;
+ value?: string;
}
export interface OpenAMTConfiguration {
diff --git a/app/react/portainer/templates/custom-templates/ListView/CustomTemplatesListItem.tsx b/app/react/portainer/templates/custom-templates/ListView/CustomTemplatesListItem.tsx
index 69e886c9f..3fe1fe278 100644
--- a/app/react/portainer/templates/custom-templates/ListView/CustomTemplatesListItem.tsx
+++ b/app/react/portainer/templates/custom-templates/ListView/CustomTemplatesListItem.tsx
@@ -46,7 +46,7 @@ export function CustomTemplatesListItem({
props={{
to: '.edit',
params: {
- templateId: template.Id,
+ id: template.Id,
},
}}
icon={Edit}
diff --git a/app/react/portainer/templates/custom-templates/queries/useCreateTemplateMutation.ts b/app/react/portainer/templates/custom-templates/queries/useCreateTemplateMutation.ts
index 56c8f9387..0ad51be1d 100644
--- a/app/react/portainer/templates/custom-templates/queries/useCreateTemplateMutation.ts
+++ b/app/react/portainer/templates/custom-templates/queries/useCreateTemplateMutation.ts
@@ -12,7 +12,10 @@ import {
import { StackType } from '@/react/common/stacks/types';
import { FormValues } from '@/react/edge/templates/custom-templates/CreateView/types';
import { VariableDefinition } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesDefinitionField/CustomTemplatesVariablesDefinitionField';
-import { CustomTemplate } from '@/react/portainer/templates/custom-templates/types';
+import {
+ CustomTemplate,
+ EdgeTemplateSettings,
+} from '@/react/portainer/templates/custom-templates/types';
import { Platform } from '../../types';
@@ -41,7 +44,18 @@ function createTemplate({
case 'upload':
return createTemplateFromFile(values);
case 'repository':
- return createTemplateFromGit({ ...values, ...Git });
+ return createTemplateFromGit({
+ ...values,
+ ...Git,
+ ...(values.EdgeSettings
+ ? {
+ EdgeSettings: {
+ ...values.EdgeSettings,
+ ...values.EdgeSettings.RelativePathSettings,
+ },
+ }
+ : {}),
+ });
default:
throw new Error('Unknown method');
}
@@ -69,6 +83,7 @@ interface CustomTemplateFromFileContentPayload {
Variables: VariableDefinition[];
/** Indicates if this template is for Edge Stack. */
EdgeTemplate?: boolean;
+ EdgeSettings?: EdgeTemplateSettings;
}
async function createTemplateFromText(
values: CustomTemplateFromFileContentPayload
@@ -103,6 +118,7 @@ interface CustomTemplateFromFilePayload {
Variables?: VariableDefinition[];
/** Indicates if this template is for Edge Stack. */
EdgeTemplate?: boolean;
+ EdgeSettings?: EdgeTemplateSettings;
}
async function createTemplateFromFile(values: CustomTemplateFromFilePayload) {
@@ -121,6 +137,7 @@ async function createTemplateFromFile(values: CustomTemplateFromFilePayload) {
File: values.File,
Variables: values.Variables,
EdgeTemplate: values.EdgeTemplate,
+ EdgeSettings: values.EdgeSettings,
});
const { data } = await axios.post(
@@ -157,7 +174,7 @@ interface CustomTemplateFromGitRepositoryPayload {
/** Reference name of a Git repository hosting the Stack file. */
RepositoryReferenceName?: string;
/** Use basic authentication to clone the Git repository. */
- RepositoryAuthentication: boolean;
+ RepositoryAuthentication?: boolean;
/** Username used in basic authentication when RepositoryAuthentication is true. */
RepositoryUsername?: string;
/** Password used in basic authentication when RepositoryAuthentication is true. */
@@ -167,11 +184,12 @@ interface CustomTemplateFromGitRepositoryPayload {
/** Definitions of variables in the stack file. */
Variables: VariableDefinition[];
/** Indicates whether to skip SSL verification when cloning the Git repository. */
- TLSSkipVerify: boolean;
+ TLSSkipVerify?: boolean;
/** Indicates if the Kubernetes template is created from a Docker Compose file. */
IsComposeFormat?: boolean;
/** Indicates if this template is for Edge Stack. */
EdgeTemplate?: boolean;
+ EdgeSettings?: EdgeTemplateSettings;
}
async function createTemplateFromGit(
values: CustomTemplateFromGitRepositoryPayload
diff --git a/app/react/portainer/templates/custom-templates/queries/useUpdateTemplateMutation.ts b/app/react/portainer/templates/custom-templates/queries/useUpdateTemplateMutation.ts
index 8d1550343..8ba62774a 100644
--- a/app/react/portainer/templates/custom-templates/queries/useUpdateTemplateMutation.ts
+++ b/app/react/portainer/templates/custom-templates/queries/useUpdateTemplateMutation.ts
@@ -9,7 +9,7 @@ import {
import { StackType } from '@/react/common/stacks/types';
import { VariableDefinition } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesDefinitionField/CustomTemplatesVariablesDefinitionField';
-import { CustomTemplate } from '../types';
+import { CustomTemplate, EdgeTemplateSettings } from '../types';
import { Platform } from '../../types';
import { buildUrl } from './build-url';
@@ -75,6 +75,7 @@ interface CustomTemplateUpdatePayload {
IsComposeFormat?: boolean;
/** EdgeTemplate indicates if this template purpose for Edge Stack */
EdgeTemplate?: boolean;
+ EdgeSettings?: EdgeTemplateSettings;
}
async function updateTemplate(
diff --git a/app/react/portainer/templates/custom-templates/types.ts b/app/react/portainer/templates/custom-templates/types.ts
index 7a951db8d..0b6230860 100644
--- a/app/react/portainer/templates/custom-templates/types.ts
+++ b/app/react/portainer/templates/custom-templates/types.ts
@@ -2,9 +2,12 @@ import { UserId } from '@/portainer/users/types';
import { StackType } from '@/react/common/stacks/types';
import { ResourceControlResponse } from '../../access-control/types';
-import { RepoConfigResponse } from '../../gitops/types';
+import { RelativePathModel, RepoConfigResponse } from '../../gitops/types';
import { VariableDefinition } from '../../custom-templates/components/CustomTemplatesVariablesDefinitionField';
import { Platform } from '../types';
+import { RegistryId } from '../../registries/types';
+import { getDefaultRelativePathModel } from '../../gitops/RelativePathFieldset/types';
+import { isBE } from '../../feature-flags/feature-flags.service';
export type CustomTemplate = {
Id: number;
@@ -87,16 +90,38 @@ export type CustomTemplate = {
/** EdgeTemplate indicates if this template purpose for Edge Stack */
EdgeTemplate: boolean;
+
+ EdgeSettings?: EdgeTemplateSettings;
+};
+
+/**
+ * EdgeTemplateSettings represents the configuration of a custom template for Edge
+ */
+export type EdgeTemplateSettings = {
+ PrePullImage: boolean;
+
+ RetryDeploy: boolean;
+
+ PrivateRegistryId: RegistryId | undefined;
+
+ RelativePathSettings: RelativePathModel;
};
export type CustomTemplateFileContent = {
FileContent: string;
};
-export const CustomTemplateKubernetesType = 3;
+export const CustomTemplateKubernetesType = StackType.Kubernetes;
-export enum Types {
- SWARM = 1,
- STANDALONE,
- KUBERNETES,
+export function getDefaultEdgeTemplateSettings() {
+ if (!isBE) {
+ return undefined;
+ }
+
+ return {
+ PrePullImage: false,
+ RetryDeploy: false,
+ PrivateRegistryId: undefined,
+ RelativePathSettings: getDefaultRelativePathModel(),
+ };
}