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";
|
||||
|
||||
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> {
|
||||
|
||||
@ -58,7 +58,6 @@ const getComponent = (tabData: LogTabData) => {
|
||||
tabId="tabId"
|
||||
tabData={tabData}
|
||||
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.
|
||||
*/
|
||||
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,
|
||||
});
|
||||
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
@ -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} />;
|
||||
}
|
||||
|
||||
@ -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 = () => {
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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,
|
||||
}),
|
||||
},
|
||||
|
||||
@ -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 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,
|
||||
|
||||
@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
@ -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 { 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 {
|
||||
|
||||
@ -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,
|
||||
}),
|
||||
},
|
||||
|
||||
@ -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>(
|
||||
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} />;
|
||||
}
|
||||
|
||||
@ -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} />;
|
||||
}
|
||||
|
||||
@ -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} />;
|
||||
}
|
||||
|
||||
@ -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} />;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user