diff --git a/src/main/cluster.ts b/src/main/cluster.ts index b9ff62e8ac..03e97d47dd 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -182,7 +182,7 @@ export class Cluster implements ClusterModel, ClusterState { */ @observable metadata: ClusterMetadata = {}; /** - * List of allowed namespaces + * List of allowed namespaces verified via K8S::SelfSubjectAccessReview api * * @observable */ @@ -195,7 +195,7 @@ export class Cluster implements ClusterModel, ClusterState { */ @observable allowedResources: string[] = []; /** - * List of accessible namespaces + * List of accessible namespaces provided by user in the Cluster Settings * * @observable */ diff --git a/src/renderer/api/kube-watch-api.ts b/src/renderer/api/kube-watch-api.ts index 78ca25256e..d1c496e59c 100644 --- a/src/renderer/api/kube-watch-api.ts +++ b/src/renderer/api/kube-watch-api.ts @@ -11,7 +11,7 @@ import { apiPrefix, isDevelopment } from "../../common/vars"; import { getHostedCluster } from "../../common/cluster-store"; export interface IKubeWatchEvent { - type: "ADDED" | "MODIFIED" | "DELETED"; + type: "ADDED" | "MODIFIED" | "DELETED" | "ERROR"; object?: T; } @@ -158,6 +158,11 @@ export class KubeWatchApi { addListener(store: KubeObjectStore, callback: (evt: IKubeWatchEvent) => void) { const listener = (evt: IKubeWatchEvent) => { + if (evt.type === "ERROR") { + // console.error(evt.object); + return; // fixme: too old resource version (e.g. reproduce: quickly jump btw pages) + } + const { namespace, resourceVersion } = evt.object.metadata; const api = apiManager.getApiByKind(evt.object.kind, evt.object.apiVersion); diff --git a/src/renderer/components/+namespaces/namespace.store.ts b/src/renderer/components/+namespaces/namespace.store.ts index ad02dd137c..09877aea4b 100644 --- a/src/renderer/components/+namespaces/namespace.store.ts +++ b/src/renderer/components/+namespaces/namespace.store.ts @@ -1,7 +1,7 @@ import { action, comparer, observable, reaction } from "mobx"; import { autobind, createStorage } from "../../utils"; -import { KubeObjectStore } from "../../kube-object.store"; -import { Namespace, namespacesApi } from "../../api/endpoints"; +import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../kube-object.store"; +import { Namespace, namespacesApi } from "../../api/endpoints/namespaces.api"; import { createPageParam } from "../../navigation"; import { apiManager } from "../../api/api-manager"; import { isAllowedResource } from "../../../common/rbac"; @@ -61,18 +61,11 @@ export class NamespaceStore extends KubeObjectStore { return super.subscribe(apis); } - protected async loadItems(namespaces?: string[]) { + protected async loadItems({ isAdmin, namespaces }: KubeObjectStoreLoadingParams) { if (!isAllowedResource("namespaces")) { - if (namespaces) return namespaces.map(this.getDummyNamespace); - - return []; - } - - if (namespaces) { - return Promise.all(namespaces.map(name => this.api.get({ name }))); - } else { - return super.loadItems(); + return namespaces.map(this.getDummyNamespace); } + return Promise.all(namespaces.map(name => this.api.get({ name }))); } protected getDummyNamespace(name: string) { @@ -105,12 +98,6 @@ export class NamespaceStore extends KubeObjectStore { else this.contextNs.push(namespace); } - @action - reset() { - super.reset(); - this.contextNs.clear(); - } - @action async remove(item: Namespace) { await super.remove(item); 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 f293dea6f0..4b96ee8ffc 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 @@ -1,7 +1,7 @@ import difference from "lodash/difference"; import uniqBy from "lodash/uniqBy"; import { clusterRoleBindingApi, IRoleBindingSubject, RoleBinding, roleBindingApi } from "../../api/endpoints"; -import { KubeObjectStore } from "../../kube-object.store"; +import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../kube-object.store"; import { autobind } from "../../utils"; import { apiManager } from "../../api/api-manager"; @@ -26,15 +26,12 @@ export class RoleBindingsStore extends KubeObjectStore { return clusterRoleBindingApi.get(params); } - protected loadItems(namespaces?: string[]) { - if (namespaces) { - return Promise.all( - namespaces.map(namespace => roleBindingApi.list({ namespace })) - ).then(items => items.flat()); - } else { - return Promise.all([clusterRoleBindingApi.list(), roleBindingApi.list()]) - .then(items => items.flat()); - } + protected async loadItems({ isAdmin, namespaces }: KubeObjectStoreLoadingParams): Promise { + const items = await Promise.all([ + super.loadItems({ isAdmin, namespaces, api: clusterRoleBindingApi }), + super.loadItems({ isAdmin, namespaces, api: roleBindingApi }), + ]); + return items.flat(); } protected async createItem(params: { name: string; namespace?: string }, data?: Partial) { diff --git a/src/renderer/components/+user-management-roles/roles.store.ts b/src/renderer/components/+user-management-roles/roles.store.ts index 6af33deacb..f395dfc69c 100644 --- a/src/renderer/components/+user-management-roles/roles.store.ts +++ b/src/renderer/components/+user-management-roles/roles.store.ts @@ -1,6 +1,6 @@ import { clusterRoleApi, Role, roleApi } from "../../api/endpoints"; import { autobind } from "../../utils"; -import { KubeObjectStore } from "../../kube-object.store"; +import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../kube-object.store"; import { apiManager } from "../../api/api-manager"; @autobind() @@ -24,15 +24,12 @@ export class RolesStore extends KubeObjectStore { return clusterRoleApi.get(params); } - protected loadItems(namespaces?: string[]): Promise { - if (namespaces) { - return Promise.all( - namespaces.map(namespace => roleApi.list({ namespace })) - ).then(items => items.flat()); - } else { - return Promise.all([clusterRoleApi.list(), roleApi.list()]) - .then(items => items.flat()); - } + protected async loadItems({ isAdmin, namespaces }: KubeObjectStoreLoadingParams): Promise { + const items = await Promise.all([ + super.loadItems({ isAdmin, namespaces, api: clusterRoleApi }), + super.loadItems({ isAdmin, namespaces, api: roleApi }), + ]); + return items.flat(); } protected async createItem(params: { name: string; namespace?: string }, data?: Partial) { diff --git a/src/renderer/kube-object.store.ts b/src/renderer/kube-object.store.ts index bb2fffd819..b19ef3f9a0 100644 --- a/src/renderer/kube-object.store.ts +++ b/src/renderer/kube-object.store.ts @@ -8,6 +8,12 @@ import { IKubeApiQueryParams, KubeApi } from "./api/kube-api"; import { KubeJsonApiData } from "./api/kube-json-api"; import { getHostedCluster } from "../common/cluster-store"; +export interface KubeObjectStoreLoadingParams { + isAdmin: boolean; + namespaces: string[]; + api?: KubeApi; +} + @autobind() export abstract class KubeObjectStore extends ItemStore { abstract api: KubeApi; @@ -71,14 +77,15 @@ export abstract class KubeObjectStore extends ItemSt } } - protected async loadItems(allowedNamespaces?: string[]): Promise { - if (!this.api.isNamespaced || !allowedNamespaces) { - return this.api.list({}, this.query); - } else { - return Promise - .all(allowedNamespaces.map(namespace => this.api.list({ namespace }))) - .then(items => items.flat()); + protected async loadItems({ isAdmin, namespaces, api }: KubeObjectStoreLoadingParams): Promise { + if (!api.isNamespaced) { + if (isAdmin) return api.list({}, this.query); + return []; } + + return Promise + .all(namespaces.map(namespace => api.list({ namespace }))) + .then(items => items.flat()); } protected filterItemsOnLoad(items: T[]) { @@ -91,22 +98,16 @@ export abstract class KubeObjectStore extends ItemSt let items: T[]; try { - const { allowedNamespaces, accessibleNamespaces, isAdmin } = getHostedCluster(); - - if (isAdmin && accessibleNamespaces.length == 0) { - items = await this.loadItems(); - } else { - items = await this.loadItems(allowedNamespaces); - } - + const { allowedNamespaces: namespaces, isAdmin } = getHostedCluster(); + items = await this.loadItems({ isAdmin, namespaces, api: this.api }); items = this.filterItemsOnLoad(items); - } finally { - if (items) { - items = this.sortItems(items); - this.items.replace(items); - } - this.isLoading = false; + items = this.sortItems(items); + this.items.replace(items); this.isLoaded = true; + } catch (error) { + console.error("Loading store items failed", { error, store: this }); + } finally { + this.isLoading = false; } } @@ -194,7 +195,7 @@ export abstract class KubeObjectStore extends ItemSt // create latest non-observable copy of items to apply updates in one action (==single render) const items = this.items.toJS(); - for (const {type, object} of this.eventsBuffer.clear()) { + for (const { type, object } of this.eventsBuffer.clear()) { const index = items.findIndex(item => item.getId() === object.metadata?.uid); const item = items[index]; const api = apiManager.getApiByKind(object.kind, object.apiVersion);