diff --git a/app/edge/__module.js b/app/edge/__module.js index bbe36170a..db971c8c8 100644 --- a/app/edge/__module.js +++ b/app/edge/__module.js @@ -69,7 +69,7 @@ angular url: '/new?templateId&templateType', views: { 'content@': { - component: 'createEdgeStackView', + component: 'edgeStacksCreateView', }, }, data: { diff --git a/app/edge/react/components/index.ts b/app/edge/react/components/index.ts index 61a2016e3..22fd69132 100644 --- a/app/edge/react/components/index.ts +++ b/app/edge/react/components/index.ts @@ -13,7 +13,6 @@ import { withUIRouter } from '@/react-tools/withUIRouter'; import { EdgeGroupAssociationTable } from '@/react/edge/components/EdgeGroupAssociationTable'; import { AssociatedEdgeEnvironmentsSelector } from '@/react/edge/components/AssociatedEdgeEnvironmentsSelector'; import { EnvironmentsDatatable } from '@/react/edge/edge-stacks/ItemView/EnvironmentsDatatable'; -import { TemplateFieldset } from '@/react/edge/edge-stacks/CreateView/TemplateFieldset/TemplateFieldset'; import { edgeJobsModule } from './edge-jobs'; @@ -102,10 +101,6 @@ const ngModule = angular 'onChange', 'value', ]) - ) - .component( - 'edgeStackCreateTemplateFieldset', - r2a(withReactQuery(TemplateFieldset), ['setValues', 'values', 'errors']) ); export const componentsModule = ngModule.name; diff --git a/app/edge/react/views/edge-stacks.ts b/app/edge/react/views/edge-stacks.ts new file mode 100644 index 000000000..7b074ef9f --- /dev/null +++ b/app/edge/react/views/edge-stacks.ts @@ -0,0 +1,13 @@ +import angular from 'angular'; + +import { r2a } from '@/react-tools/react2angular'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; +import { withUIRouter } from '@/react-tools/withUIRouter'; +import { CreateView } from '@/react/edge/edge-stacks/CreateView/CreateView'; + +export const stacksModule = angular + .module('portainer.edge.react.views.stacks', []) + .component( + 'edgeStacksCreateView', + r2a(withCurrentUser(withUIRouter(CreateView)), []) + ).name; diff --git a/app/edge/react/views/index.ts b/app/edge/react/views/index.ts index aaa1d21ce..3be8c997e 100644 --- a/app/edge/react/views/index.ts +++ b/app/edge/react/views/index.ts @@ -10,9 +10,14 @@ import { ListView as EdgeGroupsListView } from '@/react/edge/edge-groups/ListVie import { templatesModule } from './templates'; import { jobsModule } from './jobs'; +import { stacksModule } from './edge-stacks'; export const viewsModule = angular - .module('portainer.edge.react.views', [templatesModule, jobsModule]) + .module('portainer.edge.react.views', [ + templatesModule, + jobsModule, + stacksModule, + ]) .component( 'waitingRoomView', r2a(withUIRouter(withReactQuery(withCurrentUser(WaitingRoomView))), []) 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 deleted file mode 100644 index d80704458..000000000 --- a/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.controller.js +++ /dev/null @@ -1,432 +0,0 @@ -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'; -import { baseEdgeStackWebhookUrl } from '@/portainer/helpers/webhookHelper'; -import { EnvironmentType } from '@/react/portainer/environments/types'; -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'; -import { applySetStateAction } from '@/react-tools/apply-set-state-action'; -import { getVariablesFieldDefaultValues } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesField'; -import { renderTemplate } from '@/react/portainer/custom-templates/components/utils'; -import { getInitialTemplateValues } from '@/react/edge/edge-stacks/CreateView/TemplateFieldset/TemplateFieldset'; -import { getAppTemplates } from '@/react/portainer/templates/app-templates/queries/useAppTemplates'; -import { fetchFilePreview } from '@/react/portainer/templates/app-templates/queries/useFetchTemplateFile'; -import { TemplateViewModel } from '@/react/portainer/templates/app-templates/view-model'; -import { getDefaultValues as getAppVariablesDefaultValues } from '@/react/portainer/templates/app-templates/DeployFormWidget/EnvVarsFieldset'; - -export default class CreateEdgeStackViewController { - /* @ngInject */ - constructor($state, $window, EdgeStackService, EdgeGroupService, Notifications, FormHelper, $async, $scope) { - Object.assign(this, { $state, $window, EdgeStackService, EdgeGroupService, Notifications, FormHelper, $async, $scope }); - - this.formValues = { - Name: '', - StackFileContent: '', - StackFile: null, - RepositoryURL: '', - RepositoryReferenceName: '', - RepositoryAuthentication: false, - RepositoryUsername: '', - RepositoryPassword: '', - ComposeFilePathInRepository: '', - Groups: [], - DeploymentType: 0, - UseManifestNamespaces: false, - TLSSkipVerify: false, - envVars: [], - }; - - this.EditorType = EditorType; - this.EnvironmentType = EnvironmentType; - this.isBE = isBE; - - this.state = { - Method: 'editor', - formValidationError: '', - actionInProgress: false, - StackType: null, - isEditorDirty: false, - hasKubeEndpoint: false, - endpointTypes: [], - baseWebhookUrl: baseEdgeStackWebhookUrl(), - isEdit: false, - templateValues: getInitialTemplateValues(), - }; - - this.edgeGroups = null; - - $scope.STACK_NAME_VALIDATION_REGEX = STACK_NAME_VALIDATION_REGEX; - - this.createStack = this.createStack.bind(this); - this.validateForm = this.validateForm.bind(this); - this.createStackByMethod = this.createStackByMethod.bind(this); - this.createStackFromFileContent = this.createStackFromFileContent.bind(this); - this.createStackFromFileUpload = this.createStackFromFileUpload.bind(this); - this.createStackFromGitRepository = this.createStackFromGitRepository.bind(this); - this.onChangeGroups = this.onChangeGroups.bind(this); - this.hasType = this.hasType.bind(this); - this.onChangeDeploymentType = this.onChangeDeploymentType.bind(this); - this.onEnvVarChange = this.onEnvVarChange.bind(this); - this.setTemplateValues = this.setTemplateValues.bind(this); - this.onChangeTemplate = this.onChangeTemplate.bind(this); - } - - /** - * @param {import('react').SetStateAction} templateAction - */ - setTemplateValues(templateAction) { - return this.$async(async () => { - const newTemplateValues = applySetStateAction(templateAction, this.state.templateValues); - const oldTemplateId = this.state.templateValues.template && this.state.templateValues.template.Id; - const newTemplateId = newTemplateValues.template && newTemplateValues.template.Id; - this.state.templateValues = newTemplateValues; - if (newTemplateId !== oldTemplateId) { - await this.onChangeTemplate(newTemplateValues.type, newTemplateValues.template); - } - - if (newTemplateValues.type === 'custom') { - const definitions = this.state.templateValues.template.Variables; - const newFile = renderTemplate(this.state.templateValues.file, this.state.templateValues.variables, definitions); - - this.formValues.StackFileContent = newFile; - } - }); - } - - onChangeTemplate(type, template) { - return this.$async(async () => { - if (!template) { - return; - } - - if (type === 'custom') { - 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, - PrivateRegistryId: template.EdgeSettings.PrivateRegistryId || null, - ...template.EdgeSettings.RelativePathSettings, - } - : {}), - }; - - const fileContent = await getCustomTemplateFile({ id: template.Id, git: !!template.GitConfig }); - this.state.templateValues.file = fileContent; - } - - if (type === 'app') { - this.formValues.StackFileContent = ''; - try { - const fileContent = await fetchFilePreview(template.Id); - this.formValues.StackFileContent = fileContent; - } catch (err) { - this.Notifications.error('Failure', err, 'Unable to retrieve Template'); - } - } - }); - } - - onEnvVarChange(envVars) { - return this.$scope.$evalAsync(() => { - this.formValues.envVars = envVars; - }); - } - - buildAnalyticsProperties() { - const format = 'compose'; - const metadata = { type: methodLabel(this.state.Method), format }; - - if (metadata.type === 'template') { - metadata.templateName = this.state.selectedTemplate && this.state.selectedTemplate.title; - } - - return { metadata }; - - function methodLabel(method) { - switch (method) { - case 'editor': - return 'web-editor'; - case 'repository': - return 'git'; - case 'upload': - return 'file-upload'; - case 'template': - return 'template'; - } - } - } - - uiCanExit() { - if (this.state.Method === 'editor' && this.formValues.StackFileContent && this.state.isEditorDirty) { - return confirmWebEditorDiscard(); - } - } - - /** - * - * @param {'app' | 'custom'} templateType - * @param {number} templateId - * @returns {Promise} - */ - async preSelectTemplate(templateType, templateId) { - return this.$async(async () => { - try { - this.state.Method = 'template'; - const template = await getTemplate(templateType, templateId); - if (!template) { - return; - } - - this.setTemplateValues({ - template, - type: templateType, - envVars: templateType === 'app' ? getAppVariablesDefaultValues(template.Env) : {}, - variables: templateType === 'custom' ? getVariablesFieldDefaultValues(template.Variables) : [], - }); - } catch (e) { - notifyError('Failed loading template', e); - } - }); - } - - async $onInit() { - try { - this.edgeGroups = await this.EdgeGroupService.groups(); - } catch (err) { - this.Notifications.error('Failure', err, 'Unable to retrieve Edge groups'); - } - - const templateId = parseInt(this.$state.params.templateId, 10); - const templateType = this.$state.params.templateType; - if (templateType && templateId && !Number.isNaN(templateId)) { - this.preSelectTemplate(templateType, templateId); - } - - this.$window.onbeforeunload = () => { - if (this.state.Method === 'editor' && this.formValues.StackFileContent && this.state.isEditorDirty) { - return ''; - } - }; - } - - $onDestroy() { - this.state.isEditorDirty = false; - } - - createStack() { - return this.$async(async () => { - const name = this.formValues.Name; - - if (!this.validateTemplate()) { - return; - } - - let envVars = this.formValues.envVars; - if (this.state.Method === 'template' && this.state.templateValues.type === 'app') { - envVars = [...envVars, ...Object.entries(this.state.templateValues.envVars).map(([key, value]) => ({ name: key, value }))]; - } - - const method = getMethod(this.state.Method, this.state.templateValues.template); - - if (!this.validateForm(method)) { - return; - } - - this.state.actionInProgress = true; - try { - await this.createStackByMethod(name, method, envVars); - - this.Notifications.success('Success', 'Stack successfully deployed'); - this.state.isEditorDirty = false; - this.$state.go('edge.stacks'); - } catch (err) { - this.Notifications.error('Deployment error', err, 'Unable to deploy stack'); - } finally { - this.state.actionInProgress = false; - } - }); - } - - onChangeGroups(groups) { - return this.$scope.$evalAsync(() => { - this.formValues.Groups = groups; - - this.checkIfEndpointTypes(groups); - }); - } - - checkIfEndpointTypes(groups) { - return this.$scope.$evalAsync(() => { - const edgeGroups = groups.map((id) => this.edgeGroups.find((e) => e.Id === id)); - this.state.endpointTypes = edgeGroups.flatMap((group) => group.EndpointTypes); - this.selectValidDeploymentType(); - }); - } - - selectValidDeploymentType() { - const validTypes = getValidEditorTypes(this.state.endpointTypes); - - if (!validTypes.includes(this.formValues.DeploymentType)) { - this.onChangeDeploymentType(validTypes[0]); - } - } - - hasType(envType) { - return this.state.endpointTypes.includes(envType); - } - - validateForm(method) { - this.state.formValidationError = ''; - - if (method === 'editor' && this.formValues.StackFileContent === '') { - this.state.formValidationError = 'Stack file content must not be empty'; - return; - } - - return true; - } - - createStackByMethod(name, method, envVars) { - switch (method) { - case 'editor': - return this.createStackFromFileContent(name, envVars); - case 'upload': - return this.createStackFromFileUpload(name, envVars); - case 'repository': - return this.createStackFromGitRepository(name, envVars); - } - } - - createStackFromFileContent(name, envVars) { - const { StackFileContent, Groups, DeploymentType, UseManifestNamespaces } = this.formValues; - - return this.EdgeStackService.createStackFromFileContent({ - name, - StackFileContent, - EdgeGroups: Groups, - DeploymentType, - UseManifestNamespaces, - envVars, - }); - } - - createStackFromFileUpload(name, envVars) { - const { StackFile, Groups, DeploymentType, UseManifestNamespaces } = this.formValues; - - return this.EdgeStackService.createStackFromFileUpload( - { - Name: name, - EdgeGroups: Groups, - DeploymentType, - UseManifestNamespaces, - envVars, - }, - StackFile - ); - } - - async createStackFromGitRepository(name, envVars) { - const { Groups, DeploymentType, UseManifestNamespaces } = this.formValues; - - const repositoryOptions = { - RepositoryURL: this.formValues.RepositoryURL, - RepositoryReferenceName: this.formValues.RepositoryReferenceName, - FilePathInRepository: this.formValues.ComposeFilePathInRepository, - RepositoryAuthentication: this.formValues.RepositoryAuthentication, - RepositoryUsername: this.formValues.RepositoryUsername, - RepositoryPassword: this.formValues.RepositoryPassword, - TLSSkipVerify: this.formValues.TLSSkipVerify, - CreatedFromCustomTemplateID: this.state.templateValues.template && this.state.templateValues.template.Id, - }; - return this.EdgeStackService.createStackFromGitRepository( - { - name, - EdgeGroups: Groups, - DeploymentType, - UseManifestNamespaces, - envVars, - }, - repositoryOptions - ); - } - - onChangeDeploymentType(deploymentType) { - return this.$scope.$evalAsync(() => { - this.formValues.DeploymentType = deploymentType; - this.state.Method = 'editor'; - this.formValues.StackFileContent = ''; - this.state.templateValues = getInitialTemplateValues(); - }); - } - - validateTemplate() { - if (this.state.Method === 'template' && this.state.templateValues.type === 'app') { - return Object.entries(this.state.templateValues.envVars).every(([, value]) => !!value); - } - - if (this.state.Method === 'template' && this.state.templateValues.type === 'custom') { - return Object.entries(this.state.templateValues.variables).every(([, v]) => { - return !!v.value; - }); - } - return true; - } - - formIsInvalid() { - return ( - this.form.$invalid || - !this.formValues.Groups.length || - (['template', 'editor'].includes(this.state.Method) && !this.formValues.StackFileContent) || - ('upload' === this.state.Method && !this.formValues.StackFile) || - !this.validateTemplate() - ); - } -} - -/** - * - * @param {'template'|'repository' | 'editor' | 'upload'} method - * @param {import('@/react/portainer/templates/custom-templates/types').CustomTemplate | undefined} template - * @returns 'repository' | 'editor' | 'upload' - */ -function getMethod(method, template) { - if (method !== 'template') { - return method; - } - - if (template && template.GitConfig) { - return 'repository'; - } - return 'editor'; -} - -/** - * - * @param {'app' | 'custom'} templateType - * @param {number} templateId - * @returns {Promise} - */ -async function getTemplate(templateType, templateId) { - if (!['app', 'custom'].includes(templateType)) { - notifyError('Invalid template type', `Invalid template type: ${templateType}`); - return; - } - - if (templateType === 'app') { - const templatesResponse = await getAppTemplates(); - const template = templatesResponse.templates.find((t) => t.id === templateId); - return new TemplateViewModel(template, templatesResponse.version); - } - - const template = await getCustomTemplate(templateId); - return template; -} diff --git a/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.html b/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.html deleted file mode 100644 index 69b3fe494..000000000 --- a/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.html +++ /dev/null @@ -1,101 +0,0 @@ - - -
-
- - -
- -
- -
- -
-
-
-

- - Name is required. -

-

- - This field must consist of lower case alphanumeric characters, '_' or '-' (e.g. 'my-name', or 'abc-123'). -

-
-
-
-
-
- - - - -

- There are no available deployment types when there is more than one type of environment in your edge group - selection (e.g. Kubernetes and Docker environments). Please select edge groups that have environments of the same type. -

- - - - - - - -
- -
- - -
Actions
-
-
- - {{ $ctrl.state.formValidationError }} -
-
- -
-
-
-
-
diff --git a/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.js b/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.js deleted file mode 100644 index 7da77fb05..000000000 --- a/app/edge/views/edge-stacks/createEdgeStackView/create-edge-stack-view.js +++ /dev/null @@ -1,6 +0,0 @@ -import controller from './create-edge-stack-view.controller'; - -export const createEdgeStackView = { - templateUrl: './create-edge-stack-view.html', - controller, -}; diff --git a/app/edge/views/edge-stacks/createEdgeStackView/docker-compose-form/docker-compose-form.controller.js b/app/edge/views/edge-stacks/createEdgeStackView/docker-compose-form/docker-compose-form.controller.js deleted file mode 100644 index 0403d9f3f..000000000 --- a/app/edge/views/edge-stacks/createEdgeStackView/docker-compose-form/docker-compose-form.controller.js +++ /dev/null @@ -1,51 +0,0 @@ -import { getInitialTemplateValues } from '@/react/edge/edge-stacks/CreateView/TemplateFieldset/TemplateFieldset'; -import { editor, git, edgeStackTemplate, upload } from '@@/BoxSelector/common-options/build-methods'; - -class DockerComposeFormController { - /* @ngInject */ - constructor($async, Notifications) { - Object.assign(this, { $async, Notifications }); - - this.methodOptions = [editor, upload, git, edgeStackTemplate]; - - this.onChangeFileContent = this.onChangeFileContent.bind(this); - this.onChangeFile = this.onChangeFile.bind(this); - this.onChangeMethod = this.onChangeMethod.bind(this); - this.onChangeFormValues = this.onChangeFormValues.bind(this); - this.isGitTemplate = this.isGitTemplate.bind(this); - } - - isGitTemplate() { - return this.state.Method === 'template' && !!this.templateValues.template && !!this.templateValues.template.GitConfig; - } - - onChangeFormValues(newValues) { - return this.$async(async () => { - this.formValues = { - ...this.formValues, - ...newValues, - }; - }); - } - - onChangeMethod(method) { - this.state.Method = method; - this.formValues.StackFileContent = ''; - this.setTemplateValues(getInitialTemplateValues()); - } - - onChangeFileContent(value) { - return this.$async(async () => { - this.formValues.StackFileContent = value; - this.state.isEditorDirty = true; - }); - } - - onChangeFile(value) { - return this.$async(async () => { - this.formValues.StackFile = value; - }); - } -} - -export default DockerComposeFormController; diff --git a/app/edge/views/edge-stacks/createEdgeStackView/docker-compose-form/docker-compose-form.html b/app/edge/views/edge-stacks/createEdgeStackView/docker-compose-form/docker-compose-form.html deleted file mode 100644 index c0d13f8b5..000000000 --- a/app/edge/views/edge-stacks/createEdgeStackView/docker-compose-form/docker-compose-form.html +++ /dev/null @@ -1,41 +0,0 @@ -
Build method
- - - -
- -
- - - - - You can get more information about Compose file format in the - official documentation - . - - - - - You can upload a Compose file from your computer. - - -
- -
diff --git a/app/edge/views/edge-stacks/createEdgeStackView/docker-compose-form/index.js b/app/edge/views/edge-stacks/createEdgeStackView/docker-compose-form/index.js deleted file mode 100644 index 87e2b3162..000000000 --- a/app/edge/views/edge-stacks/createEdgeStackView/docker-compose-form/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import controller from './docker-compose-form.controller.js'; - -export const edgeStacksDockerComposeForm = { - templateUrl: './docker-compose-form.html', - controller, - - bindings: { - formValues: '=', - state: '=', - templateValues: '<', - setTemplateValues: '<', - }, -}; diff --git a/app/edge/views/edge-stacks/createEdgeStackView/index.js b/app/edge/views/edge-stacks/createEdgeStackView/index.js deleted file mode 100644 index af8eee573..000000000 --- a/app/edge/views/edge-stacks/createEdgeStackView/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import angular from 'angular'; - -import { createEdgeStackView } from './create-edge-stack-view'; -import { edgeStacksDockerComposeForm } from './docker-compose-form'; -import { kubeManifestForm } from './kube-manifest-form'; -import { kubeDeployDescription } from './kube-deploy-description'; - -export default angular - .module('portainer.edge.stacks.create', []) - .component('createEdgeStackView', createEdgeStackView) - .component('edgeStacksDockerComposeForm', edgeStacksDockerComposeForm) - .component('edgeStacksKubeManifestForm', kubeManifestForm) - .component('kubeDeployDescription', kubeDeployDescription).name; diff --git a/app/edge/views/edge-stacks/createEdgeStackView/kube-deploy-description/index.js b/app/edge/views/edge-stacks/createEdgeStackView/kube-deploy-description/index.js deleted file mode 100644 index 53d8f3dd5..000000000 --- a/app/edge/views/edge-stacks/createEdgeStackView/kube-deploy-description/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export const kubeDeployDescription = { - templateUrl: './kube-deploy-description.html', -}; diff --git a/app/edge/views/edge-stacks/createEdgeStackView/kube-deploy-description/kube-deploy-description.html b/app/edge/views/edge-stacks/createEdgeStackView/kube-deploy-description/kube-deploy-description.html deleted file mode 100644 index 0ffc3c9dd..000000000 --- a/app/edge/views/edge-stacks/createEdgeStackView/kube-deploy-description/kube-deploy-description.html +++ /dev/null @@ -1,5 +0,0 @@ -
Templates allow deploying any kind of Kubernetes resource (Deployment, Secret, ConfigMap...)
-
- You can get more information about Kubernetes file format in the - official documentation. -
diff --git a/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/index.js b/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/index.js deleted file mode 100644 index 41949f7e3..000000000 --- a/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import controller from './kube-manifest-form.controller.js'; - -export const kubeManifestForm = { - templateUrl: './kube-manifest-form.html', - controller, - - bindings: { - formValues: '=', - state: '=', - }, -}; diff --git a/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.controller.js b/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.controller.js deleted file mode 100644 index 64002c69c..000000000 --- a/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.controller.js +++ /dev/null @@ -1,48 +0,0 @@ -import { editor, git, upload } from '@@/BoxSelector/common-options/build-methods'; - -class KubeManifestFormController { - /* @ngInject */ - constructor($async) { - Object.assign(this, { $async }); - - this.methodOptions = [editor, upload, git]; - - this.onChangeFileContent = this.onChangeFileContent.bind(this); - this.onChangeFormValues = this.onChangeFormValues.bind(this); - this.onChangeFile = this.onChangeFile.bind(this); - this.onChangeMethod = this.onChangeMethod.bind(this); - this.onChangeUseManifestNamespaces = this.onChangeUseManifestNamespaces.bind(this); - } - - onChangeFormValues(newValues) { - return this.$async(async () => { - this.formValues = { - ...this.formValues, - ...newValues, - }; - }); - } - - onChangeUseManifestNamespaces(value) { - this.onChangeFormValues({ UseManifestNamespaces: value }); - } - - onChangeFileContent(value) { - this.state.isEditorDirty = true; - this.formValues.StackFileContent = value; - } - - onChangeFile(value) { - return this.$async(async () => { - this.formValues.StackFile = value; - }); - } - - onChangeMethod(method) { - return this.$async(async () => { - this.state.Method = method; - }); - } -} - -export default KubeManifestFormController; diff --git a/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.html b/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.html deleted file mode 100644 index 41f8c7530..000000000 --- a/app/edge/views/edge-stacks/createEdgeStackView/kube-manifest-form/kube-manifest-form.html +++ /dev/null @@ -1,42 +0,0 @@ -
-
- -
-
- -
Build method
- - - - - - - - - - - - - - - diff --git a/app/edge/views/edge-stacks/index.js b/app/edge/views/edge-stacks/index.js index ed21f54f6..e44a9bb23 100644 --- a/app/edge/views/edge-stacks/index.js +++ b/app/edge/views/edge-stacks/index.js @@ -1,5 +1,3 @@ import angular from 'angular'; -import createModule from './createEdgeStackView'; - -export default angular.module('portainer.edge.stacks', [createModule]).name; +export default angular.module('portainer.edge.stacks', []).name; diff --git a/app/react-tools/apply-set-state-action.ts b/app/react-tools/apply-set-state-action.ts index bf6f0d17f..5c84bff75 100644 --- a/app/react-tools/apply-set-state-action.ts +++ b/app/react-tools/apply-set-state-action.ts @@ -1,8 +1,8 @@ import { SetStateAction } from 'react'; -export function applySetStateAction(applier: SetStateAction, values?: T) { +export function applySetStateAction(applier: SetStateAction, values: T) { if (isFunction(applier)) { - return values ? applier(values) : undefined; + return applier(values); } return applier; diff --git a/app/react/components/form-components/FileUpload/FileUploadForm.test.tsx b/app/react/components/form-components/FileUpload/FileUploadForm.test.tsx index ac2f4cca6..1f965254e 100644 --- a/app/react/components/form-components/FileUpload/FileUploadForm.test.tsx +++ b/app/react/components/form-components/FileUpload/FileUploadForm.test.tsx @@ -6,6 +6,7 @@ test('render should include description', async () => { const onClick = vi.fn(); const { findByText } = render( test description} diff --git a/app/react/components/form-components/FileUpload/FileUploadForm.tsx b/app/react/components/form-components/FileUpload/FileUploadForm.tsx index da601d610..50185abee 100644 --- a/app/react/components/form-components/FileUpload/FileUploadForm.tsx +++ b/app/react/components/form-components/FileUpload/FileUploadForm.tsx @@ -6,8 +6,8 @@ import { FormSectionTitle } from '@@/form-components/FormSectionTitle'; import { FileUploadField } from '@@/form-components/FileUpload/FileUploadField'; export interface Props { - onChange(value: unknown): void; - value?: File; + onChange(value?: File): void; + value: File | undefined; title?: string; required?: boolean; description: ReactNode; diff --git a/app/react/components/form-components/FormSection/FormSection.tsx b/app/react/components/form-components/FormSection/FormSection.tsx index 38f7f56f7..20725a95f 100644 --- a/app/react/components/form-components/FormSection/FormSection.tsx +++ b/app/react/components/form-components/FormSection/FormSection.tsx @@ -12,6 +12,7 @@ interface Props { defaultFolded?: boolean; titleClassName?: string; className?: string; + htmlFor?: string; } export function FormSection({ @@ -22,13 +23,14 @@ export function FormSection({ defaultFolded = isFoldable, titleClassName, className, + htmlFor = '', }: PropsWithChildren) { const [isExpanded, setIsExpanded] = useState(!defaultFolded); return (
diff --git a/app/react/components/form-components/SwitchField/SwitchField.tsx b/app/react/components/form-components/SwitchField/SwitchField.tsx index 4ab2e8848..935a651d6 100644 --- a/app/react/components/form-components/SwitchField/SwitchField.tsx +++ b/app/react/components/form-components/SwitchField/SwitchField.tsx @@ -1,6 +1,6 @@ import clsx from 'clsx'; import uuid from 'uuid'; -import { ComponentProps, PropsWithChildren, ReactNode } from 'react'; +import { ComponentProps, PropsWithChildren, ReactNode, useState } from 'react'; import { FeatureId } from '@/react/portainer/feature-flags/enums'; import { AutomationTestingProps } from '@/types'; @@ -33,7 +33,7 @@ export function SwitchField({ checked, label, index, - name = uuid(), + name, labelClass, fieldClass, 'data-cy': dataCy, @@ -44,13 +44,14 @@ export function SwitchField({ setTooltipHtmlMessage, valueExplanation, }: PropsWithChildren) { + const [toggleId] = useState(() => `toggle_${uuid()}`); const toggleName = name ? `toggle_${name}` : ''; return (