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