Merge pull request #3529 from influxdata/log-viewer/placeholder-markup
Log Viewer Search & Filtering UIpull/10616/head
commit
955761ec3b
|
@ -26,24 +26,32 @@ class LogViewerHeader extends PureComponent<Props> {
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const {timeRange} = this.props
|
const {timeRange} = this.props
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="page-header full-width">
|
||||||
<Dropdown
|
<div className="page-header__container">
|
||||||
className="dropdown-300"
|
<div className="page-header__left">
|
||||||
items={this.sourceDropDownItems}
|
<h1 className="page-header__title">Log Viewer</h1>
|
||||||
selected={this.selectedSource}
|
</div>
|
||||||
onChoose={this.handleChooseSource}
|
<div className="page-header__right">
|
||||||
/>
|
<Dropdown
|
||||||
<Dropdown
|
className="dropdown-300"
|
||||||
className="dropdown-300"
|
items={this.sourceDropDownItems}
|
||||||
items={this.namespaceDropDownItems}
|
selected={this.selectedSource}
|
||||||
selected={this.selectedNamespace}
|
onChoose={this.handleChooseSource}
|
||||||
onChoose={this.handleChooseNamespace}
|
/>
|
||||||
/>
|
<Dropdown
|
||||||
<TimeRangeDropdown
|
className="dropdown-180"
|
||||||
onChooseTimeRange={this.handleChooseTimeRange}
|
iconName="disks"
|
||||||
selected={timeRange}
|
items={this.namespaceDropDownItems}
|
||||||
/>
|
selected={this.selectedNamespace}
|
||||||
</>
|
onChoose={this.handleChooseNamespace}
|
||||||
|
/>
|
||||||
|
<TimeRangeDropdown
|
||||||
|
onChooseTimeRange={this.handleChooseTimeRange}
|
||||||
|
selected={timeRange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import {Filter} from 'src/logs/containers/LogsPage'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
filter: Filter
|
||||||
|
onDelete: (id: string) => () => void
|
||||||
|
onToggleStatus: (id: string) => () => void
|
||||||
|
onToggleOperator: (id: string) => () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
expanded: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogsFilter extends PureComponent<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
expanded: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const {
|
||||||
|
filter: {id},
|
||||||
|
onDelete,
|
||||||
|
} = this.props
|
||||||
|
const {expanded} = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className={this.className} onMouseLeave={this.handleMouseLeave}>
|
||||||
|
{this.label}
|
||||||
|
<div className="logs-viewer--filter-remove" onClick={onDelete(id)} />
|
||||||
|
{expanded && this.renderTooltip}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get label(): JSX.Element {
|
||||||
|
const {
|
||||||
|
filter: {key, operator, value},
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
onMouseEnter={this.handleMouseEnter}
|
||||||
|
>{`${key} ${operator} ${value}`}</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get className(): string {
|
||||||
|
const {expanded} = this.state
|
||||||
|
const {
|
||||||
|
filter: {enabled},
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return classnames('logs-viewer--filter', {
|
||||||
|
active: expanded,
|
||||||
|
disabled: !enabled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMouseEnter = (): void => {
|
||||||
|
this.setState({expanded: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMouseLeave = (): void => {
|
||||||
|
this.setState({expanded: false})
|
||||||
|
}
|
||||||
|
|
||||||
|
private get renderTooltip(): JSX.Element {
|
||||||
|
const {
|
||||||
|
filter: {id, enabled, operator},
|
||||||
|
onDelete,
|
||||||
|
onToggleStatus,
|
||||||
|
onToggleOperator,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const toggleStatusText = enabled ? 'Disable' : 'Enable'
|
||||||
|
const toggleOperatorText = operator === '==' ? '!=' : '=='
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="logs-viewer--filter-tooltip">
|
||||||
|
<li onClick={onToggleStatus(id)}>{toggleStatusText}</li>
|
||||||
|
<li onClick={onToggleOperator(id)}>{toggleOperatorText}</li>
|
||||||
|
<li onClick={onDelete(id)}>Delete</li>
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LogsFilter
|
|
@ -0,0 +1,84 @@
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
import {Filter} from 'src/logs/containers/LogsPage'
|
||||||
|
import FilterBlock from 'src/logs/components/LogsFilter'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
numResults: number
|
||||||
|
filters: Filter[]
|
||||||
|
onUpdateFilters: (fitlers: Filter[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogsFilters extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {numResults} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="logs-viewer--filter-bar">
|
||||||
|
<label className="logs-viewer--results-text">
|
||||||
|
Query returned <strong>{numResults} Events</strong>
|
||||||
|
</label>
|
||||||
|
<ul className="logs-viewer--filters">{this.renderFilters}</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get renderFilters(): JSX.Element[] {
|
||||||
|
const {filters} = this.props
|
||||||
|
|
||||||
|
return filters.map(filter => (
|
||||||
|
<FilterBlock
|
||||||
|
key={filter.id}
|
||||||
|
filter={filter}
|
||||||
|
onDelete={this.handleDeleteFilter}
|
||||||
|
onToggleStatus={this.handleToggleFilterStatus}
|
||||||
|
onToggleOperator={this.handleToggleFilterOperator}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleDeleteFilter = (id: string) => (): void => {
|
||||||
|
const {filters, onUpdateFilters} = this.props
|
||||||
|
|
||||||
|
const filteredFilters = filters.filter(filter => filter.id !== id)
|
||||||
|
|
||||||
|
onUpdateFilters(filteredFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleToggleFilterStatus = (id: string) => (): void => {
|
||||||
|
const {filters, onUpdateFilters} = this.props
|
||||||
|
|
||||||
|
const filteredFilters = filters.map(filter => {
|
||||||
|
if (filter.id === id) {
|
||||||
|
return {...filter, enabled: !filter.enabled}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter
|
||||||
|
})
|
||||||
|
|
||||||
|
onUpdateFilters(filteredFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleToggleFilterOperator = (id: string) => (): void => {
|
||||||
|
const {filters, onUpdateFilters} = this.props
|
||||||
|
|
||||||
|
const filteredFilters = filters.map(filter => {
|
||||||
|
if (filter.id === id) {
|
||||||
|
return {...filter, operator: this.toggleOperator(filter.operator)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter
|
||||||
|
})
|
||||||
|
|
||||||
|
onUpdateFilters(filteredFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleOperator = (op: string): string => {
|
||||||
|
if (op === '==') {
|
||||||
|
return '!='
|
||||||
|
}
|
||||||
|
|
||||||
|
return '=='
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LogsFilters
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React, {PureComponent, ChangeEvent, KeyboardEvent} from 'react'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
searchString: string
|
||||||
|
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||||
|
onSearch: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogsSearchBar extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {searchString, onSearch, onChange} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="logs-viewer--search-bar">
|
||||||
|
<div className="logs-viewer--search-input">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search logs using Keywords or Regular Expressions..."
|
||||||
|
value={searchString}
|
||||||
|
onChange={onChange}
|
||||||
|
onKeyDown={this.handleInputKeyDown}
|
||||||
|
className="form-control input-sm"
|
||||||
|
spellCheck={false}
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
<span className="icon search" />
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-sm btn-primary" onClick={onSearch}>
|
||||||
|
<span className="icon search" />
|
||||||
|
Search
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
return this.props.onSearch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LogsSearchBar
|
|
@ -0,0 +1,17 @@
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
thing: string
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogsTableContainer extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<div className="logs-viewer--table-container">
|
||||||
|
<p>{this.props.thing}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LogsTableContainer
|
|
@ -1,22 +0,0 @@
|
||||||
import React, {PureComponent} from 'react'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
thing: string
|
|
||||||
}
|
|
||||||
|
|
||||||
class LogsTableContainer extends PureComponent<Props> {
|
|
||||||
public render() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="logs-viewer--search-container">
|
|
||||||
<p>search</p>
|
|
||||||
</div>
|
|
||||||
<div className="logs-viewer--table-container">
|
|
||||||
<p>{this.props.thing}</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LogsTableContainer
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, {Component} from 'react'
|
import React, {Component} from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||||
import timeRanges from 'src/logs/data/timeRanges'
|
import timeRanges from 'src/logs/data/timeRanges'
|
||||||
|
@ -56,16 +57,12 @@ class TimeRangeDropdown extends Component<Props, State> {
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {selected, preventCustomTimeRange, page} = this.props
|
const {selected, preventCustomTimeRange, page} = this.props
|
||||||
const {customTimeRange, isCustomTimeRangeOpen, isOpen} = this.state
|
const {customTimeRange, isCustomTimeRangeOpen} = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClickOutside onClickOutside={this.handleClickOutside}>
|
<ClickOutside onClickOutside={this.handleClickOutside}>
|
||||||
<div className="time-range-dropdown">
|
<div className="time-range-dropdown">
|
||||||
<div
|
<div className={this.dropdownClassName}>
|
||||||
className={classnames('dropdown dropdown-290', {
|
|
||||||
open: isOpen,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="btn btn-sm btn-default dropdown-toggle"
|
className="btn btn-sm btn-default dropdown-toggle"
|
||||||
onClick={this.toggleMenu}
|
onClick={this.toggleMenu}
|
||||||
|
@ -132,6 +129,21 @@ class TimeRangeDropdown extends Component<Props, State> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get dropdownClassName(): string {
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
customTimeRange: {lower, upper},
|
||||||
|
} = this.state
|
||||||
|
|
||||||
|
const absoluteTimeRange = !_.isEmpty(lower) && !_.isEmpty(upper)
|
||||||
|
|
||||||
|
return classnames('dropdown', {
|
||||||
|
'dropdown-290': absoluteTimeRange,
|
||||||
|
'dropdown-120': !absoluteTimeRange,
|
||||||
|
open: isOpen,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private findTimeRangeInputValue = ({upper, lower}: TimeRange) => {
|
private findTimeRangeInputValue = ({upper, lower}: TimeRange) => {
|
||||||
if (upper && lower) {
|
if (upper && lower) {
|
||||||
if (upper === 'now()') {
|
if (upper === 'now()') {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {PureComponent} from 'react'
|
import React, {PureComponent, ChangeEvent} from 'react'
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import {
|
import {
|
||||||
getSourceAndPopulateNamespacesAsync,
|
getSourceAndPopulateNamespacesAsync,
|
||||||
|
@ -9,12 +9,22 @@ import {
|
||||||
} from 'src/logs/actions'
|
} from 'src/logs/actions'
|
||||||
import {getSourcesAsync} from 'src/shared/actions/sources'
|
import {getSourcesAsync} from 'src/shared/actions/sources'
|
||||||
import LogViewerHeader from 'src/logs/components/LogViewerHeader'
|
import LogViewerHeader from 'src/logs/components/LogViewerHeader'
|
||||||
|
import Graph from 'src/logs/components/LogsGraph'
|
||||||
|
import Table from 'src/logs/components/LogsTable'
|
||||||
|
import SearchBar from 'src/logs/components/LogsSearchBar'
|
||||||
|
import FilterBar from 'src/logs/components/LogsFilterBar'
|
||||||
import LogViewerChart from 'src/logs/components/LogViewerChart'
|
import LogViewerChart from 'src/logs/components/LogViewerChart'
|
||||||
import GraphContainer from 'src/logs/components/LogsGraphContainer'
|
|
||||||
import TableContainer from 'src/logs/components/LogsTableContainer'
|
|
||||||
|
|
||||||
import {Source, Namespace, TimeRange} from 'src/types'
|
import {Source, Namespace, TimeRange} from 'src/types'
|
||||||
|
|
||||||
|
export interface Filter {
|
||||||
|
id: string
|
||||||
|
key: string
|
||||||
|
value: string
|
||||||
|
operator: string
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
sources: Source[]
|
sources: Source[]
|
||||||
currentSource: Source | null
|
currentSource: Source | null
|
||||||
|
@ -30,7 +40,31 @@ interface Props {
|
||||||
histogramData: object[]
|
histogramData: object[]
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogsPage extends PureComponent<Props> {
|
interface State {
|
||||||
|
searchString: string
|
||||||
|
filters: Filter[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const DUMMY_FILTERS = [
|
||||||
|
{
|
||||||
|
id: '0',
|
||||||
|
key: 'host',
|
||||||
|
value: 'prod1-rsavage.local',
|
||||||
|
operator: '==',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
class LogsPage extends PureComponent<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
searchString: '',
|
||||||
|
filters: DUMMY_FILTERS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public componentDidUpdate() {
|
public componentDidUpdate() {
|
||||||
if (!this.props.currentSource) {
|
if (!this.props.currentSource) {
|
||||||
this.props.getSource(this.props.sources[0].id)
|
this.props.getSource(this.props.sources[0].id)
|
||||||
|
@ -42,19 +76,24 @@ class LogsPage extends PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
const {searchString, filters} = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<div className="page-header full-width">
|
{this.header}
|
||||||
<div className="page-header__container">
|
|
||||||
<div className="page-header__left">
|
|
||||||
<h1 className="page-header__title">Log Viewer</h1>
|
|
||||||
</div>
|
|
||||||
<div className="page-header__right">{this.header}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="page-contents logs-viewer">
|
<div className="page-contents logs-viewer">
|
||||||
<GraphContainer>{this.chart}</GraphContainer>
|
<Graph>{this.chart}</Graph>
|
||||||
<TableContainer thing="snooo" />
|
<SearchBar
|
||||||
|
searchString={searchString}
|
||||||
|
onChange={this.handleSearchInputChange}
|
||||||
|
onSearch={this.handleSubmitSearch}
|
||||||
|
/>
|
||||||
|
<FilterBar
|
||||||
|
numResults={300}
|
||||||
|
filters={filters}
|
||||||
|
onUpdateFilters={this.handleUpdateFilters}
|
||||||
|
/>
|
||||||
|
<Table thing="snooo" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -94,6 +133,20 @@ class LogsPage extends PureComponent<Props> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleSearchInputChange = (
|
||||||
|
e: ChangeEvent<HTMLInputElement>
|
||||||
|
): void => {
|
||||||
|
this.setState({searchString: e.target.value})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSubmitSearch = (): void => {
|
||||||
|
// do the thing
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleUpdateFilters = (filters: Filter[]): void => {
|
||||||
|
this.setState({filters})
|
||||||
|
}
|
||||||
|
|
||||||
private handleChooseTimerange = (timeRange: TimeRange) => {
|
private handleChooseTimerange = (timeRange: TimeRange) => {
|
||||||
this.props.setTimeRangeAsync(timeRange)
|
this.props.setTimeRangeAsync(timeRange)
|
||||||
this.props.executeHistogramQueryAsync()
|
this.props.executeHistogramQueryAsync()
|
||||||
|
|
|
@ -174,7 +174,7 @@
|
||||||
min-width: 350px;
|
min-width: 350px;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
box-shadow: 0 0 10px 2px $g2-kevlar;
|
@extend %drop-shadow;
|
||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|
|
@ -125,3 +125,8 @@ $scrollbar-offset: 3px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shadows
|
||||||
|
%drop-shadow {
|
||||||
|
box-shadow: 0 0 10px 2px $g2-kevlar;
|
||||||
|
}
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$logs-viewer-graph-height: 240px;
|
$logs-viewer-graph-height: 240px;
|
||||||
$logs-viewer-search-height: 108px;
|
$logs-viewer-search-height: 46px;
|
||||||
|
$logs-viewer-filter-height: 42px;
|
||||||
$logs-viewer-gutter: 60px;
|
$logs-viewer-gutter: 60px;
|
||||||
|
|
||||||
.logs-viewer {
|
.logs-viewer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
flex-wrap: none;
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs-viewer--graph-container {
|
.logs-viewer--graph-container {
|
||||||
|
@ -21,18 +22,180 @@ $logs-viewer-gutter: 60px;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs-viewer--search-container {
|
.logs-viewer--search-bar {
|
||||||
padding: 20px $logs-viewer-gutter;
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
padding: 0 $logs-viewer-gutter;
|
||||||
height: $logs-viewer-search-height;
|
height: $logs-viewer-search-height;
|
||||||
background-color: $g3-castle;
|
background-color: $g3-castle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs-viewer--table-container {
|
.logs-viewer--table-container {
|
||||||
padding: 12px $logs-viewer-gutter 30px $logs-viewer-gutter;
|
padding: 12px $logs-viewer-gutter 30px $logs-viewer-gutter;
|
||||||
height: calc(100% - #{$logs-viewer-graph-height + $logs-viewer-search-height});
|
height: calc(100% - #{$logs-viewer-graph-height + $logs-viewer-search-height + $logs-viewer-filter-height});
|
||||||
background-color: $g3-castle;
|
background-color: $g3-castle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search Bar
|
||||||
|
.logs-viewer--search-input {
|
||||||
|
flex: 1 0 0;
|
||||||
|
margin-right: 8px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> span.icon.search {
|
||||||
|
font-size: 14px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 12px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: $g8-storm;
|
||||||
|
transition: color 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
> input.form-control.input-sm {
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> input.form-control.input-sm:focus + span.icon.search {
|
||||||
|
color: $c-pool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filters Bar
|
||||||
|
.logs-viewer--filter-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
@include no-user-select();
|
||||||
|
padding: 0 $logs-viewer-gutter;
|
||||||
|
height: $logs-viewer-filter-height;
|
||||||
|
background-color: $g3-castle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-viewer--results-text {
|
||||||
|
margin: 0 12px 0 33px;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $g9-mountain;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: $g15-platinum;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-viewer--filters {
|
||||||
|
flex: 1 0 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-viewer--filter {
|
||||||
|
position: relative;
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0 2px 0 8px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: $g5-pepper;
|
||||||
|
color: $g13-mist;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 2px;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
background-color: $g4-onyx;
|
||||||
|
color: $g9-mountain;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $g6-smoke;
|
||||||
|
color: $g15-platinum;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-viewer--filter-remove {
|
||||||
|
outline: none;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
border-radius: 1px;
|
||||||
|
background-color: $g8-storm;
|
||||||
|
transition: background-color 0.25s ease;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
transform: translate(-50%, -50%) rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
transform: translate(-50%, -50%) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
background-color: $c-dreamsicle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-viewer--filter-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
border-radius: $radius;
|
||||||
|
z-index: 9999;
|
||||||
|
@extend %drop-shadow;
|
||||||
|
background-color: $g4-onyx;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> li {
|
||||||
|
height: 26px;
|
||||||
|
line-height: 26px;
|
||||||
|
padding: 0 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $g11-sidewalk;
|
||||||
|
transition: background-color 0.25s ease, color 0.25s ease;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: $g5-pepper;
|
||||||
|
color: $g18-cloud;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graph
|
||||||
.logs-viewer--graph {
|
.logs-viewer--graph {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
Loading…
Reference in New Issue