Commit 842dde3d by Ivana Huckova Committed by GitHub

Explore: Refactor log rows (#21066)

parent 71382ae7
...@@ -7,6 +7,7 @@ import { LogDetailsRow } from './LogDetailsRow'; ...@@ -7,6 +7,7 @@ import { LogDetailsRow } from './LogDetailsRow';
const setup = (propOverrides?: Partial<Props>, rowOverrides?: Partial<LogRowModel>) => { const setup = (propOverrides?: Partial<Props>, rowOverrides?: Partial<LogRowModel>) => {
const props: Props = { const props: Props = {
theme: {} as GrafanaTheme, theme: {} as GrafanaTheme,
showDuplicates: false,
row: { row: {
dataFrame: new MutableDataFrame(), dataFrame: new MutableDataFrame(),
entryFieldIndex: 0, entryFieldIndex: 0,
......
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import memoizeOne from 'memoize-one'; import memoizeOne from 'memoize-one';
import { css, cx } from 'emotion';
import { import {
calculateFieldStats, calculateFieldStats,
calculateLogsLabelStats, calculateLogsLabelStats,
...@@ -8,11 +9,14 @@ import { ...@@ -8,11 +9,14 @@ import {
getParser, getParser,
LinkModel, LinkModel,
LogRowModel, LogRowModel,
GrafanaTheme,
} from '@grafana/data'; } from '@grafana/data';
import { Themeable } from '../../types/theme'; import { Themeable } from '../../types/theme';
import { withTheme } from '../../themes/index'; import { withTheme } from '../../themes/index';
import { getLogRowStyles } from './getLogRowStyles'; import { getLogRowStyles } from './getLogRowStyles';
import { stylesFactory } from '../../themes/stylesFactory';
import { selectThemeVariant } from '../../themes/selectThemeVariant';
//Components //Components
import { LogDetailsRow } from './LogDetailsRow'; import { LogDetailsRow } from './LogDetailsRow';
...@@ -26,12 +30,36 @@ type FieldDef = { ...@@ -26,12 +30,36 @@ type FieldDef = {
export interface Props extends Themeable { export interface Props extends Themeable {
row: LogRowModel; row: LogRowModel;
showDuplicates: boolean;
getRows: () => LogRowModel[]; getRows: () => LogRowModel[];
className?: string;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
onClickFilterLabel?: (key: string, value: string) => void; onClickFilterLabel?: (key: string, value: string) => void;
onClickFilterOutLabel?: (key: string, value: string) => void; onClickFilterOutLabel?: (key: string, value: string) => void;
getFieldLinks?: (field: Field, rowIndex: number) => Array<LinkModel<Field>>; getFieldLinks?: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
} }
const getStyles = stylesFactory((theme: GrafanaTheme) => {
const bgColor = selectThemeVariant({ light: theme.colors.gray7, dark: theme.colors.dark2 }, theme.type);
return {
hoverBackground: css`
label: hoverBackground;
background-color: ${bgColor};
`,
logsRowLevelDetails: css`
label: logs-row__level_details;
&::after {
top: -3px;
}
`,
logDetailsDefaultCursor: css`
label: logDetailsDefaultCursor;
cursor: default;
`,
};
});
class UnThemedLogDetails extends PureComponent<Props> { class UnThemedLogDetails extends PureComponent<Props> {
getParser = memoizeOne(getParser); getParser = memoizeOne(getParser);
...@@ -102,72 +130,93 @@ class UnThemedLogDetails extends PureComponent<Props> { ...@@ -102,72 +130,93 @@ class UnThemedLogDetails extends PureComponent<Props> {
}; };
render() { render() {
const { row, theme, onClickFilterOutLabel, onClickFilterLabel, getRows } = this.props; const {
row,
theme,
onClickFilterOutLabel,
onClickFilterLabel,
getRows,
showDuplicates,
className,
onMouseEnter,
onMouseLeave,
} = this.props;
const style = getLogRowStyles(theme, row.logLevel); const style = getLogRowStyles(theme, row.logLevel);
const styles = getStyles(theme);
const labels = row.labels ? row.labels : {}; const labels = row.labels ? row.labels : {};
const labelsAvailable = Object.keys(labels).length > 0; const labelsAvailable = Object.keys(labels).length > 0;
const fields = this.getAllFields(row); const fields = this.getAllFields(row);
const parsedFieldsAvailable = fields && fields.length > 0; const parsedFieldsAvailable = fields && fields.length > 0;
return ( return (
<div className={style.logDetailsContainer}> <tr
<table className={style.logDetailsTable}> className={cx(className, styles.logDetailsDefaultCursor)}
<tbody> onMouseEnter={onMouseEnter}
{labelsAvailable && ( onMouseLeave={onMouseLeave}
<tr> >
<td colSpan={5} className={style.logDetailsHeading} aria-label="Log Labels"> {showDuplicates && <td />}
Log Labels: <td className={cx(style.logsRowLevel, styles.logsRowLevelDetails)} />
</td> <td colSpan={4}>
</tr> <div className={style.logDetailsContainer}>
)} <table className={style.logDetailsTable}>
{Object.keys(labels).map(key => { <tbody>
const value = labels[key]; {labelsAvailable && (
return ( <tr>
<LogDetailsRow <td colSpan={5} className={style.logDetailsHeading} aria-label="Log Labels">
key={`${key}=${value}`} Log Labels:
parsedKey={key} </td>
parsedValue={value} </tr>
isLabel={true} )}
getStats={() => calculateLogsLabelStats(getRows(), key)} {Object.keys(labels).map(key => {
onClickFilterOutLabel={onClickFilterOutLabel} const value = labels[key];
onClickFilterLabel={onClickFilterLabel} return (
/> <LogDetailsRow
); key={`${key}=${value}`}
})} parsedKey={key}
parsedValue={value}
isLabel={true}
getStats={() => calculateLogsLabelStats(getRows(), key)}
onClickFilterOutLabel={onClickFilterOutLabel}
onClickFilterLabel={onClickFilterLabel}
/>
);
})}
{parsedFieldsAvailable && ( {parsedFieldsAvailable && (
<tr> <tr>
<td colSpan={5} className={style.logDetailsHeading} aria-label="Parsed Fields"> <td colSpan={5} className={style.logDetailsHeading} aria-label="Parsed Fields">
Parsed Fields: Parsed Fields:
</td> </td>
</tr> </tr>
)} )}
{fields.map(field => { {fields.map(field => {
const { key, value, links, fieldIndex } = field; const { key, value, links, fieldIndex } = field;
return ( return (
<LogDetailsRow <LogDetailsRow
key={`${key}=${value}`} key={`${key}=${value}`}
parsedKey={key} parsedKey={key}
parsedValue={value} parsedValue={value}
links={links} links={links}
getStats={() => getStats={() =>
fieldIndex === undefined fieldIndex === undefined
? this.getStatsForParsedField(key) ? this.getStatsForParsedField(key)
: calculateStats(row.dataFrame.fields[fieldIndex].values.toArray()) : calculateStats(row.dataFrame.fields[fieldIndex].values.toArray())
} }
/> />
); );
})} })}
{!parsedFieldsAvailable && !labelsAvailable && ( {!parsedFieldsAvailable && !labelsAvailable && (
<tr> <tr>
<td colSpan={5} aria-label="No details"> <td colSpan={5} aria-label="No details">
No details available No details available
</td> </td>
</tr> </tr>
)} )}
</tbody> </tbody>
</table> </table>
</div> </div>
</td>
</tr>
); );
} }
} }
......
...@@ -38,6 +38,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -38,6 +38,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
label: hoverCursor; label: hoverCursor;
cursor: pointer; cursor: pointer;
`, `,
wordBreakAll: css`
label: wordBreakAll;
word-break: break-all;
`,
}; };
}); });
...@@ -102,7 +106,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> { ...@@ -102,7 +106,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
{/* Key - value columns */} {/* Key - value columns */}
<td className={style.logDetailsLabel}>{parsedKey}</td> <td className={style.logDetailsLabel}>{parsedKey}</td>
<td className={style.logsRowCell}> <td className={styles.wordBreakAll}>
{parsedValue} {parsedValue}
{links && {links &&
links.map(link => { links.map(link => {
......
...@@ -24,7 +24,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -24,7 +24,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
padding: 0 2px; padding: 0 2px;
background-color: ${selectThemeVariant({ light: theme.colors.gray5, dark: theme.colors.dark6 }, theme.type)}; background-color: ${selectThemeVariant({ light: theme.colors.gray5, dark: theme.colors.dark6 }, theme.type)};
border-radius: ${theme.border.radius}; border-radius: ${theme.border.radius};
margin: 0 4px 2px 0; margin: 1px 4px 0 0;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
......
...@@ -12,6 +12,7 @@ import { Themeable } from '../../types/theme'; ...@@ -12,6 +12,7 @@ import { Themeable } from '../../types/theme';
import { withTheme } from '../../themes/index'; import { withTheme } from '../../themes/index';
import { getLogRowStyles } from './getLogRowStyles'; import { getLogRowStyles } from './getLogRowStyles';
import { stylesFactory } from '../../themes/stylesFactory'; import { stylesFactory } from '../../themes/stylesFactory';
import { selectThemeVariant } from '../../themes/selectThemeVariant';
//Components //Components
import { LogDetails } from './LogDetails'; import { LogDetails } from './LogDetails';
...@@ -38,14 +39,20 @@ interface Props extends Themeable { ...@@ -38,14 +39,20 @@ interface Props extends Themeable {
interface State { interface State {
showContext: boolean; showContext: boolean;
showDetails: boolean; showDetails: boolean;
hasHoverBackground: boolean;
} }
const getStyles = stylesFactory((theme: GrafanaTheme) => { const getStyles = stylesFactory((theme: GrafanaTheme) => {
const bgColor = selectThemeVariant({ light: theme.colors.gray7, dark: theme.colors.dark2 }, theme.type);
return { return {
topVerticalAlign: css` topVerticalAlign: css`
label: topVerticalAlign; label: topVerticalAlign;
vertical-align: top; vertical-align: top;
`, `,
hoverBackground: css`
label: hoverBackground;
background-color: ${bgColor};
`,
}; };
}); });
/** /**
...@@ -59,6 +66,7 @@ class UnThemedLogRow extends PureComponent<Props, State> { ...@@ -59,6 +66,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
state: State = { state: State = {
showContext: false, showContext: false,
showDetails: false, showDetails: false,
hasHoverBackground: false,
}; };
toggleContext = () => { toggleContext = () => {
...@@ -69,6 +77,22 @@ class UnThemedLogRow extends PureComponent<Props, State> { ...@@ -69,6 +77,22 @@ class UnThemedLogRow extends PureComponent<Props, State> {
}); });
}; };
/**
* We are using onMouse events to change background of Log Details Table to hover-state-background when
* hovered over Log Row and vice versa. This can't be done with css because we use 2 separate table rows without common parent element.
*/
addHoverBackground = () => {
this.setState({
hasHoverBackground: true,
});
};
clearHoverBackground = () => {
this.setState({
hasHoverBackground: false,
});
};
toggleDetails = () => { toggleDetails = () => {
if (this.props.allowDetails) { if (this.props.allowDetails) {
return; return;
...@@ -101,72 +125,76 @@ class UnThemedLogRow extends PureComponent<Props, State> { ...@@ -101,72 +125,76 @@ class UnThemedLogRow extends PureComponent<Props, State> {
theme, theme,
getFieldLinks, getFieldLinks,
} = this.props; } = this.props;
const { showDetails, showContext } = this.state; const { showDetails, showContext, hasHoverBackground } = this.state;
const style = getLogRowStyles(theme, row.logLevel); const style = getLogRowStyles(theme, row.logLevel);
const styles = getStyles(theme); const styles = getStyles(theme);
const showUtc = timeZone === 'utc'; const showUtc = timeZone === 'utc';
const showDetailsClassName = showDetails const showDetailsClassName = showDetails
? cx(['fa fa-chevron-down', styles.topVerticalAlign]) ? cx(['fa fa-chevron-down', styles.topVerticalAlign])
: cx(['fa fa-chevron-right', styles.topVerticalAlign]); : cx(['fa fa-chevron-right', styles.topVerticalAlign]);
const hoverBackground = cx(style.logsRow, { [styles.hoverBackground]: hasHoverBackground });
return ( return (
<div className={style.logsRow}> <>
{showDuplicates && ( <tr
<div className={style.logsRowDuplicates}> className={hoverBackground}
{row.duplicates && row.duplicates > 0 ? `${row.duplicates + 1}x` : null} onMouseEnter={this.addHoverBackground}
</div> onMouseLeave={this.clearHoverBackground}
)} onClick={this.toggleDetails}
<div className={style.logsRowLevel} /> >
{!allowDetails && ( {showDuplicates && (
<div <td className={style.logsRowDuplicates}>
title={showDetails ? 'Hide log details' : 'See log details'} {row.duplicates && row.duplicates > 0 ? `${row.duplicates + 1}x` : null}
onClick={this.toggleDetails} </td>
className={style.logsRowToggleDetails} )}
> <td className={style.logsRowLevel} />
<i className={showDetailsClassName} /> {!allowDetails && (
</div> <td title={showDetails ? 'Hide log details' : 'See log details'} className={style.logsRowToggleDetails}>
)} <i className={showDetailsClassName} />
<div> </td>
<div onClick={this.toggleDetails}> )}
{showTime && showUtc && ( {showTime && showUtc && (
<div className={style.logsRowLocalTime} title={`Local: ${row.timeLocal} (${row.timeFromNow})`}> <td className={style.logsRowLocalTime} title={`Local: ${row.timeLocal} (${row.timeFromNow})`}>
{row.timeUtc} {row.timeUtc}
</div> </td>
)}
{showTime && !showUtc && (
<div className={style.logsRowLocalTime} title={`${row.timeUtc} (${row.timeFromNow})`}>
{row.timeLocal}
</div>
)}
{showLabels && row.uniqueLabels && (
<div className={style.logsRowLabels}>
<LogLabels labels={row.uniqueLabels} />
</div>
)}
<LogRowMessage
highlighterExpressions={highlighterExpressions}
row={row}
getRows={getRows}
errors={errors}
hasMoreContextRows={hasMoreContextRows}
updateLimit={updateLimit}
context={context}
showContext={showContext}
wrapLogMessage={wrapLogMessage}
onToggleContext={this.toggleContext}
/>
</div>
{this.state.showDetails && (
<LogDetails
getFieldLinks={getFieldLinks}
onClickFilterLabel={onClickFilterLabel}
onClickFilterOutLabel={onClickFilterOutLabel}
getRows={getRows}
row={row}
/>
)} )}
</div> {showTime && !showUtc && (
</div> <td className={style.logsRowLocalTime} title={`${row.timeUtc} (${row.timeFromNow})`}>
{row.timeLocal}
</td>
)}
{showLabels && row.uniqueLabels && (
<td className={style.logsRowLabels}>
<LogLabels labels={row.uniqueLabels} />
</td>
)}
<LogRowMessage
highlighterExpressions={highlighterExpressions}
row={row}
getRows={getRows}
errors={errors}
hasMoreContextRows={hasMoreContextRows}
updateLimit={updateLimit}
context={context}
showContext={showContext}
wrapLogMessage={wrapLogMessage}
onToggleContext={this.toggleContext}
/>
</tr>
{this.state.showDetails && (
<LogDetails
className={hoverBackground}
onMouseEnter={this.addHoverBackground}
onMouseLeave={this.clearHoverBackground}
showDuplicates={showDuplicates}
getFieldLinks={getFieldLinks}
onClickFilterLabel={onClickFilterLabel}
onClickFilterOutLabel={onClickFilterOutLabel}
getRows={getRows}
row={row}
/>
)}
</>
); );
} }
......
...@@ -96,7 +96,7 @@ class UnThemedLogRowMessage extends PureComponent<Props, State> { ...@@ -96,7 +96,7 @@ class UnThemedLogRowMessage extends PureComponent<Props, State> {
: cx([style.logsRowMatchHighLight]); : cx([style.logsRowMatchHighLight]);
const styles = getStyles(theme); const styles = getStyles(theme);
return ( return (
<div className={style.logsRowMessage}> <td className={style.logsRowMessage}>
<div className={cx(styles.positionRelative, { [styles.horizontalScroll]: !wrapLogMessage })}> <div className={cx(styles.positionRelative, { [styles.horizontalScroll]: !wrapLogMessage })}>
{showContext && context && ( {showContext && context && (
<LogRowContext <LogRowContext
...@@ -133,7 +133,7 @@ class UnThemedLogRowMessage extends PureComponent<Props, State> { ...@@ -133,7 +133,7 @@ class UnThemedLogRowMessage extends PureComponent<Props, State> {
</span> </span>
)} )}
</div> </div>
</div> </td>
); );
} }
} }
......
...@@ -88,7 +88,7 @@ class UnThemedLogRows extends PureComponent<Props, State> { ...@@ -88,7 +88,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
getFieldLinks, getFieldLinks,
} = this.props; } = this.props;
const { renderAll } = this.state; const { renderAll } = this.state;
const { logsRows, logsRowsHorizontalScroll } = getLogRowStyles(theme); const { logsRowsTable, logsRowsHorizontalScroll } = getLogRowStyles(theme);
const dedupedRows = deduplicatedRows ? deduplicatedRows : logRows; const dedupedRows = deduplicatedRows ? deduplicatedRows : logRows;
const hasData = logRows && logRows.length > 0; const hasData = logRows && logRows.length > 0;
const dedupCount = dedupedRows const dedupCount = dedupedRows
...@@ -108,48 +108,54 @@ class UnThemedLogRows extends PureComponent<Props, State> { ...@@ -108,48 +108,54 @@ class UnThemedLogRows extends PureComponent<Props, State> {
const getRowContext = this.props.getRowContext ? this.props.getRowContext : () => Promise.resolve([]); const getRowContext = this.props.getRowContext ? this.props.getRowContext : () => Promise.resolve([]);
return ( return (
<div className={logsRows}> <div className={horizontalScrollWindow}>
<div className={horizontalScrollWindow}> <table className={logsRowsTable}>
{hasData && <tbody>
firstRows.map((row, index) => ( {hasData &&
<LogRow firstRows.map((row, index) => (
key={row.uid} <LogRow
getRows={getRows} key={row.uid}
getRowContext={getRowContext} getRows={getRows}
highlighterExpressions={highlighterExpressions} getRowContext={getRowContext}
row={row} highlighterExpressions={highlighterExpressions}
showDuplicates={showDuplicates} row={row}
showLabels={showLabels} showDuplicates={showDuplicates}
showTime={showTime} showLabels={showLabels}
wrapLogMessage={wrapLogMessage} showTime={showTime}
timeZone={timeZone} wrapLogMessage={wrapLogMessage}
allowDetails={allowDetails} timeZone={timeZone}
onClickFilterLabel={onClickFilterLabel} allowDetails={allowDetails}
onClickFilterOutLabel={onClickFilterOutLabel} onClickFilterLabel={onClickFilterLabel}
getFieldLinks={getFieldLinks} onClickFilterOutLabel={onClickFilterOutLabel}
/> getFieldLinks={getFieldLinks}
))} />
{hasData && ))}
renderAll && {hasData &&
lastRows.map((row, index) => ( renderAll &&
<LogRow lastRows.map((row, index) => (
key={row.uid} <LogRow
getRows={getRows} key={row.uid}
getRowContext={getRowContext} getRows={getRows}
row={row} getRowContext={getRowContext}
showDuplicates={showDuplicates} row={row}
showLabels={showLabels} showDuplicates={showDuplicates}
showTime={showTime} showLabels={showLabels}
wrapLogMessage={wrapLogMessage} showTime={showTime}
timeZone={timeZone} wrapLogMessage={wrapLogMessage}
allowDetails={allowDetails} timeZone={timeZone}
onClickFilterLabel={onClickFilterLabel} allowDetails={allowDetails}
onClickFilterOutLabel={onClickFilterOutLabel} onClickFilterLabel={onClickFilterLabel}
getFieldLinks={getFieldLinks} onClickFilterOutLabel={onClickFilterOutLabel}
/> getFieldLinks={getFieldLinks}
))} />
{hasData && !renderAll && <span>Rendering {rowCount - previewLimit!} rows...</span>} ))}
</div> {hasData && !renderAll && (
<tr>
<td colSpan={5}>Rendering {rowCount - previewLimit!} rows...</td>
</tr>
)}
</tbody>
</table>
</div> </div>
); );
} }
......
...@@ -53,23 +53,22 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo ...@@ -53,23 +53,22 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
background-color: rgba(${theme.colors.yellow}, 0.2); background-color: rgba(${theme.colors.yellow}, 0.2);
border-bottom-style: dotted; border-bottom-style: dotted;
`, `,
logsRows: css` logsRowsTable: css`
label: logs-rows; label: logs-rows;
font-family: ${theme.typography.fontFamily.monospace}; font-family: ${theme.typography.fontFamily.monospace};
font-size: ${theme.typography.size.sm}; font-size: ${theme.typography.size.sm};
display: table;
table-layout: fixed;
width: 100%; width: 100%;
`, `,
logsRowsHorizontalScroll: css` logsRowsHorizontalScroll: css`
label: logs-rows__horizontal-scroll; label: logs-rows__horizontal-scroll;
overflow-y: scroll; overflow: scroll;
`, `,
context: context, context: context,
logsRow: css` logsRow: css`
label: logs-row; label: logs-row;
display: table-row; width: 100%;
cursor: pointer; cursor: pointer;
vertical-align: top;
&:hover { &:hover {
.${context} { .${context} {
visibility: visible; visibility: visible;
...@@ -82,8 +81,7 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo ...@@ -82,8 +81,7 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
} }
} }
> div { > td {
display: table-cell;
padding-right: ${theme.spacing.sm}; padding-right: ${theme.spacing.sm};
border-top: ${theme.border.width.sm} solid transparent; border-top: ${theme.border.width.sm} solid transparent;
border-bottom: ${theme.border.width.sm} solid transparent; border-bottom: ${theme.border.width.sm} solid transparent;
...@@ -103,9 +101,7 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo ...@@ -103,9 +101,7 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
logsRowLevel: css` logsRowLevel: css`
label: logs-row__level; label: logs-row__level;
position: relative; position: relative;
width: 10px;
cursor: default; cursor: default;
&::after { &::after {
content: ''; content: '';
display: block; display: block;
...@@ -116,39 +112,24 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo ...@@ -116,39 +112,24 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
background-color: ${logColor}; background-color: ${logColor};
} }
`, `,
logsRowCell: css`
label: logs-row-cell;
word-break: break-all;
padding-right: ${theme.spacing.sm};
`,
logsRowToggleDetails: css` logsRowToggleDetails: css`
label: logs-row-toggle-details__level; label: logs-row-toggle-details__level;
position: relative; position: relative;
width: 15px;
font-size: 9px; font-size: 9px;
padding-top: 5px;
`, `,
logsRowLocalTime: css` logsRowLocalTime: css`
label: logs-row__localtime; label: logs-row__localtime;
display: table-cell;
white-space: nowrap; white-space: nowrap;
width: 12.5em;
padding-right: 1em;
`, `,
logsRowLabels: css` logsRowLabels: css`
label: logs-row__labels; label: logs-row__labels;
display: table-cell;
white-space: nowrap; white-space: nowrap;
width: 22em; max-width: 22em;
padding-right: 1em;
`, `,
logsRowMessage: css` logsRowMessage: css`
label: logs-row__message; label: logs-row__message;
word-break: break-all; word-break: break-all;
display: table-cell;
`,
logsRowStats: css`
label: logs-row__stats;
margin: 5px 0;
`, `,
//Log details sepcific CSS //Log details sepcific CSS
logDetailsContainer: css` logDetailsContainer: css`
...@@ -156,23 +137,27 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo ...@@ -156,23 +137,27 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
border: 1px solid ${borderColor}; border: 1px solid ${borderColor};
padding: 0 ${theme.spacing.sm} ${theme.spacing.sm}; padding: 0 ${theme.spacing.sm} ${theme.spacing.sm};
border-radius: 3px; border-radius: 3px;
margin: 20px 0; margin: 20px 8px 20px 16px;
cursor: default; cursor: default;
`, `,
logDetailsTable: css` logDetailsTable: css`
label: logs-row-details-table; label: logs-row-details-table;
line-height: 2;
width: 100%; width: 100%;
td:last-child {
width: 100%;
}
`, `,
logsDetailsIcon: css` logsDetailsIcon: css`
label: logs-row-details__icon; label: logs-row-details__icon;
position: relative; position: relative;
padding-right: ${theme.spacing.sm}; padding-right: ${theme.spacing.md};
color: ${theme.colors.gray3}; color: ${theme.colors.gray3};
`, `,
logDetailsLabel: css` logDetailsLabel: css`
label: logs-row-details__label; label: logs-row-details__label;
max-width: 25em; max-width: 25em;
min-width: 12em; min-width: 15em;
padding: 0 ${theme.spacing.sm}; padding: 0 ${theme.spacing.sm};
word-break: break-all; word-break: break-all;
`, `,
...@@ -183,8 +168,6 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo ...@@ -183,8 +168,6 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
`, `,
logDetailsValue: css` logDetailsValue: css`
label: logs-row-details__row; label: logs-row-details__row;
line-height: 2;
padding: ${theme.spacing.sm};
position: relative; position: relative;
vertical-align: top; vertical-align: top;
cursor: default; cursor: default;
......
...@@ -64,7 +64,7 @@ interface State { ...@@ -64,7 +64,7 @@ interface State {
class LiveLogs extends PureComponent<Props, State> { class LiveLogs extends PureComponent<Props, State> {
private liveEndDiv: HTMLDivElement | null = null; private liveEndDiv: HTMLDivElement | null = null;
private scrollContainerRef = React.createRef<HTMLDivElement>(); private scrollContainerRef = React.createRef<HTMLTableSectionElement>();
private lastScrollPos: number | null = null; private lastScrollPos: number | null = null;
constructor(props: Props) { constructor(props: Props) {
...@@ -139,39 +139,41 @@ class LiveLogs extends PureComponent<Props, State> { ...@@ -139,39 +139,41 @@ class LiveLogs extends PureComponent<Props, State> {
return ( return (
<div> <div>
<div <table>
onScroll={isPaused ? undefined : this.onScroll} <tbody
className={cx(['logs-rows', styles.logsRowsLive])} onScroll={isPaused ? undefined : this.onScroll}
ref={this.scrollContainerRef} className={cx(['logs-rows', styles.logsRowsLive])}
> ref={this.scrollContainerRef}
{this.rowsToRender().map((row: LogRowModel) => { >
return ( {this.rowsToRender().map((row: LogRowModel) => {
<div className={cx(logsRow, styles.logsRowFade)} key={row.uid}> return (
{showUtc && ( <tr className={cx(logsRow, styles.logsRowFade)} key={row.uid}>
<div className={cx(logsRowLocalTime)} title={`Local: ${row.timeLocal} (${row.timeFromNow})`}> {showUtc && (
{row.timeUtc} <td className={cx(logsRowLocalTime)} title={`Local: ${row.timeLocal} (${row.timeFromNow})`}>
</div> {row.timeUtc}
)} </td>
{!showUtc && ( )}
<div className={cx(logsRowLocalTime)} title={`${row.timeUtc} (${row.timeFromNow})`}> {!showUtc && (
{row.timeLocal} <td className={cx(logsRowLocalTime)} title={`${row.timeUtc} (${row.timeFromNow})`}>
</div> {row.timeLocal}
)} </td>
<div className={cx(logsRowMessage)}>{row.entry}</div> )}
</div> <td className={cx(logsRowMessage)}>{row.entry}</td>
); </tr>
})} );
<div })}
ref={element => { <tr
this.liveEndDiv = element; ref={element => {
// This is triggered on every update so on every new row. It keeps the view scrolled at the bottom by this.liveEndDiv = element;
// default. // This is triggered on every update so on every new row. It keeps the view scrolled at the bottom by
if (this.liveEndDiv && !isPaused) { // default.
this.liveEndDiv.scrollIntoView(false); if (this.liveEndDiv && !isPaused) {
} this.liveEndDiv.scrollIntoView(false);
}} }
/> }}
</div> />
</tbody>
</table>
<div className={cx([styles.logsRowsIndicator])}> <div className={cx([styles.logsRowsIndicator])}>
<button onClick={isPaused ? onResume : onPause} className={cx('btn btn-secondary', styles.button)}> <button onClick={isPaused ? onResume : onPause} className={cx('btn btn-secondary', styles.button)}>
<i className={cx('fa', isPaused ? 'fa-play' : 'fa-pause')} /> <i className={cx('fa', isPaused ? 'fa-play' : 'fa-pause')} />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment