diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 58dbbf944f..6d3331c447 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -62,6 +62,8 @@ export enum ClusterMetricsResourceType { VolumeClaim = "VolumeClaim", ReplicaSet = "ReplicaSet", DaemonSet = "DaemonSet", + Job = "Job", + Namespace = "Namespace" } export type ClusterRefreshOptions = { diff --git a/src/renderer/api/endpoints/cluster.api.ts b/src/renderer/api/endpoints/cluster.api.ts index 75f15b5a5b..f0879fa501 100644 --- a/src/renderer/api/endpoints/cluster.api.ts +++ b/src/renderer/api/endpoints/cluster.api.ts @@ -26,30 +26,30 @@ import { KubeApi } from "../kube-api"; export class ClusterApi extends KubeApi { static kind = "Cluster"; static namespaced = true; +} - async getMetrics(nodeNames: string[], params?: IMetricsReqParams): Promise { - const nodes = nodeNames.join("|"); - const opts = { category: "cluster", nodes }; +export function getMetricsByNodeNames(nodeNames: string[], params?: IMetricsReqParams): Promise { + const nodes = nodeNames.join("|"); + const opts = { category: "cluster", nodes }; - return metricsApi.getMetrics({ - memoryUsage: opts, - workloadMemoryUsage: opts, - memoryRequests: opts, - memoryLimits: opts, - memoryCapacity: opts, - memoryAllocatableCapacity: opts, - cpuUsage: opts, - cpuRequests: opts, - cpuLimits: opts, - cpuCapacity: opts, - cpuAllocatableCapacity: opts, - podUsage: opts, - podCapacity: opts, - podAllocatableCapacity: opts, - fsSize: opts, - fsUsage: opts - }, params); - } + return metricsApi.getMetrics({ + memoryUsage: opts, + workloadMemoryUsage: opts, + memoryRequests: opts, + memoryLimits: opts, + memoryCapacity: opts, + memoryAllocatableCapacity: opts, + cpuUsage: opts, + cpuRequests: opts, + cpuLimits: opts, + cpuCapacity: opts, + cpuAllocatableCapacity: opts, + podUsage: opts, + podCapacity: opts, + podAllocatableCapacity: opts, + fsSize: opts, + fsUsage: opts + }, params); } export enum ClusterStatus { diff --git a/src/renderer/api/endpoints/daemon-set.api.ts b/src/renderer/api/endpoints/daemon-set.api.ts index 43419b5ac1..cb584806d4 100644 --- a/src/renderer/api/endpoints/daemon-set.api.ts +++ b/src/renderer/api/endpoints/daemon-set.api.ts @@ -20,11 +20,12 @@ */ import get from "lodash/get"; -import type { IPodContainer } from "./pods.api"; import { IAffinity, WorkloadKubeObject } from "../workload-kube-object"; import { autoBind } from "../../utils"; import { KubeApi } from "../kube-api"; +import { metricsApi } from "./metrics.api"; import type { KubeJsonApiData } from "../kube-json-api"; +import type { IPodContainer, IPodMetrics } from "./pods.api"; export class DaemonSet extends WorkloadKubeObject { static kind = "DaemonSet"; @@ -97,6 +98,24 @@ export class DaemonSet extends WorkloadKubeObject { } } -export const daemonSetApi = new KubeApi({ +export class DaemonSetApi extends KubeApi { +} + +export function getMetricsForDaemonSets(daemonsets: DaemonSet[], namespace: string, selector = ""): Promise { + const podSelector = daemonsets.map(daemonset => `${daemonset.getName()}-[[:alnum:]]{5}`).join("|"); + const opts = { category: "pods", pods: podSelector, namespace, selector }; + + return metricsApi.getMetrics({ + cpuUsage: opts, + memoryUsage: opts, + fsUsage: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); +} + +export const daemonSetApi = new DaemonSetApi({ objectConstructor: DaemonSet, }); diff --git a/src/renderer/api/endpoints/deployment.api.ts b/src/renderer/api/endpoints/deployment.api.ts index ef5b1c9ca7..0d5bef48dd 100644 --- a/src/renderer/api/endpoints/deployment.api.ts +++ b/src/renderer/api/endpoints/deployment.api.ts @@ -24,6 +24,8 @@ import moment from "moment"; import { IAffinity, WorkloadKubeObject } from "../workload-kube-object"; import { autoBind } from "../../utils"; import { KubeApi } from "../kube-api"; +import { metricsApi } from "./metrics.api"; +import type { IPodMetrics } from "./pods.api"; import type { KubeJsonApiData } from "../kube-json-api"; export class DeploymentApi extends KubeApi { @@ -68,6 +70,21 @@ export class DeploymentApi extends KubeApi { } } +export function getMetricsForDeployments(deployments: Deployment[], namespace: string, selector = ""): Promise { + const podSelector = deployments.map(deployment => `${deployment.getName()}-[[:alnum:]]{9,}-[[:alnum:]]{5}`).join("|"); + const opts = { category: "pods", pods: podSelector, namespace, selector }; + + return metricsApi.getMetrics({ + cpuUsage: opts, + memoryUsage: opts, + fsUsage: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); +} + interface IContainerProbe { httpGet?: { path?: string; diff --git a/src/renderer/api/endpoints/ingress.api.ts b/src/renderer/api/endpoints/ingress.api.ts index a65af0e326..31745f30c4 100644 --- a/src/renderer/api/endpoints/ingress.api.ts +++ b/src/renderer/api/endpoints/ingress.api.ts @@ -26,18 +26,19 @@ import { KubeApi } from "../kube-api"; import type { KubeJsonApiData } from "../kube-json-api"; export class IngressApi extends KubeApi { - getMetrics(ingress: string, namespace: string): Promise { - const opts = { category: "ingress", ingress, namespace }; +} - return metricsApi.getMetrics({ - bytesSentSuccess: opts, - bytesSentFailure: opts, - requestDurationSeconds: opts, - responseDurationSeconds: opts - }, { - namespace, - }); - } +export function getMetricsForIngress(ingress: string, namespace: string): Promise { + const opts = { category: "ingress", ingress }; + + return metricsApi.getMetrics({ + bytesSentSuccess: opts, + bytesSentFailure: opts, + requestDurationSeconds: opts, + responseDurationSeconds: opts + }, { + namespace, + }); } export interface IIngressMetrics { diff --git a/src/renderer/api/endpoints/job.api.ts b/src/renderer/api/endpoints/job.api.ts index f9e3188021..823a04bef9 100644 --- a/src/renderer/api/endpoints/job.api.ts +++ b/src/renderer/api/endpoints/job.api.ts @@ -22,10 +22,11 @@ import get from "lodash/get"; import { autoBind } from "../../utils"; import { IAffinity, WorkloadKubeObject } from "../workload-kube-object"; -import type { IPodContainer } from "./pods.api"; import { KubeApi } from "../kube-api"; +import { metricsApi } from "./metrics.api"; import type { JsonApiParams } from "../json-api"; import type { KubeJsonApiData } from "../kube-json-api"; +import type { IPodContainer, IPodMetrics } from "./pods.api"; export class Job extends WorkloadKubeObject { static kind = "Job"; @@ -129,6 +130,24 @@ export class Job extends WorkloadKubeObject { } } -export const jobApi = new KubeApi({ +export class JobApi extends KubeApi { +} + +export function getMetricsForJobs(jobs: Job[], namespace: string, selector = ""): Promise { + const podSelector = jobs.map(job => `${job.getName()}-[[:alnum:]]{5}`).join("|"); + const opts = { category: "pods", pods: podSelector, namespace, selector }; + + return metricsApi.getMetrics({ + cpuUsage: opts, + memoryUsage: opts, + fsUsage: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); +} + +export const jobApi = new JobApi({ objectConstructor: Job, }); diff --git a/src/renderer/api/endpoints/metrics.api.ts b/src/renderer/api/endpoints/metrics.api.ts index 3f5b6e66e7..b0e3c0bf9d 100644 --- a/src/renderer/api/endpoints/metrics.api.ts +++ b/src/renderer/api/endpoints/metrics.api.ts @@ -60,6 +60,15 @@ export interface IMetricsReqParams { namespace?: string; // rbac-proxy validation param } +export interface IResourceMetrics { + [metric: string]: T; + cpuUsage: T; + memoryUsage: T; + fsUsage: T; + networkReceive: T; + networkTransmit: T; +} + export const metricsApi = { async getMetrics(query: T, reqParams: IMetricsReqParams = {}): Promise { const { range = 3600, step = 60, namespace } = reqParams; diff --git a/src/renderer/api/endpoints/namespaces.api.ts b/src/renderer/api/endpoints/namespaces.api.ts index 63dd6a2ab0..208edb023f 100644 --- a/src/renderer/api/endpoints/namespaces.api.ts +++ b/src/renderer/api/endpoints/namespaces.api.ts @@ -22,6 +22,8 @@ import { KubeApi } from "../kube-api"; import { KubeObject } from "../kube-object"; import { autoBind } from "../../utils"; +import { metricsApi } from "./metrics.api"; +import type { IPodMetrics } from "./pods.api"; import type { KubeJsonApiData } from "../kube-json-api"; export enum NamespaceStatus { @@ -50,6 +52,23 @@ export class Namespace extends KubeObject { } } -export const namespacesApi = new KubeApi({ +export class NamespaceApi extends KubeApi { +} + +export function getMetricsForNamespace(namespace: string, selector = ""): Promise { + const opts = { category: "pods", pods: ".*", namespace, selector }; + + return metricsApi.getMetrics({ + cpuUsage: opts, + memoryUsage: opts, + fsUsage: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); +} + +export const namespacesApi = new NamespaceApi({ objectConstructor: Namespace, }); diff --git a/src/renderer/api/endpoints/nodes.api.ts b/src/renderer/api/endpoints/nodes.api.ts index d2c35761ca..21f7776c58 100644 --- a/src/renderer/api/endpoints/nodes.api.ts +++ b/src/renderer/api/endpoints/nodes.api.ts @@ -26,20 +26,21 @@ import { KubeApi } from "../kube-api"; import type { KubeJsonApiData } from "../kube-json-api"; export class NodesApi extends KubeApi { - getMetrics(): Promise { - const opts = { category: "nodes" }; +} - return metricsApi.getMetrics({ - memoryUsage: opts, - workloadMemoryUsage: opts, - memoryCapacity: opts, - memoryAllocatableCapacity: opts, - cpuUsage: opts, - cpuCapacity: opts, - fsSize: opts, - fsUsage: opts - }); - } +export function getMetricsForAllNodes(): Promise { + const opts = { category: "nodes"}; + + return metricsApi.getMetrics({ + memoryUsage: opts, + workloadMemoryUsage: opts, + memoryCapacity: opts, + memoryAllocatableCapacity: opts, + cpuUsage: opts, + cpuCapacity: opts, + fsSize: opts, + fsUsage: opts + }); } export interface INodeMetrics { diff --git a/src/renderer/api/endpoints/persistent-volume-claims.api.ts b/src/renderer/api/endpoints/persistent-volume-claims.api.ts index e8b50683c9..356122569a 100644 --- a/src/renderer/api/endpoints/persistent-volume-claims.api.ts +++ b/src/renderer/api/endpoints/persistent-volume-claims.api.ts @@ -27,14 +27,15 @@ import { KubeApi } from "../kube-api"; import type { KubeJsonApiData } from "../kube-json-api"; export class PersistentVolumeClaimsApi extends KubeApi { - getMetrics(pvcName: string, namespace: string): Promise { - return metricsApi.getMetrics({ - diskUsage: { category: "pvc", pvc: pvcName, namespace }, - diskCapacity: { category: "pvc", pvc: pvcName, namespace } - }, { - namespace - }); - } +} + +export function getMetricsForPvc(pvc: PersistentVolumeClaim): Promise { + return metricsApi.getMetrics({ + diskUsage: { category: "pvc", pvc: pvc.getName() }, + diskCapacity: { category: "pvc", pvc: pvc.getName() } + }, { + namespace: pvc.getNs() + }); } export interface IPvcMetrics { diff --git a/src/renderer/api/endpoints/pods.api.ts b/src/renderer/api/endpoints/pods.api.ts index 5b85a3d42b..e4d33c75bc 100644 --- a/src/renderer/api/endpoints/pods.api.ts +++ b/src/renderer/api/endpoints/pods.api.ts @@ -31,38 +31,38 @@ export class PodsApi extends KubeApi { return this.request.get(path, { query }); } +} - getMetrics(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise { - const podSelector = pods.map(pod => pod.getName()).join("|"); - const opts = { category: "pods", pods: podSelector, namespace, selector }; +export function getMetricsForPods(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise { + const podSelector = pods.map(pod => pod.getName()).join("|"); + const opts = { category: "pods", pods: podSelector, namespace, selector }; - return metricsApi.getMetrics({ - cpuUsage: opts, - cpuRequests: opts, - cpuLimits: opts, - memoryUsage: opts, - memoryRequests: opts, - memoryLimits: opts, - fsUsage: opts, - networkReceive: opts, - networkTransmit: opts, - }, { - namespace, - }); - } + return metricsApi.getMetrics({ + cpuUsage: opts, + cpuRequests: opts, + cpuLimits: opts, + memoryUsage: opts, + memoryRequests: opts, + memoryLimits: opts, + fsUsage: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); } export interface IPodMetrics { [metric: string]: T; cpuUsage: T; - cpuRequests: T; - cpuLimits: T; memoryUsage: T; - memoryRequests: T; - memoryLimits: T; fsUsage: T; networkReceive: T; networkTransmit: T; + cpuRequests?: T; + cpuLimits?: T; + memoryRequests?: T; + memoryLimits?: T; } // Reference: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-log-pod-v1-core diff --git a/src/renderer/api/endpoints/replica-set.api.ts b/src/renderer/api/endpoints/replica-set.api.ts index e2311c6b87..4632e1bfad 100644 --- a/src/renderer/api/endpoints/replica-set.api.ts +++ b/src/renderer/api/endpoints/replica-set.api.ts @@ -22,8 +22,9 @@ import get from "lodash/get"; import { autoBind } from "../../utils"; import { WorkloadKubeObject } from "../workload-kube-object"; -import type { IPodContainer, Pod } from "./pods.api"; import { KubeApi } from "../kube-api"; +import { metricsApi } from "./metrics.api"; +import type { IPodContainer, IPodMetrics, Pod } from "./pods.api"; import type { KubeJsonApiData } from "../kube-json-api"; export class ReplicaSetApi extends KubeApi { @@ -49,6 +50,21 @@ export class ReplicaSetApi extends KubeApi { } } +export function getMetricsForReplicaSets(replicasets: ReplicaSet[], namespace: string, selector = ""): Promise { + const podSelector = replicasets.map(replicaset => `${replicaset.getName()}-[[:alnum:]]{5}`).join("|"); + const opts = { category: "pods", pods: podSelector, namespace, selector }; + + return metricsApi.getMetrics({ + cpuUsage: opts, + memoryUsage: opts, + fsUsage: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); +} + export class ReplicaSet extends WorkloadKubeObject { static kind = "ReplicaSet"; static namespaced = true; diff --git a/src/renderer/api/endpoints/stateful-set.api.ts b/src/renderer/api/endpoints/stateful-set.api.ts index b91e06c25c..21f4c6e02e 100644 --- a/src/renderer/api/endpoints/stateful-set.api.ts +++ b/src/renderer/api/endpoints/stateful-set.api.ts @@ -20,10 +20,11 @@ */ import get from "lodash/get"; -import type { IPodContainer } from "./pods.api"; import { IAffinity, WorkloadKubeObject } from "../workload-kube-object"; import { autoBind } from "../../utils"; import { KubeApi } from "../kube-api"; +import { metricsApi } from "./metrics.api"; +import type { IPodContainer, IPodMetrics } from "./pods.api"; import type { KubeJsonApiData } from "../kube-json-api"; export class StatefulSetApi extends KubeApi { @@ -49,6 +50,21 @@ export class StatefulSetApi extends KubeApi { } } +export function getMetricsForStatefulSets(statefulSets: StatefulSet[], namespace: string, selector = ""): Promise { + const podSelector = statefulSets.map(statefulset => `${statefulset.getName()}-[[:digit:]]+`).join("|"); + const opts = { category: "pods", pods: podSelector, namespace, selector }; + + return metricsApi.getMetrics({ + cpuUsage: opts, + memoryUsage: opts, + fsUsage: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); +} + export class StatefulSet extends WorkloadKubeObject { static kind = "StatefulSet"; static namespaced = true; diff --git a/src/renderer/components/+cluster/cluster-overview.store.ts b/src/renderer/components/+cluster/cluster-overview.store.ts index 846f2466b4..547eb6832f 100644 --- a/src/renderer/components/+cluster/cluster-overview.store.ts +++ b/src/renderer/components/+cluster/cluster-overview.store.ts @@ -21,7 +21,7 @@ import { action, observable, reaction, when, makeObservable } from "mobx"; import { KubeObjectStore } from "../../kube-object.store"; -import { Cluster, clusterApi, IClusterMetrics } from "../../api/endpoints"; +import { Cluster, clusterApi, getMetricsByNodeNames, IClusterMetrics } from "../../api/endpoints"; import { autoBind, createStorage } from "../../utils"; import { IMetricsReqParams, normalizeMetrics } from "../../api/endpoints/metrics.api"; import { nodesStore } from "../+nodes/nodes.store"; @@ -101,7 +101,7 @@ export class ClusterOverviewStore extends KubeObjectStore implements Cl const { masterNodes, workerNodes } = nodesStore; const nodes = this.metricNodeRole === MetricNodeRole.MASTER && masterNodes.length ? masterNodes : workerNodes; - this.metrics = await clusterApi.getMetrics(nodes.map(node => node.getName()), params); + this.metrics = await getMetricsByNodeNames(nodes.map(node => node.getName()), params); this.metricsLoaded = true; } diff --git a/src/renderer/components/+namespaces/namespace-details.tsx b/src/renderer/components/+namespaces/namespace-details.tsx index 95103f553b..f61db41711 100644 --- a/src/renderer/components/+namespaces/namespace-details.tsx +++ b/src/renderer/components/+namespaces/namespace-details.tsx @@ -22,28 +22,44 @@ import "./namespace-details.scss"; import React from "react"; -import { computed, makeObservable } from "mobx"; -import { observer } from "mobx-react"; +import { computed, makeObservable, observable, reaction } from "mobx"; +import { disposeOnUnmount, observer } from "mobx-react"; import { DrawerItem } from "../drawer"; import { cssNames } from "../../utils"; -import type { Namespace } from "../../api/endpoints"; +import { getMetricsForNamespace, IPodMetrics, Namespace } from "../../api/endpoints"; import { getDetailsUrl, KubeObjectDetailsProps } from "../kube-object"; import { Link } from "react-router-dom"; import { Spinner } from "../spinner"; import { resourceQuotaStore } from "../+config-resource-quotas/resource-quotas.store"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { limitRangeStore } from "../+config-limit-ranges/limit-ranges.store"; +import { ResourceMetrics } from "../resource-metrics"; +import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; +import { ClusterMetricsResourceType } from "../../../main/cluster"; +import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; interface Props extends KubeObjectDetailsProps { } @observer export class NamespaceDetails extends React.Component { + @observable metrics: IPodMetrics = null; + constructor(props: Props) { super(props); makeObservable(this); } + @disposeOnUnmount + clean = reaction(() => this.props.object, () => { + this.metrics = null; + }); + + componentDidMount() { + resourceQuotaStore.reloadAll(); + limitRangeStore.reloadAll(); + } + @computed get quotas() { const namespace = this.props.object.getName(); @@ -56,9 +72,8 @@ export class NamespaceDetails extends React.Component { return limitRangeStore.getAllByNs(namespace); } - componentDidMount() { - resourceQuotaStore.reloadAll(); - limitRangeStore.reloadAll(); + async loadMetrics() { + this.metrics = await getMetricsForNamespace(this.props.object.getName(), ""); } render() { @@ -66,9 +81,18 @@ export class NamespaceDetails extends React.Component { if (!namespace) return null; const status = namespace.getStatus(); + const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Namespace); return (
+ {!isMetricHidden && ( + + + + )} diff --git a/src/renderer/components/+network-ingresses/ingress-details.tsx b/src/renderer/components/+network-ingresses/ingress-details.tsx index 8e3c836b34..995e4d3b0f 100644 --- a/src/renderer/components/+network-ingresses/ingress-details.tsx +++ b/src/renderer/components/+network-ingresses/ingress-details.tsx @@ -23,16 +23,15 @@ import "./ingress-details.scss"; import React from "react"; import { disposeOnUnmount, observer } from "mobx-react"; -import { reaction } from "mobx"; +import { observable, reaction } from "mobx"; import { DrawerItem, DrawerTitle } from "../drawer"; import type { ILoadBalancerIngress, Ingress } from "../../api/endpoints"; import { Table, TableCell, TableHead, TableRow } from "../table"; -import { ingressStore } from "./ingress.store"; import { ResourceMetrics } from "../resource-metrics"; import type { KubeObjectDetailsProps } from "../kube-object"; import { IngressCharts } from "./ingress-charts"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; -import { getBackendServiceNamePort } from "../../api/endpoints/ingress.api"; +import { getBackendServiceNamePort, getMetricsForIngress, IIngressMetrics } from "../../api/endpoints/ingress.api"; import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../main/cluster"; @@ -41,13 +40,17 @@ interface Props extends KubeObjectDetailsProps { @observer export class IngressDetails extends React.Component { + @observable metrics: IIngressMetrics = null; + @disposeOnUnmount clean = reaction(() => this.props.object, () => { - ingressStore.reset(); + this.metrics = null; }); - componentWillUnmount() { - ingressStore.reset(); + async loadMetrics() { + const { object: ingress } = this.props; + + this.metrics = await getMetricsForIngress(ingress.getName(), ingress.getNs()); } renderPaths(ingress: Ingress) { @@ -124,7 +127,7 @@ export class IngressDetails extends React.Component { const { spec, status } = ingress; const ingressPoints = status?.loadBalancer?.ingress; - const { metrics } = ingressStore; + const { metrics } = this; const metricTabs = [ "Network", "Duration", @@ -136,7 +139,7 @@ export class IngressDetails extends React.Component {
{!isMetricHidden && ( ingressStore.loadMetrics(ingress)} + loader={this.loadMetrics} tabs={metricTabs} object={ingress} params={{ metrics }} > diff --git a/src/renderer/components/+network-ingresses/ingress.store.ts b/src/renderer/components/+network-ingresses/ingress.store.ts index 3d17aefa3c..72190a5103 100644 --- a/src/renderer/components/+network-ingresses/ingress.store.ts +++ b/src/renderer/components/+network-ingresses/ingress.store.ts @@ -18,31 +18,12 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -import { observable, makeObservable } from "mobx"; -import { KubeObjectStore } from "../../kube-object.store"; -import { autoBind } from "../../utils"; -import { IIngressMetrics, Ingress, ingressApi } from "../../api/endpoints"; import { apiManager } from "../../api/api-manager"; +import { Ingress, ingressApi } from "../../api/endpoints"; +import { KubeObjectStore } from "../../kube-object.store"; export class IngressStore extends KubeObjectStore { api = ingressApi; - @observable metrics: IIngressMetrics = null; - - constructor() { - super(); - - makeObservable(this); - autoBind(this); - } - - async loadMetrics(ingress: Ingress) { - this.metrics = await this.api.getMetrics(ingress.getName(), ingress.getNs()); - } - - reset() { - this.metrics = null; - } } export const ingressStore = new IngressStore(); diff --git a/src/renderer/components/+nodes/node-details.tsx b/src/renderer/components/+nodes/node-details.tsx index a12109235b..56ce3e53e2 100644 --- a/src/renderer/components/+nodes/node-details.tsx +++ b/src/renderer/components/+nodes/node-details.tsx @@ -27,36 +27,46 @@ import kebabCase from "lodash/kebabCase"; import { disposeOnUnmount, observer } from "mobx-react"; import { DrawerItem, DrawerItemLabels } from "../drawer"; import { Badge } from "../badge"; -import { nodesStore } from "./nodes.store"; import { ResourceMetrics } from "../resource-metrics"; import { podsStore } from "../+workloads-pods/pods.store"; import type { KubeObjectDetailsProps } from "../kube-object"; -import type { Node } from "../../api/endpoints"; +import { getMetricsByNodeNames, IClusterMetrics, Node } from "../../api/endpoints"; import { NodeCharts } from "./node-charts"; -import { reaction } from "mobx"; +import { makeObservable, observable, reaction } from "mobx"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../main/cluster"; import { NodeDetailsResources } from "./node-details-resources"; import { DrawerTitle } from "../drawer/drawer-title"; +import { boundMethod } from "../../utils"; interface Props extends KubeObjectDetailsProps { } @observer export class NodeDetails extends React.Component { + @observable metrics: Partial; + + constructor(props: Props) { + super(props); + makeObservable(this); + } + @disposeOnUnmount clean = reaction(() => this.props.object.getName(), () => { - nodesStore.nodeMetrics = null; + this.metrics = null; }); async componentDidMount() { podsStore.reloadAll(); } - componentWillUnmount() { - nodesStore.nodeMetrics = null; + @boundMethod + async loadMetrics() { + const { object: node } = this.props; + + this.metrics = await getMetricsByNodeNames([node.getName()]); } render() { @@ -68,7 +78,7 @@ export class NodeDetails extends React.Component { const conditions = node.getActiveConditions(); const taints = node.getTaints(); const childPods = podsStore.getPodsByNode(node.getName()); - const metrics = nodesStore.nodeMetrics; + const { metrics } = this; const metricTabs = [ "CPU", "Memory", @@ -81,7 +91,7 @@ export class NodeDetails extends React.Component {
{!isMetricHidden && podsStore.isLoaded && ( nodesStore.loadMetrics(node.getName())} + loader={this.loadMetrics} tabs={metricTabs} object={node} params={{ metrics }} > diff --git a/src/renderer/components/+nodes/nodes.store.ts b/src/renderer/components/+nodes/nodes.store.ts index 676643db62..43b5e32cca 100644 --- a/src/renderer/components/+nodes/nodes.store.ts +++ b/src/renderer/components/+nodes/nodes.store.ts @@ -18,22 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - import { sum } from "lodash"; -import { action, computed, observable, makeObservable } from "mobx"; -import { clusterApi, IClusterMetrics, INodeMetrics, Node, nodesApi } from "../../api/endpoints"; -import { autoBind } from "../../utils"; -import { KubeObjectStore } from "../../kube-object.store"; +import { computed, makeObservable } from "mobx"; + import { apiManager } from "../../api/api-manager"; +import { Node, nodesApi } from "../../api/endpoints"; +import { KubeObjectStore } from "../../kube-object.store"; +import { autoBind } from "../../utils"; export class NodesStore extends KubeObjectStore { api = nodesApi; - @observable metrics: Partial = {}; - @observable nodeMetrics: Partial = null; - @observable metricsLoading = false; - @observable metricsLoaded = false; - constructor() { super(); @@ -41,23 +36,6 @@ export class NodesStore extends KubeObjectStore { autoBind(this); } - @action - async loadUsageMetrics() { - this.metricsLoading = true; - - try { - this.metrics = await nodesApi.getMetrics(); - this.metricsLoaded = true; - } finally { - this.metricsLoading = false; - } - } - - @action - async loadMetrics(nodeName: string) { - this.nodeMetrics = await clusterApi.getMetrics([nodeName]); - } - @computed get masterNodes() { return this.items.filter(node => node.getRoleLabels().includes("master")); } @@ -66,41 +44,9 @@ export class NodesStore extends KubeObjectStore { return this.items.filter(node => !node.getRoleLabels().includes("master")); } - getLastMetricValues(node: Node, metricNames: string[]): number[] { - if (!this.metricsLoaded) { - return []; - } - const nodeName = node.getName(); - - return metricNames.map(metricName => { - try { - const metric = this.metrics[metricName]; - const result = metric.data.result.find(result => { - return [ - result.metric.node, - result.metric.instance, - result.metric.kubernetes_node, - ].includes(nodeName); - }); - - return result ? parseFloat(result.values.slice(-1)[0][1]) : 0; - } catch (e) { - return 0; - } - }); - } - getWarningsCount(): number { return sum(this.items.map((node: Node) => node.getWarningConditions().length)); } - - reset() { - super.reset(); - this.metrics = {}; - this.nodeMetrics = null; - this.metricsLoading = false; - this.metricsLoaded = false; - } } export const nodesStore = new NodesStore(); diff --git a/src/renderer/components/+nodes/nodes.tsx b/src/renderer/components/+nodes/nodes.tsx index abd389d6e7..4b5672b816 100644 --- a/src/renderer/components/+nodes/nodes.tsx +++ b/src/renderer/components/+nodes/nodes.tsx @@ -28,7 +28,7 @@ import { TabLayout } from "../layout/tab-layout"; import { nodesStore } from "./nodes.store"; import { podsStore } from "../+workloads-pods/pods.store"; import { KubeObjectListLayout } from "../kube-object"; -import type { Node } from "../../api/endpoints/nodes.api"; +import { getMetricsForAllNodes, INodeMetrics, Node } from "../../api/endpoints/nodes.api"; import { LineProgress } from "../line-progress"; import { bytesToUnits } from "../../utils/convertMemory"; import { Tooltip, TooltipPosition } from "../tooltip"; @@ -39,6 +39,8 @@ import { Badge } from "../badge/badge"; import { kubeWatchApi } from "../../api/kube-watch-api"; import { eventStore } from "../+events/event.store"; import type { NodesRouteParams } from "../../../common/routes"; +import { observable } from "mobx"; +import isEmpty from "lodash/isEmpty"; enum columnId { name = "name", @@ -58,7 +60,8 @@ interface Props extends RouteComponentProps { @observer export class Nodes extends React.Component { - private metricsWatcher = interval(30, () => nodesStore.loadUsageMetrics()); + @observable metrics: Partial = {}; + private metricsWatcher = interval(30, async () => this.metrics = await getMetricsForAllNodes()); componentDidMount() { this.metricsWatcher.start(true); @@ -73,8 +76,32 @@ export class Nodes extends React.Component { this.metricsWatcher.stop(); } + getLastMetricValues(node: Node, metricNames: string[]): number[] { + if (isEmpty(this.metrics)) { + return []; + } + const nodeName = node.getName(); + + return metricNames.map(metricName => { + try { + const metric = this.metrics[metricName]; + const result = metric.data.result.find(result => { + return [ + result.metric.node, + result.metric.instance, + result.metric.kubernetes_node, + ].includes(nodeName); + }); + + return result ? parseFloat(result.values.slice(-1)[0][1]) : 0; + } catch (e) { + return 0; + } + }); + } + renderCpuUsage(node: Node) { - const metrics = nodesStore.getLastMetricValues(node, ["cpuUsage", "cpuCapacity"]); + const metrics = this.getLastMetricValues(node, ["cpuUsage", "cpuCapacity"]); if (!metrics || !metrics[1]) return ; const usage = metrics[0]; @@ -97,7 +124,7 @@ export class Nodes extends React.Component { } renderMemoryUsage(node: Node) { - const metrics = nodesStore.getLastMetricValues(node, ["workloadMemoryUsage", "memoryAllocatableCapacity"]); + const metrics = this.getLastMetricValues(node, ["workloadMemoryUsage", "memoryAllocatableCapacity"]); if (!metrics || !metrics[1]) return ; const usage = metrics[0]; @@ -116,7 +143,7 @@ export class Nodes extends React.Component { } renderDiskUsage(node: Node): any { - const metrics = nodesStore.getLastMetricValues(node, ["fsUsage", "fsSize"]); + const metrics = this.getLastMetricValues(node, ["fsUsage", "fsSize"]); if (!metrics || !metrics[1]) return ; const usage = metrics[0]; @@ -172,9 +199,9 @@ export class Nodes extends React.Component { isSelectable={false} sortingCallbacks={{ [columnId.name]: (node: Node) => node.getName(), - [columnId.cpu]: (node: Node) => nodesStore.getLastMetricValues(node, ["cpuUsage"]), - [columnId.memory]: (node: Node) => nodesStore.getLastMetricValues(node, ["memoryUsage"]), - [columnId.disk]: (node: Node) => nodesStore.getLastMetricValues(node, ["fsUsage"]), + [columnId.cpu]: (node: Node) => this.getLastMetricValues(node, ["cpuUsage"]), + [columnId.memory]: (node: Node) => this.getLastMetricValues(node, ["memoryUsage"]), + [columnId.disk]: (node: Node) => this.getLastMetricValues(node, ["fsUsage"]), [columnId.conditions]: (node: Node) => node.getNodeConditionText(), [columnId.taints]: (node: Node) => node.getTaints().length, [columnId.roles]: (node: Node) => node.getRoleLabels(), diff --git a/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx b/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx index 3066aafea8..372d6bfb6e 100644 --- a/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx +++ b/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx @@ -22,17 +22,16 @@ import "./volume-claim-details.scss"; import React, { Fragment } from "react"; -import { reaction } from "mobx"; +import { action, observable, reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import { DrawerItem, DrawerTitle } from "../drawer"; import { Badge } from "../badge"; import { podsStore } from "../+workloads-pods/pods.store"; import { Link } from "react-router-dom"; -import { volumeClaimStore } from "./volume-claim.store"; import { ResourceMetrics } from "../resource-metrics"; import { VolumeClaimDiskChart } from "./volume-claim-disk-chart"; import { getDetailsUrl, KubeObjectDetailsProps, KubeObjectMeta } from "../kube-object"; -import type { PersistentVolumeClaim } from "../../api/endpoints"; +import { getMetricsForPvc, IPvcMetrics, PersistentVolumeClaim } from "../../api/endpoints"; import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../main/cluster"; @@ -41,13 +40,18 @@ interface Props extends KubeObjectDetailsProps { @observer export class PersistentVolumeClaimDetails extends React.Component { + @observable metrics: IPvcMetrics = null; + @disposeOnUnmount clean = reaction(() => this.props.object, () => { - volumeClaimStore.reset(); + this.metrics = null; }); - componentWillUnmount() { - volumeClaimStore.reset(); + @action + async loadMetrics() { + const { object: volumeClaim } = this.props; + + this.metrics = await getMetricsForPvc(volumeClaim); } render() { @@ -57,7 +61,7 @@ export class PersistentVolumeClaimDetails extends React.Component { return null; } const { storageClassName, accessModes } = volumeClaim.spec; - const { metrics } = volumeClaimStore; + const { metrics } = this; const pods = volumeClaim.getPods(podsStore.items); const metricTabs = [ "Disk" @@ -68,7 +72,7 @@ export class PersistentVolumeClaimDetails extends React.Component {
{!isMetricHidden && ( volumeClaimStore.loadMetrics(volumeClaim)} + loader={this.loadMetrics} tabs={metricTabs} object={volumeClaim} params={{ metrics }} > diff --git a/src/renderer/components/+storage-volume-claims/volume-claim.store.ts b/src/renderer/components/+storage-volume-claims/volume-claim.store.ts index d703dd8d46..4beca645a5 100644 --- a/src/renderer/components/+storage-volume-claims/volume-claim.store.ts +++ b/src/renderer/components/+storage-volume-claims/volume-claim.store.ts @@ -18,32 +18,12 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -import { action, observable, makeObservable } from "mobx"; -import { KubeObjectStore } from "../../kube-object.store"; -import { autoBind } from "../../utils"; -import { IPvcMetrics, PersistentVolumeClaim, pvcApi } from "../../api/endpoints"; import { apiManager } from "../../api/api-manager"; +import { PersistentVolumeClaim, pvcApi } from "../../api/endpoints"; +import { KubeObjectStore } from "../../kube-object.store"; export class VolumeClaimStore extends KubeObjectStore { api = pvcApi; - @observable metrics: IPvcMetrics = null; - - constructor() { - super(); - - makeObservable(this); - autoBind(this); - } - - @action - async loadMetrics(pvc: PersistentVolumeClaim) { - this.metrics = await pvcApi.getMetrics(pvc.getName(), pvc.getNs()); - } - - reset() { - this.metrics = null; - } } export const volumeClaimStore = new VolumeClaimStore(); diff --git a/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx b/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx index 199385a1f4..4479995506 100644 --- a/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx +++ b/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx @@ -31,31 +31,42 @@ import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities" import { daemonSetStore } from "./daemonsets.store"; import { podsStore } from "../+workloads-pods/pods.store"; import type { KubeObjectDetailsProps } from "../kube-object"; -import type { DaemonSet } from "../../api/endpoints"; +import { DaemonSet, getMetricsForDaemonSets, IPodMetrics } from "../../api/endpoints"; import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; -import { reaction } from "mobx"; +import { makeObservable, observable, reaction } from "mobx"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../main/cluster"; +import { boundMethod } from "../../utils"; interface Props extends KubeObjectDetailsProps { } @observer export class DaemonSetDetails extends React.Component { + @observable metrics: IPodMetrics = null; + + constructor(props: Props) { + super(props); + makeObservable(this); + } + @disposeOnUnmount clean = reaction(() => this.props.object, () => { - daemonSetStore.reset(); + this.metrics = null; }); componentDidMount() { podsStore.reloadAll(); } - componentWillUnmount() { - daemonSetStore.reset(); + @boundMethod + async loadMetrics() { + const { object: daemonSet } = this.props; + + this.metrics = await getMetricsForDaemonSets([daemonSet], daemonSet.getNs(), ""); } render() { @@ -67,15 +78,14 @@ export class DaemonSetDetails extends React.Component { const images = daemonSet.getImages(); const nodeSelector = daemonSet.getNodeSelectors(); const childPods = daemonSetStore.getChildPods(daemonSet); - const metrics = daemonSetStore.metrics; const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.DaemonSet); return (
{!isMetricHidden && podsStore.isLoaded && ( daemonSetStore.loadMetrics(daemonSet)} - tabs={podMetricTabs} object={daemonSet} params={{ metrics }} + loader={this.loadMetrics} + tabs={podMetricTabs} object={daemonSet} params={{ metrics: this.metrics }} > @@ -110,7 +120,7 @@ export class DaemonSetDetails extends React.Component { - +
); diff --git a/src/renderer/components/+workloads-daemonsets/daemonsets.store.ts b/src/renderer/components/+workloads-daemonsets/daemonsets.store.ts index 210e6bdcdd..095eb2b9d7 100644 --- a/src/renderer/components/+workloads-daemonsets/daemonsets.store.ts +++ b/src/renderer/components/+workloads-daemonsets/daemonsets.store.ts @@ -18,19 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import { makeObservable } from "mobx"; -import { observable, makeObservable } from "mobx"; -import { KubeObjectStore } from "../../kube-object.store"; -import { autoBind } from "../../utils"; -import { DaemonSet, daemonSetApi, IPodMetrics, Pod, podsApi, PodStatus } from "../../api/endpoints"; import { podsStore } from "../+workloads-pods/pods.store"; import { apiManager } from "../../api/api-manager"; +import { DaemonSet, daemonSetApi, Pod, PodStatus } from "../../api/endpoints"; +import { KubeObjectStore } from "../../kube-object.store"; +import { autoBind } from "../../utils"; export class DaemonSetStore extends KubeObjectStore { api = daemonSetApi; - @observable metrics: IPodMetrics = null; - constructor() { super(); @@ -38,12 +36,6 @@ export class DaemonSetStore extends KubeObjectStore { autoBind(this); } - async loadMetrics(daemonSet: DaemonSet) { - const pods = this.getChildPods(daemonSet); - - this.metrics = await podsApi.getMetrics(pods, daemonSet.getNs(), ""); - } - getChildPods(daemonSet: DaemonSet): Pod[] { return podsStore.getPodsByOwnerId(daemonSet.getId()); } @@ -67,10 +59,6 @@ export class DaemonSetStore extends KubeObjectStore { return status; } - - reset() { - this.metrics = null; - } } export const daemonSetStore = new DaemonSetStore(); diff --git a/src/renderer/components/+workloads-deployments/deployment-details.tsx b/src/renderer/components/+workloads-deployments/deployment-details.tsx index 87f4e08378..110c9a5679 100644 --- a/src/renderer/components/+workloads-deployments/deployment-details.tsx +++ b/src/renderer/components/+workloads-deployments/deployment-details.tsx @@ -26,7 +26,7 @@ import kebabCase from "lodash/kebabCase"; import { disposeOnUnmount, observer } from "mobx-react"; import { DrawerItem } from "../drawer"; import { Badge } from "../badge"; -import type { Deployment } from "../../api/endpoints"; +import { Deployment, getMetricsForDeployments, IPodMetrics } from "../../api/endpoints"; import { PodDetailsTolerations } from "../+workloads-pods/pod-details-tolerations"; import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities"; import { podsStore } from "../+workloads-pods/pods.store"; @@ -34,22 +34,30 @@ import type { KubeObjectDetailsProps } from "../kube-object"; import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics"; import { deploymentStore } from "./deployments.store"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; -import { reaction } from "mobx"; +import { makeObservable, observable, reaction } from "mobx"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { replicaSetStore } from "../+workloads-replicasets/replicasets.store"; import { DeploymentReplicaSets } from "./deployment-replicasets"; import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../main/cluster"; +import { boundMethod } from "../../utils"; interface Props extends KubeObjectDetailsProps { } @observer export class DeploymentDetails extends React.Component { + @observable metrics: IPodMetrics = null; + + constructor(props: Props) { + super(props); + makeObservable(this); + } + @disposeOnUnmount clean = reaction(() => this.props.object, () => { - deploymentStore.reset(); + this.metrics = null; }); componentDidMount() { @@ -57,8 +65,11 @@ export class DeploymentDetails extends React.Component { replicaSetStore.reloadAll(); } - componentWillUnmount() { - deploymentStore.reset(); + @boundMethod + async loadMetrics() { + const { object: deployment } = this.props; + + this.metrics = await getMetricsForDeployments([deployment], deployment.getNs(), ""); } render() { @@ -70,15 +81,14 @@ export class DeploymentDetails extends React.Component { const selectors = deployment.getSelectors(); const childPods = deploymentStore.getChildPods(deployment); const replicaSets = replicaSetStore.getReplicaSetsByOwner(deployment); - const metrics = deploymentStore.metrics; const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Deployment); return (
{!isMetricHidden && podsStore.isLoaded && ( deploymentStore.loadMetrics(deployment)} - tabs={podMetricTabs} object={deployment} params={{ metrics }} + loader={this.loadMetrics} + tabs={podMetricTabs} object={deployment} params={{ metrics: this.metrics }} > @@ -132,7 +142,7 @@ export class DeploymentDetails extends React.Component { - +
diff --git a/src/renderer/components/+workloads-deployments/deployments.store.ts b/src/renderer/components/+workloads-deployments/deployments.store.ts index 8d99731d4f..96149c8745 100644 --- a/src/renderer/components/+workloads-deployments/deployments.store.ts +++ b/src/renderer/components/+workloads-deployments/deployments.store.ts @@ -18,17 +18,16 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import { makeObservable } from "mobx"; -import { observable, makeObservable } from "mobx"; -import { Deployment, deploymentApi, IPodMetrics, podsApi, PodStatus } from "../../api/endpoints"; -import { KubeObjectStore } from "../../kube-object.store"; -import { autoBind } from "../../utils"; import { podsStore } from "../+workloads-pods/pods.store"; import { apiManager } from "../../api/api-manager"; +import { Deployment, deploymentApi, PodStatus } from "../../api/endpoints"; +import { KubeObjectStore } from "../../kube-object.store"; +import { autoBind } from "../../utils"; export class DeploymentStore extends KubeObjectStore { api = deploymentApi; - @observable metrics: IPodMetrics = null; constructor() { super(); @@ -43,12 +42,6 @@ export class DeploymentStore extends KubeObjectStore { ], "desc"); } - async loadMetrics(deployment: Deployment) { - const pods = this.getChildPods(deployment); - - this.metrics = await podsApi.getMetrics(pods, deployment.getNs(), ""); - } - getStatuses(deployments?: Deployment[]) { const status = { failed: 0, pending: 0, running: 0 }; @@ -74,10 +67,6 @@ export class DeploymentStore extends KubeObjectStore { .getByLabel(deployment.getTemplateLabels()) .filter(pod => pod.getNs() === deployment.getNs()); } - - reset() { - this.metrics = null; - } } export const deploymentStore = new DeploymentStore(); diff --git a/src/renderer/components/+workloads-jobs/job-details.tsx b/src/renderer/components/+workloads-jobs/job-details.tsx index 02cd5b41d4..b0ba055122 100644 --- a/src/renderer/components/+workloads-jobs/job-details.tsx +++ b/src/renderer/components/+workloads-jobs/job-details.tsx @@ -33,20 +33,40 @@ import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities" import { podsStore } from "../+workloads-pods/pods.store"; import { jobStore } from "./job.store"; import { getDetailsUrl, KubeObjectDetailsProps } from "../kube-object"; -import type { Job } from "../../api/endpoints"; +import { getMetricsForJobs, IPodMetrics, Job } from "../../api/endpoints"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { lookupApiLink } from "../../api/kube-api"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; +import { makeObservable, observable } from "mobx"; +import { podMetricTabs, PodCharts } from "../+workloads-pods/pod-charts"; +import { ClusterMetricsResourceType } from "../../../main/cluster"; +import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; +import { ResourceMetrics } from "../resource-metrics"; +import { boundMethod } from "autobind-decorator"; interface Props extends KubeObjectDetailsProps { } @observer export class JobDetails extends React.Component { + @observable metrics: IPodMetrics = null; + + constructor(props: Props) { + super(props); + makeObservable(this); + } + async componentDidMount() { podsStore.reloadAll(); } + @boundMethod + async loadMetrics() { + const { object: job } = this.props; + + this.metrics = await getMetricsForJobs([job], job.getNs(), ""); + } + render() { const { object: job } = this.props; @@ -57,9 +77,18 @@ export class JobDetails extends React.Component { const childPods = jobStore.getChildPods(job); const ownerRefs = job.getOwnerRefs(); const condition = job.getCondition(); + const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Job); return (
+ {!isMetricHidden && ( + + + + )} { diff --git a/src/renderer/components/+workloads-pods/pod-details.tsx b/src/renderer/components/+workloads-pods/pod-details.tsx index 2bb08deeff..6249288b77 100644 --- a/src/renderer/components/+workloads-pods/pod-details.tsx +++ b/src/renderer/components/+workloads-pods/pod-details.tsx @@ -25,18 +25,17 @@ import React from "react"; import kebabCase from "lodash/kebabCase"; import { disposeOnUnmount, observer } from "mobx-react"; import { Link } from "react-router-dom"; -import { autorun, observable, reaction, makeObservable } from "mobx"; -import { IPodMetrics, nodesApi, Pod, pvcApi, configMapApi } from "../../api/endpoints"; +import { observable, reaction, makeObservable } from "mobx"; +import { IPodMetrics, nodesApi, Pod, pvcApi, configMapApi, getMetricsForPods } from "../../api/endpoints"; import { DrawerItem, DrawerTitle } from "../drawer"; import { Badge } from "../badge"; -import { boundMethod, cssNames, interval, toJS } from "../../utils"; +import { boundMethod, cssNames, toJS } from "../../utils"; import { PodDetailsContainer } from "./pod-details-container"; import { PodDetailsAffinities } from "./pod-details-affinities"; import { PodDetailsTolerations } from "./pod-details-tolerations"; import { Icon } from "../icon"; import { PodDetailsSecrets } from "./pod-details-secrets"; import { ResourceMetrics } from "../resource-metrics"; -import { podsStore } from "./pods.store"; import { getDetailsUrl, KubeObjectDetailsProps } from "../kube-object"; import { getItemMetrics } from "../../api/endpoints/metrics.api"; import { PodCharts, podMetricTabs } from "./pod-charts"; @@ -49,10 +48,9 @@ interface Props extends KubeObjectDetailsProps { @observer export class PodDetails extends React.Component { + @observable metrics: IPodMetrics; @observable containerMetrics: IPodMetrics; - private watcher = interval(60, () => this.loadMetrics()); - constructor(props: Props) { super(props); makeObservable(this); @@ -60,26 +58,19 @@ export class PodDetails extends React.Component { componentDidMount() { disposeOnUnmount(this, [ - autorun(() => { - this.containerMetrics = null; - this.loadMetrics(); - }), reaction(() => this.props.object, () => { - podsStore.reset(); + this.metrics = null; + this.containerMetrics = null; }) ]); - this.watcher.start(); - } - - componentWillUnmount() { - podsStore.reset(); } @boundMethod async loadMetrics() { const { object: pod } = this.props; - this.containerMetrics = await podsStore.loadContainerMetrics(pod); + this.metrics = await getMetricsForPods([pod], pod.getNs()); + this.containerMetrics = await getMetricsForPods([pod], pod.getNs(), "container, namespace"); } render() { @@ -92,15 +83,14 @@ export class PodDetails extends React.Component { const { nodeName } = spec; const nodeSelector = pod.getNodeSelectors(); const volumes = pod.getVolumes(); - const metrics = podsStore.metrics; const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Pod); return (
{!isMetricHidden && ( podsStore.loadMetrics(pod)} - tabs={podMetricTabs} object={pod} params={{ metrics }} + loader={this.loadMetrics} + tabs={podMetricTabs} object={pod} params={{ metrics: this.metrics }} > diff --git a/src/renderer/components/+workloads-pods/pods.store.ts b/src/renderer/components/+workloads-pods/pods.store.ts index 62a6a720af..1708690a8f 100644 --- a/src/renderer/components/+workloads-pods/pods.store.ts +++ b/src/renderer/components/+workloads-pods/pods.store.ts @@ -20,17 +20,16 @@ */ import countBy from "lodash/countBy"; -import { action, observable, makeObservable } from "mobx"; +import { observable, makeObservable } from "mobx"; import { KubeObjectStore } from "../../kube-object.store"; import { autoBind, cpuUnitsToNumber, unitsToBytes } from "../../utils"; -import { IPodMetrics, Pod, PodMetrics, podMetricsApi, podsApi } from "../../api/endpoints"; +import { Pod, PodMetrics, podMetricsApi, podsApi } from "../../api/endpoints"; import { apiManager } from "../../api/api-manager"; import type { WorkloadKubeObject } from "../../api/workload-kube-object"; export class PodsStore extends KubeObjectStore { api = podsApi; - @observable metrics: IPodMetrics = null; @observable kubeMetrics = observable.array([]); constructor() { @@ -40,15 +39,6 @@ export class PodsStore extends KubeObjectStore { autoBind(this); } - @action - async loadMetrics(pod: Pod) { - this.metrics = await podsApi.getMetrics([pod], pod.getNs()); - } - - loadContainerMetrics(pod: Pod) { - return podsApi.getMetrics([pod], pod.getNs(), "container, namespace"); - } - async loadKubeMetrics(namespace?: string) { try { this.kubeMetrics.replace(await podMetricsApi.list({ namespace })); @@ -111,10 +101,6 @@ export class PodsStore extends KubeObjectStore { }; }, empty); } - - reset() { - this.metrics = null; - } } export const podsStore = new PodsStore(); diff --git a/src/renderer/components/+workloads-replicasets/replicaset-details.tsx b/src/renderer/components/+workloads-replicasets/replicaset-details.tsx index 7818be6765..7c06b56840 100644 --- a/src/renderer/components/+workloads-replicasets/replicaset-details.tsx +++ b/src/renderer/components/+workloads-replicasets/replicaset-details.tsx @@ -21,7 +21,7 @@ import "./replicaset-details.scss"; import React from "react"; -import { reaction } from "mobx"; +import { makeObservable, observable, reaction } from "mobx"; import { DrawerItem } from "../drawer"; import { Badge } from "../badge"; import { replicaSetStore } from "./replicasets.store"; @@ -31,37 +31,48 @@ import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities" import { disposeOnUnmount, observer } from "mobx-react"; import { podsStore } from "../+workloads-pods/pods.store"; import type { KubeObjectDetailsProps } from "../kube-object"; -import type { ReplicaSet } from "../../api/endpoints"; +import { getMetricsForReplicaSets, IPodMetrics, ReplicaSet } from "../../api/endpoints"; import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../main/cluster"; +import { boundMethod } from "../../utils"; interface Props extends KubeObjectDetailsProps { } @observer export class ReplicaSetDetails extends React.Component { + @observable metrics: IPodMetrics = null; + + constructor(props: Props) { + super(props); + makeObservable(this); + } + @disposeOnUnmount clean = reaction(() => this.props.object, () => { - replicaSetStore.reset(); + this.metrics = null; }); async componentDidMount() { podsStore.reloadAll(); } - componentWillUnmount() { - replicaSetStore.reset(); + @boundMethod + async loadMetrics() { + const { object: replicaSet } = this.props; + + this.metrics = await getMetricsForReplicaSets([replicaSet], replicaSet.getNs(), ""); } render() { const { object: replicaSet } = this.props; if (!replicaSet) return null; - const { metrics } = replicaSetStore; + const { metrics } = this; const { status } = replicaSet; const { availableReplicas, replicas } = status; const selectors = replicaSet.getSelectors(); @@ -74,7 +85,7 @@ export class ReplicaSetDetails extends React.Component {
{!isMetricHidden && podsStore.isLoaded && ( replicaSetStore.loadMetrics(replicaSet)} + loader={this.loadMetrics} tabs={podMetricTabs} object={replicaSet} params={{ metrics }} > diff --git a/src/renderer/components/+workloads-replicasets/replicasets.store.ts b/src/renderer/components/+workloads-replicasets/replicasets.store.ts index 66794bc7ad..c7d7729889 100644 --- a/src/renderer/components/+workloads-replicasets/replicasets.store.ts +++ b/src/renderer/components/+workloads-replicasets/replicasets.store.ts @@ -18,18 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import { makeObservable } from "mobx"; -import { observable, makeObservable } from "mobx"; -import { autoBind } from "../../utils"; -import { KubeObjectStore } from "../../kube-object.store"; -import { Deployment, IPodMetrics, podsApi, ReplicaSet, replicaSetApi } from "../../api/endpoints"; import { podsStore } from "../+workloads-pods/pods.store"; import { apiManager } from "../../api/api-manager"; +import { Deployment, ReplicaSet, replicaSetApi } from "../../api/endpoints"; import { PodStatus } from "../../api/endpoints/pods.api"; +import { KubeObjectStore } from "../../kube-object.store"; +import { autoBind } from "../../utils"; export class ReplicaSetStore extends KubeObjectStore { api = replicaSetApi; - @observable metrics: IPodMetrics = null; constructor() { super(); @@ -38,12 +37,6 @@ export class ReplicaSetStore extends KubeObjectStore { autoBind(this); } - async loadMetrics(replicaSet: ReplicaSet) { - const pods = this.getChildPods(replicaSet); - - this.metrics = await podsApi.getMetrics(pods, replicaSet.getNs(), ""); - } - getChildPods(replicaSet: ReplicaSet) { return podsStore.getPodsByOwnerId(replicaSet.getId()); } @@ -73,10 +66,6 @@ export class ReplicaSetStore extends KubeObjectStore { !!replicaSet.getOwnerRefs().find(owner => owner.uid === deployment.getId()) ); } - - reset() { - this.metrics = null; - } } export const replicaSetStore = new ReplicaSetStore(); diff --git a/src/renderer/components/+workloads-statefulsets/statefulset-details.tsx b/src/renderer/components/+workloads-statefulsets/statefulset-details.tsx index dafe8c16b6..f16578c703 100644 --- a/src/renderer/components/+workloads-statefulsets/statefulset-details.tsx +++ b/src/renderer/components/+workloads-statefulsets/statefulset-details.tsx @@ -23,7 +23,7 @@ import "./statefulset-details.scss"; import React from "react"; import { disposeOnUnmount, observer } from "mobx-react"; -import { reaction } from "mobx"; +import { makeObservable, observable, reaction } from "mobx"; import { Badge } from "../badge"; import { DrawerItem } from "../drawer"; import { PodDetailsStatuses } from "../+workloads-pods/pod-details-statuses"; @@ -32,30 +32,41 @@ import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities" import { podsStore } from "../+workloads-pods/pods.store"; import { statefulSetStore } from "./statefulset.store"; import type { KubeObjectDetailsProps } from "../kube-object"; -import type { StatefulSet } from "../../api/endpoints"; +import { getMetricsForStatefulSets, IPodMetrics, StatefulSet } from "../../api/endpoints"; import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../main/cluster"; +import { boundMethod } from "../../utils"; interface Props extends KubeObjectDetailsProps { } @observer export class StatefulSetDetails extends React.Component { + @observable metrics: IPodMetrics = null; + + constructor(props: Props) { + super(props); + makeObservable(this); + } + @disposeOnUnmount clean = reaction(() => this.props.object, () => { - statefulSetStore.reset(); + this.metrics = null; }); componentDidMount() { podsStore.reloadAll(); } - componentWillUnmount() { - statefulSetStore.reset(); + @boundMethod + async loadMetrics() { + const { object: statefulSet } = this.props; + + this.metrics = await getMetricsForStatefulSets([statefulSet], statefulSet.getNs(), ""); } render() { @@ -66,15 +77,14 @@ export class StatefulSetDetails extends React.Component { const selectors = statefulSet.getSelectors(); const nodeSelector = statefulSet.getNodeSelectors(); const childPods = statefulSetStore.getChildPods(statefulSet); - const metrics = statefulSetStore.metrics; const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.StatefulSet); return (
{!isMetricHidden && podsStore.isLoaded && ( statefulSetStore.loadMetrics(statefulSet)} - tabs={podMetricTabs} object={statefulSet} params={{ metrics }} + loader={() => this.loadMetrics} + tabs={podMetricTabs} object={statefulSet} params={{ metrics: this.metrics }} > @@ -108,7 +118,7 @@ export class StatefulSetDetails extends React.Component { - +
); diff --git a/src/renderer/components/+workloads-statefulsets/statefulset.store.ts b/src/renderer/components/+workloads-statefulsets/statefulset.store.ts index 572e39447a..c1908c01a5 100644 --- a/src/renderer/components/+workloads-statefulsets/statefulset.store.ts +++ b/src/renderer/components/+workloads-statefulsets/statefulset.store.ts @@ -18,17 +18,16 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import { makeObservable } from "mobx"; -import { observable, makeObservable } from "mobx"; -import { autoBind } from "../../utils"; -import { KubeObjectStore } from "../../kube-object.store"; -import { IPodMetrics, podsApi, PodStatus, StatefulSet, statefulSetApi } from "../../api/endpoints"; import { podsStore } from "../+workloads-pods/pods.store"; import { apiManager } from "../../api/api-manager"; +import { PodStatus, StatefulSet, statefulSetApi } from "../../api/endpoints"; +import { KubeObjectStore } from "../../kube-object.store"; +import { autoBind } from "../../utils"; export class StatefulSetStore extends KubeObjectStore { api = statefulSetApi; - @observable metrics: IPodMetrics = null; constructor() { super(); @@ -37,13 +36,6 @@ export class StatefulSetStore extends KubeObjectStore { autoBind(this); } - - async loadMetrics(statefulSet: StatefulSet) { - const pods = this.getChildPods(statefulSet); - - this.metrics = await podsApi.getMetrics(pods, statefulSet.getNs(), ""); - } - getChildPods(statefulSet: StatefulSet) { return podsStore.getPodsByOwnerId(statefulSet.getId()); } @@ -67,10 +59,6 @@ export class StatefulSetStore extends KubeObjectStore { return status; } - - reset() { - this.metrics = null; - } } export const statefulSetStore = new StatefulSetStore();