mirror of https://github.com/portainer/portainer
feat(k8s/application): Support multi-container pods applications (#4208)
* feat(application): Support multi-container pods applications * feat(application): Support multi-container pods applications * fix(application): use only one pod in app details and fix logs and console links * fix(application): show all containers in containers datatable * fix(application): fix order by pod name * feat(k8s/application): minor UI update * feat(k8s/application): minor UI update * feat(k8s/application): minor UI update * feat(k8s/application): minor UI update * feat(k8s/application): minor UI update * fix(application): fix persisted folders in application details Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>pull/4210/head
parent
fe4a80c7bd
commit
00389a7da9
|
@ -77,7 +77,7 @@ angular.module('portainer.kubernetes', ['portainer.app']).config([
|
||||||
|
|
||||||
const applicationConsole = {
|
const applicationConsole = {
|
||||||
name: 'kubernetes.applications.application.console',
|
name: 'kubernetes.applications.application.console',
|
||||||
url: '/:pod/console',
|
url: '/:pod/:container/console',
|
||||||
views: {
|
views: {
|
||||||
'content@': {
|
'content@': {
|
||||||
component: 'kubernetesApplicationConsoleView',
|
component: 'kubernetesApplicationConsoleView',
|
||||||
|
@ -87,7 +87,7 @@ angular.module('portainer.kubernetes', ['portainer.app']).config([
|
||||||
|
|
||||||
const applicationLogs = {
|
const applicationLogs = {
|
||||||
name: 'kubernetes.applications.application.logs',
|
name: 'kubernetes.applications.application.logs',
|
||||||
url: '/:pod/logs',
|
url: '/:pod/:container/logs',
|
||||||
views: {
|
views: {
|
||||||
'content@': {
|
'content@': {
|
||||||
component: 'kubernetesApplicationLogsView',
|
component: 'kubernetesApplicationLogsView',
|
||||||
|
|
|
@ -65,10 +65,17 @@
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<a ng-click="$ctrl.changeOrderBy('Images')">
|
<a ng-click="$ctrl.changeOrderBy('PodName')">
|
||||||
|
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-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'PodName' && $ctrl.state.reverseOrder"></i>
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<a ng-click="$ctrl.changeOrderBy('Image')">
|
||||||
Image
|
Image
|
||||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Images' && !$ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && !$ctrl.state.reverseOrder"></i>
|
||||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Images' && $ctrl.state.reverseOrder"></i>
|
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Image' && $ctrl.state.reverseOrder"></i>
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
|
@ -101,9 +108,8 @@
|
||||||
pagination-id="$ctrl.tableKey"
|
pagination-id="$ctrl.tableKey"
|
||||||
>
|
>
|
||||||
<td>{{ item.Name }}</td>
|
<td>{{ item.Name }}</td>
|
||||||
<td
|
<td>{{ item.PodName }}</td>
|
||||||
><span ng-repeat="image in item.Images track by $index">{{ image }}<br /></span
|
<td>{{ item.Image }}</td>
|
||||||
></td>
|
|
||||||
<td
|
<td
|
||||||
><span class="label label-{{ item.Status | kubernetesPodStatusColor }}">{{ item.Status }}</span></td
|
><span class="label label-{{ item.Status | kubernetesPodStatusColor }}">{{ item.Status }}</span></td
|
||||||
>
|
>
|
||||||
|
@ -117,8 +123,8 @@
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.CreationDate | getisodate }}</td>
|
<td>{{ item.CreationDate | getisodate }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a ui-sref="kubernetes.applications.application.logs({ pod: item.Name })"> <i class="fa fa-file-alt" aria-hidden="true"></i> Logs </a>
|
<a ui-sref="kubernetes.applications.application.logs({ pod: item.PodName, container: item.Name })"> <i class="fa fa-file-alt" aria-hidden="true"></i> Logs </a>
|
||||||
<a ng-if="item.Status === 'Running'" ui-sref="kubernetes.applications.application.console({ pod: item.Name })" style="margin-left: 10px;">
|
<a ng-if="item.Status === 'Running'" ui-sref="kubernetes.applications.application.console({ pod: item.PodName, container: item.Name })" style="margin-left: 10px;">
|
||||||
<i class="fa fa-terminal" aria-hidden="true"></i> Console
|
<i class="fa fa-terminal" aria-hidden="true"></i> Console
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
|
@ -1,5 +1,5 @@
|
||||||
angular.module('portainer.kubernetes').component('kubernetesPodsDatatable', {
|
angular.module('portainer.kubernetes').component('kubernetesContainersDatatable', {
|
||||||
templateUrl: './podsDatatable.html',
|
templateUrl: './containersDatatable.html',
|
||||||
controller: 'GenericDatatableController',
|
controller: 'GenericDatatableController',
|
||||||
bindings: {
|
bindings: {
|
||||||
titleText: '@',
|
titleText: '@',
|
|
@ -147,7 +147,9 @@
|
||||||
<td>
|
<td>
|
||||||
<a ui-sref="kubernetes.resourcePools.resourcePool({ id: item.ResourcePool })">{{ item.ResourcePool }}</a>
|
<a ui-sref="kubernetes.resourcePools.resourcePool({ id: item.ResourcePool })">{{ item.ResourcePool }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.Image }}</td>
|
<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>
|
||||||
<span ng-if="item.DeploymentType === $ctrl.KubernetesApplicationDeploymentTypes.REPLICATED">Replicated</span>
|
<span ng-if="item.DeploymentType === $ctrl.KubernetesApplicationDeploymentTypes.REPLICATED">Replicated</span>
|
||||||
|
|
|
@ -118,7 +118,9 @@
|
||||||
<td>
|
<td>
|
||||||
<a ui-sref="kubernetes.resourcePools.resourcePool({ id: item.ResourcePool })">{{ item.ResourcePool }}</a>
|
<a ui-sref="kubernetes.resourcePools.resourcePool({ id: item.ResourcePool })">{{ item.ResourcePool }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.Image }}</td>
|
<td
|
||||||
|
>{{ item.Image }} <span ng-if="item.Containers.length > 1">+ {{ item.Containers.length - 1 }}</span></td
|
||||||
|
>
|
||||||
<td>{{ item.CPU | kubernetesApplicationCPUValue }}</td>
|
<td>{{ item.CPU | kubernetesApplicationCPUValue }}</td>
|
||||||
<td>{{ item.Memory | humansize }}</td>
|
<td>{{ item.Memory | humansize }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -107,7 +107,9 @@
|
||||||
<span style="margin-left: 5px;" class="label label-primary image-tag" ng-if="$ctrl.isExternalApplication(item)">external</span>
|
<span style="margin-left: 5px;" class="label label-primary image-tag" ng-if="$ctrl.isExternalApplication(item)">external</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.StackName }}</td>
|
<td>{{ item.StackName }}</td>
|
||||||
<td>{{ item.Image }}</td>
|
<td
|
||||||
|
>{{ item.Image }} <span ng-if="item.Containers.length > 1">+ {{ item.Containers.length - 1 }}</span></td
|
||||||
|
>
|
||||||
<td>{{ item.CPU | kubernetesApplicationCPUValue }}</td>
|
<td>{{ item.CPU | kubernetesApplicationCPUValue }}</td>
|
||||||
<td>{{ item.Memory | humansize }}</td>
|
<td>{{ item.Memory | humansize }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -50,6 +50,7 @@ function _apiPortsToPublishedPorts(pList, pRefs) {
|
||||||
|
|
||||||
class KubernetesApplicationConverter {
|
class KubernetesApplicationConverter {
|
||||||
static applicationCommon(res, data, service, ingresses) {
|
static applicationCommon(res, data, service, ingresses) {
|
||||||
|
const containers = _.without(_.concat(data.spec.template.spec.containers, data.spec.template.spec.initContainers), undefined);
|
||||||
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] || '-' : '-';
|
||||||
|
@ -57,16 +58,16 @@ class KubernetesApplicationConverter {
|
||||||
res.Note = data.metadata.annotations ? data.metadata.annotations[KubernetesPortainerApplicationNote] || '' : '';
|
res.Note = data.metadata.annotations ? data.metadata.annotations[KubernetesPortainerApplicationNote] || '' : '';
|
||||||
res.ApplicationName = data.metadata.labels ? data.metadata.labels[KubernetesPortainerApplicationNameLabel] || res.Name : res.Name;
|
res.ApplicationName = data.metadata.labels ? data.metadata.labels[KubernetesPortainerApplicationNameLabel] || res.Name : res.Name;
|
||||||
res.ResourcePool = data.metadata.namespace;
|
res.ResourcePool = data.metadata.namespace;
|
||||||
res.Image = data.spec.template.spec.containers[0].image;
|
res.Image = containers[0].image;
|
||||||
res.CreationDate = data.metadata.creationTimestamp;
|
res.CreationDate = data.metadata.creationTimestamp;
|
||||||
res.Pods = data.Pods;
|
res.Env = _.without(_.flatMap(_.map(containers, 'env')), undefined);
|
||||||
res.Env = data.spec.template.spec.containers[0].env;
|
|
||||||
const limits = {
|
const limits = {
|
||||||
Cpu: 0,
|
Cpu: 0,
|
||||||
Memory: 0,
|
Memory: 0,
|
||||||
};
|
};
|
||||||
res.Limits = _.reduce(
|
res.Limits = _.reduce(
|
||||||
data.spec.template.spec.containers,
|
containers,
|
||||||
(acc, item) => {
|
(acc, item) => {
|
||||||
if (item.resources.limits && item.resources.limits.cpu) {
|
if (item.resources.limits && item.resources.limits.cpu) {
|
||||||
acc.Cpu += KubernetesResourceReservationHelper.parseCPU(item.resources.limits.cpu);
|
acc.Cpu += KubernetesResourceReservationHelper.parseCPU(item.resources.limits.cpu);
|
||||||
|
@ -84,7 +85,7 @@ class KubernetesApplicationConverter {
|
||||||
Memory: 0,
|
Memory: 0,
|
||||||
};
|
};
|
||||||
res.Requests = _.reduce(
|
res.Requests = _.reduce(
|
||||||
data.spec.template.spec.containers,
|
containers,
|
||||||
(acc, item) => {
|
(acc, item) => {
|
||||||
if (item.resources.requests && item.resources.requests.cpu) {
|
if (item.resources.requests && item.resources.requests.cpu) {
|
||||||
acc.Cpu += KubernetesResourceReservationHelper.parseCPU(item.resources.requests.cpu);
|
acc.Cpu += KubernetesResourceReservationHelper.parseCPU(item.resources.requests.cpu);
|
||||||
|
@ -109,7 +110,7 @@ class KubernetesApplicationConverter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const portsRefs = _.concat(..._.map(data.spec.template.spec.containers, (container) => container.ports));
|
const portsRefs = _.concat(..._.map(containers, (container) => container.ports));
|
||||||
const ports = _apiPortsToPublishedPorts(service.spec.ports, portsRefs);
|
const ports = _apiPortsToPublishedPorts(service.spec.ports, portsRefs);
|
||||||
const rules = KubernetesIngressHelper.findSBoundServiceIngressesRules(ingresses, service.metadata.name);
|
const rules = KubernetesIngressHelper.findSBoundServiceIngressesRules(ingresses, service.metadata.name);
|
||||||
_.forEach(ports, (port) => (port.IngressRules = _.filter(rules, (rule) => rule.Port === port.Port)));
|
_.forEach(ports, (port) => (port.IngressRules = _.filter(rules, (rule) => rule.Port === port.Port)));
|
||||||
|
@ -147,7 +148,8 @@ class KubernetesApplicationConverter {
|
||||||
const persistedFolders = _.filter(res.Volumes, (volume) => volume.persistentVolumeClaim || volume.hostPath);
|
const persistedFolders = _.filter(res.Volumes, (volume) => volume.persistentVolumeClaim || volume.hostPath);
|
||||||
|
|
||||||
res.PersistedFolders = _.map(persistedFolders, (volume) => {
|
res.PersistedFolders = _.map(persistedFolders, (volume) => {
|
||||||
const matchingVolumeMount = _.find(data.spec.template.spec.containers[0].volumeMounts, { name: volume.name });
|
const volumeMounts = _.uniq(_.flatMap(_.map(containers, 'volumeMounts')), 'name');
|
||||||
|
const matchingVolumeMount = _.find(volumeMounts, { name: volume.name });
|
||||||
|
|
||||||
if (matchingVolumeMount) {
|
if (matchingVolumeMount) {
|
||||||
const persistedFolder = new KubernetesApplicationPersistedFolder();
|
const persistedFolder = new KubernetesApplicationPersistedFolder();
|
||||||
|
@ -169,7 +171,7 @@ class KubernetesApplicationConverter {
|
||||||
data.spec.template.spec.volumes,
|
data.spec.template.spec.volumes,
|
||||||
(acc, volume) => {
|
(acc, volume) => {
|
||||||
if (volume.configMap || volume.secret) {
|
if (volume.configMap || volume.secret) {
|
||||||
const matchingVolumeMount = _.find(data.spec.template.spec.containers[0].volumeMounts, { name: volume.name });
|
const matchingVolumeMount = _.find(_.flatMap(_.map(containers, 'volumeMounts')), { name: volume.name });
|
||||||
|
|
||||||
if (matchingVolumeMount) {
|
if (matchingVolumeMount) {
|
||||||
let items = [];
|
let items = [];
|
||||||
|
@ -262,6 +264,7 @@ class KubernetesApplicationConverter {
|
||||||
res.Configurations = KubernetesApplicationHelper.generateConfigurationFormValuesFromEnvAndVolumes(app.Env, app.ConfigurationVolumes, configurations);
|
res.Configurations = KubernetesApplicationHelper.generateConfigurationFormValuesFromEnvAndVolumes(app.Env, app.ConfigurationVolumes, configurations);
|
||||||
res.AutoScaler = KubernetesApplicationHelper.generateAutoScalerFormValueFromHorizontalPodAutoScaler(app.AutoScaler, res.ReplicaCount);
|
res.AutoScaler = KubernetesApplicationHelper.generateAutoScalerFormValueFromHorizontalPodAutoScaler(app.AutoScaler, res.ReplicaCount);
|
||||||
res.PublishedPorts = KubernetesApplicationHelper.generatePublishedPortsFormValuesFromPublishedPorts(app.ServiceType, app.PublishedPorts);
|
res.PublishedPorts = KubernetesApplicationHelper.generatePublishedPortsFormValuesFromPublishedPorts(app.ServiceType, app.PublishedPorts);
|
||||||
|
res.Containers = app.Containers;
|
||||||
|
|
||||||
const isIngress = _.filter(res.PublishedPorts, (p) => p.IngressName).length;
|
const isIngress = _.filter(res.PublishedPorts, (p) => p.IngressName).length;
|
||||||
if (app.ServiceType === KubernetesServiceTypes.LOAD_BALANCER) {
|
if (app.ServiceType === KubernetesServiceTypes.LOAD_BALANCER) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ class KubernetesDeploymentConverter {
|
||||||
res.CpuLimit = formValues.CpuLimit;
|
res.CpuLimit = formValues.CpuLimit;
|
||||||
res.MemoryLimit = KubernetesResourceReservationHelper.bytesValue(formValues.MemoryLimit);
|
res.MemoryLimit = KubernetesResourceReservationHelper.bytesValue(formValues.MemoryLimit);
|
||||||
res.Env = KubernetesApplicationHelper.generateEnvFromEnvVariables(formValues.EnvironmentVariables);
|
res.Env = KubernetesApplicationHelper.generateEnvFromEnvVariables(formValues.EnvironmentVariables);
|
||||||
|
res.Containers = formValues.Containers;
|
||||||
KubernetesApplicationHelper.generateVolumesFromPersistentVolumClaims(res, volumeClaims);
|
KubernetesApplicationHelper.generateVolumesFromPersistentVolumClaims(res, volumeClaims);
|
||||||
KubernetesApplicationHelper.generateEnvOrVolumesFromConfigurations(res, formValues.Configurations);
|
KubernetesApplicationHelper.generateEnvOrVolumesFromConfigurations(res, formValues.Configurations);
|
||||||
return res;
|
return res;
|
||||||
|
|
|
@ -28,6 +28,43 @@ class KubernetesApplicationHelper {
|
||||||
return _.filter(pods, { Labels: app.spec.selector.matchLabels });
|
return _.filter(pods, { Labels: app.spec.selector.matchLabels });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static associateContainerPersistedFoldersAndConfigurations(app, containers) {
|
||||||
|
_.forEach(containers, (container) => {
|
||||||
|
container.PersistedFolders = _.without(
|
||||||
|
_.map(app.PersistedFolders, (pf) => {
|
||||||
|
if (pf.MountPath && _.includes(_.map(container.VolumeMounts, 'mountPath'), pf.MountPath)) {
|
||||||
|
return pf;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
container.ConfigurationVolumes = _.without(
|
||||||
|
_.map(app.ConfigurationVolumes, (cv) => {
|
||||||
|
if (cv.rootMountPath && _.includes(_.map(container.VolumeMounts, 'mountPath'), cv.rootMountPath)) {
|
||||||
|
return cv;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static associateContainersAndApplication(app) {
|
||||||
|
if (!app.Pods) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const containers = app.Pods[0].Containers;
|
||||||
|
KubernetesApplicationHelper.associateContainerPersistedFoldersAndConfigurations(app, containers);
|
||||||
|
return containers;
|
||||||
|
}
|
||||||
|
|
||||||
|
static associateAllContainersAndApplication(app) {
|
||||||
|
const containers = _.flatMap(_.map(app.Pods, 'Containers'));
|
||||||
|
KubernetesApplicationHelper.associateContainerPersistedFoldersAndConfigurations(app, containers);
|
||||||
|
return containers;
|
||||||
|
}
|
||||||
|
|
||||||
static portMappingsFromApplications(applications) {
|
static portMappingsFromApplications(applications) {
|
||||||
const res = _.reduce(
|
const res = _.reduce(
|
||||||
applications,
|
applications,
|
||||||
|
|
|
@ -9,13 +9,13 @@ class KubernetesResourceReservationHelper {
|
||||||
return _.reduce(
|
return _.reduce(
|
||||||
containers,
|
containers,
|
||||||
(acc, container) => {
|
(acc, container) => {
|
||||||
if (container.resources && container.resources.requests) {
|
if (container.Requests) {
|
||||||
if (container.resources.requests.memory) {
|
if (container.Requests.memory) {
|
||||||
acc.Memory += filesizeParser(container.resources.requests.memory, { base: 10 });
|
acc.Memory += filesizeParser(container.Requests.memory, { base: 10 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.resources.requests.cpu) {
|
if (container.Requests.cpu) {
|
||||||
acc.CPU += KubernetesResourceReservationHelper.parseCPU(container.resources.requests.cpu);
|
acc.CPU += KubernetesResourceReservationHelper.parseCPU(container.Requests.cpu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ const _KubernetesApplicationFormValues = Object.freeze({
|
||||||
PublishingType: KubernetesApplicationPublishingTypes.INTERNAL,
|
PublishingType: KubernetesApplicationPublishingTypes.INTERNAL,
|
||||||
DataAccessPolicy: KubernetesApplicationDataAccessPolicies.SHARED,
|
DataAccessPolicy: KubernetesApplicationDataAccessPolicies.SHARED,
|
||||||
Configurations: [], // KubernetesApplicationConfigurationFormValue list
|
Configurations: [], // KubernetesApplicationConfigurationFormValue list
|
||||||
|
Containers: [],
|
||||||
AutoScaler: {},
|
AutoScaler: {},
|
||||||
OriginalIngresses: undefined,
|
OriginalIngresses: undefined,
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,7 @@ const _KubernetesApplication = Object.freeze({
|
||||||
Image: '',
|
Image: '',
|
||||||
CreationDate: 0,
|
CreationDate: 0,
|
||||||
Pods: [],
|
Pods: [],
|
||||||
|
Containers: [],
|
||||||
Limits: {},
|
Limits: {},
|
||||||
ServiceType: '',
|
ServiceType: '',
|
||||||
ServiceId: '',
|
ServiceId: '',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import { KubernetesPod, KubernetesPodToleration, KubernetesPodAffinity } from 'Kubernetes/pod/models';
|
import { KubernetesPod, KubernetesPodToleration, KubernetesPodAffinity, KubernetesPodContainer, KubernetesPodContainerTypes } from 'Kubernetes/pod/models';
|
||||||
|
|
||||||
function computeStatus(statuses) {
|
function computeStatus(statuses) {
|
||||||
const containerStatuses = _.map(statuses, 'state');
|
const containerStatuses = _.map(statuses, 'state');
|
||||||
|
@ -13,6 +13,21 @@ function computeStatus(statuses) {
|
||||||
return 'Running';
|
return 'Running';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function computeContainerStatus(statuses, name) {
|
||||||
|
const status = _.find(statuses, { name: name });
|
||||||
|
if (!status) {
|
||||||
|
return 'Terminated';
|
||||||
|
}
|
||||||
|
const state = status.state;
|
||||||
|
if (state.waiting) {
|
||||||
|
return 'Waiting';
|
||||||
|
}
|
||||||
|
if (!state.running) {
|
||||||
|
return 'Terminated';
|
||||||
|
}
|
||||||
|
return 'Running';
|
||||||
|
}
|
||||||
|
|
||||||
function computeAffinity(affinity) {
|
function computeAffinity(affinity) {
|
||||||
const res = new KubernetesPodAffinity();
|
const res = new KubernetesPodAffinity();
|
||||||
if (affinity) {
|
if (affinity) {
|
||||||
|
@ -33,6 +48,44 @@ function computeTolerations(tolerations) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function computeContainers(data) {
|
||||||
|
const containers = data.spec.containers;
|
||||||
|
const initContainers = data.spec.initContainers;
|
||||||
|
|
||||||
|
return _.concat(
|
||||||
|
_.map(containers, (item) => {
|
||||||
|
const res = new KubernetesPodContainer();
|
||||||
|
res.Type = KubernetesPodContainerTypes.APP;
|
||||||
|
res.PodName = data.metadata.name;
|
||||||
|
res.Name = item.name;
|
||||||
|
res.Image = item.image;
|
||||||
|
res.Node = data.spec.nodeName;
|
||||||
|
res.CreationDate = data.status.startTime;
|
||||||
|
res.Status = computeContainerStatus(data.status.containerStatuses, item.name);
|
||||||
|
res.Limits = item.resources.limits;
|
||||||
|
res.Requests = item.resources.requests;
|
||||||
|
res.VolumeMounts = item.volumeMounts;
|
||||||
|
res.Env = item.env;
|
||||||
|
return res;
|
||||||
|
}),
|
||||||
|
_.map(initContainers, (item) => {
|
||||||
|
const res = new KubernetesPodContainer();
|
||||||
|
res.Type = KubernetesPodContainerTypes.INIT;
|
||||||
|
res.PodName = data.metadata.name;
|
||||||
|
res.Name = item.name;
|
||||||
|
res.Image = item.image;
|
||||||
|
res.Node = data.spec.nodeName;
|
||||||
|
res.CreationDate = data.status.startTime;
|
||||||
|
res.Status = computeContainerStatus(data.status.containerStatuses, item.name);
|
||||||
|
res.Limits = item.resources.limits;
|
||||||
|
res.Requests = item.resources.requests;
|
||||||
|
res.VolumeMounts = item.volumeMounts;
|
||||||
|
res.Env = item.env;
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default class KubernetesPodConverter {
|
export default class KubernetesPodConverter {
|
||||||
static apiToModel(data) {
|
static apiToModel(data) {
|
||||||
const res = new KubernetesPod();
|
const res = new KubernetesPod();
|
||||||
|
@ -44,7 +97,7 @@ export default class KubernetesPodConverter {
|
||||||
res.Restarts = _.sumBy(data.status.containerStatuses, 'restartCount');
|
res.Restarts = _.sumBy(data.status.containerStatuses, 'restartCount');
|
||||||
res.Node = data.spec.nodeName;
|
res.Node = data.spec.nodeName;
|
||||||
res.CreationDate = data.status.startTime;
|
res.CreationDate = data.status.startTime;
|
||||||
res.Containers = data.spec.containers;
|
res.Containers = computeContainers(data);
|
||||||
res.Labels = data.metadata.labels;
|
res.Labels = data.metadata.labels;
|
||||||
res.Affinity = computeAffinity(data.spec.affinity);
|
res.Affinity = computeAffinity(data.spec.affinity);
|
||||||
res.NodeSelector = data.spec.nodeSelector;
|
res.NodeSelector = data.spec.nodeSelector;
|
||||||
|
|
|
@ -40,3 +40,30 @@ export class KubernetesPodToleration {
|
||||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesPodToleration)));
|
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesPodToleration)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _KubernetesPodContainer = Object.freeze({
|
||||||
|
Type: 0,
|
||||||
|
PodName: '',
|
||||||
|
Name: '',
|
||||||
|
Image: '',
|
||||||
|
Node: '',
|
||||||
|
CreationDate: '',
|
||||||
|
Status: '',
|
||||||
|
Limits: {},
|
||||||
|
Requests: {},
|
||||||
|
VolumeMounts: {},
|
||||||
|
ConfigurationVolumes: [],
|
||||||
|
PersistedFolders: [],
|
||||||
|
Env: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
export class KubernetesPodContainer {
|
||||||
|
constructor() {
|
||||||
|
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesPodContainer)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const KubernetesPodContainerTypes = {
|
||||||
|
INIT: 1,
|
||||||
|
APP: 2,
|
||||||
|
};
|
||||||
|
|
|
@ -36,11 +36,15 @@ class KubernetesPodService {
|
||||||
*
|
*
|
||||||
* @param {string} namespace
|
* @param {string} namespace
|
||||||
* @param {string} podName
|
* @param {string} podName
|
||||||
|
* @param {string} containerName
|
||||||
*/
|
*/
|
||||||
async logsAsync(namespace, podName) {
|
async logsAsync(namespace, podName, containerName) {
|
||||||
try {
|
try {
|
||||||
const params = new KubernetesCommonParams();
|
const params = new KubernetesCommonParams();
|
||||||
params.id = podName;
|
params.id = podName;
|
||||||
|
if (containerName) {
|
||||||
|
params.container = containerName;
|
||||||
|
}
|
||||||
const data = await this.KubernetesPods(namespace).logs(params).$promise;
|
const data = await this.KubernetesPods(namespace).logs(params).$promise;
|
||||||
return data.logs.length === 0 ? [] : data.logs.split('\n');
|
return data.logs.length === 0 ? [] : data.logs.split('\n');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -48,8 +52,8 @@ class KubernetesPodService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logs(namespace, podName) {
|
logs(namespace, podName, containerName) {
|
||||||
return this.$async(this.logsAsync, namespace, podName);
|
return this.$async(this.logsAsync, namespace, podName, containerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -115,6 +115,7 @@ class KubernetesApplicationService {
|
||||||
application.Yaml = rootItem.value.Yaml;
|
application.Yaml = rootItem.value.Yaml;
|
||||||
application.Raw = rootItem.value.Raw;
|
application.Raw = rootItem.value.Raw;
|
||||||
application.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods.value, application.Raw);
|
application.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods.value, application.Raw);
|
||||||
|
application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
|
||||||
|
|
||||||
const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers.value, application);
|
const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers.value, application);
|
||||||
const scaler = boundScaler ? await this.KubernetesHorizontalPodAutoScalerService.get(namespace, boundScaler.Name) : undefined;
|
const scaler = boundScaler ? await this.KubernetesHorizontalPodAutoScalerService.get(namespace, boundScaler.Name) : undefined;
|
||||||
|
@ -144,6 +145,7 @@ class KubernetesApplicationService {
|
||||||
const service = KubernetesServiceHelper.findApplicationBoundService(services, item);
|
const service = KubernetesServiceHelper.findApplicationBoundService(services, item);
|
||||||
const application = converterFunc(item, service, ingresses);
|
const application = converterFunc(item, service, ingresses);
|
||||||
application.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods, item);
|
application.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods, item);
|
||||||
|
application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
|
||||||
return application;
|
return application;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<a ui-sref="kubernetes.resourcePools.resourcePool({ id: ctrl.application.ResourcePool })">{{ ctrl.application.ResourcePool }}</a> >
|
<a ui-sref="kubernetes.resourcePools.resourcePool({ id: ctrl.application.ResourcePool })">{{ ctrl.application.ResourcePool }}</a> >
|
||||||
<a ui-sref="kubernetes.applications">Applications</a> >
|
<a ui-sref="kubernetes.applications">Applications</a> >
|
||||||
<a ui-sref="kubernetes.applications.application({ name: ctrl.application.Name, namespace: ctrl.application.ResourcePool })">{{ ctrl.application.Name }}</a> > Pods >
|
<a ui-sref="kubernetes.applications.application({ name: ctrl.application.Name, namespace: ctrl.application.ResourcePool })">{{ ctrl.application.Name }}</a> > Pods >
|
||||||
{{ ctrl.podName }} > Console
|
{{ ctrl.podName }} > Containers > {{ ctrl.containerName }} > Console
|
||||||
</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>
|
||||||
|
|
|
@ -54,7 +54,7 @@ class KubernetesApplicationConsoleController {
|
||||||
endpointId: this.EndpointProvider.endpointID(),
|
endpointId: this.EndpointProvider.endpointID(),
|
||||||
namespace: this.application.ResourcePool,
|
namespace: this.application.ResourcePool,
|
||||||
podName: this.podName,
|
podName: this.podName,
|
||||||
containerName: this.application.Pods[0].Containers[0].name,
|
containerName: this.containerName,
|
||||||
command: this.state.command,
|
command: this.state.command,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -92,8 +92,10 @@ class KubernetesApplicationConsoleController {
|
||||||
const podName = this.$transition$.params().pod;
|
const podName = this.$transition$.params().pod;
|
||||||
const applicationName = this.$transition$.params().name;
|
const applicationName = this.$transition$.params().name;
|
||||||
const namespace = this.$transition$.params().namespace;
|
const namespace = this.$transition$.params().namespace;
|
||||||
|
const containerName = this.$transition$.params().container;
|
||||||
|
|
||||||
this.podName = podName;
|
this.podName = podName;
|
||||||
|
this.containerName = containerName;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.application = await this.KubernetesApplicationService.get(namespace, applicationName);
|
this.application = await this.KubernetesApplicationService.get(namespace, applicationName);
|
||||||
|
|
|
@ -54,7 +54,15 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="container_image" class="col-sm-1 control-label text-left">Image</label>
|
<label for="container_image" class="col-sm-1 control-label text-left">Image</label>
|
||||||
<div class="col-sm-11">
|
<div class="col-sm-11">
|
||||||
<input type="text" class="form-control" name="container_image" ng-model="ctrl.formValues.Image" placeholder="nginx:latest" required />
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
name="container_image"
|
||||||
|
ng-model="ctrl.formValues.Image"
|
||||||
|
placeholder="nginx:latest"
|
||||||
|
required
|
||||||
|
ng-disabled="ctrl.formValues.Containers.length > 1"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="kubernetesApplicationCreationForm.container_image.$invalid">
|
<div class="form-group" ng-show="kubernetesApplicationCreationForm.container_image.$invalid">
|
||||||
|
@ -128,7 +136,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<label class="control-label text-left">Environment variables</label>
|
<label class="control-label text-left">Environment variables</label>
|
||||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addEnvironmentVariable()">
|
<span ng-if="ctrl.formValues.Containers.length <= 1" class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addEnvironmentVariable()">
|
||||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add environment variable
|
<i class="fa fa-plus-circle" aria-hidden="true"></i> add environment variable
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -146,6 +154,7 @@
|
||||||
ng-change="ctrl.onChangeEnvironmentName()"
|
ng-change="ctrl.onChangeEnvironmentName()"
|
||||||
ng-pattern="/^[a-zA-Z]([-_a-zA-Z0-9]*[a-zA-Z0-9])?$/"
|
ng-pattern="/^[a-zA-Z]([-_a-zA-Z0-9]*[a-zA-Z0-9])?$/"
|
||||||
placeholder="foo"
|
placeholder="foo"
|
||||||
|
ng-disabled="ctrl.formValues.Containers.length > 1"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -171,10 +180,17 @@
|
||||||
|
|
||||||
<div class="input-group col-sm-4 input-group-sm" ng-class="{ striked: envVar.NeedsDeletion }">
|
<div class="input-group col-sm-4 input-group-sm" ng-class="{ striked: envVar.NeedsDeletion }">
|
||||||
<span class="input-group-addon">value</span>
|
<span class="input-group-addon">value</span>
|
||||||
<input type="text" name="environment_variable_value_{{ $index }}" class="form-control" ng-model="envVar.Value" placeholder="bar" />
|
<input
|
||||||
|
type="text"
|
||||||
|
name="environment_variable_value_{{ $index }}"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="envVar.Value"
|
||||||
|
placeholder="bar"
|
||||||
|
ng-disabled="ctrl.formValues.Containers.length > 1"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group col-sm-2 input-group-sm">
|
<div class="input-group col-sm-2 input-group-sm" ng-if="ctrl.formValues.Containers.length <= 1">
|
||||||
<button ng-if="!envVar.NeedsDeletion" class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removeEnvironmentVariable($index)">
|
<button ng-if="!envVar.NeedsDeletion" class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removeEnvironmentVariable($index)">
|
||||||
<i class="fa fa-trash-alt" aria-hidden="true"></i>
|
<i class="fa fa-trash-alt" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -194,7 +210,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<label class="control-label text-left">Configurations</label>
|
<label class="control-label text-left">Configurations</label>
|
||||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addConfiguration()">
|
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addConfiguration()" ng-if="ctrl.formValues.Containers.length <= 1">
|
||||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add configuration
|
<i class="fa fa-plus-circle" aria-hidden="true"></i> add configuration
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -214,6 +230,7 @@
|
||||||
ng-model="config.SelectedConfiguration"
|
ng-model="config.SelectedConfiguration"
|
||||||
ng-options="c as c.Name for c in ctrl.configurations"
|
ng-options="c as c.Name for c in ctrl.configurations"
|
||||||
ng-change="ctrl.resetConfiguration(index)"
|
ng-change="ctrl.resetConfiguration(index)"
|
||||||
|
ng-disabled="ctrl.formValues.Containers.length > 1"
|
||||||
></select>
|
></select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6" style="margin-top: 2px;">
|
<div class="col-sm-6" style="margin-top: 2px;">
|
||||||
|
@ -222,14 +239,20 @@
|
||||||
type="button"
|
type="button"
|
||||||
ng-if="!config.Overriden"
|
ng-if="!config.Overriden"
|
||||||
ng-click="ctrl.overrideConfiguration(index)"
|
ng-click="ctrl.overrideConfiguration(index)"
|
||||||
ng-disabled="!config.SelectedConfiguration"
|
ng-disabled="!config.SelectedConfiguration || ctrl.formValues.Containers.length > 1"
|
||||||
>
|
>
|
||||||
<i class="fa fa-list" aria-hidden="true"></i> Override
|
<i class="fa fa-list" aria-hidden="true"></i> Override
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-primary" type="button" ng-if="config.Overriden" ng-click="ctrl.resetConfiguration(index)">
|
<button
|
||||||
|
class="btn btn-sm btn-primary"
|
||||||
|
type="button"
|
||||||
|
ng-if="config.Overriden"
|
||||||
|
ng-click="ctrl.resetConfiguration(index)"
|
||||||
|
ng-disabled="ctrl.formValues.Containers.length > 1"
|
||||||
|
>
|
||||||
<i class="fa fa-undo" aria-hidden="true"></i> Auto
|
<i class="fa fa-undo" aria-hidden="true"></i> Auto
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removeConfiguration(index)"> <i class="fa fa-trash-alt" aria-hidden="true"></i> Remove </button>
|
<button class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removeConfiguration(index)" ng-if="ctrl.formValues.Containers.length <= 1"> <i class="fa fa-trash-alt" aria-hidden="true"></i> Remove </button>
|
||||||
</div>
|
</div>
|
||||||
<!-- no-override -->
|
<!-- no-override -->
|
||||||
<div class="col-sm-12" style="margin-top: 10px;" ng-if="config.SelectedConfiguration && !config.Overriden">
|
<div class="col-sm-12" style="margin-top: 10px;" ng-if="config.SelectedConfiguration && !config.Overriden">
|
||||||
|
@ -266,6 +289,7 @@
|
||||||
ng-model="overridenKey.Path"
|
ng-model="overridenKey.Path"
|
||||||
placeholder="/etc/myapp/conf.d"
|
placeholder="/etc/myapp/conf.d"
|
||||||
name="overriden_key_path_{{ index }}_{{ keyIndex }}"
|
name="overriden_key_path_{{ index }}_{{ keyIndex }}"
|
||||||
|
ng-disabled="ctrl.formValues.Containers.length > 1"
|
||||||
required
|
required
|
||||||
ng-change="ctrl.onChangeConfigurationPath()"
|
ng-change="ctrl.onChangeConfigurationPath()"
|
||||||
/>
|
/>
|
||||||
|
@ -316,7 +340,12 @@
|
||||||
<div class="form-group" ng-if="ctrl.storageClassAvailable()">
|
<div class="form-group" ng-if="ctrl.storageClassAvailable()">
|
||||||
<div class="col-sm-12" style="margin-top: 5px;">
|
<div class="col-sm-12" style="margin-top: 5px;">
|
||||||
<label class="control-label text-left">Persisted folders</label>
|
<label class="control-label text-left">Persisted folders</label>
|
||||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addPersistedFolder()" ng-if="!ctrl.isEditAndStatefulSet()">
|
<span
|
||||||
|
class="label label-default interactive"
|
||||||
|
style="margin-left: 10px;"
|
||||||
|
ng-click="ctrl.addPersistedFolder()"
|
||||||
|
ng-if="!ctrl.isEditAndStatefulSet() && ctrl.formValues.Containers.length <= 1"
|
||||||
|
>
|
||||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add persisted folder
|
<i class="fa fa-plus-circle" aria-hidden="true"></i> add persisted folder
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -331,7 +360,7 @@
|
||||||
name="persisted_folder_path_{{ $index }}"
|
name="persisted_folder_path_{{ $index }}"
|
||||||
ng-model="persistedFolder.ContainerPath"
|
ng-model="persistedFolder.ContainerPath"
|
||||||
ng-change="ctrl.onChangePersistedFolderPath()"
|
ng-change="ctrl.onChangePersistedFolderPath()"
|
||||||
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index)"
|
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index) || ctrl.formValues.Containers.length > 1"
|
||||||
placeholder="/data"
|
placeholder="/data"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
@ -341,7 +370,11 @@
|
||||||
<span
|
<span
|
||||||
class="btn-group btn-group-sm"
|
class="btn-group btn-group-sm"
|
||||||
ng-class="{ striked: persistedFolder.NeedsDeletion }"
|
ng-class="{ striked: persistedFolder.NeedsDeletion }"
|
||||||
ng-if="!ctrl.isEditAndExistingPersistedFolder($index) && ctrl.application.ApplicationType !== ctrl.ApplicationTypes.STATEFULSET"
|
ng-if="
|
||||||
|
!ctrl.isEditAndExistingPersistedFolder($index) &&
|
||||||
|
ctrl.application.ApplicationType !== ctrl.ApplicationTypes.STATEFULSET &&
|
||||||
|
ctrl.formValues.Containers.length <= 1
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
|
@ -372,14 +405,14 @@
|
||||||
placeholder="20"
|
placeholder="20"
|
||||||
ng-min="0"
|
ng-min="0"
|
||||||
required
|
required
|
||||||
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index)"
|
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index) || ctrl.formValues.Containers.length > 1"
|
||||||
/>
|
/>
|
||||||
<span class="input-group-addon" style="padding: 0;">
|
<span class="input-group-addon" style="padding: 0;">
|
||||||
<select
|
<select
|
||||||
ng-model="persistedFolder.SizeUnit"
|
ng-model="persistedFolder.SizeUnit"
|
||||||
ng-style="{ width: '100%', height: '100%', cursor: ctrl.isEditAndExistingPersistedFolder($index) ? 'not-allowed' : 'auto' }"
|
ng-style="{ width: '100%', height: '100%', cursor: ctrl.isEditAndExistingPersistedFolder($index) ? 'not-allowed' : 'auto' }"
|
||||||
ng-options="unit for unit in ctrl.state.availableSizeUnits"
|
ng-options="unit for unit in ctrl.state.availableSizeUnits"
|
||||||
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index)"
|
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index) || ctrl.formValues.Containers.length > 1"
|
||||||
></select>
|
></select>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -396,7 +429,7 @@
|
||||||
class="form-control"
|
class="form-control"
|
||||||
ng-model="persistedFolder.StorageClass"
|
ng-model="persistedFolder.StorageClass"
|
||||||
ng-options="storageClass as storageClass.Name for storageClass in ctrl.storageClasses"
|
ng-options="storageClass as storageClass.Name for storageClass in ctrl.storageClasses"
|
||||||
ng-disabled="ctrl.state.isEdit"
|
ng-disabled="ctrl.state.isEdit || ctrl.formValues.Containers.length > 1"
|
||||||
></select>
|
></select>
|
||||||
<input ng-if="!ctrl.hasMultipleStorageClassesAvailable()" type="text" class="form-control" disabled ng-model="persistedFolder.StorageClass.Name" />
|
<input ng-if="!ctrl.hasMultipleStorageClassesAvailable()" type="text" class="form-control" disabled ng-model="persistedFolder.StorageClass.Name" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -409,7 +442,7 @@
|
||||||
ng-model="ctrl.formValues.PersistedFolders[$index].ExistingVolume"
|
ng-model="ctrl.formValues.PersistedFolders[$index].ExistingVolume"
|
||||||
ng-options="vol as vol.PersistentVolumeClaim.Name for vol in ctrl.availableVolumes"
|
ng-options="vol as vol.PersistentVolumeClaim.Name for vol in ctrl.availableVolumes"
|
||||||
ng-change="ctrl.onChangeExistingVolumeSelection()"
|
ng-change="ctrl.onChangeExistingVolumeSelection()"
|
||||||
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index)"
|
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index) || ctrl.formValues.Containers.length > 1"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<option selected disabled hidden value="">Select a volume</option>
|
<option selected disabled hidden value="">Select a volume</option>
|
||||||
|
@ -417,7 +450,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group col-sm-1 input-group-sm">
|
<div class="input-group col-sm-1 input-group-sm">
|
||||||
<div style="vertical-align: top;" ng-if="!ctrl.isEditAndStatefulSet()" ng-if="!ctrl.state.useExistingVolume[$index]">
|
<div style="vertical-align: top;" ng-if="!ctrl.isEditAndStatefulSet() && !ctrl.state.useExistingVolume[$index] && ctrl.formValues.Containers.length <= 1">
|
||||||
<button ng-if="!persistedFolder.NeedsDeletion" class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removePersistedFolder($index)">
|
<button ng-if="!persistedFolder.NeedsDeletion" class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removePersistedFolder($index)">
|
||||||
<i class="fa fa-trash-alt" aria-hidden="true"></i>
|
<i class="fa fa-trash-alt" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -607,7 +640,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- memory-limit-input -->
|
<!-- memory-limit-input -->
|
||||||
<div class="form-group" ng-if="!ctrl.state.resourcePoolHasQuota || (ctrl.state.resourcePoolHasQuota && !ctrl.resourceQuotaCapacityExceeded())">
|
<div
|
||||||
|
class="form-group"
|
||||||
|
ng-if="(!ctrl.state.resourcePoolHasQuota || (ctrl.state.resourcePoolHasQuota && !ctrl.resourceQuotaCapacityExceeded())) && ctrl.formValues.Containers.length <= 1"
|
||||||
|
>
|
||||||
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px;">
|
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px;">
|
||||||
Memory
|
Memory
|
||||||
<portainer-tooltip
|
<portainer-tooltip
|
||||||
|
@ -648,7 +684,10 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- !memory-limit-input -->
|
<!-- !memory-limit-input -->
|
||||||
<!-- cpu-limit-input -->
|
<!-- cpu-limit-input -->
|
||||||
<div class="form-group" ng-if="!ctrl.state.resourcePoolHasQuota || (ctrl.state.resourcePoolHasQuota && !ctrl.resourceQuotaCapacityExceeded())">
|
<div
|
||||||
|
class="form-group"
|
||||||
|
ng-if="(!ctrl.state.resourcePoolHasQuota || (ctrl.state.resourcePoolHasQuota && !ctrl.resourceQuotaCapacityExceeded())) && ctrl.formValues.Containers.length <= 1"
|
||||||
|
>
|
||||||
<label for="cpu-limit" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px;">
|
<label for="cpu-limit" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px;">
|
||||||
CPU
|
CPU
|
||||||
<portainer-tooltip
|
<portainer-tooltip
|
||||||
|
|
|
@ -366,77 +366,92 @@
|
||||||
<!-- CONFIGURATIONS -->
|
<!-- CONFIGURATIONS -->
|
||||||
<div class="text-muted" style="margin-bottom: 15px; margin-top: 25px;"> <i class="fa fa-file-code" aria-hidden="true" style="margin-right: 2px;"></i> Configuration </div>
|
<div class="text-muted" style="margin-bottom: 15px; margin-top: 25px;"> <i class="fa fa-file-code" aria-hidden="true" style="margin-right: 2px;"></i> Configuration </div>
|
||||||
|
|
||||||
<div class="small text-muted" ng-if="!ctrl.application.Env && !ctrl.hasVolumeConfiguration()" style="margin-bottom: 15px;">
|
<div class="small text-muted" ng-if="!ctrl.application.Env.length > 0 && !ctrl.hasVolumeConfiguration()" style="margin-bottom: 15px;">
|
||||||
<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 not using any environment variable or configuration.
|
This application is not using any environment variable or configuration.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-if="ctrl.application.Env.length > 0">
|
<table class="table" ng-if="ctrl.application.Env.length > 0">
|
||||||
<div>
|
<tr class="text-muted">
|
||||||
<table class="table">
|
<td style="width: 25%;">Container</td>
|
||||||
<tbody>
|
<td style="width: 25%;">Environment variable</td>
|
||||||
<tr class="text-muted">
|
<td style="width: 25%;">Value</td>
|
||||||
<td style="width: 33%;">Environment variable</td>
|
<td style="width: 25%;">Configuration</td>
|
||||||
<td style="width: 33%;">Value</td>
|
</tr>
|
||||||
<td style="width: 33%;">Configuration</td>
|
<tbody ng-repeat="container in ctrl.application.Containers" style="border-top: 0;">
|
||||||
</tr>
|
<tr ng-repeat="envvar in container.Env track by $index">
|
||||||
<tr ng-repeat="envvar in ctrl.application.Env track by $index">
|
<td>
|
||||||
<td>{{ envvar.name }}</td>
|
{{ container.Name }}
|
||||||
<td>
|
<span ng-if="container.Type === ctrl.KubernetesPodContainerTypes.INIT"
|
||||||
<span ng-if="envvar.value">{{ envvar.value }}</span>
|
><i class="fa fa-asterisk" aria-hidden="true"></i> {{ envvar.valueFrom.fieldRef.fieldPath }} (<a
|
||||||
<span ng-if="envvar.valueFrom.configMapKeyRef"><i class="fa fa-key" aria-hidden="true"></i> {{ envvar.valueFrom.configMapKeyRef.key }}</span>
|
href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/"
|
||||||
<span ng-if="envvar.valueFrom.secretKeyRef"><i class="fa fa-key" aria-hidden="true"></i> {{ envvar.valueFrom.secretKeyRef.key }}</span>
|
target="_blank"
|
||||||
<span ng-if="envvar.valueFrom.fieldRef"
|
>init container</a
|
||||||
><i class="fa fa-asterisk" aria-hidden="true"></i> {{ envvar.valueFrom.fieldRef.fieldPath }} (<a
|
>)</span
|
||||||
href="https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/#capabilities-of-the-downward-api"
|
>
|
||||||
target="_blank"
|
</td>
|
||||||
>downward API</a
|
<td>{{ envvar.name }}</td>
|
||||||
>)</span
|
<td>
|
||||||
>
|
<span ng-if="envvar.value">{{ envvar.value }}</span>
|
||||||
<span ng-if="!envvar.value && !envvar.valueFrom.secretKeyRef && !envvar.valueFrom.configMapKeyRef && !envvar.valueFrom.fieldRef">-</span>
|
<span ng-if="envvar.valueFrom.configMapKeyRef"><i class="fa fa-key" aria-hidden="true"></i> {{ envvar.valueFrom.configMapKeyRef.key }}</span>
|
||||||
</td>
|
<span ng-if="envvar.valueFrom.secretKeyRef"><i class="fa fa-key" aria-hidden="true"></i> {{ envvar.valueFrom.secretKeyRef.key }}</span>
|
||||||
<td>
|
<span ng-if="envvar.valueFrom.fieldRef"
|
||||||
<span ng-if="envvar.value || envvar.valueFrom.fieldRef || (!envvar.valueFrom.secretKeyRef && !envvar.valueFrom.configMapKeyRef)">-</span>
|
><i class="fa fa-asterisk" aria-hidden="true"></i> {{ envvar.valueFrom.fieldRef.fieldPath }} (<a
|
||||||
<span ng-if="envvar.valueFrom.configMapKeyRef"
|
href="https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/#capabilities-of-the-downward-api"
|
||||||
><a ui-sref="kubernetes.configurations.configuration({ name: envvar.valueFrom.configMapKeyRef.name, namespace: ctrl.application.ResourcePool })"
|
target="_blank"
|
||||||
><i class="fa fa-file-code" aria-hidden="true"></i> {{ envvar.valueFrom.configMapKeyRef.name }}</a
|
>downward API</a
|
||||||
></span
|
>)</span
|
||||||
>
|
>
|
||||||
<span ng-if="envvar.valueFrom.secretKeyRef"
|
<span ng-if="!envvar.value && !envvar.valueFrom.secretKeyRef && !envvar.valueFrom.configMapKeyRef && !envvar.valueFrom.fieldRef">-</span>
|
||||||
><a ui-sref="kubernetes.configurations.configuration({ name: envvar.valueFrom.secretKeyRef.name, namespace: ctrl.application.ResourcePool })"
|
</td>
|
||||||
><i class="fa fa-file-code" aria-hidden="true"></i> {{ envvar.valueFrom.secretKeyRef.name }}</a
|
<td>
|
||||||
></span
|
<span ng-if="envvar.value || envvar.valueFrom.fieldRef || (!envvar.valueFrom.secretKeyRef && !envvar.valueFrom.configMapKeyRef)">-</span>
|
||||||
>
|
<span ng-if="envvar.valueFrom.configMapKeyRef"
|
||||||
</td>
|
><a ui-sref="kubernetes.configurations.configuration({ name: envvar.valueFrom.configMapKeyRef.name, namespace: ctrl.application.ResourcePool })"
|
||||||
</tr>
|
><i class="fa fa-file-code" aria-hidden="true"></i> {{ envvar.valueFrom.configMapKeyRef.name }}</a
|
||||||
</tbody>
|
></span
|
||||||
</table>
|
>
|
||||||
</div>
|
<span ng-if="envvar.valueFrom.secretKeyRef"
|
||||||
</div>
|
><a ui-sref="kubernetes.configurations.configuration({ name: envvar.valueFrom.secretKeyRef.name, namespace: ctrl.application.ResourcePool })"
|
||||||
|
><i class="fa fa-file-code" aria-hidden="true"></i> {{ envvar.valueFrom.secretKeyRef.name }}</a
|
||||||
|
></span
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<div ng-if="ctrl.hasVolumeConfiguration()">
|
<table class="table" ng-if="ctrl.hasVolumeConfiguration()">
|
||||||
<table class="table">
|
<tr class="text-muted">
|
||||||
<tbody>
|
<td style="width: 25%;">Container</td>
|
||||||
<tr class="text-muted">
|
<td style="width: 25%;">Configuration path</td>
|
||||||
<td style="width: 33%;">Configuration path</td>
|
<td style="width: 25%;">Value</td>
|
||||||
<td style="width: 33%;">Value</td>
|
<td style="width: 25%;">Configuration</td>
|
||||||
<td style="width: 33%;">Configuration</td>
|
</tr>
|
||||||
</tr>
|
<tbody ng-repeat="container in ctrl.application.Containers" style="border-top: 0;">
|
||||||
|
<tr ng-repeat="volume in container.ConfigurationVolumes track by $index" style="border-top: 0;">
|
||||||
<tr ng-repeat="volume in ctrl.application.ConfigurationVolumes track by $index" style="border-top: 0;">
|
<td>
|
||||||
<td>
|
{{ container.Name }}
|
||||||
{{ volume.fileMountPath }}
|
<span ng-if="container.Type === ctrl.KubernetesPodContainerTypes.INIT"
|
||||||
</td>
|
><i class="fa fa-asterisk" aria-hidden="true"></i> {{ envvar.valueFrom.fieldRef.fieldPath }} (<a
|
||||||
<td> <i class="fa fa-key" ng-if="volume.configurationKey" aria-hidden="true"></i> {{ volume.configurationKey ? volume.configurationKey : '-' }} </td>
|
href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/"
|
||||||
<td>
|
target="_blank"
|
||||||
<a ui-sref="kubernetes.configurations.configuration({ name: volume.configurationName, namespace: ctrl.application.ResourcePool })"
|
>init container</a
|
||||||
><i class="fa fa-file-code" aria-hidden="true"></i> {{ volume.configurationName }}</a
|
>)</span
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
<td>
|
||||||
</tbody>
|
{{ volume.fileMountPath }}
|
||||||
</table>
|
</td>
|
||||||
</div>
|
<td> <i class="fa fa-key" ng-if="volume.configurationKey" aria-hidden="true"></i> {{ volume.configurationKey ? volume.configurationKey : '-' }} </td>
|
||||||
|
<td>
|
||||||
|
<a ui-sref="kubernetes.configurations.configuration({ name: volume.configurationName, namespace: ctrl.application.ResourcePool })"
|
||||||
|
><i class="fa fa-file-code" aria-hidden="true"></i> {{ volume.configurationName }}</a
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
<!-- !CONFIGURATIONS -->
|
<!-- !CONFIGURATIONS -->
|
||||||
|
|
||||||
<!-- DATA PERSISTENCE -->
|
<!-- DATA PERSISTENCE -->
|
||||||
|
@ -457,12 +472,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table" ng-if="ctrl.application.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.SHARED">
|
<table class="table" ng-if="ctrl.application.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.SHARED">
|
||||||
<tbody>
|
<tr class="text-muted">
|
||||||
<tr class="text-muted">
|
<td style="width: 33%;">Persisted folder</td>
|
||||||
<td style="width: 50%;">Persisted folder</td>
|
<td style="width: 66%;">Persistence</td>
|
||||||
<td style="width: 50%;">Persistence</td>
|
</tr>
|
||||||
</tr>
|
<tbody ng-repeat="container in ctrl.application.Containers" style="border-top: 0;">
|
||||||
<tr ng-repeat="volume in ctrl.application.PersistedFolders track by $index">
|
<tr ng-repeat="volume in container.PersistedFolders track by $index">
|
||||||
<td>
|
<td>
|
||||||
{{ volume.MountPath }}
|
{{ volume.MountPath }}
|
||||||
</td>
|
</td>
|
||||||
|
@ -479,22 +494,31 @@
|
||||||
<table class="table" ng-if="ctrl.application.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.ISOLATED">
|
<table class="table" ng-if="ctrl.application.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.ISOLATED">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-muted">
|
<tr class="text-muted">
|
||||||
<td style="width: 33%;">Pod</td>
|
<td style="width: 25%;">Container name</td>
|
||||||
<td style="width: 33%;">Persisted folder</td>
|
<td style="width: 25%;">Pod name</td>
|
||||||
<td style="width: 33%;">Persistence</td>
|
<td style="width: 25%;">Persisted folder</td>
|
||||||
|
<td style="width: 25%;">Persistence</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody ng-repeat="pod in ctrl.application.Pods track by $index" style="border-top: none;">
|
<tbody ng-repeat="container in ctrl.allContainers track by $index" style="border-top: none;">
|
||||||
<tr ng-repeat="volume in ctrl.application.PersistedFolders track by $index">
|
<tr ng-repeat="volume in container.PersistedFolders track by $index">
|
||||||
<td>
|
<td>
|
||||||
{{ pod.Name }}
|
{{ container.Name }}
|
||||||
|
<span ng-if="container.Type === ctrl.KubernetesPodContainerTypes.INIT"
|
||||||
|
><i class="fa fa-asterisk" aria-hidden="true"></i> {{ envvar.valueFrom.fieldRef.fieldPath }} (<a
|
||||||
|
href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/"
|
||||||
|
target="_blank"
|
||||||
|
>init container</a
|
||||||
|
>)</span
|
||||||
|
>
|
||||||
</td>
|
</td>
|
||||||
|
<td>{{ container.PodName }}</td>
|
||||||
<td>
|
<td>
|
||||||
{{ volume.MountPath }}
|
{{ volume.MountPath }}
|
||||||
</td>
|
</td>
|
||||||
<td ng-if="volume.PersistentVolumeClaimName">
|
<td ng-if="volume.PersistentVolumeClaimName">
|
||||||
<a ui-sref="kubernetes.volumes.volume({ name: volume.PersistentVolumeClaimName + '-' + pod.Name, namespace: ctrl.application.ResourcePool })">
|
<a ui-sref="kubernetes.volumes.volume({ name: volume.PersistentVolumeClaimName + '-' + container.PodName, namespace: ctrl.application.ResourcePool })">
|
||||||
<i class="fa fa-database" aria-hidden="true"></i> {{ volume.PersistentVolumeClaimName + '-' + pod.Name }}</a
|
<i class="fa fa-database" aria-hidden="true"></i> {{ volume.PersistentVolumeClaimName + '-' + container.PodName }}</a
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
<td ng-if="volume.HostPath"> {{ volume.HostPath }} on host filesystem </td>
|
<td ng-if="volume.HostPath"> {{ volume.HostPath }} on host filesystem </td>
|
||||||
|
@ -510,8 +534,14 @@
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<kubernetes-pods-datatable title-text="Application pods" title-icon="fa-server" dataset="ctrl.application.Pods" table-key="kubernetes.application.pods" order-by="Name">
|
<kubernetes-containers-datatable
|
||||||
</kubernetes-pods-datatable>
|
title-text="Application containers"
|
||||||
|
title-icon="fa-server"
|
||||||
|
dataset="ctrl.allContainers"
|
||||||
|
table-key="kubernetes.application.containers"
|
||||||
|
order-by="Name"
|
||||||
|
>
|
||||||
|
</kubernetes-containers-datatable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,6 +6,7 @@ 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';
|
||||||
import { KubernetesPodNodeAffinityNodeSelectorRequirementOperators } from 'Kubernetes/pod/models';
|
import { KubernetesPodNodeAffinityNodeSelectorRequirementOperators } from 'Kubernetes/pod/models';
|
||||||
|
import { KubernetesPodContainerTypes } from 'Kubernetes/pod/models/index';
|
||||||
|
|
||||||
function computeTolerations(nodes, application) {
|
function computeTolerations(nodes, application) {
|
||||||
const pod = application.Pods[0];
|
const pod = application.Pods[0];
|
||||||
|
@ -120,6 +121,7 @@ class KubernetesApplicationController {
|
||||||
|
|
||||||
this.ApplicationDataAccessPolicies = KubernetesApplicationDataAccessPolicies;
|
this.ApplicationDataAccessPolicies = KubernetesApplicationDataAccessPolicies;
|
||||||
this.KubernetesServiceTypes = KubernetesServiceTypes;
|
this.KubernetesServiceTypes = KubernetesServiceTypes;
|
||||||
|
this.KubernetesPodContainerTypes = KubernetesPodContainerTypes;
|
||||||
|
|
||||||
this.onInit = this.onInit.bind(this);
|
this.onInit = this.onInit.bind(this);
|
||||||
this.getApplication = this.getApplication.bind(this);
|
this.getApplication = this.getApplication.bind(this);
|
||||||
|
@ -284,6 +286,7 @@ class KubernetesApplicationController {
|
||||||
this.KubernetesNodeService.get(),
|
this.KubernetesNodeService.get(),
|
||||||
]);
|
]);
|
||||||
this.application = application;
|
this.application = application;
|
||||||
|
this.allContainers = KubernetesApplicationHelper.associateAllContainersAndApplication(application);
|
||||||
this.formValues.Note = this.application.Note;
|
this.formValues.Note = this.application.Note;
|
||||||
if (this.application.Note) {
|
if (this.application.Note) {
|
||||||
this.state.expandedNote = true;
|
this.state.expandedNote = true;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<a ui-sref="kubernetes.resourcePools.resourcePool({ id: ctrl.application.ResourcePool })">{{ ctrl.application.ResourcePool }}</a> >
|
<a ui-sref="kubernetes.resourcePools.resourcePool({ id: ctrl.application.ResourcePool })">{{ ctrl.application.ResourcePool }}</a> >
|
||||||
<a ui-sref="kubernetes.applications">Applications</a> >
|
<a ui-sref="kubernetes.applications">Applications</a> >
|
||||||
<a ui-sref="kubernetes.applications.application({ name: ctrl.application.Name, namespace: ctrl.application.ResourcePool })">{{ ctrl.application.Name }}</a> > Pods >
|
<a ui-sref="kubernetes.applications.application({ name: ctrl.application.Name, namespace: ctrl.application.ResourcePool })">{{ ctrl.application.Name }}</a> > Pods >
|
||||||
{{ ctrl.podName }} > Logs
|
{{ ctrl.podName }} > Containers > {{ ctrl.containerName }} > Logs
|
||||||
</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>
|
||||||
|
|
|
@ -45,7 +45,7 @@ class KubernetesApplicationLogsController {
|
||||||
|
|
||||||
async getApplicationLogsAsync() {
|
async getApplicationLogsAsync() {
|
||||||
try {
|
try {
|
||||||
this.applicationLogs = await this.KubernetesPodService.logs(this.application.ResourcePool, this.podName);
|
this.applicationLogs = await this.KubernetesPodService.logs(this.application.ResourcePool, this.podName, this.containerName);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.stopRepeater();
|
this.stopRepeater();
|
||||||
this.Notifications.error('Failure', err, 'Unable to retrieve application logs');
|
this.Notifications.error('Failure', err, 'Unable to retrieve application logs');
|
||||||
|
@ -63,14 +63,16 @@ class KubernetesApplicationLogsController {
|
||||||
const podName = this.$transition$.params().pod;
|
const podName = this.$transition$.params().pod;
|
||||||
const applicationName = this.$transition$.params().name;
|
const applicationName = this.$transition$.params().name;
|
||||||
const namespace = this.$transition$.params().namespace;
|
const namespace = this.$transition$.params().namespace;
|
||||||
|
const containerName = this.$transition$.params().container;
|
||||||
|
|
||||||
this.applicationLogs = [];
|
this.applicationLogs = [];
|
||||||
this.podName = podName;
|
this.podName = podName;
|
||||||
|
this.containerName = containerName;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [application, applicationLogs] = await Promise.all([
|
const [application, applicationLogs] = await Promise.all([
|
||||||
this.KubernetesApplicationService.get(namespace, applicationName),
|
this.KubernetesApplicationService.get(namespace, applicationName),
|
||||||
this.KubernetesPodService.logs(namespace, podName),
|
this.KubernetesPodService.logs(namespace, podName, containerName),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.application = application;
|
this.application = application;
|
||||||
|
|
|
@ -43,12 +43,12 @@ class KubernetesStackLogsController {
|
||||||
this.repeater = this.$interval(this.getStackLogsAsync, this.state.refreshRate);
|
this.repeater = this.$interval(this.getStackLogsAsync, this.state.refreshRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateLogsPromise(pod) {
|
async generateLogsPromise(pod, container) {
|
||||||
const res = {
|
const res = {
|
||||||
Pod: pod,
|
Pod: pod,
|
||||||
Logs: [],
|
Logs: [],
|
||||||
};
|
};
|
||||||
res.Logs = await this.KubernetesPodService.logs(pod.Namespace, pod.Name);
|
res.Logs = await this.KubernetesPodService.logs(pod.Namespace, pod.Name, container.Name);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ class KubernetesStackLogsController {
|
||||||
Pods: [],
|
Pods: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const promises = _.map(app.Pods, this.generateLogsPromise);
|
const promises = _.flatMap(_.map(app.Pods, (pod) => _.map(pod.Containers, (container) => this.generateLogsPromise(pod, container))));
|
||||||
const result = await $allSettled(promises);
|
const result = await $allSettled(promises);
|
||||||
res.Pods = result.fulfilled;
|
res.Pods = result.fulfilled;
|
||||||
return res;
|
return res;
|
||||||
|
|
Loading…
Reference in New Issue