mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix detail views not watching child components
- Add subscribeStores calls to all relavent details - Add support for tracking overlapping subscribes as an optimization Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
df230d2bec
commit
624ac4680d
@ -33,9 +33,14 @@ import type { RequestInit } from "node-fetch";
|
|||||||
import AbortController from "abort-controller";
|
import AbortController from "abort-controller";
|
||||||
import type { Patch } from "rfc6902";
|
import type { Patch } from "rfc6902";
|
||||||
|
|
||||||
export interface KubeObjectStoreLoadingParams<K extends KubeObject> {
|
export interface KubeObjectStoreLoadingParams {
|
||||||
namespaces: string[];
|
namespaces: string[];
|
||||||
api?: KubeApi<K>;
|
reqInit?: RequestInit;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KubeObjectStoreLoadAllParams {
|
||||||
|
namespaces?: string[];
|
||||||
|
merge?: boolean;
|
||||||
reqInit?: RequestInit;
|
reqInit?: RequestInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,10 +146,10 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async loadItems({ namespaces, api, reqInit }: KubeObjectStoreLoadingParams<T>): Promise<T[]> {
|
protected async loadItems({ namespaces, reqInit }: KubeObjectStoreLoadingParams): Promise<T[]> {
|
||||||
if (this.context?.cluster.isAllowedResource(api.kind)) {
|
if (this.context?.cluster.isAllowedResource(this.api.kind)) {
|
||||||
if (!api.isNamespaced) {
|
if (!this.api.isNamespaced) {
|
||||||
return api.list({ reqInit }, this.query);
|
return this.api.list({ reqInit }, this.query);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLoadingAll = this.context.allNamespaces?.length > 1
|
const isLoadingAll = this.context.allNamespaces?.length > 1
|
||||||
@ -154,12 +159,12 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
if (isLoadingAll) {
|
if (isLoadingAll) {
|
||||||
this.loadedNamespaces = [];
|
this.loadedNamespaces = [];
|
||||||
|
|
||||||
return api.list({ reqInit }, this.query);
|
return this.api.list({ reqInit }, this.query);
|
||||||
} else {
|
} else {
|
||||||
this.loadedNamespaces = namespaces;
|
this.loadedNamespaces = namespaces;
|
||||||
|
|
||||||
return Promise // load resources per namespace
|
return Promise // load resources per namespace
|
||||||
.all(namespaces.map(namespace => api.list({ namespace, reqInit }, this.query)))
|
.all(namespaces.map(namespace => this.api.list({ namespace, reqInit }, this.query)))
|
||||||
.then(items => items.flat().filter(Boolean));
|
.then(items => items.flat().filter(Boolean));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,24 +177,14 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async loadAll(options: { namespaces?: string[], merge?: boolean, reqInit?: RequestInit } = {}): Promise<void | T[]> {
|
async loadAll({ namespaces = this.context.contextNamespaces, merge = true, reqInit }: KubeObjectStoreLoadAllParams = {}): Promise<void | T[]> {
|
||||||
await this.contextReady;
|
await this.contextReady;
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const items = await this.loadItems({ namespaces, reqInit });
|
||||||
namespaces = this.context.allNamespaces, // load all namespaces by default
|
|
||||||
merge = true, // merge loaded items or return as result
|
|
||||||
reqInit,
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
const items = await this.loadItems({ namespaces, api: this.api, reqInit });
|
this.mergeItems(items, { merge });
|
||||||
|
|
||||||
if (merge) {
|
|
||||||
this.mergeItems(items, { replace: false });
|
|
||||||
} else {
|
|
||||||
this.mergeItems(items, { replace: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
this.failedLoading = false;
|
this.failedLoading = false;
|
||||||
@ -216,11 +211,11 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected mergeItems(partialItems: T[], { replace = false, updateStore = true, sort = true, filter = true } = {}): T[] {
|
protected mergeItems(partialItems: T[], { merge = true, updateStore = true, sort = true, filter = true } = {}): T[] {
|
||||||
let items = partialItems;
|
let items = partialItems;
|
||||||
|
|
||||||
// update existing items
|
// update existing items
|
||||||
if (!replace) {
|
if (merge) {
|
||||||
const namespaces = partialItems.map(item => item.getNs());
|
const namespaces = partialItems.map(item => item.getNs());
|
||||||
|
|
||||||
items = [
|
items = [
|
||||||
@ -297,7 +292,7 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
async patch(item: T, patch: Patch): Promise<T> {
|
async patch(item: T, patch: Patch): Promise<T> {
|
||||||
return this.postUpdate(
|
return this.postUpdate(
|
||||||
await this.api.patch(
|
await this.api.patch(
|
||||||
{
|
{
|
||||||
name: item.getName(), namespace: item.getNs(),
|
name: item.getName(), namespace: item.getNs(),
|
||||||
},
|
},
|
||||||
patch,
|
patch,
|
||||||
@ -309,7 +304,7 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
async update(item: T, data: Partial<T>): Promise<T> {
|
async update(item: T, data: Partial<T>): Promise<T> {
|
||||||
return this.postUpdate(
|
return this.postUpdate(
|
||||||
await this.api.update(
|
await this.api.update(
|
||||||
{
|
{
|
||||||
name: item.getName(), namespace: item.getNs(),
|
name: item.getName(), namespace: item.getNs(),
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
@ -335,9 +330,7 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe() {
|
subscribe(abortController = new AbortController()) {
|
||||||
const abortController = new AbortController();
|
|
||||||
|
|
||||||
if (this.api.isNamespaced) {
|
if (this.api.isNamespaced) {
|
||||||
Promise.race([rejectPromiseBy(abortController.signal), Promise.all([this.contextReady, this.namespacesReady])])
|
Promise.race([rejectPromiseBy(abortController.signal), Promise.all([this.contextReady, this.namespacesReady])])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|||||||
@ -25,131 +25,131 @@
|
|||||||
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 plimit from "p-limit";
|
|
||||||
import { comparer, observable, reaction, makeObservable } from "mobx";
|
import { comparer, observable, reaction, makeObservable } from "mobx";
|
||||||
import { autoBind, Disposer, noop } from "../utils";
|
import { autoBind, disposer, Disposer, ExtendedMap, noop } from "../utils";
|
||||||
import type { KubeApi } from "./kube-api";
|
|
||||||
import type { KubeJsonApiData } from "./kube-json-api";
|
import type { KubeJsonApiData } from "./kube-json-api";
|
||||||
import { isDebugging, isProduction } from "../vars";
|
import { isProduction } from "../vars";
|
||||||
import type { KubeObject } from "./kube-object";
|
import type { KubeObject } from "./kube-object";
|
||||||
|
import AbortController from "abort-controller";
|
||||||
|
import { once } from "lodash";
|
||||||
|
|
||||||
|
class WrappedAbortController extends AbortController {
|
||||||
|
constructor(protected parent: AbortController) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
parent.signal.addEventListener("abort", () => {
|
||||||
|
this.abort();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface IKubeWatchEvent<T extends KubeJsonApiData> {
|
export interface IKubeWatchEvent<T extends KubeJsonApiData> {
|
||||||
type: "ADDED" | "MODIFIED" | "DELETED" | "ERROR";
|
type: "ADDED" | "MODIFIED" | "DELETED" | "ERROR";
|
||||||
object?: T;
|
object?: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IKubeWatchSubscribeStoreOptions {
|
export interface KubeWatchSubscribeStoreOptions {
|
||||||
namespaces?: string[]; // default: all accessible namespaces
|
/**
|
||||||
preload?: boolean; // preload store items, default: true
|
* The namespaces to watch, if not specified then changes to the set of
|
||||||
waitUntilLoaded?: boolean; // subscribe only after loading all stores, default: true
|
* selected namespaces will be watched as well
|
||||||
loadOnce?: boolean; // check store.isLoaded to skip loading if done already, default: false
|
*
|
||||||
}
|
* @default all selected namespaces
|
||||||
|
*/
|
||||||
export interface IKubeWatchLog {
|
namespaces?: string[];
|
||||||
message: string | string[] | Error;
|
|
||||||
meta?: object;
|
|
||||||
cssStyle?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KubeWatchApi {
|
export class KubeWatchApi {
|
||||||
@observable context: ClusterContext = null;
|
@observable context: ClusterContext = null;
|
||||||
|
duplicateWatchSet = new ExtendedMap<KubeObjectStore<KubeObject>, number>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
isAllowedApi(api: KubeApi<KubeObject>): boolean {
|
private subscribeStore(store: KubeObjectStore<KubeObject>, parent: AbortController, watchChanges: boolean, namespaces: string[]): Disposer {
|
||||||
return Boolean(this.context?.cluster.isAllowedResource(api.kind));
|
const count = this.duplicateWatchSet.getOrInsert(store, () => 1);
|
||||||
}
|
|
||||||
|
|
||||||
preloadStores(stores: KubeObjectStore<KubeObject>[], opts: { namespaces?: string[], loadOnce?: boolean } = {}) {
|
if (count > 1) {
|
||||||
const limitRequests = plimit(1); // load stores one by one to allow quick skipping when fast clicking btw pages
|
// don't load or subscribe to a store more than once
|
||||||
const preloading: Promise<any>[] = [];
|
return () => {
|
||||||
|
this.duplicateWatchSet.set(store, this.duplicateWatchSet.get(store) - 1);
|
||||||
for (const store of stores) {
|
};
|
||||||
preloading.push(limitRequests(async () => {
|
|
||||||
if (store.isLoaded && opts.loadOnce) return; // skip
|
|
||||||
|
|
||||||
return store.loadAll({ namespaces: opts.namespaces });
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
let childController = new WrappedAbortController(parent);
|
||||||
loading: Promise.allSettled(preloading),
|
const unsubscribe = disposer();
|
||||||
cancelLoading: () => limitRequests.clearQueue(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribeStores(stores: KubeObjectStore<KubeObject>[], opts: IKubeWatchSubscribeStoreOptions = {}): Disposer {
|
const loadThenSubscribe = async (namespaces: string[]) => {
|
||||||
const { preload = true, waitUntilLoaded = true, loadOnce = false } = opts;
|
try {
|
||||||
const subscribingNamespaces = opts.namespaces ?? this.context?.allNamespaces ?? [];
|
await store.loadAll({ namespaces, reqInit: { signal: childController.signal }});
|
||||||
const unsubscribeList: Function[] = [];
|
unsubscribe.push(store.subscribe(childController));
|
||||||
let isUnsubscribed = false;
|
} catch (error) {
|
||||||
|
if (!(error instanceof DOMException)) {
|
||||||
const load = (namespaces = subscribingNamespaces) => this.preloadStores(stores, { namespaces, loadOnce });
|
this.log(Object.assign(new Error("Loading stores has failed"), { cause: error }), {
|
||||||
let preloading = preload && load();
|
meta: { store, namespaces },
|
||||||
let cancelReloading: Disposer = noop;
|
|
||||||
|
|
||||||
const subscribe = () => {
|
|
||||||
if (isUnsubscribed) return;
|
|
||||||
|
|
||||||
stores.forEach((store) => {
|
|
||||||
unsubscribeList.push(store.subscribe());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (preloading) {
|
|
||||||
if (waitUntilLoaded) {
|
|
||||||
preloading.loading.then(subscribe, error => {
|
|
||||||
this.log({
|
|
||||||
message: new Error("Loading stores has failed"),
|
|
||||||
meta: { stores, error, options: opts },
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
} else {
|
|
||||||
subscribe();
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// reload stores only for context namespaces change
|
loadThenSubscribe(namespaces);
|
||||||
cancelReloading = reaction(() => this.context?.contextNamespaces, namespaces => {
|
|
||||||
preloading?.cancelLoading();
|
const cancelReloading = watchChanges
|
||||||
unsubscribeList.forEach(unsubscribe => unsubscribe());
|
? noop // don't watch namespaces if namespaces were provided
|
||||||
unsubscribeList.length = 0;
|
: reaction(() => this.context.contextNamespaces, namespaces => {
|
||||||
preloading = load(namespaces);
|
childController.abort();
|
||||||
preloading.loading.then(subscribe);
|
unsubscribe();
|
||||||
|
childController = new WrappedAbortController(parent);
|
||||||
|
loadThenSubscribe(namespaces);
|
||||||
}, {
|
}, {
|
||||||
equals: comparer.shallow,
|
equals: comparer.shallow,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// unsubscribe
|
|
||||||
return () => {
|
return () => {
|
||||||
if (isUnsubscribed) return;
|
const newCount = this.duplicateWatchSet.get(store) - 1;
|
||||||
isUnsubscribed = true;
|
|
||||||
cancelReloading();
|
this.duplicateWatchSet.set(store, newCount);
|
||||||
preloading?.cancelLoading();
|
|
||||||
unsubscribeList.forEach(unsubscribe => unsubscribe());
|
if (newCount === 0) {
|
||||||
unsubscribeList.length = 0;
|
cancelReloading();
|
||||||
|
childController.abort();
|
||||||
|
unsubscribe();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected log({ message, cssStyle = "", meta = {}}: IKubeWatchLog) {
|
subscribeStores(stores: KubeObjectStore<KubeObject>[], options: KubeWatchSubscribeStoreOptions = {}): Disposer {
|
||||||
if (isProduction && !isDebugging) {
|
const parent = new AbortController();
|
||||||
|
const unsubscribe = disposer(
|
||||||
|
...stores.map(store => this.subscribeStore(
|
||||||
|
store,
|
||||||
|
parent,
|
||||||
|
!options.namespaces,
|
||||||
|
options.namespaces ?? this.context?.contextNamespaces ?? [],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// unsubscribe
|
||||||
|
return once(() => {
|
||||||
|
parent.abort();
|
||||||
|
unsubscribe();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected log(message: any, meta: any) {
|
||||||
|
if (isProduction) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logInfo = [`%c[KUBE-WATCH-API]:`, `font-weight: bold; ${cssStyle}`, message].flat().map(String);
|
const log = message instanceof Error
|
||||||
const logMeta = {
|
? console.error
|
||||||
|
: console.debug;
|
||||||
|
|
||||||
|
log("[KUBE-WATCH-API]:", message, {
|
||||||
time: new Date().toLocaleString(),
|
time: new Date().toLocaleString(),
|
||||||
...meta,
|
...meta,
|
||||||
};
|
});
|
||||||
|
|
||||||
if (message instanceof Error) {
|
|
||||||
console.error(...logInfo, logMeta);
|
|
||||||
} else {
|
|
||||||
console.info(...logInfo, logMeta);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -55,9 +55,11 @@ export class ClusterOverview extends React.Component {
|
|||||||
this.metricPoller.start(true);
|
this.metricPoller.start(true);
|
||||||
|
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
kubeWatchApi.subscribeStores([podsStore, eventStore, nodesStore], {
|
kubeWatchApi.subscribeStores([
|
||||||
preload: true,
|
podsStore,
|
||||||
}),
|
eventStore,
|
||||||
|
nodesStore,
|
||||||
|
]),
|
||||||
reaction(
|
reaction(
|
||||||
() => clusterOverviewStore.metricNodeRole, // Toggle Master/Worker node switcher
|
() => clusterOverviewStore.metricNodeRole, // Toggle Master/Worker node switcher
|
||||||
() => this.metricPoller.restart(true),
|
() => this.metricPoller.restart(true),
|
||||||
|
|||||||
@ -22,13 +22,14 @@
|
|||||||
import "./kube-event-details.scss";
|
import "./kube-event-details.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { LocaleDate } from "../locale-date";
|
import { LocaleDate } from "../locale-date";
|
||||||
import { eventStore } from "./event.store";
|
import { eventStore } from "./event.store";
|
||||||
import logger from "../../../common/logger";
|
import logger from "../../../common/logger";
|
||||||
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
|
|
||||||
export interface KubeEventDetailsProps {
|
export interface KubeEventDetailsProps {
|
||||||
object: KubeObject;
|
object: KubeObject;
|
||||||
@ -36,8 +37,12 @@ export interface KubeEventDetailsProps {
|
|||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class KubeEventDetails extends React.Component<KubeEventDetailsProps> {
|
export class KubeEventDetails extends React.Component<KubeEventDetailsProps> {
|
||||||
async componentDidMount() {
|
componentDidMount() {
|
||||||
eventStore.reloadAll();
|
disposeOnUnmount(this, [
|
||||||
|
kubeWatchApi.subscribeStores([
|
||||||
|
eventStore,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
import logger from "../../../common/logger";
|
import logger from "../../../common/logger";
|
||||||
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Namespace> {
|
interface Props extends KubeObjectDetailsProps<Namespace> {
|
||||||
}
|
}
|
||||||
@ -52,14 +53,16 @@ export class NamespaceDetails extends React.Component<Props> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@disposeOnUnmount
|
|
||||||
clean = reaction(() => this.props.object, () => {
|
|
||||||
this.metrics = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
resourceQuotaStore.reloadAll();
|
disposeOnUnmount(this, [
|
||||||
limitRangeStore.reloadAll();
|
reaction(() => this.props.object, () => {
|
||||||
|
this.metrics = null;
|
||||||
|
}),
|
||||||
|
kubeWatchApi.subscribeStores([
|
||||||
|
resourceQuotaStore,
|
||||||
|
limitRangeStore,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get quotas() {
|
@computed get quotas() {
|
||||||
|
|||||||
@ -23,12 +23,11 @@ import "./namespace-select.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { computed, makeObservable } from "mobx";
|
import { computed, makeObservable } from "mobx";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Select, SelectOption, SelectProps } from "../select";
|
import { Select, SelectOption, SelectProps } from "../select";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { namespaceStore } from "./namespace.store";
|
import { namespaceStore } from "./namespace.store";
|
||||||
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
|
||||||
|
|
||||||
interface Props extends SelectProps {
|
interface Props extends SelectProps {
|
||||||
showIcons?: boolean;
|
showIcons?: boolean;
|
||||||
@ -50,14 +49,7 @@ export class NamespaceSelect extends React.Component<Props> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
// No subscribe here because the subscribe is in <App /> (the cluster frame root component)
|
||||||
disposeOnUnmount(this, [
|
|
||||||
kubeWatchApi.subscribeStores([namespaceStore], {
|
|
||||||
preload: true,
|
|
||||||
loadOnce: true, // skip reloading namespaces on every render / page visit
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed.struct get options(): SelectOption[] {
|
@computed.struct get options(): SelectOption[] {
|
||||||
const { customizeOptions, showAllNamespacesOption, sort } = this.props;
|
const { customizeOptions, showAllNamespacesOption, sort } = this.props;
|
||||||
|
|||||||
@ -133,7 +133,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
|||||||
return super.subscribe();
|
return super.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async loadItems(params: KubeObjectStoreLoadingParams<Namespace>): Promise<Namespace[]> {
|
protected async loadItems(params: KubeObjectStoreLoadingParams): Promise<Namespace[]> {
|
||||||
const { allowedNamespaces } = this;
|
const { allowedNamespaces } = this;
|
||||||
|
|
||||||
let namespaces = await super.loadItems(params).catch(() => []);
|
let namespaces = await super.loadItems(params).catch(() => []);
|
||||||
|
|||||||
@ -49,10 +49,13 @@ export class IngressDetails extends React.Component<Props> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@disposeOnUnmount
|
componentDidMount() {
|
||||||
clean = reaction(() => this.props.object, () => {
|
disposeOnUnmount(this, [
|
||||||
this.metrics = null;
|
reaction(() => this.props.object, () => {
|
||||||
});
|
this.metrics = null;
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
async loadMetrics() {
|
async loadMetrics() {
|
||||||
|
|||||||
@ -44,8 +44,9 @@ export class ServiceDetails extends React.Component<Props> {
|
|||||||
const { object: service } = this.props;
|
const { object: service } = this.props;
|
||||||
|
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
kubeWatchApi.subscribeStores([endpointStore], {
|
kubeWatchApi.subscribeStores([
|
||||||
preload: true,
|
endpointStore,
|
||||||
|
], {
|
||||||
namespaces: [service.getNs()],
|
namespaces: [service.getNs()],
|
||||||
}),
|
}),
|
||||||
portForwardStore.watch(),
|
portForwardStore.watch(),
|
||||||
|
|||||||
@ -41,6 +41,7 @@ import { NodeDetailsResources } from "./node-details-resources";
|
|||||||
import { DrawerTitle } from "../drawer/drawer-title";
|
import { DrawerTitle } from "../drawer/drawer-title";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
import logger from "../../../common/logger";
|
import logger from "../../../common/logger";
|
||||||
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Node> {
|
interface Props extends KubeObjectDetailsProps<Node> {
|
||||||
}
|
}
|
||||||
@ -54,13 +55,15 @@ export class NodeDetails extends React.Component<Props> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@disposeOnUnmount
|
componentDidMount() {
|
||||||
clean = reaction(() => this.props.object.getName(), () => {
|
disposeOnUnmount(this, [
|
||||||
this.metrics = null;
|
reaction(() => this.props.object.getName(), () => {
|
||||||
});
|
this.metrics = null;
|
||||||
|
}),
|
||||||
async componentDidMount() {
|
kubeWatchApi.subscribeStores([
|
||||||
podsStore.reloadAll();
|
podsStore,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import React from "react";
|
|||||||
import startCase from "lodash/startCase";
|
import startCase from "lodash/startCase";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import { StorageClass } from "../../../common/k8s-api/endpoints";
|
import { StorageClass } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
@ -33,14 +33,19 @@ import { storageClassStore } from "./storage-class.store";
|
|||||||
import { VolumeDetailsList } from "../+storage-volumes/volume-details-list";
|
import { VolumeDetailsList } from "../+storage-volumes/volume-details-list";
|
||||||
import { volumesStore } from "../+storage-volumes/volumes.store";
|
import { volumesStore } from "../+storage-volumes/volumes.store";
|
||||||
import logger from "../../../common/logger";
|
import logger from "../../../common/logger";
|
||||||
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<StorageClass> {
|
interface Props extends KubeObjectDetailsProps<StorageClass> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class StorageClassDetails extends React.Component<Props> {
|
export class StorageClassDetails extends React.Component<Props> {
|
||||||
async componentDidMount() {
|
componentDidMount() {
|
||||||
volumesStore.reloadAll();
|
disposeOnUnmount(this, [
|
||||||
|
kubeWatchApi.subscribeStores([
|
||||||
|
volumesStore,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@ -51,10 +51,13 @@ export class PersistentVolumeClaimDetails extends React.Component<Props> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@disposeOnUnmount
|
componentDidMount() {
|
||||||
clean = reaction(() => this.props.object, () => {
|
disposeOnUnmount(this, [
|
||||||
this.metrics = null;
|
reaction(() => this.props.object, () => {
|
||||||
});
|
this.metrics = null;
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
async loadMetrics() {
|
async loadMetrics() {
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import "./cronjob-details.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import kebabCase from "lodash/kebabCase";
|
import kebabCase from "lodash/kebabCase";
|
||||||
import { observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { Badge } from "../badge/badge";
|
import { Badge } from "../badge/badge";
|
||||||
import { jobStore } from "../+workloads-jobs/job.store";
|
import { jobStore } from "../+workloads-jobs/job.store";
|
||||||
@ -34,14 +34,19 @@ import { getDetailsUrl } from "../kube-detail-params";
|
|||||||
import { CronJob, Job } from "../../../common/k8s-api/endpoints";
|
import { CronJob, Job } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import logger from "../../../common/logger";
|
import logger from "../../../common/logger";
|
||||||
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<CronJob> {
|
interface Props extends KubeObjectDetailsProps<CronJob> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class CronJobDetails extends React.Component<Props> {
|
export class CronJobDetails extends React.Component<Props> {
|
||||||
async componentDidMount() {
|
componentDidMount() {
|
||||||
jobStore.reloadAll();
|
disposeOnUnmount(this, [
|
||||||
|
kubeWatchApi.subscribeStores([
|
||||||
|
jobStore,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@ -41,6 +41,7 @@ import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
|||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
import logger from "../../../common/logger";
|
import logger from "../../../common/logger";
|
||||||
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<DaemonSet> {
|
interface Props extends KubeObjectDetailsProps<DaemonSet> {
|
||||||
}
|
}
|
||||||
@ -54,13 +55,15 @@ export class DaemonSetDetails extends React.Component<Props> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@disposeOnUnmount
|
|
||||||
clean = reaction(() => this.props.object, () => {
|
|
||||||
this.metrics = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
podsStore.reloadAll();
|
disposeOnUnmount(this, [
|
||||||
|
reaction(() => this.props.object, () => {
|
||||||
|
this.metrics = null;
|
||||||
|
}),
|
||||||
|
kubeWatchApi.subscribeStores([
|
||||||
|
podsStore,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
|||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
import logger from "../../../common/logger";
|
import logger from "../../../common/logger";
|
||||||
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Deployment> {
|
interface Props extends KubeObjectDetailsProps<Deployment> {
|
||||||
}
|
}
|
||||||
@ -56,14 +57,16 @@ export class DeploymentDetails extends React.Component<Props> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@disposeOnUnmount
|
|
||||||
clean = reaction(() => this.props.object, () => {
|
|
||||||
this.metrics = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
podsStore.reloadAll();
|
disposeOnUnmount(this, [
|
||||||
replicaSetStore.reloadAll();
|
reaction(() => this.props.object, () => {
|
||||||
|
this.metrics = null;
|
||||||
|
}),
|
||||||
|
kubeWatchApi.subscribeStores([
|
||||||
|
podsStore,
|
||||||
|
replicaSetStore,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import "./job-details.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import kebabCase from "lodash/kebabCase";
|
import kebabCase from "lodash/kebabCase";
|
||||||
import { observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { DrawerItem } from "../drawer";
|
import { DrawerItem } from "../drawer";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { PodDetailsStatuses } from "../+workloads-pods/pod-details-statuses";
|
import { PodDetailsStatuses } from "../+workloads-pods/pod-details-statuses";
|
||||||
@ -36,7 +36,7 @@ import type { KubeObjectDetailsProps } from "../kube-object-details";
|
|||||||
import { getMetricsForJobs, IPodMetrics, Job } from "../../../common/k8s-api/endpoints";
|
import { getMetricsForJobs, IPodMetrics, Job } from "../../../common/k8s-api/endpoints";
|
||||||
import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { makeObservable, observable } from "mobx";
|
import { makeObservable, observable, reaction } from "mobx";
|
||||||
import { podMetricTabs, PodCharts } from "../+workloads-pods/pod-charts";
|
import { podMetricTabs, PodCharts } from "../+workloads-pods/pod-charts";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
@ -45,6 +45,7 @@ import { boundMethod } from "autobind-decorator";
|
|||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||||
import logger from "../../../common/logger";
|
import logger from "../../../common/logger";
|
||||||
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Job> {
|
interface Props extends KubeObjectDetailsProps<Job> {
|
||||||
}
|
}
|
||||||
@ -58,8 +59,15 @@ export class JobDetails extends React.Component<Props> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
componentDidMount() {
|
||||||
podsStore.reloadAll();
|
disposeOnUnmount(this, [
|
||||||
|
reaction(() => this.props.object, () => {
|
||||||
|
this.metrics = null;
|
||||||
|
}),
|
||||||
|
kubeWatchApi.subscribeStores([
|
||||||
|
podsStore,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
|
|||||||
@ -33,7 +33,6 @@ import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
|
|||||||
import { jobStore } from "../+workloads-jobs/job.store";
|
import { jobStore } from "../+workloads-jobs/job.store";
|
||||||
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
|
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
|
||||||
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
import { clusterContext } from "../context";
|
|
||||||
import { WorkloadsOverviewDetailRegistry } from "../../../extensions/registries";
|
import { WorkloadsOverviewDetailRegistry } from "../../../extensions/registries";
|
||||||
import type { WorkloadsOverviewRouteParams } from "../../../common/routes";
|
import type { WorkloadsOverviewRouteParams } from "../../../common/routes";
|
||||||
|
|
||||||
@ -45,12 +44,15 @@ export class WorkloadsOverview extends React.Component<Props> {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
kubeWatchApi.subscribeStores([
|
kubeWatchApi.subscribeStores([
|
||||||
podsStore, deploymentStore, daemonSetStore, statefulSetStore, replicaSetStore,
|
cronJobStore,
|
||||||
jobStore, cronJobStore, eventStore,
|
daemonSetStore,
|
||||||
], {
|
deploymentStore,
|
||||||
preload: true,
|
eventStore,
|
||||||
namespaces: clusterContext.contextNamespaces,
|
jobStore,
|
||||||
}),
|
podsStore,
|
||||||
|
replicaSetStore,
|
||||||
|
statefulSetStore,
|
||||||
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
|||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
import logger from "../../../common/logger";
|
import logger from "../../../common/logger";
|
||||||
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<ReplicaSet> {
|
interface Props extends KubeObjectDetailsProps<ReplicaSet> {
|
||||||
}
|
}
|
||||||
@ -53,13 +54,15 @@ export class ReplicaSetDetails extends React.Component<Props> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@disposeOnUnmount
|
componentDidMount() {
|
||||||
clean = reaction(() => this.props.object, () => {
|
disposeOnUnmount(this, [
|
||||||
this.metrics = null;
|
reaction(() => this.props.object, () => {
|
||||||
});
|
this.metrics = null;
|
||||||
|
}),
|
||||||
async componentDidMount() {
|
kubeWatchApi.subscribeStores([
|
||||||
podsStore.reloadAll();
|
podsStore,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
|
|||||||
@ -41,6 +41,7 @@ import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
|||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
import logger from "../../../common/logger";
|
import logger from "../../../common/logger";
|
||||||
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<StatefulSet> {
|
interface Props extends KubeObjectDetailsProps<StatefulSet> {
|
||||||
}
|
}
|
||||||
@ -54,13 +55,15 @@ export class StatefulSetDetails extends React.Component<Props> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@disposeOnUnmount
|
|
||||||
clean = reaction(() => this.props.object, () => {
|
|
||||||
this.metrics = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
podsStore.reloadAll();
|
disposeOnUnmount(this, [
|
||||||
|
reaction(() => this.props.object, () => {
|
||||||
|
this.metrics = null;
|
||||||
|
}),
|
||||||
|
kubeWatchApi.subscribeStores([
|
||||||
|
podsStore,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
|
|||||||
@ -128,9 +128,9 @@ export class App extends React.Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
kubeWatchApi.subscribeStores([namespaceStore], {
|
kubeWatchApi.subscribeStores([
|
||||||
preload: true,
|
namespaceStore,
|
||||||
}),
|
]),
|
||||||
|
|
||||||
watchHistoryState(),
|
watchHistoryState(),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -28,7 +28,6 @@ import { ItemListLayout, ItemListLayoutProps } from "../item-object-list/item-li
|
|||||||
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
|
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
|
||||||
import { KubeObjectMenu } from "../kube-object-menu";
|
import { KubeObjectMenu } from "../kube-object-menu";
|
||||||
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
import { clusterContext } from "../context";
|
|
||||||
import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
|
import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
|
||||||
import { ResourceKindMap, ResourceNames } from "../../utils/rbac";
|
import { ResourceKindMap, ResourceNames } from "../../utils/rbac";
|
||||||
import { kubeSelectedUrlParam, toggleDetails } from "../kube-detail-params";
|
import { kubeSelectedUrlParam, toggleDetails } from "../kube-detail-params";
|
||||||
@ -63,10 +62,7 @@ export class KubeObjectListLayout<K extends KubeObject> extends React.Component<
|
|||||||
|
|
||||||
if (subscribeStores) {
|
if (subscribeStores) {
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
kubeWatchApi.subscribeStores(stores, {
|
kubeWatchApi.subscribeStores(stores),
|
||||||
preload: true,
|
|
||||||
namespaces: clusterContext.contextNamespaces,
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,7 +54,9 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
kubeWatchApi.subscribeStores([crdStore]),
|
kubeWatchApi.subscribeStores([
|
||||||
|
crdStore,
|
||||||
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user