Merge pull request #4415 from portainer/feat/GH/4011-pods-as-applications
feat(k8s/applications): exposed naked pods as applicationspull/4495/head
commit
3d9c10adf1
|
@ -57,7 +57,7 @@
|
||||||
<table class="table table-hover nowrap-cells">
|
<table class="table table-hover nowrap-cells">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th ng-if="!$ctrl.isPod">
|
||||||
<a ng-click="$ctrl.changeOrderBy('PodName')">
|
<a ng-click="$ctrl.changeOrderBy('PodName')">
|
||||||
Pod
|
Pod
|
||||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'PodName' && !$ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'PodName' && !$ctrl.state.reverseOrder"></i>
|
||||||
|
@ -107,7 +107,7 @@
|
||||||
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit: $ctrl.tableKey))"
|
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit: $ctrl.tableKey))"
|
||||||
pagination-id="$ctrl.tableKey"
|
pagination-id="$ctrl.tableKey"
|
||||||
>
|
>
|
||||||
<td>{{ item.PodName }}</td>
|
<td ng-if="!$ctrl.isPod">{{ item.PodName }}</td>
|
||||||
<td>{{ item.Name }}</td>
|
<td>{{ item.Name }}</td>
|
||||||
<td>{{ item.Image }}</td>
|
<td>{{ item.Image }}</td>
|
||||||
<td
|
<td
|
||||||
|
|
|
@ -8,5 +8,6 @@ angular.module('portainer.kubernetes').component('kubernetesContainersDatatable'
|
||||||
tableKey: '@',
|
tableKey: '@',
|
||||||
orderBy: '@',
|
orderBy: '@',
|
||||||
refreshCallback: '<',
|
refreshCallback: '<',
|
||||||
|
isPod: '<',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -151,11 +151,14 @@
|
||||||
>{{ item.Image }} <span ng-if="item.Containers.length > 1">+ {{ item.Containers.length - 1 }}</span></td
|
>{{ item.Image }} <span ng-if="item.Containers.length > 1">+ {{ item.Containers.length - 1 }}</span></td
|
||||||
>
|
>
|
||||||
<td>{{ item.ApplicationType | kubernetesApplicationTypeText }}</td>
|
<td>{{ item.ApplicationType | kubernetesApplicationTypeText }}</td>
|
||||||
<td>
|
<td ng-if="item.ApplicationType !== $ctrl.KubernetesApplicationTypes.POD">
|
||||||
<span ng-if="item.DeploymentType === $ctrl.KubernetesApplicationDeploymentTypes.REPLICATED">Replicated</span>
|
<span ng-if="item.DeploymentType === $ctrl.KubernetesApplicationDeploymentTypes.REPLICATED">Replicated</span>
|
||||||
<span ng-if="item.DeploymentType === $ctrl.KubernetesApplicationDeploymentTypes.GLOBAL">Global</span>
|
<span ng-if="item.DeploymentType === $ctrl.KubernetesApplicationDeploymentTypes.GLOBAL">Global</span>
|
||||||
<code>{{ item.RunningPodsCount }}</code> / <code>{{ item.TotalPodsCount }}</code></td
|
<code>{{ item.RunningPodsCount }}</code> / <code>{{ item.TotalPodsCount }}</code>
|
||||||
>
|
</td>
|
||||||
|
<td ng-if="item.ApplicationType === $ctrl.KubernetesApplicationTypes.POD">
|
||||||
|
{{ item.Pods[0].Status }}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span ng-if="item.PublishedPorts.length">
|
<span ng-if="item.PublishedPorts.length">
|
||||||
<span>
|
<span>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { KubernetesApplicationDeploymentTypes } from 'Kubernetes/models/application/models';
|
import { KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
|
||||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
||||||
|
|
||||||
angular.module('portainer.docker').controller('KubernetesApplicationsDatatableController', [
|
angular.module('portainer.docker').controller('KubernetesApplicationsDatatableController', [
|
||||||
|
@ -42,6 +42,7 @@ angular.module('portainer.docker').controller('KubernetesApplicationsDatatableCo
|
||||||
this.$onInit = function () {
|
this.$onInit = function () {
|
||||||
this.isAdmin = Authentication.isAdmin();
|
this.isAdmin = Authentication.isAdmin();
|
||||||
this.KubernetesApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
|
this.KubernetesApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
|
||||||
|
this.KubernetesApplicationTypes = KubernetesApplicationTypes;
|
||||||
this.setDefaults();
|
this.setDefaults();
|
||||||
this.prepareTableFromDataset();
|
this.prepareTableFromDataset();
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ function _apiPortsToPublishedPorts(pList, pRefs) {
|
||||||
|
|
||||||
class KubernetesApplicationConverter {
|
class KubernetesApplicationConverter {
|
||||||
static applicationCommon(res, data, pods, service, ingresses) {
|
static applicationCommon(res, data, pods, service, ingresses) {
|
||||||
const containers = _.without(_.concat(data.spec.template.spec.containers, data.spec.template.spec.initContainers), undefined);
|
const containers = data.spec.template ? _.without(_.concat(data.spec.template.spec.containers, data.spec.template.spec.initContainers), undefined) : data.spec.containers;
|
||||||
res.Id = data.metadata.uid;
|
res.Id = data.metadata.uid;
|
||||||
res.Name = data.metadata.name;
|
res.Name = data.metadata.name;
|
||||||
res.StackName = data.metadata.labels ? data.metadata.labels[KubernetesPortainerApplicationStackNameLabel] || '-' : '-';
|
res.StackName = data.metadata.labels ? data.metadata.labels[KubernetesPortainerApplicationStackNameLabel] || '-' : '-';
|
||||||
|
@ -61,7 +61,7 @@ class KubernetesApplicationConverter {
|
||||||
res.Image = containers[0].image;
|
res.Image = containers[0].image;
|
||||||
res.CreationDate = data.metadata.creationTimestamp;
|
res.CreationDate = data.metadata.creationTimestamp;
|
||||||
res.Env = _.without(_.flatMap(_.map(containers, 'env')), undefined);
|
res.Env = _.without(_.flatMap(_.map(containers, 'env')), undefined);
|
||||||
res.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods, data);
|
res.Pods = data.spec.selector ? KubernetesApplicationHelper.associatePodsAndApplication(pods, data.spec.selector) : [data];
|
||||||
|
|
||||||
const limits = {
|
const limits = {
|
||||||
Cpu: 0,
|
Cpu: 0,
|
||||||
|
@ -118,7 +118,11 @@ class KubernetesApplicationConverter {
|
||||||
res.PublishedPorts = ports;
|
res.PublishedPorts = ports;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.spec.templates) {
|
||||||
res.Volumes = data.spec.template.spec.volumes ? data.spec.template.spec.volumes : [];
|
res.Volumes = data.spec.template.spec.volumes ? data.spec.template.spec.volumes : [];
|
||||||
|
} else {
|
||||||
|
res.Volumes = data.spec.volumes;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: review
|
// TODO: review
|
||||||
// this if() fixs direct use of PVC reference inside spec.template.spec.containers[0].volumeMounts
|
// this if() fixs direct use of PVC reference inside spec.template.spec.containers[0].volumeMounts
|
||||||
|
@ -169,7 +173,7 @@ class KubernetesApplicationConverter {
|
||||||
res.PersistedFolders = _.without(res.PersistedFolders, undefined);
|
res.PersistedFolders = _.without(res.PersistedFolders, undefined);
|
||||||
|
|
||||||
res.ConfigurationVolumes = _.reduce(
|
res.ConfigurationVolumes = _.reduce(
|
||||||
data.spec.template.spec.volumes,
|
res.Volumes,
|
||||||
(acc, volume) => {
|
(acc, volume) => {
|
||||||
if (volume.configMap || volume.secret) {
|
if (volume.configMap || volume.secret) {
|
||||||
const matchingVolumeMount = _.find(_.flatMap(_.map(containers, 'volumeMounts')), { name: volume.name });
|
const matchingVolumeMount = _.find(_.flatMap(_.map(containers, 'volumeMounts')), { name: volume.name });
|
||||||
|
@ -213,6 +217,13 @@ class KubernetesApplicationConverter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static apiPodToApplication(data, pods, service, ingresses) {
|
||||||
|
const res = new KubernetesApplication();
|
||||||
|
KubernetesApplicationConverter.applicationCommon(res, data, pods, service, ingresses);
|
||||||
|
res.ApplicationType = KubernetesApplicationTypes.POD;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
static apiDeploymentToApplication(data, pods, service, ingresses) {
|
static apiDeploymentToApplication(data, pods, service, ingresses) {
|
||||||
const res = new KubernetesApplication();
|
const res = new KubernetesApplication();
|
||||||
KubernetesApplicationConverter.applicationCommon(res, data, pods, service, ingresses);
|
KubernetesApplicationConverter.applicationCommon(res, data, pods, service, ingresses);
|
||||||
|
@ -310,7 +321,7 @@ class KubernetesApplicationConverter {
|
||||||
} else if (daemonSet) {
|
} else if (daemonSet) {
|
||||||
app = KubernetesDaemonSetConverter.applicationFormValuesToDaemonSet(formValues, claims);
|
app = KubernetesDaemonSetConverter.applicationFormValuesToDaemonSet(formValues, claims);
|
||||||
} else {
|
} else {
|
||||||
throw new PortainerError('Unable to determine which association to use');
|
throw new PortainerError('Unable to determine which association to use to convert form');
|
||||||
}
|
}
|
||||||
|
|
||||||
let headlessService;
|
let headlessService;
|
||||||
|
|
|
@ -57,6 +57,8 @@ angular
|
||||||
return KubernetesApplicationTypeStrings.DAEMONSET;
|
return KubernetesApplicationTypeStrings.DAEMONSET;
|
||||||
case KubernetesApplicationTypes.STATEFULSET:
|
case KubernetesApplicationTypes.STATEFULSET:
|
||||||
return KubernetesApplicationTypeStrings.STATEFULSET;
|
return KubernetesApplicationTypeStrings.STATEFULSET;
|
||||||
|
case KubernetesApplicationTypes.POD:
|
||||||
|
return KubernetesApplicationTypeStrings.POD;
|
||||||
default:
|
default:
|
||||||
return '-';
|
return '-';
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,8 @@ class KubernetesApplicationHelper {
|
||||||
return !application.ApplicationOwner;
|
return !application.ApplicationOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
static associatePodsAndApplication(pods, app) {
|
static associatePodsAndApplication(pods, selector) {
|
||||||
return _.filter(pods, { Labels: app.spec.selector.matchLabels });
|
return _.filter(pods, ['metadata.labels', selector.matchLabels]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static associateContainerPersistedFoldersAndConfigurations(app, containers) {
|
static associateContainerPersistedFoldersAndConfigurations(app, containers) {
|
||||||
|
|
|
@ -20,7 +20,7 @@ class KubernetesApplicationRollbackHelper {
|
||||||
result = KubernetesApplicationRollbackHelper._getStatefulSetPayload(application, targetRevision);
|
result = KubernetesApplicationRollbackHelper._getStatefulSetPayload(application, targetRevision);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new PortainerError('Unable to determine which association to use');
|
throw new PortainerError('Unable to determine which association to use to convert patch');
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ class KubernetesHistoryHelper {
|
||||||
[currentRevision, revisionsList] = KubernetesHistoryHelper._getStatefulSetRevisions(rawRevisions, application.Raw);
|
[currentRevision, revisionsList] = KubernetesHistoryHelper._getStatefulSetRevisions(rawRevisions, application.Raw);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new PortainerError('Unable to determine which association to use');
|
throw new PortainerError('Unable to determine which association to use to get revisions');
|
||||||
}
|
}
|
||||||
revisionsList = _.sortBy(revisionsList, 'revision');
|
revisionsList = _.sortBy(revisionsList, 'revision');
|
||||||
return [currentRevision, revisionsList];
|
return [currentRevision, revisionsList];
|
||||||
|
|
|
@ -7,6 +7,9 @@ class KubernetesServiceHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
static findApplicationBoundService(services, rawApp) {
|
static findApplicationBoundService(services, rawApp) {
|
||||||
|
if (!rawApp.spec.template) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
return _.find(services, (item) => item.spec.selector && _.isMatch(rawApp.spec.template.metadata.labels, item.spec.selector));
|
return _.find(services, (item) => item.spec.selector && _.isMatch(rawApp.spec.template.metadata.labels, item.spec.selector));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,8 @@ export class KubernetesHorizontalPodAutoScalerHelper {
|
||||||
return KubernetesApplicationTypeStrings.DAEMONSET;
|
return KubernetesApplicationTypeStrings.DAEMONSET;
|
||||||
} else if ((app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.STATEFULSET) || app instanceof KubernetesStatefulSet) {
|
} else if ((app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.STATEFULSET) || app instanceof KubernetesStatefulSet) {
|
||||||
return KubernetesApplicationTypeStrings.STATEFULSET;
|
return KubernetesApplicationTypeStrings.STATEFULSET;
|
||||||
// } else if () { ---> TODO: refactor - handle bare pod type !
|
} else if (app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.POD) {
|
||||||
|
return KubernetesApplicationTypeStrings.POD;
|
||||||
} else {
|
} else {
|
||||||
throw new PortainerError('Unable to determine application type');
|
throw new PortainerError('Unable to determine application type');
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,14 @@ export const KubernetesApplicationTypes = Object.freeze({
|
||||||
DEPLOYMENT: 1,
|
DEPLOYMENT: 1,
|
||||||
DAEMONSET: 2,
|
DAEMONSET: 2,
|
||||||
STATEFULSET: 3,
|
STATEFULSET: 3,
|
||||||
|
POD: 4,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const KubernetesApplicationTypeStrings = Object.freeze({
|
export const KubernetesApplicationTypeStrings = Object.freeze({
|
||||||
DEPLOYMENT: 'Deployment',
|
DEPLOYMENT: 'Deployment',
|
||||||
DAEMONSET: 'DaemonSet',
|
DAEMONSET: 'DaemonSet',
|
||||||
STATEFULSET: 'StatefulSet',
|
STATEFULSET: 'StatefulSet',
|
||||||
|
POD: 'Pod',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const KubernetesApplicationPublishingTypes = Object.freeze({
|
export const KubernetesApplicationPublishingTypes = Object.freeze({
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* Generic params
|
* Generic params
|
||||||
*/
|
*/
|
||||||
const _KubernetesCommonParams = Object.freeze({
|
export function KubernetesCommonParams() {
|
||||||
|
return {
|
||||||
id: '',
|
id: '',
|
||||||
});
|
};
|
||||||
export class KubernetesCommonParams {
|
|
||||||
constructor() {
|
|
||||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesCommonParams)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import _ from 'lodash-es';
|
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import PortainerError from 'Portainer/error';
|
import PortainerError from 'Portainer/error';
|
||||||
|
|
||||||
import { KubernetesCommonParams } from 'Kubernetes/models/common/params';
|
import { KubernetesCommonParams } from 'Kubernetes/models/common/params';
|
||||||
import KubernetesPodConverter from 'Kubernetes/pod/converter';
|
|
||||||
|
|
||||||
class KubernetesPodService {
|
class KubernetesPodService {
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
|
@ -11,23 +9,43 @@ class KubernetesPodService {
|
||||||
this.$async = $async;
|
this.$async = $async;
|
||||||
this.KubernetesPods = KubernetesPods;
|
this.KubernetesPods = KubernetesPods;
|
||||||
|
|
||||||
|
this.getAsync = this.getAsync.bind(this);
|
||||||
this.getAllAsync = this.getAllAsync.bind(this);
|
this.getAllAsync = this.getAllAsync.bind(this);
|
||||||
this.logsAsync = this.logsAsync.bind(this);
|
this.logsAsync = this.logsAsync.bind(this);
|
||||||
this.deleteAsync = this.deleteAsync.bind(this);
|
this.deleteAsync = this.deleteAsync.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAsync(namespace, name) {
|
||||||
|
try {
|
||||||
|
const params = new KubernetesCommonParams();
|
||||||
|
params.id = name;
|
||||||
|
const [raw, yaml] = await Promise.all([this.KubernetesPods(namespace).get(params).$promise, this.KubernetesPods(namespace).getYaml(params).$promise]);
|
||||||
|
const res = {
|
||||||
|
Raw: raw,
|
||||||
|
Yaml: yaml.data,
|
||||||
|
};
|
||||||
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
throw new PortainerError('Unable to retrieve pod', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET ALL
|
* GET ALL
|
||||||
*/
|
*/
|
||||||
async getAllAsync(namespace) {
|
async getAllAsync(namespace) {
|
||||||
try {
|
try {
|
||||||
const data = await this.KubernetesPods(namespace).get().$promise;
|
const data = await this.KubernetesPods(namespace).get().$promise;
|
||||||
return _.map(data.items, (item) => KubernetesPodConverter.apiToModel(item));
|
return data.items;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new PortainerError('Unable to retrieve pods', err);
|
throw new PortainerError('Unable to retrieve pods', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get(namespace) {
|
get(namespace, name) {
|
||||||
|
if (name) {
|
||||||
|
return this.$async(this.getAsync, namespace, name);
|
||||||
|
}
|
||||||
return this.$async(this.getAllAsync, namespace);
|
return this.$async(this.getAllAsync, namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import KubernetesServiceHelper from 'Kubernetes/helpers/serviceHelper';
|
||||||
import { KubernetesHorizontalPodAutoScalerHelper } from 'Kubernetes/horizontal-pod-auto-scaler/helper';
|
import { KubernetesHorizontalPodAutoScalerHelper } from 'Kubernetes/horizontal-pod-auto-scaler/helper';
|
||||||
import { KubernetesHorizontalPodAutoScalerConverter } from 'Kubernetes/horizontal-pod-auto-scaler/converter';
|
import { KubernetesHorizontalPodAutoScalerConverter } from 'Kubernetes/horizontal-pod-auto-scaler/converter';
|
||||||
import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
|
import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
|
||||||
|
import KubernetesPodConverter from 'Kubernetes/pod/converter';
|
||||||
|
|
||||||
class KubernetesApplicationService {
|
class KubernetesApplicationService {
|
||||||
/* #region CONSTRUCTOR */
|
/* #region CONSTRUCTOR */
|
||||||
|
@ -71,7 +72,7 @@ class KubernetesApplicationService {
|
||||||
} else if (app instanceof KubernetesStatefulSet || (app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.STATEFULSET)) {
|
} else if (app instanceof KubernetesStatefulSet || (app instanceof KubernetesApplication && app.ApplicationType === KubernetesApplicationTypes.STATEFULSET)) {
|
||||||
apiService = this.KubernetesStatefulSetService;
|
apiService = this.KubernetesStatefulSetService;
|
||||||
} else {
|
} else {
|
||||||
throw new PortainerError('Unable to determine which association to use');
|
throw new PortainerError('Unable to determine which association to use to retrieve API Service');
|
||||||
}
|
}
|
||||||
return apiService;
|
return apiService;
|
||||||
}
|
}
|
||||||
|
@ -87,15 +88,18 @@ class KubernetesApplicationService {
|
||||||
/* #region GET */
|
/* #region GET */
|
||||||
async getAsync(namespace, name) {
|
async getAsync(namespace, name) {
|
||||||
try {
|
try {
|
||||||
const [deployment, daemonSet, statefulSet, pods, autoScalers, ingresses] = await Promise.allSettled([
|
const [deployment, daemonSet, statefulSet, pod, pods, autoScalers, ingresses] = await Promise.allSettled([
|
||||||
this.KubernetesDeploymentService.get(namespace, name),
|
this.KubernetesDeploymentService.get(namespace, name),
|
||||||
this.KubernetesDaemonSetService.get(namespace, name),
|
this.KubernetesDaemonSetService.get(namespace, name),
|
||||||
this.KubernetesStatefulSetService.get(namespace, name),
|
this.KubernetesStatefulSetService.get(namespace, name),
|
||||||
|
this.KubernetesPodService.get(namespace, name),
|
||||||
this.KubernetesPodService.get(namespace),
|
this.KubernetesPodService.get(namespace),
|
||||||
this.KubernetesHorizontalPodAutoScalerService.get(namespace),
|
this.KubernetesHorizontalPodAutoScalerService.get(namespace),
|
||||||
this.KubernetesIngressService.get(namespace),
|
this.KubernetesIngressService.get(namespace),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// const pod = _.find(pods.value, ['metadata.namespace', namespace, 'metadata.name', name]);
|
||||||
|
|
||||||
let rootItem;
|
let rootItem;
|
||||||
let converterFunc;
|
let converterFunc;
|
||||||
if (deployment.status === 'fulfilled') {
|
if (deployment.status === 'fulfilled') {
|
||||||
|
@ -107,8 +111,11 @@ class KubernetesApplicationService {
|
||||||
} else if (statefulSet.status === 'fulfilled') {
|
} else if (statefulSet.status === 'fulfilled') {
|
||||||
rootItem = statefulSet;
|
rootItem = statefulSet;
|
||||||
converterFunc = KubernetesApplicationConverter.apiStatefulSetToapplication;
|
converterFunc = KubernetesApplicationConverter.apiStatefulSetToapplication;
|
||||||
|
} else if (pod.status === 'fulfilled') {
|
||||||
|
rootItem = pod;
|
||||||
|
converterFunc = KubernetesApplicationConverter.apiPodToApplication;
|
||||||
} else {
|
} else {
|
||||||
throw new PortainerError('Unable to determine which association to use');
|
throw new PortainerError('Unable to determine which association to use to convert application');
|
||||||
}
|
}
|
||||||
|
|
||||||
const services = await this.KubernetesServiceService.get(namespace);
|
const services = await this.KubernetesServiceService.get(namespace);
|
||||||
|
@ -118,6 +125,7 @@ class KubernetesApplicationService {
|
||||||
const application = converterFunc(rootItem.value.Raw, pods.value, service.Raw, ingresses.value);
|
const application = converterFunc(rootItem.value.Raw, pods.value, service.Raw, ingresses.value);
|
||||||
application.Yaml = rootItem.value.Yaml;
|
application.Yaml = rootItem.value.Yaml;
|
||||||
application.Raw = rootItem.value.Raw;
|
application.Raw = rootItem.value.Raw;
|
||||||
|
application.Pods = _.map(application.Pods, (item) => KubernetesPodConverter.apiToModel(item));
|
||||||
application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
|
application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
|
||||||
|
|
||||||
const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers.value, application);
|
const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers.value, application);
|
||||||
|
@ -173,7 +181,14 @@ class KubernetesApplicationService {
|
||||||
convertToApplication(item, KubernetesApplicationConverter.apiStatefulSetToapplication, services, pods, ingresses)
|
convertToApplication(item, KubernetesApplicationConverter.apiStatefulSetToapplication, services, pods, ingresses)
|
||||||
);
|
);
|
||||||
|
|
||||||
const applications = _.concat(deploymentApplications, daemonSetApplications, statefulSetApplications);
|
const boundPods = _.concat(_.flatMap(deploymentApplications, 'Pods'), _.flatMap(daemonSetApplications, 'Pods'), _.flatMap(statefulSetApplications, 'Pods'));
|
||||||
|
const unboundPods = _.without(pods, ...boundPods);
|
||||||
|
const nakedPodsApplications = _.map(unboundPods, (item) => convertToApplication(item, KubernetesApplicationConverter.apiPodToApplication, services, pods, ingresses));
|
||||||
|
|
||||||
|
const applications = _.concat(deploymentApplications, daemonSetApplications, statefulSetApplications, nakedPodsApplications);
|
||||||
|
_.forEach(applications, (app) => {
|
||||||
|
app.Pods = _.map(app.Pods, (item) => KubernetesPodConverter.apiToModel(item));
|
||||||
|
});
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
_.forEach(applications, async (application) => {
|
_.forEach(applications, async (application) => {
|
||||||
const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers, application);
|
const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers, application);
|
||||||
|
|
|
@ -32,13 +32,17 @@ class KubernetesHistoryService {
|
||||||
case KubernetesApplicationTypes.STATEFULSET:
|
case KubernetesApplicationTypes.STATEFULSET:
|
||||||
rawRevisions = await this.KubernetesControllerRevisionService.get(namespace);
|
rawRevisions = await this.KubernetesControllerRevisionService.get(namespace);
|
||||||
break;
|
break;
|
||||||
|
case KubernetesApplicationTypes.POD:
|
||||||
|
rawRevisions = [];
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new PortainerError('Unable to determine which association to use');
|
throw new PortainerError('Unable to determine which association to use for history');
|
||||||
}
|
}
|
||||||
|
if (rawRevisions.length) {
|
||||||
const [currentRevision, revisionsList] = KubernetesHistoryHelper.getRevisions(rawRevisions, application);
|
const [currentRevision, revisionsList] = KubernetesHistoryHelper.getRevisions(rawRevisions, application);
|
||||||
application.CurrentRevision = currentRevision;
|
application.CurrentRevision = currentRevision;
|
||||||
application.Revisions = revisionsList;
|
application.Revisions = revisionsList;
|
||||||
|
}
|
||||||
return application;
|
return application;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new PortainerError('', err);
|
throw new PortainerError('', err);
|
||||||
|
|
|
@ -43,16 +43,21 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Status</td>
|
<td>Status</td>
|
||||||
<td>
|
<td ng-if="ctrl.application.ApplicationType !== ctrl.KubernetesApplicationTypes.POD">
|
||||||
<span ng-if="ctrl.application.DeploymentType === ctrl.KubernetesApplicationDeploymentTypes.REPLICATED">Replicated</span>
|
<span ng-if="ctrl.application.DeploymentType === ctrl.KubernetesApplicationDeploymentTypes.REPLICATED">Replicated</span>
|
||||||
<span ng-if="ctrl.application.DeploymentType === ctrl.KubernetesApplicationDeploymentTypes.GLOBAL">Global</span>
|
<span ng-if="ctrl.application.DeploymentType === ctrl.KubernetesApplicationDeploymentTypes.GLOBAL">Global</span>
|
||||||
<code>{{ ctrl.application.RunningPodsCount }}</code> / <code>{{ ctrl.application.TotalPodsCount }}</code>
|
<code>{{ ctrl.application.RunningPodsCount }}</code> / <code>{{ ctrl.application.TotalPodsCount }}</code>
|
||||||
</td>
|
</td>
|
||||||
|
<td ng-if="ctrl.application.ApplicationType === ctrl.KubernetesApplicationTypes.POD">
|
||||||
|
{{ ctrl.application.Pods[0].Status }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-if="ctrl.application.Requests.Cpu || ctrl.application.Requests.Memory">
|
<tr ng-if="ctrl.application.Requests.Cpu || ctrl.application.Requests.Memory">
|
||||||
<td>
|
<td>
|
||||||
<div>Resource reservations</div>
|
<div>Resource reservations</div>
|
||||||
<div class="text-muted small">per instance</div>
|
<div ng-if="ctrl.application.ApplicationType !== ctrl.KubernetesApplicationTypes.POD" class="text-muted small">
|
||||||
|
per instance
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div ng-if="ctrl.application.Requests.Cpu">CPU {{ ctrl.application.Requests.Cpu | kubernetesApplicationCPUValue }}</div>
|
<div ng-if="ctrl.application.Requests.Cpu">CPU {{ ctrl.application.Requests.Cpu | kubernetesApplicationCPUValue }}</div>
|
||||||
|
@ -557,7 +562,8 @@
|
||||||
title-icon="fa-server"
|
title-icon="fa-server"
|
||||||
dataset="ctrl.allContainers"
|
dataset="ctrl.allContainers"
|
||||||
table-key="kubernetes.application.containers"
|
table-key="kubernetes.application.containers"
|
||||||
order-by="PodName"
|
is-pod="ctrl.application.ApplicationType === ctrl.KubernetesApplicationTypes.POD"
|
||||||
|
order-by="{{ ctrl.application.ApplicationType === ctrl.KubernetesApplicationTypes.POD ? 'Name' : 'PodName' }}"
|
||||||
>
|
>
|
||||||
</kubernetes-containers-datatable>
|
</kubernetes-containers-datatable>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import * as _ from 'lodash-es';
|
import * as _ from 'lodash-es';
|
||||||
import * as JsonPatch from 'fast-json-patch';
|
import * as JsonPatch from 'fast-json-patch';
|
||||||
import { KubernetesApplicationDataAccessPolicies, KubernetesApplicationDeploymentTypes } from 'Kubernetes/models/application/models';
|
import { KubernetesApplicationDataAccessPolicies, KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
|
||||||
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
||||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
||||||
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
|
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
|
||||||
|
@ -123,6 +123,8 @@ class KubernetesApplicationController {
|
||||||
|
|
||||||
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
||||||
|
|
||||||
|
this.KubernetesApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
|
||||||
|
this.KubernetesApplicationTypes = KubernetesApplicationTypes;
|
||||||
this.ApplicationDataAccessPolicies = KubernetesApplicationDataAccessPolicies;
|
this.ApplicationDataAccessPolicies = KubernetesApplicationDataAccessPolicies;
|
||||||
this.KubernetesServiceTypes = KubernetesServiceTypes;
|
this.KubernetesServiceTypes = KubernetesServiceTypes;
|
||||||
this.KubernetesPodContainerTypes = KubernetesPodContainerTypes;
|
this.KubernetesPodContainerTypes = KubernetesPodContainerTypes;
|
||||||
|
@ -340,7 +342,6 @@ class KubernetesApplicationController {
|
||||||
SelectedRevision: undefined,
|
SelectedRevision: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.KubernetesApplicationDeploymentTypes = KubernetesApplicationDeploymentTypes;
|
|
||||||
await this.getApplication();
|
await this.getApplication();
|
||||||
await this.getEvents();
|
await this.getEvents();
|
||||||
this.state.viewReady = true;
|
this.state.viewReady = true;
|
||||||
|
|
|
@ -61,7 +61,7 @@ class InitEndpointController {
|
||||||
case PortainerEndpointConnectionTypes.AGENT:
|
case PortainerEndpointConnectionTypes.AGENT:
|
||||||
return this.createAgentEndpoint();
|
return this.createAgentEndpoint();
|
||||||
default:
|
default:
|
||||||
this.Notifications.error('Failure', 'Unable to determine which action to do');
|
this.Notifications.error('Failure', 'Unable to determine which action to do to create endpoint');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue