mirror of
https://github.com/portainer/portainer.git
synced 2025-08-07 14:55:27 +02:00
refactor(templates): migrate list view to react [EE-2296] (#10999)
This commit is contained in:
parent
d38085a560
commit
6ff4fd3db2
103 changed files with 2628 additions and 1315 deletions
|
@ -1,16 +0,0 @@
|
|||
import angular from 'angular';
|
||||
|
||||
angular.module('portainer.app').component('stackFromTemplateForm', {
|
||||
templateUrl: './stackFromTemplateForm.html',
|
||||
bindings: {
|
||||
template: '=',
|
||||
formValues: '=',
|
||||
state: '=',
|
||||
createTemplate: '<',
|
||||
unselectTemplate: '<',
|
||||
nameRegex: '<',
|
||||
},
|
||||
transclude: {
|
||||
advanced: '?advancedForm',
|
||||
},
|
||||
});
|
|
@ -1,95 +0,0 @@
|
|||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-custom-header icon="$ctrl.template.Logo" title-text="$ctrl.template.Title"></rd-widget-custom-header>
|
||||
<rd-widget-body classes="padding">
|
||||
<form class="form-horizontal" name="stackTemplateForm">
|
||||
<!-- description -->
|
||||
<div ng-if="$ctrl.template.Note">
|
||||
<div class="form-section-title"> Information </div>
|
||||
<div class="col-sm-12 form-group">
|
||||
<div class="template-note" ng-bind-html="$ctrl.template.Note"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !description -->
|
||||
<div class="form-section-title"> Configuration </div>
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="template_name" class="col-sm-2 control-label text-left">Name</label>
|
||||
<div class="col-sm-6">
|
||||
<input
|
||||
type="text"
|
||||
name="template_name"
|
||||
class="form-control"
|
||||
ng-model="$ctrl.formValues.name"
|
||||
ng-pattern="$ctrl.nameRegex"
|
||||
placeholder="e.g. myStack"
|
||||
required
|
||||
data-cy="stack-name-input"
|
||||
/>
|
||||
<div class="form-group" ng-if="stackTemplateForm.template_name.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="stackTemplateForm.template_name.$error">
|
||||
<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>
|
||||
<p ng-message="required" class="vertical-center"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>This field is required. </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- !name-input -->
|
||||
<!-- env -->
|
||||
<div ng-repeat="var in $ctrl.template.Env" ng-if="!var.preset || var.select" class="form-group">
|
||||
<label for="field_{{ $index }}" class="col-sm-2 control-label text-left">
|
||||
{{ var.label }}
|
||||
<portainer-tooltip ng-if="var.description" message="var.description"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" class="form-control" ng-if="!var.select" ng-model="var.value" id="field_{{ $index }}" data-cy="stackFromTemplateForm-input" />
|
||||
<select class="form-control" ng-if="var.select" ng-model="var.value" id="field_{{ $index }}" data-cy="stackFromTemplateForm-select">
|
||||
<option selected disabled hidden value="">Select value</option>
|
||||
<option ng-repeat="choice in var.select" value="{{ choice.value }}">{{ choice.text }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !env -->
|
||||
<ng-transclude ng-transclude-slot="advanced"></ng-transclude>
|
||||
|
||||
<!-- access-control -->
|
||||
<por-access-control-form form-data="$ctrl.formValues.AccessControlData"></por-access-control-form>
|
||||
<!-- !access-control -->
|
||||
<!-- actions -->
|
||||
<div class="form-section-title"> Actions </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
ng-disabled="$ctrl.state.actionInProgress || !$ctrl.formValues.name || !$ctrl.state.deployable || stackTemplateForm.$invalid"
|
||||
ng-click="$ctrl.createTemplate()"
|
||||
button-spinner="$ctrl.state.actionInProgress"
|
||||
>
|
||||
<span ng-hide="$ctrl.state.actionInProgress">Deploy the stack</span>
|
||||
<span ng-show="$ctrl.state.actionInProgress">Deployment in progress...</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" ng-click="$ctrl.unselectTemplate($ctrl.template)">Hide</button>
|
||||
<div class="form-group" ng-if="$ctrl.state.formValidationError">
|
||||
<div class="col-sm-12 small text-danger" ng-if="$ctrl.state.formValidationError">
|
||||
<p class="vertical-center"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>{{ $ctrl.state.formValidationError }} </p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="!$ctrl.state.deployable">
|
||||
<div class="col-sm-12 small text-danger" ng-if="!$ctrl.state.deployable">
|
||||
<p class="vertical-center"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>This template type cannot be deployed on this environment. </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -1,127 +0,0 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
angular.module('portainer.app').factory('TemplateHelper', [
|
||||
function TemplateHelperFactory() {
|
||||
'use strict';
|
||||
var helper = {};
|
||||
|
||||
helper.getDefaultContainerConfiguration = function () {
|
||||
return {
|
||||
Env: [],
|
||||
OpenStdin: false,
|
||||
Tty: false,
|
||||
ExposedPorts: {},
|
||||
HostConfig: {
|
||||
RestartPolicy: {
|
||||
Name: 'no',
|
||||
},
|
||||
PortBindings: {},
|
||||
Binds: [],
|
||||
Privileged: false,
|
||||
ExtraHosts: [],
|
||||
},
|
||||
Volumes: {},
|
||||
Labels: {},
|
||||
};
|
||||
};
|
||||
|
||||
helper.portArrayToPortConfiguration = function (ports) {
|
||||
var portConfiguration = {
|
||||
bindings: {},
|
||||
exposedPorts: {},
|
||||
};
|
||||
ports.forEach(function (p) {
|
||||
if (p.containerPort) {
|
||||
var key = p.containerPort + '/' + p.protocol;
|
||||
var binding = {};
|
||||
if (p.hostPort) {
|
||||
binding.HostPort = p.hostPort;
|
||||
if (p.hostPort.indexOf(':') > -1) {
|
||||
var hostAndPort = p.hostPort.split(':');
|
||||
binding.HostIp = hostAndPort[0];
|
||||
binding.HostPort = hostAndPort[1];
|
||||
}
|
||||
}
|
||||
portConfiguration.bindings[key] = [binding];
|
||||
portConfiguration.exposedPorts[key] = {};
|
||||
}
|
||||
});
|
||||
return portConfiguration;
|
||||
};
|
||||
|
||||
helper.updateContainerConfigurationWithLabels = function (labelsArray) {
|
||||
var labels = {};
|
||||
labelsArray.forEach(function (l) {
|
||||
if (l.name) {
|
||||
if (l.value) {
|
||||
labels[l.name] = l.value;
|
||||
} else {
|
||||
labels[l.name] = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
return labels;
|
||||
};
|
||||
|
||||
helper.EnvToStringArray = function (templateEnvironment) {
|
||||
var env = [];
|
||||
templateEnvironment.forEach(function (envvar) {
|
||||
if (envvar.value || envvar.set) {
|
||||
var value = envvar.set ? envvar.set : envvar.value;
|
||||
env.push(envvar.name + '=' + value);
|
||||
}
|
||||
});
|
||||
return env;
|
||||
};
|
||||
|
||||
helper.getConsoleConfiguration = function (interactiveFlag) {
|
||||
var consoleConfiguration = {
|
||||
openStdin: false,
|
||||
tty: false,
|
||||
};
|
||||
if (interactiveFlag === true) {
|
||||
consoleConfiguration.openStdin = true;
|
||||
consoleConfiguration.tty = true;
|
||||
}
|
||||
return consoleConfiguration;
|
||||
};
|
||||
|
||||
helper.createVolumeBindings = function (volumes, generatedVolumesPile) {
|
||||
volumes.forEach(function (volume) {
|
||||
if (volume.container) {
|
||||
var binding;
|
||||
if (volume.type === 'auto') {
|
||||
binding = generatedVolumesPile.pop().Id + ':' + volume.container;
|
||||
} else if (volume.type !== 'auto' && volume.bind) {
|
||||
binding = volume.bind + ':' + volume.container;
|
||||
}
|
||||
if (volume.readonly) {
|
||||
binding += ':ro';
|
||||
}
|
||||
volume.binding = binding;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
helper.determineRequiredGeneratedVolumeCount = function (volumes) {
|
||||
var count = 0;
|
||||
volumes.forEach(function (volume) {
|
||||
if (volume.type === 'auto') {
|
||||
++count;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
helper.getUniqueCategories = function (templates) {
|
||||
var categories = [];
|
||||
for (var i = 0; i < templates.length; i++) {
|
||||
var template = templates[i];
|
||||
categories = categories.concat(template.Categories);
|
||||
}
|
||||
return _.uniq(categories);
|
||||
};
|
||||
|
||||
return helper;
|
||||
},
|
||||
]);
|
|
@ -13,7 +13,6 @@ import {
|
|||
import { PlatformField } from '@/react/portainer/custom-templates/components/PlatformSelector';
|
||||
import { TemplateTypeSelector } from '@/react/portainer/custom-templates/components/TemplateTypeSelector';
|
||||
import { withFormValidation } from '@/react-tools/withFormValidation';
|
||||
import { AppTemplatesList } from '@/react/portainer/templates/app-templates/AppTemplatesList';
|
||||
import { CustomTemplatesList } from '@/react/portainer/templates/custom-templates/ListView/CustomTemplatesList';
|
||||
|
||||
import { VariablesFieldAngular } from './variables-field';
|
||||
|
@ -39,18 +38,6 @@ export const ngModule = angular
|
|||
'isVariablesNamesFromParent',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'appTemplatesList',
|
||||
r2a(withUIRouter(withCurrentUser(AppTemplatesList)), [
|
||||
'onSelect',
|
||||
'templates',
|
||||
'selectedId',
|
||||
'disabledTypes',
|
||||
'fixedCategories',
|
||||
'storageKey',
|
||||
'templateLinkParams',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'customTemplatesList',
|
||||
r2a(withUIRouter(withCurrentUser(CustomTemplatesList)), [
|
||||
|
|
|
@ -19,6 +19,7 @@ import { updateSchedulesModule } from './update-schedules';
|
|||
import { environmentGroupModule } from './env-groups';
|
||||
import { registriesModule } from './registries';
|
||||
import { activityLogsModule } from './activity-logs';
|
||||
import { templatesModule } from './templates';
|
||||
|
||||
export const viewsModule = angular
|
||||
.module('portainer.app.react.views', [
|
||||
|
@ -28,6 +29,7 @@ export const viewsModule = angular
|
|||
environmentGroupModule,
|
||||
registriesModule,
|
||||
activityLogsModule,
|
||||
templatesModule,
|
||||
])
|
||||
.component(
|
||||
'homeView',
|
||||
|
|
23
app/portainer/react/views/templates.ts
Normal file
23
app/portainer/react/views/templates.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
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/portainer/templates/custom-templates/CreateView';
|
||||
import { EditView } from '@/react/portainer/templates/custom-templates/EditView';
|
||||
import { AppTemplatesView } from '@/react/portainer/templates/app-templates/AppTemplatesView';
|
||||
|
||||
export const templatesModule = angular
|
||||
.module('portainer.app.react.views.templates', [])
|
||||
.component(
|
||||
'appTemplatesView',
|
||||
r2a(withCurrentUser(withUIRouter(AppTemplatesView)), [])
|
||||
)
|
||||
.component(
|
||||
'createCustomTemplatesView',
|
||||
r2a(withCurrentUser(withUIRouter(CreateView)), [])
|
||||
)
|
||||
.component(
|
||||
'editCustomTemplatesView',
|
||||
r2a(withCurrentUser(withUIRouter(EditView)), [])
|
||||
).name;
|
|
@ -1,11 +1,10 @@
|
|||
import { commandStringToArray } from '@/docker/helpers/containers';
|
||||
import { TemplateViewModel } from '@/react/portainer/templates/app-templates/view-model';
|
||||
import { DockerHubViewModel } from 'Portainer/models/dockerhub';
|
||||
|
||||
angular.module('portainer.app').factory('TemplateService', TemplateServiceFactory);
|
||||
|
||||
/* @ngInject */
|
||||
function TemplateServiceFactory($q, Templates, TemplateHelper, ImageHelper, ContainerHelper, EndpointService) {
|
||||
function TemplateServiceFactory($q, Templates, EndpointService) {
|
||||
var service = {
|
||||
templates,
|
||||
};
|
||||
|
@ -45,43 +44,5 @@ function TemplateServiceFactory($q, Templates, TemplateHelper, ImageHelper, Cont
|
|||
return Templates.file({ repositoryUrl, composeFilePathInRepository }).$promise;
|
||||
}
|
||||
|
||||
service.createTemplateConfiguration = function (template, containerName, network) {
|
||||
var imageConfiguration = ImageHelper.createImageConfigForContainer(template.RegistryModel);
|
||||
var containerConfiguration = createContainerConfiguration(template, containerName, network);
|
||||
containerConfiguration.Image = imageConfiguration.fromImage;
|
||||
return containerConfiguration;
|
||||
};
|
||||
|
||||
function createContainerConfiguration(template, containerName, network) {
|
||||
var configuration = TemplateHelper.getDefaultContainerConfiguration();
|
||||
configuration.HostConfig.NetworkMode = network.Name;
|
||||
configuration.HostConfig.Privileged = template.Privileged;
|
||||
configuration.HostConfig.RestartPolicy = { Name: template.RestartPolicy };
|
||||
configuration.HostConfig.ExtraHosts = template.Hosts ? template.Hosts : [];
|
||||
configuration.name = containerName;
|
||||
configuration.Hostname = template.Hostname;
|
||||
configuration.Env = TemplateHelper.EnvToStringArray(template.Env);
|
||||
configuration.Cmd = commandStringToArray(template.Command);
|
||||
var portConfiguration = TemplateHelper.portArrayToPortConfiguration(template.Ports);
|
||||
configuration.HostConfig.PortBindings = portConfiguration.bindings;
|
||||
configuration.ExposedPorts = portConfiguration.exposedPorts;
|
||||
var consoleConfiguration = TemplateHelper.getConsoleConfiguration(template.Interactive);
|
||||
configuration.OpenStdin = consoleConfiguration.openStdin;
|
||||
configuration.Tty = consoleConfiguration.tty;
|
||||
configuration.Labels = TemplateHelper.updateContainerConfigurationWithLabels(template.Labels);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
service.updateContainerConfigurationWithVolumes = function (configuration, template, generatedVolumesPile) {
|
||||
var volumes = template.Volumes;
|
||||
TemplateHelper.createVolumeBindings(volumes, generatedVolumesPile);
|
||||
volumes.forEach(function (volume) {
|
||||
if (volume.binding) {
|
||||
configuration.Volumes[volume.container] = {};
|
||||
configuration.HostConfig.Binds.push(volume.binding);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
}
|
||||
|
|
|
@ -1,67 +1,10 @@
|
|||
<page-header title="'Custom Templates'" breadcrumbs="['Custom Templates']" reload="true"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<stack-from-template-form
|
||||
ng-if="$ctrl.state.selectedTemplate"
|
||||
template="$ctrl.state.selectedTemplate"
|
||||
form-values="$ctrl.formValues"
|
||||
name-regex="$ctrl.state.templateNameRegex"
|
||||
state="$ctrl.state"
|
||||
create-template="$ctrl.createStack"
|
||||
unselect-template="$ctrl.unselectTemplate"
|
||||
>
|
||||
<advanced-form>
|
||||
<custom-templates-variables-field
|
||||
ng-if="$ctrl.isTemplateVariablesEnabled"
|
||||
definitions="$ctrl.state.selectedTemplate.Variables"
|
||||
value="$ctrl.formValues.variables"
|
||||
on-change="($ctrl.onChangeTemplateVariables)"
|
||||
></custom-templates-variables-field>
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.state.selectedTemplate && !$ctrl.state.templateLoadFailed">
|
||||
<div class="col-sm-12">
|
||||
<a class="small interactive vertical-center" ng-show="!$ctrl.state.showAdvancedOptions" ng-click="$ctrl.state.showAdvancedOptions = true;">
|
||||
<pr-icon icon="'plus'" class-name="space-right" feather="true"></pr-icon> {{ $ctrl.state.selectedTemplate.GitConfig !== null ? 'View' : 'Customize' }} stack
|
||||
</a>
|
||||
<a class="small interactive vertical-center" ng-show="$ctrl.state.showAdvancedOptions" ng-click="$ctrl.state.showAdvancedOptions = false;">
|
||||
<pr-icon icon="'minus'" class-name="space-right" feather="true"></pr-icon> Hide {{ $ctrl.state.selectedTemplate.GitConfig === null ? 'custom' : '' }} stack
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span ng-if="$ctrl.state.selectedTemplate && $ctrl.state.templateLoadFailed">
|
||||
<p class="small vertical-center text-danger mb-5" ng-if="$ctrl.currentUser.isAdmin || $ctrl.currentUser.id === $ctrl.state.selectedTemplate.CreatedByUserId">
|
||||
<pr-icon icon="'alert-triangle'" mode="'danger'" size="'md'" feather="true"></pr-icon>Custom template could not be loaded, please
|
||||
<a ui-sref="docker.templates.custom.edit({id: $ctrl.state.selectedTemplate.Id})">click here</a> for configuration.</p
|
||||
>
|
||||
<p class="small vertical-center text-danger mb-5" ng-if="!($ctrl.currentUser.isAdmin || $ctrl.currentUser.id === $ctrl.state.selectedTemplate.CreatedByUserId)">
|
||||
<pr-icon icon="'alert-triangle'" mode="'danger'" size="'md'" feather="true"></pr-icon>Custom template could not be loaded, please contact your administrator.</p
|
||||
>
|
||||
</span>
|
||||
|
||||
<!-- web-editor -->
|
||||
<web-editor-form
|
||||
ng-if="$ctrl.state.showAdvancedOptions"
|
||||
identifier="custom-template-creation-editor"
|
||||
value="$ctrl.formValues.fileContent"
|
||||
on-change="($ctrl.editorUpdate)"
|
||||
ng-required="true"
|
||||
yml="true"
|
||||
placeholder="Define or paste the content of your docker compose file here"
|
||||
read-only="$ctrl.state.isEditorReadOnly"
|
||||
>
|
||||
<editor-description>
|
||||
<p>
|
||||
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>
|
||||
.
|
||||
</p>
|
||||
</editor-description>
|
||||
</web-editor-form>
|
||||
<!-- !web-editor -->
|
||||
</advanced-form>
|
||||
</stack-from-template-form>
|
||||
</div>
|
||||
<stack-from-custom-template-form-widget
|
||||
ng-if="$ctrl.state.selectedTemplate"
|
||||
template="$ctrl.state.selectedTemplate"
|
||||
unselect="$ctrl.unselectTemplate"
|
||||
></stack-from-custom-template-form-widget>
|
||||
|
||||
<custom-templates-list
|
||||
templates="$ctrl.templates"
|
||||
|
|
|
@ -228,6 +228,8 @@ class CustomTemplatesViewController {
|
|||
const variables = getVariablesFieldDefaultValues(template.Variables);
|
||||
this.onChangeTemplateVariables(variables);
|
||||
}
|
||||
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
getNetworks(provider, apiVersion) {
|
||||
|
|
|
@ -1,292 +0,0 @@
|
|||
<page-header id="'view-top'" title="'Application templates list'" breadcrumbs="['Templates']"> </page-header>
|
||||
|
||||
<div class="row">
|
||||
<!-- stack-form -->
|
||||
<stack-from-template-form
|
||||
ng-if="state.selectedTemplate && (state.selectedTemplate.Type === 2 || state.selectedTemplate.Type === 3)"
|
||||
template="state.selectedTemplate"
|
||||
form-values="formValues"
|
||||
state="state"
|
||||
create-template="createTemplate"
|
||||
unselect-template="unselectTemplate"
|
||||
>
|
||||
</stack-from-template-form>
|
||||
<!-- !stack-form -->
|
||||
<!-- container-form -->
|
||||
<div class="col-sm-12" ng-if="state.selectedTemplate && state.selectedTemplate.Type === 1">
|
||||
<rd-widget>
|
||||
<rd-widget-custom-header icon="state.selectedTemplate.Logo" title-text="state.selectedTemplate.Title"> </rd-widget-custom-header>
|
||||
<rd-widget-body classes="padding">
|
||||
<form class="form-horizontal" name="selectedTemplateType1">
|
||||
<!-- description -->
|
||||
<div ng-if="state.selectedTemplate.Note">
|
||||
<div class="form-section-title"> Information </div>
|
||||
<div class="col-sm-12 form-group">
|
||||
<div class="template-note" ng-bind-html="state.selectedTemplate.Note"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !description -->
|
||||
<div class="form-section-title"> Configuration </div>
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_name" class="col-sm-2 control-label text-left">Name</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="container_name" class="form-control" ng-model="formValues.name" placeholder="e.g. web (optional)" data-cy="container-name-input" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- network-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_network" class="col-sm-2 control-label text-left">Network</label>
|
||||
<div class="col-sm-6">
|
||||
<select class="form-control" ng-options="net.Name for net in availableNetworks | orderBy: 'Name'" ng-model="formValues.network" data-cy="network-select">
|
||||
<option disabled hidden value="">Select a network</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !network-input -->
|
||||
<!-- env -->
|
||||
<div ng-repeat="var in state.selectedTemplate.Env" ng-if="!var.preset || var.select" class="form-group">
|
||||
<label for="field_{{ $index }}" class="col-sm-2 control-label text-left">
|
||||
{{ var.label }}
|
||||
<portainer-tooltip ng-if="var.description" message="var.description"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" class="form-control" ng-if="!var.select" ng-model="var.value" id="field_{{ $index }}" data-cy="env-input-{{ $index}" />
|
||||
<select class="form-control" ng-if="var.select" ng-model="var.value" id="field_{{ $index }}" data-cy="env-select-{{ $index }}">
|
||||
<option selected disabled hidden value="">Select value</option>
|
||||
<option ng-repeat="choice in var.select" value="{{ choice.value }}">{{ choice.text }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !env -->
|
||||
<!-- access-control -->
|
||||
<por-access-control-form form-data="formValues.AccessControlData"></por-access-control-form>
|
||||
<!-- !access-control -->
|
||||
<div class="form-group col-sm-12">
|
||||
<a class="small interactive vertical-center" ng-if="!state.showAdvancedOptions" ng-click="state.showAdvancedOptions = true;">
|
||||
<pr-icon icon="'plus'"></pr-icon> Show advanced options
|
||||
</a>
|
||||
<a class="small interactive vertical-center" ng-if="state.showAdvancedOptions" ng-click="state.showAdvancedOptions = false;">
|
||||
<pr-icon icon="'minus'"></pr-icon> Hide advanced options
|
||||
</a>
|
||||
</div>
|
||||
<div ng-if="state.showAdvancedOptions">
|
||||
<!-- port-mapping -->
|
||||
<div class="form-group mt-2">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">Port mapping</label>
|
||||
<div class="mt-1" ng-if="state.selectedTemplate.Ports.length > 0">
|
||||
<div class="small text-muted">Portainer will automatically assign a port if you leave the host port empty.</div>
|
||||
<div class="form-inline mt-2" ng-repeat="portBinding in state.selectedTemplate.Ports">
|
||||
<!-- host-port -->
|
||||
<div class="input-group col-sm-4 input-group-sm">
|
||||
<span class="input-group-addon">host</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
ng-model="portBinding.hostPort"
|
||||
placeholder="e.g. 80 or 1.2.3.4:80 (optional)"
|
||||
data-cy="host-port-input-{{ $index }}"
|
||||
/>
|
||||
</div>
|
||||
<!-- !host-port -->
|
||||
<pr-icon icon="'arrow-right'"></pr-icon>
|
||||
<!-- container-port -->
|
||||
<div class="input-group col-sm-4 input-group-sm">
|
||||
<span class="input-group-addon">container</span>
|
||||
<input type="text" class="form-control" ng-model="portBinding.containerPort" placeholder="e.g. 80" data-cy="container-port-input-{{ $index }}" />
|
||||
</div>
|
||||
<!-- !container-port -->
|
||||
<!-- protocol-actions -->
|
||||
<div class="input-group col-sm-3 input-group-sm">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-light" ng-model="portBinding.protocol" uib-btn-radio="'tcp'">TCP</label>
|
||||
<label class="btn btn-light" ng-model="portBinding.protocol" uib-btn-radio="'udp'">UDP</label>
|
||||
</div>
|
||||
<button class="btn btn-light" type="button" ng-click="removePortBinding($index)">
|
||||
<pr-icon icon="'trash-2'" class-name="'icon-secondary icon-md'"></pr-icon>
|
||||
</button>
|
||||
</div>
|
||||
<!-- !protocol-actions -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<span class="form-group small interactive text-muted vertical-center mt-2" ng-click="addPortBinding()">
|
||||
<pr-icon icon="'plus'"></pr-icon> Add map additional port
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !port-mapping -->
|
||||
<!-- volume-mapping -->
|
||||
<div class="form-group mt-4">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">Volume mapping</label>
|
||||
<div class="mt-1" ng-if="state.selectedTemplate.Volumes.length > 0">
|
||||
<div class="small text-muted">Portainer will automatically create and map a local volume when using the <b>auto</b> option.</div>
|
||||
<div class="mt-2" ng-repeat="volume in state.selectedTemplate.Volumes">
|
||||
<!-- volume-line1 -->
|
||||
<div class="form-inline">
|
||||
<!-- container-path -->
|
||||
<div class="input-group input-group-sm col-sm-6">
|
||||
<span class="input-group-addon">container</span>
|
||||
<input type="text" class="form-control" ng-model="volume.container" placeholder="e.g. /path/in/container" data-cy="container-path-input-{{ $index }}" />
|
||||
</div>
|
||||
<!-- !container-path -->
|
||||
<!-- volume-type -->
|
||||
<div class="input-group col-sm-5 space-left">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-light" ng-model="volume.type" uib-btn-radio="'auto'" ng-click="volume.bind = ''">Auto</label>
|
||||
<label class="btn btn-light" ng-model="volume.type" uib-btn-radio="'volume'" ng-click="volume.bind = ''">Volume</label>
|
||||
<label class="btn btn-light" ng-model="volume.type" uib-btn-radio="'bind'" ng-click="volume.bind = ''" ng-if="isAdmin || allowBindMounts">Bind</label>
|
||||
</div>
|
||||
<button class="btn btn-light" type="button" ng-click="removeVolume($index)">
|
||||
<pr-icon icon="'trash-2'" class-name="'icon-secondary icon-md'"></pr-icon>
|
||||
</button>
|
||||
</div>
|
||||
<!-- !volume-type -->
|
||||
</div>
|
||||
<!-- !volume-line1 -->
|
||||
<!-- volume-line2 -->
|
||||
<div class="form-inline mt-1" ng-if="volume.type !== 'auto'">
|
||||
<pr-icon icon="'arrow-right'"></pr-icon>
|
||||
<!-- volume -->
|
||||
<div class="input-group input-group-sm col-sm-6" ng-if="volume.type === 'volume'">
|
||||
<div class="col-sm-12 input-group">
|
||||
<span class="input-group-addon">volume</span>
|
||||
<div class="col-sm-12 input-group">
|
||||
<select class="form-control" ng-model="volume.bind" ng-options="vol.Name as vol.Name for vol in availableVolumes" data-cy="volume-bind-select">
|
||||
<option value="" disabled selected>Select a volume</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !volume -->
|
||||
<!-- bind -->
|
||||
<div class="input-group input-group-sm col-sm-6" ng-if="volume.type === 'bind'">
|
||||
<span class="input-group-addon">host</span>
|
||||
<input type="text" class="form-control" ng-model="volume.bind" placeholder="e.g. /path/on/host" data-cy="host-path-input-{{ $index }}" />
|
||||
</div>
|
||||
<!-- !bind -->
|
||||
<!-- read-only -->
|
||||
<div class="input-group input-group-sm col-sm-5 space-left">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-light" ng-model="volume.readonly" uib-btn-radio="false">Writable</label>
|
||||
<label class="btn btn-light" ng-model="volume.readonly" uib-btn-radio="true">Read-only</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !read-only -->
|
||||
</div>
|
||||
<!-- !volume-line2 -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<span class="form-group small interactive text-muted vertical-center mt-2" ng-click="addVolume()">
|
||||
<pr-icon icon="'plus'"></pr-icon> Add map additional volume
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !volume-mapping -->
|
||||
<!-- extra-host -->
|
||||
<div class="form-group mt-4">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">Hosts file entries</label>
|
||||
<!-- extra-host-input-list -->
|
||||
<div class="mt-1" ng-if="state.selectedTemplate.Hosts.length > 0">
|
||||
<div class="form-inline mt-2" ng-repeat="(idx, host) in state.selectedTemplate.Hosts track by $index">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="state.selectedTemplate.Hosts[idx]" placeholder="e.g. host:IP" data-cy="host-input-{{ $index }}" />
|
||||
</div>
|
||||
<button class="btn btn-light" type="button" ng-click="removeExtraHost($index)">
|
||||
<pr-icon icon="'trash-2'" class-name="'icon-secondary icon-md'"></pr-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<span class="form-group small interactive text-muted vertical-center mt-2" ng-click="addExtraHost()">
|
||||
<pr-icon icon="'plus'"></pr-icon> Add additional entry
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !extra-host -->
|
||||
<!-- labels -->
|
||||
<div class="form-group mt-4">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">Labels</label>
|
||||
<!-- labels-input-list -->
|
||||
<div class="mt-1" ng-if="state.selectedTemplate.Labels.length > 0">
|
||||
<div class="form-inline mt-2" ng-repeat="label in state.selectedTemplate.Labels">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input type="text" class="form-control" ng-model="label.name" placeholder="e.g. com.example.foo" data-cy="label-name-input-{{ $index }}" />
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar" data-cy="label-value-input-{{ $index }}" />
|
||||
</div>
|
||||
<button class="btn btn-light" type="button" ng-click="removeLabel($index)">
|
||||
<pr-icon icon="'trash-2'" class-name="'icon-secondary icon-md'"></pr-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !labels-input-list -->
|
||||
<div class="col-sm-12">
|
||||
<span class="form-group small interactive text-muted vertical-center mt-2" ng-click="addLabel()"> <pr-icon icon="'plus'"></pr-icon> Add label </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !labels -->
|
||||
<!-- hostname -->
|
||||
<div class="form-group mt-4">
|
||||
<label for="container_hostname" class="col-sm-2 control-label text-left">Hostname</label>
|
||||
<div class="col-sm-6">
|
||||
<input
|
||||
type="text"
|
||||
name="container_hostname"
|
||||
class="form-control"
|
||||
ng-model="state.selectedTemplate.Hostname"
|
||||
placeholder="leave empty to use docker default"
|
||||
data-cy="hostname-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !hostname -->
|
||||
</div>
|
||||
<!-- !advanced-options -->
|
||||
<!-- actions -->
|
||||
<div class="form-section-title"> Actions </div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
ng-disabled="state.actionInProgress || !formValues.network"
|
||||
ng-click="createTemplate()"
|
||||
button-spinner="state.actionInProgress"
|
||||
>
|
||||
<span ng-hide="state.actionInProgress">Deploy the container</span>
|
||||
<span ng-show="state.actionInProgress">Deployment in progress...</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" ng-click="unselectTemplate(state.selectedTemplate)">Hide</button>
|
||||
<span class="text-danger space-left" ng-if="state.formValidationError">{{ state.formValidationError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
<!-- container-form -->
|
||||
</div>
|
||||
|
||||
<app-templates-list
|
||||
storage-key="'docker-app-templates'"
|
||||
templates="templates"
|
||||
on-select="(selectTemplate)"
|
||||
selected-id="state.selectedTemplate.Id"
|
||||
disabled-types="disabledTypes"
|
||||
></app-templates-list>
|
|
@ -1,317 +0,0 @@
|
|||
import _ from 'lodash-es';
|
||||
import { TemplateType } from '@/react/portainer/templates/app-templates/types';
|
||||
import { AccessControlFormData } from '../../components/accessControlForm/porAccessControlFormModel';
|
||||
|
||||
angular.module('portainer.app').controller('TemplatesController', [
|
||||
'$scope',
|
||||
'$q',
|
||||
'$state',
|
||||
'$anchorScroll',
|
||||
'ContainerService',
|
||||
'ImageService',
|
||||
'NetworkService',
|
||||
'TemplateService',
|
||||
'TemplateHelper',
|
||||
'VolumeService',
|
||||
'Notifications',
|
||||
'ResourceControlService',
|
||||
'Authentication',
|
||||
'FormValidator',
|
||||
'StackService',
|
||||
'endpoint',
|
||||
'$async',
|
||||
function (
|
||||
$scope,
|
||||
$q,
|
||||
$state,
|
||||
$anchorScroll,
|
||||
ContainerService,
|
||||
ImageService,
|
||||
NetworkService,
|
||||
TemplateService,
|
||||
TemplateHelper,
|
||||
VolumeService,
|
||||
Notifications,
|
||||
ResourceControlService,
|
||||
Authentication,
|
||||
FormValidator,
|
||||
StackService,
|
||||
endpoint,
|
||||
$async
|
||||
) {
|
||||
const DOCKER_STANDALONE = 'DOCKER_STANDALONE';
|
||||
const DOCKER_SWARM_MODE = 'DOCKER_SWARM_MODE';
|
||||
|
||||
$scope.state = {
|
||||
selectedTemplate: null,
|
||||
showAdvancedOptions: false,
|
||||
formValidationError: '',
|
||||
actionInProgress: false,
|
||||
};
|
||||
|
||||
$scope.enabledTypes = [TemplateType.Container, TemplateType.ComposeStack];
|
||||
|
||||
$scope.formValues = {
|
||||
network: '',
|
||||
name: '',
|
||||
AccessControlData: new AccessControlFormData(),
|
||||
};
|
||||
|
||||
$scope.addVolume = function () {
|
||||
$scope.state.selectedTemplate.Volumes.push({ containerPath: '', bind: '', readonly: false, type: 'auto' });
|
||||
};
|
||||
|
||||
$scope.removeVolume = function (index) {
|
||||
$scope.state.selectedTemplate.Volumes.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addPortBinding = function () {
|
||||
$scope.state.selectedTemplate.Ports.push({ hostPort: '', containerPort: '', protocol: 'tcp' });
|
||||
};
|
||||
|
||||
$scope.removePortBinding = function (index) {
|
||||
$scope.state.selectedTemplate.Ports.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addExtraHost = function () {
|
||||
$scope.state.selectedTemplate.Hosts.push('');
|
||||
};
|
||||
|
||||
$scope.removeExtraHost = function (index) {
|
||||
$scope.state.selectedTemplate.Hosts.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addLabel = function () {
|
||||
$scope.state.selectedTemplate.Labels.push({ name: '', value: '' });
|
||||
};
|
||||
|
||||
$scope.removeLabel = function (index) {
|
||||
$scope.state.selectedTemplate.Labels.splice(index, 1);
|
||||
};
|
||||
|
||||
function validateForm(accessControlData, isAdmin) {
|
||||
$scope.state.formValidationError = '';
|
||||
var error = '';
|
||||
error = FormValidator.validateAccessControl(accessControlData, isAdmin);
|
||||
|
||||
if (error) {
|
||||
$scope.state.formValidationError = error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function createContainerFromTemplate(template, userId, accessControlData) {
|
||||
var templateConfiguration = createTemplateConfiguration(template);
|
||||
var generatedVolumeCount = TemplateHelper.determineRequiredGeneratedVolumeCount(template.Volumes);
|
||||
var generatedVolumeIds = [];
|
||||
VolumeService.createXAutoGeneratedLocalVolumes(generatedVolumeCount)
|
||||
.then(function success(data) {
|
||||
angular.forEach(data, function (volume) {
|
||||
var volumeId = volume.Id;
|
||||
generatedVolumeIds.push(volumeId);
|
||||
});
|
||||
TemplateService.updateContainerConfigurationWithVolumes(templateConfiguration, template, data);
|
||||
return ImageService.pullImage(template.RegistryModel, true);
|
||||
})
|
||||
.then(function success() {
|
||||
return ContainerService.createAndStartContainer(endpoint.Id, templateConfiguration);
|
||||
})
|
||||
.then(function success(data) {
|
||||
const resourceControl = data.Portainer.ResourceControl;
|
||||
return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl, generatedVolumeIds);
|
||||
})
|
||||
.then(function success() {
|
||||
Notifications.success('Success', 'Container successfully created');
|
||||
$state.go('docker.containers', {}, { reload: true });
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, err.msg);
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
function createComposeStackFromTemplate(template, userId, accessControlData) {
|
||||
var stackName = $scope.formValues.name;
|
||||
|
||||
for (var i = 0; i < template.Env.length; i++) {
|
||||
var envvar = template.Env[i];
|
||||
if (envvar.preset) {
|
||||
envvar.value = envvar.default;
|
||||
}
|
||||
}
|
||||
|
||||
var repositoryOptions = {
|
||||
RepositoryURL: template.Repository.url,
|
||||
ComposeFilePathInRepository: template.Repository.stackfile,
|
||||
FromAppTemplate: true,
|
||||
};
|
||||
|
||||
const endpointId = +$state.params.endpointId;
|
||||
StackService.createComposeStackFromGitRepository(stackName, repositoryOptions, template.Env, endpointId)
|
||||
.then(function success(data) {
|
||||
const resourceControl = data.ResourceControl;
|
||||
return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl);
|
||||
})
|
||||
.then(function success() {
|
||||
Notifications.success('Success', 'Stack successfully deployed');
|
||||
$state.go('docker.stacks');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Deployment error', err);
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
function createStackFromTemplate(template, userId, accessControlData) {
|
||||
var stackName = $scope.formValues.name;
|
||||
var env = _.filter(
|
||||
_.map(template.Env, function transformEnvVar(envvar) {
|
||||
return {
|
||||
name: envvar.name,
|
||||
value: envvar.preset || !envvar.value ? envvar.default : envvar.value,
|
||||
};
|
||||
}),
|
||||
function removeUndefinedVars(envvar) {
|
||||
return envvar.value && envvar.name;
|
||||
}
|
||||
);
|
||||
|
||||
var repositoryOptions = {
|
||||
RepositoryURL: template.Repository.url,
|
||||
ComposeFilePathInRepository: template.Repository.stackfile,
|
||||
FromAppTemplate: true,
|
||||
};
|
||||
|
||||
const endpointId = +$state.params.endpointId;
|
||||
|
||||
StackService.createSwarmStackFromGitRepository(stackName, repositoryOptions, env, endpointId)
|
||||
.then(function success(data) {
|
||||
const resourceControl = data.ResourceControl;
|
||||
return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl);
|
||||
})
|
||||
.then(function success() {
|
||||
Notifications.success('Success', 'Stack successfully deployed');
|
||||
$state.go('docker.stacks');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Deployment error', err);
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.createTemplate = function () {
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var userId = userDetails.ID;
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
|
||||
if (!validateForm(accessControlData, $scope.isAdmin)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var template = $scope.state.selectedTemplate;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
if (template.Type === 2) {
|
||||
createStackFromTemplate(template, userId, accessControlData);
|
||||
} else if (template.Type === 3) {
|
||||
createComposeStackFromTemplate(template, userId, accessControlData);
|
||||
} else {
|
||||
createContainerFromTemplate(template, userId, accessControlData);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.unselectTemplate = function () {
|
||||
return $async(async () => {
|
||||
$scope.state.selectedTemplate = null;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectTemplate = function (template) {
|
||||
return $async(async () => {
|
||||
if ($scope.state.selectedTemplate) {
|
||||
await $scope.unselectTemplate($scope.state.selectedTemplate);
|
||||
}
|
||||
|
||||
if (template.Network) {
|
||||
$scope.formValues.network = _.find($scope.availableNetworks, function (o) {
|
||||
return o.Name === template.Network;
|
||||
});
|
||||
} else {
|
||||
$scope.formValues.network = _.find($scope.availableNetworks, function (o) {
|
||||
return o.Name === 'bridge';
|
||||
});
|
||||
}
|
||||
|
||||
$scope.formValues.name = template.Name ? template.Name : '';
|
||||
$scope.state.selectedTemplate = template;
|
||||
$scope.state.deployable = isDeployable($scope.applicationState.endpoint, template.Type);
|
||||
$anchorScroll('view-top');
|
||||
});
|
||||
};
|
||||
|
||||
function isDeployable(endpoint, templateType) {
|
||||
let deployable = false;
|
||||
switch (templateType) {
|
||||
case 1:
|
||||
deployable = endpoint.mode.provider === DOCKER_SWARM_MODE || endpoint.mode.provider === DOCKER_STANDALONE;
|
||||
break;
|
||||
case 2:
|
||||
deployable = endpoint.mode.provider === DOCKER_SWARM_MODE;
|
||||
break;
|
||||
case 3:
|
||||
deployable = endpoint.mode.provider === DOCKER_SWARM_MODE || endpoint.mode.provider === DOCKER_STANDALONE;
|
||||
break;
|
||||
}
|
||||
return deployable;
|
||||
}
|
||||
|
||||
function createTemplateConfiguration(template) {
|
||||
var network = $scope.formValues.network;
|
||||
var name = $scope.formValues.name;
|
||||
return TemplateService.createTemplateConfiguration(template, name, network);
|
||||
}
|
||||
|
||||
function initView() {
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
|
||||
var endpointMode = $scope.applicationState.endpoint.mode;
|
||||
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
||||
const endpointId = +$state.params.endpointId;
|
||||
|
||||
const showSwarmStacks = endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER' && apiVersion >= 1.25;
|
||||
|
||||
$scope.disabledTypes = !showSwarmStacks ? [TemplateType.SwarmStack] : [];
|
||||
|
||||
$q.all({
|
||||
templates: TemplateService.templates(endpointId),
|
||||
volumes: VolumeService.getVolumes(),
|
||||
networks: NetworkService.networks(
|
||||
endpointMode.provider === 'DOCKER_STANDALONE' || endpointMode.provider === 'DOCKER_SWARM_MODE',
|
||||
false,
|
||||
endpointMode.provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25
|
||||
),
|
||||
})
|
||||
.then(function success(data) {
|
||||
var templates = data.templates;
|
||||
$scope.templates = templates;
|
||||
$scope.availableVolumes = _.orderBy(data.volumes.Volumes, [(volume) => volume.Name.toLowerCase()], ['asc']);
|
||||
var networks = data.networks;
|
||||
$scope.availableNetworks = networks;
|
||||
$scope.allowBindMounts = endpoint.SecuritySettings.allowBindMountsForRegularUsers;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.templates = [];
|
||||
Notifications.error('Failure', err, 'An error occurred during apps initialization.');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
},
|
||||
]);
|
Loading…
Add table
Add a link
Reference in a new issue