mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix endless re-rending of logs
Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
4441d714dd
commit
48948c7286
@ -27,11 +27,11 @@ import type { KubeJsonApiData } from "../kube-json-api";
|
|||||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
||||||
|
|
||||||
export class PodsApi extends KubeApi<Pod> {
|
export class PodsApi extends KubeApi<Pod> {
|
||||||
async getLogs(params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise<string> {
|
getLogs = async (params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise<string> => {
|
||||||
const path = `${this.getUrl(params)}/log`;
|
const path = `${this.getUrl(params)}/log`;
|
||||||
|
|
||||||
return this.request.get(path, { query });
|
return this.request.get(path, { query });
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMetricsForPods(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise<IPodMetrics> {
|
export function getMetricsForPods(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise<IPodMetrics> {
|
||||||
|
|||||||
@ -58,7 +58,6 @@ const getComponent = (tabData: LogTabData) => {
|
|||||||
tabId="tabId"
|
tabId="tabId"
|
||||||
tabData={tabData}
|
tabData={tabData}
|
||||||
save={jest.fn()}
|
save={jest.fn()}
|
||||||
reload={jest.fn()}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import createStorageInjectable from "../../../../utils/create-storage/create-storage.injectable";
|
||||||
|
import { DockStorageState, TabKind } from "../dock.store";
|
||||||
|
|
||||||
|
const dockStorageInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const createStorage = di.inject(createStorageInjectable);
|
||||||
|
|
||||||
|
return createStorage<DockStorageState>("dock", {
|
||||||
|
height: 300,
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
id: "terminal",
|
||||||
|
kind: TabKind.TERMINAL,
|
||||||
|
title: "Terminal",
|
||||||
|
pinned: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default dockStorageInjectable;
|
||||||
@ -19,29 +19,14 @@
|
|||||||
* 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 { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { DockStorageState, DockStore, TabKind } from "./dock.store";
|
import { DockStore } from "./dock.store";
|
||||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
import dockStorageInjectable from "./dock-storage/dock-storage.injectable";
|
||||||
|
|
||||||
const dockStoreInjectable = getInjectable({
|
const dockStoreInjectable = getInjectable({
|
||||||
instantiate: (di) => {
|
instantiate: (di) =>
|
||||||
const createStorage = di.inject(createStorageInjectable);
|
new DockStore({
|
||||||
|
storage: di.inject(dockStorageInjectable),
|
||||||
const storage = createStorage<DockStorageState>("dock", {
|
}),
|
||||||
height: 300,
|
|
||||||
tabs: [
|
|
||||||
{
|
|
||||||
id: "terminal",
|
|
||||||
kind: TabKind.TERMINAL,
|
|
||||||
title: "Terminal",
|
|
||||||
pinned: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
return new DockStore({
|
|
||||||
storage,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -131,16 +131,18 @@ export class DockStore implements DockStorageState {
|
|||||||
return this.dependencies.storage.whenReady;
|
return this.dependencies.storage.whenReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
get isOpen(): boolean {
|
get isOpen(): boolean {
|
||||||
return this.dependencies.storage.get().isOpen;
|
return this.dependencies.storage.value.isOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
set isOpen(isOpen: boolean) {
|
set isOpen(isOpen: boolean) {
|
||||||
this.dependencies.storage.merge({ isOpen });
|
this.dependencies.storage.merge({ isOpen });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
get height(): number {
|
get height(): number {
|
||||||
return this.dependencies.storage.get().height;
|
return this.dependencies.storage.value.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
set height(height: number) {
|
set height(height: number) {
|
||||||
@ -149,21 +151,23 @@ export class DockStore implements DockStorageState {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
get tabs(): DockTab[] {
|
get tabs(): DockTab[] {
|
||||||
return this.dependencies.storage.get().tabs;
|
return this.dependencies.storage.value.tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
set tabs(tabs: DockTab[]) {
|
set tabs(tabs: DockTab[]) {
|
||||||
this.dependencies.storage.merge({ tabs });
|
this.dependencies.storage.merge({ tabs });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
get selectedTabId(): TabId | undefined {
|
get selectedTabId(): TabId | undefined {
|
||||||
return this.dependencies.storage.get().selectedTabId
|
const storageData = this.dependencies.storage.value;
|
||||||
|| (
|
|
||||||
this.tabs.length > 0
|
return (
|
||||||
? this.tabs[0]?.id
|
storageData.selectedTabId ||
|
||||||
: undefined
|
(this.tabs.length > 0 ? this.tabs[0]?.id : undefined)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
set selectedTabId(tabId: TabId) {
|
set selectedTabId(tabId: TabId) {
|
||||||
|
|||||||
@ -58,8 +58,9 @@ export class DockTabStore<T> {
|
|||||||
// auto-save to local-storage
|
// auto-save to local-storage
|
||||||
if (storageKey) {
|
if (storageKey) {
|
||||||
this.storage = this.dependencies.createStorage<T>(storageKey, {});
|
this.storage = this.dependencies.createStorage<T>(storageKey, {});
|
||||||
|
|
||||||
this.storage.whenReady.then(() => {
|
this.storage.whenReady.then(() => {
|
||||||
this.data.replace(this.storage.get());
|
this.data.replace(this.storage.value);
|
||||||
reaction(() => this.toJSON(), data => this.storage.set(data));
|
reaction(() => this.toJSON(), data => this.storage.set(data));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,7 +101,7 @@ class NonInjectedDock extends React.Component<Props & Dependencies> {
|
|||||||
case TabKind.UPGRADE_CHART:
|
case TabKind.UPGRADE_CHART:
|
||||||
return <UpgradeChart tab={tab} />;
|
return <UpgradeChart tab={tab} />;
|
||||||
case TabKind.POD_LOGS:
|
case TabKind.POD_LOGS:
|
||||||
return <Logs tab={tab} />;
|
return <Logs />;
|
||||||
case TabKind.TERMINAL:
|
case TabKind.TERMINAL:
|
||||||
return <TerminalWindow tab={tab} />;
|
return <TerminalWindow tab={tab} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,6 @@ interface Props {
|
|||||||
tabData?: LogTabData
|
tabData?: LogTabData
|
||||||
logs: string[]
|
logs: string[]
|
||||||
save: (data: Partial<LogTabData>) => void
|
save: (data: Partial<LogTabData>) => void
|
||||||
reload: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
@ -45,7 +44,7 @@ interface Dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedLogControls = observer((props: Props & Dependencies) => {
|
const NonInjectedLogControls = observer((props: Props & Dependencies) => {
|
||||||
const { tabData, save, reload, logs, logStore } = props;
|
const { tabData, save, logs, logStore } = props;
|
||||||
|
|
||||||
if (!tabData) {
|
if (!tabData) {
|
||||||
return null;
|
return null;
|
||||||
@ -61,7 +60,7 @@ const NonInjectedLogControls = observer((props: Props & Dependencies) => {
|
|||||||
|
|
||||||
const togglePrevious = () => {
|
const togglePrevious = () => {
|
||||||
save({ previous: !previous });
|
save({ previous: !previous });
|
||||||
reload();
|
logStore.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadLogs = () => {
|
const downloadLogs = () => {
|
||||||
|
|||||||
@ -32,7 +32,6 @@ import type { Align, ListOnScrollProps } from "react-window";
|
|||||||
import { SearchStore } from "../../search-store/search-store";
|
import { SearchStore } from "../../search-store/search-store";
|
||||||
import { UserStore } from "../../../common/user-store";
|
import { UserStore } from "../../../common/user-store";
|
||||||
import { array, boundMethod, cssNames } from "../../utils";
|
import { array, boundMethod, cssNames } from "../../utils";
|
||||||
import { Spinner } from "../spinner";
|
|
||||||
import { VirtualList } from "../virtual-list";
|
import { VirtualList } from "../virtual-list";
|
||||||
import type { LogStore } from "./log-store/log.store";
|
import type { LogStore } from "./log-store/log.store";
|
||||||
import type { LogTabStore } from "./log-tab-store/log-tab.store";
|
import type { LogTabStore } from "./log-tab-store/log-tab.store";
|
||||||
@ -44,8 +43,6 @@ import searchStoreInjectable from "../../search-store/search-store.injectable";
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
logs: string[]
|
logs: string[]
|
||||||
isLoading: boolean
|
|
||||||
load: () => void
|
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +55,7 @@ interface Dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class NonInjectedLogList extends React.Component<Props & Dependencies> {
|
export class NonInjectedLogList extends React.Component<Props & Dependencies> {
|
||||||
@observable isJumpButtonVisible = false;
|
@observable isJumpButtonVisible = false;
|
||||||
@observable isLastLineVisible = true;
|
@observable isLastLineVisible = true;
|
||||||
|
|
||||||
@ -165,7 +162,7 @@ class NonInjectedLogList extends React.Component<Props & Dependencies> {
|
|||||||
const { scrollOffset } = props;
|
const { scrollOffset } = props;
|
||||||
|
|
||||||
if (scrollOffset === 0) {
|
if (scrollOffset === 0) {
|
||||||
this.props.load();
|
this.props.logStore.load();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -241,18 +238,8 @@ class NonInjectedLogList extends React.Component<Props & Dependencies> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isLoading } = this.props;
|
|
||||||
const isInitLoading = isLoading && !this.logs.length;
|
|
||||||
const rowHeights = array.filled(this.logs.length, this.lineHeight);
|
const rowHeights = array.filled(this.logs.length, this.lineHeight);
|
||||||
|
|
||||||
if (isInitLoading) {
|
|
||||||
return (
|
|
||||||
<div className="LogList flex box grow align-center justify-center">
|
|
||||||
<Spinner center/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.logs.length) {
|
if (!this.logs.length) {
|
||||||
return (
|
return (
|
||||||
<div className="LogList flex box grow align-center justify-center">
|
<div className="LogList flex box grow align-center justify-center">
|
||||||
@ -262,7 +249,7 @@ class NonInjectedLogList extends React.Component<Props & Dependencies> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("LogList flex", { isLoading })}>
|
<div className={cssNames("LogList flex" )}>
|
||||||
<VirtualList
|
<VirtualList
|
||||||
items={this.logs}
|
items={this.logs}
|
||||||
rowHeights={rowHeights}
|
rowHeights={rowHeights}
|
||||||
|
|||||||
@ -32,20 +32,21 @@ import { podsStore } from "../+workloads-pods/pods.store";
|
|||||||
import type { TabId } from "./dock-store/dock.store";
|
import type { TabId } from "./dock-store/dock.store";
|
||||||
import logTabStoreInjectable from "./log-tab-store/log-tab-store.injectable";
|
import logTabStoreInjectable from "./log-tab-store/log-tab-store.injectable";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import logStoreInjectable from "./log-store/log-store.injectable";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tabId: TabId
|
tabId: TabId
|
||||||
tabData: LogTabData
|
tabData: LogTabData
|
||||||
save: (data: Partial<LogTabData>) => void
|
save: (data: Partial<LogTabData>) => void
|
||||||
reload: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
logTabStore: LogTabStore
|
logTabStore: LogTabStore
|
||||||
|
reloadLogs: () => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedLogResourceSelector = observer((props: Props & Dependencies) => {
|
const NonInjectedLogResourceSelector = observer((props: Props & Dependencies) => {
|
||||||
const { tabData, save, reload, tabId, logTabStore } = props;
|
const { tabData, save, tabId, logTabStore, reloadLogs } = props;
|
||||||
const { selectedPod, selectedContainer, pods } = tabData;
|
const { selectedPod, selectedContainer, pods } = tabData;
|
||||||
const pod = new Pod(selectedPod);
|
const pod = new Pod(selectedPod);
|
||||||
const containers = pod.getContainers();
|
const containers = pod.getContainers();
|
||||||
@ -57,7 +58,8 @@ const NonInjectedLogResourceSelector = observer((props: Props & Dependencies) =>
|
|||||||
.concat(initContainers)
|
.concat(initContainers)
|
||||||
.find(container => container.name === option.value),
|
.find(container => container.name === option.value),
|
||||||
});
|
});
|
||||||
reload();
|
|
||||||
|
reloadLogs();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPodChange = (option: SelectOption) => {
|
const onPodChange = (option: SelectOption) => {
|
||||||
@ -96,7 +98,7 @@ const NonInjectedLogResourceSelector = observer((props: Props & Dependencies) =>
|
|||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reload();
|
reloadLogs();
|
||||||
}, [selectedPod]);
|
}, [selectedPod]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -128,6 +130,7 @@ export const LogResourceSelector = withInjectables<Dependencies, Props>(
|
|||||||
{
|
{
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
logTabStore: di.inject(logTabStoreInjectable),
|
logTabStore: di.inject(logTabStoreInjectable),
|
||||||
|
reloadLogs: di.inject(logStoreInjectable).reload,
|
||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { podsApi } from "../../../../../common/k8s-api/endpoints";
|
||||||
|
|
||||||
|
const callForLogsInjectable = getInjectable({
|
||||||
|
instantiate: () => podsApi.getLogs,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default callForLogsInjectable;
|
||||||
@ -22,11 +22,13 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|||||||
import { LogStore } from "./log.store";
|
import { LogStore } from "./log.store";
|
||||||
import logTabStoreInjectable from "../log-tab-store/log-tab-store.injectable";
|
import logTabStoreInjectable from "../log-tab-store/log-tab-store.injectable";
|
||||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||||
|
import callForLogsInjectable from "./call-for-logs/call-for-logs.injectable";
|
||||||
|
|
||||||
const logStoreInjectable = getInjectable({
|
const logStoreInjectable = getInjectable({
|
||||||
instantiate: (di) => new LogStore({
|
instantiate: (di) => new LogStore({
|
||||||
logTabStore: di.inject(logTabStoreInjectable),
|
logTabStore: di.inject(logTabStoreInjectable),
|
||||||
dockStore: di.inject(dockStoreInjectable),
|
dockStore: di.inject(dockStoreInjectable),
|
||||||
|
callForLogs: di.inject(callForLogsInjectable),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
import { autorun, computed, observable, makeObservable } from "mobx";
|
import { autorun, computed, observable, makeObservable } from "mobx";
|
||||||
|
|
||||||
import { IPodLogsQuery, Pod, podsApi } from "../../../../common/k8s-api/endpoints";
|
import { IPodLogsQuery, Pod } from "../../../../common/k8s-api/endpoints";
|
||||||
import { autoBind, interval } from "../../../utils";
|
import { autoBind, interval } from "../../../utils";
|
||||||
import { DockStore, TabId, TabKind } from "../dock-store/dock.store";
|
import { DockStore, TabId, TabKind } from "../dock-store/dock.store";
|
||||||
import type { LogTabStore } from "../log-tab-store/log-tab.store";
|
import type { LogTabStore } from "../log-tab-store/log-tab.store";
|
||||||
@ -33,6 +33,7 @@ const logLinesToLoad = 500;
|
|||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
logTabStore: LogTabStore
|
logTabStore: LogTabStore
|
||||||
dockStore: DockStore
|
dockStore: DockStore
|
||||||
|
callForLogs: ({ namespace, name }: { namespace: string, name: string }, query: IPodLogsQuery) => Promise<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LogStore {
|
export class LogStore {
|
||||||
@ -79,9 +80,10 @@ export class LogStore {
|
|||||||
* Each time it increasing it's number, caused to fetch more logs.
|
* Each time it increasing it's number, caused to fetch more logs.
|
||||||
* Also, it handles loading errors, rewriting whole logs with error
|
* Also, it handles loading errors, rewriting whole logs with error
|
||||||
* messages
|
* messages
|
||||||
* @param tabId
|
|
||||||
*/
|
*/
|
||||||
load = async (tabId: TabId) => {
|
load = async () => {
|
||||||
|
const tabId = this.dependencies.dockStore.selectedTabId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const logs = await this.loadLogs(tabId, {
|
const logs = await this.loadLogs(tabId, {
|
||||||
tailLines: this.lines + logLinesToLoad,
|
tailLines: this.lines + logLinesToLoad,
|
||||||
@ -127,12 +129,13 @@ export class LogStore {
|
|||||||
*/
|
*/
|
||||||
async loadLogs(tabId: TabId, params: Partial<IPodLogsQuery>): Promise<string[]> {
|
async loadLogs(tabId: TabId, params: Partial<IPodLogsQuery>): Promise<string[]> {
|
||||||
const data = this.dependencies.logTabStore.getData(tabId);
|
const data = this.dependencies.logTabStore.getData(tabId);
|
||||||
|
|
||||||
const { selectedContainer, previous } = data;
|
const { selectedContainer, previous } = data;
|
||||||
const pod = new Pod(data.selectedPod);
|
const pod = new Pod(data.selectedPod);
|
||||||
const namespace = pod.getNs();
|
const namespace = pod.getNs();
|
||||||
const name = pod.getName();
|
const name = pod.getName();
|
||||||
|
|
||||||
const result = await podsApi.getLogs({ namespace, name }, {
|
const result = await this.dependencies.callForLogs({ namespace, name }, {
|
||||||
...params,
|
...params,
|
||||||
timestamps: true, // Always setting timestamp to separate old logs from new ones
|
timestamps: true, // Always setting timestamp to separate old logs from new ones
|
||||||
container: selectedContainer.name,
|
container: selectedContainer.name,
|
||||||
@ -205,4 +208,10 @@ export class LogStore {
|
|||||||
clearLogs(tabId: TabId) {
|
clearLogs(tabId: TabId) {
|
||||||
this.podLogs.delete(tabId);
|
this.podLogs.delete(tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reload = async () => {
|
||||||
|
this.clearLogs(this.dependencies.dockStore.selectedTabId);
|
||||||
|
|
||||||
|
await this.load();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import logStoreInjectable from "./log-store.injectable";
|
||||||
|
|
||||||
|
const reloadedLogStoreInjectable = getInjectable({
|
||||||
|
instantiate: async (di) => {
|
||||||
|
const nonReloadedStore = di.inject(logStoreInjectable);
|
||||||
|
|
||||||
|
await nonReloadedStore.reload();
|
||||||
|
|
||||||
|
return nonReloadedStore;
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.transient,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default reloadedLogStoreInjectable;
|
||||||
@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import uniqueId from "lodash/uniqueId";
|
import uniqueId from "lodash/uniqueId";
|
||||||
import { reaction } from "mobx";
|
import { computed, makeObservable, reaction } from "mobx";
|
||||||
import { podsStore } from "../../+workloads-pods/pods.store";
|
import { podsStore } from "../../+workloads-pods/pods.store";
|
||||||
|
|
||||||
import { IPodContainer, Pod } from "../../../../common/k8s-api/endpoints";
|
import { IPodContainer, Pod } from "../../../../common/k8s-api/endpoints";
|
||||||
@ -59,6 +59,14 @@ export class LogTabStore extends DockTabStore<LogTabData> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
reaction(() => podsStore.items.length, () => this.updateTabsData());
|
reaction(() => podsStore.items.length, () => this.updateTabsData());
|
||||||
|
|
||||||
|
makeObservable(this, {
|
||||||
|
tabs: computed,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get tabs() {
|
||||||
|
return this.data.get(this.dependencies.dockStore.selectedTabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
createPodTab({ selectedPod, selectedContainer }: PodLogsTabData): string {
|
createPodTab({ selectedPod, selectedContainer }: PodLogsTabData): string {
|
||||||
|
|||||||
@ -20,87 +20,46 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observable, reaction, makeObservable } from "mobx";
|
import { observer } from "mobx-react";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
|
||||||
|
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
import type { DockTab } from "./dock-store/dock.store";
|
|
||||||
import { InfoPanel } from "./info-panel";
|
import { InfoPanel } from "./info-panel";
|
||||||
import { LogResourceSelector } from "./log-resource-selector";
|
import { LogResourceSelector } from "./log-resource-selector";
|
||||||
import { LogList } from "./log-list";
|
import { LogList, NonInjectedLogList } from "./log-list";
|
||||||
import type { LogStore } from "./log-store/log.store";
|
|
||||||
import { LogSearch } from "./log-search";
|
import { LogSearch } from "./log-search";
|
||||||
import { LogControls } from "./log-controls";
|
import { LogControls } from "./log-controls";
|
||||||
import type { LogTabData, LogTabStore } from "./log-tab-store/log-tab.store";
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import logTabStoreInjectable from "./log-tab-store/log-tab-store.injectable";
|
|
||||||
import logStoreInjectable from "./log-store/log-store.injectable";
|
|
||||||
import type { SearchStore } from "../../search-store/search-store";
|
import type { SearchStore } from "../../search-store/search-store";
|
||||||
import searchStoreInjectable from "../../search-store/search-store.injectable";
|
import searchStoreInjectable from "../../search-store/search-store.injectable";
|
||||||
|
import { Spinner } from "../spinner";
|
||||||
|
import logsViewModelInjectable from "./logs/logs-view-model/logs-view-model.injectable";
|
||||||
|
import type { LogsViewModel } from "./logs/logs-view-model/logs-view-model";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string
|
className?: string;
|
||||||
tab: DockTab
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
logTabStore: LogTabStore
|
|
||||||
logStore: LogStore
|
|
||||||
searchStore: SearchStore
|
searchStore: SearchStore
|
||||||
|
model: LogsViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class NonInjectedLogs extends React.Component<Props & Dependencies> {
|
class NonInjectedLogs extends React.Component<Props & Dependencies> {
|
||||||
@observable isLoading = true;
|
private logListElement = React.createRef<NonInjectedLogList>(); // A reference for VirtualList component
|
||||||
|
|
||||||
private logListElement = React.createRef<typeof LogList>(); // A reference for VirtualList component
|
get model() {
|
||||||
|
return this.props.model;
|
||||||
constructor(props: Props & Dependencies) {
|
|
||||||
super(props);
|
|
||||||
makeObservable(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
disposeOnUnmount(this,
|
|
||||||
reaction(() => this.props.tab.id, this.reload, { fireImmediately: true }),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get tabId() {
|
|
||||||
return this.props.tab.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
load = async () => {
|
|
||||||
this.isLoading = true;
|
|
||||||
await this.props.logStore.load(this.tabId);
|
|
||||||
this.isLoading = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
reload = async () => {
|
|
||||||
this.props.logStore.clearLogs(this.tabId);
|
|
||||||
await this.load();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function for various actions after search is happened
|
|
||||||
* @param query {string} A text from search field
|
|
||||||
*/
|
|
||||||
@boundMethod
|
|
||||||
onSearch() {
|
|
||||||
this.toOverlay();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scrolling to active overlay (search word highlight)
|
* Scrolling to active overlay (search word highlight)
|
||||||
*/
|
*/
|
||||||
@boundMethod
|
@boundMethod
|
||||||
toOverlay() {
|
scrollToOverlay() {
|
||||||
const { activeOverlayLine } = this.props.searchStore;
|
const { activeOverlayLine } = this.props.searchStore;
|
||||||
|
|
||||||
if (!this.logListElement.current || activeOverlayLine === undefined) return;
|
if (!this.logListElement.current || activeOverlayLine === undefined) return;
|
||||||
// Scroll vertically
|
// Scroll vertically
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
this.logListElement.current.scrollToItem(activeOverlayLine, "center");
|
this.logListElement.current.scrollToItem(activeOverlayLine, "center");
|
||||||
// Scroll horizontally in timeout since virtual list need some time to prepare its contents
|
// Scroll horizontally in timeout since virtual list need some time to prepare its contents
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -111,33 +70,35 @@ class NonInjectedLogs extends React.Component<Props & Dependencies> {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderResourceSelector(data?: LogTabData) {
|
renderResourceSelector() {
|
||||||
if (!data) {
|
const { tabs, logs, logsWithoutTimestamps, saveTab, tabId } = this.model;
|
||||||
|
|
||||||
|
if (!tabs) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logs = this.props.logStore.logs;
|
const searchLogs = tabs.showTimestamps ? logs : logsWithoutTimestamps;
|
||||||
const searchLogs = data.showTimestamps ? logs : this.props.logStore.logsWithoutTimestamps;
|
|
||||||
const controls = (
|
const controls = (
|
||||||
<div className="flex gaps">
|
<div className="flex gaps">
|
||||||
<LogResourceSelector
|
<LogResourceSelector
|
||||||
tabId={this.tabId}
|
tabId={tabId}
|
||||||
tabData={data}
|
tabData={tabs}
|
||||||
save={newData => this.props.logTabStore.setData(this.tabId, { ...data, ...newData })}
|
save={saveTab}
|
||||||
reload={this.reload}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LogSearch
|
<LogSearch
|
||||||
onSearch={this.onSearch}
|
onSearch={this.scrollToOverlay}
|
||||||
logs={searchLogs}
|
logs={searchLogs}
|
||||||
toPrevOverlay={this.toOverlay}
|
toPrevOverlay={this.scrollToOverlay}
|
||||||
toNextOverlay={this.toOverlay}
|
toNextOverlay={this.scrollToOverlay}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InfoPanel
|
<InfoPanel
|
||||||
tabId={this.props.tab.id}
|
tabId={this.model.tabId}
|
||||||
controls={controls}
|
controls={controls}
|
||||||
showSubmitClose={false}
|
showSubmitClose={false}
|
||||||
showButtons={false}
|
showButtons={false}
|
||||||
@ -147,44 +108,44 @@ class NonInjectedLogs extends React.Component<Props & Dependencies> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const logs = this.props.logStore.logs;
|
const { logs, tabs, tabId, saveTab } = this.model;
|
||||||
const data = this.props.logTabStore.getData(this.tabId);
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
this.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="PodLogs flex column">
|
<div className="PodLogs flex column">
|
||||||
{this.renderResourceSelector(data)}
|
{this.renderResourceSelector()}
|
||||||
|
|
||||||
<LogList
|
<LogList
|
||||||
logs={logs}
|
logs={logs}
|
||||||
id={this.tabId}
|
id={tabId}
|
||||||
isLoading={this.isLoading}
|
|
||||||
load={this.load}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
ref={this.logListElement}
|
ref={this.logListElement}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LogControls
|
<LogControls
|
||||||
logs={logs}
|
logs={logs}
|
||||||
tabData={data}
|
tabData={tabs}
|
||||||
save={newData => this.props.logTabStore.setData(this.tabId, { ...data, ...newData })}
|
save={saveTab}
|
||||||
reload={this.reload}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const Logs = withInjectables<Dependencies, Props>(
|
export const Logs = withInjectables<Dependencies, Props>(
|
||||||
NonInjectedLogs,
|
NonInjectedLogs,
|
||||||
|
|
||||||
{
|
{
|
||||||
getProps: (di, props) => ({
|
|
||||||
logTabStore: di.inject(logTabStoreInjectable),
|
getPlaceholder: () => (
|
||||||
logStore: di.inject(logStoreInjectable),
|
<div className="flex box grow align-center justify-center">
|
||||||
|
<Spinner center />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
|
||||||
|
getProps: async (di, props) => ({
|
||||||
searchStore: di.inject(searchStoreInjectable),
|
searchStore: di.inject(searchStoreInjectable),
|
||||||
|
model: await di.inject(logsViewModelInjectable),
|
||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import dockStoreInjectable from "../../dock-store/dock-store.injectable";
|
||||||
|
import logTabStoreInjectable from "../../log-tab-store/log-tab-store.injectable";
|
||||||
|
import reloadedLogStoreInjectable from "../../log-store/reloaded-log-store.injectable";
|
||||||
|
import { LogsViewModel } from "./logs-view-model";
|
||||||
|
|
||||||
|
const logsViewModelInjectable = getInjectable({
|
||||||
|
instantiate: async (di) => new LogsViewModel({
|
||||||
|
dockStore: di.inject(dockStoreInjectable),
|
||||||
|
logTabStore: di.inject(logTabStoreInjectable),
|
||||||
|
logStore: await di.inject(reloadedLogStoreInjectable),
|
||||||
|
}),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default logsViewModelInjectable;
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import type { LogTabData, LogTabStore } from "../../log-tab-store/log-tab.store";
|
||||||
|
import type { LogStore } from "../../log-store/log.store";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import { makeObservable } from "mobx";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
dockStore: { selectedTabId: string },
|
||||||
|
logTabStore: LogTabStore
|
||||||
|
logStore: LogStore
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LogsViewModel {
|
||||||
|
constructor(private dependencies: Dependencies) {
|
||||||
|
makeObservable(this, {
|
||||||
|
logs: computed,
|
||||||
|
logsWithoutTimestamps: computed,
|
||||||
|
tabs: computed,
|
||||||
|
tabId: computed,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get logs() {
|
||||||
|
return this.dependencies.logStore.logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
get logsWithoutTimestamps() {
|
||||||
|
return this.dependencies.logStore.logsWithoutTimestamps;
|
||||||
|
}
|
||||||
|
|
||||||
|
get tabs() {
|
||||||
|
return this.dependencies.logTabStore.tabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
get tabId() {
|
||||||
|
return this.dependencies.dockStore.selectedTabId;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveTab = (newTabs: LogTabData) => {
|
||||||
|
this.dependencies.logTabStore.setData(this.tabId, { ...this.tabs, ...newTabs });
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -546,7 +546,10 @@ class NonInjectedItemListLayout<I extends ItemObject> extends React.Component<It
|
|||||||
export function ItemListLayout<I extends ItemObject>(
|
export function ItemListLayout<I extends ItemObject>(
|
||||||
props: ItemListLayoutProps<I>,
|
props: ItemListLayoutProps<I>,
|
||||||
) {
|
) {
|
||||||
return withInjectables<Dependencies, ItemListLayoutProps<I>>(
|
const InjectedItemListLayout = withInjectables<
|
||||||
|
Dependencies,
|
||||||
|
ItemListLayoutProps<I>
|
||||||
|
>(
|
||||||
NonInjectedItemListLayout,
|
NonInjectedItemListLayout,
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -556,5 +559,7 @@ export function ItemListLayout<I extends ItemObject>(
|
|||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)(props);
|
);
|
||||||
|
|
||||||
|
return <InjectedItemListLayout {...props} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -157,7 +157,10 @@ class NonInjectedKubeObjectListLayout<K extends KubeObject> extends React.Compon
|
|||||||
export function KubeObjectListLayout<K extends KubeObject>(
|
export function KubeObjectListLayout<K extends KubeObject>(
|
||||||
props: KubeObjectListLayoutProps<K>,
|
props: KubeObjectListLayoutProps<K>,
|
||||||
) {
|
) {
|
||||||
return withInjectables<Dependencies, KubeObjectListLayoutProps<K>>(
|
const InjectedKubeObjectListLayout = withInjectables<
|
||||||
|
Dependencies,
|
||||||
|
KubeObjectListLayoutProps<K>
|
||||||
|
>(
|
||||||
NonInjectedKubeObjectListLayout,
|
NonInjectedKubeObjectListLayout,
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -167,5 +170,7 @@ export function KubeObjectListLayout<K extends KubeObject>(
|
|||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)(props);
|
);
|
||||||
|
|
||||||
|
return <InjectedKubeObjectListLayout {...props} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -127,7 +127,7 @@ class NonInjectedKubeObjectMenu<TKubeObject extends KubeObject> extends React.Co
|
|||||||
export function KubeObjectMenu<T extends KubeObject>(
|
export function KubeObjectMenu<T extends KubeObject>(
|
||||||
props: KubeObjectMenuProps<T>,
|
props: KubeObjectMenuProps<T>,
|
||||||
) {
|
) {
|
||||||
return withInjectables<Dependencies, KubeObjectMenuProps<T>>(
|
const InjectedKubeObjectMenu = withInjectables<Dependencies, KubeObjectMenuProps<T>>(
|
||||||
NonInjectedKubeObjectMenu,
|
NonInjectedKubeObjectMenu,
|
||||||
{
|
{
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
@ -142,5 +142,7 @@ export function KubeObjectMenu<T extends KubeObject>(
|
|||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)(props);
|
);
|
||||||
|
|
||||||
|
return <InjectedKubeObjectMenu {...props} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -263,7 +263,7 @@ class NonInjectedTable<Item> extends React.Component<TableProps<Item> & Dependen
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Table<Item>(props: TableProps<Item>) {
|
export function Table<Item>(props: TableProps<Item>) {
|
||||||
return withInjectables<Dependencies, TableProps<Item>>(
|
const InjectedTable = withInjectables<Dependencies, TableProps<Item>>(
|
||||||
NonInjectedTable,
|
NonInjectedTable,
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -272,6 +272,8 @@ export function Table<Item>(props: TableProps<Item>) {
|
|||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)(props);
|
);
|
||||||
|
|
||||||
|
return <InjectedTable {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Helper for working with storages (e.g. window.localStorage, NodeJS/file-system, etc.)
|
// Helper for working with storages (e.g. window.localStorage, NodeJS/file-system, etc.)
|
||||||
import { action, comparer, makeObservable, observable, toJS, when } from "mobx";
|
import {
|
||||||
|
action,
|
||||||
|
comparer,
|
||||||
|
computed,
|
||||||
|
makeObservable,
|
||||||
|
observable,
|
||||||
|
toJS,
|
||||||
|
when,
|
||||||
|
} from "mobx";
|
||||||
import produce, { Draft, isDraft } from "immer";
|
import produce, { Draft, isDraft } from "immer";
|
||||||
import { isEqual, isPlainObject } from "lodash";
|
import { isEqual, isPlainObject } from "lodash";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
@ -66,10 +74,6 @@ export class StorageHelper<T> {
|
|||||||
|
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
|
||||||
this.data.observe_(({ newValue, oldValue }) => {
|
|
||||||
this.onChange(newValue as T, oldValue as T);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (autoInit) {
|
if (autoInit) {
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
@ -130,16 +134,25 @@ export class StorageHelper<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(): T {
|
get(): T {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
get value(): T {
|
||||||
return this.data.get() ?? this.defaultValue;
|
return this.data.get() ?? this.defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
set(value: T) {
|
set(newValue: T) {
|
||||||
if (this.isDefaultValue(value)) {
|
const oldValue = this.value;
|
||||||
|
|
||||||
|
if (this.isDefaultValue(newValue)) {
|
||||||
this.reset();
|
this.reset();
|
||||||
} else {
|
} else {
|
||||||
this.data.set(value);
|
this.data.set(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.onChange(newValue, oldValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user