feat(configurations): portainer k8s configurations lingo update for explicitness EE-1626 (#5722)

* kubernetes sidebar configuration lingo updated

* configurations list view updated

* updated configurations list add config button

* - updated create and update configuration buttons to display type of configuration being created/updated
- configuration filter displays explicit configuration type

* updated create configuration sub-title

* add configmap wording update

* portainer service lingo updated in k8s app creation and update forms

* publishing mode text updates

* KubernetesApplicationPublishingTypes updated INTERNAL and CLUSTER to CLUSTER_IP and NODE_PORT respectively

* application ports datatable updated

* updated service and ingress lingo on application view page

* reduced spacing to fit in ConfigMaps & Secrets in sidenav for different screen res
pull/5790/head
zees-dev 2021-09-29 13:58:04 +13:00 committed by GitHub
parent 01529203f1
commit e3b6e4a1d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 546 additions and 519 deletions

View File

@ -287,7 +287,7 @@ json-tree .branch-preview {
margin-top: 15px; margin-top: 15px;
} }
.summary { .bold {
color: var(--text-summary-color); color: var(--text-summary-color);
font-weight: 700; font-weight: 700;
} }

View File

@ -125,7 +125,7 @@
<td> <td>
<!-- LB --> <!-- LB -->
<span ng-if="item.ServiceType === $ctrl.KubernetesServiceTypes.LOAD_BALANCER"> <span ng-if="item.ServiceType === $ctrl.KubernetesServiceTypes.LOAD_BALANCER">
<span> <i class="fa fa-project-diagram" aria-hidden="true" style="margin-right: 2px;"></i> Load balancer </span> <span> <i class="fa fa-project-diagram" aria-hidden="true" style="margin-right: 2px;"></i> LoadBalancer </span>
<span class="text-muted small" style="margin-left: 5px;"> <span class="text-muted small" style="margin-left: 5px;">
<span ng-if="item.LoadBalancerIPAddress">{{ item.LoadBalancerIPAddress }}</span> <span ng-if="item.LoadBalancerIPAddress">{{ item.LoadBalancerIPAddress }}</span>
<span ng-if="!item.LoadBalancerIPAddress">pending</span> <span ng-if="!item.LoadBalancerIPAddress">pending</span>
@ -133,10 +133,10 @@
</span> </span>
<!-- Internal --> <!-- Internal -->
<span ng-if="item.ServiceType === $ctrl.KubernetesServiceTypes.CLUSTER_IP"> <span ng-if="item.ServiceType === $ctrl.KubernetesServiceTypes.CLUSTER_IP">
<i class="fa fa-list-alt" aria-hidden="true" style="margin-right: 2px;"></i> Internal <i class="fa fa-list-alt" aria-hidden="true" style="margin-right: 2px;"></i> ClusterIP
</span> </span>
<!-- Cluster --> <!-- Cluster -->
<span ng-if="item.ServiceType === $ctrl.KubernetesServiceTypes.NODE_PORT"> <i class="fa fa-list" aria-hidden="true" style="margin-right: 2px;"></i> Cluster </span> <span ng-if="item.ServiceType === $ctrl.KubernetesServiceTypes.NODE_PORT"> <i class="fa fa-list" aria-hidden="true" style="margin-right: 2px;"></i> NodePort </span>
</td> </td>
<!-- Exposed port --> <!-- Exposed port -->
<td> <td>

View File

@ -67,7 +67,7 @@
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove <i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button> </button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="kubernetes.configurations.new" data-cy="k8sConfig-addConfigWithFormButton"> <button type="button" class="btn btn-sm btn-primary" ui-sref="kubernetes.configurations.new" data-cy="k8sConfig-addConfigWithFormButton">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add configuration with form <i class="fa fa-plus space-right" aria-hidden="true"></i>Add with form
</button> </button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="kubernetes.deploy" data-cy="k8sConfig-deployFromManifestButton"> <button type="button" class="btn btn-sm btn-primary" ui-sref="kubernetes.deploy" data-cy="k8sConfig-deployFromManifestButton">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Create from manifest <i class="fa fa-plus space-right" aria-hidden="true"></i>Create from manifest

View File

@ -49,7 +49,7 @@
class-name="sidebar-list" class-name="sidebar-list"
data-cy="k8sSidebar-configurations" data-cy="k8sSidebar-configurations"
> >
Configurations ConfigMaps & Secrets
</sidebar-menu-item> </sidebar-menu-item>
<sidebar-menu-item path="kubernetes.volumes" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-database fa-fw" class-name="sidebar-list" data-cy="k8sSidebar-volumes"> <sidebar-menu-item path="kubernetes.volumes" path-params="{ endpointId: $ctrl.endpointId }" icon-class="fa-database fa-fw" class-name="sidebar-list" data-cy="k8sSidebar-volumes">

View File

@ -299,11 +299,11 @@ class KubernetesApplicationConverter {
if (app.ServiceType === KubernetesServiceTypes.LOAD_BALANCER) { if (app.ServiceType === KubernetesServiceTypes.LOAD_BALANCER) {
res.PublishingType = KubernetesApplicationPublishingTypes.LOAD_BALANCER; res.PublishingType = KubernetesApplicationPublishingTypes.LOAD_BALANCER;
} else if (app.ServiceType === KubernetesServiceTypes.NODE_PORT) { } else if (app.ServiceType === KubernetesServiceTypes.NODE_PORT) {
res.PublishingType = KubernetesApplicationPublishingTypes.CLUSTER; res.PublishingType = KubernetesApplicationPublishingTypes.NODE_PORT;
} else if (app.ServiceType === KubernetesServiceTypes.CLUSTER_IP && isIngress) { } else if (app.ServiceType === KubernetesServiceTypes.CLUSTER_IP && isIngress) {
res.PublishingType = KubernetesApplicationPublishingTypes.INGRESS; res.PublishingType = KubernetesApplicationPublishingTypes.INGRESS;
} else { } else {
res.PublishingType = KubernetesApplicationPublishingTypes.INTERNAL; res.PublishingType = KubernetesApplicationPublishingTypes.CLUSTER_IP;
} }
if (app.Pods && app.Pods.length) { if (app.Pods && app.Pods.length) {

View File

@ -42,7 +42,7 @@ class KubernetesServiceConverter {
res.StackName = formValues.StackName ? formValues.StackName : formValues.Name; res.StackName = formValues.StackName ? formValues.StackName : formValues.Name;
res.ApplicationOwner = formValues.ApplicationOwner; res.ApplicationOwner = formValues.ApplicationOwner;
res.ApplicationName = formValues.Name; res.ApplicationName = formValues.Name;
if (formValues.PublishingType === KubernetesApplicationPublishingTypes.CLUSTER) { if (formValues.PublishingType === KubernetesApplicationPublishingTypes.NODE_PORT) {
res.Type = KubernetesServiceTypes.NODE_PORT; res.Type = KubernetesServiceTypes.NODE_PORT;
} else if (formValues.PublishingType === KubernetesApplicationPublishingTypes.LOAD_BALANCER) { } else if (formValues.PublishingType === KubernetesApplicationPublishingTypes.LOAD_BALANCER) {
res.Type = KubernetesServiceTypes.LOAD_BALANCER; res.Type = KubernetesServiceTypes.LOAD_BALANCER;

View File

@ -1,6 +1,5 @@
import _ from 'lodash-es'; import _ from 'lodash-es';
import { KubernetesApplicationDataAccessPolicies } from 'Kubernetes/models/application/models'; import { KubernetesApplicationDataAccessPolicies } from 'Kubernetes/models/application/models';
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
import { KubernetesApplicationTypes, KubernetesApplicationTypeStrings } from 'Kubernetes/models/application/models'; import { KubernetesApplicationTypes, KubernetesApplicationTypeStrings } from 'Kubernetes/models/application/models';
import { KubernetesPodNodeAffinityNodeSelectorRequirementOperators } from 'Kubernetes/pod/models'; import { KubernetesPodNodeAffinityNodeSelectorRequirementOperators } from 'Kubernetes/pod/models';
@ -26,24 +25,11 @@ angular
var status = _.toLower(text); var status = _.toLower(text);
switch (status) { switch (status) {
case 'loadbalancer': case 'loadbalancer':
return 'Load balancer'; return 'LoadBalancer';
case 'clusterip': case 'clusterip':
return 'Internal'; return 'ClusterIP';
case 'nodeport': case 'nodeport':
return 'Cluster'; return 'NodePort';
}
};
})
.filter('kubernetesApplicationPortsTableHeaderText', function () {
'use strict';
return function (serviceType) {
switch (serviceType) {
case KubernetesServiceTypes.LOAD_BALANCER:
return 'Load balancer';
case KubernetesServiceTypes.CLUSTER_IP:
return 'Application';
case KubernetesServiceTypes.NODE_PORT:
return 'Cluster node';
} }
}; };
}) })

