mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 05:45:22 +02:00
feat(templates): introduce templates management (#2017)
This commit is contained in:
parent
e7939a5384
commit
61c285bd2e
63 changed files with 3489 additions and 637 deletions
|
@ -361,36 +361,6 @@ angular.module('portainer.docker', ['portainer.app'])
|
|||
}
|
||||
};
|
||||
|
||||
var templates = {
|
||||
name: 'docker.templates',
|
||||
url: '/templates',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/templates/templates.html',
|
||||
controller: 'TemplatesController'
|
||||
}
|
||||
},
|
||||
params: {
|
||||
key: 'containers',
|
||||
hide_descriptions: false
|
||||
}
|
||||
};
|
||||
|
||||
var templatesLinuxServer = {
|
||||
name: 'docker.templates.linuxserver',
|
||||
url: '/linuxserver',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: 'app/docker/views/templates/templates.html',
|
||||
controller: 'TemplatesController'
|
||||
}
|
||||
},
|
||||
params: {
|
||||
key: 'linuxserver.io',
|
||||
hide_descriptions: true
|
||||
}
|
||||
};
|
||||
|
||||
var volumes = {
|
||||
name: 'docker.volumes',
|
||||
url: '/volumes',
|
||||
|
@ -458,8 +428,6 @@ angular.module('portainer.docker', ['portainer.app'])
|
|||
$stateRegistryProvider.register(tasks);
|
||||
$stateRegistryProvider.register(task);
|
||||
$stateRegistryProvider.register(taskLogs);
|
||||
$stateRegistryProvider.register(templates);
|
||||
$stateRegistryProvider.register(templatesLinuxServer);
|
||||
$stateRegistryProvider.register(volumes);
|
||||
$stateRegistryProvider.register(volume);
|
||||
$stateRegistryProvider.register(volumeCreation);
|
||||
|
|
|
@ -2,10 +2,7 @@
|
|||
<a ui-sref="docker.dashboard" ui-sref-active="active">Dashboard <span class="menu-icon fa fa-tachometer-alt fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="docker.templates" ui-sref-active="active">App Templates <span class="menu-icon fa fa-rocket fa-fw"></span></a>
|
||||
<div class="sidebar-sublist" ng-if="$ctrl.sidebarToggledOn && $ctrl.externalContributions && ($ctrl.currentState === 'docker.templates' || $ctrl.currentState === 'docker.templates.linuxserver')">
|
||||
<a ui-sref="docker.templates.linuxserver" ui-sref-active="active">LinuxServer.io</a>
|
||||
</div>
|
||||
<a ui-sref="portainer.templates" ui-sref-active="active">App Templates <span class="menu-icon fa fa-rocket fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="portainer.stacks" ui-sref-active="active">Stacks <span class="menu-icon fa fa-th-list fa-fw"></span></a>
|
||||
|
|
|
@ -4,6 +4,11 @@ angular.module('portainer.docker').component('porImageRegistry', {
|
|||
bindings: {
|
||||
'image': '=',
|
||||
'registry': '=',
|
||||
'autoComplete': '<'
|
||||
'autoComplete': '<',
|
||||
'labelClass': '@',
|
||||
'inputClass': '@'
|
||||
},
|
||||
require: {
|
||||
form: '^form'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
<div>
|
||||
<label for="image_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-11 col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="image_name" ng-class="$ctrl.labelClass" class="control-label text-left">Image</label>
|
||||
<div ng-class="$ctrl.inputClass">
|
||||
<input type="text" class="form-control" uib-typeahead="image for image in $ctrl.availableImages | filter:$viewValue | limitTo:5"
|
||||
ng-model="$ctrl.image" id="image_name" placeholder="e.g. myImage:myTag">
|
||||
ng-model="$ctrl.image" name="image_name" placeholder="e.g. myImage:myTag" required>
|
||||
</div>
|
||||
<label for="image_registry" class="col-sm-2 col-md-1 margin-sm-top control-label text-left">
|
||||
<label for="image_registry" class="margin-sm-top control-label text-right" ng-class="$ctrl.labelClass">
|
||||
Registry
|
||||
</label>
|
||||
<div class="col-sm-10 col-md-4 margin-sm-top">
|
||||
<div ng-class="$ctrl.inputClass" class="margin-sm-top">
|
||||
<select ng-options="registry as registry.Name for registry in $ctrl.availableRegistries" ng-model="$ctrl.registry" id="image_registry"
|
||||
class="form-control"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="$ctrl.form.image_name.$invalid">
|
||||
<div class="col-sm-12 small text-danger">
|
||||
<div ng-messages="$ctrl.form.image_name.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Image name is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('porImageRegistryController', ['$q', 'RegistryService', 'DockerHubService', 'ImageService', 'Notifications',
|
||||
function ($q, RegistryService, DockerHubService, ImageService, Notifications) {
|
||||
var ctrl = this;
|
||||
.controller('porImageRegistryController', ['$q', 'RegistryService', 'DockerHubService', 'ImageService', 'Notifications',
|
||||
function ($q, RegistryService, DockerHubService, ImageService, Notifications) {
|
||||
var ctrl = this;
|
||||
|
||||
function initComponent() {
|
||||
$q.all({
|
||||
registries: RegistryService.registries(),
|
||||
dockerhub: DockerHubService.dockerhub(),
|
||||
availableImages: ctrl.autoComplete ? ImageService.images() : []
|
||||
})
|
||||
.then(function success(data) {
|
||||
var dockerhub = data.dockerhub;
|
||||
var registries = data.registries;
|
||||
ctrl.availableImages = ImageService.getUniqueTagListFromImages(data.availableImages);
|
||||
ctrl.availableRegistries = [dockerhub].concat(registries);
|
||||
if (!ctrl.registry.Id) {
|
||||
ctrl.registry = dockerhub;
|
||||
} else {
|
||||
ctrl.registry = _.find(ctrl.availableRegistries, { 'Id': ctrl.registry.Id });
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve registries');
|
||||
});
|
||||
function initComponent() {
|
||||
$q.all({
|
||||
registries: RegistryService.registries(),
|
||||
dockerhub: DockerHubService.dockerhub(),
|
||||
availableImages: ctrl.autoComplete ? ImageService.images() : []
|
||||
})
|
||||
.then(function success(data) {
|
||||
var dockerhub = data.dockerhub;
|
||||
var registries = data.registries;
|
||||
ctrl.availableImages = ImageService.getUniqueTagListFromImages(data.availableImages);
|
||||
ctrl.availableRegistries = [dockerhub].concat(registries);
|
||||
if (!ctrl.registry.Id) {
|
||||
ctrl.registry = dockerhub;
|
||||
} else {
|
||||
ctrl.registry = _.find(ctrl.availableRegistries, { 'Id': ctrl.registry.Id });
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve registries');
|
||||
});
|
||||
}
|
||||
|
||||
initComponent();
|
||||
}]);
|
||||
initComponent();
|
||||
}]);
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
function StackTemplateViewModel(data) {
|
||||
this.Type = data.type;
|
||||
this.Name = data.name;
|
||||
this.Title = data.title;
|
||||
this.Description = data.description;
|
||||
this.Note = data.note;
|
||||
this.Categories = data.categories ? data.categories : [];
|
||||
this.Platform = data.platform ? data.platform : 'undefined';
|
||||
this.Logo = data.logo;
|
||||
this.Repository = data.repository;
|
||||
this.Env = data.env ? data.env : [];
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
function TemplateViewModel(data) {
|
||||
this.Type = data.type;
|
||||
this.Name = data.name;
|
||||
this.Hostname = data.hostname;
|
||||
this.Title = data.title;
|
||||
this.Description = data.description;
|
||||
this.Note = data.note;
|
||||
this.Categories = data.categories ? data.categories : [];
|
||||
this.Platform = data.platform ? data.platform : 'undefined';
|
||||
this.Logo = data.logo;
|
||||
this.Image = data.image;
|
||||
this.Registry = data.registry ? data.registry : '';
|
||||
this.Command = data.command ? data.command : '';
|
||||
this.Network = data.network ? data.network : '';
|
||||
this.Env = data.env ? data.env : [];
|
||||
this.Privileged = data.privileged ? data.privileged : false;
|
||||
this.Interactive = data.interactive ? data.interactive : false;
|
||||
this.RestartPolicy = data.restart_policy ? data.restart_policy : 'always';
|
||||
this.Labels = data.labels ? data.labels : [];
|
||||
this.Volumes = [];
|
||||
|
||||
if (data.volumes) {
|
||||
this.Volumes = data.volumes.map(function (v) {
|
||||
// @DEPRECATED: New volume definition introduced
|
||||
// via https://github.com/portainer/portainer/pull/1154
|
||||
var volume = {
|
||||
readOnly: v.readonly || false,
|
||||
containerPath: v.container || v,
|
||||
type: 'auto'
|
||||
};
|
||||
|
||||
if (v.bind) {
|
||||
volume.name = v.bind;
|
||||
volume.type = 'bind';
|
||||
}
|
||||
|
||||
return volume;
|
||||
});
|
||||
}
|
||||
this.Ports = [];
|
||||
if (data.ports) {
|
||||
this.Ports = data.ports.map(function (p) {
|
||||
var portAndProtocol = _.split(p, '/');
|
||||
return {
|
||||
containerPort: portAndProtocol[0],
|
||||
protocol: portAndProtocol[1]
|
||||
};
|
||||
});
|
||||
}
|
||||
this.Hosts = data.hosts ? data.hosts : [];
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
function TemplateLSIOViewModel(data) {
|
||||
this.Type = data.type;
|
||||
this.Title = data.title;
|
||||
this.Note = data.description;
|
||||
this.Categories = data.category ? data.category : [];
|
||||
this.Platform = data.platform ? data.platform : 'linux';
|
||||
this.Logo = data.logo;
|
||||
this.Image = data.image;
|
||||
this.Registry = data.registry ? data.registry : '';
|
||||
this.Command = data.command ? data.command : '';
|
||||
this.Network = data.network ? data.network : '';
|
||||
this.Env = data.env ? data.env : [];
|
||||
this.Privileged = data.privileged ? data.privileged : false;
|
||||
this.Interactive = data.interactive ? data.interactive : false;
|
||||
this.RestartPolicy = data.restart_policy ? data.restart_policy : 'always';
|
||||
this.Volumes = [];
|
||||
if (data.volumes) {
|
||||
this.Volumes = data.volumes.map(function (v) {
|
||||
return {
|
||||
readOnly: false,
|
||||
containerPath: v,
|
||||
type: 'auto'
|
||||
};
|
||||
});
|
||||
}
|
||||
this.Ports = [];
|
||||
if (data.ports) {
|
||||
this.Ports = data.ports.map(function (p) {
|
||||
var portAndProtocol = _.split(p, '/');
|
||||
return {
|
||||
containerPort: portAndProtocol[0],
|
||||
protocol: portAndProtocol[1]
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -27,9 +27,13 @@
|
|||
</div>
|
||||
<div ng-if="formValues.Registry || !fromContainer">
|
||||
<!-- image-and-registry -->
|
||||
<div class="form-group">
|
||||
<por-image-registry image="config.Image" registry="formValues.Registry" ng-if="formValues.Registry" auto-complete="true"></por-image-registry>
|
||||
</div>
|
||||
<por-image-registry
|
||||
image="config.Image"
|
||||
registry="formValues.Registry"
|
||||
ng-if="formValues.Registry"
|
||||
auto-complete="true"
|
||||
label-class="col-sm-1" input-class="col-sm-11 col-md-5"
|
||||
></por-image-registry>
|
||||
<!-- !image-and-registry -->
|
||||
<!-- always-pull -->
|
||||
<div class="form-group">
|
||||
|
|
|
@ -153,9 +153,12 @@
|
|||
</div>
|
||||
<!-- !tag-description -->
|
||||
<!-- image-and-registry -->
|
||||
<div class="form-group">
|
||||
<por-image-registry image="config.Image" registry="config.Registry"></por-image-registry>
|
||||
</div>
|
||||
<por-image-registry
|
||||
image="config.Image"
|
||||
registry="config.Registry"
|
||||
auto-complete="true"
|
||||
label-class="col-sm-1" input-class="col-sm-11 col-md-5"
|
||||
></por-image-registry>
|
||||
<!-- !image-and-registry -->
|
||||
<!-- tag-note -->
|
||||
<div class="form-group">
|
||||
|
|
|
@ -63,9 +63,11 @@
|
|||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- image-and-registry -->
|
||||
<div class="form-group">
|
||||
<por-image-registry image="formValues.Image" registry="formValues.Registry"></por-image-registry>
|
||||
</div>
|
||||
<por-image-registry
|
||||
image="formValues.Image"
|
||||
registry="formValues.Registry"
|
||||
label-class="col-sm-1" input-class="col-sm-11 col-md-5"
|
||||
></por-image-registry>
|
||||
<!-- !image-and-registry -->
|
||||
<!-- tag-note -->
|
||||
<div class="form-group">
|
||||
|
|
|
@ -15,9 +15,11 @@
|
|||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- image-and-registry -->
|
||||
<div class="form-group">
|
||||
<por-image-registry image="formValues.Image" registry="formValues.Registry"></por-image-registry>
|
||||
</div>
|
||||
<por-image-registry
|
||||
image="formValues.Image"
|
||||
registry="formValues.Registry"
|
||||
label-class="col-sm-1" input-class="col-sm-11 col-md-5"
|
||||
></por-image-registry>
|
||||
<!-- !image-and-registry -->
|
||||
<!-- tag-note -->
|
||||
<div class="form-group">
|
||||
|
|
|
@ -22,9 +22,12 @@
|
|||
Image configuration
|
||||
</div>
|
||||
<!-- image-and-registry -->
|
||||
<div class="form-group">
|
||||
<por-image-registry image="formValues.Image" registry="formValues.Registry" auto-complete="true"></por-image-registry>
|
||||
</div>
|
||||
<por-image-registry
|
||||
image="formValues.Image"
|
||||
registry="formValues.Registry"
|
||||
auto-complete="true"
|
||||
label-class="col-sm-1" input-class="col-sm-11 col-md-5"
|
||||
></por-image-registry>
|
||||
<!-- !image-and-registry -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Scheduling
|
||||
|
@ -142,7 +145,7 @@
|
|||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Command
|
||||
</div>
|
||||
</div>
|
||||
<!-- command-input -->
|
||||
<div class="form-group">
|
||||
<label for="service_command" class="col-sm-2 col-lg-1 control-label text-left">Command</label>
|
||||
|
@ -202,12 +205,12 @@
|
|||
<div class="col-sm-12 form-section-title">
|
||||
Logging
|
||||
</div>
|
||||
<!-- logging-driver -->
|
||||
<!-- logging-driver -->
|
||||
<div class="form-group">
|
||||
<label for="log-driver" class="col-sm-2 col-lg-1 control-label text-left">Driver</label>
|
||||
<div class="col-sm-4">
|
||||
<select class="form-control" ng-model="formValues.LogDriverName" id="log-driver">
|
||||
<option selected value="">Default logging driver</option>
|
||||
<option selected value="">Default logging driver</option>
|
||||
<option ng-repeat="driver in availableLoggingDrivers" ng-value="driver">{{ driver }}</option>
|
||||
<option value="none">none</option>
|
||||
</select>
|
||||
|
@ -247,10 +250,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- logging-opts-input-list -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- !logging-opts -->
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<!-- !tab-command -->
|
||||
|
|
|
@ -1,469 +0,0 @@
|
|||
<rd-header id="view-top">
|
||||
<rd-header-title title-text="Application templates list">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.templates" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-sync" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>Templates</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<!-- stack-form -->
|
||||
<div class="col-sm-12" ng-if="state.selectedTemplate && state.filters.Type === 'stack'">
|
||||
<rd-widget>
|
||||
<rd-widget-custom-header icon="state.selectedTemplate.Logo" title-text="state.selectedTemplate.Title">
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-click="unselectTemplate()">Hide</button>
|
||||
</div>
|
||||
</rd-widget-custom-header>
|
||||
<rd-widget-body classes="padding">
|
||||
|
||||
<form class="form-horizontal">
|
||||
<!-- description -->
|
||||
<div ng-if="state.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-if="state.selectedTemplate.Note" ng-bind-html="state.selectedTemplate.Note"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !description -->
|
||||
<div class="col-sm-12 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-10">
|
||||
<input type="text" name="container_name" class="form-control" ng-model="formValues.name" placeholder="e.g. myStack" required>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- env -->
|
||||
<div ng-repeat="var in state.selectedTemplate.Env" ng-if="var.label && !var.set" class="form-group">
|
||||
<label for="field_{{ $index }}" class="col-sm-2 control-label text-left">{{ var.label }}<portainer-tooltip ng-if="var.description" position="bottom" message="{{ var.description }}"></portainer-tooltip></label>
|
||||
<div class="col-sm-10">
|
||||
<!-- <input ng-if="!var.values && (!var.type || !var.type === 'container')" type="text" class="form-control" ng-model="var.value" id="field_{{ $index }}"> -->
|
||||
<input type="text" class="form-control" ng-if="!var.select" ng-model="var.value" id="field_{{ $index }}">
|
||||
<select class="form-control" ng-if="var.select" ng-model="var.value" id="field_{{ $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" ng-if="applicationState.application.authentication"></por-access-control-form>
|
||||
<!-- !access-control -->
|
||||
<!-- 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="state.actionInProgress || !formValues.name" ng-click="createTemplate()" button-spinner="state.actionInProgress">
|
||||
<span ng-hide="state.actionInProgress">Deploy the stack</span>
|
||||
<span ng-show="state.actionInProgress">Deployment in progress...</span>
|
||||
</button>
|
||||
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
<!-- !stack-form -->
|
||||
<!-- container-form -->
|
||||
<div class="col-sm-12" ng-if="state.selectedTemplate && state.filters.Type === 'container'">
|
||||
<rd-widget>
|
||||
<rd-widget-custom-header icon="state.selectedTemplate.Logo" title-text="state.selectedTemplate.Image">
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-click="unselectTemplate()">Hide</button>
|
||||
</div>
|
||||
</rd-widget-custom-header>
|
||||
<rd-widget-body classes="padding">
|
||||
|
||||
<form class="form-horizontal">
|
||||
<!-- description -->
|
||||
<div ng-if="state.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-if="state.selectedTemplate.Note" ng-bind-html="state.selectedTemplate.Note"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !description -->
|
||||
<div class="col-sm-12 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-10">
|
||||
<input type="text" name="container_name" class="form-control" ng-model="formValues.name" placeholder="e.g. web (optional)">
|
||||
</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-10">
|
||||
<select class="form-control" ng-options="net.Name for net in availableNetworks" ng-model="formValues.network">
|
||||
<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.set" class="form-group">
|
||||
<label for="field_{{ $index }}" class="col-sm-2 control-label text-left">{{ var.label }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select ng-if="var.type === 'container'" ng-options="container|containername for container in runningContainers" class="form-control" ng-model="var.value">
|
||||
<option selected disabled hidden value="">Select a container</option>
|
||||
</select>
|
||||
<input ng-if="!var.type || !var.type === 'container'" type="text" class="form-control" ng-model="var.value" id="field_{{ $index }}">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !env -->
|
||||
<!-- access-control -->
|
||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
||||
<!-- !access-control -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<a class="small interactive" ng-if="!state.showAdvancedOptions" ng-click="state.showAdvancedOptions = true;">
|
||||
<i class="fa fa-plus space-right" aria-hidden="true"></i> Show advanced options
|
||||
</a>
|
||||
<a class="small interactive" ng-if="state.showAdvancedOptions" ng-click="state.showAdvancedOptions = false;">
|
||||
<i class="fa fa-minus space-right" aria-hidden="true"></i> Hide advanced options
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="state.showAdvancedOptions">
|
||||
<!-- port-mapping -->
|
||||
<div class="form-group" >
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Port mapping</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addPortBinding()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> map additional port
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-12" style="margin-top: 10px" ng-if="state.selectedTemplate.Ports.length > 0">
|
||||
<span class="small text-muted">Portainer will automatically assign a port if you leave the host port empty.</span>
|
||||
</div>
|
||||
<!-- !port-mapping -->
|
||||
<!-- port-mapping-input-list -->
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="portBinding in state.selectedTemplate.Ports" style="margin-top: 2px;">
|
||||
<!-- 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)">
|
||||
</div>
|
||||
<!-- !host-port -->
|
||||
<span style="margin: 0 10px 0 10px;">
|
||||
<i class="fa fa-long-arrow-alt-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
<!-- 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">
|
||||
</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-primary" ng-model="portBinding.protocol" uib-btn-radio="'tcp'">TCP</label>
|
||||
<label class="btn btn-primary" ng-model="portBinding.protocol" uib-btn-radio="'udp'">UDP</label>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removePortBinding($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- !protocol-actions -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !port-mapping-input-list -->
|
||||
<!-- volume-mapping -->
|
||||
<div class="form-group" >
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Volume mapping</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addVolume()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> map additional volume
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-12" style="margin-top: 10px" ng-if="state.selectedTemplate.Volumes.length > 0">
|
||||
<span class="small text-muted">Portainer will automatically create and map a local volume when using the <b>auto</b> option.</span>
|
||||
</div>
|
||||
<div ng-repeat="volume in state.selectedTemplate.Volumes">
|
||||
<div class="col-sm-12" style="margin-top: 10px;">
|
||||
<!-- volume-line1 -->
|
||||
<div class="col-sm-12 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.containerPath" placeholder="e.g. /path/in/container">
|
||||
</div>
|
||||
<!-- !container-path -->
|
||||
<!-- volume-type -->
|
||||
<div class="input-group col-sm-5" style="margin-left: 5px;">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'auto'" ng-click="volume.name = ''">Auto</label>
|
||||
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'volume'" ng-click="volume.name = ''">Volume</label>
|
||||
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'bind'" ng-click="volume.name = ''" ng-if="isAdmin || allowBindMounts">Bind</label>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeVolume($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- !volume-type -->
|
||||
</div>
|
||||
<!-- !volume-line1 -->
|
||||
<!-- volume-line2 -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 5px;" ng-if="volume.type !== 'auto'">
|
||||
<i class="fa fa-long-arrow-alt-right" aria-hidden="true"></i>
|
||||
<!-- volume -->
|
||||
<div class="input-group input-group-sm col-sm-6" ng-if="volume.type === 'volume'">
|
||||
<span class="input-group-addon">volume</span>
|
||||
<select class="form-control" ng-model="volume.name">
|
||||
<option selected disabled hidden value="">Select a volume</option>
|
||||
<option ng-repeat="vol in availableVolumes" ng-value="vol.Name">{{ vol.Name|truncate:30}}</option>
|
||||
</select>
|
||||
</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.name" placeholder="e.g. /path/on/host">
|
||||
</div>
|
||||
<!-- !bind -->
|
||||
<!-- read-only -->
|
||||
<div class="input-group input-group-sm col-sm-5" style="margin-left: 5px;">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="volume.readOnly" uib-btn-radio="false">Writable</label>
|
||||
<label class="btn btn-primary" ng-model="volume.readOnly" uib-btn-radio="true">Read-only</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !read-only -->
|
||||
</div>
|
||||
<!-- !volume-line2 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !volume-mapping -->
|
||||
<!-- extra-host -->
|
||||
<div class="form-group" >
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Hosts file entries</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addExtraHost()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add additional entry
|
||||
</span>
|
||||
</div>
|
||||
<!-- extra-host-input-list -->
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="(idx, host) in state.selectedTemplate.Hosts track by $index" style="margin-top: 2px;">
|
||||
<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">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeExtraHost($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !extra-host -->
|
||||
<!-- Label -->
|
||||
<div class="form-group" >
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Labels</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addLabel()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add label
|
||||
</span>
|
||||
</div>
|
||||
<!-- labels-input-list -->
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="label in state.selectedTemplate.Labels" style="margin-top: 2px;">
|
||||
<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">
|
||||
</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">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeLabel($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !labels-input-list -->
|
||||
</div>
|
||||
<!-- !Label -->
|
||||
<!-- hostname -->
|
||||
<div class="form-group">
|
||||
<label for="container_hostname" class="col-sm-2 control-label text-left">Hostname</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" name="container_hostname" class="form-control" ng-model="state.selectedTemplate.Hostname" placeholder="leave empty to use docker default">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !hostname -->
|
||||
</div>
|
||||
<!-- !advanced-options -->
|
||||
<!-- 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="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>
|
||||
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
<!-- container-form -->
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12" style="height: 100%">
|
||||
<rd-template-widget>
|
||||
<rd-widget-header icon="fa-rocket" title-text="Templates">
|
||||
<div ng-if="availableCategories.length > 0" class="pull-right">
|
||||
Category
|
||||
<select ng-model="state.filters.Categories">
|
||||
<option value="!">All</option>
|
||||
<option ng-repeat="category in availableCategories" value="{{ category }}">{{ category }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar>
|
||||
<div>
|
||||
<!-- Platform -->
|
||||
<span class="btn-group btn-group-sm" style="margin-right: 15px;">
|
||||
<label class="btn btn-primary" ng-model="state.filters.Platform" uib-btn-radio="'!'">
|
||||
All
|
||||
</label>
|
||||
<label class="btn btn-primary" ng-model="state.filters.Platform" uib-btn-radio="'windows'">
|
||||
<i class="fab fa-windows" aria-hidden="true"></i>
|
||||
Windows
|
||||
</label>
|
||||
<label class="btn btn-primary" ng-model="state.filters.Platform" uib-btn-radio="'linux'">
|
||||
<i class="fab fa-linux" aria-hidden="true"></i>
|
||||
Linux
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="padding template-widget-body">
|
||||
<form class="form-horizontal">
|
||||
<div ng-if="templatesKey !== 'linuxserver.io' && state.showDeploymentSelector">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Deployment method
|
||||
</div>
|
||||
<div class="form-group"></div>
|
||||
<div class="form-group" style="margin-bottom: 0">
|
||||
<div class="boxselector_wrapper">
|
||||
<div ng-click="updateCategories(templates, state.filters.Type)">
|
||||
<input type="radio" id="template_stack" ng-model="state.filters.Type" value="stack">
|
||||
<label for="template_stack">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-th-list" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Stack
|
||||
</div>
|
||||
<p>Multi-containers deployment</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-click="updateCategories(templates, state.filters.Type)">
|
||||
<input type="radio" id="template_container" ng-model="state.filters.Type" value="container">
|
||||
<label for="template_container">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-server" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Container
|
||||
</div>
|
||||
<p>Single container deployment</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="templatesKey !== 'linuxserver.io' && state.showDeploymentSelector">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Templates
|
||||
</div>
|
||||
<div class="form-group"></div>
|
||||
</div>
|
||||
|
||||
<div class="template-list">
|
||||
<!-- template -->
|
||||
<div ng-repeat="tpl in templates | filter:state.filters:true" class="template-container" id="template_{{ tpl.index }}" ng-click="selectTemplate(tpl.index, $index)">
|
||||
<div class="template-main">
|
||||
<!-- template-image -->
|
||||
<span class="">
|
||||
<img class="template-logo" ng-src="{{ tpl.Logo }}" />
|
||||
</span>
|
||||
<!-- !template-image -->
|
||||
<!-- template-details -->
|
||||
<span class="col-sm-12">
|
||||
<!-- template-line1 -->
|
||||
<div class="template-line">
|
||||
<span class="template-title">
|
||||
{{ tpl.Title }}
|
||||
</span>
|
||||
<span>
|
||||
<i class="fab fa-windows" aria-hidden="true" ng-if="tpl.Platform === 'windows'"></i>
|
||||
<i class="fab fa-linux" aria-hidden="true" ng-if="tpl.Platform === 'linux'"></i>
|
||||
<!-- Arch / Platform -->
|
||||
</span>
|
||||
</div>
|
||||
<!-- !template-line1 -->
|
||||
<!-- template-line2 -->
|
||||
<div class="template-line">
|
||||
<span class="template-description">
|
||||
{{ tpl.Description }}
|
||||
</span>
|
||||
<span class="small text-muted" ng-if="tpl.Categories.length > 0">
|
||||
{{ tpl.Categories.join(', ') }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- !template-line2 -->
|
||||
</span>
|
||||
<!-- !template-details -->
|
||||
</div>
|
||||
<!-- !template -->
|
||||
</div>
|
||||
<div ng-if="!templates" class="text-center text-muted">
|
||||
Loading...
|
||||
</div>
|
||||
<div ng-if="(templates | filter:state.filters:true).length == 0" class="text-center text-muted">
|
||||
No templates available.
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-template-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -1,274 +0,0 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('TemplatesController', ['$scope', '$q', '$state', '$transition$', '$anchorScroll', '$filter', 'ContainerService', 'ContainerHelper', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Notifications', 'PaginationService', 'ResourceControlService', 'Authentication', 'FormValidator', 'SettingsService', 'StackService', 'EndpointProvider',
|
||||
function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerService, ContainerHelper, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Notifications, PaginationService, ResourceControlService, Authentication, FormValidator, SettingsService, StackService, EndpointProvider) {
|
||||
$scope.state = {
|
||||
selectedTemplate: null,
|
||||
showAdvancedOptions: false,
|
||||
hideDescriptions: $transition$.params().hide_descriptions,
|
||||
formValidationError: '',
|
||||
showDeploymentSelector: false,
|
||||
actionInProgress: false,
|
||||
filters: {
|
||||
Categories: '!',
|
||||
Platform: '!',
|
||||
Type: 'container'
|
||||
}
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
network: '',
|
||||
name: '',
|
||||
AccessControlData: new AccessControlFormData()
|
||||
};
|
||||
|
||||
$scope.addVolume = function () {
|
||||
$scope.state.selectedTemplate.Volumes.push({ containerPath: '', name: '', 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) {
|
||||
var volumeResourceControlQueries = [];
|
||||
angular.forEach(data, function (volume) {
|
||||
var volumeId = volume.Id;
|
||||
generatedVolumeIds.push(volumeId);
|
||||
});
|
||||
TemplateService.updateContainerConfigurationWithVolumes(templateConfiguration, template, data);
|
||||
return ImageService.pullImage(template.Image, { URL: template.Registry }, true);
|
||||
})
|
||||
.then(function success(data) {
|
||||
return ContainerService.createAndStartContainer(templateConfiguration);
|
||||
})
|
||||
.then(function success(data) {
|
||||
var containerIdentifier = data.Id;
|
||||
return ResourceControlService.applyResourceControl('container', containerIdentifier, userId, accessControlData, generatedVolumeIds);
|
||||
})
|
||||
.then(function success() {
|
||||
Notifications.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 createStackFromTemplate(template, userId, accessControlData) {
|
||||
var stackName = $scope.formValues.name;
|
||||
|
||||
for (var i = 0; i < template.Env.length; i++) {
|
||||
var envvar = template.Env[i];
|
||||
if (envvar.set) {
|
||||
envvar.value = envvar.set;
|
||||
}
|
||||
}
|
||||
|
||||
var repositoryOptions = {
|
||||
RepositoryURL: template.Repository.url,
|
||||
ComposeFilePathInRepository: template.Repository.stackfile
|
||||
};
|
||||
|
||||
var endpointId = EndpointProvider.endpointID();
|
||||
StackService.createSwarmStackFromGitRepository(stackName, repositoryOptions, template.Env, endpointId)
|
||||
.then(function success(data) {
|
||||
return ResourceControlService.applyResourceControl('stack', stackName, userId, accessControlData, []);
|
||||
})
|
||||
.then(function success() {
|
||||
Notifications.success('Stack successfully deployed');
|
||||
$state.go('portainer.stacks');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.warning('Deployment error', err.err.data.err);
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.createTemplate = function() {
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var userId = userDetails.ID;
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
var isAdmin = userDetails.role === 1;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var template = $scope.state.selectedTemplate;
|
||||
var templatesKey = $scope.templatesKey;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
if (template.Type === 'stack') {
|
||||
createStackFromTemplate(template, userId, accessControlData);
|
||||
} else {
|
||||
createContainerFromTemplate(template, userId, accessControlData);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.unselectTemplate = function() {
|
||||
var currentTemplateIndex = $scope.state.selectedTemplate.index;
|
||||
$('#template_' + currentTemplateIndex).toggleClass('template-container--selected');
|
||||
$scope.state.selectedTemplate = null;
|
||||
};
|
||||
|
||||
$scope.selectTemplate = function(index, pos) {
|
||||
if ($scope.state.selectedTemplate && $scope.state.selectedTemplate.index !== index) {
|
||||
$scope.unselectTemplate();
|
||||
}
|
||||
|
||||
var templates = $filter('filter')($scope.templates, $scope.state.filters, true);
|
||||
var template = templates[pos];
|
||||
if (template === $scope.state.selectedTemplate) {
|
||||
$scope.unselectTemplate();
|
||||
} else {
|
||||
selectTemplate(index, pos, templates);
|
||||
}
|
||||
};
|
||||
|
||||
function selectTemplate(index, pos, filteredTemplates) {
|
||||
$('#template_' + index).toggleClass('template-container--selected');
|
||||
var selectedTemplate = filteredTemplates[pos];
|
||||
$scope.state.selectedTemplate = selectedTemplate;
|
||||
|
||||
if (selectedTemplate.Network) {
|
||||
$scope.formValues.network = _.find($scope.availableNetworks, function(o) { return o.Name === selectedTemplate.Network; });
|
||||
} else {
|
||||
$scope.formValues.network = _.find($scope.availableNetworks, function(o) { return o.Name === 'bridge'; });
|
||||
}
|
||||
|
||||
if (selectedTemplate.Name) {
|
||||
$scope.formValues.name = selectedTemplate.Name;
|
||||
} else {
|
||||
$scope.formValues.name = '';
|
||||
}
|
||||
|
||||
$anchorScroll('view-top');
|
||||
}
|
||||
|
||||
function createTemplateConfiguration(template) {
|
||||
var network = $scope.formValues.network;
|
||||
var name = $scope.formValues.name;
|
||||
var containerMapping = determineContainerMapping(network);
|
||||
return TemplateService.createTemplateConfiguration(template, name, network, containerMapping);
|
||||
}
|
||||
|
||||
function determineContainerMapping(network) {
|
||||
var containerMapping = 'BY_CONTAINER_IP';
|
||||
if (network.Name !== 'bridge') {
|
||||
containerMapping = 'BY_CONTAINER_NAME';
|
||||
}
|
||||
return containerMapping;
|
||||
}
|
||||
|
||||
$scope.updateCategories = function(templates, type) {
|
||||
$scope.state.filters.Categories = '!';
|
||||
updateCategories(templates, type);
|
||||
};
|
||||
|
||||
function updateCategories(templates, type) {
|
||||
var availableCategories = [];
|
||||
angular.forEach(templates, function(template) {
|
||||
if (template.Type === type) {
|
||||
availableCategories = availableCategories.concat(template.Categories);
|
||||
}
|
||||
});
|
||||
$scope.availableCategories = _.sortBy(_.uniq(availableCategories));
|
||||
}
|
||||
|
||||
function initTemplates(templatesKey, type, provider, apiVersion) {
|
||||
$q.all({
|
||||
templates: TemplateService.getTemplates(templatesKey),
|
||||
containers: ContainerService.containers(0),
|
||||
volumes: VolumeService.getVolumes(),
|
||||
networks: NetworkService.networks(
|
||||
provider === 'DOCKER_STANDALONE' || provider === 'DOCKER_SWARM_MODE',
|
||||
false,
|
||||
provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25
|
||||
),
|
||||
settings: SettingsService.publicSettings()
|
||||
})
|
||||
.then(function success(data) {
|
||||
var templates = data.templates;
|
||||
updateCategories(templates, type);
|
||||
$scope.templates = templates;
|
||||
$scope.runningContainers = data.containers;
|
||||
$scope.availableVolumes = data.volumes.Volumes;
|
||||
var networks = data.networks;
|
||||
$scope.availableNetworks = networks;
|
||||
$scope.globalNetworkCount = networks.length;
|
||||
var settings = data.settings;
|
||||
$scope.allowBindMounts = settings.AllowBindMountsForRegularUsers;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.templates = [];
|
||||
Notifications.error('Failure', err, 'An error occured during apps initialization.');
|
||||
});
|
||||
}
|
||||
|
||||
function initView() {
|
||||
var templatesKey = $transition$.params().key;
|
||||
$scope.templatesKey = templatesKey;
|
||||
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
$scope.isAdmin = userDetails.role === 1;
|
||||
|
||||
var endpointMode = $scope.applicationState.endpoint.mode;
|
||||
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
||||
|
||||
if (templatesKey !== 'linuxserver.io'
|
||||
&& endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER' && apiVersion >= 1.25) {
|
||||
$scope.state.filters.Type = 'stack';
|
||||
$scope.state.showDeploymentSelector = true;
|
||||
}
|
||||
|
||||
initTemplates(templatesKey, $scope.state.filters.Type, endpointMode.provider, apiVersion);
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
Loading…
Add table
Add a link
Reference in a new issue