diff --git a/ui/src/logs/components/LogViewerHeader.tsx b/ui/src/logs/components/LogViewerHeader.tsx index 4ab7f5ebe9..c61068cba2 100644 --- a/ui/src/logs/components/LogViewerHeader.tsx +++ b/ui/src/logs/components/LogViewerHeader.tsx @@ -1,6 +1,7 @@ import _ from 'lodash' import React, {PureComponent} from 'react' import {Source, Namespace} from 'src/types' +import classnames from 'classnames' import Dropdown from 'src/shared/components/Dropdown' import TimeRangeDropdown from 'src/logs/components/TimeRangeDropdown' @@ -17,9 +18,11 @@ interface Props { currentSource: Source | null currentNamespaces: Namespace[] timeRange: TimeRange + liveUpdating: boolean onChooseSource: (sourceID: string) => void onChooseNamespace: (namespace: Namespace) => void onChooseTimerange: (timeRange: TimeRange) => void + onChangeLiveUpdatingStatus: () => void } class LogViewerHeader extends PureComponent { @@ -29,7 +32,10 @@ class LogViewerHeader extends PureComponent {
-

Log Viewer

+ {this.status} +

+ Log Viewer +

{ ) } + private get status(): JSX.Element { + const {liveUpdating, onChangeLiveUpdatingStatus} = this.props + + return ( +
    +
  • + +
  • +
  • + +
  • +
+ ) + } + private handleChooseTimeRange = (timerange: TimeRange) => { this.props.onChooseTimerange(timerange) } diff --git a/ui/src/logs/components/LogsTable.tsx b/ui/src/logs/components/LogsTable.tsx index 555983ba35..09a4d09282 100644 --- a/ui/src/logs/components/LogsTable.tsx +++ b/ui/src/logs/components/LogsTable.tsx @@ -1,39 +1,64 @@ import _ from 'lodash' import moment from 'moment' -import React, {PureComponent, MouseEvent} from 'react' +import React, {Component, MouseEvent} from 'react' import {Grid, AutoSizer} from 'react-virtualized' import {getDeep} from 'src/utils/wrappers' import FancyScrollbar from 'src/shared/components/FancyScrollbar' -const ROW_HEIGHT = 40 +const ROW_HEIGHT = 30 +const HIGHLIGHT_COLOR = '#555' interface Props { data: { columns: string[] values: string[] } + isScrolledToTop: boolean + onScrollVertical: () => void + onScrolledToTop: () => void } interface State { scrollLeft: number scrollTop: number + currentRow: number } -class LogsTable extends PureComponent { +class LogsTable extends Component { + public static getDerivedStateFromProps(props, state) { + const {scrolledToTop} = props + + let scrollTop = _.get(state, 'scrollTop', 0) + if (scrolledToTop) { + scrollTop = 0 + } + + return { + scrollTop, + scrollLeft: 0, + currentRow: -1, + } + } + constructor(props: Props) { super(props) this.state = { - scrollLeft: 0, scrollTop: 0, + scrollLeft: 0, + currentRow: -1, } } + public render() { const rowCount = getDeep(this.props, 'data.values.length', 0) const columnCount = getDeep(this.props, 'data.columns.length', 1) - 1 return ( -
+
{({width}) => ( { cellRenderer={this.cellRenderer} columnCount={columnCount} columnWidth={this.getColumnWidth} - style={{height: 40 * rowCount}} + style={{height: ROW_HEIGHT * rowCount}} /> )} @@ -91,6 +116,12 @@ class LogsTable extends PureComponent { private handleScroll = scrollInfo => { const {scrollLeft, scrollTop} = scrollInfo + if (scrollTop === 0) { + this.props.onScrolledToTop() + } else if (scrollTop !== this.state.scrollTop) { + this.props.onScrollVertical() + } + this.setState({scrollLeft, scrollTop}) } @@ -116,7 +147,7 @@ class LogsTable extends PureComponent { switch (column) { case 'message': - return 900 + return 1200 case 'timestamp': return 200 case 'procid': @@ -168,7 +199,9 @@ class LogsTable extends PureComponent { '' ) - let value = this.props.data.values[rowIndex][columnIndex + 1] + let value: string | JSX.Element = this.props.data.values[rowIndex][ + columnIndex + 1 + ] switch (column) { case 'timestamp': @@ -181,22 +214,39 @@ class LogsTable extends PureComponent { value = _.replace(value, '\\n', '') break case 'severity': - return ( -
-
-
+ value = ( +
) } + let backgroundColor = '' + if (rowIndex === this.state.currentRow && columnIndex > 0) { + backgroundColor = HIGHLIGHT_COLOR + } + return ( -
+
{value}
) } + + private handleMouseOver = (e: MouseEvent) => { + const target = e.target as HTMLElement + this.setState({currentRow: +target.dataset.index}) + } + + private handleMouseOut = () => { + this.setState({currentRow: -1}) + } } export default LogsTable diff --git a/ui/src/logs/containers/LogsPage.tsx b/ui/src/logs/containers/LogsPage.tsx index 2770264971..6901c6dd97 100644 --- a/ui/src/logs/containers/LogsPage.tsx +++ b/ui/src/logs/containers/LogsPage.tsx @@ -51,6 +51,7 @@ interface Props { interface State { searchString: string filters: Filter[] + liveUpdating: boolean } const DUMMY_FILTERS = [ @@ -64,12 +65,15 @@ const DUMMY_FILTERS = [ ] class LogsPage extends PureComponent { + private interval: NodeJS.Timer + constructor(props: Props) { super(props) this.state = { searchString: '', filters: DUMMY_FILTERS, + liveUpdating: false, } } @@ -85,10 +89,16 @@ class LogsPage extends PureComponent { if (this.props.currentNamespace) { this.props.executeQueriesAsync() } + + this.startUpdating() + } + + public componentWillUnmount() { + clearInterval(this.interval) } public render() { - const {filters} = this.state + const {filters, liveUpdating} = this.state const {searchTerm} = this.props const count = getDeep(this.props, 'tableData.values.length', 0) @@ -107,12 +117,43 @@ class LogsPage extends PureComponent { filters={filters} onUpdateFilters={this.handleUpdateFilters} /> - +
) } + private startUpdating = () => { + if (this.interval) { + clearInterval(this.interval) + } + + this.interval = setInterval(this.handleInterval, 10000) + this.setState({liveUpdating: true}) + } + + private handleScrollToTop = () => { + if (!this.state.liveUpdating) { + this.startUpdating() + } + } + + private handleVerticalScroll = () => { + if (this.state.liveUpdating) { + clearInterval(this.interval) + this.setState({liveUpdating: false}) + } + } + + private handleInterval = () => { + this.props.executeQueriesAsync() + } + private get chart(): JSX.Element { const {histogramData, timeRange} = this.props return ( @@ -133,8 +174,11 @@ class LogsPage extends PureComponent { timeRange, } = this.props + const {liveUpdating} = this.state + return ( { currentSource={currentSource} currentNamespaces={currentNamespaces} currentNamespace={currentNamespace} + onChangeLiveUpdatingStatus={this.handleChangeLiveUpdatingStatus} /> ) } + private handleChangeLiveUpdatingStatus = (): void => { + const {liveUpdating} = this.state + + if (liveUpdating) { + clearInterval(this.interval) + this.setState({liveUpdating: false}) + } else { + this.startUpdating() + } + } + private handleSubmitSearch = (value: string): void => { this.props.setSearchTermAsync(value) } diff --git a/ui/src/style/fonts/icomoon.eot b/ui/src/style/fonts/icomoon.eot index d6b6879bbd..e3b8460f95 100755 Binary files a/ui/src/style/fonts/icomoon.eot and b/ui/src/style/fonts/icomoon.eot differ diff --git a/ui/src/style/fonts/icomoon.svg b/ui/src/style/fonts/icomoon.svg index 33b8b9d4cc..df4916ae35 100755 --- a/ui/src/style/fonts/icomoon.svg +++ b/ui/src/style/fonts/icomoon.svg @@ -28,6 +28,7 @@ + @@ -40,7 +41,6 @@ - diff --git a/ui/src/style/fonts/icomoon.ttf b/ui/src/style/fonts/icomoon.ttf index 74200cab61..54e7da30ee 100755 Binary files a/ui/src/style/fonts/icomoon.ttf and b/ui/src/style/fonts/icomoon.ttf differ diff --git a/ui/src/style/fonts/icomoon.woff b/ui/src/style/fonts/icomoon.woff index 1086e46ab4..ef8173eb74 100755 Binary files a/ui/src/style/fonts/icomoon.woff and b/ui/src/style/fonts/icomoon.woff differ diff --git a/ui/src/style/fonts/icomoon.woff2 b/ui/src/style/fonts/icomoon.woff2 index 4e5431d7c0..7ead7563cc 100755 Binary files a/ui/src/style/fonts/icomoon.woff2 and b/ui/src/style/fonts/icomoon.woff2 differ diff --git a/ui/src/style/fonts/icon-font.scss b/ui/src/style/fonts/icon-font.scss index 2fb4e6288e..7780a293a4 100644 --- a/ui/src/style/fonts/icon-font.scss +++ b/ui/src/style/fonts/icon-font.scss @@ -32,7 +32,6 @@ &.authzero:before {content: "\e951";} &.bar-chart:before {content: "\e913";} - &.brush:before {content: "\e939";} &.caret-down:before {content: "\e902";} &.caret-left:before {content: "\e900";} &.caret-right:before {content: "\e903";} @@ -65,6 +64,7 @@ &.octagon:before {content: "\e92d";} &.okta:before {content: "\e912";} &.pause:before {content: "\e94a";} + &.play:before {content: "\e914";} &.plus:before {content: "\e90a";} &.pulse-c:before {content: "\e936";} &.refresh:before {content: "\e949";} diff --git a/ui/src/style/pages/logs-viewer.scss b/ui/src/style/pages/logs-viewer.scss index db62bbb2dd..6f31f99884 100644 --- a/ui/src/style/pages/logs-viewer.scss +++ b/ui/src/style/pages/logs-viewer.scss @@ -15,6 +15,10 @@ $logs-viewer-gutter: 60px; flex-wrap: nowrap; } +.logs-viewer-header-title { + margin-left: 10px; +} + .logs-viewer--graph-container { padding: 22px ($logs-viewer-gutter - 16px) 10px ($logs-viewer-gutter - 16px); height: $logs-viewer-graph-height; @@ -234,4 +238,13 @@ $logs-viewer-gutter: 60px; &.debug-severity { @include gradient-diag-up($g5-pepper, $g6-smoke); } +} + +// Play & Pause Toggle in Header +.nav.nav-tablist.nav-tablist-sm.logs-viewer--mode-toggle { + > li { + padding: 0; + width: 26px; + justify-content: center; + } } \ No newline at end of file