View File

@ -5,9 +5,9 @@ angular.module('portainer.kubernetes').filter('kubernetesConfigurationTypeText',
return function (type) { return function (type) {
switch (type) { switch (type) {
case KubernetesConfigurationTypes.SECRET: case KubernetesConfigurationTypes.SECRET:
return 'Sensitive'; return 'Secret';
case KubernetesConfigurationTypes.CONFIGMAP: case KubernetesConfigurationTypes.CONFIGMAP:
return 'Non-sensitive'; return 'ConfigMap';
} }
}; };
}); });

View File

@ -22,7 +22,7 @@ export function KubernetesApplicationFormValues() {
this.DataAccessPolicy = KubernetesApplicationDataAccessPolicies.SHARED; this.DataAccessPolicy = KubernetesApplicationDataAccessPolicies.SHARED;
this.PersistedFolders = []; // KubernetesApplicationPersistedFolderFormValue lis; this.PersistedFolders = []; // KubernetesApplicationPersistedFolderFormValue lis;
this.Configurations = []; // KubernetesApplicationConfigurationFormValue lis; this.Configurations = []; // KubernetesApplicationConfigurationFormValue lis;
this.PublishingType = KubernetesApplicationPublishingTypes.INTERNAL; this.PublishingType = KubernetesApplicationPublishingTypes.CLUSTER_IP;
this.PublishedPorts = []; // KubernetesApplicationPublishedPortFormValue lis; this.PublishedPorts = []; // KubernetesApplicationPublishedPortFormValue lis;
this.PlacementType = KubernetesApplicationPlacementTypes.PREFERRED; this.PlacementType = KubernetesApplicationPlacementTypes.PREFERRED;
this.Placements = []; // KubernetesApplicationPlacementFormValue lis; this.Placements = []; // KubernetesApplicationPlacementFormValue lis;

View File

@ -25,8 +25,8 @@ export const KubernetesApplicationTypeStrings = Object.freeze({
}); });
export const KubernetesApplicationPublishingTypes = Object.freeze({ export const KubernetesApplicationPublishingTypes = Object.freeze({
INTERNAL: 1, CLUSTER_IP: 1,
CLUSTER: 2, NODE_PORT: 2,
LOAD_BALANCER: 3, LOAD_BALANCER: 3,
INGRESS: 4, INGRESS: 4,
}); });

View File

