From 624ac4680dd79ea236c8f9e509da6fe76c17c092 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 15 Nov 2021 14:34:38 -0500 Subject: [PATCH] Fix detail views not watching child components - Add subscribeStores calls to all relavent details - Add support for tracking overlapping subscribes as an optimization Signed-off-by: Sebastian Malton --- src/common/k8s-api/kube-object.store.ts | 49 +++-- src/common/k8s-api/kube-watch-api.ts | 172 +++++++++--------- .../components/+cluster/cluster-overview.tsx | 8 +- .../components/+events/kube-event-details.tsx | 11 +- .../+namespaces/namespace-details.tsx | 17 +- .../+namespaces/namespace-select.tsx | 12 +- .../components/+namespaces/namespace.store.ts | 2 +- .../+network-ingresses/ingress-details.tsx | 11 +- .../+network-services/service-details.tsx | 5 +- .../components/+nodes/node-details.tsx | 17 +- .../storage-class-details.tsx | 11 +- .../volume-claim-details.tsx | 11 +- .../+workloads-cronjobs/cronjob-details.tsx | 11 +- .../daemonset-details.tsx | 15 +- .../deployment-details.tsx | 17 +- .../+workloads-jobs/job-details.tsx | 16 +- .../+workloads-overview/overview.tsx | 16 +- .../replicaset-details.tsx | 17 +- .../statefulset-details.tsx | 15 +- src/renderer/components/app.tsx | 6 +- .../kube-object-list-layout.tsx | 6 +- src/renderer/components/layout/sidebar.tsx | 4 +- 22 files changed, 242 insertions(+), 207 deletions(-) diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index 885d4b9347..7ec09b22ba 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -33,9 +33,14 @@ import type { RequestInit } from "node-fetch"; import AbortController from "abort-controller"; import type { Patch } from "rfc6902"; -export interface KubeObjectStoreLoadingParams { +export interface KubeObjectStoreLoadingParams { namespaces: string[]; - api?: KubeApi; + reqInit?: RequestInit; +} + +export interface KubeObjectStoreLoadAllParams { + namespaces?: string[]; + merge?: boolean; reqInit?: RequestInit; } @@ -141,10 +146,10 @@ export abstract class KubeObjectStore extends ItemStore } } - protected async loadItems({ namespaces, api, reqInit }: KubeObjectStoreLoadingParams): Promise { - if (this.context?.cluster.isAllowedResource(api.kind)) { - if (!api.isNamespaced) { - return api.list({ reqInit }, this.query); + protected async loadItems({ namespaces, reqInit }: KubeObjectStoreLoadingParams): Promise { + if (this.context?.cluster.isAllowedResource(this.api.kind)) { + if (!this.api.isNamespaced) { + return this.api.list({ reqInit }, this.query); } const isLoadingAll = this.context.allNamespaces?.length > 1 @@ -154,12 +159,12 @@ export abstract class KubeObjectStore extends ItemStore if (isLoadingAll) { this.loadedNamespaces = []; - return api.list({ reqInit }, this.query); + return this.api.list({ reqInit }, this.query); } else { this.loadedNamespaces = namespaces; return Promise // load resources per namespace - .all(namespaces.map(namespace => api.list({ namespace, reqInit }, this.query))) + .all(namespaces.map(namespace => this.api.list({ namespace, reqInit }, this.query))) .then(items => items.flat().filter(Boolean)); } } @@ -172,24 +177,14 @@ export abstract class KubeObjectStore extends ItemStore } @action - async loadAll(options: { namespaces?: string[], merge?: boolean, reqInit?: RequestInit } = {}): Promise { + async loadAll({ namespaces = this.context.contextNamespaces, merge = true, reqInit }: KubeObjectStoreLoadAllParams = {}): Promise { await this.contextReady; this.isLoading = true; try { - const { - namespaces = this.context.allNamespaces, // load all namespaces by default - merge = true, // merge loaded items or return as result - reqInit, - } = options; + const items = await this.loadItems({ namespaces, reqInit }); - const items = await this.loadItems({ namespaces, api: this.api, reqInit }); - - if (merge) { - this.mergeItems(items, { replace: false }); - } else { - this.mergeItems(items, { replace: true }); - } + this.mergeItems(items, { merge }); this.isLoaded = true; this.failedLoading = false; @@ -216,11 +211,11 @@ export abstract class KubeObjectStore extends ItemStore } @action - protected mergeItems(partialItems: T[], { replace = false, updateStore = true, sort = true, filter = true } = {}): T[] { + protected mergeItems(partialItems: T[], { merge = true, updateStore = true, sort = true, filter = true } = {}): T[] { let items = partialItems; // update existing items - if (!replace) { + if (merge) { const namespaces = partialItems.map(item => item.getNs()); items = [ @@ -297,7 +292,7 @@ export abstract class KubeObjectStore extends ItemStore async patch(item: T, patch: Patch): Promise { return this.postUpdate( await this.api.patch( - { + { name: item.getName(), namespace: item.getNs(), }, patch, @@ -309,7 +304,7 @@ export abstract class KubeObjectStore extends ItemStore async update(item: T, data: Partial): Promise { return this.postUpdate( await this.api.update( - { + { name: item.getName(), namespace: item.getNs(), }, data, @@ -335,9 +330,7 @@ export abstract class KubeObjectStore extends ItemStore }); } - subscribe() { - const abortController = new AbortController(); - + subscribe(abortController = new AbortController()) { if (this.api.isNamespaced) { Promise.race([rejectPromiseBy(abortController.signal), Promise.all([this.contextReady, this.namespacesReady])]) .then(() => { diff --git a/src/common/k8s-api/kube-watch-api.ts b/src/common/k8s-api/kube-watch-api.ts index 2ab1123173..2ff84a77d8 100644 --- a/src/common/k8s-api/kube-watch-api.ts +++ b/src/common/k8s-api/kube-watch-api.ts @@ -25,131 +25,131 @@ import type { KubeObjectStore } from "./kube-object.store"; import type { ClusterContext } from "./cluster-context"; -import plimit from "p-limit"; import { comparer, observable, reaction, makeObservable } from "mobx"; -import { autoBind, Disposer, noop } from "../utils"; -import type { KubeApi } from "./kube-api"; +import { autoBind, disposer, Disposer, ExtendedMap, noop } from "../utils"; import type { KubeJsonApiData } from "./kube-json-api"; -import { isDebugging, isProduction } from "../vars"; +import { isProduction } from "../vars"; import type { KubeObject } from "./kube-object"; +import AbortController from "abort-controller"; +import { once } from "lodash"; + +class WrappedAbortController extends AbortController { + constructor(protected parent: AbortController) { + super(); + + parent.signal.addEventListener("abort", () => { + this.abort(); + }); + } +} export interface IKubeWatchEvent { type: "ADDED" | "MODIFIED" | "DELETED" | "ERROR"; object?: T; } -export interface IKubeWatchSubscribeStoreOptions { - namespaces?: string[]; // default: all accessible namespaces - preload?: boolean; // preload store items, default: true - waitUntilLoaded?: boolean; // subscribe only after loading all stores, default: true - loadOnce?: boolean; // check store.isLoaded to skip loading if done already, default: false -} - -export interface IKubeWatchLog { - message: string | string[] | Error; - meta?: object; - cssStyle?: string; +export interface KubeWatchSubscribeStoreOptions { + /** + * The namespaces to watch, if not specified then changes to the set of + * selected namespaces will be watched as well + * + * @default all selected namespaces + */ + namespaces?: string[]; } export class KubeWatchApi { @observable context: ClusterContext = null; + duplicateWatchSet = new ExtendedMap, number>(); constructor() { makeObservable(this); autoBind(this); } - isAllowedApi(api: KubeApi): boolean { - return Boolean(this.context?.cluster.isAllowedResource(api.kind)); - } + private subscribeStore(store: KubeObjectStore, parent: AbortController, watchChanges: boolean, namespaces: string[]): Disposer { + const count = this.duplicateWatchSet.getOrInsert(store, () => 1); - preloadStores(stores: KubeObjectStore[], opts: { namespaces?: string[], loadOnce?: boolean } = {}) { - const limitRequests = plimit(1); // load stores one by one to allow quick skipping when fast clicking btw pages - const preloading: Promise[] = []; - - for (const store of stores) { - preloading.push(limitRequests(async () => { - if (store.isLoaded && opts.loadOnce) return; // skip - - return store.loadAll({ namespaces: opts.namespaces }); - })); + if (count > 1) { + // don't load or subscribe to a store more than once + return () => { + this.duplicateWatchSet.set(store, this.duplicateWatchSet.get(store) - 1); + }; } - return { - loading: Promise.allSettled(preloading), - cancelLoading: () => limitRequests.clearQueue(), - }; - } + let childController = new WrappedAbortController(parent); + const unsubscribe = disposer(); - subscribeStores(stores: KubeObjectStore[], opts: IKubeWatchSubscribeStoreOptions = {}): Disposer { - const { preload = true, waitUntilLoaded = true, loadOnce = false } = opts; - const subscribingNamespaces = opts.namespaces ?? this.context?.allNamespaces ?? []; - const unsubscribeList: Function[] = []; - let isUnsubscribed = false; - - const load = (namespaces = subscribingNamespaces) => this.preloadStores(stores, { namespaces, loadOnce }); - let preloading = preload && load(); - let cancelReloading: Disposer = noop; - - const subscribe = () => { - if (isUnsubscribed) return; - - stores.forEach((store) => { - unsubscribeList.push(store.subscribe()); - }); - }; - - if (preloading) { - if (waitUntilLoaded) { - preloading.loading.then(subscribe, error => { - this.log({ - message: new Error("Loading stores has failed"), - meta: { stores, error, options: opts }, + const loadThenSubscribe = async (namespaces: string[]) => { + try { + await store.loadAll({ namespaces, reqInit: { signal: childController.signal }}); + unsubscribe.push(store.subscribe(childController)); + } catch (error) { + if (!(error instanceof DOMException)) { + this.log(Object.assign(new Error("Loading stores has failed"), { cause: error }), { + meta: { store, namespaces }, }); - }); - } else { - subscribe(); + } } + }; - // reload stores only for context namespaces change - cancelReloading = reaction(() => this.context?.contextNamespaces, namespaces => { - preloading?.cancelLoading(); - unsubscribeList.forEach(unsubscribe => unsubscribe()); - unsubscribeList.length = 0; - preloading = load(namespaces); - preloading.loading.then(subscribe); + loadThenSubscribe(namespaces); + + const cancelReloading = watchChanges + ? noop // don't watch namespaces if namespaces were provided + : reaction(() => this.context.contextNamespaces, namespaces => { + childController.abort(); + unsubscribe(); + childController = new WrappedAbortController(parent); + loadThenSubscribe(namespaces); }, { equals: comparer.shallow, }); - } - // unsubscribe return () => { - if (isUnsubscribed) return; - isUnsubscribed = true; - cancelReloading(); - preloading?.cancelLoading(); - unsubscribeList.forEach(unsubscribe => unsubscribe()); - unsubscribeList.length = 0; + const newCount = this.duplicateWatchSet.get(store) - 1; + + this.duplicateWatchSet.set(store, newCount); + + if (newCount === 0) { + cancelReloading(); + childController.abort(); + unsubscribe(); + } }; } - protected log({ message, cssStyle = "", meta = {}}: IKubeWatchLog) { - if (isProduction && !isDebugging) { + subscribeStores(stores: KubeObjectStore[], options: KubeWatchSubscribeStoreOptions = {}): Disposer { + const parent = new AbortController(); + const unsubscribe = disposer( + ...stores.map(store => this.subscribeStore( + store, + parent, + !options.namespaces, + options.namespaces ?? this.context?.contextNamespaces ?? [], + )), + ); + + // unsubscribe + return once(() => { + parent.abort(); + unsubscribe(); + }); + } + + protected log(message: any, meta: any) { + if (isProduction) { return; } - const logInfo = [`%c[KUBE-WATCH-API]:`, `font-weight: bold; ${cssStyle}`, message].flat().map(String); - const logMeta = { + const log = message instanceof Error + ? console.error + : console.debug; + + log("[KUBE-WATCH-API]:", message, { time: new Date().toLocaleString(), ...meta, - }; - - if (message instanceof Error) { - console.error(...logInfo, logMeta); - } else { - console.info(...logInfo, logMeta); - } + }); } } diff --git a/src/renderer/components/+cluster/cluster-overview.tsx b/src/renderer/components/+cluster/cluster-overview.tsx index 1815fee001..d2e3e3ee9a 100644 --- a/src/renderer/components/+cluster/cluster-overview.tsx +++ b/src/renderer/components/+cluster/cluster-overview.tsx @@ -55,9 +55,11 @@ export class ClusterOverview extends React.Component { this.metricPoller.start(true); disposeOnUnmount(this, [ - kubeWatchApi.subscribeStores([podsStore, eventStore, nodesStore], { - preload: true, - }), + kubeWatchApi.subscribeStores([ + podsStore, + eventStore, + nodesStore, + ]), reaction( () => clusterOverviewStore.metricNodeRole, // Toggle Master/Worker node switcher () => this.metricPoller.restart(true), diff --git a/src/renderer/components/+events/kube-event-details.tsx b/src/renderer/components/+events/kube-event-details.tsx index a06911e08a..e573f97303 100644 --- a/src/renderer/components/+events/kube-event-details.tsx +++ b/src/renderer/components/+events/kube-event-details.tsx @@ -22,13 +22,14 @@ import "./kube-event-details.scss"; import React from "react"; -import { observer } from "mobx-react"; +import { disposeOnUnmount, observer } from "mobx-react"; import { KubeObject } from "../../../common/k8s-api/kube-object"; import { DrawerItem, DrawerTitle } from "../drawer"; import { cssNames } from "../../utils"; import { LocaleDate } from "../locale-date"; import { eventStore } from "./event.store"; import logger from "../../../common/logger"; +import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; export interface KubeEventDetailsProps { object: KubeObject; @@ -36,8 +37,12 @@ export interface KubeEventDetailsProps { @observer export class KubeEventDetails extends React.Component { - async componentDidMount() { - eventStore.reloadAll(); + componentDidMount() { + disposeOnUnmount(this, [ + kubeWatchApi.subscribeStores([ + eventStore, + ]), + ]); } render() { diff --git a/src/renderer/components/+namespaces/namespace-details.tsx b/src/renderer/components/+namespaces/namespace-details.tsx index 8267637de3..68136499ed 100644 --- a/src/renderer/components/+namespaces/namespace-details.tsx +++ b/src/renderer/components/+namespaces/namespace-details.tsx @@ -39,6 +39,7 @@ import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { getDetailsUrl } from "../kube-detail-params"; import logger from "../../../common/logger"; +import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; interface Props extends KubeObjectDetailsProps { } @@ -52,14 +53,16 @@ export class NamespaceDetails extends React.Component { makeObservable(this); } - @disposeOnUnmount - clean = reaction(() => this.props.object, () => { - this.metrics = null; - }); - componentDidMount() { - resourceQuotaStore.reloadAll(); - limitRangeStore.reloadAll(); + disposeOnUnmount(this, [ + reaction(() => this.props.object, () => { + this.metrics = null; + }), + kubeWatchApi.subscribeStores([ + resourceQuotaStore, + limitRangeStore, + ]), + ]); } @computed get quotas() { diff --git a/src/renderer/components/+namespaces/namespace-select.tsx b/src/renderer/components/+namespaces/namespace-select.tsx index aadfa601a2..7974b25f79 100644 --- a/src/renderer/components/+namespaces/namespace-select.tsx +++ b/src/renderer/components/+namespaces/namespace-select.tsx @@ -23,12 +23,11 @@ import "./namespace-select.scss"; import React from "react"; import { computed, makeObservable } from "mobx"; -import { disposeOnUnmount, observer } from "mobx-react"; +import { observer } from "mobx-react"; import { Select, SelectOption, SelectProps } from "../select"; import { cssNames } from "../../utils"; import { Icon } from "../icon"; import { namespaceStore } from "./namespace.store"; -import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; interface Props extends SelectProps { showIcons?: boolean; @@ -50,14 +49,7 @@ export class NamespaceSelect extends React.Component { makeObservable(this); } - componentDidMount() { - disposeOnUnmount(this, [ - kubeWatchApi.subscribeStores([namespaceStore], { - preload: true, - loadOnce: true, // skip reloading namespaces on every render / page visit - }), - ]); - } + // No subscribe here because the subscribe is in (the cluster frame root component) @computed.struct get options(): SelectOption[] { const { customizeOptions, showAllNamespacesOption, sort } = this.props; diff --git a/src/renderer/components/+namespaces/namespace.store.ts b/src/renderer/components/+namespaces/namespace.store.ts index 093a3301ae..c7ec3cf421 100644 --- a/src/renderer/components/+namespaces/namespace.store.ts +++ b/src/renderer/components/+namespaces/namespace.store.ts @@ -133,7 +133,7 @@ export class NamespaceStore extends KubeObjectStore { return super.subscribe(); } - protected async loadItems(params: KubeObjectStoreLoadingParams): Promise { + protected async loadItems(params: KubeObjectStoreLoadingParams): Promise { const { allowedNamespaces } = this; let namespaces = await super.loadItems(params).catch(() => []); diff --git a/src/renderer/components/+network-ingresses/ingress-details.tsx b/src/renderer/components/+network-ingresses/ingress-details.tsx index a0ec3ce8ca..632f0d5b60 100644 --- a/src/renderer/components/+network-ingresses/ingress-details.tsx +++ b/src/renderer/components/+network-ingresses/ingress-details.tsx @@ -49,10 +49,13 @@ export class IngressDetails extends React.Component { makeObservable(this); } - @disposeOnUnmount - clean = reaction(() => this.props.object, () => { - this.metrics = null; - }); + componentDidMount() { + disposeOnUnmount(this, [ + reaction(() => this.props.object, () => { + this.metrics = null; + }), + ]); + } @boundMethod async loadMetrics() { diff --git a/src/renderer/components/+network-services/service-details.tsx b/src/renderer/components/+network-services/service-details.tsx index d247e7908c..36dc9dee8a 100644 --- a/src/renderer/components/+network-services/service-details.tsx +++ b/src/renderer/components/+network-services/service-details.tsx @@ -44,8 +44,9 @@ export class ServiceDetails extends React.Component { const { object: service } = this.props; disposeOnUnmount(this, [ - kubeWatchApi.subscribeStores([endpointStore], { - preload: true, + kubeWatchApi.subscribeStores([ + endpointStore, + ], { namespaces: [service.getNs()], }), portForwardStore.watch(), diff --git a/src/renderer/components/+nodes/node-details.tsx b/src/renderer/components/+nodes/node-details.tsx index 350ba563b8..c576397903 100644 --- a/src/renderer/components/+nodes/node-details.tsx +++ b/src/renderer/components/+nodes/node-details.tsx @@ -41,6 +41,7 @@ import { NodeDetailsResources } from "./node-details-resources"; import { DrawerTitle } from "../drawer/drawer-title"; import { boundMethod } from "../../utils"; import logger from "../../../common/logger"; +import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; interface Props extends KubeObjectDetailsProps { } @@ -54,13 +55,15 @@ export class NodeDetails extends React.Component { makeObservable(this); } - @disposeOnUnmount - clean = reaction(() => this.props.object.getName(), () => { - this.metrics = null; - }); - - async componentDidMount() { - podsStore.reloadAll(); + componentDidMount() { + disposeOnUnmount(this, [ + reaction(() => this.props.object.getName(), () => { + this.metrics = null; + }), + kubeWatchApi.subscribeStores([ + podsStore, + ]), + ]); } @boundMethod diff --git a/src/renderer/components/+storage-classes/storage-class-details.tsx b/src/renderer/components/+storage-classes/storage-class-details.tsx index 0ce2827014..30e0df6a6b 100644 --- a/src/renderer/components/+storage-classes/storage-class-details.tsx +++ b/src/renderer/components/+storage-classes/storage-class-details.tsx @@ -25,7 +25,7 @@ import React from "react"; import startCase from "lodash/startCase"; import { DrawerItem, DrawerTitle } from "../drawer"; import { Badge } from "../badge"; -import { observer } from "mobx-react"; +import { disposeOnUnmount, observer } from "mobx-react"; import type { KubeObjectDetailsProps } from "../kube-object-details"; import { StorageClass } from "../../../common/k8s-api/endpoints"; import { KubeObjectMeta } from "../kube-object-meta"; @@ -33,14 +33,19 @@ import { storageClassStore } from "./storage-class.store"; import { VolumeDetailsList } from "../+storage-volumes/volume-details-list"; import { volumesStore } from "../+storage-volumes/volumes.store"; import logger from "../../../common/logger"; +import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; interface Props extends KubeObjectDetailsProps { } @observer export class StorageClassDetails extends React.Component { - async componentDidMount() { - volumesStore.reloadAll(); + componentDidMount() { + disposeOnUnmount(this, [ + kubeWatchApi.subscribeStores([ + volumesStore, + ]), + ]); } render() { 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 35ca4989e1..6018af72e1 100644 --- a/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx +++ b/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx @@ -51,10 +51,13 @@ export class PersistentVolumeClaimDetails extends React.Component { makeObservable(this); } - @disposeOnUnmount - clean = reaction(() => this.props.object, () => { - this.metrics = null; - }); + componentDidMount() { + disposeOnUnmount(this, [ + reaction(() => this.props.object, () => { + this.metrics = null; + }), + ]); + } @boundMethod async loadMetrics() { diff --git a/src/renderer/components/+workloads-cronjobs/cronjob-details.tsx b/src/renderer/components/+workloads-cronjobs/cronjob-details.tsx index 78e7a6ba7c..a4ba0cbeea 100644 --- a/src/renderer/components/+workloads-cronjobs/cronjob-details.tsx +++ b/src/renderer/components/+workloads-cronjobs/cronjob-details.tsx @@ -23,7 +23,7 @@ import "./cronjob-details.scss"; import React from "react"; import kebabCase from "lodash/kebabCase"; -import { observer } from "mobx-react"; +import { disposeOnUnmount, observer } from "mobx-react"; import { DrawerItem, DrawerTitle } from "../drawer"; import { Badge } from "../badge/badge"; import { jobStore } from "../+workloads-jobs/job.store"; @@ -34,14 +34,19 @@ import { getDetailsUrl } from "../kube-detail-params"; import { CronJob, Job } from "../../../common/k8s-api/endpoints"; import { KubeObjectMeta } from "../kube-object-meta"; import logger from "../../../common/logger"; +import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; interface Props extends KubeObjectDetailsProps { } @observer export class CronJobDetails extends React.Component { - async componentDidMount() { - jobStore.reloadAll(); + componentDidMount() { + disposeOnUnmount(this, [ + kubeWatchApi.subscribeStores([ + jobStore, + ]), + ]); } render() { diff --git a/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx b/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx index efd67c1fae..3e431986c3 100644 --- a/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx +++ b/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx @@ -41,6 +41,7 @@ import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { boundMethod } from "../../utils"; import logger from "../../../common/logger"; +import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; interface Props extends KubeObjectDetailsProps { } @@ -54,13 +55,15 @@ export class DaemonSetDetails extends React.Component { makeObservable(this); } - @disposeOnUnmount - clean = reaction(() => this.props.object, () => { - this.metrics = null; - }); - componentDidMount() { - podsStore.reloadAll(); + disposeOnUnmount(this, [ + reaction(() => this.props.object, () => { + this.metrics = null; + }), + kubeWatchApi.subscribeStores([ + podsStore, + ]), + ]); } @boundMethod diff --git a/src/renderer/components/+workloads-deployments/deployment-details.tsx b/src/renderer/components/+workloads-deployments/deployment-details.tsx index b60f8cb5fc..f0ae0c1d6e 100644 --- a/src/renderer/components/+workloads-deployments/deployment-details.tsx +++ b/src/renderer/components/+workloads-deployments/deployment-details.tsx @@ -43,6 +43,7 @@ import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { boundMethod } from "../../utils"; import logger from "../../../common/logger"; +import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; interface Props extends KubeObjectDetailsProps { } @@ -56,14 +57,16 @@ export class DeploymentDetails extends React.Component { makeObservable(this); } - @disposeOnUnmount - clean = reaction(() => this.props.object, () => { - this.metrics = null; - }); - componentDidMount() { - podsStore.reloadAll(); - replicaSetStore.reloadAll(); + disposeOnUnmount(this, [ + reaction(() => this.props.object, () => { + this.metrics = null; + }), + kubeWatchApi.subscribeStores([ + podsStore, + replicaSetStore, + ]), + ]); } @boundMethod diff --git a/src/renderer/components/+workloads-jobs/job-details.tsx b/src/renderer/components/+workloads-jobs/job-details.tsx index 88dcaf7547..a0445874d8 100644 --- a/src/renderer/components/+workloads-jobs/job-details.tsx +++ b/src/renderer/components/+workloads-jobs/job-details.tsx @@ -23,7 +23,7 @@ import "./job-details.scss"; import React from "react"; import kebabCase from "lodash/kebabCase"; -import { observer } from "mobx-react"; +import { disposeOnUnmount, observer } from "mobx-react"; import { DrawerItem } from "../drawer"; import { Badge } from "../badge"; import { PodDetailsStatuses } from "../+workloads-pods/pod-details-statuses"; @@ -36,7 +36,7 @@ import type { KubeObjectDetailsProps } from "../kube-object-details"; import { getMetricsForJobs, IPodMetrics, Job } from "../../../common/k8s-api/endpoints"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; import { KubeObjectMeta } from "../kube-object-meta"; -import { makeObservable, observable } from "mobx"; +import { makeObservable, observable, reaction } from "mobx"; import { podMetricTabs, PodCharts } from "../+workloads-pods/pod-charts"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; @@ -45,6 +45,7 @@ import { boundMethod } from "autobind-decorator"; import { getDetailsUrl } from "../kube-detail-params"; import { apiManager } from "../../../common/k8s-api/api-manager"; import logger from "../../../common/logger"; +import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; interface Props extends KubeObjectDetailsProps { } @@ -58,8 +59,15 @@ export class JobDetails extends React.Component { makeObservable(this); } - async componentDidMount() { - podsStore.reloadAll(); + componentDidMount() { + disposeOnUnmount(this, [ + reaction(() => this.props.object, () => { + this.metrics = null; + }), + kubeWatchApi.subscribeStores([ + podsStore, + ]), + ]); } @boundMethod diff --git a/src/renderer/components/+workloads-overview/overview.tsx b/src/renderer/components/+workloads-overview/overview.tsx index 695e3edf95..62e447fa27 100644 --- a/src/renderer/components/+workloads-overview/overview.tsx +++ b/src/renderer/components/+workloads-overview/overview.tsx @@ -33,7 +33,6 @@ import { replicaSetStore } from "../+workloads-replicasets/replicasets.store"; import { jobStore } from "../+workloads-jobs/job.store"; import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; -import { clusterContext } from "../context"; import { WorkloadsOverviewDetailRegistry } from "../../../extensions/registries"; import type { WorkloadsOverviewRouteParams } from "../../../common/routes"; @@ -45,12 +44,15 @@ export class WorkloadsOverview extends React.Component { componentDidMount() { disposeOnUnmount(this, [ kubeWatchApi.subscribeStores([ - podsStore, deploymentStore, daemonSetStore, statefulSetStore, replicaSetStore, - jobStore, cronJobStore, eventStore, - ], { - preload: true, - namespaces: clusterContext.contextNamespaces, - }), + cronJobStore, + daemonSetStore, + deploymentStore, + eventStore, + jobStore, + podsStore, + replicaSetStore, + statefulSetStore, + ]), ]); } diff --git a/src/renderer/components/+workloads-replicasets/replicaset-details.tsx b/src/renderer/components/+workloads-replicasets/replicaset-details.tsx index e8f77a34ce..4e38ae9e1e 100644 --- a/src/renderer/components/+workloads-replicasets/replicaset-details.tsx +++ b/src/renderer/components/+workloads-replicasets/replicaset-details.tsx @@ -40,6 +40,7 @@ import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { boundMethod } from "../../utils"; import logger from "../../../common/logger"; +import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; interface Props extends KubeObjectDetailsProps { } @@ -53,13 +54,15 @@ export class ReplicaSetDetails extends React.Component { makeObservable(this); } - @disposeOnUnmount - clean = reaction(() => this.props.object, () => { - this.metrics = null; - }); - - async componentDidMount() { - podsStore.reloadAll(); + componentDidMount() { + disposeOnUnmount(this, [ + reaction(() => this.props.object, () => { + this.metrics = null; + }), + kubeWatchApi.subscribeStores([ + podsStore, + ]), + ]); } @boundMethod diff --git a/src/renderer/components/+workloads-statefulsets/statefulset-details.tsx b/src/renderer/components/+workloads-statefulsets/statefulset-details.tsx index 5351ec53f0..7d2d66568c 100644 --- a/src/renderer/components/+workloads-statefulsets/statefulset-details.tsx +++ b/src/renderer/components/+workloads-statefulsets/statefulset-details.tsx @@ -41,6 +41,7 @@ import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { boundMethod } from "../../utils"; import logger from "../../../common/logger"; +import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; interface Props extends KubeObjectDetailsProps { } @@ -54,13 +55,15 @@ export class StatefulSetDetails extends React.Component { makeObservable(this); } - @disposeOnUnmount - clean = reaction(() => this.props.object, () => { - this.metrics = null; - }); - componentDidMount() { - podsStore.reloadAll(); + disposeOnUnmount(this, [ + reaction(() => this.props.object, () => { + this.metrics = null; + }), + kubeWatchApi.subscribeStores([ + podsStore, + ]), + ]); } @boundMethod diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index 4de3cf85c2..bb521854b9 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -128,9 +128,9 @@ export class App extends React.Component { componentDidMount() { disposeOnUnmount(this, [ - kubeWatchApi.subscribeStores([namespaceStore], { - preload: true, - }), + kubeWatchApi.subscribeStores([ + namespaceStore, + ]), watchHistoryState(), ]); diff --git a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx index c8f00350d7..8b8923b951 100644 --- a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx +++ b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx @@ -28,7 +28,6 @@ import { ItemListLayout, ItemListLayoutProps } from "../item-object-list/item-li import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectMenu } from "../kube-object-menu"; import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; -import { clusterContext } from "../context"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter"; import { ResourceKindMap, ResourceNames } from "../../utils/rbac"; import { kubeSelectedUrlParam, toggleDetails } from "../kube-detail-params"; @@ -63,10 +62,7 @@ export class KubeObjectListLayout extends React.Component< if (subscribeStores) { disposeOnUnmount(this, [ - kubeWatchApi.subscribeStores(stores, { - preload: true, - namespaces: clusterContext.contextNamespaces, - }), + kubeWatchApi.subscribeStores(stores), ]); } } diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index 09471b34f0..bd4c4ec661 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -54,7 +54,9 @@ export class Sidebar extends React.Component { componentDidMount() { disposeOnUnmount(this, [ - kubeWatchApi.subscribeStores([crdStore]), + kubeWatchApi.subscribeStores([ + crdStore, + ]), ]); }