1
0
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:
Janne Savolainen 2022-01-10 12:14:10 +02:00
parent 4441d714dd
commit 48948c7286
No known key found for this signature in database
GPG Key ID: 5F465B5672372402
23 changed files with 354 additions and 161 deletions

View File

@ -27,11 +27,11 @@ import type { KubeJsonApiData } from "../kube-json-api";
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
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`;
return this.request.get(path, { query });
}
};
}
export function getMetricsForPods(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise<IPodMetrics> {

View File

@ -58,7 +58,6 @@ const getComponent = (tabData: LogTabData) => {
tabId="tabId"
tabData={tabData}
save={jest.fn()}
reload={jest.fn()}
/>
);
};

View File

@ -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;

View File

@ -19,29 +19,14 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { DockStorageState, DockStore, TabKind } from "./dock.store";
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
import { DockStore } from "./dock.store";
import dockStorageInjectable from "./dock-storage/dock-storage.injectable";
const dockStoreInjectable = getInjectable({
instantiate: (di) => {
const createStorage = di.inject(createStorageInjectable);
const storage = createStorage<DockStorageState>("dock", {
height: 300,
tabs: [
{
id: "terminal",
kind: TabKind.TERMINAL,
title: "Terminal",
pinned: false,
},
],
});
return new DockStore({
storage,
});
},
instantiate: (di) =>
new DockStore({
storage: di.inject(dockStorageInjectable),
}),
lifecycle: lifecycleEnum.singleton,
});

View File

@ -131,16 +131,18 @@ export class DockStore implements DockStorageState {
return this.dependencies.storage.whenReady;
}
@computed
get isOpen(): boolean {
return this.dependencies.storage.get().isOpen;
return this.dependencies.storage.value.isOpen;
}
set isOpen(isOpen: boolean) {
this.dependencies.storage.merge({ isOpen });
}
@computed
get height(): number {
return this.dependencies.storage.get().height;
return this.dependencies.storage.value.height;
}
set height(height: number) {
@ -149,20 +151,22 @@ export class DockStore implements DockStorageState {
});
}
@computed
get tabs(): DockTab[] {
return this.dependencies.storage.get().tabs;
return this.dependencies.storage.value.tabs;
}
set tabs(tabs: DockTab[]) {
this.dependencies.storage.merge({ tabs });
}
@computed
get selectedTabId(): TabId | undefined {
return this.dependencies.storage.get().selectedTabId
|| (
this.tabs.length > 0
? this.tabs[0]?.id
: undefined
const storageData = this.dependencies.storage.value;
return (
storageData.selectedTabId ||
(this.tabs.length > 0 ? this.tabs[0]?.id : undefined)
);
}

View File

@ -58,8 +58,9 @@ export class DockTabStore<T> {
// auto-save to local-storage
if (storageKey) {
this.storage = this.dependencies.createStorage<T>(storageKey, {});
this.storage.whenReady.then(() => {
this.data.replace(this.storage.get());
this.data.replace(this.storage.value);
reaction(() => this.toJSON(), data => this.storage.set(data));
});
}

View File

@ -101,7 +101,7 @@ class NonInjectedDock extends React.Component<Props & Dependencies> {
case TabKind.UPGRADE_CHART:
return <UpgradeChart tab={tab} />;
case TabKind.POD_LOGS:
return <Logs tab={tab} />;
return <Logs />;
case TabKind.TERMINAL:
return <TerminalWindow tab={tab} />;
}

View File

@ -37,7 +37,6 @@ interface Props {
tabData?: LogTabData
logs: string[]
save: (data: Partial<LogTabData>) => void
reload: () => void
}
interface Dependencies {
@ -45,7 +44,7 @@ interface Dependencies {
}
const NonInjectedLogControls = observer((props: Props & Dependencies) => {
const { tabData, save, reload, logs, logStore } = props;
const { tabData, save, logs, logStore } = props;
if (!tabData) {
return null;
@ -61,7 +60,7 @@ const NonInjectedLogControls = observer((props: Props & Dependencies) => {
const togglePrevious = () => {
save({ previous: !previous });
reload();
logStore.reload();
};
const downloadLogs = () => {

View File

@ -32,7 +32,6 @@ import type { Align, ListOnScrollProps } from "react-window";
import { SearchStore } from "../../search-store/search-store";
import { UserStore } from "../../../common/user-store";
import { array, boundMethod, cssNames } from "../../utils";
import { Spinner } from "../spinner";
import { VirtualList } from "../virtual-list";
import type { LogStore } from "./log-store/log.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 {
logs: string[]
isLoading: boolean
load: () => void
id: string
}
@ -58,7 +55,7 @@ interface Dependencies {
}
@observer
class NonInjectedLogList extends React.Component<Props & Dependencies> {
export class NonInjectedLogList extends React.Component<Props & Dependencies> {
@observable isJumpButtonVisible = false;
@observable isLastLineVisible = true;
@ -165,7 +162,7 @@ class NonInjectedLogList extends React.Component<Props & Dependencies> {
const { scrollOffset } = props;
if (scrollOffset === 0) {
this.props.load();
this.props.logStore.load();
}
};
@ -241,18 +238,8 @@ class NonInjectedLogList extends React.Component<Props & Dependencies> {
};
render() {
const { isLoading } = this.props;
const isInitLoading = isLoading && !this.logs.length;
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) {
return (
<div className="LogList flex box grow align-center justify-center">
@ -262,7 +249,7 @@ class NonInjectedLogList extends React.Component<Props & Dependencies> {
}
return (
<div className={cssNames("LogList flex", { isLoading })}>
<div className={cssNames("LogList flex" )}>
<VirtualList
items={this.logs}
rowHeights={rowHeights}

View File

@ -32,20 +32,21 @@ import { podsStore } from "../+workloads-pods/pods.store";
import type { TabId } from "./dock-store/dock.store";
import logTabStoreInjectable from "./log-tab-store/log-tab-store.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import logStoreInjectable from "./log-store/log-store.injectable";
interface Props {
tabId: TabId
tabData: LogTabData
save: (data: Partial<LogTabData>) => void
reload: () => void
}
interface Dependencies {
logTabStore: LogTabStore
reloadLogs: () => Promise<void>
}
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 pod = new Pod(selectedPod);
const containers = pod.getContainers();
@ -57,7 +58,8 @@ const NonInjectedLogResourceSelector = observer((props: Props & Dependencies) =>
.concat(initContainers)
.find(container => container.name === option.value),
});
reload();
reloadLogs();
};
const onPodChange = (option: SelectOption) => {
@ -96,7 +98,7 @@ const NonInjectedLogResourceSelector = observer((props: Props & Dependencies) =>
];
useEffect(() => {
reload();
reloadLogs();
}, [selectedPod]);
return (
@ -128,6 +130,7 @@ export const LogResourceSelector = withInjectables<Dependencies, Props>(
{
getProps: (di, props) => ({
logTabStore: di.inject(logTabStoreInjectable),
reloadLogs: di.inject(logStoreInjectable).reload,
...props,
}),
},

View File

@ -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;

View File

@ -22,11 +22,13 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { LogStore } from "./log.store";
import logTabStoreInjectable from "../log-tab-store/log-tab-store.injectable";
import dockStoreInjectable from "../dock-store/dock-store.injectable";
import callForLogsInjectable from "./call-for-logs/call-for-logs.injectable";
const logStoreInjectable = getInjectable({
instantiate: (di) => new LogStore({
logTabStore: di.inject(logTabStoreInjectable),
dockStore: di.inject(dockStoreInjectable),
callForLogs: di.inject(callForLogsInjectable),
}),
lifecycle: lifecycleEnum.singleton,

View File

@ -21,7 +21,7 @@
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 { DockStore, TabId, TabKind } from "../dock-store/dock.store";
import type { LogTabStore } from "../log-tab-store/log-tab.store";
@ -33,6 +33,7 @@ const logLinesToLoad = 500;
interface Dependencies {
logTabStore: LogTabStore
dockStore: DockStore
callForLogs: ({ namespace, name }: { namespace: string, name: string }, query: IPodLogsQuery) => Promise<string>
}
export class LogStore {
@ -79,9 +80,10 @@ export class LogStore {
* Each time it increasing it's number, caused to fetch more logs.
* Also, it handles loading errors, rewriting whole logs with error
* messages
* @param tabId
*/
load = async (tabId: TabId) => {
load = async () => {
const tabId = this.dependencies.dockStore.selectedTabId;
try {
const logs = await this.loadLogs(tabId, {
tailLines: this.lines + logLinesToLoad,
@ -127,12 +129,13 @@ export class LogStore {
*/
async loadLogs(tabId: TabId, params: Partial<IPodLogsQuery>): Promise<string[]> {
const data = this.dependencies.logTabStore.getData(tabId);
const { selectedContainer, previous } = data;
const pod = new Pod(data.selectedPod);
const namespace = pod.getNs();
const name = pod.getName();
const result = await podsApi.getLogs({ namespace, name }, {
const result = await this.dependencies.callForLogs({ namespace, name }, {
...params,
timestamps: true, // Always setting timestamp to separate old logs from new ones
container: selectedContainer.name,
@ -205,4 +208,10 @@ export class LogStore {
clearLogs(tabId: TabId) {
this.podLogs.delete(tabId);
}
reload = async () => {
this.clearLogs(this.dependencies.dockStore.selectedTabId);
await this.load();
};
}

View File

@ -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;

View File

@ -20,7 +20,7 @@
*/
import uniqueId from "lodash/uniqueId";
import { reaction } from "mobx";
import { computed, makeObservable, reaction } from "mobx";
import { podsStore } from "../../+workloads-pods/pods.store";
import { IPodContainer, Pod } from "../../../../common/k8s-api/endpoints";
@ -59,6 +59,14 @@ export class LogTabStore extends DockTabStore<LogTabData> {
});
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 {

View File

@ -20,87 +20,46 @@
*/
import React from "react";
import { observable, reaction, makeObservable } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { observer } from "mobx-react";
import { boundMethod } from "../../utils";
import type { DockTab } from "./dock-store/dock.store";
import { InfoPanel } from "./info-panel";
import { LogResourceSelector } from "./log-resource-selector";
import { LogList } from "./log-list";
import type { LogStore } from "./log-store/log.store";
import { LogList, NonInjectedLogList } from "./log-list";
import { LogSearch } from "./log-search";
import { LogControls } from "./log-controls";
import type { LogTabData, LogTabStore } from "./log-tab-store/log-tab.store";
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 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 {
className?: string
tab: DockTab
className?: string;
}
interface Dependencies {
logTabStore: LogTabStore
logStore: LogStore
searchStore: SearchStore
model: LogsViewModel
}
@observer
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
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();
get model() {
return this.props.model;
}
/**
* Scrolling to active overlay (search word highlight)
*/
@boundMethod
toOverlay() {
scrollToOverlay() {
const { activeOverlayLine } = this.props.searchStore;
if (!this.logListElement.current || activeOverlayLine === undefined) return;
// Scroll vertically
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.logListElement.current.scrollToItem(activeOverlayLine, "center");
// Scroll horizontally in timeout since virtual list need some time to prepare its contents
setTimeout(() => {
@ -111,33 +70,35 @@ class NonInjectedLogs extends React.Component<Props & Dependencies> {
}, 100);
}
renderResourceSelector(data?: LogTabData) {
if (!data) {
renderResourceSelector() {
const { tabs, logs, logsWithoutTimestamps, saveTab, tabId } = this.model;
if (!tabs) {
return null;
}
const logs = this.props.logStore.logs;
const searchLogs = data.showTimestamps ? logs : this.props.logStore.logsWithoutTimestamps;
const searchLogs = tabs.showTimestamps ? logs : logsWithoutTimestamps;
const controls = (
<div className="flex gaps">
<LogResourceSelector
tabId={this.tabId}
tabData={data}
save={newData => this.props.logTabStore.setData(this.tabId, { ...data, ...newData })}
reload={this.reload}
tabId={tabId}
tabData={tabs}
save={saveTab}
/>
<LogSearch
onSearch={this.onSearch}
onSearch={this.scrollToOverlay}
logs={searchLogs}
toPrevOverlay={this.toOverlay}
toNextOverlay={this.toOverlay}
toPrevOverlay={this.scrollToOverlay}
toNextOverlay={this.scrollToOverlay}
/>
</div>
);
return (
<InfoPanel
tabId={this.props.tab.id}
tabId={this.model.tabId}
controls={controls}
showSubmitClose={false}
showButtons={false}
@ -147,44 +108,44 @@ class NonInjectedLogs extends React.Component<Props & Dependencies> {
}
render() {
const logs = this.props.logStore.logs;
const data = this.props.logTabStore.getData(this.tabId);
if (!data) {
this.reload();
}
const { logs, tabs, tabId, saveTab } = this.model;
return (
<div className="PodLogs flex column">
{this.renderResourceSelector(data)}
{this.renderResourceSelector()}
<LogList
logs={logs}
id={this.tabId}
isLoading={this.isLoading}
load={this.load}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
id={tabId}
ref={this.logListElement}
/>
<LogControls
logs={logs}
tabData={data}
save={newData => this.props.logTabStore.setData(this.tabId, { ...data, ...newData })}
reload={this.reload}
tabData={tabs}
save={saveTab}
/>
</div>
);
}
}
export const Logs = withInjectables<Dependencies, Props>(
NonInjectedLogs,
{
getProps: (di, props) => ({
logTabStore: di.inject(logTabStoreInjectable),
logStore: di.inject(logStoreInjectable),
getPlaceholder: () => (
<div className="flex box grow align-center justify-center">
<Spinner center />
</div>
),
getProps: async (di, props) => ({
searchStore: di.inject(searchStoreInjectable),
model: await di.inject(logsViewModelInjectable),
...props,
}),
},

View File

@ -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;

View File

@ -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 });
};
}

View File

@ -546,7 +546,10 @@ class NonInjectedItemListLayout<I extends ItemObject> extends React.Component<It
export function ItemListLayout<I extends ItemObject>(
props: ItemListLayoutProps<I>,
) {
return withInjectables<Dependencies, ItemListLayoutProps<I>>(
const InjectedItemListLayout = withInjectables<
Dependencies,
ItemListLayoutProps<I>
>(
NonInjectedItemListLayout,
{
@ -556,5 +559,7 @@ export function ItemListLayout<I extends ItemObject>(
...props,
}),
},
)(props);
);
return <InjectedItemListLayout {...props} />;
}

View File

@ -157,7 +157,10 @@ class NonInjectedKubeObjectListLayout<K extends KubeObject> extends React.Compon
export function KubeObjectListLayout<K extends KubeObject>(
props: KubeObjectListLayoutProps<K>,
) {
return withInjectables<Dependencies, KubeObjectListLayoutProps<K>>(
const InjectedKubeObjectListLayout = withInjectables<
Dependencies,
KubeObjectListLayoutProps<K>
>(
NonInjectedKubeObjectListLayout,
{
@ -167,5 +170,7 @@ export function KubeObjectListLayout<K extends KubeObject>(
...props,
}),
},
)(props);
);
return <InjectedKubeObjectListLayout {...props} />;
}

View File

@ -127,7 +127,7 @@ class NonInjectedKubeObjectMenu<TKubeObject extends KubeObject> extends React.Co
export function KubeObjectMenu<T extends KubeObject>(
props: KubeObjectMenuProps<T>,
) {
return withInjectables<Dependencies, KubeObjectMenuProps<T>>(
const InjectedKubeObjectMenu = withInjectables<Dependencies, KubeObjectMenuProps<T>>(
NonInjectedKubeObjectMenu,
{
getProps: (di, props) => ({
@ -142,5 +142,7 @@ export function KubeObjectMenu<T extends KubeObject>(
...props,
}),
},
)(props);
);
return <InjectedKubeObjectMenu {...props} />;
}

View File

@ -263,7 +263,7 @@ class NonInjectedTable<Item> extends React.Component<TableProps<Item> & Dependen
}
export function Table<Item>(props: TableProps<Item>) {
return withInjectables<Dependencies, TableProps<Item>>(
const InjectedTable = withInjectables<Dependencies, TableProps<Item>>(
NonInjectedTable,
{
@ -272,6 +272,8 @@ export function Table<Item>(props: TableProps<Item>) {
...props,
}),
},
)(props);
);
return <InjectedTable {...props} />;
}

View File

@ -20,7 +20,15 @@
*/
// 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 { isEqual, isPlainObject } from "lodash";
import logger from "../../main/logger";
@ -66,10 +74,6 @@ export class StorageHelper<T> {
this.storage = storage;
this.data.observe_(({ newValue, oldValue }) => {
this.onChange(newValue as T, oldValue as T);
});
if (autoInit) {
this.init();
}
@ -130,16 +134,25 @@ export class StorageHelper<T> {
}
get(): T {
return this.value;
}
@computed
get value(): T {
return this.data.get() ?? this.defaultValue;
}
@action
set(value: T) {
if (this.isDefaultValue(value)) {
set(newValue: T) {
const oldValue = this.value;
if (this.isDefaultValue(newValue)) {
this.reset();
} else {
this.data.set(value);
this.data.set(newValue);
}
this.onChange(newValue, oldValue);
}
@action