@ -1239,498 +1239,515 @@
</div> </div>
<!-- #region PUBLISHING OPTIONS --> <!-- #region PUBLISHING OPTIONS -->
<div class="form-group"> <div class="form-group">
<div class="col-sm-12 small text-muted"> <div class="col-sm-12">
Select how you want to publish your application. <label for="enable_port_publishing" class="control-label text-left">
Enable publishing for this application
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" class="form-control" name="enable_port_publishing" ng-model="ctrl.formValues.IsPublishingService" />
<i></i>
</label>
</div> </div>
</div> </div>
<!-- publishing options --> <span ng-if="ctrl.formValues.IsPublishingService">
<div class="form-group" style="margin-bottom: 0;"> <div class="form-group">
<div class="boxselector_wrapper"> <div class="col-sm-12 small text-muted">
<div ng-style="{ color: ctrl.isPublishingTypeEditDisabled() ? '#767676' : '' }"> Select how you want to publish your application.
<input
type="radio"
id="publishing_internal"
ng-value="ctrl.ApplicationPublishingTypes.INTERNAL"
ng-model="ctrl.formValues.PublishingType"
ng-change="ctrl.onChangePublishedPorts()"
ng-disabled="ctrl.isPublishingTypeEditDisabled()"
data-cy="k8sAppCreate-internalPublishButton"
/>
<label
for="publishing_internal"
ng-if="
!ctrl.isPublishingTypeEditDisabled() ||
(ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INTERNAL)
"
>
<div class="boxselector_header">
<i class="fa fa-list-alt" aria-hidden="true" style="margin-right: 2px;"></i>
Internal
</div>
<p>Internal communications inside the cluster only</p>
</label>
<label
for="publishing_internal"
ng-if="ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType !== ctrl.ApplicationPublishingTypes.INTERNAL"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
uib-tooltip="Changing the publishing mode is not allowed until you delete all previously existing ports"
style="cursor: pointer; border-color: #767676;"
>
<div class="boxselector_header">
<i class="fa fa-list-alt" aria-hidden="true" style="margin-right: 2px;"></i>
Internal
</div>
<p>Internal communications inside the cluster only</p>
</label>
</div>
<div ng-style="{ color: ctrl.isPublishingTypeEditDisabled() ? '#767676' : '' }">
<input
type="radio"
id="publishing_cluster"
ng-value="ctrl.ApplicationPublishingTypes.CLUSTER"
ng-model="ctrl.formValues.PublishingType"
ng-change="ctrl.onChangePublishedPorts()"
ng-disabled="ctrl.isPublishingTypeEditDisabled()"
data-cy="k8sAppCreate-clusterPublishButton"
/>
<label
for="publishing_cluster"
ng-if="
!ctrl.isPublishingTypeEditDisabled() ||
(ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.CLUSTER)
"
>
<div class="boxselector_header">
<i class="fa fa-list" aria-hidden="true" style="margin-right: 2px;"></i>
Cluster
</div>
<p>Publish this application via a port on all nodes of the cluster</p>
</label>
<label
for="publishing_cluster"
ng-if="ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType !== ctrl.ApplicationPublishingTypes.CLUSTER"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
uib-tooltip="Changing the publishing mode is not allowed until you delete all previously existing ports"
style="cursor: pointer; border-color: #767676;"
>
<div class="boxselector_header">
<i class="fa fa-list" aria-hidden="true" style="margin-right: 2px;"></i>
Cluster
</div>
<p>Publish this application via a port on all nodes of the cluster</p>
</label>
</div>
<div ng-if="ctrl.publishViaIngressEnabled()" ng-style="{ color: ctrl.isPublishingTypeEditDisabled() ? '#767676' : '' }">
<input
type="radio"
id="publishing_ingress"
ng-value="ctrl.ApplicationPublishingTypes.INGRESS"
ng-model="ctrl.formValues.PublishingType"
ng-change="ctrl.onChangePublishedPorts()"
ng-disabled="ctrl.isPublishingTypeEditDisabled()"
data-cy="k8sAppCreate-ingressPublishButton"
/>
<label
for="publishing_ingress"
ng-if="
!ctrl.isPublishingTypeEditDisabled() ||
(ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS)
"
>
<div class="boxselector_header">
<i class="fa fa-route" aria-hidden="true" style="margin-right: 2px;"></i>
Ingress
</div>
<p>Publish this application via a HTTP route</p>
</label>
<label
for="publishing_ingress"
ng-if="ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType !== ctrl.ApplicationPublishingTypes.INGRESS"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
uib-tooltip="Changing the publishing mode is not allowed until you delete all previously existing ports"
style="cursor: pointer; border-color: #767676;"
>
<div class="boxselector_header">
<i class="fa fa-route" aria-hidden="true" style="margin-right: 2px;"></i>
Ingress
</div>
<p>Publish this application via a HTTP route</p>
</label>
</div>
<div ng-if="ctrl.publishViaLoadBalancerEnabled()" ng-style="{ color: ctrl.isPublishingTypeEditDisabled() ? '#767676' : '' }">
<input
type="radio"
id="publishing_loadbalancer"
ng-value="ctrl.ApplicationPublishingTypes.LOAD_BALANCER"
ng-model="ctrl.formValues.PublishingType"
ng-change="ctrl.onChangePublishedPorts()"
ng-disabled="ctrl.isPublishingTypeEditDisabled()"
data-cy="k8sAppCreate-lbPublichButton"
/>
<label
for="publishing_loadbalancer"
ng-if="
!ctrl.isPublishingTypeEditDisabled() ||
(ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.LOAD_BALANCER)
"
>
<div class="boxselector_header">
<i class="fa fa-project-diagram" aria-hidden="true" style="margin-right: 2px;"></i>
Load balancer
</div>
<p>Publish this application via a load balancer</p>
</label>
<label
for="publishing_loadbalancer"
ng-if="ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType !== ctrl.ApplicationPublishingTypes.LOAD_BALANCER"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
uib-tooltip="Changing the publishing mode is not allowed until you delete all previously existing ports"
style="cursor: pointer; border-color: #767676;"
>
<div class="boxselector_header">
<i class="fa fa-project-diagram" aria-hidden="true" style="margin-right: 2px;"></i>
Load balancer
</div>
<p>Publish this application via a load balancer</p>
</label>
</div> </div>
</div> </div>
</div>
<!-- #endregion -->
<!-- #region PUBLISHED PORTS --> <!-- publishing options -->
<div class="form-group"> <div class="form-group" style="margin-bottom: 0;">
<div class="col-sm-12" style="margin-top: 5px;"> <div class="boxselector_wrapper">
<label class="control-label text-left">Published ports</label> <div ng-style="{ color: ctrl.isPublishingTypeEditDisabled() ? '#767676' : '' }">
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addPublishedPort()" data-cy="k8sAppCreate-addNewPortButton">
<i class="fa fa-plus-circle" aria-hidden="true"></i> publish a new port
</span>
</div>
<div
class="col-sm-12 small text-muted"
style="margin-top: 15px;"
ng-if="ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.CLUSTER && ctrl.formValues.PublishedPorts.length > 0"
>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
When publishing a port in cluster mode, the node port is optional. If left empty Kubernetes will use a random port number. If you wish to specify a port, use a
port number inside the default range <code>30000-32767</code>.
</div>
<div ng-if="ctrl.isNotInternalAndHasNoPublishedPorts()" class="col-sm-12 small text-warning" style="margin-top: 12px;">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> At least one published port must be defined.
</div>
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
<!-- #region INPUTS -->
<div
ng-repeat-start="publishedPort in ctrl.formValues.PublishedPorts"
style="margin-top: 2px;"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
tooltip-enable="ctrl.disableLoadBalancerEdit()"
uib-tooltip="Edition is not allowed while the Load Balancer is in 'Pending' state"
>
<div
class="input-group input-group-sm"
ng-class="{
striked: publishedPort.NeedsDeletion,
'col-sm-2': ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS,
'col-sm-3': ctrl.formValues.PublishingType !== ctrl.ApplicationPublishingTypes.INGRESS
}"
>
<span class="input-group-addon">container port</span>
<input <input
type="number" type="radio"
class="form-control" id="publishing_internal"
name="container_port_{{ $index }}" ng-value="ctrl.ApplicationPublishingTypes.CLUSTER_IP"
ng-model="publishedPort.ContainerPort" ng-model="ctrl.formValues.PublishingType"
placeholder="80"
ng-min="1"
ng-max="65535"
ng-required="!publishedPort.NeedsDeletion"
ng-change="ctrl.onChangePortMappingContainerPort()"
ng-disabled="ctrl.disableLoadBalancerEdit() || ctrl.isEditAndNotNewPublishedPort($index)"
data-cy="k8sAppCreate-containerPort_{{ $index }}"
/>
</div>
<div
class="col-sm-3 input-group input-group-sm"
ng-class="{ striked: publishedPort.NeedsDeletion }"
ng-if="
(publishedPort.IsNew && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.CLUSTER) ||
(!publishedPort.IsNew && ctrl.savedFormValues.PublishingType === ctrl.ApplicationPublishingTypes.CLUSTER)
"
>
<span class="input-group-addon">node port</span>
<input
name="published_node_port_{{ $index }}"
type="number"
class="form-control"
ng-model="publishedPort.NodePort"
placeholder="30080"
ng-min="30000"
ng-max="32767"
ng-change="ctrl.onChangePortMappingNodePort()"
ng-disabled="ctrl.disableLoadBalancerEdit() || ctrl.isEditAndNotNewPublishedPort($index)"
data-cy="k8sAppCreate-nodePort_{{ $index }}"
/>
</div>
<div
class="col-sm-3 input-group input-group-sm"
ng-class="{ striked: publishedPort.NeedsDeletion }"
ng-if="
(publishedPort.IsNew && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.LOAD_BALANCER) ||
(!publishedPort.IsNew && ctrl.savedFormValues.PublishingType === ctrl.ApplicationPublishingTypes.LOAD_BALANCER)
"
>
<span class="input-group-addon">load balancer port</span>
<input
type="number"
class="form-control"
name="load_balancer_port_{{ $index }}"
ng-model="publishedPort.LoadBalancerPort"
placeholder="80"
value="8080"
ng-min="1"
ng-max="65535"
ng-required="!publishedPort.NeedsDeletion"
ng-change="ctrl.onChangePortMappingLoadBalancer()"
ng-disabled="ctrl.disableLoadBalancerEdit() || ctrl.isEditAndNotNewPublishedPort($index)"
data-cy="k8sAppCreate-lbPortInput_{{ $index }}"
/>
</div>
<div
class="col-sm-2 input-group input-group-sm"
ng-class="{ striked: publishedPort.NeedsDeletion }"
ng-if="
(publishedPort.IsNew && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS) ||
(!publishedPort.IsNew && ctrl.savedFormValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS)
"
>
<span class="input-group-addon">ingress</span>
<select
class="form-control"
name="ingress_class_{{ $index }}"
ng-model="publishedPort.IngressName"
ng-options="ingress.Name as ingress.Name for ingress in ctrl.ingresses"
ng-required="!publishedPort.NeedsDeletion"
ng-change="ctrl.onChangePortMappingIngress($index)"
ng-disabled="ctrl.disableLoadBalancerEdit() || ctrl.isEditAndNotNewPublishedPort($index)"
data-cy="k8sAppCreate-ingressSelect_{{ $index }}"
>
<option selected disabled hidden value="">Select an ingress</option>
</select>
</div>
<div
class="col-sm-2 input-group input-group-sm"
ng-class="{ striked: publishedPort.NeedsDeletion }"
ng-if="
(publishedPort.IsNew && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS) ||
(!publishedPort.IsNew && ctrl.savedFormValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS)
"
>
<span class="input-group-addon">hostname</span>
<select
class="form-control"
name="ingress_hostname_{{ $index }}"
ng-model="publishedPort.IngressHost"
ng-options="host as (host | kubernetesApplicationIngressEmptyHostname) for host in publishedPort.IngressHosts"
ng-change="ctrl.onChangePublishedPorts()" ng-change="ctrl.onChangePublishedPorts()"
ng-disabled="ctrl.disableLoadBalancerEdit() || ctrl.isEditAndNotNewPublishedPort($index)" ng-disabled="ctrl.isPublishingTypeEditDisabled()"
data-cy="k8sAppCreate-hostnameSelect_{{ $index }}" data-cy="k8sAppCreate-internalPublishButton"
>
<option selected disabled hidden value="">Select a hostname</option>
</select>
</div>
<div
class="col-sm-2 input-group input-group-sm"
ng-class="{ striked: publishedPort.NeedsDeletion }"
ng-if="
(publishedPort.IsNew && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS) ||
(!publishedPort.IsNew && ctrl.savedFormValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS)
"
>
<span class="input-group-addon">route</span>
<input
class="form-control"
name="ingress_route_{{ $index }}"
ng-model="publishedPort.IngressRoute"
placeholder="route"
ng-required="!publishedPort.NeedsDeletion"
ng-change="ctrl.onChangePortMappingIngressRoute()"
ng-pattern="/^(\/?[a-zA-Z0-9]+([a-zA-Z0-9-/_]*[a-zA-Z0-9])?|[a-zA-Z0-9]+)|(\/){1}$/"
ng-disabled="ctrl.disableLoadBalancerEdit() || ctrl.isEditAndNotNewPublishedPort($index)"
data-cy="k8sAppCreate-ingressRoute_{{ $index }}"
/> />
<label
for="publishing_internal"
ng-if="
!ctrl.isPublishingTypeEditDisabled() ||
(ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.CLUSTER_IP)
"
>
<div class="boxselector_header">
<i class="fa fa-list-alt" aria-hidden="true" style="margin-right: 2px;"></i>
ClusterIP
</div>
<p>Internal communications inside the cluster only</p>
</label>
<label
for="publishing_internal"
ng-if="ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType !== ctrl.ApplicationPublishingTypes.CLUSTER_IP"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
uib-tooltip="Changing the publishing mode is not allowed until you delete all previously existing ports"
style="cursor: pointer; border-color: #767676;"
>
<div class="boxselector_header">
<i class="fa fa-list-alt" aria-hidden="true" style="margin-right: 2px;"></i>
ClusterIP
</div>
<p>Internal communications inside the cluster only</p>
</label>
</div> </div>
<div class="input-group col-sm-2 input-group-sm"> <div ng-style="{ color: ctrl.isPublishingTypeEditDisabled() ? '#767676' : '' }">
<div class="btn-group btn-group-sm" ng-class="{ striked: publishedPort.NeedsDeletion }"> <input
<label type="radio"
class="btn btn-primary" id="publishing_cluster"
ng-model="publishedPort.Protocol" ng-value="ctrl.ApplicationPublishingTypes.NODE_PORT"
uib-btn-radio="'TCP'" ng-model="ctrl.formValues.PublishingType"
ng-change="ctrl.onChangePortProtocol($index)" ng-change="ctrl.onChangePublishedPorts()"
ng-disabled="ctrl.isProtocolOptionDisabled($index, 'TCP')" ng-disabled="ctrl.isPublishingTypeEditDisabled()"
data-cy="k8sAppCreate-TCPButton_{{ $index }}" data-cy="k8sAppCreate-clusterPublishButton"
>TCP</label />
> <label
<label for="publishing_cluster"
class="btn btn-primary" ng-if="
ng-model="publishedPort.Protocol" !ctrl.isPublishingTypeEditDisabled() ||
uib-btn-radio="'UDP'" (ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.NODE_PORT)
ng-change="ctrl.onChangePortProtocol($index)" "
ng-disabled="ctrl.isProtocolOptionDisabled($index, 'UDP')"
data-cy="k8sAppCreate-UDPButton_{{ $index }}"
>UDP</label
>
</div>
<button
ng-if="!ctrl.disableLoadBalancerEdit() && !publishedPort.NeedsDeletion"
class="btn btn-sm btn-danger"
type="button"
ng-click="ctrl.removePublishedPort($index)"
data-cy="k8sAppCreate-rmPortButton_{{ $index }}"
> >
<i class="fa fa-trash-alt" aria-hidden="true"></i> <div class="boxselector_header">
</button> <i class="fa fa-list" aria-hidden="true" style="margin-right: 2px;"></i>
<button NodePort
ng-if="publishedPort.NeedsDeletion && ctrl.formValues.PublishingType === ctrl.savedFormValues.PublishingType" </div>
class="btn btn-sm btn-primary" <p>Publish this application via a port on all nodes of the cluster</p>
type="button" </label>
ng-click="ctrl.restorePublishedPort($index)" <label
data-cy="k8sAppCreate-restorePortButton_{{ $index }}" for="publishing_cluster"
ng-if="ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType !== ctrl.ApplicationPublishingTypes.NODE_PORT"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
uib-tooltip="Changing the publishing mode is not allowed until you delete all previously existing ports"
style="cursor: pointer; border-color: #767676;"
> >
<i class="fa fa-trash-restore" aria-hidden="true"></i> <div class="boxselector_header">
</button> <i class="fa fa-list" aria-hidden="true" style="margin-right: 2px;"></i>
NodePort
</div>
<p>Publish this application via a port on all nodes of the cluster</p>
</label>
</div>
<div ng-if="ctrl.publishViaIngressEnabled()" ng-style="{ color: ctrl.isPublishingTypeEditDisabled() ? '#767676' : '' }">
<input
type="radio"
id="publishing_ingress"
ng-value="ctrl.ApplicationPublishingTypes.INGRESS"
ng-model="ctrl.formValues.PublishingType"
ng-change="ctrl.onChangePublishedPorts()"
ng-disabled="ctrl.isPublishingTypeEditDisabled()"
data-cy="k8sAppCreate-ingressPublishButton"
/>
<label
for="publishing_ingress"
ng-if="
!ctrl.isPublishingTypeEditDisabled() ||
(ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS)
"
>
<div class="boxselector_header">
<i class="fa fa-route" aria-hidden="true" style="margin-right: 2px;"></i>
Ingress
</div>
<p>Publish this application via a HTTP route</p>
</label>
<label
for="publishing_ingress"
ng-if="ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType !== ctrl.ApplicationPublishingTypes.INGRESS"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
uib-tooltip="Changing the publishing mode is not allowed until you delete all previously existing ports"
style="cursor: pointer; border-color: #767676;"
>
<div class="boxselector_header">
<i class="fa fa-route" aria-hidden="true" style="margin-right: 2px;"></i>
Ingress
</div>
<p>Publish this application via a HTTP route</p>
</label>
</div>
<div ng-if="ctrl.publishViaLoadBalancerEnabled()" ng-style="{ color: ctrl.isPublishingTypeEditDisabled() ? '#767676' : '' }">
<input
type="radio"
id="publishing_loadbalancer"
ng-value="ctrl.ApplicationPublishingTypes.LOAD_BALANCER"
ng-model="ctrl.formValues.PublishingType"
ng-change="ctrl.onChangePublishedPorts()"
ng-disabled="ctrl.isPublishingTypeEditDisabled()"
data-cy="k8sAppCreate-lbPublichButton"
/>
<label
for="publishing_loadbalancer"
ng-if="
!ctrl.isPublishingTypeEditDisabled() ||
(ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.LOAD_BALANCER)
"
>
<div class="boxselector_header">
<i class="fa fa-project-diagram" aria-hidden="true" style="margin-right: 2px;"></i>
Load balancer
</div>
<p>Publish this application via a load balancer</p>
</label>
<label
for="publishing_loadbalancer"
ng-if="ctrl.isPublishingTypeEditDisabled() && ctrl.formValues.PublishingType !== ctrl.ApplicationPublishingTypes.LOAD_BALANCER"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
uib-tooltip="Changing the publishing mode is not allowed until you delete all previously existing ports"
style="cursor: pointer; border-color: #767676;"
>
<div class="boxselector_header">
<i class="fa fa-project-diagram" aria-hidden="true" style="margin-right: 2px;"></i>
Load balancer
</div>
<p>Publish this application via a load balancer</p>
</label>
</div> </div>
</div> </div>
<!-- #endregion --> </div>
<!-- #endregion -->
<!-- #region PUBLISHED PORTS -->
<div class="form-group">
<div class="col-sm-12" style="margin-top: 5px;">
<label class="control-label text-left">Published ports</label>
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addPublishedPort()" data-cy="k8sAppCreate-addNewPortButton">
<i class="fa fa-plus-circle" aria-hidden="true"></i> publish a new port
</span>
</div>
<!-- #region VALIDATION -->
<div <div
ng-repeat-end class="col-sm-12 small text-muted"
ng-show=" style="margin-top: 15px;"
kubernetesApplicationCreationForm['container_port_' + $index].$invalid || ng-if="ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.NODE_PORT && ctrl.formValues.PublishedPorts.length > 0"
kubernetesApplicationCreationForm['published_node_port_' + $index].$invalid ||
kubernetesApplicationCreationForm['load_balancer_port_' + $index].$invalid ||
kubernetesApplicationCreationForm['ingress_class_' + $index].$invalid ||
kubernetesApplicationCreationForm['ingress_route_' + $index].$invalid ||
ctrl.state.duplicates.publishedPorts.containerPorts.refs[$index] !== undefined ||
(ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.CLUSTER && ctrl.state.duplicates.publishedPorts.nodePorts.refs[$index] !== undefined) ||
(ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS &&
ctrl.state.duplicates.publishedPorts.ingressRoutes.refs[$index] !== undefined) ||
(ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.LOAD_BALANCER &&
ctrl.state.duplicates.publishedPorts.loadBalancerPorts.refs[$index] !== undefined)
"
> >
<div class="col-sm-3 input-group input-group-sm"> <i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
<div When publishing a port in cluster mode, the node port is optional. If left empty Kubernetes will use a random port number. If you wish to specify a port, use
class="small text-warning" a port number inside the default range <code>30000-32767</code>.
style="margin-top: 5px;" </div>
ng-if=" <div ng-if="ctrl.hasNoPublishedPorts()" class="col-sm-12 small text-warning" style="margin-top: 12px;">
kubernetesApplicationCreationForm['container_port_' + $index].$invalid || ctrl.state.duplicates.publishedPorts.containerPorts.refs[$index] !== undefined <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> At least one published port must be defined.
" </div>
>
<div ng-messages="kubernetesApplicationCreationForm['container_port_'+$index].$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Container port number is required.</p>
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Container port number must be inside the range 1-65535.</p>
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Container port number must be inside the range 1-65535.</p>
</div>
<p ng-if="ctrl.state.duplicates.publishedPorts.containerPorts.refs[$index] !== undefined">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This port is already used.
</p>
</div>
</div>
<div class="col-sm-3 input-group input-group-sm" ng-if="ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.CLUSTER"> <div class="col-sm-12 form-inline" style="margin-top: 10px;">
<!-- #region INPUTS -->
<div
ng-repeat-start="publishedPort in ctrl.formValues.PublishedPorts"
style="margin-top: 2px;"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
tooltip-enable="ctrl.disableLoadBalancerEdit()"
uib-tooltip="Edition is not allowed while the Load Balancer is in 'Pending' state"
>
<div <div
class="small text-warning" class="input-group input-group-sm"
style="margin-top: 5px;" ng-class="{
ng-if=" striked: publishedPort.NeedsDeletion,
kubernetesApplicationCreationForm['published_node_port_' + $index].$invalid || ctrl.state.duplicates.publishedPorts.nodePorts.refs[$index] !== undefined 'col-sm-2': ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS,
" 'col-sm-3': ctrl.formValues.PublishingType !== ctrl.ApplicationPublishingTypes.INGRESS
}"
> >
<div ng-messages="kubernetesApplicationCreationForm['published_node_port_'+$index].$error"> <span class="input-group-addon">container port</span>
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Node port number must be inside the range 30000-32767.</p> <input
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Node port number must be inside the range 30000-32767.</p> type="number"
</div> class="form-control"
<p ng-if="ctrl.state.duplicates.publishedPorts.nodePorts.refs[$index] !== undefined"> name="container_port_{{ $index }}"
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This port is already used. ng-model="publishedPort.ContainerPort"
</p> placeholder="80"
ng-min="1"
ng-max="65535"
ng-required="!publishedPort.NeedsDeletion"
ng-change="ctrl.onChangePortMappingContainerPort()"
ng-disabled="ctrl.disableLoadBalancerEdit() || ctrl.isEditAndNotNewPublishedPort($index)"
data-cy="k8sAppCreate-containerPort_{{ $index }}"
/>
</div> </div>
</div>
<div class="col-sm-3 input-group input-group-sm" ng-if="ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS">
<div class="small text-warning" style="margin-top: 5px;" ng-if="kubernetesApplicationCreationForm['ingress_class_' + $index].$invalid">
<div ng-messages="kubernetesApplicationCreationForm['ingress_class_'+$index].$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Ingress selection is required.</p>
</div>
</div>
</div>
<div class="col-sm-3 input-group input-group-sm" ng-if="ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS">
<div <div
class="small text-warning" class="col-sm-3 input-group input-group-sm"
style="margin-top: 5px;" ng-class="{ striked: publishedPort.NeedsDeletion }"
ng-if=" ng-if="
kubernetesApplicationCreationForm['ingress_route_' + $index].$invalid || ctrl.state.duplicates.publishedPorts.ingressRoutes.refs[$index] !== undefined (publishedPort.IsNew && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.NODE_PORT) ||
(!publishedPort.IsNew && ctrl.savedFormValues.PublishingType === ctrl.ApplicationPublishingTypes.NODE_PORT)
" "
> >
<div ng-messages="kubernetesApplicationCreationForm['ingress_route_'+$index].$error"> <span class="input-group-addon">node port</span>
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Route is required.</p> <input
<p ng-message="pattern" name="published_node_port_{{ $index }}"
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field must consist of alphanumeric characters or the special characters: '-', '_' type="number"
or '/'. It must start and end with an alphanumeric character (e.g. 'my-route', or 'route-123').</p class="form-control"
ng-model="publishedPort.NodePort"
placeholder="30080"
ng-min="30000"
ng-max="32767"
ng-change="ctrl.onChangePortMappingNodePort()"
ng-disabled="ctrl.disableLoadBalancerEdit() || ctrl.isEditAndNotNewPublishedPort($index)"
data-cy="k8sAppCreate-nodePort_{{ $index }}"
/>
</div>
<div
class="col-sm-3 input-group input-group-sm"
ng-class="{ striked: publishedPort.NeedsDeletion }"
ng-if="
(publishedPort.IsNew && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.LOAD_BALANCER) ||
(!publishedPort.IsNew && ctrl.savedFormValues.PublishingType === ctrl.ApplicationPublishingTypes.LOAD_BALANCER)
"
>
<span class="input-group-addon">load balancer port</span>
<input
type="number"
class="form-control"
name="load_balancer_port_{{ $index }}"
ng-model="publishedPort.LoadBalancerPort"
placeholder="80"
value="8080"
ng-min="1"
ng-max="65535"
ng-required="!publishedPort.NeedsDeletion"
ng-change="ctrl.onChangePortMappingLoadBalancer()"
ng-disabled="ctrl.disableLoadBalancerEdit() || ctrl.isEditAndNotNewPublishedPort($index)"
data-cy="k8sAppCreate-lbPortInput_{{ $index }}"
/>
</div>
<div
class="col-sm-2 input-group input-group-sm"
ng-class="{ striked: publishedPort.NeedsDeletion }"
ng-if="
(publishedPort.IsNew && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS) ||
(!publishedPort.IsNew && ctrl.savedFormValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS)
"
>
<span class="input-group-addon">ingress</span>
<select
class="form-control"
name="ingress_class_{{ $index }}"
ng-model="publishedPort.IngressName"
ng-options="ingress.Name as ingress.Name for ingress in ctrl.ingresses"
ng-required="!publishedPort.NeedsDeletion"
ng-change="ctrl.onChangePortMappingIngress($index)"
ng-disabled="ctrl.disableLoadBalancerEdit() || ctrl.isEditAndNotNewPublishedPort($index)"
data-cy="k8sAppCreate-ingressSelect_{{ $index }}"
>
<option selected disabled hidden value="">Select an ingress</option>
</select>
</div>
<div
class="col-sm-2 input-group input-group-sm"
ng-class="{ striked: publishedPort.NeedsDeletion }"
ng-if="
(publishedPort.IsNew && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS) ||
(!publishedPort.IsNew && ctrl.savedFormValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS)
"
>
<span class="input-group-addon">hostname</span>
<select
class="form-control"
name="ingress_hostname_{{ $index }}"
ng-model="publishedPort.IngressHost"
ng-options="host as (host | kubernetesApplicationIngressEmptyHostname) for host in publishedPort.IngressHosts"
ng-change="ctrl.onChangePublishedPorts()"
ng-disabled="ctrl.disableLoadBalancerEdit() || ctrl.isEditAndNotNewPublishedPort($index)"
data-cy="k8sAppCreate-hostnameSelect_{{ $index }}"
>
<option selected disabled hidden value="">Select a hostname</option>
</select>
</div>
<div
class="col-sm-2 input-group input-group-sm"
ng-class="{ striked: publishedPort.NeedsDeletion }"
ng-if="
(publishedPort.IsNew && ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS) ||
(!publishedPort.IsNew && ctrl.savedFormValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS)
"
>
<span class="input-group-addon">route</span>
<input
class="form-control"
name="ingress_route_{{ $index }}"
ng-model="publishedPort.IngressRoute"
placeholder="route"
ng-required="!publishedPort.NeedsDeletion"
ng-change="ctrl.onChangePortMappingIngressRoute()"
ng-pattern="/^(\/?[a-zA-Z0-9]+([a-zA-Z0-9-/_]*[a-zA-Z0-9])?|[a-zA-Z0-9]+)|(\/){1}$/"
ng-disabled="ctrl.disableLoadBalancerEdit() || ctrl.isEditAndNotNewPublishedPort($index)"
data-cy="k8sAppCreate-ingressRoute_{{ $index }}"
/>
</div>
<div class="input-group col-sm-2 input-group-sm">
<div class="btn-group btn-group-sm" ng-class="{ striked: publishedPort.NeedsDeletion }">
<label
class="btn btn-primary"
ng-model="publishedPort.Protocol"
uib-btn-radio="'TCP'"
ng-change="ctrl.onChangePortProtocol($index)"
ng-disabled="ctrl.isProtocolOptionDisabled($index, 'TCP')"
data-cy="k8sAppCreate-TCPButton_{{ $index }}"
>TCP</label
>
<label
class="btn btn-primary"
ng-model="publishedPort.Protocol"
uib-btn-radio="'UDP'"
ng-change="ctrl.onChangePortProtocol($index)"
ng-disabled="ctrl.isProtocolOptionDisabled($index, 'UDP')"
data-cy="k8sAppCreate-UDPButton_{{ $index }}"
>UDP</label
> >
</div> </div>
<p ng-if="ctrl.state.duplicates.publishedPorts.ingressRoutes.refs[$index] !== undefined"> <button
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This route is already used. ng-if="!ctrl.disableLoadBalancerEdit() && !publishedPort.NeedsDeletion"
</p> class="btn btn-sm btn-danger"
type="button"
ng-click="ctrl.removePublishedPort($index)"
data-cy="k8sAppCreate-rmPortButton_{{ $index }}"
>
<i class="fa fa-trash-alt" aria-hidden="true"></i>
</button>
<button
ng-if="publishedPort.NeedsDeletion && ctrl.formValues.PublishingType === ctrl.savedFormValues.PublishingType"
class="btn btn-sm btn-primary"
type="button"
ng-click="ctrl.restorePublishedPort($index)"
data-cy="k8sAppCreate-restorePortButton_{{ $index }}"
>
<i class="fa fa-trash-restore" aria-hidden="true"></i>
</button>
</div> </div>
</div> </div>
<!-- #endregion -->
<div class="col-sm-3 input-group input-group-sm" ng-if="ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.LOAD_BALANCER"> <!-- #region VALIDATION -->
<div <div
class="small text-warning" ng-repeat-end
style="margin-top: 5px;" ng-show="
ng-if=" kubernetesApplicationCreationForm['container_port_' + $index].$invalid ||
kubernetesApplicationCreationForm['load_balancer_port_' + $index].$invalid || kubernetesApplicationCreationForm['published_node_port_' + $index].$invalid ||
ctrl.state.duplicates.publishedPorts.loadBalancerPorts.refs[$index] !== undefined kubernetesApplicationCreationForm['load_balancer_port_' + $index].$invalid ||
" kubernetesApplicationCreationForm['ingress_class_' + $index].$invalid ||
> kubernetesApplicationCreationForm['ingress_route_' + $index].$invalid ||
<div ng-messages="kubernetesApplicationCreationForm['load_balancer_port_'+$index].$error"> ctrl.state.duplicates.publishedPorts.containerPorts.refs[$index] !== undefined ||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Load balancer port number is required.</p> (ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.NODE_PORT &&
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Load balancer port number must be inside the range 1-65535.</p> ctrl.state.duplicates.publishedPorts.nodePorts.refs[$index] !== undefined) ||
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Load balancer port number must be inside the range 1-65535.</p> (ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS &&
ctrl.state.duplicates.publishedPorts.ingressRoutes.refs[$index] !== undefined) ||
(ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.LOAD_BALANCER &&
ctrl.state.duplicates.publishedPorts.loadBalancerPorts.refs[$index] !== undefined)
"
>
<div class="col-sm-3 input-group input-group-sm">
<div
class="small text-warning"
style="margin-top: 5px;"
ng-if="
kubernetesApplicationCreationForm['container_port_' + $index].$invalid ||
ctrl.state.duplicates.publishedPorts.containerPorts.refs[$index] !== undefined
"
>
<div ng-messages="kubernetesApplicationCreationForm['container_port_'+$index].$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Container port number is required.</p>
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Container port number must be inside the range 1-65535.</p>
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Container port number must be inside the range 1-65535.</p>
</div>
<p ng-if="ctrl.state.duplicates.publishedPorts.containerPorts.refs[$index] !== undefined">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This port is already used.
</p>
</div> </div>
<p ng-if="ctrl.state.duplicates.publishedPorts.loadBalancerPorts.refs[$index] !== undefined">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
This port is already used.
</p>
</div> </div>
</div>
<div class="input-group col-sm-1 input-group-sm"> </div> <div class="col-sm-3 input-group input-group-sm" ng-if="ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.NODE_PORT">
<div
class="small text-warning"
style="margin-top: 5px;"
ng-if="
kubernetesApplicationCreationForm['published_node_port_' + $index].$invalid ||
ctrl.state.duplicates.publishedPorts.nodePorts.refs[$index] !== undefined
"
>
<div ng-messages="kubernetesApplicationCreationForm['published_node_port_'+$index].$error">
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Node port number must be inside the range 30000-32767.</p>
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Node port number must be inside the range 30000-32767.</p>
</div>
<p ng-if="ctrl.state.duplicates.publishedPorts.nodePorts.refs[$index] !== undefined">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This port is already used.
</p>
</div>
</div>
<div class="col-sm-3 input-group input-group-sm" ng-if="ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS">
<div class="small text-warning" style="margin-top: 5px;" ng-if="kubernetesApplicationCreationForm['ingress_class_' + $index].$invalid">
<div ng-messages="kubernetesApplicationCreationForm['ingress_class_'+$index].$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Ingress selection is required.</p>
</div>
</div>
</div>
<div class="col-sm-3 input-group input-group-sm" ng-if="ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.INGRESS">
<div
class="small text-warning"
style="margin-top: 5px;"
ng-if="
kubernetesApplicationCreationForm['ingress_route_' + $index].$invalid || ctrl.state.duplicates.publishedPorts.ingressRoutes.refs[$index] !== undefined
"
>
<div ng-messages="kubernetesApplicationCreationForm['ingress_route_'+$index].$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Route is required.</p>
<p ng-message="pattern"
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field must consist of alphanumeric characters or the special characters: '-',
'_' or '/'. It must start and end with an alphanumeric character (e.g. 'my-route', or 'route-123').</p
>
</div>
<p ng-if="ctrl.state.duplicates.publishedPorts.ingressRoutes.refs[$index] !== undefined">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This route is already used.
</p>
</div>
</div>
<div class="col-sm-3 input-group input-group-sm" ng-if="ctrl.formValues.PublishingType === ctrl.ApplicationPublishingTypes.LOAD_BALANCER">
<div
class="small text-warning"
style="margin-top: 5px;"
ng-if="
kubernetesApplicationCreationForm['load_balancer_port_' + $index].$invalid ||
ctrl.state.duplicates.publishedPorts.loadBalancerPorts.refs[$index] !== undefined
"
>
<div ng-messages="kubernetesApplicationCreationForm['load_balancer_port_'+$index].$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Load balancer port number is required.</p>
<p ng-message="min"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Load balancer port number must be inside the range 1-65535.</p>
<p ng-message="max"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Load balancer port number must be inside the range 1-65535.</p>
</div>
<p ng-if="ctrl.state.duplicates.publishedPorts.loadBalancerPorts.refs[$index] !== undefined">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
This port is already used.
</p>
</div>
</div>
<div class="input-group col-sm-1 input-group-sm"> </div>
</div>
<!-- #endregion -->
</div> </div>
<!-- #endregion -->
</div> </div>
</div> </span>
<!-- #endregion --> <!-- #endregion -->
<!-- summary --> <!-- summary -->

