2022-09-07 04:25:00 +00:00
|
|
|
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
2021-09-02 05:28:51 +00:00
|
|
|
import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
|
2022-11-13 08:10:18 +00:00
|
|
|
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
2022-05-31 10:00:47 +00:00
|
|
|
import { getTemplateVariables, intersectVariables } from '@/react/portainer/custom-templates/components/utils';
|
2023-02-14 08:19:41 +00:00
|
|
|
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
2023-04-04 00:44:42 +00:00
|
|
|
import { getFilePreview } from '@/react/portainer/gitops/gitops.service';
|
2023-04-20 21:39:55 +00:00
|
|
|
import { KUBE_TEMPLATE_NAME_VALIDATION_REGEX } from '@/constants';
|
2021-09-02 05:28:51 +00:00
|
|
|
|
|
|
|
class KubeEditCustomTemplateViewController {
|
|
|
|
/* @ngInject */
|
2023-02-14 08:19:41 +00:00
|
|
|
constructor($async, $state, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService) {
|
|
|
|
Object.assign(this, { $async, $state, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService });
|
2021-09-02 05:28:51 +00:00
|
|
|
|
2022-06-16 05:32:41 +00:00
|
|
|
this.isTemplateVariablesEnabled = isBE;
|
|
|
|
|
2023-04-04 00:44:42 +00:00
|
|
|
this.formValues = {
|
|
|
|
Variables: [],
|
|
|
|
TLSSkipVerify: false,
|
|
|
|
};
|
2021-09-02 05:28:51 +00:00
|
|
|
this.state = {
|
|
|
|
formValidationError: '',
|
|
|
|
isEditorDirty: false,
|
2022-05-31 10:00:47 +00:00
|
|
|
isTemplateValid: true,
|
2023-04-04 00:44:42 +00:00
|
|
|
isEditorReadOnly: false,
|
|
|
|
templateLoadFailed: false,
|
|
|
|
templatePreviewFailed: false,
|
|
|
|
templatePreviewError: '',
|
2023-04-20 21:39:55 +00:00
|
|
|
templateNameRegex: KUBE_TEMPLATE_NAME_VALIDATION_REGEX,
|
2021-09-02 05:28:51 +00:00
|
|
|
};
|
|
|
|
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);
|
2022-05-31 10:00:47 +00:00
|
|
|
this.handleChange = this.handleChange.bind(this);
|
|
|
|
this.onVariablesChange = this.onVariablesChange.bind(this);
|
2023-04-04 00:44:42 +00:00
|
|
|
this.previewFileFromGitRepository = this.previewFileFromGitRepository.bind(this);
|
2021-09-02 05:28:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getTemplate() {
|
|
|
|
return this.$async(async () => {
|
|
|
|
try {
|
|
|
|
const { id } = this.$state.params;
|
|
|
|
|
2023-04-04 00:44:42 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-05-31 10:00:47 +00:00
|
|
|
template.Variables = template.Variables || [];
|
|
|
|
|
2023-04-04 00:44:42 +00:00
|
|
|
this.formValues = { ...this.formValues, ...template };
|
2022-05-31 10:00:47 +00:00
|
|
|
|
2023-04-04 00:44:42 +00:00
|
|
|
this.parseTemplate(template.FileContent);
|
|
|
|
this.parseGitConfig(template.GitConfig);
|
2022-05-31 10:00:47 +00:00
|
|
|
|
2021-09-02 05:28:51 +00:00
|
|
|
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');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-05-31 10:00:47 +00:00
|
|
|
onVariablesChange(values) {
|
|
|
|
this.handleChange({ Variables: values });
|
|
|
|
}
|
|
|
|
|
|
|
|
handleChange(values) {
|
|
|
|
return this.$async(async () => {
|
|
|
|
this.formValues = {
|
|
|
|
...this.formValues,
|
|
|
|
...values,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
parseTemplate(templateStr) {
|
2022-06-16 05:32:41 +00:00
|
|
|
if (!this.isTemplateVariablesEnabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-31 10:00:47 +00:00
|
|
|
const variables = getTemplateVariables(templateStr);
|
|
|
|
|
|
|
|
const isValid = !!variables;
|
|
|
|
|
|
|
|
this.state.isTemplateValid = isValid;
|
|
|
|
|
|
|
|
if (isValid) {
|
|
|
|
this.onVariablesChange(intersectVariables(this.formValues.Variables, variables));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-04 00:44:42 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-09-02 05:28:51 +00:00
|
|
|
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);
|
|
|
|
|
2022-08-10 05:07:35 +00:00
|
|
|
this.Notifications.success('Success', 'Custom template successfully updated');
|
2021-09-02 05:28:51 +00:00
|
|
|
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;
|
2022-05-31 10:00:47 +00:00
|
|
|
this.parseTemplate(value);
|
2021-09-02 05:28:51 +00:00
|
|
|
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()) {
|
2023-02-14 08:19:41 +00:00
|
|
|
return confirmWebEditorDiscard();
|
2021-09-02 05:28:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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, '');
|
|
|
|
}
|