diff --git a/src/common/k8s-api/cluster-context.ts b/src/common/k8s-api/cluster-context.ts index 596e658bde..af892a2ce3 100644 --- a/src/common/k8s-api/cluster-context.ts +++ b/src/common/k8s-api/cluster-context.ts @@ -25,4 +25,5 @@ export interface ClusterContext { cluster?: Cluster; allNamespaces: string[]; // available / allowed namespaces from cluster.ts contextNamespaces: string[]; // selected by user (see: namespace-select.tsx) + hasSelectedAll: boolean; } diff --git a/src/common/k8s-api/kube-watch-api.ts b/src/common/k8s-api/kube-watch-api.ts index 1ec352168a..a97fc81c9e 100644 --- a/src/common/k8s-api/kube-watch-api.ts +++ b/src/common/k8s-api/kube-watch-api.ts @@ -25,8 +25,8 @@ import type { KubeObjectStore } from "./kube-object.store"; import type { ClusterContext } from "./cluster-context"; -import { comparer, observable, reaction, makeObservable } from "mobx"; -import { autoBind, disposer, Disposer, noop } from "../utils"; +import { comparer, reaction } from "mobx"; +import { disposer, Disposer, noop } from "../utils"; import type { KubeJsonApiData } from "./kube-json-api"; import type { KubeObject } from "./kube-object"; import AbortController from "abort-controller"; @@ -111,15 +111,10 @@ class WatchCount { } export class KubeWatchApi { - @observable context: ClusterContext = null; + static context: ClusterContext = null; #watch = new WatchCount(); - constructor() { - makeObservable(this); - autoBind(this); - } - private subscribeStore({ store, parent, watchChanges, namespaces, onLoadFailure }: SubscribeStoreParams): Disposer { if (this.#watch.inc(store) > 1) { // don't load or subscribe to a store more than once @@ -151,9 +146,15 @@ export class KubeWatchApi { const cancelReloading = watchChanges ? reaction( // Note: must slice because reaction won't fire if it isn't there - () => this.context.contextNamespaces.slice(), - namespaces => { - console.log(`changing watch ${store.api.apiBase}`, namespaces); + () => [KubeWatchApi.context.contextNamespaces.slice(), KubeWatchApi.context.hasSelectedAll] as const, + ([namespaces, curSelectedAll], [, prevSelectedAll]) => { + if (curSelectedAll && prevSelectedAll) { + console.log(`[KUBE-WATCH-API]: Not changing watch for ${store.api.apiBase} because a new namespace was created but all namespaces are selected`); + + return; + } + + console.log(`[KUBE-WATCH-API]: changing watch ${store.api.apiBase}`, namespaces); childController.abort(); unsubscribe(); childController = new WrappedAbortController(parent); @@ -181,8 +182,8 @@ export class KubeWatchApi { ...stores.map(store => this.subscribeStore({ store, parent, - watchChanges: !namespaces, - namespaces: namespaces ?? this.context?.contextNamespaces ?? [], + watchChanges: !namespaces && store.api.isNamespaced, + namespaces: namespaces ?? KubeWatchApi.context?.contextNamespaces ?? [], onLoadFailure, })), ); diff --git a/src/renderer/cluster-frame.tsx b/src/renderer/cluster-frame.tsx index 9359838ee7..734e6c8486 100755 --- a/src/renderer/cluster-frame.tsx +++ b/src/renderer/cluster-frame.tsx @@ -42,11 +42,11 @@ import whatInput from "what-input"; import { clusterSetFrameIdHandler } from "../common/cluster-ipc"; import { ClusterPageMenuRegistration, ClusterPageMenuRegistry } from "../extensions/registries"; import { StatefulSetScaleDialog } from "./components/+workloads-statefulsets/statefulset-scale-dialog"; -import { kubeWatchApi } from "../common/k8s-api/kube-watch-api"; +import { KubeWatchApi, kubeWatchApi } from "../common/k8s-api/kube-watch-api"; import { ReplicaSetScaleDialog } from "./components/+workloads-replicasets/replicaset-scale-dialog"; import { CommandContainer } from "./components/command-palette/command-container"; import { KubeObjectStore } from "../common/k8s-api/kube-object.store"; -import { clusterContext } from "./components/context"; +import { FrameContext } from "./components/context"; import * as routes from "../common/routes"; import { TabLayout, TabLayoutRoute } from "./components/layout/tab-layout"; import { ErrorBoundary } from "./components/error-boundary"; @@ -73,6 +73,8 @@ import { watchHistoryState } from "./remote-helpers/history-updater"; import { unmountComponentAtNode } from "react-dom"; import { PortForwardDialog } from "./port-forward"; import { DeleteClusterDialog } from "./components/delete-cluster-dialog"; +import { WorkloadsOverview } from "./components/+workloads-overview/overview"; +import { KubeObjectListLayout } from "./components/kube-object-list-layout"; @observer export class ClusterFrame extends React.Component { @@ -91,10 +93,12 @@ export class ClusterFrame extends React.Component { ClusterFrame.clusterId = getHostedClusterId(); + const cluster = ClusterStore.getInstance().getById(ClusterFrame.clusterId); + logger.info(`${ClusterFrame.logPrefix} Init dashboard, clusterId=${ClusterFrame.clusterId}, frameId=${frameId}`); await Terminal.preloadFonts(); await requestMain(clusterSetFrameIdHandler, ClusterFrame.clusterId); - await ClusterStore.getInstance().getById(ClusterFrame.clusterId).whenReady; // cluster.activate() is done at this point + await cluster.whenReady; // cluster.activate() is done at this point catalogEntityRegistry.activeEntity = ClusterFrame.clusterId; @@ -120,9 +124,14 @@ export class ClusterFrame extends React.Component { whatInput.ask(); // Start to monitor user input device + const clusterContext = new FrameContext(cluster); + // Setup hosted cluster context KubeObjectStore.defaultContext.set(clusterContext); - kubeWatchApi.context = clusterContext; + WorkloadsOverview.clusterContext + = KubeObjectListLayout.clusterContext + = KubeWatchApi.context + = clusterContext; } componentDidMount() { diff --git a/src/renderer/components/+workloads-overview/overview.tsx b/src/renderer/components/+workloads-overview/overview.tsx index d613dc5dc9..fa7ad590b0 100644 --- a/src/renderer/components/+workloads-overview/overview.tsx +++ b/src/renderer/components/+workloads-overview/overview.tsx @@ -36,16 +36,18 @@ import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; import { WorkloadsOverviewDetailRegistry } from "../../../extensions/registries"; import type { WorkloadsOverviewRouteParams } from "../../../common/routes"; import { makeObservable, observable, reaction } from "mobx"; -import { clusterContext } from "../context"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter"; import { Icon } from "../icon"; import { TooltipPosition } from "../tooltip"; +import type { ClusterContext } from "../../../common/k8s-api/cluster-context"; interface Props extends RouteComponentProps { } @observer export class WorkloadsOverview extends React.Component { + static clusterContext: ClusterContext; + @observable loadErrors: string[] = []; constructor(props: Props) { @@ -67,7 +69,7 @@ export class WorkloadsOverview extends React.Component { ], { onLoadFailure: error => this.loadErrors.push(String(error)), }), - reaction(() => clusterContext.contextNamespaces.slice(), () => { + reaction(() => WorkloadsOverview.clusterContext.contextNamespaces.slice(), () => { // clear load errors this.loadErrors.length = 0; }), diff --git a/src/renderer/components/context.ts b/src/renderer/components/context.ts index 6e37aabdce..fc494f5f37 100755 --- a/src/renderer/components/context.ts +++ b/src/renderer/components/context.ts @@ -19,24 +19,19 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { ClusterStore } from "../../common/cluster-store"; import type { Cluster } from "../../main/cluster"; -import { getHostedClusterId } from "../utils"; import { namespaceStore } from "./+namespaces/namespace.store"; import type { ClusterContext } from "../../common/k8s-api/cluster-context"; +import { computed, makeObservable } from "mobx"; -export const clusterContext: ClusterContext = { - get cluster(): Cluster | null { - return ClusterStore.getInstance().getById(getHostedClusterId()); - }, - - get allNamespaces(): string[] { - if (!this.cluster) { - return []; - } +export class FrameContext implements ClusterContext { + constructor(public cluster: Cluster) { + makeObservable(this); + } + @computed get allNamespaces(): string[] { // user given list of namespaces - if (this.cluster?.accessibleNamespaces.length) { + if (this.cluster.accessibleNamespaces.length) { return this.cluster.accessibleNamespaces; } @@ -47,9 +42,17 @@ export const clusterContext: ClusterContext = { // fallback to cluster resolved namespaces because we could not load list return this.cluster.allowedNamespaces || []; } - }, + } - get contextNamespaces(): string[] { - return namespaceStore.contextNamespaces ?? []; - }, -}; + @computed get contextNamespaces(): string[] { + return namespaceStore.contextNamespaces; + } + + @computed get hasSelectedAll(): boolean { + const namespaces = new Set(this.contextNamespaces); + + return this.allNamespaces?.length > 1 + && this.cluster.accessibleNamespaces.length === 0 + && this.allNamespaces.every(ns => namespaces.has(ns)); + } +} 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 0cace44ffe..f29a6da6e7 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 @@ -35,7 +35,7 @@ import { ResourceKindMap, ResourceNames } from "../../utils/rbac"; import { kubeSelectedUrlParam, toggleDetails } from "../kube-detail-params"; import { Icon } from "../icon"; import { TooltipPosition } from "../tooltip"; -import { clusterContext } from "../context"; +import type { ClusterContext } from "../../../common/k8s-api/cluster-context"; export interface KubeObjectListLayoutProps extends ItemListLayoutProps { store: KubeObjectStore; @@ -51,6 +51,7 @@ const defaultProps: Partial> = { @observer export class KubeObjectListLayout extends React.Component> { static defaultProps = defaultProps as object; + static clusterContext: ClusterContext; constructor(props: KubeObjectListLayoutProps) { super(props); @@ -67,7 +68,7 @@ export class KubeObjectListLayout extends React.Component< const { store, dependentStores = [], subscribeStores } = this.props; const stores = Array.from(new Set([store, ...dependentStores])); const reactions: Disposer[] = [ - reaction(() => clusterContext.contextNamespaces.slice(), () => { + reaction(() => KubeObjectListLayout.clusterContext.contextNamespaces.slice(), () => { // clear load errors this.loadErrors.length = 0; }),