mirror of https://github.com/portainer/portainer
758 lines
43 KiB
HTML
758 lines
43 KiB
HTML
<page-header
|
|
ng-if="!ctrl.state.isEdit && !ctrl.stack.IsComposeFormat && ctrl.state.viewReady"
|
|
title="'Create application'"
|
|
breadcrumbs="[
|
|
{ label:'Applications', link:'kubernetes.applications' },
|
|
'Create an application'
|
|
]"
|
|
reload="true"
|
|
>
|
|
</page-header>
|
|
|
|
<page-header
|
|
ng-if="ctrl.state.isEdit && !ctrl.stack.IsComposeFormat && ctrl.state.viewReady"
|
|
title="'Edit application'"
|
|
breadcrumbs="[
|
|
{ label:'Namespaces', link:'kubernetes.resourcePools' },
|
|
{
|
|
label:ctrl.application.ResourcePool,
|
|
link: 'kubernetes.resourcePools.resourcePool',
|
|
linkParams:{ id: ctrl.application.ResourcePool }
|
|
},
|
|
{ label:'Applications', link:'kubernetes.applications' },
|
|
{
|
|
label:ctrl.application.Name,
|
|
link: 'kubernetes.applications.application',
|
|
linkParams:{ name: ctrl.application.Name, namespace: ctrl.application.ResourcePool }
|
|
},
|
|
'Edit',
|
|
]"
|
|
reload="true"
|
|
>
|
|
</page-header>
|
|
|
|
<page-header
|
|
ng-if="ctrl.stack.IsComposeFormat"
|
|
title="'View application'"
|
|
breadcrumbs="[
|
|
{ label:'Namespaces', link:'kubernetes.resourcePools' },
|
|
{
|
|
label:ctrl.application.ResourcePool,
|
|
link: 'kubernetes.resourcePools.resourcePool',
|
|
linkParams:{ id: ctrl.application.ResourcePool }
|
|
},
|
|
{ label:'Applications', link:'kubernetes.applications' },
|
|
{
|
|
label:ctrl.application.Name,
|
|
link: 'kubernetes.applications.application',
|
|
linkParams:{ name: ctrl.application.Name, namespace: ctrl.application.ResourcePool }
|
|
},
|
|
'View',
|
|
]"
|
|
reload="true"
|
|
>
|
|
</page-header>
|
|
|
|
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
|
|
<div ng-if="ctrl.state.viewReady">
|
|
<div class="row kubernetes-create">
|
|
<div class="col-xs-12">
|
|
<rd-widget>
|
|
<rd-widget-body>
|
|
<form class="form-horizontal mt-4" name="kubernetesApplicationCreationForm" autocomplete="off" novalidate>
|
|
<div ng-if="ctrl.isExternalApplication()">
|
|
<div class="col-sm-12 form-section-title" ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"> Namespace </div>
|
|
<!-- #region NAMESPACE -->
|
|
<div class="form-group" ng-if="ctrl.formValues.ResourcePool">
|
|
<label for="resource-pool-selector" class="col-sm-1 control-label text-left">Namespace</label>
|
|
<div class="col-sm-11">
|
|
<select
|
|
class="form-control"
|
|
id="resource-pool-selector"
|
|
ng-model="ctrl.formValues.ResourcePool"
|
|
ng-options="resourcePool.Namespace.Name for resourcePool in ctrl.resourcePools"
|
|
ng-change="ctrl.onResourcePoolSelectionChange()"
|
|
ng-disabled="ctrl.state.isEdit"
|
|
data-cy="k8sAppCreate-nsSelect"
|
|
></select>
|
|
</div>
|
|
</div>
|
|
<div class="form-group" ng-if="ctrl.state.resourcePoolHasQuota && ctrl.resourceQuotaCapacityExceeded() && ctrl.formValues.ResourcePool">
|
|
<div class="col-sm-12 small text-danger">
|
|
<pr-icon icon="'alert-circle'" mode="'danger'"></pr-icon>
|
|
This namespace has exhausted its resource capacity and you will not be able to deploy the application. Contact your administrator to expand the capacity of the
|
|
namespace.
|
|
</div>
|
|
</div>
|
|
<div class="form-group" ng-if="!ctrl.formValues.ResourcePool">
|
|
<div class="col-sm-12 small text-muted">
|
|
<pr-icon icon="'alert-circle'" mode="'warning'"></pr-icon>
|
|
You do not have access to any namespace. Contact your administrator to get access to a namespace.
|
|
</div>
|
|
</div>
|
|
<!-- kubernetes services options -->
|
|
<div ng-if="ctrl.formValues.ResourcePool">
|
|
<kube-services-form
|
|
on-change="(ctrl.onServicesChange)"
|
|
values="ctrl.formValues.Services"
|
|
app-name="ctrl.formValues.Name"
|
|
selector="ctrl.formValues.Selector"
|
|
validation-data="{nodePortServices: ctrl.state.nodePortServices, formServices: ctrl.formValues.Services, ingressPaths: ctrl.ingressPaths, originalIngressPaths: ctrl.originalIngressPaths}"
|
|
is-edit-mode="ctrl.state.isEdit"
|
|
namespace="ctrl.formValues.ResourcePool.Namespace.Name"
|
|
></kube-services-form>
|
|
</div>
|
|
<!-- kubernetes services options -->
|
|
<kubernetes-summary-view form-values="ctrl.formValues" old-form-values="ctrl.savedFormValues"></kubernetes-summary-view>
|
|
<!-- #region ACTIONS -->
|
|
<div class="col-sm-12 form-section-title !mt-6" ng-if="ctrl.state.appType !== ctrl.KubernetesDeploymentTypes.GIT" ng-hide="ctrl.stack.IsComposeFormat"> Actions </div>
|
|
<div class="form-group" ng-hide="ctrl.stack.IsComposeFormat">
|
|
<div class="col-sm-12">
|
|
<button
|
|
ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"
|
|
type="button"
|
|
class="btn btn-primary btn-sm !ml-0"
|
|
ng-disabled="!kubernetesApplicationCreationForm.$valid || ctrl.isDeployUpdateButtonDisabled() || !ctrl.imageValidityIsValid() || ctrl.hasPortErrors() || !ctrl.formValues.ResourcePool"
|
|
ng-click="ctrl.deployApplication()"
|
|
button-spinner="ctrl.state.actionInProgress"
|
|
data-cy="k8sAppCreate-deployButton"
|
|
>
|
|
<span ng-show="!ctrl.state.isEdit && !ctrl.state.actionInProgress">Deploy application</span>
|
|
<span ng-show="!ctrl.state.isEdit && ctrl.state.actionInProgress">Deployment in progress...</span>
|
|
<span ng-show="ctrl.state.isEdit && !ctrl.state.actionInProgress">Update application</span>
|
|
<span ng-show="ctrl.state.isEdit && ctrl.state.actionInProgress">Update in progress...</span>
|
|
</button>
|
|
<button
|
|
ng-if="ctrl.state.isEdit && !ctrl.state.actionInProgress && ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"
|
|
type="button"
|
|
class="btn btn-sm btn-default"
|
|
ui-sref="kubernetes.applications.application({ name: ctrl.application.Name, namespace: ctrl.application.ResourcePool })"
|
|
data-cy="k8sAppCreate-appCancelButton"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<!-- #Web editor buttons -->
|
|
<button
|
|
class="btn btn-sm btn-primary"
|
|
ng-click="ctrl.updateApplicationViaWebEditor()"
|
|
ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.CONTENT || ctrl.state.updateWebEditorInProgress"
|
|
ng-disabled="!kubernetesApplicationCreationForm.$valid || !ctrl.state.isEditorDirty || ctrl.state.updateWebEditorInProgress"
|
|
style="margin-top: 7px; margin-left: 0"
|
|
button-spinner="ctrl.state.updateWebEditorInProgress"
|
|
>
|
|
<span ng-show="!ctrl.state.updateWebEditorInProgress">Update the application</span>
|
|
<span ng-show="ctrl.state.updateWebEditorInProgress">Update in progress...</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<!-- #endregion -->
|
|
</div>
|
|
<div ng-if="!ctrl.isExternalApplication()">
|
|
<git-form-info-panel
|
|
ng-if="ctrl.state.appType == ctrl.KubernetesDeploymentTypes.GIT"
|
|
class-name="'text-muted'"
|
|
url="ctrl.stack.GitConfig.URL"
|
|
config-file-path="ctrl.stack.GitConfig.ConfigFilePath"
|
|
additional-files="ctrl.stack.AdditionalFiles"
|
|
type="'application'"
|
|
></git-form-info-panel>
|
|
<!-- #region NAMESPACE -->
|
|
<div class="form-group" ng-if="ctrl.formValues.ResourcePool">
|
|
<label for="resource-pool-selector" class="col-sm-3 col-lg-2 control-label text-left">Namespace</label>
|
|
<div class="col-sm-8">
|
|
<select
|
|
class="form-control"
|
|
id="resource-pool-selector"
|
|
ng-model="ctrl.formValues.ResourcePool"
|
|
ng-options="resourcePool.Namespace.Name for resourcePool in ctrl.resourcePools"
|
|
ng-change="ctrl.onResourcePoolSelectionChange()"
|
|
ng-disabled="ctrl.state.isEdit"
|
|
data-cy="k8sAppCreate-nsSelect"
|
|
></select>
|
|
</div>
|
|
</div>
|
|
<div class="form-group" ng-if="ctrl.state.resourcePoolHasQuota && ctrl.resourceQuotaCapacityExceeded() && ctrl.formValues.ResourcePool">
|
|
<div class="col-sm-12 small text-danger">
|
|
<pr-icon icon="'alert-triangle'"></pr-icon>
|
|
This namespace has exhausted its resource capacity and you will not be able to deploy the application. Contact your administrator to expand the capacity of the
|
|
namespace.
|
|
</div>
|
|
</div>
|
|
<div class="form-group" ng-if="!ctrl.formValues.ResourcePool">
|
|
<div class="col-sm-12 small text-warning">
|
|
<pr-icon icon="'alert-triangle'"></pr-icon>
|
|
You do not have access to any namespace. Contact your administrator to get access to a namespace.
|
|
</div>
|
|
</div>
|
|
<!-- #endregion -->
|
|
|
|
<!-- #region STACK -->
|
|
<div class="form-group" ng-if="!ctrl.deploymentOptions.hideStacksFunctionality && ctrl.state.appType !== ctrl.KubernetesDeploymentTypes.APPLICATION_FORM">
|
|
<div class="col-sm-12 small text-muted vertical-center">
|
|
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
|
Portainer can automatically bundle multiple applications inside a stack. Enter a name of a new stack or select an existing stack in the list. Leave empty to use
|
|
the application name.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" ng-if="!ctrl.deploymentOptions.hideStacksFunctionality && ctrl.state.appType !== ctrl.KubernetesDeploymentTypes.APPLICATION_FORM">
|
|
<label for="stack_name" class="col-sm-3 col-lg-2 control-label text-left">
|
|
Stack
|
|
<portainer-tooltip
|
|
ng-if="!ctrl.isAdmin"
|
|
message="'The stack field below was previously labelled \'Name\' but, in
|
|
fact, it\'s always been the stack name (hence the relabelling).'"
|
|
class-name="'[&>span]:!text-left'"
|
|
set-html-message="true"
|
|
>
|
|
</portainer-tooltip>
|
|
<portainer-tooltip
|
|
ng-if="ctrl.isAdmin"
|
|
message="'The stack field below was previously labelled \'Name\' but, in
|
|
fact, it\'s always been the stack name (hence the relabelling).<br/>
|
|
Kubernetes Stacks functionality can be turned off entirely via
|
|
<a href=\'/#!/settings\' target=\'_blank\'>
|
|
Kubernetes Settings
|
|
</a>.'"
|
|
class-name="'[&>span]:!text-left'"
|
|
set-html-message="true"
|
|
>
|
|
</portainer-tooltip>
|
|
</label>
|
|
<div class="col-sm-8">
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
placeholder="myStack"
|
|
ng-model="ctrl.formValues.StackName"
|
|
name="stack_name"
|
|
uib-typeahead="stack for stack in ctrl.stacks | filter:$viewValue | limitTo:7"
|
|
typeahead-min-length="0"
|
|
data-cy="k8sAppCreate-stackName"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<!-- #endregion -->
|
|
|
|
<!-- #region Git repository -->
|
|
<kubernetes-redeploy-app-git-form
|
|
ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.GIT"
|
|
stack="ctrl.stack"
|
|
namespace="ctrl.formValues.ResourcePool.Namespace.Name"
|
|
></kubernetes-redeploy-app-git-form>
|
|
<!-- #endregion -->
|
|
|
|
<!-- #region web editor -->
|
|
<web-editor-form
|
|
read-only="ctrl.stack.IsComposeFormat"
|
|
ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.CONTENT"
|
|
value="ctrl.stackFileContent"
|
|
yml="true"
|
|
identifier="kubernetes-deploy-editor"
|
|
placeholder="Define or paste the content of your manifest file here"
|
|
on-change="(ctrl.onChangeFileContent)"
|
|
>
|
|
<editor-description>
|
|
<div class="flex gap-1" ng-show="ctrl.stack.IsComposeFormat">
|
|
<pr-icon icon="'alert-circle'" mode="'warning'" class-name="'!mt-1'"></pr-icon>
|
|
<div>
|
|
<p>
|
|
Portainer no longer supports <a href="https://docs.docker.com/compose/compose-file/" target="_blank">docker-compose</a> format manifests for Kubernetes
|
|
deployments, and we have removed the <a href="https://kompose.io/" target="_blank">Kompose</a>
|
|
conversion tool which enables this. The reason for this is because Kompose now poses a security risk, since it has a number of Common Vulnerabilities and
|
|
Exposures (CVEs).
|
|
</p>
|
|
<p>
|
|
Unfortunately, while the Kompose project has a maintainer and is part of the CNCF, it is not being actively maintained. Releases are very infrequent and new
|
|
pull requests to the project (including ones we've submitted) are taking months to be merged, with new CVEs arising in the meantime.
|
|
</p>
|
|
<p>
|
|
We advise installing your own instance of Kompose in a sandbox environment, performing conversions of your Docker Compose files to Kubernetes manifests and
|
|
using those manifests to set up applications.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<span ng-show="!ctrl.stack.IsComposeFormat">
|
|
<p class="vertical-center">
|
|
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
|
This feature allows you to deploy any kind of Kubernetes resource in this environment (Deployment, Secret, ConfigMap...).
|
|
</p>
|
|
<p>
|
|
You can get more information about Kubernetes file format in the
|
|
<a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/" target="_blank">official documentation</a>.
|
|
</p>
|
|
</span>
|
|
</editor-description>
|
|
</web-editor-form>
|
|
<!-- #endregion -->
|
|
<div ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM">
|
|
<!-- #region NAME FIELD -->
|
|
<div class="form-group">
|
|
<label for="application_name" class="col-sm-3 col-lg-2 control-label required text-left">Name</label>
|
|
<div class="col-sm-8">
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
name="application_name"
|
|
ng-model="ctrl.formValues.Name"
|
|
ng-change="ctrl.onChangeName()"
|
|
placeholder="my-app"
|
|
ng-pattern="/^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$/"
|
|
auto-focus
|
|
required
|
|
ng-disabled="ctrl.state.isEdit"
|
|
data-cy="k8sAppCreate-applicationName"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="form-group" ng-show="kubernetesApplicationCreationForm.application_name.$invalid || ctrl.state.alreadyExists">
|
|
<div class="small">
|
|
<div class="col-sm-3 col-lg-2"> </div>
|
|
<div class="col-sm-8" ng-messages="kubernetesApplicationCreationForm.application_name.$error">
|
|
<p class="text-warning vertical-center" ng-message="required"
|
|
><pr-icon class="vertical-center" icon="'alert-triangle'" mode="'warning'"></pr-icon> This field is required.</p
|
|
>
|
|
<p class="text-warning vertical-center" ng-message="pattern">
|
|
<pr-icon class="vertical-center" icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
|
This field must consist of lower case alphanumeric characters or '-', contain at most 63 characters, start with an alphabetic character, and end with an
|
|
alphanumeric character (e.g. 'my-name', or 'abc-123').
|
|
</p>
|
|
</div>
|
|
<div class="col-sm-8" ng-if="ctrl.state.alreadyExists">
|
|
<p class="text-warning vertical-center">
|
|
<pr-icon class="vertical-center" icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
|
An application with the same name already exists inside the selected namespace.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- #endregion -->
|
|
|
|
<!-- #region IMAGE FIELD -->
|
|
<div class="form-group mb-2">
|
|
<div class="col-sm-12">
|
|
<por-image-registry
|
|
model="ctrl.formValues.ImageModel"
|
|
ng-if="ctrl.formValues.ResourcePool"
|
|
auto-complete="false"
|
|
label-class="col-sm-3 col-lg-2"
|
|
input-class="col-sm-8"
|
|
namespace="ctrl.formValues.ResourcePool.Namespace.Name"
|
|
endpoint="ctrl.endpoint"
|
|
is-admin="ctrl.isAdmin"
|
|
check-rate-limits="true"
|
|
set-validity="ctrl.setPullImageValidity"
|
|
></por-image-registry>
|
|
</div>
|
|
</div>
|
|
<!-- #end region IMAGE FIELD -->
|
|
|
|
<div class="col-sm-12 mb-4 !p-0">
|
|
<annotations-be-teaser></annotations-be-teaser>
|
|
</div>
|
|
|
|
<div ng-if="ctrl.formValues.ResourcePool">
|
|
<!-- #region STACK -->
|
|
<div class="form-group" ng-if="!ctrl.deploymentOptions.hideStacksFunctionality">
|
|
<div class="col-sm-12 small text-muted vertical-center">
|
|
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
|
Portainer can automatically bundle multiple applications inside a stack. Enter a name of a new stack or select an existing stack in the list. Leave empty to
|
|
use the application name.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" ng-if="!ctrl.deploymentOptions.hideStacksFunctionality">
|
|
<label for="stack_name" class="col-sm-3 col-lg-2 control-label text-left">Stack</label>
|
|
<div class="col-sm-8">
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
placeholder="myStack"
|
|
ng-model="ctrl.formValues.StackName"
|
|
name="stack_name"
|
|
uib-typeahead="stack for stack in ctrl.stacks | filter:$viewValue | limitTo:7"
|
|
typeahead-min-length="0"
|
|
data-cy="k8sAppCreate-stackName"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<!-- #endregion -->
|
|
|
|
<!-- #region ENVIRONMENT VARIABLES -->
|
|
<div class="form-group">
|
|
<div class="col-sm-12 vertical-center">
|
|
<label class="control-label !pt-0 text-left !text-sm">Environment variables</label>
|
|
</div>
|
|
<div class="col-sm-11 col-lg-10 mt-2">
|
|
<kube-environment-variables-fieldset
|
|
values="ctrl.formValues.EnvironmentVariables"
|
|
on-change="(ctrl.onEnvironmentVariableChange)"
|
|
can-undo-delete="true"
|
|
></kube-environment-variables-fieldset>
|
|
</div>
|
|
</div>
|
|
<!-- #endregion -->
|
|
|
|
<!-- #region CONFIGMAPS -->
|
|
<config-maps-form-section
|
|
values="ctrl.formValues.ConfigMaps"
|
|
on-change="(ctrl.onConfigMapsChange)"
|
|
namespace="ctrl.formValues.ResourcePool.Namespace.Name"
|
|
validation-data="ctrl.formValues.ConfigMaps"
|
|
></config-maps-form-section>
|
|
|
|
<!-- #region SECRETS -->
|
|
<secrets-form-section
|
|
values="ctrl.formValues.Secrets"
|
|
on-change="(ctrl.onSecretsChange)"
|
|
namespace="ctrl.formValues.ResourcePool.Namespace.Name"
|
|
validation-data="ctrl.formValues.Secrets"
|
|
></secrets-form-section>
|
|
<!-- #endregion -->
|
|
|
|
<persisted-folders-form-section
|
|
values="ctrl.formValues.PersistedFolders"
|
|
initial-values="ctrl.formValues.OriginalPersistedFolders"
|
|
on-change="(ctrl.onChangePersistedFolder)"
|
|
is-edit="ctrl.state.isEdit"
|
|
application-values="ctrl.formValues"
|
|
is-add-persistent-folder-button-shown="ctrl.isAddPersistentFolderButtonShown()"
|
|
available-volumes="ctrl.availableVolumes"
|
|
validation-data="{ namespaceQuotas: ctrl.formValues.ResourcePool.Quota, persistedFolders: ctrl.formValues.PersistedFolders, storageAvailabilities: ctrl.state.storages.availabilities }"
|
|
></persisted-folders-form-section>
|
|
|
|
<!-- #region DATA ACCESS POLICY -->
|
|
<div ng-if="ctrl.showDataAccessPolicySection()">
|
|
<data-access-policy-form-section
|
|
value="ctrl.formValues.DataAccessPolicy"
|
|
on-change="(ctrl.onDataAccessPolicyChange)"
|
|
is-edit="ctrl.state.isEdit"
|
|
persisted-folders-use-existing-volumes="ctrl.state.persistedFoldersUseExistingVolumes"
|
|
></data-access-policy-form-section>
|
|
</div>
|
|
<!-- #endregion -->
|
|
|
|
<resource-reservation-form-section
|
|
values="{memoryLimit: ctrl.formValues.MemoryLimit, cpuLimit: ctrl.formValues.CpuLimit}"
|
|
on-change="(ctrl.onChangeResourceReservation)"
|
|
namespace-has-quota="ctrl.state.resourcePoolHasQuota"
|
|
max-memory-limit="ctrl.state.sliders.memory.max"
|
|
max-cpu-limit="ctrl.state.sliders.cpu.max"
|
|
validation-data="{maxMemoryLimit: ctrl.state.sliders.memory.max, maxCpuLimit: ctrl.state.sliders.cpu.max}"
|
|
resource-quota-capacity-exceeded="ctrl.resourceQuotaCapacityExceeded()"
|
|
></resource-reservation-form-section>
|
|
|
|
<!-- deployment options -->
|
|
<app-deployment-type-form-section
|
|
value="ctrl.formValues.DeploymentType"
|
|
on-change="(ctrl.onChangeDeploymentType)"
|
|
support-global-deployment="ctrl.supportGlobalDeployment()"
|
|
radio-name="'deploymentType'"
|
|
></app-deployment-type-form-section>
|
|
|
|
<!-- replica count -->
|
|
<div ng-if="ctrl.formValues.DeploymentType === ctrl.ApplicationDeploymentTypes.REPLICATED">
|
|
<replication-form-section
|
|
values="{replicaCount: ctrl.formValues.ReplicaCount}"
|
|
on-change="(ctrl.onChangeReplicaCount)"
|
|
support-scalable-replica-deployment="ctrl.supportScalableReplicaDeployment()"
|
|
memory-limit="ctrl.formValues.MemoryLimit"
|
|
cpu-limit="ctrl.formValues.CpuLimit"
|
|
resource-reservations-overflow="ctrl.resourceReservationsOverflow()"
|
|
non-scalable-storage="ctrl.getNonScalableStorage()"
|
|
validation-data="{resourceReservationsOverflow: ctrl.resourceReservationsOverflow(), quotaExceeded: ctrl.state.storages.quotaExceeded, nonScalableStorage: ctrl.getNonScalableStorage(), supportScalableReplicaDeployment: ctrl.supportScalableReplicaDeployment()}"
|
|
></replication-form-section>
|
|
</div>
|
|
<!-- #endregion -->
|
|
|
|
<!-- #region AUTO SCALING -->
|
|
|
|
<div class="form-group !mb-0" ng-if="ctrl.formValues.DeploymentType !== ctrl.ApplicationDeploymentTypes.GLOBAL && !ctrl.state.useServerMetrics">
|
|
<div class="col-sm-12 small text-muted">
|
|
<p ng-if="!ctrl.isAdmin"> This feature is currently disabled and must be enabled by an administrator user. </p>
|
|
<p ng-if="ctrl.isAdmin">
|
|
Server metrics features must be enabled in the
|
|
<a ui-sref="kubernetes.cluster.setup" class="ctrl.isAdmin">environment configuration view</a>.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="col-sm-12">
|
|
<div class="col-sm-3 col-lg-2 pl-0 pt-0">
|
|
<label for="enable_auto_scaling" class="control-label text-left"> Enable auto scaling for this application </label>
|
|
</div>
|
|
<label class="switch ml-4 mt-1">
|
|
<input
|
|
type="checkbox"
|
|
class="form-control"
|
|
name="enable_auto_scaling"
|
|
ng-model="ctrl.formValues.AutoScaler.IsUsed"
|
|
data-cy="k8sAppCreate-autoScaleCheckbox"
|
|
ng-disabled="!(ctrl.formValues.DeploymentType !== ctrl.ApplicationDeploymentTypes.GLOBAL && ctrl.state.useServerMetrics)"
|
|
/>
|
|
<span class="slider round"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-inline" ng-if="ctrl.formValues.DeploymentType !== ctrl.ApplicationDeploymentTypes.GLOBAL && ctrl.formValues.AutoScaler.IsUsed">
|
|
<div class="row">
|
|
<div class="col-sm-4 pl-0">
|
|
<label class="control-label pb-2 text-left" for="auto_scaler_min">Minimum instances</label>
|
|
<div class="input-group input-group-sm" style="width: 100%">
|
|
<input
|
|
type="number"
|
|
class="form-control"
|
|
name="auto_scaler_min"
|
|
min="0"
|
|
ng-max="ctrl.formValues.AutoScaler.MaxReplicas"
|
|
ng-model="ctrl.formValues.AutoScaler.MinReplicas"
|
|
data-cy="k8sAppCreate-autoScaleMin"
|
|
required
|
|
/>
|
|
</div>
|
|
<span ng-show="kubernetesApplicationCreationForm['auto_scaler_min'].$invalid">
|
|
<div class="small text-warning" style="margin-top: 5px">
|
|
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_min'].$error">
|
|
<p ng-message="required" class="vertical-center"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Minimum instances is required. </p>
|
|
<p ng-message="min" class="vertical-center">
|
|
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Minimum instances must be greater than 0.
|
|
</p>
|
|
<p ng-message="max" class="vertical-center">
|
|
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Minimum instances must be smaller than maximum instances.
|
|
</p>
|
|
</ng-messages>
|
|
</div>
|
|
</span>
|
|
</div>
|
|
<div class="col-sm-4 pl-0">
|
|
<label class="control-label pb-2 text-left" for="auto_scaler_max">Maximum instances</label>
|
|
<div class="input-group input-group-sm" style="width: 100%">
|
|
<input
|
|
type="number"
|
|
class="form-control"
|
|
name="auto_scaler_max"
|
|
ng-min="ctrl.formValues.AutoScaler.MinReplicas"
|
|
ng-model="ctrl.formValues.AutoScaler.MaxReplicas"
|
|
/>
|
|
</div>
|
|
<span ng-show="kubernetesApplicationCreationForm['auto_scaler_max'].$invalid || ctrl.autoScalerOverflow()">
|
|
<div class="small text-warning" style="margin-top: 5px">
|
|
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_max'].$error">
|
|
<p ng-message="required" class="vertical-center"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Maximum instances is required. </p>
|
|
<p ng-message="min" class="vertical-center">
|
|
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Maximum instances must be greater than minimum instances.
|
|
</p>
|
|
</ng-messages>
|
|
</div>
|
|
</span>
|
|
</div>
|
|
<div class="col-sm-4 pl-0">
|
|
<label class="control-label pb-2 text-left" for="auto_scaler_cpu">
|
|
Target CPU usage (<b>%</b>)
|
|
<portainer-tooltip message="'The autoscaler will ensure enough instances are running to maintain an average CPU usage across all instances.'">
|
|
</portainer-tooltip>
|
|
</label>
|
|
<div class="input-group input-group-sm" style="width: 100%">
|
|
<input
|
|
type="number"
|
|
class="form-control"
|
|
name="auto_scaler_cpu"
|
|
ng-model="ctrl.formValues.AutoScaler.TargetCPUUtilization"
|
|
min="1"
|
|
max="100"
|
|
required
|
|
data-cy="k8sAppCreate-targetCPUInput"
|
|
/>
|
|
</div>
|
|
<span ng-show="kubernetesApplicationCreationForm['auto_scaler_cpu'].$invalid">
|
|
<div class="small text-warning" style="margin-top: 5px">
|
|
<ng-messages for="kubernetesApplicationCreationForm['auto_scaler_cpu'].$error">
|
|
<p ng-message="required" class="vertical-center"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Target CPU usage is required. </p>
|
|
<p ng-message="min" class="vertical-center">
|
|
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Target CPU usage must be greater than 0.
|
|
</p>
|
|
<p ng-message="max" class="vertical-center">
|
|
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Target CPU usage must be smaller than 100.
|
|
</p>
|
|
</ng-messages>
|
|
</div>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" ng-if="ctrl.autoScalerOverflow()" style="margin-bottom: 10px">
|
|
<div class="col-sm-12 small text-danger">
|
|
<pr-icon icon="'alert-circle'" mode="'danger'"></pr-icon>
|
|
This application would exceed available resources. Please review resource reservations or the maximum instance count of the auto-scaling policy.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- #endregion -->
|
|
|
|
<div class="mt-4 mb-2" ng-if="ctrl.formValues.DeploymentType === ctrl.ApplicationDeploymentTypes.REPLICATED">
|
|
<div class="col-sm-12 control-label !mb-2 !p-0 text-left"> Placement preferences and constraints </div>
|
|
|
|
<!-- #region PLACEMENTS -->
|
|
<div class="form-group">
|
|
<div class="col-sm-12 small text-muted vertical-center !mb-2" ng-if="ctrl.formValues.Placements.length > 0">
|
|
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
|
|
<div> Deploy this application on nodes that respect <b>ALL</b> of the following placement rules. Placement rules are based on node labels. </div>
|
|
</div>
|
|
|
|
<div class="col-sm-12 form-inline">
|
|
<div ng-repeat-start="placement in ctrl.formValues.Placements" class="!mb-2">
|
|
<div class="col-sm-5 input-group mr-2" ng-class="{ striked: placement.NeedsDeletion }">
|
|
<select
|
|
class="form-control !rounded"
|
|
ng-model="placement.Label"
|
|
ng-options="label as (label.Key | kubernetesNodeLabelHumanReadbleText) for label in ctrl.nodesLabels"
|
|
ng-change="ctrl.onChangePlacementLabel($index)"
|
|
ng-disabled="ctrl.isEditAndNotNewPlacement($index)"
|
|
data-cy="k8sAppCreate-placementLabel_{{ $index }}"
|
|
>
|
|
</select>
|
|
</div>
|
|
<div class="col-sm-5 input-group mr-2" ng-class="{ striked: placement.NeedsDeletion }">
|
|
<select
|
|
class="form-control !rounded"
|
|
ng-model="placement.Value"
|
|
ng-options="value for value in placement.Label.Values"
|
|
ng-disabled="ctrl.isEditAndNotNewPlacement($index)"
|
|
data-cy="k8sAppCreate-placementName_{{ $index }}"
|
|
>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-sm-1 input-group">
|
|
<button
|
|
ng-if="!placement.NeedsDeletion"
|
|
class="btn btn-md btn-dangerlight btn-only-icon !ml-0"
|
|
type="button"
|
|
ng-click="ctrl.removePlacement($index)"
|
|
data-cy="k8sAppCreate-deletePlacementButton"
|
|
>
|
|
<pr-icon icon="'trash-2'" size="'md'"></pr-icon>
|
|
</button>
|
|
<button
|
|
ng-if="placement.NeedsDeletion"
|
|
class="btn btn-sm btn-light btn-only-icon !ml-0"
|
|
type="button"
|
|
ng-click="ctrl.restorePlacement($index)"
|
|
data-cy="k8sAppCreate-restorePlacementButton"
|
|
>
|
|
<pr-icon icon="'rotate-cw'" size="'md'"></pr-icon>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div ng-repeat-end ng-show="ctrl.state.duplicates.placements.refs[$index] !== undefined">
|
|
<div class="col-sm-5 input-group">
|
|
<div class="small text-warning" ng-if="ctrl.state.duplicates.placements.refs[$index] !== undefined">
|
|
<p class="vertical-center" ng-if="ctrl.state.duplicates.placements.refs[$index] !== undefined">
|
|
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> This label is already defined.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-sm-12">
|
|
<span class="btn btn-primary btn-sm btn btn-sm btn-light mb-2 !ml-0 mt-2" ng-click="ctrl.addPlacement()">
|
|
<pr-icon icon="'plus'" size="'sm'"></pr-icon> Add rule
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div ng-if="ctrl.showPlacementPolicySection()">
|
|
<div class="form-group">
|
|
<div class="col-sm-12">
|
|
<label class="control-label text-left">Placement policy</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="col-sm-12 small text-muted"> Specify the policy associated to the placement rules. </div>
|
|
</div>
|
|
|
|
<box-selector
|
|
ng-if="ctrl.formValues.Placements.length"
|
|
options="ctrl.placementOptions"
|
|
slim="true"
|
|
value="ctrl.formValues.PlacementType"
|
|
on-change="(ctrl.onChangePlacementType)"
|
|
radio-name="'placementType'"
|
|
></box-selector>
|
|
</div>
|
|
</div>
|
|
<!-- #endregion -->
|
|
</div>
|
|
|
|
<!-- kubernetes services options -->
|
|
<div ng-if="ctrl.formValues.ResourcePool">
|
|
<kube-services-form
|
|
on-change="(ctrl.onServicesChange)"
|
|
values="ctrl.formValues.Services"
|
|
load-balancer-enabled="ctrl.publishViaLoadBalancerEnabled()"
|
|
app-name="ctrl.formValues.Name"
|
|
selector="ctrl.formValues.Selector"
|
|
validation-data="{nodePortServices: ctrl.state.nodePortServices, formServices: ctrl.formValues.Services, ingressPaths: ctrl.ingressPaths, originalIngressPaths: ctrl.originalIngressPaths}"
|
|
is-edit-mode="ctrl.state.isEdit"
|
|
namespace="ctrl.formValues.ResourcePool.Namespace.Name"
|
|
></kube-services-form>
|
|
</div>
|
|
<!-- kubernetes services options -->
|
|
<kubernetes-summary-view
|
|
ng-if="!(!kubernetesApplicationCreationForm.$valid || ctrl.isDeployUpdateButtonDisabled() || !ctrl.state.pullImageValidity)"
|
|
form-values="ctrl.formValues"
|
|
old-form-values="ctrl.savedFormValues"
|
|
></kubernetes-summary-view>
|
|
</div>
|
|
<!-- #region ACTIONS -->
|
|
<div class="col-sm-12 form-section-title !mt-6" ng-if="ctrl.state.appType !== ctrl.KubernetesDeploymentTypes.GIT" ng-hide="ctrl.stack.IsComposeFormat"> Actions </div>
|
|
<div class="form-group" ng-hide="ctrl.stack.IsComposeFormat">
|
|
<div class="col-sm-12">
|
|
<button
|
|
ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"
|
|
type="button"
|
|
class="btn btn-primary btn-sm !ml-0"
|
|
ng-disabled="!kubernetesApplicationCreationForm.$valid || ctrl.isDeployUpdateButtonDisabled() || !ctrl.imageValidityIsValid() || ctrl.hasPortErrors() || !ctrl.formValues.ResourcePool"
|
|
ng-click="ctrl.deployApplication()"
|
|
button-spinner="ctrl.state.actionInProgress"
|
|
data-cy="k8sAppCreate-deployButton"
|
|
>
|
|
<span ng-show="!ctrl.state.isEdit && !ctrl.state.actionInProgress">Deploy application</span>
|
|
<span ng-show="!ctrl.state.isEdit && ctrl.state.actionInProgress">Deployment in progress...</span>
|
|
<span ng-show="ctrl.state.isEdit && !ctrl.state.actionInProgress">Update application</span>
|
|
<span ng-show="ctrl.state.isEdit && ctrl.state.actionInProgress">Update in progress...</span>
|
|
</button>
|
|
<button
|
|
ng-if="ctrl.state.isEdit && !ctrl.state.actionInProgress && ctrl.state.appType === ctrl.KubernetesDeploymentTypes.APPLICATION_FORM"
|
|
type="button"
|
|
class="btn btn-sm btn-default"
|
|
ui-sref="kubernetes.applications.application({ name: ctrl.application.Name, namespace: ctrl.application.ResourcePool })"
|
|
data-cy="k8sAppCreate-appCancelButton"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<!-- #Web editor buttons -->
|
|
<button
|
|
class="btn btn-sm btn-primary"
|
|
ng-click="ctrl.updateApplicationViaWebEditor()"
|
|
ng-if="ctrl.state.appType === ctrl.KubernetesDeploymentTypes.CONTENT || ctrl.state.updateWebEditorInProgress"
|
|
ng-disabled="!kubernetesApplicationCreationForm.$valid || !ctrl.state.isEditorDirty || ctrl.state.updateWebEditorInProgress"
|
|
style="margin-top: 7px; margin-left: 0"
|
|
button-spinner="ctrl.state.updateWebEditorInProgress"
|
|
>
|
|
<span ng-show="!ctrl.state.updateWebEditorInProgress">Update the application</span>
|
|
<span ng-show="ctrl.state.updateWebEditorInProgress">Update in progress...</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</rd-widget-body>
|
|
</rd-widget>
|
|
</div>
|
|
</div>
|
|
</div>
|