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:
parent
286bbd8849
commit
3c1c635f60
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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;
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}),
|
}),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user