mirror of https://github.com/portainer/portainer
468 lines
24 KiB
HTML
468 lines
24 KiB
HTML
<rd-header id="view-top">
|
|
<rd-header-title title="Application templates list">
|
|
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.templates" ui-sref-opts="{reload: true}">
|
|
<i class="fa fa-refresh" 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="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="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="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM' && 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>
|
|
<select ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM' && var.type === 'container'" ng-options="container|swarmcontainername 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-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-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 -->
|
|
</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="small text-muted" style="margin-left: 10px" ng-if="globalNetworkCount === 0 && applicationState.endpoint.mode.provider === 'DOCKER_SWARM' && !state.formValidationError">
|
|
When using Swarm, we recommend deploying containers in a shared network. Looks like you don't have any shared network, head over the <a ui-sref="docker.networks">networks view</a> to create one.
|
|
</span>
|
|
<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="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="fa fa-windows" aria-hidden="true"></i>
|
|
Windows
|
|
</label>
|
|
<label class="btn btn-primary" ng-model="state.filters.Platform" uib-btn-radio="'linux'">
|
|
<i class="fa 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="fa fa-windows" aria-hidden="true" ng-if="tpl.Platform === 'windows'"></i>
|
|
<i class="fa 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>
|