View File

@ -37,6 +37,7 @@ class KubernetesCreateApplicationController {
/* @ngInject */ /* @ngInject */
constructor( constructor(
$scope,
$async, $async,
$state, $state,
Notifications, Notifications,
@ -54,6 +55,7 @@ class KubernetesCreateApplicationController {
StackService, StackService,
KubernetesNodesLimitsService KubernetesNodesLimitsService
) { ) {
this.$scope = $scope;
this.$async = $async; this.$async = $async;
this.$state = $state; this.$state = $state;
this.Notifications = Notifications; this.Notifications = Notifications;
@ -140,6 +142,9 @@ class KubernetesCreateApplicationController {
this.deployApplicationAsync = this.deployApplicationAsync.bind(this); this.deployApplicationAsync = this.deployApplicationAsync.bind(this);
this.setPullImageValidity = this.setPullImageValidity.bind(this); this.setPullImageValidity = this.setPullImageValidity.bind(this);
this.onChangeFileContent = this.onChangeFileContent.bind(this); this.onChangeFileContent = this.onChangeFileContent.bind(this);
this.onServicePublishChange = this.onServicePublishChange.bind(this);
this.$scope.$watch(() => this.formValues.IsPublishingService, this.onServicePublishChange);
} }
/* #endregion */ /* #endregion */
@ -416,6 +421,27 @@ class KubernetesCreateApplicationController {
/* #endregion */ /* #endregion */
onServicePublishChange() {
// service creation
if (this.formValues.PublishedPorts.length === 0) {
if (this.formValues.IsPublishingService) {
// toggle enabled
this.addPublishedPort();
}
return;
}
// service update
if (this.formValues.IsPublishingService) {
// toggle enabled
this.formValues.PublishedPorts.forEach((port) => (port.NeedsDeletion = false));
} else {
// toggle disabled
// all new ports need to be deleted, existing ports need to be marked as needing deletion
this.formValues.PublishedPorts = this.formValues.PublishedPorts.filter((port) => !port.IsNew).map((port) => ({ ...port, NeedsDeletion: true }));
}
}
/* #region PUBLISHED PORTS UI MANAGEMENT */ /* #region PUBLISHED PORTS UI MANAGEMENT */
addPublishedPort() { addPublishedPort() {
const p = new KubernetesApplicationPublishedPortFormValue(); const p = new KubernetesApplicationPublishedPortFormValue();
@ -476,7 +502,7 @@ class KubernetesCreateApplicationController {
onChangePortMappingNodePort() { onChangePortMappingNodePort() {
const state = this.state.duplicates.publishedPorts.nodePorts; const state = this.state.duplicates.publishedPorts.nodePorts;
if (this.formValues.PublishingType === KubernetesApplicationPublishingTypes.CLUSTER) { if (this.formValues.PublishingType === KubernetesApplicationPublishingTypes.NODE_PORT) {
const source = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion ? undefined : p.NodePort)); const source = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion ? undefined : p.NodePort));
const duplicates = KubernetesFormValidationHelper.getDuplicates(source); const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
state.refs = duplicates; state.refs = duplicates;
@ -736,10 +762,8 @@ class KubernetesCreateApplicationController {
return this.state.isEdit && !this.formValues.PublishedPorts[index].IsNew; return this.state.isEdit && !this.formValues.PublishedPorts[index].IsNew;
} }
isNotInternalAndHasNoPublishedPorts() { hasNoPublishedPorts() {
const toDelPorts = _.filter(this.formValues.PublishedPorts, { NeedsDeletion: true }); return this.formValues.PublishedPorts.filter((port) => !port.NeedsDeletion).length === 0;
const toKeepPorts = _.without(this.formValues.PublishedPorts, ...toDelPorts);
return this.formValues.PublishingType !== KubernetesApplicationPublishingTypes.INTERNAL && toKeepPorts.length === 0;
} }
isEditAndNotNewPlacement(index) { isEditAndNotNewPlacement(index) {
@ -771,8 +795,8 @@ class KubernetesCreateApplicationController {
const invalid = !this.isValid(); const invalid = !this.isValid();
const hasNoChanges = this.isEditAndNoChangesMade(); const hasNoChanges = this.isEditAndNoChangesMade();
const nonScalable = this.isNonScalable(); const nonScalable = this.isNonScalable();
const notInternalNoPorts = this.isNotInternalAndHasNoPublishedPorts(); const isPublishingWithoutPorts = this.formValues.IsPublishingService && this.hasNoPublishedPorts();
return overflow || autoScalerOverflow || inProgress || invalid || hasNoChanges || nonScalable || notInternalNoPorts; return overflow || autoScalerOverflow || inProgress || invalid || hasNoChanges || nonScalable || isPublishingWithoutPorts;
} }
disableLoadBalancerEdit() { disableLoadBalancerEdit() {
@ -926,7 +950,7 @@ class KubernetesCreateApplicationController {
if (this.savedFormValues) { if (this.savedFormValues) {
this.formValues.PublishingType = this.savedFormValues.PublishingType; this.formValues.PublishingType = this.savedFormValues.PublishingType;
} else { } else {
this.formValues.PublishingType = this.ApplicationPublishingTypes.INTERNAL; this.formValues.PublishingType = this.ApplicationPublishingTypes.CLUSTER_IP;
} }
} }
this.formValues.OriginalIngresses = this.ingresses; this.formValues.OriginalIngresses = this.ingresses;
@ -1114,6 +1138,8 @@ class KubernetesCreateApplicationController {
this.nodesLimits.excludesPods(this.application.Pods, this.formValues.CpuLimit, KubernetesResourceReservationHelper.bytesValue(this.formValues.MemoryLimit)); this.nodesLimits.excludesPods(this.application.Pods, this.formValues.CpuLimit, KubernetesResourceReservationHelper.bytesValue(this.formValues.MemoryLimit));
} }
this.formValues.IsPublishingService = this.formValues.PublishedPorts.length > 0;
this.updateNamespaceLimits(); this.updateNamespaceLimits();
this.updateSliders(); this.updateSliders();
} catch (err) { } catch (err) {

View File

@ -253,7 +253,8 @@
<div ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.LOAD_BALANCER"> <div ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.LOAD_BALANCER">
<div class="small text-muted"> <div class="small text-muted">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
This application is exposed through an external load balancer. Use the links below to access the different ports exposed. This application is exposed through a service of type <span class="bold">{{ ctrl.application.ServiceType }}</span
>. Refer to the port configuration below to access it.
</div> </div>
<div style="margin-top: 10px;" class="small text-muted"> <div style="margin-top: 10px;" class="small text-muted">
<span ng-if="!ctrl.application.LoadBalancerIPAddress"> <span ng-if="!ctrl.application.LoadBalancerIPAddress">
@ -282,45 +283,41 @@
</div> </div>
</div> </div>
<!-- cluster notice --> <!-- NodePort notice -->
<div ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.NODE_PORT"> <div ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.NODE_PORT">
<div class="small text-muted"> <div class="small text-muted">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
This application is exposed globally on all nodes of your cluster. It can be reached using the IP address of any node in your cluster using the port configuration This application is exposed through a service of type <span class="bold">{{ ctrl.application.ServiceType }}</span
below. >. It can be reached using the IP address of any node in your cluster using the port configuration below.
</div> </div>
</div> </div>
<!-- internal notice --> <!-- ClusterIP notice -->
<div ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.CLUSTER_IP && !ctrl.state.useIngress"> <div ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.CLUSTER_IP && !ctrl.state.useIngress">
<div class="small text-muted"> <div class="small text-muted">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
This application is only available for internal usage inside the cluster via the application name <code>{{ ctrl.application.ServiceName }}</code> This application is exposed through a service of type <span class="bold">{{ ctrl.application.ServiceType }}</span
>. It can be reached via the application name <code>{{ ctrl.application.ServiceName }}</code> and the port configuration below.
<span class="btn btn-primary btn-xs" ng-click="ctrl.copyApplicationName()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy</span> <span class="btn btn-primary btn-xs" ng-click="ctrl.copyApplicationName()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy</span>
<span id="copyNotificationApplicationName" style="margin-left: 7px; display: none; color: #23ae89;" class="small" <span id="copyNotificationApplicationName" style="margin-left: 7px; display: none; color: #23ae89;" class="small"
><i class="fa fa-check" aria-hidden="true"></i> copied</span ><i class="fa fa-check" aria-hidden="true"></i> copied</span
> >
</div> </div>
<div class="small text-muted" style="margin-top: 2px;">
<p>Refer to the below port configuration to access the application.</p>
</div>
</div> </div>
<!-- ingress notice --> <!-- Ingress notice -->
<div ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.CLUSTER_IP && ctrl.state.useIngress"> <div ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.CLUSTER_IP && ctrl.state.useIngress">
<div class="small text-muted"> <div class="small text-muted">
<p> <p>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
This application is available for internal usage inside the cluster via the application name <code>{{ ctrl.application.ServiceName }}</code> This application is exposed through a service of type <span class="bold">{{ ctrl.application.ServiceType }}</span
>. It can be reached via the application name <code>{{ ctrl.application.ServiceName }}</code> and the port configuration below.
<span class="btn btn-primary btn-xs" ng-click="ctrl.copyApplicationName()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy</span> <span class="btn btn-primary btn-xs" ng-click="ctrl.copyApplicationName()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy</span>
<span id="copyNotificationApplicationName" style="margin-left: 7px; display: none; color: #23ae89;" class="small" <span id="copyNotificationApplicationName" style="margin-left: 7px; display: none; color: #23ae89;" class="small"
><i class="fa fa-check" aria-hidden="true"></i> copied</span ><i class="fa fa-check" aria-hidden="true"></i> copied</span
> >
</p> </p>
<p>It can also be accessed via specific HTTP route(s).</p> <p>It is also associated to an <span class="bold">Ingress</span> and can be accessed via specific HTTP route(s).</p>
</div>
<div class="small text-muted" style="margin-top: 2px;">
<p>Refer to the below port configuration to access the application.</p>
</div> </div>
</div> </div>
@ -330,7 +327,7 @@
<tbody> <tbody>
<tr class="text-muted"> <tr class="text-muted">
<td style="width: 25%;">Container port</td> <td style="width: 25%;">Container port</td>
<td style="width: 25%;">{{ ctrl.application.ServiceType | kubernetesApplicationPortsTableHeaderText }} port</td> <td style="width: 25%;">Service port</td>
<td style="width: 50%;">HTTP route</td> <td style="width: 50%;">HTTP route</td>
</tr> </tr>
<tr ng-repeat-start="port in ctrl.application.PublishedPorts"> <tr ng-repeat-start="port in ctrl.application.PublishedPorts">

View File

@ -1,5 +1,5 @@
<kubernetes-view-header title="Configuration list" state="kubernetes.configurations" view-ready="ctrl.state.viewReady"> <kubernetes-view-header title="ConfigMaps & Secrets list" state="kubernetes.configurations" view-ready="ctrl.state.viewReady">
Configurations ConfigMaps & Secrets
</kubernetes-view-header> </kubernetes-view-header>
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading> <kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>

View File

@ -1,5 +1,5 @@
<kubernetes-view-header title="Create configuration" state="kubernetes.configurations.new" view-ready="ctrl.state.viewReady"> <kubernetes-view-header title="Create configuration" state="kubernetes.configurations.new" view-ready="ctrl.state.viewReady">
<a ui-sref="kubernetes.configurations">Configurations</a> &gt; Create a configuration <a ui-sref="kubernetes.configurations">ConfigMaps and Secrets</a> &gt; Create a configuration
</kubernetes-view-header> </kubernetes-view-header>
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading> <kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
@ -95,7 +95,7 @@
<label for="type_basic" data-cy="k8sConfigCreate-nonSensitiveButton"> <label for="type_basic" data-cy="k8sConfigCreate-nonSensitiveButton">
<div class="boxselector_header"> <div class="boxselector_header">
<i class="fa fa-file-code" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fa fa-file-code" aria-hidden="true" style="margin-right: 2px;"></i>
Non-sensitive ConfigMap
</div> </div>
<p>This configuration holds non-sensitive information</p> <p>This configuration holds non-sensitive information</p>
</label> </label>
@ -105,7 +105,7 @@
<label for="type_secret" data-cy="k8sConfigCreate-sensitiveButton"> <label for="type_secret" data-cy="k8sConfigCreate-sensitiveButton">
<div class="boxselector_header"> <div class="boxselector_header">
<i class="fa fa-user-secret" aria-hidden="true" style="margin-right: 2px;"></i> <i class="fa fa-user-secret" aria-hidden="true" style="margin-right: 2px;"></i>
Sensitive Secret
</div> </div>
<p>This configuration holds sensitive information</p> <p>This configuration holds sensitive information</p>
</label> </label>
@ -153,7 +153,7 @@
button-spinner="ctrl.state.actionInProgress" button-spinner="ctrl.state.actionInProgress"
data-cy="k8sConfigCreate-CreateConfigButton" data-cy="k8sConfigCreate-CreateConfigButton"
> >
<span ng-hide="ctrl.state.actionInProgress">Create configuration</span> <span ng-hide="ctrl.state.actionInProgress">Create {{ ctrl.formValues.Type | kubernetesConfigurationTypeText }}</span>
<span ng-show="ctrl.state.actionInProgress">Creation in progress...</span> <span ng-show="ctrl.state.actionInProgress">Creation in progress...</span>
</button> </button>
</div> </div>

View File

@ -1,7 +1,7 @@
<kubernetes-view-header title="Configuration details" state="kubernetes.configurations.configuration" view-ready="ctrl.state.viewReady"> <kubernetes-view-header title="Configuration details" state="kubernetes.configurations.configuration" view-ready="ctrl.state.viewReady">
<a ui-sref="kubernetes.resourcePools">Namespaces</a> &gt; <a ui-sref="kubernetes.resourcePools">Namespaces</a> &gt;
<a ui-sref="kubernetes.resourcePools.resourcePool({ id: ctrl.configuration.Namespace })">{{ ctrl.configuration.Namespace }}</a> &gt; <a ui-sref="kubernetes.resourcePools.resourcePool({ id: ctrl.configuration.Namespace })">{{ ctrl.configuration.Namespace }}</a> &gt;
<a ui-sref="kubernetes.configurations">Configurations</a> &gt; {{ ctrl.configuration.Name }} <a ui-sref="kubernetes.configurations">ConfigMaps and Secrets</a> &gt; {{ ctrl.configuration.Name }}
</kubernetes-view-header> </kubernetes-view-header>
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading> <kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
@ -106,7 +106,7 @@
button-spinner="ctrl.state.actionInProgress" button-spinner="ctrl.state.actionInProgress"
data-cy="k8sConfigDetail-updateConfig" data-cy="k8sConfigDetail-updateConfig"
> >
<span ng-hide="ctrl.state.actionInProgress">Update configuration</span> <span ng-hide="ctrl.state.actionInProgress">Update {{ ctrl.configuration.Type | kubernetesConfigurationTypeText }}</span>
<span ng-show="ctrl.state.actionInProgress">Update in progress...</span> <span ng-show="ctrl.state.actionInProgress">Update in progress...</span>
</button> </button>
</div> </div>

View File

@ -22,7 +22,7 @@
<li ng-repeat="summary in $ctrl.state.resources" ng-if="summary.action && summary.kind && summary.name"> <li ng-repeat="summary in $ctrl.state.resources" ng-if="summary.action && summary.kind && summary.name">
{{ summary.action }} {{ summary.action }}
{{ $ctrl.getArticle(summary.kind, summary.action) }} {{ $ctrl.getArticle(summary.kind, summary.action) }}
<span class="summary">{{ summary.kind }}</span> named <code>{{ summary.name }}</code> <span class="bold">{{ summary.kind }}</span> named <code>{{ summary.name }}</code>
<span ng-if="summary.type"> <span ng-if="summary.type">
of type <code>{{ summary.type }}</code></span of type <code>{{ summary.type }}</code></span
> >

View File

@ -271,6 +271,7 @@ ul.sidebar .sidebar-title .form-control {
ul.sidebar .sidebar-list a, ul.sidebar .sidebar-list a,
ul.sidebar .sidebar-list .sidebar-sublist a { ul.sidebar .sidebar-list .sidebar-sublist a {
line-height: 36px; line-height: 36px;
letter-spacing: -0.03em;
} }
ul.sidebar .sidebar-list .menu-icon { ul.sidebar .sidebar-list .menu-icon {