1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-02 12:25:22 +02:00

feat(custom-templates): migrate create view to react [EE-6400] (#10715)

This commit is contained in:
Chaim Lev-Ari 2023-12-06 14:11:02 +01:00 committed by GitHub
parent bd5ba7b5d0
commit dabcf4f7db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 495 additions and 960 deletions

View file

@ -2,14 +2,12 @@ import angular from 'angular';
import { kubeCustomTemplatesView } from './kube-custom-templates-view';
import { kubeEditCustomTemplateView } from './kube-edit-custom-template-view';
import { kubeCreateCustomTemplateView } from './kube-create-custom-template-view';
export default angular
.module('portainer.kubernetes.custom-templates', [])
.config(config)
.component('kubeCustomTemplatesView', kubeCustomTemplatesView)
.component('kubeEditCustomTemplateView', kubeEditCustomTemplateView)
.component('kubeCreateCustomTemplateView', kubeCreateCustomTemplateView).name;
.component('kubeEditCustomTemplateView', kubeEditCustomTemplateView).name;
function config($stateRegistryProvider) {
const templates = {
@ -38,7 +36,7 @@ function config($stateRegistryProvider) {
views: {
'content@': {
component: 'kubeCreateCustomTemplateView',
component: 'createCustomTemplatesView',
},
},
params: {

View file

@ -1,6 +0,0 @@
import controller from './kube-create-custom-template-view.controller.js';
export const kubeCreateCustomTemplateView = {
templateUrl: './kube-create-custom-template-view.html',
controller,
};

View file

@ -1,242 +0,0 @@
import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
import { getTemplateVariables, intersectVariables, isTemplateVariablesEnabled } from '@/react/portainer/custom-templates/components/utils';
import { editor, upload, git } from '@@/BoxSelector/common-options/build-methods';
import { confirmWebEditorDiscard } from '@@/modals/confirm';
import { KUBE_TEMPLATE_NAME_VALIDATION_REGEX } from '@/constants';
class KubeCreateCustomTemplateViewController {
/* @ngInject */
constructor($async, $state, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService) {
Object.assign(this, { $async, $state, Authentication, CustomTemplateService, FormValidator, Notifications, ResourceControlService });
this.methodOptions = [editor, upload, git];
this.templates = null;
this.isTemplateVariablesEnabled = isTemplateVariablesEnabled;
this.state = {
method: 'editor',
actionInProgress: false,
formValidationError: '',
isEditorDirty: false,
isTemplateValid: true,
};
this.formValues = {
FileContent: '',
File: null,
Title: '',
Description: '',
Note: '',
Logo: '',
AccessControlData: new AccessControlFormData(),
Variables: [],
RepositoryURL: '',
RepositoryURLValid: false,
RepositoryReferenceName: 'refs/heads/main',
RepositoryAuthentication: false,
RepositoryUsername: '',
RepositoryPassword: '',
ComposeFilePathInRepository: 'manifest.yml',
};
this.validationData = {
title: {
pattern: KUBE_TEMPLATE_NAME_VALIDATION_REGEX,
error:
"This field must consist of lower-case alphanumeric characters, '.', '_' or '-', must start and end with an alphanumeric character and must be 63 characters or less (e.g. 'my-name', or 'abc-123').",
},
};
this.onChangeFile = this.onChangeFile.bind(this);
this.onChangeFileContent = this.onChangeFileContent.bind(this);
this.onChangeMethod = this.onChangeMethod.bind(this);
this.onBeforeOnload = this.onBeforeOnload.bind(this);
this.handleChange = this.handleChange.bind(this);
this.onVariablesChange = this.onVariablesChange.bind(this);
this.onChangePlatform = this.onChangePlatform.bind(this);
this.onChangeType = this.onChangeType.bind(this);
}
onChangePlatform(value) {
this.handleChange({ Platform: value });
}
onChangeType(value) {
this.handleChange({ Type: value });
}
onChangeMethod(method) {
this.state.method = method;
this.formValues.Variables = [];
}
onChangeFileContent(content) {
this.handleChange({ FileContent: content });
this.parseTemplate(content);
this.state.isEditorDirty = true;
}
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));
}
}
onVariablesChange(value) {
this.handleChange({ Variables: value });
}
onChangeFile(file) {
this.handleChange({ File: file });
}
handleChange(values) {
return this.$async(async () => {
this.formValues = {
...this.formValues,
...values,
};
});
}
async createCustomTemplate() {
return this.$async(async () => {
const { method } = this.state;
if (!this.validateForm(method)) {
return;
}
this.state.actionInProgress = true;
try {
const customTemplate = await this.createCustomTemplateByMethod(method, this.formValues);
const accessControlData = this.formValues.AccessControlData;
const userDetails = this.Authentication.getUserDetails();
const userId = userDetails.ID;
await this.ResourceControlService.applyResourceControl(userId, accessControlData, customTemplate.ResourceControl);
this.Notifications.success('Success', 'Custom template successfully created');
this.state.isEditorDirty = false;
this.$state.go('kubernetes.templates.custom');
} catch (err) {
this.Notifications.error('Failure', err, 'Failed creating custom template');
} finally {
this.state.actionInProgress = false;
}
});
}
createCustomTemplateByMethod(method, template) {
template.Type = 3;
switch (method) {
case 'editor':
return this.createCustomTemplateFromFileContent(template);
case 'upload':
return this.createCustomTemplateFromFileUpload(template);
case 'repository':
return this.createCustomTemplateFromGitRepository(template);
}
}
createCustomTemplateFromFileContent(template) {
return this.CustomTemplateService.createCustomTemplateFromFileContent(template);
}
createCustomTemplateFromFileUpload(template) {
return this.CustomTemplateService.createCustomTemplateFromFileUpload(template);
}
createCustomTemplateFromGitRepository(template) {
return this.CustomTemplateService.createCustomTemplateFromGitRepository(template);
}
validateForm(method) {
this.state.formValidationError = '';
if (method === 'editor' && this.formValues.FileContent === '') {
this.state.formValidationError = 'Template file content must not be empty';
return false;
}
const title = this.formValues.Title;
const isNotUnique = this.templates.some((template) => template.Title === title);
if (isNotUnique) {
this.state.formValidationError = 'A template with the same name already exists';
return false;
}
if (!this.state.isTemplateValid) {
this.state.formValidationError = 'Template is not valid';
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;
}
async $onInit() {
return this.$async(async () => {
const { fileContent, type } = this.$state.params;
this.formValues.FileContent = fileContent;
this.parseTemplate(fileContent);
if (type) {
this.formValues.Type = +type;
}
try {
this.templates = await this.CustomTemplateService.customTemplates(3);
} catch (err) {
this.Notifications.error('Failure loading', err, 'Failed loading custom templates');
}
this.state.loading = false;
window.addEventListener('beforeunload', this.onBeforeOnload);
});
}
$onDestroy() {
window.removeEventListener('beforeunload', this.onBeforeOnload);
}
isEditorDirty() {
return this.state.method === 'editor' && this.formValues.FileContent && this.state.isEditorDirty;
}
onBeforeOnload(event) {
if (this.isEditorDirty()) {
event.preventDefault();
event.returnValue = '';
}
}
uiCanExit() {
if (this.isEditorDirty()) {
return confirmWebEditorDiscard();
}
}
}
export default KubeCreateCustomTemplateViewController;

View file

@ -1,73 +0,0 @@
<page-header title="'Create Custom template'" breadcrumbs="[{label:'Custom Templates', link:'kubernetes.templates.custom'}, 'Create Custom template']" reload="true"> </page-header>
<div class="row">
<div class="col-sm-12">
<rd-widget>
<rd-widget-body>
<form class="form-horizontal" name="$ctrl.form">
<custom-templates-common-fields values="$ctrl.formValues" on-change="($ctrl.handleChange)" validation-data="$ctrl.validationData"></custom-templates-common-fields>
<!-- build-method -->
<div class="col-sm-12 form-section-title"> Build method </div>
<box-selector slim="true" radio-name="'method'" value="$ctrl.state.method" options="$ctrl.methodOptions" on-change="($ctrl.onChangeMethod)"></box-selector>
<div class="mt-4">
<web-editor-form
ng-if="$ctrl.state.method === 'editor'"
identifier="template-creation-editor"
value="$ctrl.formValues.FileContent"
on-change="($ctrl.onChangeFileContent)"
ng-required="true"
yml="true"
placeholder="Define or paste the content of your manifest file here"
>
<editor-description>
<p>Templates allow deploying any kind of Kubernetes resource (Deployment, Secret, ConfigMap...)</p>
<p>
You can get more information about Kubernetes file format in the
<a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/" target="_blank">official documentation</a>.
</p>
</editor-description>
</web-editor-form>
</div>
<file-upload-form ng-if="$ctrl.state.method === 'upload'" file="$ctrl.formValues.File" on-change="($ctrl.onChangeFile)" ng-required="true">
<file-upload-description> You can upload a Manifest file from your computer. </file-upload-description>
</file-upload-form>
<git-form deploy-method="kubernetes" ng-if="$ctrl.state.method === 'repository'" value="$ctrl.formValues" on-change="($ctrl.handleChange)"></git-form>
<custom-templates-variables-definition-field
ng-if="$ctrl.isTemplateVariablesEnabled"
value="$ctrl.formValues.Variables"
on-change="($ctrl.onVariablesChange)"
is-variables-names-from-parent="$ctrl.state.method === 'editor'"
></custom-templates-variables-definition-field>
<por-access-control-form form-data="$ctrl.formValues.AccessControlData"></por-access-control-form>
<!-- actions -->
<div class="col-sm-12 form-section-title"> Actions </div>
<div class="form-group">
<div class="col-sm-12">
<button
type="button"
class="btn btn-primary btn-sm !ml-0"
ng-disabled="!$ctrl.state.isTemplateValid ||$ctrl.state.actionInProgress || $ctrl.form.$invalid || ($ctrl.state.method === 'editor' && !$ctrl.formValues.FileContent)"
ng-click="$ctrl.createCustomTemplate()"
button-spinner="$ctrl.state.actionInProgress"
>
<span ng-hide="$ctrl.state.actionInProgress">Create custom template</span>
<span ng-show="$ctrl.state.actionInProgress">Creation in progress...</span>
</button>
<span class="text-danger" ng-if="$ctrl.state.formValidationError" style="margin-left: 5px">
{{ $ctrl.state.formValidationError }}
</span>
</div>
</div>
<!-- !actions -->
</form>
</rd-widget-body>
</rd-widget>
</div>
</div>