diff --git a/CHANGELOG.md b/CHANGELOG.md index d3bddbf94..ca8ad30f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ NOTE: As semantic versioning states all 0.y.z releases can contain breaking chan - [#89](https://github.com/kobsio/kobs/pull/89): Rework Opsgenie plugin to show alerts and incidents from Opsgenie. - [#91](https://github.com/kobsio/kobs/pull/91): Add force delete option for Kubernetes resources. - [#92](https://github.com/kobsio/kobs/pull/92): Preparation to build a own version of kobs using the [kobsio/app](https://github.com/kobsio/app) template. +- [#93](https://github.com/kobsio/kobs/pull/93): Show status of Kubernetes resource in the table of the resources plugin. ### Fixed diff --git a/plugins/core/src/utils/resources.tsx b/plugins/core/src/utils/resources.tsx index 1760cd623..6d57f6fdc 100644 --- a/plugins/core/src/utils/resources.tsx +++ b/plugins/core/src/utils/resources.tsx @@ -36,14 +36,18 @@ import { V1beta1PodSecurityPolicyList, V2beta1HorizontalPodAutoscalerList, } from '@kubernetes/client-node'; +import { SearchIcon, SquareIcon } from '@patternfly/react-icons'; import { IRow } from '@patternfly/react-table'; import { JSONPath } from 'jsonpath-plus'; import React from 'react'; -import { SearchIcon } from '@patternfly/react-icons'; import { getLabelSelector } from './manifests'; import { timeDifference } from './time'; +const COLOR_OK = 'var(--pf-global--success-color--100)'; +const COLOR_WARNING = 'var(--pf-global--warning-color--100)'; +const COLOR_DANGER = 'var(--pf-global--danger-color--100)'; + // IResourceItems is a list of resources for the cluster and namespace. The resources field contains a list of json // manifests, which are representing the resources. This interface is used as input for the rows function of the // resources object. @@ -149,6 +153,7 @@ export const resources: IResources = { 'Available', 'Node Selector', 'Age', + '', ], description: 'A DaemonSet ensures that all (or some) Nodes run a copy of a Pod.', isCRD: false, @@ -178,6 +183,15 @@ export const resources: IResources = { nodeSelector.push(`${key}=${daemonSet.spec?.template.spec?.nodeSelector[key]}`); } + let status = COLOR_WARNING; + if (daemonSet.status && daemonSet.status.numberMisscheduled > 0) { + status = COLOR_DANGER; + } else if (desired === current && desired === ready && desired === upToDate && desired === available) { + status = COLOR_OK; + } else if (current === 0 || ready === 0 || upToDate === 0 || available) { + status = COLOR_DANGER; + } + rows.push({ cells: [ daemonSet.metadata?.name, @@ -190,6 +204,9 @@ export const resources: IResources = { available, nodeSelector.join(', '), age, + + + , ], props: daemonSet, }); @@ -202,7 +219,7 @@ export const resources: IResources = { title: 'Daemon Sets', }, deployments: { - columns: ['Name', 'Namespace', 'Cluster', 'Ready', 'Up to date', 'Available', 'Age'], + columns: ['Name', 'Namespace', 'Cluster', 'Ready', 'Up to date', 'Available', 'Age', ''], description: 'A Deployment provides declarative updates for Pods and ReplicaSets.', isCRD: false, path: '/apis/apps/v1', @@ -225,6 +242,13 @@ export const resources: IResources = { const upToDate = deployment.status?.updatedReplicas ? deployment.status?.updatedReplicas : 0; const available = deployment.status?.availableReplicas ? deployment.status?.availableReplicas : 0; + let status = COLOR_WARNING; + if (shouldReady === ready && shouldReady === upToDate && shouldReady === available) { + status = COLOR_OK; + } else if (ready === 0 || upToDate === 0 || available) { + status = COLOR_DANGER; + } + rows.push({ cells: [ deployment.metadata?.name, @@ -234,6 +258,9 @@ export const resources: IResources = { upToDate, available, age, + + + , ], props: deployment, }); @@ -246,7 +273,7 @@ export const resources: IResources = { title: 'Deployments', }, jobs: { - columns: ['Name', 'Namespace', 'Cluster', 'Completions', 'Duration', 'Age'], + columns: ['Name', 'Namespace', 'Cluster', 'Completions', 'Duration', 'Age', ''], description: 'A Job creates one or more Pods and will continue to retry execution of the Pods until a specified number of them successfully terminate.', isCRD: false, @@ -272,6 +299,11 @@ export const resources: IResources = { ) : '-'; + let status = COLOR_OK; + if (completions !== completionsShould && completionsShould !== 0) { + status = COLOR_DANGER; + } + rows.push({ cells: [ job.metadata?.name, @@ -280,6 +312,9 @@ export const resources: IResources = { `${completions}/${completionsShould}`, duration, age, + + + , ], props: job, }); @@ -292,7 +327,7 @@ export const resources: IResources = { title: 'Jobs', }, pods: { - columns: ['Name', 'Namespace', 'Cluster', 'Ready', 'Status', 'Restarts', 'Age'], + columns: ['Name', 'Namespace', 'Cluster', 'Ready', 'Status', 'Restarts', 'Age', ''], description: 'Pods are the smallest deployable units of computing that you can create and manage in Kubernetes.', isCRD: false, path: '/api/v1', @@ -343,6 +378,17 @@ export const resources: IResources = { reason ? reason : phase, restarts, age, + + + , ], props: pod, }); @@ -355,7 +401,7 @@ export const resources: IResources = { title: 'Pods', }, replicasets: { - columns: ['Name', 'Namespace', 'Cluster', 'Desired', 'Current', 'Ready', 'Age'], + columns: ['Name', 'Namespace', 'Cluster', 'Desired', 'Current', 'Ready', 'Age', ''], description: "A ReplicaSet's purpose is to maintain a stable set of replica Pods running at any given time.", isCRD: false, path: '/apis/apps/v1', @@ -377,8 +423,24 @@ export const resources: IResources = { const current = replicaSet.status?.availableReplicas ? replicaSet.status?.availableReplicas : 0; const ready = replicaSet.status?.readyReplicas ? replicaSet.status?.readyReplicas : 0; + let status = COLOR_OK; + if (desired !== 0 && desired !== current && desired !== ready) { + status = COLOR_DANGER; + } + rows.push({ - cells: [replicaSet.metadata?.name, item.namespace, item.cluster, desired, current, ready, age], + cells: [ + replicaSet.metadata?.name, + item.namespace, + item.cluster, + desired, + current, + ready, + age, + + + , + ], props: replicaSet, }); } @@ -390,7 +452,7 @@ export const resources: IResources = { title: 'Replica Sets', }, statefulsets: { - columns: ['Name', 'Namespace', 'Cluster', 'Ready', 'Up to date', 'Age'], + columns: ['Name', 'Namespace', 'Cluster', 'Ready', 'Up to date', 'Age', ''], description: 'StatefulSet is the workload API object used to manage stateful applications.', isCRD: false, path: '/apis/apps/v1', @@ -412,8 +474,25 @@ export const resources: IResources = { const shouldReady = statefulSet.status?.replicas ? statefulSet.status?.replicas : 0; const upToDate = statefulSet.status?.updatedReplicas ? statefulSet.status?.updatedReplicas : 0; + let status = COLOR_WARNING; + if (shouldReady === 0 || (shouldReady === ready && shouldReady === upToDate)) { + status = COLOR_OK; + } else if (ready === 0 || upToDate === 0) { + status = COLOR_DANGER; + } + rows.push({ - cells: [statefulSet.metadata?.name, item.namespace, item.cluster, `${ready}/${shouldReady}`, upToDate, age], + cells: [ + statefulSet.metadata?.name, + item.namespace, + item.cluster, + `${ready}/${shouldReady}`, + upToDate, + age, + + + , + ], props: statefulSet, }); } @@ -779,7 +858,7 @@ export const resources: IResources = { title: 'Persistent Volumes', }, poddisruptionbudgets: { - columns: ['Name', 'Namespace', 'Cluster', 'Min. Available', 'Max. Unavailable', 'Allowed Disruptions', 'Age'], + columns: ['Name', 'Namespace', 'Cluster', 'Min. Available', 'Max. Unavailable', 'Allowed Disruptions', 'Age', ''], description: '', isCRD: false, path: '/apis/policy/v1beta1', @@ -798,6 +877,16 @@ export const resources: IResources = { ? timeDifference(new Date().getTime(), new Date(pdb.metadata.creationTimestamp.toString()).getTime()) : '-'; + let status = COLOR_OK; + if ( + !pdb.status || + !pdb.status.currentHealthy || + !pdb.status.desiredHealthy || + pdb.status.currentHealthy < pdb.status.desiredHealthy + ) { + status = COLOR_DANGER; + } + rows.push({ cells: [ pdb.metadata?.name, @@ -807,6 +896,9 @@ export const resources: IResources = { maxUnavailable, allowedDisruptions, age, + + + , ], props: pdb, });