mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 12:25:22 +02:00
feat(k8s): Allow mix services for k8s app EE-1791 (#6198)
allow a mix of services for k8s in ui
This commit is contained in:
parent
edf048570b
commit
c47e840b37
26 changed files with 2336 additions and 1863 deletions
|
@ -208,6 +208,17 @@
|
|||
>
|
||||
<i class="fa fa-file-code space-right" aria-hidden="true"></i>Edit this application
|
||||
</button>
|
||||
<button
|
||||
authorization="K8sApplicationDetailsW"
|
||||
ng-if="ctrl.isExternalApplication()"
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary"
|
||||
ui-sref="kubernetes.applications.application.edit"
|
||||
style="margin-left: 0;"
|
||||
data-cy="k8sAppDetail-editAppButton"
|
||||
>
|
||||
<i class="fa fa-file-code space-right" aria-hidden="true"></i>Edit External application
|
||||
</button>
|
||||
<button
|
||||
ng-if="ctrl.application.ApplicationType !== ctrl.KubernetesApplicationTypes.POD"
|
||||
type="button"
|
||||
|
@ -249,147 +260,27 @@
|
|||
</div>
|
||||
|
||||
<div ng-if="ctrl.application.PublishedPorts.length > 0">
|
||||
<!-- LB notice -->
|
||||
<div ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.LOAD_BALANCER">
|
||||
<div class="small text-muted">
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
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 style="margin-top: 10px;" class="small text-muted">
|
||||
<span ng-if="!ctrl.application.LoadBalancerIPAddress">
|
||||
<p> Load balancer status: <i class="fa fa-cog fa-spin" style="margin-left: 2px;"></i> pending </p>
|
||||
<p>
|
||||
<u>what does the "pending" status means?</u>
|
||||
<portainer-tooltip
|
||||
position="bottom"
|
||||
message="A pending status means that Portainer delegated the request to the provider responsible for creating the external load balancer. If it stays in pending state for long, this means that this capability might not be supported or you might have an issue with your cluster provider. Contact your cluster administrator for more information."
|
||||
>
|
||||
</portainer-tooltip>
|
||||
</p>
|
||||
</span>
|
||||
<span ng-if="ctrl.application.LoadBalancerIPAddress">
|
||||
<p> Load balancer status: <i class="fa fa-check green-icon" style="margin-left: 2px;"></i> available </p>
|
||||
<p>
|
||||
Load balancer IP address: {{ ctrl.application.LoadBalancerIPAddress }}
|
||||
<span class="btn btn-primary btn-xs" ng-click="ctrl.copyLoadBalancerIP()" style="margin-left: 5px;">
|
||||
<i class="fa fa-copy space-right" aria-hidden="true"></i>Copy
|
||||
</span>
|
||||
<span id="copyNotificationLB" style="margin-left: 7px; display: none; color: #23ae89;" class="small">
|
||||
<i class="fa fa-check" aria-hidden="true"></i> copied
|
||||
</span>
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NodePort notice -->
|
||||
<div ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.NODE_PORT">
|
||||
<div class="small text-muted">
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
This application is exposed through a service of type <span class="bold">{{ ctrl.application.ServiceType }}</span
|
||||
>. It can be reached using the IP address of any node in your cluster using the port configuration below.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ClusterIP notice -->
|
||||
<div ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.CLUSTER_IP && !ctrl.state.useIngress">
|
||||
<div class="small text-muted">
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
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 id="copyNotificationApplicationName" style="margin-left: 7px; display: none; color: #23ae89;" class="small"
|
||||
><i class="fa fa-check" aria-hidden="true"></i> copied</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ingress notice -->
|
||||
<div ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.CLUSTER_IP && ctrl.state.useIngress">
|
||||
<!-- Services notice -->
|
||||
<div>
|
||||
<div class="small text-muted">
|
||||
<p>
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
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 id="copyNotificationApplicationName" style="margin-left: 7px; display: none; color: #23ae89;" class="small"
|
||||
><i class="fa fa-check" aria-hidden="true"></i> copied</span
|
||||
>
|
||||
This application is exposed through service(s) as below:
|
||||
</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>
|
||||
|
||||
<!-- table -->
|
||||
<div style="margin-top: 15px;">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="text-muted">
|
||||
<td style="width: 25%;">Container port</td>
|
||||
<td style="width: 25%;">Service port</td>
|
||||
<td style="width: 50%;">HTTP route</td>
|
||||
</tr>
|
||||
<tr ng-repeat-start="port in ctrl.application.PublishedPorts">
|
||||
<td ng-if="!ctrl.portHasIngressRules(port)" data-cy="k8sAppDetail-containerPort">{{ port.TargetPort }}/{{ port.Protocol }}</td>
|
||||
<td ng-if="!ctrl.portHasIngressRules(port)">
|
||||
<span ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.NODE_PORT" data-cy="k8sAppDetail-nodePort">
|
||||
{{ port.NodePort }}
|
||||
</span>
|
||||
<span ng-if="ctrl.application.ServiceType !== ctrl.KubernetesServiceTypes.NODE_PORT" data-cy="k8sAppDetail-containerPort">
|
||||
{{ port.Port }}
|
||||
</span>
|
||||
<a
|
||||
ng-if="ctrl.application.LoadBalancerIPAddress"
|
||||
ng-href="http://{{ ctrl.application.LoadBalancerIPAddress }}:{{ port.Port }}"
|
||||
target="_blank"
|
||||
style="margin-left: 5px;"
|
||||
data-cy="k8sAppDetail-accessLink"
|
||||
>
|
||||
<i class="fa fa-external-link-alt" aria-hidden="true"></i> access
|
||||
</a>
|
||||
</td>
|
||||
<td ng-if="!ctrl.portHasIngressRules(port)">-</td>
|
||||
</tr>
|
||||
<tr ng-repeat-end ng-repeat="rule in port.IngressRules">
|
||||
<td data-cy="k8sAppDetail-httpRoute">{{ port.TargetPort }}/{{ port.Protocol }}</td>
|
||||
<td>
|
||||
<span ng-if="ctrl.application.ServiceType === ctrl.KubernetesServiceTypes.NODE_PORT" data-cy="k8sAppDetail-nodePort">
|
||||
{{ port.NodePort }}
|
||||
</span>
|
||||
<span ng-if="ctrl.application.ServiceType !== ctrl.KubernetesServiceTypes.NODE_PORT" data-cy="k8sAppDetail-port">
|
||||
{{ port.Port }}
|
||||
</span>
|
||||
<a
|
||||
ng-if="ctrl.application.LoadBalancerIPAddress"
|
||||
ng-href="http://{{ ctrl.application.LoadBalancerIPAddress }}:{{ port.Port }}"
|
||||
target="_blank"
|
||||
style="margin-left: 5px;"
|
||||
>
|
||||
<i class="fa fa-external-link-alt" aria-hidden="true"></i> access
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
ng-if="!ctrl.ruleCanBeDisplayed(rule)"
|
||||
class="text-muted"
|
||||
tooltip-append-to-body="true"
|
||||
tooltip-placement="bottom"
|
||||
tooltip-class="portainer-tooltip"
|
||||
uib-tooltip="Ingress controller IP address not available yet"
|
||||
style="cursor: pointer;"
|
||||
>pending
|
||||
</span>
|
||||
<span ng-if="ctrl.ruleCanBeDisplayed(rule)">
|
||||
<a ng-href="{{ ctrl.buildIngressRuleURL(rule) }}" target="_blank" data-cy="k8sAppDetail-httpRouteLink">
|
||||
{{ ctrl.buildIngressRuleURL(rule) | stripprotocol }}
|
||||
</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<kubernetes-application-services-table
|
||||
services="ctrl.application.Services"
|
||||
application="ctrl.application"
|
||||
public-url="ctrl.state.publicUrl"
|
||||
></kubernetes-application-services-table>
|
||||
<!-- table -->
|
||||
|
||||
<!-- table -->
|
||||
<kubernetes-application-ingress-table application="ctrl.application" public-url="ctrl.state.publicUrl"></kubernetes-application-ingress-table>
|
||||
<!-- table -->
|
||||
</div>
|
||||
<!-- !ACCESSING APPLICATION -->
|
||||
<!-- AUTO SCALING -->
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
import * as JsonPatch from 'fast-json-patch';
|
||||
|
||||
import {
|
||||
KubernetesApplicationDataAccessPolicies,
|
||||
KubernetesApplicationDeploymentTypes,
|
||||
|
@ -112,7 +113,6 @@ class KubernetesApplicationController {
|
|||
KubernetesStackService,
|
||||
KubernetesPodService,
|
||||
KubernetesNodeService,
|
||||
|
||||
StackService
|
||||
) {
|
||||
this.$async = $async;
|
||||
|
@ -317,6 +317,7 @@ class KubernetesApplicationController {
|
|||
this.application = application;
|
||||
this.allContainers = KubernetesApplicationHelper.associateAllContainersAndApplication(application);
|
||||
this.formValues.Note = this.application.Note;
|
||||
this.formValues.Services = this.application.Services;
|
||||
if (this.application.Note) {
|
||||
this.state.expandedNote = true;
|
||||
}
|
||||
|
@ -347,6 +348,12 @@ class KubernetesApplicationController {
|
|||
}
|
||||
|
||||
async onInit() {
|
||||
const endpointId = this.LocalStorage.getEndpointID();
|
||||
const endpoints = this.LocalStorage.getEndpoints();
|
||||
const endpoint = _.find(endpoints, function (item) {
|
||||
return item.Id === endpointId;
|
||||
});
|
||||
|
||||
this.state = {
|
||||
activeTab: 0,
|
||||
currentName: this.$state.$current.name,
|
||||
|
@ -365,6 +372,7 @@ class KubernetesApplicationController {
|
|||
expandedNote: false,
|
||||
useIngress: false,
|
||||
useServerMetrics: this.endpoint.Kubernetes.Configuration.UseServerMetrics,
|
||||
publicUrl: endpoint.PublicURL,
|
||||
};
|
||||
|
||||
this.state.activeTab = this.LocalStorage.getActiveTab('application');
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
export default class KubernetesApplicationIngressController {
|
||||
/* @ngInject */
|
||||
constructor($async, KubernetesIngressService) {
|
||||
this.$async = $async;
|
||||
this.KubernetesIngressService = KubernetesIngressService;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.$async(async () => {
|
||||
this.hasIngress;
|
||||
this.applicationIngress = [];
|
||||
const ingresses = await this.KubernetesIngressService.get(this.application.ResourcePool);
|
||||
const services = this.application.Services;
|
||||
|
||||
_.forEach(services, (service) => {
|
||||
_.forEach(ingresses, (ingress) => {
|
||||
_.forEach(ingress.Paths, (path) => {
|
||||
if (path.ServiceName === service.metadata.name) {
|
||||
this.applicationIngress.push(path);
|
||||
this.hasIngress = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<div style="margin-top: 15px" ng-if="$ctrl.hasIngress">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="text-muted">
|
||||
<td style="width: 15%">Ingress name</td>
|
||||
<td style="width: 10%">Service name</td>
|
||||
<td style="width: 10%">Host</td>
|
||||
<td style="width: 10%">Port</td>
|
||||
<td style="width: 10%">Path</td>
|
||||
<td style="width: 15%">HTTP Route</td>
|
||||
</tr>
|
||||
<tr ng-repeat="ingress in $ctrl.applicationIngress">
|
||||
<td>{{ ingress.IngressName }}</td>
|
||||
<td>{{ ingress.ServiceName }}</td>
|
||||
<td>{{ ingress.Host }}</td>
|
||||
<td>{{ ingress.Port }}</td>
|
||||
<td>{{ ingress.Path }}</td>
|
||||
<td
|
||||
><a target="_blank" href="http://{{ ingress.Host }}{{ ingress.Path }}">{{ ingress.Host }}{{ ingress.Path }}</a></td
|
||||
>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
|
@ -0,0 +1,11 @@
|
|||
import angular from 'angular';
|
||||
import controller from './ingress-table.controller';
|
||||
|
||||
angular.module('portainer.kubernetes').component('kubernetesApplicationIngressTable', {
|
||||
templateUrl: './ingress-table.html',
|
||||
controller,
|
||||
bindings: {
|
||||
application: '<',
|
||||
publicUrl: '<',
|
||||
},
|
||||
});
|
|
@ -0,0 +1,56 @@
|
|||
<!-- table -->
|
||||
<div style="margin-top: 15px">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="text-muted">
|
||||
<td style="width: 15%">Service name</td>
|
||||
<td style="width: 10%">Type</td>
|
||||
<td style="width: 10%">Cluster IP</td>
|
||||
<td style="width: 10%">External IP</td>
|
||||
<td style="width: 10%">Container port</td>
|
||||
<td style="width: 15%">Service port(s)</td>
|
||||
</tr>
|
||||
<tr ng-repeat="service in $ctrl.services">
|
||||
<td>{{ service.metadata.name }}</td>
|
||||
<td>{{ service.spec.type }}</td>
|
||||
<td>{{ service.spec.clusterIP }}</td>
|
||||
<td ng-show="service.spec.type === 'LoadBalancer'">
|
||||
<div ng-show="service.status.loadBalancer.ingress">
|
||||
<a target="_blank" ng-href="http://{{ service.status.loadBalancer.ingress[0].ip }}:{{ service.spec.ports[0].port }}">
|
||||
<i class="fa fa-external-link-alt" aria-hidden="true"></i>
|
||||
<span data-cy="k8sAppDetail-containerPort"> Access </span>
|
||||
</a>
|
||||
</div>
|
||||
<div ng-show="!service.status.loadBalancer.ingress">
|
||||
{{ service.spec.externalIP ? service.spec.externalIP : 'pending...' }}
|
||||
</div>
|
||||
</td>
|
||||
<td ng-show="service.spec.type !== 'LoadBalancer'">{{ service.spec.externalIP ? service.spec.externalIP : '-' }}</td>
|
||||
|
||||
<td data-cy="k8sAppDetail-containerPort">
|
||||
<div ng-repeat="port in service.spec.ports">{{ port.targetPort }}</div>
|
||||
</td>
|
||||
<td ng-if="!ctrl.portHasIngressRules(port)">
|
||||
<div ng-repeat="port in service.spec.ports">
|
||||
<a ng-if="$ctrl.publicUrl && port.nodePort" ng-href="http://{{ $ctrl.publicUrl }}:{{ port.nodePort }}" target="_blank" style="margin-left: 5px">
|
||||
<i class="fa fa-external-link-alt" aria-hidden="true"></i>
|
||||
<span data-cy="k8sAppDetail-containerPort">
|
||||
{{ port.port }}
|
||||
</span>
|
||||
<span>{{ port.nodePort ? ':' : '' }}</span>
|
||||
<span data-cy="k8sAppDetail-nodePort"> {{ port.nodePort }}/{{ port.protocol }} </span>
|
||||
</a>
|
||||
|
||||
<div ng-if="!$ctrl.publicUrl">
|
||||
<span data-cy="k8sAppDetail-servicePort">
|
||||
{{ port.port }}
|
||||
</span>
|
||||
<span>{{ port.nodePort ? ':' : '' }}</span>
|
||||
<span data-cy="k8sAppDetail-nodePort"> {{ port.nodePort }}/{{ port.protocol }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
import angular from 'angular';
|
||||
|
||||
angular.module('portainer.kubernetes').component('kubernetesApplicationServicesTable', {
|
||||
templateUrl: './services-table.html',
|
||||
bindings: {
|
||||
services: '<',
|
||||
application: '<',
|
||||
publicUrl: '<',
|
||||
},
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue