mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 22:05:23 +02:00
refactor(edge/stacks): migrate create view to react [EE-2223] (#11575)
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:s390x platform:linux version:]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:s390x platform:linux version:]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run
This commit is contained in:
parent
f22aed34b5
commit
8a81d95253
64 changed files with 1878 additions and 1005 deletions
|
@ -69,7 +69,7 @@ angular
|
|||
url: '/new?templateId&templateType',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'createEdgeStackView',
|
||||
component: 'edgeStacksCreateView',
|
||||
},
|
||||
},
|
||||
data: {
|
||||
|
|
|
@ -13,7 +13,6 @@ import { withUIRouter } from '@/react-tools/withUIRouter';
|
|||
import { EdgeGroupAssociationTable } from '@/react/edge/components/EdgeGroupAssociationTable';
|
||||
import { AssociatedEdgeEnvironmentsSelector } from '@/react/edge/components/AssociatedEdgeEnvironmentsSelector';
|
||||
import { EnvironmentsDatatable } from '@/react/edge/edge-stacks/ItemView/EnvironmentsDatatable';
|
||||
import { TemplateFieldset } from '@/react/edge/edge-stacks/CreateView/TemplateFieldset/TemplateFieldset';
|
||||
|
||||
import { edgeJobsModule } from './edge-jobs';
|
||||
|
||||
|
@ -102,10 +101,6 @@ const ngModule = angular
|
|||
'onChange',
|
||||
'value',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'edgeStackCreateTemplateFieldset',
|
||||
r2a(withReactQuery(TemplateFieldset), ['setValues', 'values', 'errors'])
|
||||
);
|
||||
|
||||
export const componentsModule = ngModule.name;
|
||||
|
|
13
app/edge/react/views/edge-stacks.ts
Normal file
13
app/edge/react/views/edge-stacks.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import { r2a } from '@/react-tools/react2angular';
|
||||
import { withCurrentUser } from '@/react-tools/withCurrentUser';
|
||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
||||
import { CreateView } from '@/react/edge/edge-stacks/CreateView/CreateView';
|
||||
|
||||
export const stacksModule = angular
|
||||
.module('portainer.edge.react.views.stacks', [])
|
||||
.component(
|
||||
'edgeStacksCreateView',
|
||||
r2a(withCurrentUser(withUIRouter(CreateView)), [])
|
||||
).name;
|
|
@ -10,9 +10,14 @@ import { ListView as EdgeGroupsListView } from '@/react/edge/edge-groups/ListVie
|
|||
|
||||
import { templatesModule } from './templates';
|
||||
import { jobsModule } from './jobs';
|
||||
import { stacksModule } from './edge-stacks';
|
||||
|
||||
export const viewsModule = angular
|
||||
.module('portainer.edge.react.views', [templatesModule, jobsModule])
|
||||
.module('portainer.edge.react.views', [
|
||||
templatesModule,
|
||||
jobsModule,
|
||||
stacksModule,
|
||||
])
|
||||
.component(
|
||||
'waitingRoomView',
|
||||
r2a(withUIRouter(withReactQuery(withCurrentUser(WaitingRoomView))), [])
|
||||
|
|
|
@ -1,432 +0,0 @@
|
|||
import { DeploymentType, EditorType } from '@/react/edge/edge-stacks/types';
|
||||
import { getValidEditorTypes } from '@/react/edge/edge-stacks/utils';
|
||||
import { STACK_NAME_VALIDATION_REGEX } from '@/react/constants';
|
||||
import { confirmWebEditorDiscard } from '@@/modals/confirm';
|
||||
import { baseEdgeStackWebhookUrl } from '@/portainer/helpers/webhookHelper';
|
||||
import { EnvironmentType } from '@/react/portainer/environments/types';
|
||||
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||
import { getCustomTemplate } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplate';
|
||||
import { notifyError } from '@/portainer/services/notifications';
|
||||
import { getCustomTemplateFile } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplateFile';
|
||||
import { toGitFormModel } from '@/react/portainer/gitops/types';
|
||||
import { StackType } from '@/react/common/stacks/types';
|
||||
import { applySetStateAction } from '@/react-tools/apply-set-state-action';
|
||||
import { getVariablesFieldDefaultValues } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesField';
|
||||
import { renderTemplate } from '@/react/portainer/custom-templates/components/utils';
|
||||
import { getInitialTemplateValues } from '@/react/edge/edge-stacks/CreateView/TemplateFieldset/TemplateFieldset';
|
||||
import { getAppTemplates } from '@/react/portainer/templates/app-templates/queries/useAppTemplates';
|
||||
import { fetchFilePreview } from '@/react/portainer/templates/app-templates/queries/useFetchTemplateFile';
|
||||
import { TemplateViewModel } from '@/react/portainer/templates/app-templates/view-model';
|
||||
import { getDefaultValues as getAppVariablesDefaultValues } from '@/react/portainer/templates/app-templates/DeployFormWidget/EnvVarsFieldset';
|
||||
|
||||
export default class CreateEdgeStackViewController {
|
||||
/* @ngInject */
|
||||
constructor($state, $window, EdgeStackService, EdgeGroupService, Notifications, FormHelper, $async, $scope) {
|
||||
Object.assign(this, { $state, $window, EdgeStackService, EdgeGroupService, Notifications, FormHelper, $async, $scope });
|
||||
|
||||
this.formValues = {
|
||||
Name: '',
|
||||
StackFileContent: '',
|
||||
StackFile: null,
|
||||
RepositoryURL: '',
|
||||
RepositoryReferenceName: '',
|
||||
RepositoryAuthentication: false,
|
||||
RepositoryUsername: '',
|
||||
RepositoryPassword: '',
|
||||
ComposeFilePathInRepository: '',
|
||||
Groups: [],
|
||||
DeploymentType: 0,
|
||||
UseManifestNamespaces: false,
|
||||
TLSSkipVerify: false,
|
||||
envVars: [],
|
||||
};
|
||||
|
||||
this.EditorType = EditorType;
|
||||
this.EnvironmentType = EnvironmentType;
|
||||
this.isBE = isBE;
|
||||
|
||||
this.state = {
|
||||
Method: 'editor',
|
||||
formValidationError: '',
|
||||
actionInProgress: false,
|
||||
StackType: null,
|
||||
isEditorDirty: false,
|
||||
hasKubeEndpoint: false,
|
||||
endpointTypes: [],
|
||||
baseWebhookUrl: baseEdgeStackWebhookUrl(),
|
||||
isEdit: false,
|
||||
templateValues: getInitialTemplateValues(),
|
||||
};
|
||||
|
||||
this.edgeGroups = null;
|
||||
|
||||
$scope.STACK_NAME_VALIDATION_REGEX = STACK_NAME_VALIDATION_REGEX;
|
||||
|
||||
this.createStack = this.createStack.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.onChangeGroups = this.onChangeGroups.bind(this);
|
||||
this.hasType = this.hasType.bind(this);
|
||||
this.onChangeDeploymentType = this.onChangeDeploymentType.bind(this);
|
||||
this.onEnvVarChange = this.onEnvVarChange.bind(this);
|
||||
this.setTemplateValues = this.setTemplateValues.bind(this);
|
||||
this.onChangeTemplate = this.onChangeTemplate.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('react').SetStateAction<import('@/react/edge/edge-stacks/CreateView/TemplateFieldset/types').Values>} templateAction
|
||||
*/
|
||||
setTemplateValues(templateAction) {
|
||||
return this.$async(async () => {
|
||||
const newTemplateValues = applySetStateAction(templateAction, this.state.templateValues);
|
||||
const oldTemplateId = this.state.templateValues.template && this.state.templateValues.template.Id;
|
||||
const newTemplateId = newTemplateValues.template && newTemplateValues.template.Id;
|
||||
this.state.templateValues = newTemplateValues;
|
||||
if (newTemplateId !== oldTemplateId) {
|
||||
await this.onChangeTemplate(newTemplateValues.type, newTemplateValues.template);
|
||||
}
|
||||
|
||||
if (newTemplateValues.type === 'custom') {
|
||||
const definitions = this.state.templateValues.template.Variables;
|
||||
const newFile = renderTemplate(this.state.templateValues.file, this.state.templateValues.variables, definitions);
|
||||
|
||||
this.formValues.StackFileContent = newFile;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onChangeTemplate(type, template) {
|
||||
return this.$async(async () => {
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'custom') {
|
||||
this.formValues = {
|
||||
...this.formValues,
|
||||
DeploymentType: template.Type === StackType.Kubernetes ? DeploymentType.Kubernetes : DeploymentType.Compose,
|
||||
...toGitFormModel(template.GitConfig),
|
||||
...(template.EdgeSettings
|
||||
? {
|
||||
PrePullImage: template.EdgeSettings.PrePullImage || false,
|
||||
RetryDeploy: template.EdgeSettings.RetryDeploy || false,
|
||||
PrivateRegistryId: template.EdgeSettings.PrivateRegistryId || null,
|
||||
...template.EdgeSettings.RelativePathSettings,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
const fileContent = await getCustomTemplateFile({ id: template.Id, git: !!template.GitConfig });
|
||||
this.state.templateValues.file = fileContent;
|
||||
}
|
||||
|
||||
if (type === 'app') {
|
||||
this.formValues.StackFileContent = '';
|
||||
try {
|
||||
const fileContent = await fetchFilePreview(template.Id);
|
||||
this.formValues.StackFileContent = fileContent;
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve Template');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onEnvVarChange(envVars) {
|
||||
return this.$scope.$evalAsync(() => {
|
||||
this.formValues.envVars = envVars;
|
||||
});
|
||||
}
|
||||
|
||||
buildAnalyticsProperties() {
|
||||
const format = 'compose';
|
||||
const metadata = { type: methodLabel(this.state.Method), format };
|
||||
|
||||
if (metadata.type === 'template') {
|
||||
metadata.templateName = this.state.selectedTemplate && this.state.selectedTemplate.title;
|
||||
}
|
||||
|
||||
return { metadata };
|
||||
|
||||
function methodLabel(method) {
|
||||
switch (method) {
|
||||
case 'editor':
|
||||
return 'web-editor';
|
||||
case 'repository':
|
||||
return 'git';
|
||||
case 'upload':
|
||||
return 'file-upload';
|
||||
case 'template':
|
||||
return 'template';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uiCanExit() {
|
||||
if (this.state.Method === 'editor' && this.formValues.StackFileContent && this.state.isEditorDirty) {
|
||||
return confirmWebEditorDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {'app' | 'custom'} templateType
|
||||
* @param {number} templateId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async preSelectTemplate(templateType, templateId) {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.state.Method = 'template';
|
||||
const template = await getTemplate(templateType, templateId);
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setTemplateValues({
|
||||
template,
|
||||
type: templateType,
|
||||
envVars: templateType === 'app' ? getAppVariablesDefaultValues(template.Env) : {},
|
||||
variables: templateType === 'custom' ? getVariablesFieldDefaultValues(template.Variables) : [],
|
||||
});
|
||||
} catch (e) {
|
||||
notifyError('Failed loading template', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async $onInit() {
|
||||
try {
|
||||
this.edgeGroups = await this.EdgeGroupService.groups();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve Edge groups');
|
||||
}
|
||||
|
||||
const templateId = parseInt(this.$state.params.templateId, 10);
|
||||
const templateType = this.$state.params.templateType;
|
||||
if (templateType && templateId && !Number.isNaN(templateId)) {
|
||||
this.preSelectTemplate(templateType, templateId);
|
||||
}
|
||||
|
||||
this.$window.onbeforeunload = () => {
|
||||
if (this.state.Method === 'editor' && this.formValues.StackFileContent && this.state.isEditorDirty) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$onDestroy() {
|
||||
this.state.isEditorDirty = false;
|
||||
}
|
||||
|
||||
createStack() {
|
||||
return this.$async(async () => {
|
||||
const name = this.formValues.Name;
|
||||
|
||||
if (!this.validateTemplate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let envVars = this.formValues.envVars;
|
||||
if (this.state.Method === 'template' && this.state.templateValues.type === 'app') {
|
||||
envVars = [...envVars, ...Object.entries(this.state.templateValues.envVars).map(([key, value]) => ({ name: key, value }))];
|
||||
}
|
||||
|
||||
const method = getMethod(this.state.Method, this.state.templateValues.template);
|
||||
|
||||
if (!this.validateForm(method)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.actionInProgress = true;
|
||||
try {
|
||||
await this.createStackByMethod(name, method, envVars);
|
||||
|
||||
this.Notifications.success('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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onChangeGroups(groups) {
|
||||
return this.$scope.$evalAsync(() => {
|
||||
this.formValues.Groups = groups;
|
||||
|
||||
this.checkIfEndpointTypes(groups);
|
||||
});
|
||||
}
|
||||
|
||||
checkIfEndpointTypes(groups) {
|
||||
return this.$scope.$evalAsync(() => {
|
||||
const edgeGroups = groups.map((id) => this.edgeGroups.find((e) => e.Id === id));
|
||||
this.state.endpointTypes = edgeGroups.flatMap((group) => group.EndpointTypes);
|
||||
this.selectValidDeploymentType();
|
||||
});
|
||||
}
|
||||
|
||||
selectValidDeploymentType() {
|
||||
const validTypes = getValidEditorTypes(this.state.endpointTypes);
|
||||
|
||||
if (!validTypes.includes(this.formValues.DeploymentType)) {
|
||||
this.onChangeDeploymentType(validTypes[0]);
|
||||
}
|
||||
}
|
||||
|
||||
hasType(envType) {
|
||||
return this.state.endpointTypes.includes(envType);
|
||||
}
|
||||
|
||||
validateForm(method) {
|
||||
this.state.formValidationError = '';
|
||||
|
||||
if (method === 'editor' && this.formValues.StackFileContent === '') {
|
||||
this.state.formValidationError = 'Stack file content must not be empty';
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
createStackByMethod(name, method, envVars) {
|
||||
switch (method) {
|
||||
case 'editor':
|
||||
return this.createStackFromFileContent(name, envVars);
|
||||
case 'upload':
|
||||
return this.createStackFromFileUpload(name, envVars);
|
||||
case 'repository':
|
||||
return this.createStackFromGitRepository(name, envVars);
|
||||
}
|
||||
}
|
||||
|
||||
createStackFromFileContent(name, envVars) {
|
||||
const { StackFileContent, Groups, DeploymentType, UseManifestNamespaces } = this.formValues;
|
||||
|
||||
return this.EdgeStackService.createStackFromFileContent({
|
||||
name,
|
||||
StackFileContent,
|
||||
EdgeGroups: Groups,
|
||||
DeploymentType,
|
||||
UseManifestNamespaces,
|
||||
envVars,
|
||||
});
|
||||
}
|
||||
|
||||
createStackFromFileUpload(name, envVars) {
|
||||
const { StackFile, Groups, DeploymentType, UseManifestNamespaces } = this.formValues;
|
||||
|
||||
return this.EdgeStackService.createStackFromFileUpload(
|
||||
{
|
||||
Name: name,
|
||||
EdgeGroups: Groups,
|
||||
DeploymentType,
|
||||
UseManifestNamespaces,
|
||||
envVars,
|
||||
},
|
||||
StackFile
|
||||
);
|
||||
}
|
||||
|
||||
async createStackFromGitRepository(name, envVars) {
|
||||
const { Groups, DeploymentType, UseManifestNamespaces } = this.formValues;
|
||||
|
||||
const repositoryOptions = {
|
||||
RepositoryURL: this.formValues.RepositoryURL,
|
||||
RepositoryReferenceName: this.formValues.RepositoryReferenceName,
|
||||
FilePathInRepository: this.formValues.ComposeFilePathInRepository,
|
||||
RepositoryAuthentication: this.formValues.RepositoryAuthentication,
|
||||
RepositoryUsername: this.formValues.RepositoryUsername,
|
||||
RepositoryPassword: this.formValues.RepositoryPassword,
|
||||
TLSSkipVerify: this.formValues.TLSSkipVerify,
|
||||
CreatedFromCustomTemplateID: this.state.templateValues.template && this.state.templateValues.template.Id,
|
||||
};
|
||||
return this.EdgeStackService.createStackFromGitRepository(
|
||||
{
|
||||
name,
|
||||
EdgeGroups: Groups,
|
||||
DeploymentType,
|
||||
UseManifestNamespaces,
|
||||
envVars,
|
||||
},
|
||||
repositoryOptions
|
||||
);
|
||||
}
|
||||
|
||||
onChangeDeploymentType(deploymentType) {
|
||||
return this.$scope.$evalAsync(() => {
|
||||
this.formValues.DeploymentType = deploymentType;
|
||||
this.state.Method = 'editor';
|
||||
this.formValues.StackFileContent = '';
|
||||
this.state.templateValues = getInitialTemplateValues();
|
||||
});
|
||||
}
|
||||
|
||||
validateTemplate() {
|
||||
if (this.state.Method === 'template' && this.state.templateValues.type === 'app') {
|
||||
return Object.entries(this.state.templateValues.envVars).every(([, value]) => !!value);
|
||||
}
|
||||
|
||||
if (this.state.Method === 'template' && this.state.templateValues.type === 'custom') {
|
||||
return Object.entries(this.state.templateValues.variables).every(([, v]) => {
|
||||
return !!v.value;
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
formIsInvalid() {
|
||||
return (
|
||||
this.form.$invalid ||
|
||||
!this.formValues.Groups.length ||
|
||||
(['template', 'editor'].includes(this.state.Method) && !this.formValues.StackFileContent) ||
|
||||
('upload' === this.state.Method && !this.formValues.StackFile) ||
|
||||
!this.validateTemplate()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {'template'|'repository' | 'editor' | 'upload'} method
|
||||
* @param {import('@/react/portainer/templates/custom-templates/types').CustomTemplate | undefined} template
|
||||
* @returns 'repository' | 'editor' | 'upload'
|
||||
*/
|
||||
function getMethod(method, template) {
|
||||
if (method !== 'template') {
|
||||
return method;
|
||||
}
|
||||
|
||||
if (template && template.GitConfig) {
|
||||
return 'repository';
|
||||
}
|
||||
return 'editor';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {'app' | 'custom'} templateType
|
||||
* @param {number} templateId
|
||||
* @returns {Promise<import('@/react/portainer/templates/app-templates/view-model').TemplateViewModel | import('@/react/portainer/templates/custom-templates/types').CustomTemplate | undefined>}
|
||||
*/
|
||||
async function getTemplate(templateType, templateId) {
|
||||
if (!['app', 'custom'].includes(templateType)) {
|
||||
notifyError('Invalid template type', `Invalid template type: ${templateType}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (templateType === 'app') {
|
||||
const templatesResponse = await getAppTemplates();
|
||||
const template = templatesResponse.templates.find((t) => t.id === templateId);
|
||||
return new TemplateViewModel(template, templatesResponse.version);
|
||||
}
|
||||
|
||||
const template = await getCustomTemplate(templateId);
|
||||
return template;
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
<page-header title="'Create Edge stack'" breadcrumbs="[{label:'Edge Stacks', link:'edge.stacks'}, 'Create Edge stack']" reload="true"> </page-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 required text-left"> Name </label>
|
||||
<div class="col-sm-11">
|
||||
<input
|
||||
type="text"
|
||||
data-cy="edgeStackCreate-nameInput"
|
||||
class="form-control"
|
||||
ng-model="$ctrl.formValues.Name"
|
||||
id="stack_name"
|
||||
name="nameField"
|
||||
ng-pattern="$ctrl.formValues.DeploymentType === $ctrl.EditorType.Compose ? STACK_NAME_VALIDATION_REGEX : ''"
|
||||
placeholder="e.g. mystack"
|
||||
auto-focus
|
||||
required
|
||||
/>
|
||||
<div class="help-block" ng-show="$ctrl.form.$invalid">
|
||||
<div class="small text-warning">
|
||||
<div ng-messages="$ctrl.form.$error">
|
||||
<p ng-message="required" class="vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
<span>Name is required.</span>
|
||||
</p>
|
||||
<p ng-message="pattern" class="vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
<span>This field must consist of lower case alphanumeric characters, '_' or '-' (e.g. 'my-name', or 'abc-123').</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
|
||||
<edge-groups-selector ng-if="$ctrl.formValues.Groups" value="$ctrl.formValues.Groups" on-change="($ctrl.onChangeGroups)"></edge-groups-selector>
|
||||
|
||||
<p class="col-sm-12 vertical-center help-block small text-warning" ng-if="$ctrl.formValues.DeploymentType === undefined">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> There are no available deployment types when there is more than one type of environment in your edge group
|
||||
selection (e.g. Kubernetes and Docker environments). Please select edge groups that have environments of the same type.
|
||||
</p>
|
||||
|
||||
<edge-stack-deployment-type-selector
|
||||
value="$ctrl.formValues.DeploymentType"
|
||||
has-docker-endpoint="$ctrl.hasType($ctrl.EnvironmentType.EdgeAgentOnDocker)"
|
||||
has-kube-endpoint="$ctrl.hasType($ctrl.EnvironmentType.EdgeAgentOnKubernetes)"
|
||||
on-change="($ctrl.onChangeDeploymentType)"
|
||||
></edge-stack-deployment-type-selector>
|
||||
|
||||
<edge-stacks-docker-compose-form
|
||||
ng-if="$ctrl.formValues.DeploymentType == $ctrl.EditorType.Compose"
|
||||
form-values="$ctrl.formValues"
|
||||
state="$ctrl.state"
|
||||
template-values="$ctrl.state.templateValues"
|
||||
set-template-values="$ctrl.setTemplateValues"
|
||||
></edge-stacks-docker-compose-form>
|
||||
|
||||
<edge-stacks-kube-manifest-form
|
||||
ng-if="$ctrl.formValues.DeploymentType == $ctrl.EditorType.Kubernetes"
|
||||
form-values="$ctrl.formValues"
|
||||
state="$ctrl.state"
|
||||
></edge-stacks-kube-manifest-form>
|
||||
|
||||
<div ng-if="$ctrl.isBE">
|
||||
<environment-variables-panel values="$ctrl.formValues.envVars" on-change="($ctrl.onEnvVarChange)"></environment-variables-panel>
|
||||
</div>
|
||||
|
||||
<!-- 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()"
|
||||
data-cy="edgeStackCreate-createStackButton"
|
||||
>
|
||||
<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>
|
|
@ -1,6 +0,0 @@
|
|||
import controller from './create-edge-stack-view.controller';
|
||||
|
||||
export const createEdgeStackView = {
|
||||
templateUrl: './create-edge-stack-view.html',
|
||||
controller,
|
||||
};
|
|
@ -1,51 +0,0 @@
|
|||
import { getInitialTemplateValues } from '@/react/edge/edge-stacks/CreateView/TemplateFieldset/TemplateFieldset';
|
||||
import { editor, git, edgeStackTemplate, upload } from '@@/BoxSelector/common-options/build-methods';
|
||||
|
||||
class DockerComposeFormController {
|
||||
/* @ngInject */
|
||||
constructor($async, Notifications) {
|
||||
Object.assign(this, { $async, Notifications });
|
||||
|
||||
this.methodOptions = [editor, upload, git, edgeStackTemplate];
|
||||
|
||||
this.onChangeFileContent = this.onChangeFileContent.bind(this);
|
||||
this.onChangeFile = this.onChangeFile.bind(this);
|
||||
this.onChangeMethod = this.onChangeMethod.bind(this);
|
||||
this.onChangeFormValues = this.onChangeFormValues.bind(this);
|
||||
this.isGitTemplate = this.isGitTemplate.bind(this);
|
||||
}
|
||||
|
||||
isGitTemplate() {
|
||||
return this.state.Method === 'template' && !!this.templateValues.template && !!this.templateValues.template.GitConfig;
|
||||
}
|
||||
|
||||
onChangeFormValues(newValues) {
|
||||
return this.$async(async () => {
|
||||
this.formValues = {
|
||||
...this.formValues,
|
||||
...newValues,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
onChangeMethod(method) {
|
||||
this.state.Method = method;
|
||||
this.formValues.StackFileContent = '';
|
||||
this.setTemplateValues(getInitialTemplateValues());
|
||||
}
|
||||
|
||||
onChangeFileContent(value) {
|
||||
return this.$async(async () => {
|
||||
this.formValues.StackFileContent = value;
|
||||
this.state.isEditorDirty = true;
|
||||
});
|
||||
}
|
||||
|
||||
onChangeFile(value) {
|
||||
return this.$async(async () => {
|
||||
this.formValues.StackFile = value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default DockerComposeFormController;
|
|
@ -1,41 +0,0 @@
|
|||
<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>
|
||||
|
||||
<!-- template -->
|
||||
<div ng-if="$ctrl.state.Method === 'template'">
|
||||
<edge-stack-create-template-fieldset values="$ctrl.templateValues" set-values="$ctrl.setTemplateValues"></edge-stack-create-template-fieldset>
|
||||
</div>
|
||||
<!-- !template -->
|
||||
|
||||
<web-editor-form
|
||||
ng-if="$ctrl.state.Method === 'editor' || ($ctrl.state.Method === 'template' && $ctrl.templateValues.template)"
|
||||
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"
|
||||
versions="$ctrl.formValues.versions"
|
||||
read-only="$ctrl.isGitTemplate()"
|
||||
>
|
||||
<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>
|
||||
|
||||
<div ng-if="$ctrl.state.Method == 'repository' || $ctrl.isGitTemplate()">
|
||||
<git-form
|
||||
value="$ctrl.formValues"
|
||||
on-change="($ctrl.onChangeFormValues)"
|
||||
base-webhook-url="{{ $ctrl.state.baseWebhookUrl }}"
|
||||
webhook-id="{{ $ctrl.state.webhookId }}"
|
||||
created-from-custom-template-id="($ctrl.state.templateValues.type === 'custom' ? $ctrl.state.templateValues.template.Id : 0)"
|
||||
docs-links
|
||||
></git-form>
|
||||
</div>
|
|
@ -1,13 +0,0 @@
|
|||
import controller from './docker-compose-form.controller.js';
|
||||
|
||||
export const edgeStacksDockerComposeForm = {
|
||||
templateUrl: './docker-compose-form.html',
|
||||
controller,
|
||||
|
||||
bindings: {
|
||||
formValues: '=',
|
||||
state: '=',
|
||||
templateValues: '<',
|
||||
setTemplateValues: '<',
|
||||
},
|
||||
};
|
|
@ -1,13 +0,0 @@
|
|||
import angular from 'angular';
|
||||
|
||||
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';
|
||||
|
||||
export default angular
|
||||
.module('portainer.edge.stacks.create', [])
|
||||
.component('createEdgeStackView', createEdgeStackView)
|
||||
.component('edgeStacksDockerComposeForm', edgeStacksDockerComposeForm)
|
||||
.component('edgeStacksKubeManifestForm', kubeManifestForm)
|
||||
.component('kubeDeployDescription', kubeDeployDescription).name;
|
|
@ -1,3 +0,0 @@
|
|||
export const kubeDeployDescription = {
|
||||
templateUrl: './kube-deploy-description.html',
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
<div>Templates allow deploying any kind of Kubernetes resource (Deployment, Secret, ConfigMap...)</div>
|
||||
<div>
|
||||
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>.
|
||||
</div>
|
|
@ -1,11 +0,0 @@
|
|||
import controller from './kube-manifest-form.controller.js';
|
||||
|
||||
export const kubeManifestForm = {
|
||||
templateUrl: './kube-manifest-form.html',
|
||||
controller,
|
||||
|
||||
bindings: {
|
||||
formValues: '=',
|
||||
state: '=',
|
||||
},
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
import { editor, git, upload } from '@@/BoxSelector/common-options/build-methods';
|
||||
|
||||
class KubeManifestFormController {
|
||||
/* @ngInject */
|
||||
constructor($async) {
|
||||
Object.assign(this, { $async });
|
||||
|
||||
this.methodOptions = [editor, upload, git];
|
||||
|
||||
this.onChangeFileContent = this.onChangeFileContent.bind(this);
|
||||
this.onChangeFormValues = this.onChangeFormValues.bind(this);
|
||||
this.onChangeFile = this.onChangeFile.bind(this);
|
||||
this.onChangeMethod = this.onChangeMethod.bind(this);
|
||||
this.onChangeUseManifestNamespaces = this.onChangeUseManifestNamespaces.bind(this);
|
||||
}
|
||||
|
||||
onChangeFormValues(newValues) {
|
||||
return this.$async(async () => {
|
||||
this.formValues = {
|
||||
...this.formValues,
|
||||
...newValues,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
onChangeUseManifestNamespaces(value) {
|
||||
this.onChangeFormValues({ UseManifestNamespaces: value });
|
||||
}
|
||||
|
||||
onChangeFileContent(value) {
|
||||
this.state.isEditorDirty = true;
|
||||
this.formValues.StackFileContent = value;
|
||||
}
|
||||
|
||||
onChangeFile(value) {
|
||||
return this.$async(async () => {
|
||||
this.formValues.StackFile = value;
|
||||
});
|
||||
}
|
||||
|
||||
onChangeMethod(method) {
|
||||
return this.$async(async () => {
|
||||
this.state.Method = method;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default KubeManifestFormController;
|
|
@ -1,42 +0,0 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
label="'Use namespace(s) specified from manifest'"
|
||||
tooltip="'If you have defined namespaces in your deployment file turning this on will enforce the use of those only in the deployment'"
|
||||
checked="$ctrl.formValues.UseManifestNamespaces"
|
||||
on-change="($ctrl.onChangeUseManifestNamespaces)"
|
||||
></por-switch-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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"
|
||||
value="$ctrl.formValues"
|
||||
on-change="($ctrl.onChangeFormValues)"
|
||||
base-webhook-url="{{ $ctrl.state.baseWebhookUrl }}"
|
||||
webhook-id="{{ $ctrl.state.webhookId }}"
|
||||
></git-form>
|
|
@ -1,5 +1,3 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import createModule from './createEdgeStackView';
|
||||
|
||||
export default angular.module('portainer.edge.stacks', [createModule]).name;
|
||||
export default angular.module('portainer.edge.stacks', []).name;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue