diff --git a/app/kubernetes/components/datatables/nodes-datatable/nodesDatatable.html b/app/kubernetes/components/datatables/nodes-datatable/nodesDatatable.html index c6526a30e..8b976a235 100644 --- a/app/kubernetes/components/datatables/nodes-datatable/nodesDatatable.html +++ b/app/kubernetes/components/datatables/nodes-datatable/nodesDatatable.html @@ -114,7 +114,10 @@ dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" > - {{ item.Name }} + + {{ item.Name }} + + api {{ item.Name }} {{ item.Role }} diff --git a/app/kubernetes/endpoint/converter.js b/app/kubernetes/endpoint/converter.js index 780a9118c..6dd728785 100644 --- a/app/kubernetes/endpoint/converter.js +++ b/app/kubernetes/endpoint/converter.js @@ -1,4 +1,4 @@ -import { KubernetesEndpoint, KubernetesEndpointAnnotationLeader } from 'Kubernetes/endpoint/models'; +import { KubernetesEndpoint, KubernetesEndpointAnnotationLeader, KubernetesEndpointSubset } from 'Kubernetes/endpoint/models'; import _ from 'lodash-es'; class KubernetesEndpointConverter { @@ -13,6 +13,16 @@ class KubernetesEndpointConverter { const split = _.split(parsedJson.holderIdentity, '_'); res.HolderIdentity = split[0]; } + + if (data.subsets) { + res.Subsets = _.map(data.subsets, (item) => { + const subset = new KubernetesEndpointSubset(); + subset.Ips = _.map(item.addresses, 'ip'); + const port = _.find(item.ports, { name: 'https' }); + subset.Port = port ? port.port : undefined; + return subset; + }); + } return res; } } diff --git a/app/kubernetes/endpoint/models.js b/app/kubernetes/endpoint/models.js index bd493adfd..506573af3 100644 --- a/app/kubernetes/endpoint/models.js +++ b/app/kubernetes/endpoint/models.js @@ -8,6 +8,7 @@ const _KubernetesEndpoint = Object.freeze({ Name: '', Namespace: '', HolderIdentity: '', + Subsets: [], }); export class KubernetesEndpoint { @@ -15,3 +16,14 @@ export class KubernetesEndpoint { Object.assign(this, JSON.parse(JSON.stringify(_KubernetesEndpoint))); } } + +const _KubernetesEndpointSubset = Object.freeze({ + Ips: [], + Port: 0, +}); + +export class KubernetesEndpointSubset { + constructor() { + Object.assign(this, JSON.parse(JSON.stringify(_KubernetesEndpointSubset))); + } +} diff --git a/app/kubernetes/node/models.js b/app/kubernetes/node/models.js index 8b77bfece..f1c547969 100644 --- a/app/kubernetes/node/models.js +++ b/app/kubernetes/node/models.js @@ -11,7 +11,9 @@ const _KubernetesNode = Object.freeze({ Memory: '', Version: '', IPAddress: '', + Api: false, Taints: [], + Port: 0, }); export class KubernetesNode { diff --git a/app/kubernetes/rest/endpoint.js b/app/kubernetes/rest/endpoint.js new file mode 100644 index 000000000..7fce22e41 --- /dev/null +++ b/app/kubernetes/rest/endpoint.js @@ -0,0 +1,20 @@ +angular.module('portainer.kubernetes').factory('KubernetesEndpoints', function KubernetesEndpointsFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) { + 'use strict'; + return function (namespace) { + const url = API_ENDPOINT_ENDPOINTS + '/:endpointId/kubernetes/api/v1' + (namespace ? '/namespaces/:namespace' : '') + '/endpoints/:id'; + return $resource( + url, + { + endpointId: EndpointProvider.endpointID, + namespace: namespace, + }, + { + get: { + method: 'GET', + timeout: 15000, + ignoreLoadingBar: true, + }, + } + ); + }; +}); diff --git a/app/kubernetes/views/cluster/cluster.html b/app/kubernetes/views/cluster/cluster.html index ad95331bf..47a198c6e 100644 --- a/app/kubernetes/views/cluster/cluster.html +++ b/app/kubernetes/views/cluster/cluster.html @@ -51,7 +51,7 @@ -
+
Leader status
@@ -62,7 +62,7 @@ Component Leader node - + {{ ep.Name }} diff --git a/app/kubernetes/views/cluster/clusterController.js b/app/kubernetes/views/cluster/clusterController.js index a8ec935c0..7726834d2 100644 --- a/app/kubernetes/views/cluster/clusterController.js +++ b/app/kubernetes/views/cluster/clusterController.js @@ -51,8 +51,17 @@ class KubernetesClusterController { async getEndpointsAsync() { try { - const endpoints = await this.KubernetesEndpointService.get('kube-system'); - this.endpoints = _.filter(endpoints, (ep) => ep.HolderIdentity); + const endpoints = await this.KubernetesEndpointService.get(); + const systemEndpoints = _.filter(endpoints, { Namespace: 'kube-system' }); + this.systemEndpoints = _.filter(systemEndpoints, (ep) => ep.HolderIdentity); + + const kubernetesEndpoint = _.find(endpoints, { Name: 'kubernetes' }); + if (kubernetesEndpoint && kubernetesEndpoint.Subsets) { + const ips = _.flatten(_.map(kubernetesEndpoint.Subsets, 'Ips')); + _.forEach(this.nodes, (node) => { + node.Api = _.includes(ips, node.IPAddress); + }); + } } catch (err) { this.Notifications.error('Failure', err, 'Unable to retrieve endpoints'); } diff --git a/app/kubernetes/views/cluster/node/node.html b/app/kubernetes/views/cluster/node/node.html index 6fabaaa77..cc503348f 100644 --- a/app/kubernetes/views/cluster/node/node.html +++ b/app/kubernetes/views/cluster/node/node.html @@ -18,7 +18,16 @@ Hostname - {{ ctrl.node.Name }} + + {{ ctrl.node.Name }} + api + + + + + Kubernetes API + + {{ ctrl.node.IPAddress }}:{{ ctrl.node.Port }} Role diff --git a/app/kubernetes/views/cluster/node/nodeController.js b/app/kubernetes/views/cluster/node/nodeController.js index eb4bb7327..1f803965a 100644 --- a/app/kubernetes/views/cluster/node/nodeController.js +++ b/app/kubernetes/views/cluster/node/nodeController.js @@ -6,7 +6,17 @@ import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper'; class KubernetesNodeController { /* @ngInject */ - constructor($async, $state, Notifications, LocalStorage, KubernetesNodeService, KubernetesEventService, KubernetesPodService, KubernetesApplicationService) { + constructor( + $async, + $state, + Notifications, + LocalStorage, + KubernetesNodeService, + KubernetesEventService, + KubernetesPodService, + KubernetesApplicationService, + KubernetesEndpointService + ) { this.$async = $async; this.$state = $state; this.Notifications = Notifications; @@ -15,18 +25,44 @@ class KubernetesNodeController { this.KubernetesEventService = KubernetesEventService; this.KubernetesPodService = KubernetesPodService; this.KubernetesApplicationService = KubernetesApplicationService; + this.KubernetesEndpointService = KubernetesEndpointService; this.onInit = this.onInit.bind(this); this.getNodeAsync = this.getNodeAsync.bind(this); this.getEvents = this.getEvents.bind(this); this.getEventsAsync = this.getEventsAsync.bind(this); this.getApplicationsAsync = this.getApplicationsAsync.bind(this); + this.getEndpointsAsync = this.getEndpointsAsync.bind(this); } selectTab(index) { this.LocalStorage.storeActiveTab('node', index); } + async getEndpointsAsync() { + try { + const endpoints = await this.KubernetesEndpointService.get(); + this.endpoint = _.find(endpoints, { Name: 'kubernetes' }); + if (this.endpoint && this.endpoint.Subsets) { + _.forEach(this.endpoint.Subsets, (subset) => { + return _.forEach(subset.Ips, (ip) => { + if (ip === this.node.IPAddress) { + this.node.Api = true; + this.node.Port = subset.Port; + return false; + } + }); + }); + } + } catch (err) { + this.Notifications.error('Failure', err, 'Unable to retrieve endpoints'); + } + } + + getEndpoints() { + return this.$async(this.getEndpointsAsync); + } + async getNodeAsync() { try { this.state.dataLoading = true; @@ -118,6 +154,7 @@ class KubernetesNodeController { await this.getNode(); await this.getEvents(); await this.getApplications(); + await this.getEndpoints(); this.state.viewReady = true; }