refactor(stacks): move custom templates selector to component (#5418)

* feat(app): introduce web-editor form component

* refactor(stacks): move custom templates selector to component

* fix(stacks): validate form for template
pull/5417/head
Chaim Lev-Ari 2021-08-18 14:40:38 +03:00 committed by GitHub
parent 6b37235eb4
commit 91653f9c36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 190 additions and 109 deletions

View File

@ -1,12 +1,18 @@
angular.module('portainer.app').controller('CodeEditorController', function CodeEditorController($document, CodeMirrorService, $scope) {
var ctrl = this;
this.$onChanges = function $onChanges({ value }) {
if (value && value.currentValue && ctrl.editor) {
ctrl.editor.setValue(value.currentValue);
}
};
this.$onInit = function () {
$document.ready(function () {
var editorElement = $document[0].getElementById(ctrl.identifier);
ctrl.editor = CodeMirrorService.applyCodeMirrorOnElement(editorElement, ctrl.yml, ctrl.readOnly);
if (ctrl.onChange) {
ctrl.editor.on('change', (...args) => $scope.$apply(() => ctrl.onChange(...args)));
ctrl.editor.on('change', (...args) => $scope.$evalAsync(() => ctrl.onChange(...args)));
}
if (ctrl.value) {
ctrl.editor.setValue(ctrl.value);

View File

@ -0,0 +1,36 @@
class CustomTemplateSelectorController {
/* @ngInject */
constructor($async, CustomTemplateService, Notifications) {
Object.assign(this, { $async, CustomTemplateService, Notifications });
this.selectedTemplate = null;
this.templates = null;
}
async handleChangeTemplate(templateId) {
this.selectedTemplate = this.templates.find((t) => t.id === templateId);
this.onChange(templateId);
}
$onChanges({ value }) {
if (value && value.currentValue && this.templates) {
this.handleChangeTemplate(value.currentValue);
}
}
$onInit() {
return this.$async(async () => {
try {
const templates = await this.CustomTemplateService.customTemplates(this.stackType);
this.templates = templates.map((template) => ({ ...template, label: `${template.Title} - ${template.Description}` }));
if (this.value) {
this.handleChangeTemplate(this.value);
}
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve Custom Templates');
}
});
}
}
export default CustomTemplateSelectorController;

View File

@ -0,0 +1,32 @@
<div>
<div class="form-group">
<label for="stack_template" class="col-sm-1 control-label text-left">
Template
</label>
<div class="col-sm-11">
<select
ng-if="$ctrl.templates.length"
class="form-control"
ng-model="$ctrl.value"
ng-options="template.Id as template.label for template in $ctrl.templates"
ng-change="$ctrl.handleChangeTemplate($ctrl.value)"
>
<option value="" label="Select a Custom template" disabled selected="selected"> </option>
</select>
<span ng-if="!$ctrl.templates.length"> No custom templates are available. Head over to the <a ui-state="$ctrl.newTemplatePath">custom template view</a> to create one. </span>
</div>
</div>
<!-- description -->
<div ng-if="$ctrl.selectedTemplate.note">
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group">
<div class="col-sm-12">
<div class="template-note" ng-bind-html="$ctrl.selectedTemplate.note"></div>
</div>
</div>
</div>
<!-- !description -->
</div>

View File

@ -0,0 +1,16 @@
import angular from 'angular';
import controller from './custom-template-selector.controller.js';
export const customTemplateSelector = {
templateUrl: './custom-template-selector.html',
controller,
bindings: {
newTemplatePath: '@',
stackType: '<',
value: '<',
onChange: '<',
},
};
angular.module('portainer.app').component('customTemplateSelector', customTemplateSelector);

View File

@ -0,0 +1,5 @@
import angular from 'angular';
import { webEditorForm } from './web-editor-form';
export default angular.module('portainer.app.components.form', []).component('webEditorForm', webEditorForm).name;

View File

@ -0,0 +1,19 @@
import controller from './web-editor-form.controller.js';
export const webEditorForm = {
templateUrl: './web-editor-form.html',
controller,
bindings: {
identifier: '@',
placeholder: '@',
yml: '<',
value: '<',
onChange: '<',
},
transclude: {
description: '?editorDescription',
},
};

View File

@ -0,0 +1,12 @@
class WebEditorFormController {
/* @ngInject */
constructor() {
this.editorUpdate = this.editorUpdate.bind(this);
}
editorUpdate(cm) {
this.onChange(cm.getValue());
}
}
export default WebEditorFormController;

View File

@ -0,0 +1,13 @@
<ng-form name="$ctrl.webEditorForm">
<div class="col-sm-12 form-section-title">
Web editor
</div>
<div class="form-group">
<span class="col-sm-12 text-muted small" ng-transclude="description"> </span>
</div>
<div class="form-group">
<div class="col-sm-12">
<code-editor identifier="{{ $ctrl.identifier }}" placeholder="{{ $ctrl.placeholder }}" yml="$ctrl.yml" value="$ctrl.value" on-change="($ctrl.editorUpdate)"></code-editor>
</div>
</div>
</ng-form>

View File

@ -1,7 +1,4 @@
class GitFormAdditionalFileItemController {
/* @ngInject */
constructor() {}
onChangePath(value) {
const fieldIsInvalid = typeof value === 'undefined';
if (fieldIsInvalid) {

View File

@ -2,5 +2,6 @@ import angular from 'angular';
import gitFormModule from './forms/git-form';
import porAccessManagementModule from './accessManagement';
import formComponentsModule from './form-components';
export default angular.module('portainer.app.components', [gitFormModule, porAccessManagementModule]).name;
export default angular.module('portainer.app.components', [gitFormModule, porAccessManagementModule, formComponentsModule]).name;

View File

@ -1,6 +1,6 @@
import angular from 'angular';
import _ from 'lodash-es';
import uuidv4 from 'uuid/v4';
import { AccessControlFormData } from '../../../components/accessControlForm/porAccessControlFormModel';
angular
@ -25,6 +25,8 @@ angular
WebhookHelper,
clipboard
) {
$scope.onChangeTemplateId = onChangeTemplateId;
$scope.formValues = {
Name: '',
StackFileContent: '',
@ -60,9 +62,9 @@ angular
}
};
$scope.$on('$destroy', function() {
$scope.$on('$destroy', function () {
$scope.state.isEditorDirty = false;
})
});
$scope.onChangeFormValues = onChangeFormValues;
@ -219,8 +221,8 @@ angular
});
};
$scope.editorUpdate = function (cm) {
$scope.formValues.StackFileContent = cm.getValue();
$scope.onChangeFileContent = function onChangeFileContent(value) {
$scope.formValues.StackFileContent = value;
$scope.state.editorYamlValidationError = StackHelper.validateYAML($scope.formValues.StackFileContent, $scope.containerNames);
$scope.state.isEditorDirty = true;
};
@ -244,15 +246,18 @@ angular
}
};
$scope.onChangeTemplate = async function onChangeTemplate(template) {
try {
$scope.formValues.StackFileContent = undefined;
$scope.selectedTemplate = template;
$scope.formValues.StackFileContent = await CustomTemplateService.customTemplateFile(template.Id);
} catch (err) {
Notifications.error('Failure', err, 'Unable to retrieve Custom Template file');
}
};
function onChangeTemplateId(templateId) {
return $async(async () => {
try {
$scope.state.templateId = templateId;
const fileContent = await CustomTemplateService.customTemplateFile(templateId);
$scope.onChangeFileContent(fileContent);
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve Custom Template file');
}
});
}
async function initView() {
var endpointMode = $scope.applicationState.endpoint.mode;
@ -261,13 +266,6 @@ angular
$scope.state.StackType = 1;
}
try {
const templates = await CustomTemplateService.customTemplates($scope.state.StackType);
$scope.templates = _.map(templates, (template) => ({ ...template, label: `${template.Title} - ${template.Description}` }));
} catch (err) {
Notifications.error('Failure', err, 'Unable to retrieve Custom Templates');
}
try {
const endpoint = EndpointProvider.currentEndpoint();
$scope.composeSyntaxMaxVersion = endpoint.ComposeSyntaxMaxVersion;

View File

@ -81,31 +81,7 @@
</div>
</div>
<!-- !build-method -->
<!-- web-editor -->
<div ng-show="state.Method === 'editor'">
<div class="col-sm-12 form-section-title">
Web editor
</div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
You can get more information about Compose file format in the <a href="https://docs.docker.com/compose/compose-file/" target="_blank">official documentation</a>.
</span>
<div class="col-sm-12" ng-if="state.editorYamlValidationError"
><span class="text-danger small">{{ state.editorYamlValidationError }}</span></div
>
</div>
<div class="form-group">
<div class="col-sm-12">
<code-editor
identifier="stack-creation-editor"
placeholder="# Define or paste the content of your docker-compose file here"
yml="true"
on-change="(editorUpdate)"
></code-editor>
</div>
</div>
</div>
<!-- !web-editor -->
<!-- upload -->
<div ng-show="state.Method === 'upload'">
<div class="col-sm-12 form-section-title">
@ -138,63 +114,34 @@
auto-update="true"
show-auth-explanation="true"
></git-form>
<!-- custom-template -->
<div ng-show="state.Method === 'template'">
<div class="form-group">
<label for="stack_template" class="col-sm-1 control-label text-left">
Template
</label>
<div class="col-sm-11">
<select
ng-if="templates.length"
class="form-control"
ng-model="selectedTemplate"
ng-options="template as template.label for template in templates"
ng-change="onChangeTemplate(selectedTemplate)"
>
<option value="" label="Select a Custom template" disabled selected="selected"> </option>
</select>
<span ng-if="!templates.length">
No custom templates are available. Head over to the <a ui-sref="docker.templates.custom.new">custom template view</a> to create one.
</span>
<custom-template-selector
ng-show="state.Method === 'template'"
new-template-path="docker.templates.custom.new"
stack-type="state.StackType"
on-change="(onChangeTemplateId)"
value="state.templateId"
></custom-template-selector>
<web-editor-form
ng-if="state.Method === 'editor' || (state.Method === 'template' && state.templateId)"
identifier="stack-creation-editor"
value="formValues.StackFileContent"
on-change="(onChangeFileContent)"
ng-required="true"
yml="true"
placeholder="# Define or paste the content of your docker-compose file here"
>
<editor-description>
<span class="col-sm-12 text-muted small">
You can get more information about Compose file format in the <a href="https://docs.docker.com/compose/compose-file/" target="_blank">official documentation</a>.
</span>
<div class="col-sm-12" ng-if="state.editorYamlValidationError">
<span class="text-danger small">{{ state.editorYamlValidationError }}</span>
</div>
</div>
<!-- description -->
<div ng-if="selectedTemplate.note">
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group">
<div class="col-sm-12">
<div class="template-note" ng-bind-html="selectedTemplate.note"></div>
</div>
</div>
</div>
<!-- !description -->
<!-- editor -->
<div ng-if="selectedTemplate && formValues.StackFileContent">
<div class="col-sm-12 form-section-title">
Web editor
</div>
<div class="form-group">
<div class="col-sm-12" ng-if="state.editorYamlValidationError"
><span class="text-danger small">{{ state.editorYamlValidationError }}</span></div
>
</div>
<div class="form-group">
<div class="col-sm-12">
<code-editor
identifier="template-content-editor"
placeholder="# Define or paste the content of your docker-compose file here"
yml="true"
value="formValues.StackFileContent"
on-change="(editorUpdate)"
></code-editor>
</div>
</div>
</div>
</div>
<!-- !custom-template -->
</editor-description>
</web-editor-form>
<!-- environment-variables -->
<environment-variables-panel ng-model="formValues.Env" explanation="These values will be used as substitutions in the stack file" on-change="(handleEnvVarChange)">
</environment-variables-panel>
@ -211,9 +158,8 @@
class="btn btn-primary btn-sm"
ng-disabled="state.actionInProgress
|| !createStackForm.$valid
|| (state.Method === 'editor' && (!formValues.StackFileContent || state.editorYamlValidationError))
|| ((state.Method === 'editor' || state.Method === 'template') && (!formValues.StackFileContent || state.editorYamlValidationError))
|| (state.Method === 'upload' && (!formValues.StackFile || state.uploadYamlValidationError))
|| (state.Method === 'template' && (!formValues.StackFileContent || !selectedTemplate || state.editorYamlValidationError))
|| (state.Method === 'repository' && ((!formValues.RepositoryURL || !formValues.ComposeFilePathInRepository) || (formValues.RepositoryAuthentication && !formValues.RepositoryPassword)))
|| !formValues.Name"
ng-click="deployStack()"