mirror of https://github.com/portainer/portainer
				
				
				
			
		
			
				
	
	
		
			266 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
			
		
		
	
	
			266 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
| import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
 | |
| import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
 | |
| import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
 | |
| import { getTemplateVariables, intersectVariables } from '@/react/portainer/custom-templates/components/utils';
 | |
| import { confirmWebEditorDiscard } from '@@/modals/confirm';
 | |
| import { getFilePreview } from '@/react/portainer/gitops/gitops.service';
 | |
| import { KUBE_TEMPLATE_NAME_VALIDATION_REGEX } from '@/constants';
 | |
| 
 | |
| class KubeEditCustomTemplateViewController {
 | |
|   /* @ngInject */
 | |
|   constructor($async, $state, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService) {
 | |
|     Object.assign(this, { $async, $state, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService });
 | |
| 
 | |
|     this.isTemplateVariablesEnabled = isBE;
 | |
| 
 | |
|     this.formValues = {
 | |
|       Variables: [],
 | |
|       TLSSkipVerify: false,
 | |
|     };
 | |
|     this.state = {
 | |
|       formValidationError: '',
 | |
|       isEditorDirty: false,
 | |
|       isTemplateValid: true,
 | |
|       isEditorReadOnly: false,
 | |
|       templateLoadFailed: false,
 | |
|       templatePreviewFailed: false,
 | |
|       templatePreviewError: '',
 | |
|       templateNameRegex: KUBE_TEMPLATE_NAME_VALIDATION_REGEX,
 | |
|     };
 | |
|     this.templates = [];
 | |
| 
 | |
|     this.getTemplate = this.getTemplate.bind(this);
 | |
|     this.submitAction = this.submitAction.bind(this);
 | |
|     this.onChangeFileContent = this.onChangeFileContent.bind(this);
 | |
|     this.onBeforeUnload = this.onBeforeUnload.bind(this);
 | |
|     this.handleChange = this.handleChange.bind(this);
 | |
|     this.onVariablesChange = this.onVariablesChange.bind(this);
 | |
|     this.previewFileFromGitRepository = this.previewFileFromGitRepository.bind(this);
 | |
|   }
 | |
| 
 | |
|   getTemplate() {
 | |
|     return this.$async(async () => {
 | |
|       try {
 | |
|         const { id } = this.$state.params;
 | |
| 
 | |
|         const template = await this.CustomTemplateService.customTemplate(id);
 | |
| 
 | |
|         if (template.GitConfig !== null) {
 | |
|           this.state.isEditorReadOnly = true;
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|           template.FileContent = await this.CustomTemplateService.customTemplateFile(id, template.GitConfig !== null);
 | |
|         } catch (err) {
 | |
|           this.state.templateLoadFailed = true;
 | |
|           throw err;
 | |
|         }
 | |
| 
 | |
|         template.Variables = template.Variables || [];
 | |
| 
 | |
|         this.formValues = { ...this.formValues, ...template };
 | |
| 
 | |
|         this.parseTemplate(template.FileContent);
 | |
|         this.parseGitConfig(template.GitConfig);
 | |
| 
 | |
|         this.oldFileContent = this.formValues.FileContent;
 | |
| 
 | |
|         this.formValues.ResourceControl = new ResourceControlViewModel(template.ResourceControl);
 | |
|         this.formValues.AccessControlData = new AccessControlFormData();
 | |
|       } catch (err) {
 | |
|         this.Notifications.error('Failure', err, 'Unable to retrieve custom template data');
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   onVariablesChange(values) {
 | |
|     this.handleChange({ Variables: values });
 | |
|   }
 | |
| 
 | |
|   handleChange(values) {
 | |
|     return this.$async(async () => {
 | |
|       this.formValues = {
 | |
|         ...this.formValues,
 | |
|         ...values,
 | |
|       };
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   parseTemplate(templateStr) {
 | |
|     if (!this.isTemplateVariablesEnabled) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     const variables = getTemplateVariables(templateStr);
 | |
| 
 | |
|     const isValid = !!variables;
 | |
| 
 | |
|     this.state.isTemplateValid = isValid;
 | |
| 
 | |
|     if (isValid) {
 | |
|       this.onVariablesChange(intersectVariables(this.formValues.Variables, variables));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   parseGitConfig(config) {
 | |
|     if (config === null) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let flatConfig = {
 | |
|       RepositoryURL: config.URL,
 | |
|       RepositoryReferenceName: config.ReferenceName,
 | |
|       ComposeFilePathInRepository: config.ConfigFilePath,
 | |
|       RepositoryAuthentication: config.Authentication !== null,
 | |
|       TLSSkipVerify: config.TLSSkipVerify,
 | |
|     };
 | |
| 
 | |
|     if (config.Authentication) {
 | |
|       flatConfig = {
 | |
|         ...flatConfig,
 | |
|         RepositoryUsername: config.Authentication.Username,
 | |
|         RepositoryPassword: config.Authentication.Password,
 | |
|       };
 | |
|     }
 | |
| 
 | |
|     this.formValues = { ...this.formValues, ...flatConfig };
 | |
|   }
 | |
| 
 | |
|   previewFileFromGitRepository() {
 | |
|     this.state.templatePreviewFailed = false;
 | |
|     this.state.templatePreviewError = '';
 | |
| 
 | |
|     let creds = {};
 | |
|     if (this.formValues.RepositoryAuthentication) {
 | |
|       creds = {
 | |
|         username: this.formValues.RepositoryUsername,
 | |
|         password: this.formValues.RepositoryPassword,
 | |
|       };
 | |
|     }
 | |
|     const payload = {
 | |
|       repository: this.formValues.RepositoryURL,
 | |
|       targetFile: this.formValues.ComposeFilePathInRepository,
 | |
|       tlsSkipVerify: this.formValues.TLSSkipVerify,
 | |
|       ...creds,
 | |
|     };
 | |
| 
 | |
|     this.$async(async () => {
 | |
|       try {
 | |
|         this.formValues.FileContent = await getFilePreview(payload);
 | |
|         this.state.isEditorDirty = true;
 | |
| 
 | |
|         // check if the template contains mustache template symbol
 | |
|         this.parseTemplate(this.formValues.FileContent);
 | |
|       } catch (err) {
 | |
|         this.state.templatePreviewError = err.message;
 | |
|         this.state.templatePreviewFailed = true;
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   validateForm() {
 | |
|     this.state.formValidationError = '';
 | |
| 
 | |
|     if (!this.formValues.FileContent) {
 | |
|       this.state.formValidationError = 'Template file content must not be empty';
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     const title = this.formValues.Title;
 | |
|     const id = this.$state.params.id;
 | |
| 
 | |
|     const isNotUnique = this.templates.some((template) => template.Title === title && template.Id != id);
 | |
|     if (isNotUnique) {
 | |
|       this.state.formValidationError = `A template with the name ${title} already exists`;
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     const isAdmin = this.Authentication.isAdmin();
 | |
|     const accessControlData = this.formValues.AccessControlData;
 | |
|     const error = this.FormValidator.validateAccessControl(accessControlData, isAdmin);
 | |
| 
 | |
|     if (error) {
 | |
|       this.state.formValidationError = error;
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   submitAction() {
 | |
|     return this.$async(async () => {
 | |
|       if (!this.validateForm()) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       this.actionInProgress = true;
 | |
|       try {
 | |
|         await this.CustomTemplateService.updateCustomTemplate(this.formValues.Id, this.formValues);
 | |
| 
 | |
|         const userDetails = this.Authentication.getUserDetails();
 | |
|         const userId = userDetails.ID;
 | |
|         await this.ResourceControlService.applyResourceControl(userId, this.formValues.AccessControlData, this.formValues.ResourceControl);
 | |
| 
 | |
|         this.Notifications.success('Success', 'Custom template successfully updated');
 | |
|         this.state.isEditorDirty = false;
 | |
|         this.$state.go('kubernetes.templates.custom');
 | |
|       } catch (err) {
 | |
|         this.Notifications.error('Failure', err, 'Unable to update custom template');
 | |
|       } finally {
 | |
|         this.actionInProgress = false;
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   onChangeFileContent(value) {
 | |
|     if (stripSpaces(this.formValues.FileContent) !== stripSpaces(value)) {
 | |
|       this.formValues.FileContent = value;
 | |
|       this.parseTemplate(value);
 | |
|       this.state.isEditorDirty = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async $onInit() {
 | |
|     this.$async(async () => {
 | |
|       this.getTemplate();
 | |
| 
 | |
|       try {
 | |
|         this.templates = await this.CustomTemplateService.customTemplates();
 | |
|       } catch (err) {
 | |
|         this.Notifications.error('Failure loading', err, 'Failed loading custom templates');
 | |
|       }
 | |
| 
 | |
|       window.addEventListener('beforeunload', this.onBeforeUnload);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   isEditorDirty() {
 | |
|     return this.formValues.FileContent !== this.oldFileContent && this.state.isEditorDirty;
 | |
|   }
 | |
| 
 | |
|   uiCanExit() {
 | |
|     if (this.isEditorDirty()) {
 | |
|       return confirmWebEditorDiscard();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   onBeforeUnload(event) {
 | |
|     if (this.formValues.FileContent !== this.oldFileContent && this.state.isEditorDirty) {
 | |
|       event.preventDefault();
 | |
|       event.returnValue = '';
 | |
| 
 | |
|       return '';
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   $onDestroy() {
 | |
|     window.removeEventListener('beforeunload', this.onBeforeUnload);
 | |
|   }
 | |
| }
 | |
| 
 | |
| export default KubeEditCustomTemplateViewController;
 | |
| 
 | |
| function stripSpaces(str = '') {
 | |
|   return str.replace(/(\r\n|\n|\r)/gm, '');
 | |
| }
 |