From 3e005fc6110ce5855a57dae2becb7ee88c12a9a6 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 21 Jan 2021 18:48:17 +0200 Subject: [PATCH] store subscribing refactoring -- part 1 Signed-off-by: Roman --- src/renderer/api/api-manager.ts | 4 +- src/renderer/api/kube-api.ts | 14 +---- src/renderer/api/kube-watch-api.ts | 63 +++++++++++++++---- .../components/+cluster/cluster-overview.tsx | 43 ++++--------- .../+namespaces/namespace-select.tsx | 19 +++--- .../components/+namespaces/namespace.store.ts | 6 +- .../+network-services/service-details.tsx | 12 ++-- .../role-bindings.store.ts | 4 +- .../+user-management-roles/roles.store.ts | 4 +- .../+workloads-overview/overview.tsx | 58 +++++------------ src/renderer/components/app.tsx | 41 +++++------- .../item-object-list/item-list-layout.tsx | 51 ++------------- .../kube-object/kube-object-list-layout.tsx | 13 +++- src/renderer/item.store.ts | 2 +- src/renderer/kube-object.store.ts | 15 +++-- 15 files changed, 148 insertions(+), 201 deletions(-) diff --git a/src/renderer/api/api-manager.ts b/src/renderer/api/api-manager.ts index 629a0f29c2..47500adf79 100644 --- a/src/renderer/api/api-manager.ts +++ b/src/renderer/api/api-manager.ts @@ -2,7 +2,7 @@ import type { KubeObjectStore } from "../kube-object.store"; import { action, observable } from "mobx"; import { autobind } from "../utils"; -import { KubeApi } from "./kube-api"; +import { KubeApi, parseKubeApi } from "./kube-api"; @autobind() export class ApiManager { @@ -11,7 +11,7 @@ export class ApiManager { getApi(pathOrCallback: string | ((api: KubeApi) => boolean)) { if (typeof pathOrCallback === "string") { - return this.apis.get(pathOrCallback) || this.apis.get(KubeApi.parseApi(pathOrCallback).apiBase); + return this.apis.get(pathOrCallback) || this.apis.get(parseKubeApi(pathOrCallback).apiBase); } return Array.from(this.apis.values()).find(pathOrCallback ?? (() => true)); diff --git a/src/renderer/api/kube-api.ts b/src/renderer/api/kube-api.ts index 8a3a2517c2..e62603b14f 100644 --- a/src/renderer/api/kube-api.ts +++ b/src/renderer/api/kube-api.ts @@ -92,14 +92,6 @@ export function ensureObjectSelfLink(api: KubeApi, object: KubeJsonApiData) { } export class KubeApi { - static parseApi = parseKubeApi; - - static watchAll(...apis: KubeApi[]) { - const disposers = apis.map(api => api.watch()); - - return () => disposers.forEach(unwatch => unwatch()); - } - readonly kind: string; readonly apiBase: string; readonly apiPrefix: string; @@ -124,7 +116,7 @@ export class KubeApi { if (!options.apiBase) { options.apiBase = objectConstructor.apiBase; } - const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = KubeApi.parseApi(options.apiBase); + const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parseKubeApi(options.apiBase); this.kind = kind; this.isNamespaced = isNamespaced; @@ -157,7 +149,7 @@ export class KubeApi { for (const apiUrl of apiBases) { // Split e.g. "/apis/extensions/v1beta1/ingresses" to parts - const { apiPrefix, apiGroup, apiVersionWithGroup, resource } = KubeApi.parseApi(apiUrl); + const { apiPrefix, apiGroup, apiVersionWithGroup, resource } = parseKubeApi(apiUrl); // Request available resources try { @@ -366,7 +358,7 @@ export class KubeApi { } watch(): () => void { - return kubeWatchApi.subscribe(this); + return kubeWatchApi.subscribeApi(this); } } diff --git a/src/renderer/api/kube-watch-api.ts b/src/renderer/api/kube-watch-api.ts index 3d440690f8..ee6c9202a1 100644 --- a/src/renderer/api/kube-watch-api.ts +++ b/src/renderer/api/kube-watch-api.ts @@ -2,13 +2,13 @@ import type { Cluster } from "../../main/cluster"; import type { IKubeWatchEvent, IKubeWatchEventStreamEnd, IWatchRoutePayload } from "../../main/routes/watch-route"; - import type { KubeObject } from "./kube-object"; +import type { KubeObjectStore } from "../kube-object.store"; + import { computed, observable, reaction } from "mobx"; import { autobind, EventEmitter } from "../utils"; -import { ensureObjectSelfLink, KubeApi } from "./kube-api"; +import { ensureObjectSelfLink, KubeApi, parseKubeApi } from "./kube-api"; import { KubeJsonApiData, KubeJsonApiError } from "./kube-json-api"; -import { KubeObjectStore } from "../kube-object.store"; import { apiPrefix, isProduction } from "../../common/vars"; import { apiManager } from "./api-manager"; @@ -21,6 +21,11 @@ export interface IKubeWatchMessage { store?: KubeObjectStore; } +export interface IKubeWatchSubscribeStoreOptions { + autoLoad?: boolean; + waitUntilLoaded?: boolean; +} + export interface IKubeWatchLog { message: string | Error; meta?: object | any; @@ -57,17 +62,49 @@ export class KubeWatchApi { return this.subscribers.get(api) || 0; } - subscribe(...apis: KubeApi[]) { + subscribeApi(api: KubeApi | KubeApi[]) { + const apis: KubeApi[] = [api].flat(); + apis.forEach(api => { this.subscribers.set(api, this.getSubscribersCount(api) + 1); }); - return () => apis.forEach(api => { - const count = this.getSubscribersCount(api) - 1; + return () => { + apis.forEach(api => { + const count = this.getSubscribersCount(api) - 1; - if (count <= 0) this.subscribers.delete(api); - else this.subscribers.set(api, count); + if (count <= 0) this.subscribers.delete(api); + else this.subscribers.set(api, count); + }); + }; + } + + async subscribeStores(stores: KubeObjectStore[], options: IKubeWatchSubscribeStoreOptions = {}): Promise<() => void> { + this.log({ + message: "Subscribing to stores", + meta: { stores, options }, }); + + const { autoLoad = true, waitUntilLoaded = true } = options; + const loading: Promise[] = []; + + if (autoLoad) { + loading.push(...stores.map(store => store.loadAll())); + } + + if (waitUntilLoaded) { + try { + await Promise.all(loading); + } catch (error) { + this.log({ + message: new Error("Loading stores has failed"), + meta: { stores, error, options }, + }) + } + } + + const disposers = await Promise.all(stores.map(store => store.subscribe())); + return () => disposers.forEach(dispose => dispose()); // unsubscribe } protected async resolveCluster(): Promise { @@ -107,7 +144,7 @@ export class KubeWatchApi { } this.log({ - message: "connecting", + message: "Connecting", meta: payload, }); @@ -204,7 +241,7 @@ export class KubeWatchApi { } protected async onServerStreamEnd(event: IKubeWatchEventStreamEnd) { - const { apiBase, namespace } = KubeApi.parseApi(event.url); + const { apiBase, namespace } = parseKubeApi(event.url); const api = apiManager.getApi(apiBase); if (api) { @@ -213,7 +250,7 @@ export class KubeWatchApi { this.connect(); } catch (error) { this.log({ - message: new Error("failed to reconnect on stream end"), + message: new Error("Failed to reconnect on stream end"), meta: { error, event }, }); @@ -227,7 +264,9 @@ export class KubeWatchApi { } protected log({ message, meta }: IKubeWatchLog) { - if (isProduction) return; + if (isProduction) { + return; + } const logMessage = `%c[KUBE-WATCH-API]: ${String(message).toUpperCase()}`; const isError = message instanceof Error; diff --git a/src/renderer/components/+cluster/cluster-overview.tsx b/src/renderer/components/+cluster/cluster-overview.tsx index 104c6fd022..74e6068e56 100644 --- a/src/renderer/components/+cluster/cluster-overview.tsx +++ b/src/renderer/components/+cluster/cluster-overview.tsx @@ -3,13 +3,9 @@ import "./cluster-overview.scss"; import React from "react"; import { reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; - -import { eventStore } from "../+events/event.store"; import { nodesStore } from "../+nodes/nodes.store"; import { podsStore } from "../+workloads-pods/pods.store"; import { getHostedCluster } from "../../../common/cluster-store"; -import { isAllowedResource } from "../../../common/rbac"; -import { KubeObjectStore } from "../../kube-object.store"; import { interval } from "../../utils"; import { TabLayout } from "../layout/tab-layout"; import { Spinner } from "../spinner"; @@ -17,45 +13,32 @@ import { ClusterIssues } from "./cluster-issues"; import { ClusterMetrics } from "./cluster-metrics"; import { clusterOverviewStore } from "./cluster-overview.store"; import { ClusterPieCharts } from "./cluster-pie-charts"; +import { eventStore } from "../+events/event.store"; +import { kubeWatchApi } from "../../api/kube-watch-api"; @observer export class ClusterOverview extends React.Component { - private stores: KubeObjectStore[] = []; - private subscribers: Array<() => void> = []; - private metricPoller = interval(60, this.loadMetrics); - - @disposeOnUnmount - fetchMetrics = reaction( - () => clusterOverviewStore.metricNodeRole, // Toggle Master/Worker node switcher - () => this.metricPoller.restart(true) - ); + private metricPoller = interval(60, () => this.loadMetrics()); loadMetrics() { getHostedCluster().available && clusterOverviewStore.loadMetrics(); } async componentDidMount() { - if (isAllowedResource("nodes")) { - this.stores.push(nodesStore); - } + this.metricPoller.start(true); - if (isAllowedResource("pods")) { - this.stores.push(podsStore); - } - - if (isAllowedResource("events")) { - this.stores.push(eventStore); - } - - await Promise.all(this.stores.map(store => store.loadAll())); - this.loadMetrics(); - - this.subscribers = this.stores.map(store => store.subscribe()); - this.metricPoller.start(); + disposeOnUnmount(this, [ + await kubeWatchApi.subscribeStores([nodesStore, podsStore, eventStore], { + autoLoad: true, + }), + reaction( + () => clusterOverviewStore.metricNodeRole, // Toggle Master/Worker node switcher + () => this.metricPoller.restart(true) + ), + ]); } componentWillUnmount() { - this.subscribers.forEach(dispose => dispose()); // unsubscribe all this.metricPoller.stop(); } diff --git a/src/renderer/components/+namespaces/namespace-select.tsx b/src/renderer/components/+namespaces/namespace-select.tsx index 079a9cd0b6..d4656659a0 100644 --- a/src/renderer/components/+namespaces/namespace-select.tsx +++ b/src/renderer/components/+namespaces/namespace-select.tsx @@ -2,9 +2,9 @@ import "./namespace-select.scss"; import React from "react"; import { computed } from "mobx"; -import { observer } from "mobx-react"; +import { disposeOnUnmount, observer } from "mobx-react"; import { Select, SelectOption, SelectProps } from "../select"; -import { cssNames, noop } from "../../utils"; +import { cssNames } from "../../utils"; import { Icon } from "../icon"; import { namespaceStore } from "./namespace.store"; import { FilterIcon } from "../item-object-list/filter-icon"; @@ -28,17 +28,14 @@ const defaultProps: Partial = { @observer export class NamespaceSelect extends React.Component { static defaultProps = defaultProps as object; - private unsubscribe = noop; async componentDidMount() { if (!namespaceStore.isLoaded) { await namespaceStore.loadAll(); } - this.unsubscribe = namespaceStore.subscribe(); - } - - componentWillUnmount() { - this.unsubscribe(); + disposeOnUnmount(this, [ + await namespaceStore.subscribe(), + ]); } @computed get options(): SelectOption[] { @@ -60,7 +57,7 @@ export class NamespaceSelect extends React.Component { return label || ( <> - {showIcons && } + {showIcons && } {value} ); @@ -103,9 +100,9 @@ export class NamespaceSelectFilter extends React.Component { return (
- + {namespace} - {isSelected && } + {isSelected && }
); }} diff --git a/src/renderer/components/+namespaces/namespace.store.ts b/src/renderer/components/+namespaces/namespace.store.ts index 50ec2c8038..4636c4b525 100644 --- a/src/renderer/components/+namespaces/namespace.store.ts +++ b/src/renderer/components/+namespaces/namespace.store.ts @@ -117,15 +117,15 @@ export class NamespaceStore extends KubeObjectStore { return namespaces; } - subscribe(apis = [this.api]) { + getSubscribeApis() { const { accessibleNamespaces } = getHostedCluster(); // if user has given static list of namespaces let's not start watches because watch adds stuff that's not wanted if (accessibleNamespaces.length > 0) { - return Function; // no-op + return []; // no-op } - return super.subscribe(apis); + return super.getSubscribeApis(); } protected async loadItems(params: KubeObjectStoreLoadingParams) { diff --git a/src/renderer/components/+network-services/service-details.tsx b/src/renderer/components/+network-services/service-details.tsx index 58cbe0a86e..695ff2785a 100644 --- a/src/renderer/components/+network-services/service-details.tsx +++ b/src/renderer/components/+network-services/service-details.tsx @@ -1,12 +1,12 @@ import "./service-details.scss"; import React from "react"; -import { observer } from "mobx-react"; +import { disposeOnUnmount, observer } from "mobx-react"; import { DrawerItem, DrawerTitle } from "../drawer"; import { Badge } from "../badge"; import { KubeEventDetails } from "../+events/kube-event-details"; import { KubeObjectDetailsProps } from "../kube-object"; -import { Service, endpointApi } from "../../api/endpoints"; +import { Service } from "../../api/endpoints"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { ServicePortComponent } from "./service-port-component"; import { endpointStore } from "../+network-endpoints/endpoints.store"; @@ -18,11 +18,13 @@ interface Props extends KubeObjectDetailsProps { @observer export class ServiceDetails extends React.Component { - componentDidMount() { + async componentDidMount() { if (!endpointStore.isLoaded) { endpointStore.loadAll(); } - endpointApi.watch(); + disposeOnUnmount(this, [ + await endpointStore.subscribe(), + ]); } render() { @@ -77,7 +79,7 @@ export class ServiceDetails extends React.Component { )} - + ); } diff --git a/src/renderer/components/+user-management-roles-bindings/role-bindings.store.ts b/src/renderer/components/+user-management-roles-bindings/role-bindings.store.ts index 71890acc44..620fbd86ac 100644 --- a/src/renderer/components/+user-management-roles-bindings/role-bindings.store.ts +++ b/src/renderer/components/+user-management-roles-bindings/role-bindings.store.ts @@ -9,8 +9,8 @@ import { apiManager } from "../../api/api-manager"; export class RoleBindingsStore extends KubeObjectStore { api = clusterRoleBindingApi; - subscribe() { - return super.subscribe([clusterRoleBindingApi, roleBindingApi]); + getSubscribeApis() { + return [clusterRoleBindingApi, roleBindingApi]; } protected sortItems(items: RoleBinding[]) { diff --git a/src/renderer/components/+user-management-roles/roles.store.ts b/src/renderer/components/+user-management-roles/roles.store.ts index 56075a6a97..bf179f9a3c 100644 --- a/src/renderer/components/+user-management-roles/roles.store.ts +++ b/src/renderer/components/+user-management-roles/roles.store.ts @@ -7,8 +7,8 @@ import { apiManager } from "../../api/api-manager"; export class RolesStore extends KubeObjectStore { api = clusterRoleApi; - subscribe() { - return super.subscribe([roleApi, clusterRoleApi]); + getSubscribeApis() { + return [roleApi, clusterRoleApi]; } protected sortItems(items: Role[]) { diff --git a/src/renderer/components/+workloads-overview/overview.tsx b/src/renderer/components/+workloads-overview/overview.tsx index 351b57462c..ad1b64b0a0 100644 --- a/src/renderer/components/+workloads-overview/overview.tsx +++ b/src/renderer/components/+workloads-overview/overview.tsx @@ -1,8 +1,8 @@ import "./overview.scss"; import React from "react"; -import { observable, when } from "mobx"; -import { observer } from "mobx-react"; +import { observable } from "mobx"; +import { disposeOnUnmount, observer } from "mobx-react"; import { OverviewStatuses } from "./overview-statuses"; import { RouteComponentProps } from "react-router"; import { IWorkloadsOverviewRouteParams } from "../+workloads"; @@ -15,9 +15,8 @@ import { replicaSetStore } from "../+workloads-replicasets/replicasets.store"; import { jobStore } from "../+workloads-jobs/job.store"; import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; import { Events } from "../+events"; -import { KubeObjectStore } from "../../kube-object.store"; import { isAllowedResource } from "../../../common/rbac"; -import { namespaceStore } from "../+namespaces/namespace.store"; +import { kubeWatchApi } from "../../api/kube-watch-api"; interface Props extends RouteComponentProps { } @@ -28,47 +27,18 @@ export class WorkloadsOverview extends React.Component { @observable isUnmounting = false; async componentDidMount() { - const stores: KubeObjectStore[] = [ - isAllowedResource("pods") && podsStore, - isAllowedResource("deployments") && deploymentStore, - isAllowedResource("daemonsets") && daemonSetStore, - isAllowedResource("statefulsets") && statefulSetStore, - isAllowedResource("replicasets") && replicaSetStore, - isAllowedResource("jobs") && jobStore, - isAllowedResource("cronjobs") && cronJobStore, - isAllowedResource("events") && eventStore, - ].filter(Boolean); + disposeOnUnmount(this, [ + await kubeWatchApi.subscribeStores([ + podsStore, deploymentStore, daemonSetStore, + statefulSetStore, replicaSetStore, + jobStore, cronJobStore, eventStore, + ]) + ]); - const unsubscribeMap = new Map void>(); - - const loadStores = async () => { - this.isLoading = true; - - for (const store of stores) { - if (this.isUnmounting) break; - - try { - await store.loadAll(); - unsubscribeMap.get(store)?.(); // unsubscribe previous watcher - unsubscribeMap.set(store, store.subscribe()); - } catch (error) { - console.error("loading store error", error); - } - } - this.isLoading = false; - }; - - namespaceStore.onContextChange(loadStores, { - fireImmediately: true, - }); - - await when(() => this.isUnmounting && !this.isLoading); - unsubscribeMap.forEach(dispose => dispose()); - unsubscribeMap.clear(); - } - - componentWillUnmount() { - this.isUnmounting = true; + // fixme: reload stores + // namespaceStore.onContextChange(loadStores, { + // fireImmediately: true, + // }); } render() { diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index 495182b2a9..7a431771b4 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { observer } from "mobx-react"; +import { disposeOnUnmount, observer } from "mobx-react"; import { Redirect, Route, Router, Switch } from "react-router"; import { history } from "../navigation"; import { Notifications } from "./notifications"; @@ -45,6 +45,7 @@ import { eventStore } from "./+events/event.store"; import { computed, reaction } from "mobx"; import { nodesStore } from "./+nodes/nodes.store"; import { podsStore } from "./+workloads-pods/pods.store"; +import { kubeWatchApi } from "../api/kube-watch-api"; import { sum } from "lodash"; import { ReplicaSetScaleDialog } from "./+workloads-replicasets/replicaset-scale-dialog"; @@ -77,36 +78,22 @@ export class App extends React.Component { async componentDidMount() { const cluster = getHostedCluster(); - const promises: Promise[] = []; - if (isAllowedResource("events") && isAllowedResource("pods")) { - promises.push(eventStore.loadAll()); - promises.push(podsStore.loadAll()); - } + disposeOnUnmount(this, [ + await kubeWatchApi.subscribeStores([podsStore, nodesStore, eventStore], { + autoLoad: true, + waitUntilLoaded: true, + }), - if (isAllowedResource("nodes")) { - promises.push(nodesStore.loadAll()); - } - await Promise.all(promises); - - if (eventStore.isLoaded && podsStore.isLoaded) { - eventStore.subscribe(); - podsStore.subscribe(); - } - - if (nodesStore.isLoaded) { - nodesStore.subscribe(); - } - - reaction(() => this.warningsCount, (count) => { - broadcastMessage(`cluster-warning-event-count:${cluster.id}`, count); - }); + reaction(() => this.warningsCount, (count) => { + broadcastMessage(`cluster-warning-event-count:${cluster.id}`, count); + }), + ]); } - @computed - get warningsCount() { - let warnings = sum(nodesStore.items - .map(node => node.getWarningConditions().length)); + // todo: move to nodes-store.ts + @computed get warningsCount() { + let warnings = sum(nodesStore.items.map(node => node.getWarningConditions().length)); warnings = warnings + eventStore.getWarnings().length; diff --git a/src/renderer/components/item-object-list/item-list-layout.tsx b/src/renderer/components/item-object-list/item-list-layout.tsx index aaeb7438ea..875cbf0a09 100644 --- a/src/renderer/components/item-object-list/item-list-layout.tsx +++ b/src/renderer/components/item-object-list/item-list-layout.tsx @@ -2,7 +2,7 @@ import "./item-list-layout.scss"; import groupBy from "lodash/groupBy"; import React, { ReactNode } from "react"; -import { computed, IReactionDisposer, observable, reaction, toJS } from "mobx"; +import { computed, observable, reaction, toJS } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import { ConfirmDialog, ConfirmDialogParams } from "../confirm-dialog"; import { Table, TableCell, TableCellProps, TableHead, TableProps, TableRow, TableRowProps, TableSortCallback } from "../table"; @@ -12,7 +12,6 @@ import { NoItems } from "../no-items"; import { Spinner } from "../spinner"; import { ItemObject, ItemStore } from "../../item.store"; import { SearchInputUrl } from "../input"; -import { namespaceStore } from "../+namespaces/namespace.store"; import { Filter, FilterType, pageFilters } from "./page-filters.store"; import { PageFiltersList } from "./page-filters-list"; import { PageFiltersSelect } from "./page-filters-select"; @@ -97,10 +96,6 @@ interface ItemListLayoutUserSettings { export class ItemListLayout extends React.Component { static defaultProps = defaultProps as object; - private watchDisposers: IReactionDisposer[] = []; - - @observable isUnmounting = false; - @observable userSettings: ItemListLayoutUserSettings = { showAppliedFilters: false, }; @@ -125,50 +120,14 @@ export class ItemListLayout extends React.Component { throw new Error("[ItemListLayout]: configurable list require props.tableId to be specified"); } - this.loadStores(); - + // fixme: reload stores if (!isClusterScoped) { - disposeOnUnmount(this, [ - namespaceStore.onContextChange(() => this.loadStores()) - ]); + // disposeOnUnmount(this, [ + // namespaceStore.onContextChange(() => this.loadStores()) + // ]); } } - async componentWillUnmount() { - this.isUnmounting = true; - this.unsubscribeStores(); - } - - @computed get stores() { - const { store, dependentStores } = this.props; - - return new Set([store, ...dependentStores]); - } - - async loadStores() { - this.unsubscribeStores(); // reset first - - // load - for (const store of this.stores) { - if (this.isUnmounting) { - this.unsubscribeStores(); - break; - } - - try { - await store.loadAll(); - this.watchDisposers.push(store.subscribe()); - } catch (error) { - console.error("loading store error", error); - } - } - } - - unsubscribeStores() { - this.watchDisposers.forEach(dispose => dispose()); - this.watchDisposers.length = 0; - } - private filterCallbacks: { [type: string]: ItemsFilter } = { [FilterType.SEARCH]: items => { const { searchFilters, isSearchable } = this.props; diff --git a/src/renderer/components/kube-object/kube-object-list-layout.tsx b/src/renderer/components/kube-object/kube-object-list-layout.tsx index 25922f0f72..136e47099d 100644 --- a/src/renderer/components/kube-object/kube-object-list-layout.tsx +++ b/src/renderer/components/kube-object/kube-object-list-layout.tsx @@ -1,15 +1,17 @@ import React from "react"; import { computed } from "mobx"; -import { observer } from "mobx-react"; +import { disposeOnUnmount, observer } from "mobx-react"; import { cssNames } from "../../utils"; import { KubeObject } from "../../api/kube-object"; import { ItemListLayout, ItemListLayoutProps } from "../item-object-list/item-list-layout"; import { KubeObjectStore } from "../../kube-object.store"; import { KubeObjectMenu } from "./kube-object-menu"; import { kubeSelectedUrlParam, showDetails } from "./kube-object-details"; +import { kubeWatchApi } from "../../api/kube-watch-api"; export interface KubeObjectListLayoutProps extends ItemListLayoutProps { store: KubeObjectStore; + dependentStores?: KubeObjectStore[]; } @observer @@ -18,6 +20,15 @@ export class KubeObjectListLayout extends React.Component { if (this.props.onDetails) { this.props.onDetails(item); diff --git a/src/renderer/item.store.ts b/src/renderer/item.store.ts index eccd2b52df..e09c65d43d 100644 --- a/src/renderer/item.store.ts +++ b/src/renderer/item.store.ts @@ -167,7 +167,7 @@ export abstract class ItemStore { async removeSelectedItems?(): Promise; // eslint-disable-next-line unused-imports/no-unused-vars-ts - subscribe(...args: any[]) { + async subscribe(...args: any[]): Promise<() => void> { return noop; } diff --git a/src/renderer/kube-object.store.ts b/src/renderer/kube-object.store.ts index 650d76e250..4242fe819a 100644 --- a/src/renderer/kube-object.store.ts +++ b/src/renderer/kube-object.store.ts @@ -5,7 +5,7 @@ import { KubeObject } from "./api/kube-object"; import { IKubeWatchEvent, IKubeWatchMessage, kubeWatchApi } from "./api/kube-watch-api"; import { ItemStore } from "./item.store"; import { apiManager } from "./api/api-manager"; -import { IKubeApiQueryParams, KubeApi } from "./api/kube-api"; +import { IKubeApiQueryParams, KubeApi, parseKubeApi } from "./api/kube-api"; import { KubeJsonApiData } from "./api/kube-json-api"; export interface KubeObjectStoreLoadingParams { @@ -152,7 +152,7 @@ export abstract class KubeObjectStore extends ItemSt @action async loadFromPath(resourcePath: string) { - const { namespace, name } = KubeApi.parseApi(resourcePath); + const { namespace, name } = parseKubeApi(resourcePath); return this.load({ name, namespace }); } @@ -203,8 +203,15 @@ export abstract class KubeObjectStore extends ItemSt }); } - subscribe(apis = [this.api]) { - return KubeApi.watchAll(...apis); + getSubscribeApis(): KubeApi[] { + return [this.api]; + } + + async subscribe(apis = this.getSubscribeApis()) { + const cluster = await this.resolveCluster(); + const allowedApis = apis.filter(api => cluster.isAllowedResource(api.kind)); + + return kubeWatchApi.subscribeApi(allowedApis); } @action