mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 05:45:22 +02:00
feat(edgestacks): support kubernetes edge stacks (#5276) [EE-393]
This commit is contained in:
parent
79ca51c92e
commit
5c8450c4c0
56 changed files with 1466 additions and 521 deletions
|
@ -1,6 +1,4 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
export class CreateEdgeStackViewController {
|
||||
export default class CreateEdgeStackViewController {
|
||||
/* @ngInject */
|
||||
constructor($state, $window, ModalService, EdgeStackService, EdgeGroupService, EdgeTemplateService, Notifications, FormHelper, $async) {
|
||||
Object.assign(this, { $state, $window, ModalService, EdgeStackService, EdgeGroupService, EdgeTemplateService, Notifications, FormHelper, $async });
|
||||
|
@ -15,8 +13,9 @@ export class CreateEdgeStackViewController {
|
|||
RepositoryUsername: '',
|
||||
RepositoryPassword: '',
|
||||
Env: [],
|
||||
ComposeFilePathInRepository: 'docker-compose.yml',
|
||||
ComposeFilePathInRepository: '',
|
||||
Groups: [],
|
||||
DeploymentType: 0,
|
||||
};
|
||||
|
||||
this.state = {
|
||||
|
@ -25,22 +24,21 @@ export class CreateEdgeStackViewController {
|
|||
actionInProgress: false,
|
||||
StackType: null,
|
||||
isEditorDirty: false,
|
||||
hasKubeEndpoint: false,
|
||||
endpointTypes: [],
|
||||
};
|
||||
|
||||
this.edgeGroups = null;
|
||||
|
||||
this.createStack = this.createStack.bind(this);
|
||||
this.createStackAsync = this.createStackAsync.bind(this);
|
||||
this.validateForm = this.validateForm.bind(this);
|
||||
this.createStackByMethod = this.createStackByMethod.bind(this);
|
||||
this.createStackFromFileContent = this.createStackFromFileContent.bind(this);
|
||||
this.createStackFromFileUpload = this.createStackFromFileUpload.bind(this);
|
||||
this.createStackFromGitRepository = this.createStackFromGitRepository.bind(this);
|
||||
this.editorUpdate = this.editorUpdate.bind(this);
|
||||
this.onChangeTemplate = this.onChangeTemplate.bind(this);
|
||||
this.onChangeTemplateAsync = this.onChangeTemplateAsync.bind(this);
|
||||
this.onChangeMethod = this.onChangeMethod.bind(this);
|
||||
this.onChangeFormValues = this.onChangeFormValues.bind(this);
|
||||
this.onChangeGroups = this.onChangeGroups.bind(this);
|
||||
this.hasDockerEndpoint = this.hasDockerEndpoint.bind(this);
|
||||
this.onChangeDeploymentType = this.onChangeDeploymentType.bind(this);
|
||||
}
|
||||
|
||||
buildAnalyticsProperties() {
|
||||
|
@ -67,7 +65,7 @@ export class CreateEdgeStackViewController {
|
|||
}
|
||||
}
|
||||
|
||||
async uiCanExit() {
|
||||
uiCanExit() {
|
||||
if (this.state.Method === 'editor' && this.formValues.StackFileContent && this.state.isEditorDirty) {
|
||||
return this.ModalService.confirmWebEditorDiscard();
|
||||
}
|
||||
|
@ -81,13 +79,6 @@ export class CreateEdgeStackViewController {
|
|||
this.Notifications.error('Failure', err, 'Unable to retrieve Edge groups');
|
||||
}
|
||||
|
||||
try {
|
||||
const templates = await this.EdgeTemplateService.edgeTemplates();
|
||||
this.templates = _.map(templates, (template) => ({ ...template, label: `${template.title} - ${template.description}` }));
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve Templates');
|
||||
}
|
||||
|
||||
this.$window.onbeforeunload = () => {
|
||||
if (this.state.Method === 'editor' && this.formValues.StackFileContent && this.state.isEditorDirty) {
|
||||
return '';
|
||||
|
@ -100,52 +91,54 @@ export class CreateEdgeStackViewController {
|
|||
}
|
||||
|
||||
createStack() {
|
||||
return this.$async(this.createStackAsync);
|
||||
return this.$async(async () => {
|
||||
const name = this.formValues.Name;
|
||||
let method = this.state.Method;
|
||||
|
||||
if (method === 'template') {
|
||||
method = 'editor';
|
||||
}
|
||||
|
||||
if (!this.validateForm(method)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.actionInProgress = true;
|
||||
try {
|
||||
await this.createStackByMethod(name, method);
|
||||
|
||||
this.Notifications.success('Stack successfully deployed');
|
||||
this.state.isEditorDirty = false;
|
||||
this.$state.go('edge.stacks');
|
||||
} catch (err) {
|
||||
this.Notifications.error('Deployment error', err, 'Unable to deploy stack');
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onChangeMethod() {
|
||||
this.formValues.StackFileContent = '';
|
||||
this.selectedTemplate = null;
|
||||
onChangeGroups(groups) {
|
||||
this.formValues.Groups = groups;
|
||||
|
||||
this.checkIfEndpointTypes(groups);
|
||||
}
|
||||
|
||||
onChangeTemplate(template) {
|
||||
return this.$async(this.onChangeTemplateAsync, template);
|
||||
}
|
||||
checkIfEndpointTypes(groups) {
|
||||
const edgeGroups = groups.map((id) => this.edgeGroups.find((e) => e.Id === id));
|
||||
this.state.endpointTypes = edgeGroups.flatMap((group) => group.EndpointTypes);
|
||||
|
||||
async onChangeTemplateAsync(template) {
|
||||
this.formValues.StackFileContent = '';
|
||||
try {
|
||||
const fileContent = await this.EdgeTemplateService.edgeTemplate(template);
|
||||
this.formValues.StackFileContent = fileContent;
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve Template');
|
||||
if (this.hasDockerEndpoint() && this.formValues.DeploymentType == 1) {
|
||||
this.onChangeDeploymentType(0);
|
||||
}
|
||||
}
|
||||
|
||||
async createStackAsync() {
|
||||
const name = this.formValues.Name;
|
||||
let method = this.state.Method;
|
||||
hasKubeEndpoint() {
|
||||
return this.state.endpointTypes.includes(7);
|
||||
}
|
||||
|
||||
if (method === 'template') {
|
||||
method = 'editor';
|
||||
}
|
||||
|
||||
if (!this.validateForm(method)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.actionInProgress = true;
|
||||
try {
|
||||
await this.createStackByMethod(name, method);
|
||||
|
||||
this.Notifications.success('Stack successfully deployed');
|
||||
this.state.isEditorDirty = false;
|
||||
this.$state.go('edge.stacks');
|
||||
} catch (err) {
|
||||
this.Notifications.error('Deployment error', err, 'Unable to deploy stack');
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
hasDockerEndpoint() {
|
||||
return this.state.endpointTypes.includes(4);
|
||||
}
|
||||
|
||||
validateForm(method) {
|
||||
|
@ -171,31 +164,55 @@ export class CreateEdgeStackViewController {
|
|||
}
|
||||
|
||||
createStackFromFileContent(name) {
|
||||
return this.EdgeStackService.createStackFromFileContent(name, this.formValues.StackFileContent, this.formValues.Groups);
|
||||
const { StackFileContent, Groups, DeploymentType } = this.formValues;
|
||||
|
||||
return this.EdgeStackService.createStackFromFileContent({
|
||||
name,
|
||||
StackFileContent,
|
||||
EdgeGroups: Groups,
|
||||
DeploymentType,
|
||||
});
|
||||
}
|
||||
|
||||
createStackFromFileUpload(name) {
|
||||
return this.EdgeStackService.createStackFromFileUpload(name, this.formValues.StackFile, this.formValues.Groups);
|
||||
const { StackFile, Groups, DeploymentType } = this.formValues;
|
||||
return this.EdgeStackService.createStackFromFileUpload(
|
||||
{
|
||||
Name: name,
|
||||
EdgeGroups: Groups,
|
||||
DeploymentType,
|
||||
},
|
||||
StackFile
|
||||
);
|
||||
}
|
||||
|
||||
createStackFromGitRepository(name) {
|
||||
const { Groups, DeploymentType } = this.formValues;
|
||||
const repositoryOptions = {
|
||||
RepositoryURL: this.formValues.RepositoryURL,
|
||||
RepositoryReferenceName: this.formValues.RepositoryReferenceName,
|
||||
ComposeFilePathInRepository: this.formValues.ComposeFilePathInRepository,
|
||||
FilePathInRepository: this.formValues.ComposeFilePathInRepository,
|
||||
RepositoryAuthentication: this.formValues.RepositoryAuthentication,
|
||||
RepositoryUsername: this.formValues.RepositoryUsername,
|
||||
RepositoryPassword: this.formValues.RepositoryPassword,
|
||||
};
|
||||
return this.EdgeStackService.createStackFromGitRepository(name, repositoryOptions, this.formValues.Groups);
|
||||
return this.EdgeStackService.createStackFromGitRepository(
|
||||
{
|
||||
name,
|
||||
EdgeGroups: Groups,
|
||||
DeploymentType,
|
||||
},
|
||||
repositoryOptions
|
||||
);
|
||||
}
|
||||
|
||||
onChangeFormValues(values) {
|
||||
this.formValues = values;
|
||||
onChangeDeploymentType(deploymentType) {
|
||||
this.formValues.DeploymentType = deploymentType;
|
||||
this.state.Method = 'editor';
|
||||
this.formValues.StackFileContent = '';
|
||||
}
|
||||
|
||||
editorUpdate(cm) {
|
||||
this.formValues.StackFileContent = cm.getValue();
|
||||
this.state.isEditorDirty = true;
|
||||
formIsInvalid() {
|
||||
return this.form.$invalid || !this.formValues.Groups.length || (['template', 'editor'].includes(this.state.Method) && !this.formValues.StackFileContent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Create Edge stack"></rd-header-title>
|
||||
<rd-header-content> <a ui-sref="edge.stacks">Edge Stacks</a> > Create Edge stack </rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal" name="$ctrl.form">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="stack_name" class="col-sm-1 control-label text-left">
|
||||
Name
|
||||
</label>
|
||||
<div class="col-sm-11">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
ng-model="$ctrl.formValues.Name"
|
||||
id="stack_name"
|
||||
name="nameField"
|
||||
placeholder="e.g. mystack"
|
||||
auto-focus
|
||||
required
|
||||
data-cy="edgeStackCreate-nameInput"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Edge Groups
|
||||
</div>
|
||||
<div class="form-group" ng-if="$ctrl.edgeGroups">
|
||||
<div class="col-sm-12">
|
||||
<edge-groups-selector ng-if="!$ctrl.noGroups" model="$ctrl.formValues.Groups" on-change="($ctrl.onChangeGroups)" items="$ctrl.edgeGroups"></edge-groups-selector>
|
||||
</div>
|
||||
<div ng-if="$ctrl.noGroups" class="col-sm-12 small text-muted">
|
||||
No Edge groups are available. Head over to the <a ui-sref="edge.groups">Edge groups view</a> to create one.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<edge-stack-deployment-type-selector
|
||||
value="$ctrl.formValues.DeploymentType"
|
||||
has-docker-endpoint="$ctrl.hasDockerEndpoint"
|
||||
on-change="($ctrl.onChangeDeploymentType)"
|
||||
></edge-stack-deployment-type-selector>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="small text-muted space-right" ng-if="$ctrl.formValues.DeploymentType === 0 && $ctrl.hasKubeEndpoint()">
|
||||
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i>
|
||||
Portainer uses <a href="https://kompose.io/" target="_blank">Kompose</a> to convert your Compose manifest to a Kubernetes compliant manifest. Be wary that not all
|
||||
the Compose format options are supported by Kompose at the moment.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<edge-stacks-docker-compose-form ng-if="$ctrl.formValues.DeploymentType == 0" form-values="$ctrl.formValues" state="$ctrl.state"></edge-stacks-docker-compose-form>
|
||||
|
||||
<edge-stacks-kube-manifest-form ng-if="$ctrl.formValues.DeploymentType == 1" form-values="$ctrl.formValues" state="$ctrl.state"></edge-stacks-kube-manifest-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"
|
||||
ng-disabled="$ctrl.state.actionInProgress || $ctrl.formIsInvalid()"
|
||||
ng-click="$ctrl.createStack()"
|
||||
button-spinner="$ctrl.state.actionInProgress"
|
||||
analytics-on
|
||||
analytics-event="edge-stack-creation"
|
||||
analytics-category="edge"
|
||||
analytics-properties="$ctrl.buildAnalyticsProperties()"
|
||||
>
|
||||
<span ng-hide="$ctrl.state.actionInProgress">Deploy the stack</span>
|
||||
<span ng-show="$ctrl.state.actionInProgress">Deployment 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>
|
|
@ -0,0 +1,6 @@
|
|||
import controller from './create-edge-stack-view.controller';
|
||||
|
||||
export const createEdgeStackView = {
|
||||
templateUrl: './create-edge-stack-view.html',
|
||||
controller,
|
||||
};
|
|
@ -1,220 +0,0 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Create Edge stack"></rd-header-title>
|
||||
<rd-header-content> <a ui-sref="edge.stacks">Edge Stacks</a> > Create Edge stack </rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="stack_name" class="col-sm-1 control-label text-left">
|
||||
Name
|
||||
</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="$ctrl.formValues.Name" id="stack_name" placeholder="e.g. mystack" auto-focus data-cy="edgeStackCreate-nameInput" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Edge Groups
|
||||
</div>
|
||||
<div class="form-group" ng-if="$ctrl.edgeGroups">
|
||||
<div class="col-sm-12">
|
||||
<edge-groups-selector ng-if="!$ctrl.noGroups" model="$ctrl.formValues.Groups" on-change="(onChangeGroups)" items="$ctrl.edgeGroups"></edge-groups-selector>
|
||||
</div>
|
||||
<div ng-if="$ctrl.noGroups" class="col-sm-12 small text-muted">
|
||||
No Edge groups are available. Head over to the <a ui-sref="edge.groups">Edge groups view</a> to create one.
|
||||
</div>
|
||||
</div>
|
||||
<!-- build-method -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Build method
|
||||
</div>
|
||||
<div class="form-group"></div>
|
||||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="method_editor" ng-model="$ctrl.state.Method" value="editor" ng-change="$ctrl.onChangeMethod()" />
|
||||
<label for="method_editor" data-cy="edgeStackCreate-webEditorButton">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-edit" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Web editor
|
||||
</div>
|
||||
<p>Use our Web editor</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_upload" ng-model="$ctrl.state.Method" value="upload" ng-change="$ctrl.onChangeMethod()" />
|
||||
<label for="method_upload" data-cy="edgeStackCreate-uploadButton">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-upload" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Upload
|
||||
</div>
|
||||
<p>Upload from your computer</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_repository" ng-model="$ctrl.state.Method" value="repository" ng-change="$ctrl.onChangeMethod()" />
|
||||
<label for="method_repository" data-cy="edgeStackCreate-repoButton">
|
||||
<div class="boxselector_header">
|
||||
<i class="fab fa-git" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Repository
|
||||
</div>
|
||||
<p>Use a git repository</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_template" ng-model="$ctrl.state.Method" value="template" ng-change="$ctrl.onChangeMethod()" />
|
||||
<label for="method_template" data-cy="edgeStackCreate-templateButton">
|
||||
<div class="boxselector_header">
|
||||
<i class="fas fa-rocket" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Template
|
||||
</div>
|
||||
<p>Use an Edge stack template</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !build-method -->
|
||||
<!-- web-editor -->
|
||||
<div ng-show="$ctrl.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>
|
||||
<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"
|
||||
value="$ctrl.formValues.StackFileContent"
|
||||
on-change="($ctrl.editorUpdate)"
|
||||
></code-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !web-editor -->
|
||||
<!-- upload -->
|
||||
<div ng-show="$ctrl.state.Method === 'upload'">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Upload
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
You can upload a Compose file from your computer.
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-sm btn-primary" ngf-select ng-model="$ctrl.formValues.StackFile">
|
||||
Select file
|
||||
</button>
|
||||
<span style="margin-left: 5px;">
|
||||
{{ $ctrl.formValues.StackFile.name }}
|
||||
<i class="fa fa-times red-icon" ng-if="!$ctrl.formValues.StackFile" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !upload -->
|
||||
<!-- repository -->
|
||||
<git-form ng-show="$ctrl.state.Method === 'repository'" model="$ctrl.formValues" on-change="($ctrl.onChangeFormValues)"></git-form>
|
||||
<!-- !repository -->
|
||||
<!-- template -->
|
||||
<div ng-show="$ctrl.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
|
||||
class="form-control"
|
||||
ng-model="$ctrl.selectedTemplate"
|
||||
ng-options="template as template.label for template in $ctrl.templates"
|
||||
ng-change="$ctrl.onChangeTemplate($ctrl.selectedTemplate)"
|
||||
>
|
||||
<option value="" label="Select an Edge stack template" disabled selected="selected"> </option>
|
||||
</select>
|
||||
</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 -->
|
||||
<!-- editor -->
|
||||
<div ng-if="$ctrl.selectedTemplate && $ctrl.formValues.StackFileContent">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Web editor
|
||||
</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="$ctrl.formValues.StackFileContent"
|
||||
on-change="($ctrl.editorUpdate)"
|
||||
></code-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !editor -->
|
||||
<!-- !template -->
|
||||
<!-- 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"
|
||||
ng-disabled="$ctrl.state.actionInProgress || !$ctrl.formValues.Groups.length
|
||||
|| ($ctrl.state.Method === 'editor' && !$ctrl.formValues.StackFileContent)
|
||||
|| ($ctrl.state.Method === 'upload' && !$ctrl.formValues.StackFile)
|
||||
|| ($ctrl.state.Method === 'repository' && ((!$ctrl.formValues.RepositoryURL || !$ctrl.formValues.ComposeFilePathInRepository) || ($ctrl.formValues.RepositoryAuthentication && (!$ctrl.formValues.RepositoryUsername || !$ctrl.formValues.RepositoryPassword))))
|
||||
|| !$ctrl.formValues.Name"
|
||||
ng-click="$ctrl.createStack()"
|
||||
button-spinner="$ctrl.state.actionInProgress"
|
||||
data-cy="edgeStackCreate-createStackButton"
|
||||
analytics-on
|
||||
analytics-event="edge-stack-creation"
|
||||
analytics-category="edge"
|
||||
analytics-properties="$ctrl.buildAnalyticsProperties()"
|
||||
>
|
||||
<span ng-hide="$ctrl.state.actionInProgress">Deploy the stack</span>
|
||||
<span ng-show="$ctrl.state.actionInProgress">Deployment 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>
|
|
@ -0,0 +1,64 @@
|
|||
class DockerComposeFormController {
|
||||
/* @ngInject */
|
||||
constructor($async, EdgeTemplateService, Notifications) {
|
||||
Object.assign(this, { $async, EdgeTemplateService, Notifications });
|
||||
|
||||
this.methodOptions = [
|
||||
{ id: 'method_editor', icon: 'fa fa-edit', label: 'Web editor', description: 'Use our Web editor', value: 'editor' },
|
||||
{ id: 'method_upload', icon: 'fa fa-upload', label: 'Upload', description: 'Upload from your computer', value: 'upload' },
|
||||
{ id: 'method_repository', icon: 'fab fa-github', label: 'Repository', description: 'Use a git repository', value: 'repository' },
|
||||
{ id: 'method_template', icon: 'fa fa-rocket', label: 'Template', description: 'Use an Edge stack template', value: 'template' },
|
||||
];
|
||||
|
||||
this.selectedTemplate = null;
|
||||
|
||||
this.onChangeFileContent = this.onChangeFileContent.bind(this);
|
||||
this.onChangeFile = this.onChangeFile.bind(this);
|
||||
this.onChangeTemplate = this.onChangeTemplate.bind(this);
|
||||
this.onChangeMethod = this.onChangeMethod.bind(this);
|
||||
this.onChangeFormValues = this.onChangeFormValues.bind(this);
|
||||
}
|
||||
|
||||
onChangeFormValues(values) {
|
||||
this.formValues = values;
|
||||
}
|
||||
|
||||
onChangeMethod() {
|
||||
this.formValues.StackFileContent = '';
|
||||
this.selectedTemplate = null;
|
||||
}
|
||||
|
||||
onChangeTemplate(template) {
|
||||
return this.$async(async () => {
|
||||
this.formValues.StackFileContent = '';
|
||||
try {
|
||||
const fileContent = await this.EdgeTemplateService.edgeTemplate(template);
|
||||
this.formValues.StackFileContent = fileContent;
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve Template');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onChangeFileContent(value) {
|
||||
this.formValues.StackFileContent = value;
|
||||
this.state.isEditorDirty = true;
|
||||
}
|
||||
|
||||
onChangeFile(value) {
|
||||
this.formValues.StackFile = value;
|
||||
}
|
||||
|
||||
async $onInit() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
const templates = await this.EdgeTemplateService.edgeTemplates();
|
||||
this.templates = templates.map((template) => ({ ...template, label: `${template.title} - ${template.description}` }));
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve Templates');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default DockerComposeFormController;
|
|
@ -0,0 +1,74 @@
|
|||
<div class="col-sm-12 form-section-title">
|
||||
Build method
|
||||
</div>
|
||||
<box-selector radio-name="method" ng-model="$ctrl.state.Method" options="$ctrl.methodOptions" on-change="($ctrl.onChangeMethod)"></box-selector>
|
||||
|
||||
<web-editor-form
|
||||
ng-if="$ctrl.state.Method === 'editor'"
|
||||
identifier="stack-creation-editor"
|
||||
value="$ctrl.formValues.StackFileContent"
|
||||
on-change="($ctrl.onChangeFileContent)"
|
||||
ng-required="true"
|
||||
yml="true"
|
||||
placeholder="# Define or paste the content of your docker-compose file here"
|
||||
>
|
||||
<editor-description>
|
||||
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>
|
||||
.
|
||||
</editor-description>
|
||||
</web-editor-form>
|
||||
|
||||
<file-upload-form ng-if="$ctrl.state.Method === 'upload'" file="$ctrl.formValues.StackFile" on-change="($ctrl.onChangeFile)" ng-required="true">
|
||||
<file-upload-description>
|
||||
You can upload a Compose file from your computer.
|
||||
</file-upload-description>
|
||||
</file-upload-form>
|
||||
|
||||
<git-form ng-if="$ctrl.state.Method === 'repository'" model="$ctrl.formValues" on-change="($ctrl.onChangeFormValues)"></git-form>
|
||||
|
||||
<!-- template -->
|
||||
<div ng-if="$ctrl.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
|
||||
class="form-control"
|
||||
ng-model="$ctrl.selectedTemplate"
|
||||
ng-options="template as template.label for template in $ctrl.templates"
|
||||
ng-change="$ctrl.onChangeTemplate($ctrl.selectedTemplate)"
|
||||
>
|
||||
<option value="" label="Select an Edge stack template" disabled selected="selected"> </option>
|
||||
</select>
|
||||
</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 -->
|
||||
|
||||
<web-editor-form
|
||||
ng-if="$ctrl.selectedTemplate && $ctrl.formValues.StackFileContent"
|
||||
identifier="template-content-editor"
|
||||
value="$ctrl.formValues.StackFileContent"
|
||||
on-change="($ctrl.onChangeFileContent)"
|
||||
yml="true"
|
||||
placeholder="# Define or paste the content of your docker-compose file here"
|
||||
ng-required="true"
|
||||
>
|
||||
</web-editor-form>
|
||||
|
||||
<!-- !template -->
|
||||
</div>
|
|
@ -0,0 +1,11 @@
|
|||
import controller from './docker-compose-form.controller.js';
|
||||
|
||||
export const edgeStacksDockerComposeForm = {
|
||||
templateUrl: './docker-compose-form.html',
|
||||
controller,
|
||||
|
||||
bindings: {
|
||||
formValues: '=',
|
||||
state: '=',
|
||||
},
|
||||
};
|
|
@ -1,8 +1,13 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import { CreateEdgeStackViewController } from './createEdgeStackViewController';
|
||||
import { createEdgeStackView } from './create-edge-stack-view';
|
||||
import { edgeStacksDockerComposeForm } from './docker-compose-form';
|
||||
import { kubeManifestForm } from './kube-manifest-form';
|
||||
import { kubeDeployDescription } from './kube-deploy-description';
|
||||
|
||||
angular.module('portainer.edge').component('createEdgeStackView', {
|
||||
templateUrl: './createEdgeStackView.html',
|
||||
controller: CreateEdgeStackViewController,
|
||||
});
|
||||
export default angular
|
||||
.module('portainer.edge.stacks.create', [])
|
||||
.component('createEdgeStackView', createEdgeStackView)
|
||||
.component('edgeStacksDockerComposeForm', edgeStacksDockerComposeForm)
|
||||
.component('edgeStacksKubeManifestForm', kubeManifestForm)
|
||||
.component('kubeDeployDescription', kubeDeployDescription).name;
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export const kubeDeployDescription = {
|
||||
templateUrl: './kube-deploy-description.html',
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
<p>
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
This feature allows you to deploy any kind of Kubernetes resource in this environment (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>
|
|
@ -0,0 +1,11 @@
|
|||
import controller from './kube-manifest-form.controller.js';
|
||||
|
||||
export const kubeManifestForm = {
|
||||
templateUrl: './kube-manifest-form.html',
|
||||
controller,
|
||||
|
||||
bindings: {
|
||||
formValues: '=',
|
||||
state: '=',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
class KubeManifestFormController {
|
||||
/* @ngInject */
|
||||
constructor() {
|
||||
this.methodOptions = [
|
||||
{ id: 'method_editor', icon: 'fa fa-edit', label: 'Web editor', description: 'Use our Web editor', value: 'editor' },
|
||||
{ id: 'method_upload', icon: 'fa fa-upload', label: 'Upload', description: 'Upload from your computer', value: 'upload' },
|
||||
{ id: 'method_repository', icon: 'fab fa-github', label: 'Repository', description: 'Use a git repository', value: 'repository' },
|
||||
];
|
||||
|
||||
this.onChangeFileContent = this.onChangeFileContent.bind(this);
|
||||
this.onChangeFormValues = this.onChangeFormValues.bind(this);
|
||||
this.onChangeFile = this.onChangeFile.bind(this);
|
||||
}
|
||||
|
||||
onChangeFormValues(values) {
|
||||
this.formValues = values;
|
||||
}
|
||||
|
||||
onChangeFileContent(value) {
|
||||
this.state.isEditorDirty = true;
|
||||
this.formValues.StackFileContent = value;
|
||||
}
|
||||
|
||||
onChangeFile(value) {
|
||||
this.formValues.StackFile = value;
|
||||
}
|
||||
}
|
||||
|
||||
export default KubeManifestFormController;
|
|
@ -0,0 +1,26 @@
|
|||
<div class="col-sm-12 form-section-title">
|
||||
Build method
|
||||
</div>
|
||||
<box-selector radio-name="method" ng-model="$ctrl.state.Method" options="$ctrl.methodOptions" on-change="($ctrl.onChangeMethod)"></box-selector>
|
||||
|
||||
<web-editor-form
|
||||
ng-if="$ctrl.state.Method === 'editor'"
|
||||
identifier="stack-creation-editor"
|
||||
value="$ctrl.formValues.StackFileContent"
|
||||
on-change="($ctrl.onChangeFileContent)"
|
||||
yml="true"
|
||||
placeholder="# Define or paste the content of your manifest here"
|
||||
ng-required="true"
|
||||
>
|
||||
<editor-description>
|
||||
<kube-deploy-description></kube-deploy-description>
|
||||
</editor-description>
|
||||
</web-editor-form>
|
||||
|
||||
<file-upload-form ng-if="$ctrl.state.Method === 'upload'" file="$ctrl.formValues.StackFile" on-change="($ctrl.onChangeFile)" ng-required="true">
|
||||
<file-upload-description>
|
||||
<kube-deploy-description></kube-deploy-description>
|
||||
</file-upload-description>
|
||||
</file-upload-form>
|
||||
|
||||
<git-form ng-if="$ctrl.state.Method === 'repository'" deploy-method="kubernetes" model="$ctrl.formValues" on-change="($ctrl.onChangeFormValues)"></git-form>
|
Loading…
Add table
Add a link
Reference in a new issue