diff --git a/src/main/cluster.ts b/src/main/cluster.ts index cd92ab2650..9742506070 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -49,6 +49,7 @@ export interface ClusterState { allowedNamespaces: string[] allowedResources: string[] isGlobalWatchEnabled: boolean; + listedNamespaces: boolean; } /** @@ -201,6 +202,15 @@ export class Cluster implements ClusterModel, ClusterState { * @observable */ @observable allowedNamespaces: string[] = []; + + /** + * Is true if `allowedNamespaces` was filled by a successful kubeAPI list + * namespaces request. + * + * @observable + */ + @observable listedNamespaces = false; + /** * List of allowed resources * @@ -630,6 +640,7 @@ export class Cluster implements ClusterModel, ClusterState { allowedNamespaces: this.allowedNamespaces, allowedResources: this.allowedResources, isGlobalWatchEnabled: this.isGlobalWatchEnabled, + listedNamespaces: this.listedNamespaces, }; return toJS(state, { @@ -669,6 +680,8 @@ export class Cluster implements ClusterModel, ClusterState { protected async getAllowedNamespaces() { if (this.accessibleNamespaces.length) { + this.listedNamespaces = false; + return this.accessibleNamespaces; } @@ -677,8 +690,11 @@ export class Cluster implements ClusterModel, ClusterState { try { const namespaceList = await api.listNamespace(); + this.listedNamespaces = true; + return namespaceList.body.items.map(ns => ns.metadata.name); } catch (error) { + this.listedNamespaces = false; const ctx = this.getProxyKubeconfig().getContextObject(this.contextName); if (ctx.namespace) return [ctx.namespace]; diff --git a/src/renderer/api/kube-watch-api.ts b/src/renderer/api/kube-watch-api.ts index f2f50193db..d5f0d79f26 100644 --- a/src/renderer/api/kube-watch-api.ts +++ b/src/renderer/api/kube-watch-api.ts @@ -64,36 +64,51 @@ export class KubeWatchApi { let isUnsubscribed = false; const load = (namespaces = subscribingNamespaces) => this.preloadStores(stores, { namespaces, loadOnce }); - let preloading = preload && load(); + let preloading: boolean | ReturnType = preload && load(); let cancelReloading: IReactionDisposer = noop; + let ac = new AbortController(); - const subscribe = () => { - if (isUnsubscribed) return; + const subscribe = async (signal: AbortSignal) => { + if (isUnsubscribed || signal.aborted) return; - stores.forEach((store) => { - unsubscribeList.push(store.subscribe()); - }); + for (const store of stores) { + if (!signal.aborted) { + unsubscribeList.push(await store.subscribe()); + } + } }; + let subscribeP: Promise; + if (preloading) { if (waitUntilLoaded) { - preloading.loading.then(subscribe, error => { - this.log({ - message: new Error("Loading stores has failed"), - meta: { stores, error, options: opts }, + subscribeP = preloading.loading + .then(() => subscribe(ac.signal)) + .catch(error => { + this.log({ + message: new Error("Loading stores has failed"), + meta: { stores, error, options: opts }, + }); }); - }); } else { - subscribe(); + subscribeP = subscribe(ac.signal); } // 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); + cancelReloading = reaction(() => this.context?.selectedNamespaces, namespaces => { + if (typeof preloading === "object") { + preloading.cancelLoading(); + } + ac.abort(); + subscribeP.then(() => { + unsubscribeList.forEach(unsubscribe => unsubscribe()); + unsubscribeList.length = 0; + + ac = new AbortController(); + preloading = load(namespaces); + preloading.loading + .then(() => subscribeP = subscribe(ac.signal)); + }); }, { equals: comparer.shallow, }); @@ -104,9 +119,15 @@ export class KubeWatchApi { if (isUnsubscribed) return; isUnsubscribed = true; cancelReloading(); - preloading?.cancelLoading(); - unsubscribeList.forEach(unsubscribe => unsubscribe()); - unsubscribeList.length = 0; + + if (typeof preloading === "object") { + preloading.cancelLoading(); + } + ac.abort(); + subscribeP.then(() => { + unsubscribeList.forEach(unsubscribe => unsubscribe()); + unsubscribeList.length = 0; + }); }; } diff --git a/src/renderer/components/+apps-releases/release.store.ts b/src/renderer/components/+apps-releases/release.store.ts index 9548e494f7..6cdf498a0d 100644 --- a/src/renderer/components/+apps-releases/release.store.ts +++ b/src/renderer/components/+apps-releases/release.store.ts @@ -74,7 +74,7 @@ export class ReleaseStore extends ItemStore { } async loadFromContextNamespaces(): Promise { - return this.loadAll(namespaceStore.contextNamespaces); + return this.loadAll(namespaceStore.selectedNamespaces); } async loadItems(namespaces: string[]) { diff --git a/src/renderer/components/+namespaces/namespace-select-filter.tsx b/src/renderer/components/+namespaces/namespace-select-filter.tsx index 453849e534..bfd0880627 100644 --- a/src/renderer/components/+namespaces/namespace-select-filter.tsx +++ b/src/renderer/components/+namespaces/namespace-select-filter.tsx @@ -13,7 +13,7 @@ import { namespaceStore } from "./namespace.store"; const Placeholder = observer((props: PlaceholderProps) => { const getPlaceholder = (): React.ReactNode => { - const namespaces = namespaceStore.contextNamespaces; + const namespaces = namespaceStore.selectedNamespaces; switch (namespaces.length) { case 0: diff --git a/src/renderer/components/+namespaces/namespace.store.ts b/src/renderer/components/+namespaces/namespace.store.ts index 1f928fe2f3..eaa90a4861 100644 --- a/src/renderer/components/+namespaces/namespace.store.ts +++ b/src/renderer/components/+namespaces/namespace.store.ts @@ -65,7 +65,11 @@ export class NamespaceStore extends KubeObjectStore { } private autoLoadAllowedNamespaces(): IReactionDisposer { - return reaction(() => this.allowedNamespaces, namespaces => this.loadAll({ namespaces }), { + return reaction(() => this.allowedNamespaces, namespaces => { + console.log("autoLoadAllowedNamespaces", namespaces); + + return this.loadAll({ namespaces }); + }, { fireImmediately: true, equals: comparer.shallow, }); @@ -98,7 +102,7 @@ export class NamespaceStore extends KubeObjectStore { ].flat())); } - @computed get contextNamespaces(): string[] { + @computed get selectedNamespaces(): string[] { const namespaces = Array.from(this.contextNs); if (!namespaces.length) { @@ -108,9 +112,11 @@ export class NamespaceStore extends KubeObjectStore { return namespaces; } - getSubscribeApis() { + async getSubscribeApis() { + await this.contextReady; + // if user has given static list of namespaces let's not start watches because watch adds stuff that's not wanted - if (this.context?.cluster.accessibleNamespaces.length > 0) { + if (this.context.cluster.accessibleNamespaces.length > 0) { return []; } 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 620fbd86ac..b9ab1be471 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,7 +9,7 @@ import { apiManager } from "../../api/api-manager"; export class RoleBindingsStore extends KubeObjectStore { api = clusterRoleBindingApi; - getSubscribeApis() { + async getSubscribeApis() { return [clusterRoleBindingApi, roleBindingApi]; } diff --git a/src/renderer/components/+user-management-roles/roles.store.ts b/src/renderer/components/+user-management-roles/roles.store.ts index 82b0e66612..4a78df2eae 100644 --- a/src/renderer/components/+user-management-roles/roles.store.ts +++ b/src/renderer/components/+user-management-roles/roles.store.ts @@ -7,7 +7,7 @@ import { apiManager } from "../../api/api-manager"; export class RolesStore extends KubeObjectStore { api = clusterRoleApi; - getSubscribeApis() { + async getSubscribeApis() { return [roleApi, clusterRoleApi]; } diff --git a/src/renderer/components/+workloads-overview/overview-statuses.tsx b/src/renderer/components/+workloads-overview/overview-statuses.tsx index bc0484dadc..567ca3b692 100644 --- a/src/renderer/components/+workloads-overview/overview-statuses.tsx +++ b/src/renderer/components/+workloads-overview/overview-statuses.tsx @@ -26,7 +26,7 @@ export class OverviewStatuses extends React.Component { @autobind() renderWorkload(resource: KubeResource): React.ReactElement { const store = workloadStores[resource]; - const items = store.getAllByNs(namespaceStore.contextNamespaces); + const items = store.getAllByNs(namespaceStore.selectedNamespaces); return (
diff --git a/src/renderer/components/+workloads-overview/overview.tsx b/src/renderer/components/+workloads-overview/overview.tsx index 92bc569307..ec7ba2fc11 100644 --- a/src/renderer/components/+workloads-overview/overview.tsx +++ b/src/renderer/components/+workloads-overview/overview.tsx @@ -30,7 +30,7 @@ export class WorkloadsOverview extends React.Component { jobStore, cronJobStore, eventStore, ], { preload: true, - namespaces: clusterContext.contextNamespaces, + namespaces: clusterContext.selectedNamespaces, }), ]); } diff --git a/src/renderer/components/context.ts b/src/renderer/components/context.ts index 3c8c6d29e4..5a6ebb147f 100755 --- a/src/renderer/components/context.ts +++ b/src/renderer/components/context.ts @@ -5,7 +5,7 @@ import { namespaceStore } from "./+namespaces/namespace.store"; export interface ClusterContext { cluster?: Cluster; allNamespaces?: string[]; // available / allowed namespaces from cluster.ts - contextNamespaces?: string[]; // selected by user (see: namespace-select.tsx) + selectedNamespaces?: string[]; // selected by user (see: namespace-select.tsx) } export const clusterContext: ClusterContext = { @@ -17,7 +17,7 @@ export const clusterContext: ClusterContext = { return this.cluster?.allowedNamespaces ?? []; }, - get contextNamespaces(): string[] { - return namespaceStore.contextNamespaces ?? []; + get selectedNamespaces(): string[] { + return namespaceStore.selectedNamespaces ?? []; }, }; 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 dc0604017f..ba8688ad04 100644 --- a/src/renderer/components/item-object-list/item-list-layout.tsx +++ b/src/renderer/components/item-object-list/item-list-layout.tsx @@ -140,7 +140,7 @@ export class ItemListLayout extends React.Component { const stores = Array.from(new Set([store, ...dependentStores])); // load context namespaces by default (see also: ``) - stores.forEach(store => store.loadAll(namespaceStore.contextNamespaces)); + stores.forEach(store => store.loadAll(namespaceStore.selectedNamespaces)); } private filterCallbacks: { [type: string]: ItemsFilter } = { 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 0ecb602c16..67bb4be45e 100644 --- a/src/renderer/components/kube-object/kube-object-list-layout.tsx +++ b/src/renderer/components/kube-object/kube-object-list-layout.tsx @@ -28,7 +28,7 @@ export class KubeObjectListLayout extends React.Component extends ItemSt } @computed get contextItems(): T[] { - const namespaces = this.context?.contextNamespaces ?? []; + const namespaces = this.context?.selectedNamespaces ?? []; return this.items.filter(item => { const itemNamespace = item.getNs(); @@ -111,7 +111,7 @@ export abstract class KubeObjectStore extends ItemSt const isLoadingAll = this.context.allNamespaces.every(ns => namespaces.includes(ns)); - if (isLoadingAll) { + if (isLoadingAll && this.context.cluster?.listedNamespaces) { this.loadedNamespaces = []; return api.list({}, this.query); @@ -264,11 +264,15 @@ export abstract class KubeObjectStore extends ItemSt }); } - getSubscribeApis(): KubeApi[] { + async getSubscribeApis(): Promise { return [this.api]; } - subscribe(apis = this.getSubscribeApis()) { + async subscribe(apis?: KubeApi[]): Promise { + + await this.contextReady; + apis ??= await this.getSubscribeApis(); + const abortController = new AbortController(); const namespaces = [...this.loadedNamespaces]; diff --git a/src/renderer/utils/index.ts b/src/renderer/utils/index.ts index 517fd8f359..004b3b6759 100755 --- a/src/renderer/utils/index.ts +++ b/src/renderer/utils/index.ts @@ -2,6 +2,8 @@ export const isElectron = !!navigator.userAgent.match(/Electron/); +export type Disposer = () => void; + export * from "../../common/utils"; export * from "./cssVar";