mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Passing raw logs to PodLogs child components
Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
parent
42817a6d97
commit
ebbc1abb85
@ -22,10 +22,9 @@ interface Props extends PodLogSearchProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const PodLogControls = observer((props: Props) => {
|
export const PodLogControls = observer((props: Props) => {
|
||||||
const { tabData, save, reload, tabId, logs } = props;
|
const { tabData, save, reload, logs } = props;
|
||||||
const { selectedContainer, showTimestamps, previous } = tabData;
|
const { selectedContainer, showTimestamps, previous } = tabData;
|
||||||
const rawLogs = podLogsStore.logs.get(tabId) || [];
|
const since = logs.length ? podLogsStore.getTimestamps(logs[0]) : null;
|
||||||
const since = rawLogs.length ? podLogsStore.getTimestamps(rawLogs[0]) : null;
|
|
||||||
const pod = new Pod(tabData.pod);
|
const pod = new Pod(tabData.pod);
|
||||||
|
|
||||||
const toggleTimestamps = () => {
|
const toggleTimestamps = () => {
|
||||||
@ -39,8 +38,9 @@ export const PodLogControls = observer((props: Props) => {
|
|||||||
|
|
||||||
const downloadLogs = () => {
|
const downloadLogs = () => {
|
||||||
const fileName = selectedContainer ? selectedContainer.name : pod.getName();
|
const fileName = selectedContainer ? selectedContainer.name : pod.getName();
|
||||||
|
const logsToDownload = showTimestamps ? logs : podLogsStore.logsWithoutTimestamps;
|
||||||
|
|
||||||
saveFileDialog(`${fileName}.log`, logs.join("\n"), "text/plain");
|
saveFileDialog(`${fileName}.log`, logsToDownload.join("\n"), "text/plain");
|
||||||
};
|
};
|
||||||
|
|
||||||
const onContainerChange = (option: SelectOption) => {
|
const onContainerChange = (option: SelectOption) => {
|
||||||
@ -118,7 +118,10 @@ export const PodLogControls = observer((props: Props) => {
|
|||||||
tooltip={_i18n._(t`Save`)}
|
tooltip={_i18n._(t`Save`)}
|
||||||
className="download-icon"
|
className="download-icon"
|
||||||
/>
|
/>
|
||||||
<PodLogSearch {...props} />
|
<PodLogSearch
|
||||||
|
{...props}
|
||||||
|
logs={showTimestamps ? logs : podLogsStore.logsWithoutTimestamps}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import AnsiUp from "ansi_up";
|
|||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import debounce from "lodash/debounce";
|
import debounce from "lodash/debounce";
|
||||||
import { Trans } from "@lingui/macro";
|
import { Trans } from "@lingui/macro";
|
||||||
import { action, observable } from "mobx";
|
import { action, computed, observable } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Align, ListOnScrollProps } from "react-window";
|
import { Align, ListOnScrollProps } from "react-window";
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ import { Button } from "../button";
|
|||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
import { VirtualList } from "../virtual-list";
|
import { VirtualList } from "../virtual-list";
|
||||||
import { logRange } from "./pod-logs.store";
|
import { podLogsStore } from "./pod-logs.store";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
logs: string[]
|
logs: string[]
|
||||||
@ -50,7 +50,6 @@ export class PodLogList extends React.Component<Props> {
|
|||||||
if (logs == prevProps.logs || !this.virtualListDiv.current) return;
|
if (logs == prevProps.logs || !this.virtualListDiv.current) return;
|
||||||
const newLogsLoaded = prevProps.logs.length < logs.length;
|
const newLogsLoaded = prevProps.logs.length < logs.length;
|
||||||
const scrolledToBeginning = this.virtualListDiv.current.scrollTop === 0;
|
const scrolledToBeginning = this.virtualListDiv.current.scrollTop === 0;
|
||||||
const fewLogsLoaded = logs.length < logRange;
|
|
||||||
|
|
||||||
if (this.isLastLineVisible) {
|
if (this.isLastLineVisible) {
|
||||||
this.scrollToBottom(); // Scroll down to keep user watching/reading experience
|
this.scrollToBottom(); // Scroll down to keep user watching/reading experience
|
||||||
@ -59,11 +58,12 @@ export class PodLogList extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (scrolledToBeginning && newLogsLoaded) {
|
if (scrolledToBeginning && newLogsLoaded) {
|
||||||
this.virtualListDiv.current.scrollTop = (logs.length - prevProps.logs.length) * this.lineHeight;
|
const firstLineContents = prevProps.logs[0];
|
||||||
}
|
const lineToScroll = this.props.logs.findIndex((value) => value == firstLineContents);
|
||||||
|
|
||||||
if (fewLogsLoaded) {
|
if (lineToScroll !== -1) {
|
||||||
this.isJumpButtonVisible = false;
|
this.scrollToItem(lineToScroll, "start");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!logs.length) {
|
if (!logs.length) {
|
||||||
@ -71,6 +71,20 @@ export class PodLogList extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns logs with or without timestamps regarding to showTimestamps prop
|
||||||
|
*/
|
||||||
|
@computed
|
||||||
|
get logs() {
|
||||||
|
const showTimestamps = podLogsStore.getData(this.props.id).showTimestamps;
|
||||||
|
|
||||||
|
if (!showTimestamps) {
|
||||||
|
return podLogsStore.logsWithoutTimestamps;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.logs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if JumpToBottom button should be visible and sets its observable
|
* Checks if JumpToBottom button should be visible and sets its observable
|
||||||
* @param props Scrolling props from virtual list core
|
* @param props Scrolling props from virtual list core
|
||||||
@ -137,7 +151,7 @@ export class PodLogList extends React.Component<Props> {
|
|||||||
*/
|
*/
|
||||||
getLogRow = (rowIndex: number) => {
|
getLogRow = (rowIndex: number) => {
|
||||||
const { searchQuery, isActiveOverlay } = searchStore;
|
const { searchQuery, isActiveOverlay } = searchStore;
|
||||||
const item = this.props.logs[rowIndex];
|
const item = this.logs[rowIndex];
|
||||||
const contents: React.ReactElement[] = [];
|
const contents: React.ReactElement[] = [];
|
||||||
const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi));
|
const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi));
|
||||||
|
|
||||||
@ -179,15 +193,15 @@ export class PodLogList extends React.Component<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { logs, isLoading } = this.props;
|
const { isLoading } = this.props;
|
||||||
const isInitLoading = isLoading && !logs.length;
|
const isInitLoading = isLoading && !this.logs.length;
|
||||||
const rowHeights = new Array(logs.length).fill(this.lineHeight);
|
const rowHeights = new Array(this.logs.length).fill(this.lineHeight);
|
||||||
|
|
||||||
if (isInitLoading) {
|
if (isInitLoading) {
|
||||||
return <Spinner center/>;
|
return <Spinner center/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!logs.length) {
|
if (!this.logs.length) {
|
||||||
return (
|
return (
|
||||||
<div className="PodLogList flex box grow align-center justify-center">
|
<div className="PodLogList flex box grow align-center justify-center">
|
||||||
<Trans>There are no logs available for container</Trans>
|
<Trans>There are no logs available for container</Trans>
|
||||||
@ -198,7 +212,7 @@ export class PodLogList extends React.Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<div className={cssNames("PodLogList flex", { isLoading })}>
|
<div className={cssNames("PodLogList flex", { isLoading })}>
|
||||||
<VirtualList
|
<VirtualList
|
||||||
items={logs}
|
items={this.logs}
|
||||||
rowHeights={rowHeights}
|
rowHeights={rowHeights}
|
||||||
getRow={this.getLogRow}
|
getRow={this.getLogRow}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
|
|||||||
@ -12,10 +12,13 @@ export interface PodLogSearchProps {
|
|||||||
onSearch: (query: string) => void
|
onSearch: (query: string) => void
|
||||||
toPrevOverlay: () => void
|
toPrevOverlay: () => void
|
||||||
toNextOverlay: () => void
|
toNextOverlay: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props extends PodLogSearchProps {
|
||||||
logs: string[]
|
logs: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PodLogSearch = observer((props: PodLogSearchProps) => {
|
export const PodLogSearch = observer((props: Props) => {
|
||||||
const { logs, onSearch, toPrevOverlay, toNextOverlay } = props;
|
const { logs, onSearch, toPrevOverlay, toNextOverlay } = props;
|
||||||
const { setNextOverlayActive, setPrevOverlayActive, searchQuery, occurrences, activeFind, totalFinds } = searchStore;
|
const { setNextOverlayActive, setPrevOverlayActive, searchQuery, occurrences, activeFind, totalFinds } = searchStore;
|
||||||
const jumpDisabled = !searchQuery || !occurrences.length;
|
const jumpDisabled = !searchQuery || !occurrences.length;
|
||||||
|
|||||||
@ -27,11 +27,11 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
private refresher = interval(10, () => {
|
private refresher = interval(10, () => {
|
||||||
const id = dockStore.selectedTabId;
|
const id = dockStore.selectedTabId;
|
||||||
|
|
||||||
if (!this.logs.get(id)) return;
|
if (!this.podLogs.get(id)) return;
|
||||||
this.loadMore(id);
|
this.loadMore(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
@observable logs = observable.map<TabId, PodLogLine[]>();
|
@observable podLogs = observable.map<TabId, PodLogLine[]>();
|
||||||
@observable newLogSince = observable.map<TabId, string>(); // Timestamp after which all logs are considered to be new
|
@observable newLogSince = observable.map<TabId, string>(); // Timestamp after which all logs are considered to be new
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -48,7 +48,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
}
|
}
|
||||||
}, { delay: 500 });
|
}, { delay: 500 });
|
||||||
|
|
||||||
reaction(() => this.logs.get(dockStore.selectedTabId), () => {
|
reaction(() => this.podLogs.get(dockStore.selectedTabId), () => {
|
||||||
this.setNewLogSince(dockStore.selectedTabId);
|
this.setNewLogSince(dockStore.selectedTabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.refresher.start();
|
this.refresher.start();
|
||||||
this.logs.set(tabId, logs);
|
this.podLogs.set(tabId, logs);
|
||||||
} catch ({error}) {
|
} catch ({error}) {
|
||||||
const message = [
|
const message = [
|
||||||
_i18n._(t`Failed to load logs: ${error.message}`),
|
_i18n._(t`Failed to load logs: ${error.message}`),
|
||||||
@ -80,7 +80,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
this.refresher.stop();
|
this.refresher.stop();
|
||||||
this.logs.set(tabId, message);
|
this.podLogs.set(tabId, message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -91,14 +91,14 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
* @param tabId
|
* @param tabId
|
||||||
*/
|
*/
|
||||||
loadMore = async (tabId: TabId) => {
|
loadMore = async (tabId: TabId) => {
|
||||||
if (!this.logs.get(tabId).length) return;
|
if (!this.podLogs.get(tabId).length) return;
|
||||||
const oldLogs = this.logs.get(tabId);
|
const oldLogs = this.podLogs.get(tabId);
|
||||||
const logs = await this.loadLogs(tabId, {
|
const logs = await this.loadLogs(tabId, {
|
||||||
sinceTime: this.getLastSinceTime(tabId)
|
sinceTime: this.getLastSinceTime(tabId)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add newly received logs to bottom
|
// Add newly received logs to bottom
|
||||||
this.logs.set(tabId, [...oldLogs, ...logs]);
|
this.podLogs.set(tabId, [...oldLogs, ...logs]);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,7 +134,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
* @param tabId
|
* @param tabId
|
||||||
*/
|
*/
|
||||||
setNewLogSince(tabId: TabId) {
|
setNewLogSince(tabId: TabId) {
|
||||||
if (!this.logs.has(tabId) || !this.logs.get(tabId).length || this.newLogSince.has(tabId)) return;
|
if (!this.podLogs.has(tabId) || !this.podLogs.get(tabId).length || this.newLogSince.has(tabId)) return;
|
||||||
const timestamp = this.getLastSinceTime(tabId);
|
const timestamp = this.getLastSinceTime(tabId);
|
||||||
|
|
||||||
this.newLogSince.set(tabId, timestamp.split(".")[0]); // Removing milliseconds from string
|
this.newLogSince.set(tabId, timestamp.split(".")[0]); // Removing milliseconds from string
|
||||||
@ -147,18 +147,38 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
@computed
|
@computed
|
||||||
get lines() {
|
get lines() {
|
||||||
const id = dockStore.selectedTabId;
|
const id = dockStore.selectedTabId;
|
||||||
const logs = this.logs.get(id);
|
const logs = this.podLogs.get(id);
|
||||||
|
|
||||||
return logs ? logs.length : 0;
|
return logs ? logs.length : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns logs with timestamps for selected tab
|
||||||
|
*/
|
||||||
|
get logs() {
|
||||||
|
const id = dockStore.selectedTabId;
|
||||||
|
|
||||||
|
if (!this.podLogs.has(id)) return [];
|
||||||
|
|
||||||
|
return this.podLogs.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes timestamps from each log line and returns changed logs
|
||||||
|
* @returns Logs without timestamps
|
||||||
|
*/
|
||||||
|
get logsWithoutTimestamps() {
|
||||||
|
return this.logs.map(item => this.removeTimestamps(item));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It gets timestamps from all logs then returns last one + 1 second
|
* It gets timestamps from all logs then returns last one + 1 second
|
||||||
* (this allows to avoid getting the last stamp in the selection)
|
* (this allows to avoid getting the last stamp in the selection)
|
||||||
* @param tabId
|
* @param tabId
|
||||||
*/
|
*/
|
||||||
getLastSinceTime(tabId: TabId) {
|
getLastSinceTime(tabId: TabId) {
|
||||||
const logs = this.logs.get(tabId);
|
const logs = this.podLogs.get(tabId);
|
||||||
const timestamps = this.getTimestamps(logs[logs.length - 1]);
|
const timestamps = this.getTimestamps(logs[logs.length - 1]);
|
||||||
const stamp = new Date(timestamps ? timestamps[0] : null);
|
const stamp = new Date(timestamps ? timestamps[0] : null);
|
||||||
|
|
||||||
@ -176,7 +196,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearLogs(tabId: TabId) {
|
clearLogs(tabId: TabId) {
|
||||||
this.logs.delete(tabId);
|
this.podLogs.delete(tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearData(tabId: TabId) {
|
clearData(tabId: TabId) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { computed, observable, reaction } from "mobx";
|
import { observable, reaction } from "mobx";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
|
|
||||||
import { searchStore } from "../../../common/search-store";
|
import { searchStore } from "../../../common/search-store";
|
||||||
@ -79,31 +79,15 @@ export class PodLogs extends React.Component<Props> {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Computed prop which returns logs with or without timestamps added to each line
|
|
||||||
* @returns {Array} An array log items
|
|
||||||
*/
|
|
||||||
@computed
|
|
||||||
get logs(): string[] {
|
|
||||||
if (!podLogsStore.logs.has(this.tabId)) return [];
|
|
||||||
const logs = podLogsStore.logs.get(this.tabId);
|
|
||||||
const { getData, removeTimestamps } = podLogsStore;
|
|
||||||
const { showTimestamps } = getData(this.tabId);
|
|
||||||
|
|
||||||
if (!showTimestamps) {
|
|
||||||
return logs.map(item => removeTimestamps(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
return logs;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const logs = podLogsStore.logs;
|
||||||
|
|
||||||
const controls = (
|
const controls = (
|
||||||
<PodLogControls
|
<PodLogControls
|
||||||
ready={!this.isLoading}
|
ready={!this.isLoading}
|
||||||
tabId={this.tabId}
|
tabId={this.tabId}
|
||||||
tabData={this.tabData}
|
tabData={this.tabData}
|
||||||
logs={this.logs}
|
logs={logs}
|
||||||
save={this.save}
|
save={this.save}
|
||||||
reload={this.reload}
|
reload={this.reload}
|
||||||
onSearch={this.onSearch}
|
onSearch={this.onSearch}
|
||||||
@ -121,9 +105,9 @@ export class PodLogs extends React.Component<Props> {
|
|||||||
showButtons={false}
|
showButtons={false}
|
||||||
/>
|
/>
|
||||||
<PodLogList
|
<PodLogList
|
||||||
|
logs={logs}
|
||||||
id={this.tabId}
|
id={this.tabId}
|
||||||
isLoading={this.isLoading}
|
isLoading={this.isLoading}
|
||||||
logs={this.logs}
|
|
||||||
load={this.load}
|
load={this.load}
|
||||||
ref={this.logListElement}
|
ref={this.logListElement}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user