mirror of https://github.com/portainer/portainer
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 templatepull/5417/head
parent
6b37235eb4
commit
91653f9c36
|
@ -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);
|
||||
|
|
|
@ -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;
|
|
@ -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>
|
|
@ -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);
|
|
@ -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;
|
|
@ -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',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
class WebEditorFormController {
|
||||
/* @ngInject */
|
||||
constructor() {
|
||||
this.editorUpdate = this.editorUpdate.bind(this);
|
||||
}
|
||||
|
||||
editorUpdate(cm) {
|
||||
this.onChange(cm.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
export default WebEditorFormController;
|
|
@ -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>
|
|
@ -1,7 +1,4 @@
|
|||
class GitFormAdditionalFileItemController {
|
||||
/* @ngInject */
|
||||
constructor() {}
|
||||
|
||||
onChangePath(value) {
|
||||
const fieldIsInvalid = typeof value === 'undefined';
|
||||
if (fieldIsInvalid) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()"
|
||||
|
|
Loading…
Reference in New Issue