|
| 1 | +import { TitleCasePipe } from '@angular/common'; |
| 2 | +import { Component, Input } from '@angular/core'; |
| 3 | +import { Store } from '@ngrx/store'; |
| 4 | +import * as moment from 'moment'; |
| 5 | +import { of } from 'rxjs'; |
| 6 | +import { filter, map } from 'rxjs/operators'; |
| 7 | + |
| 8 | +import { AppState } from '../../../../../../../store/src/app-state'; |
| 9 | +import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog.service'; |
| 10 | +import { selectEntity } from '../../../../../../../store/src/selectors/api.selectors'; |
| 11 | +import { BooleanIndicatorType } from '../../../../../shared/components/boolean-indicator/boolean-indicator.component'; |
| 12 | +import { ITableListDataSource } from '../../../../../shared/components/list/data-sources-controllers/list-data-source-types'; |
| 13 | +import { |
| 14 | + TableCellBooleanIndicatorComponent, |
| 15 | + TableCellBooleanIndicatorComponentConfig, |
| 16 | +} from '../../../../../shared/components/list/list-table/table-cell-boolean-indicator/table-cell-boolean-indicator.component'; |
| 17 | +import { |
| 18 | + TableCellIconComponent, |
| 19 | + TableCellIconComponentConfig, |
| 20 | +} from '../../../../../shared/components/list/list-table/table-cell-icon/table-cell-icon.component'; |
| 21 | +import { ITableColumn } from '../../../../../shared/components/list/list-table/table.types'; |
| 22 | +import { CardCell } from '../../../../../shared/components/list/list.types'; |
| 23 | +import { KUBERNETES_ENDPOINT_TYPE, kubernetesPodsEntityType } from '../../../kubernetes-entity-factory'; |
| 24 | +import { Container, ContainerState, ContainerStatus, InitContainer, KubernetesPod } from '../../../store/kube.types'; |
| 25 | + |
| 26 | +export interface ContainerForTable { |
| 27 | + isInit: boolean; |
| 28 | + container: Container | InitContainer; |
| 29 | + containerStatus: ContainerStatus; |
| 30 | +} |
| 31 | + |
| 32 | +@Component({ |
| 33 | + selector: 'app-kubernetes-pod-containers', |
| 34 | + templateUrl: './kubernetes-pod-containers.component.html', |
| 35 | + styleUrls: ['./kubernetes-pod-containers.component.scss'], |
| 36 | + providers: [ |
| 37 | + TitleCasePipe |
| 38 | + ] |
| 39 | +}) |
| 40 | +export class KubernetesPodContainersComponent extends CardCell<KubernetesPod> { |
| 41 | + |
| 42 | + private entityConfig = entityCatalog.getEntity(KUBERNETES_ENDPOINT_TYPE, kubernetesPodsEntityType); |
| 43 | + |
| 44 | + @Input() |
| 45 | + set row(row: KubernetesPod) { |
| 46 | + if (!row || !!this.containerDataSource) { |
| 47 | + return; |
| 48 | + } |
| 49 | + this.containerDataSource = { |
| 50 | + isTableLoading$: of(false), |
| 51 | + connect: () => this.store.select<KubernetesPod>(selectEntity(this.entityConfig.entityKey, row.metadata.uid)).pipe( |
| 52 | + filter(pod => !!pod), |
| 53 | + map(pod => this.map(pod)), |
| 54 | + ), |
| 55 | + disconnect: () => { }, |
| 56 | + trackBy: (index, container: ContainerForTable) => container.container.name, |
| 57 | + }; |
| 58 | + } |
| 59 | + |
| 60 | + constructor( |
| 61 | + private store: Store<AppState>, |
| 62 | + private titleCase: TitleCasePipe, |
| 63 | + ) { |
| 64 | + super(); |
| 65 | + } |
| 66 | + |
| 67 | + private readyBoolConfig: TableCellBooleanIndicatorComponentConfig<ContainerForTable> = { |
| 68 | + isEnabled: (row: ContainerForTable) => row.containerStatus.ready, |
| 69 | + type: BooleanIndicatorType.yesNo, |
| 70 | + subtle: false, |
| 71 | + showText: false |
| 72 | + }; |
| 73 | + |
| 74 | + private iconConfig: TableCellIconComponentConfig<ContainerForTable> = { |
| 75 | + getIcon: (row: ContainerForTable) => row.isInit ? |
| 76 | + { |
| 77 | + icon: 'border_clear', |
| 78 | + font: '', |
| 79 | + tooltip: 'Init Container' |
| 80 | + } : { |
| 81 | + icon: 'border_outer', |
| 82 | + font: '', |
| 83 | + tooltip: 'Container' |
| 84 | + }, |
| 85 | + }; |
| 86 | + |
| 87 | + public containerDataSource: ITableListDataSource<ContainerForTable>; |
| 88 | + public columns: ITableColumn<ContainerForTable>[] = [ |
| 89 | + { |
| 90 | + columnId: 'icon', |
| 91 | + headerCell: () => '', |
| 92 | + cellComponent: TableCellIconComponent, |
| 93 | + cellConfig: this.iconConfig, |
| 94 | + cellFlex: '0 0 53px', |
| 95 | + }, |
| 96 | + { |
| 97 | + columnId: 'name', |
| 98 | + headerCell: () => 'Container Name', |
| 99 | + cellDefinition: { |
| 100 | + valuePath: 'container.name' |
| 101 | + }, |
| 102 | + cellFlex: '2', |
| 103 | + }, |
| 104 | + { |
| 105 | + columnId: 'image', |
| 106 | + headerCell: () => 'Image', |
| 107 | + cellDefinition: { |
| 108 | + valuePath: 'container.image' |
| 109 | + }, |
| 110 | + cellFlex: '3', |
| 111 | + }, |
| 112 | + { |
| 113 | + columnId: 'ready', |
| 114 | + headerCell: () => 'Ready', |
| 115 | + cellComponent: TableCellBooleanIndicatorComponent, |
| 116 | + cellConfig: this.readyBoolConfig, |
| 117 | + cellFlex: '1', |
| 118 | + }, |
| 119 | + { |
| 120 | + columnId: 'status', |
| 121 | + headerCell: () => 'State', |
| 122 | + cellDefinition: { |
| 123 | + getValue: cft => { |
| 124 | + if (!cft.containerStatus.state) { |
| 125 | + return 'Unknown'; |
| 126 | + } |
| 127 | + const entries = Object.entries(cft.containerStatus.state); |
| 128 | + if (!entries.length) { |
| 129 | + return 'Unknown'; |
| 130 | + } |
| 131 | + const sorted = entries.sort((a, b) => { |
| 132 | + const aStarted = moment(a[1].startedAt); |
| 133 | + const bStarted = moment(b[1].startedAt); |
| 134 | + |
| 135 | + return aStarted.isBefore(bStarted) ? -1 : |
| 136 | + aStarted.isAfter(bStarted) ? 1 : 0; |
| 137 | + |
| 138 | + }); |
| 139 | + return this.containerStatusToString(sorted[0][0], sorted[0][1]); |
| 140 | + } |
| 141 | + }, |
| 142 | + cellFlex: '2' |
| 143 | + }, |
| 144 | + { |
| 145 | + columnId: 'restarts', |
| 146 | + headerCell: () => 'Restarts', |
| 147 | + cellDefinition: { |
| 148 | + getValue: cft => cft.containerStatus.restartCount.toString() |
| 149 | + }, |
| 150 | + cellFlex: '1', |
| 151 | + }, |
| 152 | + { |
| 153 | + columnId: 'probes', |
| 154 | + headerCell: () => 'Probes (L:R)', |
| 155 | + cellDefinition: { |
| 156 | + getValue: cft => { |
| 157 | + if (cft.isInit) { |
| 158 | + return ''; |
| 159 | + } |
| 160 | + const container: Container = cft.container as Container; |
| 161 | + return cft.isInit ? '' : `${container.livenessProbe ? 'on' : 'off'}:${container.readinessProbe ? 'on' : 'off'}`; |
| 162 | + } |
| 163 | + }, |
| 164 | + cellFlex: '1', |
| 165 | + }, |
| 166 | + ]; |
| 167 | + |
| 168 | + private map(row: KubernetesPod): ContainerForTable[] { |
| 169 | + const containerStatus = row.status.containerStatuses || []; |
| 170 | + const initContainerStatuses = row.status.initContainerStatuses || []; |
| 171 | + const containerStatusWithContainers: ContainerForTable[] = [ |
| 172 | + ...containerStatus.map(c => this.createContainerForTable(c, row.spec.containers)), |
| 173 | + ...initContainerStatuses.map(c => this.createContainerForTable(c, row.spec.initContainers, true)) |
| 174 | + ]; |
| 175 | + return containerStatusWithContainers.sort((a, b) => a.container.name.localeCompare(b.container.name)); |
| 176 | + } |
| 177 | + |
| 178 | + private createContainerForTable(containerStatus: ContainerStatus, containers: (Container | InitContainer)[], isInit = false): |
| 179 | + ContainerForTable { |
| 180 | + const containerForTable: ContainerForTable = { |
| 181 | + isInit, |
| 182 | + containerStatus, |
| 183 | + container: containers.find(c => c.name === containerStatus.name) |
| 184 | + }; |
| 185 | + return containerForTable; |
| 186 | + } |
| 187 | + |
| 188 | + private containerStatusToString(state: string, status: ContainerState): string { |
| 189 | + const exitCode = status.exitCode ? `:${status.exitCode}` : ''; |
| 190 | + const signal = status.signal ? `:${status.signal}` : ''; |
| 191 | + const reason = status.reason ? ` (${status.reason}${exitCode || signal})` : ''; |
| 192 | + return `${this.titleCase.transform(state)}${reason}`; |
| 193 | + } |
| 194 | +} |
0 commit comments