1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Detect the adding of a namespace when selected namespaces change

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2021-11-24 11:26:23 -05:00
parent 286bbd8849
commit 3c1c635f60
6 changed files with 55 additions and 38 deletions

View File

@ -25,4 +25,5 @@ export interface ClusterContext {
cluster?: Cluster; cluster?: Cluster;
allNamespaces: string[]; // available / allowed namespaces from cluster.ts allNamespaces: string[]; // available / allowed namespaces from cluster.ts
contextNamespaces: string[]; // selected by user (see: namespace-select.tsx) contextNamespaces: string[]; // selected by user (see: namespace-select.tsx)
hasSelectedAll: boolean;
} }

View File

@ -25,8 +25,8 @@
import type { KubeObjectStore } from "./kube-object.store"; import type { KubeObjectStore } from "./kube-object.store";
import type { ClusterContext } from "./cluster-context"; import type { ClusterContext } from "./cluster-context";
import { comparer, observable, reaction, makeObservable } from "mobx"; import { comparer, reaction } from "mobx";
import { autoBind, disposer, Disposer, noop } from "../utils"; import { disposer, Disposer, noop } from "../utils";
import type { KubeJsonApiData } from "./kube-json-api"; import type { KubeJsonApiData } from "./kube-json-api";
import type { KubeObject } from "./kube-object"; import type { KubeObject } from "./kube-object";
import AbortController from "abort-controller"; import AbortController from "abort-controller";
@ -111,15 +111,10 @@ class WatchCount {
} }
export class KubeWatchApi { export class KubeWatchApi {
@observable context: ClusterContext = null; static context: ClusterContext = null;
#watch = new WatchCount(); #watch = new WatchCount();
constructor() {
makeObservable(this);
autoBind(this);
}
private subscribeStore({ store, parent, watchChanges, namespaces, onLoadFailure }: SubscribeStoreParams): Disposer { private subscribeStore({ store, parent, watchChanges, namespaces, onLoadFailure }: SubscribeStoreParams): Disposer {
if (this.#watch.inc(store) > 1) { if (this.#watch.inc(store) > 1) {
// don't load or subscribe to a store more than once // don't load or subscribe to a store more than once
@ -151,9 +146,15 @@ export class KubeWatchApi {
const cancelReloading = watchChanges const cancelReloading = watchChanges
? reaction( ? reaction(
// Note: must slice because reaction won't fire if it isn't there // Note: must slice because reaction won't fire if it isn't there
() => this.context.contextNamespaces.slice(), () => [KubeWatchApi.context.contextNamespaces.slice(), KubeWatchApi.context.hasSelectedAll] as const,
namespaces => { ([namespaces, curSelectedAll], [, prevSelectedAll]) => {
console.log(`changing watch ${store.api.apiBase}`, namespaces); 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(); childController.abort();
unsubscribe(); unsubscribe();
childController = new WrappedAbortController(parent); childController = new WrappedAbortController(parent);
@ -181,8 +182,8 @@ export class KubeWatchApi {
...stores.map(store => this.subscribeStore({ ...stores.map(store => this.subscribeStore({
store, store,
parent, parent,
watchChanges: !namespaces, watchChanges: !namespaces && store.api.isNamespaced,
namespaces: namespaces ?? this.context?.contextNamespaces ?? [], namespaces: namespaces ?? KubeWatchApi.context?.contextNamespaces ?? [],
onLoadFailure, onLoadFailure,
})), })),
); );

View File

@ -42,11 +42,11 @@ import whatInput from "what-input";
import { clusterSetFrameIdHandler } from "../common/cluster-ipc"; import { clusterSetFrameIdHandler } from "../common/cluster-ipc";
import { ClusterPageMenuRegistration, ClusterPageMenuRegistry } from "../extensions/registries"; import { ClusterPageMenuRegistration, ClusterPageMenuRegistry } from "../extensions/registries";
import { StatefulSetScaleDialog } from "./components/+workloads-statefulsets/statefulset-scale-dialog"; 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 { ReplicaSetScaleDialog } from "./components/+workloads-replicasets/replicaset-scale-dialog";
import { CommandContainer } from "./components/command-palette/command-container"; import { CommandContainer } from "./components/command-palette/command-container";
import { KubeObjectStore } from "../common/k8s-api/kube-object.store"; 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 * as routes from "../common/routes";
import { TabLayout, TabLayoutRoute } from "./components/layout/tab-layout"; import { TabLayout, TabLayoutRoute } from "./components/layout/tab-layout";
import { ErrorBoundary } from "./components/error-boundary"; import { ErrorBoundary } from "./components/error-boundary";
@ -73,6 +73,8 @@ import { watchHistoryState } from "./remote-helpers/history-updater";
import { unmountComponentAtNode } from "react-dom"; import { unmountComponentAtNode } from "react-dom";
import { PortForwardDialog } from "./port-forward"; import { PortForwardDialog } from "./port-forward";
import { DeleteClusterDialog } from "./components/delete-cluster-dialog"; import { DeleteClusterDialog } from "./components/delete-cluster-dialog";
import { WorkloadsOverview } from "./components/+workloads-overview/overview";
import { KubeObjectListLayout } from "./components/kube-object-list-layout";
@observer @observer
export class ClusterFrame extends React.Component { export class ClusterFrame extends React.Component {
@ -91,10 +93,12 @@ export class ClusterFrame extends React.Component {
ClusterFrame.clusterId = getHostedClusterId(); ClusterFrame.clusterId = getHostedClusterId();
const cluster = ClusterStore.getInstance().getById(ClusterFrame.clusterId);
logger.info(`${ClusterFrame.logPrefix} Init dashboard, clusterId=${ClusterFrame.clusterId}, frameId=${frameId}`); logger.info(`${ClusterFrame.logPrefix} Init dashboard, clusterId=${ClusterFrame.clusterId}, frameId=${frameId}`);
await Terminal.preloadFonts(); await Terminal.preloadFonts();
await requestMain(clusterSetFrameIdHandler, ClusterFrame.clusterId); 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; catalogEntityRegistry.activeEntity = ClusterFrame.clusterId;
@ -120,9 +124,14 @@ export class ClusterFrame extends React.Component {
whatInput.ask(); // Start to monitor user input device whatInput.ask(); // Start to monitor user input device
const clusterContext = new FrameContext(cluster);
// Setup hosted cluster context // Setup hosted cluster context
KubeObjectStore.defaultContext.set(clusterContext); KubeObjectStore.defaultContext.set(clusterContext);
kubeWatchApi.context = clusterContext; WorkloadsOverview.clusterContext
= KubeObjectListLayout.clusterContext
= KubeWatchApi.context
= clusterContext;
} }
componentDidMount() { componentDidMount() {

View File

@ -36,16 +36,18 @@ import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
import { WorkloadsOverviewDetailRegistry } from "../../../extensions/registries"; import { WorkloadsOverviewDetailRegistry } from "../../../extensions/registries";
import type { WorkloadsOverviewRouteParams } from "../../../common/routes"; import type { WorkloadsOverviewRouteParams } from "../../../common/routes";
import { makeObservable, observable, reaction } from "mobx"; import { makeObservable, observable, reaction } from "mobx";
import { clusterContext } from "../context";
import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { TooltipPosition } from "../tooltip"; import { TooltipPosition } from "../tooltip";
import type { ClusterContext } from "../../../common/k8s-api/cluster-context";
interface Props extends RouteComponentProps<WorkloadsOverviewRouteParams> { interface Props extends RouteComponentProps<WorkloadsOverviewRouteParams> {
} }
@observer @observer
export class WorkloadsOverview extends React.Component<Props> { export class WorkloadsOverview extends React.Component<Props> {
static clusterContext: ClusterContext;
@observable loadErrors: string[] = []; @observable loadErrors: string[] = [];
constructor(props: Props) { constructor(props: Props) {
@ -67,7 +69,7 @@ export class WorkloadsOverview extends React.Component<Props> {
], { ], {
onLoadFailure: error => this.loadErrors.push(String(error)), onLoadFailure: error => this.loadErrors.push(String(error)),
}), }),
reaction(() => clusterContext.contextNamespaces.slice(), () => { reaction(() => WorkloadsOverview.clusterContext.contextNamespaces.slice(), () => {
// clear load errors // clear load errors
this.loadErrors.length = 0; this.loadErrors.length = 0;
}), }),

View File

@ -19,24 +19,19 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 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 type { Cluster } from "../../main/cluster";
import { getHostedClusterId } from "../utils";
import { namespaceStore } from "./+namespaces/namespace.store"; import { namespaceStore } from "./+namespaces/namespace.store";
import type { ClusterContext } from "../../common/k8s-api/cluster-context"; import type { ClusterContext } from "../../common/k8s-api/cluster-context";
import { computed, makeObservable } from "mobx";
export const clusterContext: ClusterContext = { export class FrameContext implements ClusterContext {
get cluster(): Cluster | null { constructor(public cluster: Cluster) {
return ClusterStore.getInstance().getById(getHostedClusterId()); makeObservable(this);
}, }
get allNamespaces(): string[] {
if (!this.cluster) {
return [];
}
@computed get allNamespaces(): string[] {
// user given list of namespaces // user given list of namespaces
if (this.cluster?.accessibleNamespaces.length) { if (this.cluster.accessibleNamespaces.length) {
return this.cluster.accessibleNamespaces; return this.cluster.accessibleNamespaces;
} }
@ -47,9 +42,17 @@ export const clusterContext: ClusterContext = {
// fallback to cluster resolved namespaces because we could not load list // fallback to cluster resolved namespaces because we could not load list
return this.cluster.allowedNamespaces || []; return this.cluster.allowedNamespaces || [];
} }
}, }
get contextNamespaces(): string[] { @computed get contextNamespaces(): string[] {
return namespaceStore.contextNamespaces ?? []; 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));
}
}

View File

@ -35,7 +35,7 @@ import { ResourceKindMap, ResourceNames } from "../../utils/rbac";
import { kubeSelectedUrlParam, toggleDetails } from "../kube-detail-params"; import { kubeSelectedUrlParam, toggleDetails } from "../kube-detail-params";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { TooltipPosition } from "../tooltip"; import { TooltipPosition } from "../tooltip";
import { clusterContext } from "../context"; import type { ClusterContext } from "../../../common/k8s-api/cluster-context";
export interface KubeObjectListLayoutProps<K extends KubeObject> extends ItemListLayoutProps<K> { export interface KubeObjectListLayoutProps<K extends KubeObject> extends ItemListLayoutProps<K> {
store: KubeObjectStore<K>; store: KubeObjectStore<K>;
@ -51,6 +51,7 @@ const defaultProps: Partial<KubeObjectListLayoutProps<KubeObject>> = {
@observer @observer
export class KubeObjectListLayout<K extends KubeObject> extends React.Component<KubeObjectListLayoutProps<K>> { export class KubeObjectListLayout<K extends KubeObject> extends React.Component<KubeObjectListLayoutProps<K>> {
static defaultProps = defaultProps as object; static defaultProps = defaultProps as object;
static clusterContext: ClusterContext;
constructor(props: KubeObjectListLayoutProps<K>) { constructor(props: KubeObjectListLayoutProps<K>) {
super(props); super(props);
@ -67,7 +68,7 @@ export class KubeObjectListLayout<K extends KubeObject> extends React.Component<
const { store, dependentStores = [], subscribeStores } = this.props; const { store, dependentStores = [], subscribeStores } = this.props;
const stores = Array.from(new Set([store, ...dependentStores])); const stores = Array.from(new Set([store, ...dependentStores]));
const reactions: Disposer[] = [ const reactions: Disposer[] = [
reaction(() => clusterContext.contextNamespaces.slice(), () => { reaction(() => KubeObjectListLayout.clusterContext.contextNamespaces.slice(), () => {
// clear load errors // clear load errors
this.loadErrors.length = 0; this.loadErrors.length = 0;
}), }),