Merge pull request #3508 from influxdata/features/log-viewer-scaffold
Add log viewer page with headerpull/10616/head
commit
f362d2046d
|
@ -26,6 +26,7 @@ import {StatusPage} from 'src/status'
|
|||
import {HostsPage, HostPage} from 'src/hosts'
|
||||
import DataExplorerPage from 'src/data_explorer'
|
||||
import {DashboardsPage, DashboardPage} from 'src/dashboards'
|
||||
import {LogsPage} from 'src/logs'
|
||||
import AlertsApp from 'src/alerts'
|
||||
import {
|
||||
KapacitorPage,
|
||||
|
@ -119,6 +120,9 @@ class Root extends PureComponent<{}, State> {
|
|||
<Route path="/" component={UserIsAuthenticated(CheckSources)} />
|
||||
<Route path="/login" component={UserIsNotAuthenticated(Login)} />
|
||||
<Route path="/purgatory" component={UserIsAuthenticated(Purgatory)} />
|
||||
<Route component={UserIsAuthenticated(App)}>
|
||||
<Route path="/logs" component={LogsPage} />
|
||||
</Route>
|
||||
<Route
|
||||
path="/sources/new"
|
||||
component={UserIsAuthenticated(SourcePage)}
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
import {Source, Namespace, TimeRange} from 'src/types'
|
||||
import {getSource} from 'src/shared/apis'
|
||||
import {getDatabasesWithRetentionPolicies} from 'src/shared/apis/databases'
|
||||
import {get} from 'src/utils/wrappers'
|
||||
|
||||
export enum ActionTypes {
|
||||
SetSource = 'LOGS_SET_SOURCE',
|
||||
SetNamespaces = 'LOGS_SET_NAMESPACES',
|
||||
SetTimeRange = 'LOGS_SET_TIMERANGE',
|
||||
SetNamespace = 'LOGS_SET_NAMESPACE',
|
||||
}
|
||||
|
||||
interface SetSourceAction {
|
||||
type: ActionTypes.SetSource
|
||||
payload: {
|
||||
source: Source
|
||||
}
|
||||
}
|
||||
|
||||
interface SetNamespacesAction {
|
||||
type: ActionTypes.SetNamespaces
|
||||
payload: {
|
||||
namespaces: Namespace[]
|
||||
}
|
||||
}
|
||||
|
||||
interface SetNamespaceAction {
|
||||
type: ActionTypes.SetNamespace
|
||||
payload: {
|
||||
namespace: Namespace
|
||||
}
|
||||
}
|
||||
|
||||
interface SetTimeRangeAction {
|
||||
type: ActionTypes.SetTimeRange
|
||||
payload: {
|
||||
timeRange: TimeRange
|
||||
}
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| SetSourceAction
|
||||
| SetNamespacesAction
|
||||
| SetTimeRangeAction
|
||||
| SetNamespaceAction
|
||||
|
||||
export const setSource = (source: Source): SetSourceAction => ({
|
||||
type: ActionTypes.SetSource,
|
||||
payload: {
|
||||
source,
|
||||
},
|
||||
})
|
||||
|
||||
export const setNamespace = (namespace: Namespace): SetNamespaceAction => ({
|
||||
type: ActionTypes.SetNamespace,
|
||||
payload: {
|
||||
namespace,
|
||||
},
|
||||
})
|
||||
|
||||
export const setNamespaces = (
|
||||
namespaces: Namespace[]
|
||||
): SetNamespacesAction => ({
|
||||
type: ActionTypes.SetNamespaces,
|
||||
payload: {
|
||||
namespaces,
|
||||
},
|
||||
})
|
||||
|
||||
export const setTimeRange = (timeRange: TimeRange): SetTimeRangeAction => ({
|
||||
type: ActionTypes.SetTimeRange,
|
||||
payload: {
|
||||
timeRange,
|
||||
},
|
||||
})
|
||||
|
||||
export const getSourceAsync = (sourceID: string) => async dispatch => {
|
||||
const response = await getSource(sourceID)
|
||||
const source = response.data
|
||||
|
||||
if (source) {
|
||||
const namespaces = await getDatabasesWithRetentionPolicies(
|
||||
get(source, 'links.proxy', '')
|
||||
)
|
||||
|
||||
if (namespaces && namespaces.length > 0) {
|
||||
dispatch(setNamespace(namespaces[0]))
|
||||
}
|
||||
|
||||
dispatch(setNamespaces(namespaces))
|
||||
dispatch(setSource(source))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
import _ from 'lodash'
|
||||
import React, {PureComponent} from 'react'
|
||||
import {Source, Namespace} from 'src/types'
|
||||
import Dropdown from 'src/shared/components/Dropdown'
|
||||
import TimeRangeDropdown from 'src/logs/components/TimeRangeDropdown'
|
||||
|
||||
import {TimeRange} from 'src/types'
|
||||
|
||||
interface SourceItem {
|
||||
id: string
|
||||
text: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
currentNamespace: Namespace
|
||||
availableSources: Source[]
|
||||
currentSource: Source | null
|
||||
currentNamespaces: Namespace[]
|
||||
timeRange: TimeRange
|
||||
onChooseSource: (sourceID: string) => void
|
||||
onChooseNamespace: (namespace: Namespace) => void
|
||||
onChooseTimerange: (timeRange: TimeRange) => void
|
||||
}
|
||||
|
||||
class LogViewerHeader extends PureComponent<Props> {
|
||||
public render(): JSX.Element {
|
||||
const {timeRange} = this.props
|
||||
return (
|
||||
<>
|
||||
<Dropdown
|
||||
className="dropdown-300"
|
||||
items={this.sourceDropDownItems}
|
||||
selected={this.selectedSource}
|
||||
onChoose={this.handleChooseSource}
|
||||
/>
|
||||
<Dropdown
|
||||
className="dropdown-300"
|
||||
items={this.namespaceDropDownItems}
|
||||
selected={this.selectedNamespace}
|
||||
onChoose={this.handleChooseNamespace}
|
||||
/>
|
||||
<TimeRangeDropdown
|
||||
onChooseTimeRange={this.handleChooseTimeRange}
|
||||
selected={timeRange}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
private handleChooseTimeRange = (timerange: TimeRange) => {
|
||||
this.props.onChooseTimerange(timerange)
|
||||
}
|
||||
|
||||
private handleChooseSource = (item: SourceItem) => {
|
||||
this.props.onChooseSource(item.id)
|
||||
}
|
||||
|
||||
private handleChooseNamespace = (namespace: Namespace) => {
|
||||
this.props.onChooseNamespace(namespace)
|
||||
}
|
||||
|
||||
private get selectedSource(): string {
|
||||
if (_.isEmpty(this.sourceDropDownItems)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return this.sourceDropDownItems[0].text
|
||||
}
|
||||
|
||||
private get selectedNamespace(): string {
|
||||
const {currentNamespace} = this.props
|
||||
|
||||
if (!currentNamespace) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return `${currentNamespace.database}.${currentNamespace.retentionPolicy}`
|
||||
}
|
||||
|
||||
private get namespaceDropDownItems() {
|
||||
const {currentNamespaces} = this.props
|
||||
|
||||
return currentNamespaces.map(namespace => {
|
||||
return {
|
||||
text: `${namespace.database}.${namespace.retentionPolicy}`,
|
||||
...namespace,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private get sourceDropDownItems(): SourceItem[] {
|
||||
const {availableSources} = this.props
|
||||
|
||||
return availableSources.map(source => {
|
||||
return {
|
||||
text: `${source.name} @ ${source.url}`,
|
||||
id: source.id,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default LogViewerHeader
|
|
@ -0,0 +1,174 @@
|
|||
import React, {Component} from 'react'
|
||||
import classnames from 'classnames'
|
||||
import moment from 'moment'
|
||||
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
import timeRanges from 'src/logs/data/timeRanges'
|
||||
import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {ClickOutside} from 'src/shared/components/ClickOutside'
|
||||
import CustomTimeRange from 'src/shared/components/CustomTimeRange'
|
||||
|
||||
import {TimeRange} from 'src/types'
|
||||
|
||||
const dateFormat = 'YYYY-MM-DD HH:mm'
|
||||
const emptyTime = {lower: '', upper: ''}
|
||||
const format = t => moment(t.replace(/\'/g, '')).format(dateFormat)
|
||||
|
||||
interface Props {
|
||||
selected: {
|
||||
lower: string
|
||||
upper?: string
|
||||
}
|
||||
|
||||
onChooseTimeRange: (timeRange: TimeRange) => void
|
||||
preventCustomTimeRange?: boolean
|
||||
page?: string
|
||||
}
|
||||
|
||||
interface State {
|
||||
autobind: boolean
|
||||
isOpen: boolean
|
||||
isCustomTimeRangeOpen: boolean
|
||||
customTimeRange: TimeRange
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class TimeRangeDropdown extends Component<Props, State> {
|
||||
public static defaultProps = {
|
||||
page: 'default',
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
const {lower, upper} = props.selected
|
||||
|
||||
const isTimeValid = moment(upper).isValid() && moment(lower).isValid()
|
||||
const customTimeRange = isTimeValid ? {lower, upper} : emptyTime
|
||||
|
||||
this.state = {
|
||||
autobind: false,
|
||||
isOpen: false,
|
||||
isCustomTimeRangeOpen: false,
|
||||
customTimeRange,
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {selected, preventCustomTimeRange, page} = this.props
|
||||
const {customTimeRange, isCustomTimeRangeOpen, isOpen} = this.state
|
||||
|
||||
return (
|
||||
<ClickOutside onClickOutside={this.handleClickOutside}>
|
||||
<div className="time-range-dropdown">
|
||||
<div
|
||||
className={classnames('dropdown dropdown-290', {
|
||||
open: isOpen,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className="btn btn-sm btn-default dropdown-toggle"
|
||||
onClick={this.toggleMenu}
|
||||
>
|
||||
<span className="icon clock" />
|
||||
<span className="dropdown-selected">
|
||||
{this.findTimeRangeInputValue(selected)}
|
||||
</span>
|
||||
<span className="caret" />
|
||||
</div>
|
||||
<ul className="dropdown-menu">
|
||||
<FancyScrollbar
|
||||
autoHide={false}
|
||||
autoHeight={true}
|
||||
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
|
||||
>
|
||||
{preventCustomTimeRange ? null : (
|
||||
<div>
|
||||
<li className="dropdown-header">Absolute Time</li>
|
||||
<li
|
||||
className={
|
||||
isCustomTimeRangeOpen
|
||||
? 'active dropdown-item custom-timerange'
|
||||
: 'dropdown-item custom-timerange'
|
||||
}
|
||||
>
|
||||
<a href="#" onClick={this.showCustomTimeRange}>
|
||||
Date Picker
|
||||
</a>
|
||||
</li>
|
||||
</div>
|
||||
)}
|
||||
<li className="dropdown-header">
|
||||
{preventCustomTimeRange ? '' : 'Relative '}Time
|
||||
</li>
|
||||
{timeRanges.map(item => {
|
||||
return (
|
||||
<li className="dropdown-item" key={item.menuOption}>
|
||||
<a href="#" onClick={this.handleSelection(item)}>
|
||||
{item.menuOption}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</FancyScrollbar>
|
||||
</ul>
|
||||
</div>
|
||||
{isCustomTimeRangeOpen ? (
|
||||
<ClickOutside onClickOutside={this.handleCloseCustomTimeRange}>
|
||||
<div className="custom-time--overlay">
|
||||
<CustomTimeRange
|
||||
onApplyTimeRange={this.handleApplyCustomTimeRange}
|
||||
timeRange={customTimeRange}
|
||||
onClose={this.handleCloseCustomTimeRange}
|
||||
isVisible={isCustomTimeRangeOpen}
|
||||
timeInterval={60}
|
||||
page={page}
|
||||
/>
|
||||
</div>
|
||||
</ClickOutside>
|
||||
) : null}
|
||||
</div>
|
||||
</ClickOutside>
|
||||
)
|
||||
}
|
||||
|
||||
private findTimeRangeInputValue = ({upper, lower}: TimeRange) => {
|
||||
if (upper && lower) {
|
||||
if (upper === 'now()') {
|
||||
return `${format(lower)} - Now`
|
||||
}
|
||||
|
||||
return `${format(lower)} - ${format(upper)}`
|
||||
}
|
||||
|
||||
const selected = timeRanges.find(range => range.lower === lower)
|
||||
return selected ? selected.inputValue : 'Custom'
|
||||
}
|
||||
|
||||
private handleClickOutside = () => {
|
||||
this.setState({isOpen: false})
|
||||
}
|
||||
|
||||
private handleSelection = timeRange => () => {
|
||||
this.props.onChooseTimeRange(timeRange)
|
||||
this.setState({customTimeRange: emptyTime, isOpen: false})
|
||||
}
|
||||
|
||||
private toggleMenu = () => {
|
||||
this.setState({isOpen: !this.state.isOpen})
|
||||
}
|
||||
|
||||
private showCustomTimeRange = () => {
|
||||
this.setState({isCustomTimeRangeOpen: true})
|
||||
}
|
||||
|
||||
private handleApplyCustomTimeRange = customTimeRange => {
|
||||
this.props.onChooseTimeRange({...customTimeRange})
|
||||
this.setState({customTimeRange, isOpen: false})
|
||||
}
|
||||
|
||||
private handleCloseCustomTimeRange = () => {
|
||||
this.setState({isCustomTimeRangeOpen: false})
|
||||
}
|
||||
}
|
||||
export default TimeRangeDropdown
|
|
@ -0,0 +1,101 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {getSourceAsync, setTimeRange, setNamespace} from 'src/logs/actions'
|
||||
import {getSourcesAsync} from 'src/shared/actions/sources'
|
||||
import {Source, Namespace, TimeRange} from 'src/types'
|
||||
import LogViewerHeader from 'src/logs/components/LogViewerHeader'
|
||||
|
||||
interface Props {
|
||||
sources: Source[]
|
||||
currentSource: Source | null
|
||||
currentNamespaces: Namespace[]
|
||||
currentNamespace: Namespace
|
||||
getSource: (sourceID: string) => void
|
||||
getSources: () => void
|
||||
setTimeRange: (timeRange: TimeRange) => void
|
||||
setNamespace: (namespace: Namespace) => void
|
||||
timeRange: TimeRange
|
||||
}
|
||||
|
||||
class LogsPage extends PureComponent<Props> {
|
||||
public componentDidUpdate() {
|
||||
if (!this.props.currentSource) {
|
||||
this.props.getSource(this.props.sources[0].id)
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.props.getSources()
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="page hosts-list-page">
|
||||
<div className="page-header full-width">
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
private get header(): JSX.Element {
|
||||
const {
|
||||
sources,
|
||||
currentSource,
|
||||
currentNamespaces,
|
||||
timeRange,
|
||||
currentNamespace,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<LogViewerHeader
|
||||
availableSources={sources}
|
||||
timeRange={timeRange}
|
||||
onChooseSource={this.handleChooseSource}
|
||||
onChooseNamespace={this.handleChooseNamespace}
|
||||
onChooseTimerange={this.handleChooseTimerange}
|
||||
currentSource={currentSource}
|
||||
currentNamespaces={currentNamespaces}
|
||||
currentNamespace={currentNamespace}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private handleChooseTimerange = (timeRange: TimeRange) => {
|
||||
this.props.setTimeRange(timeRange)
|
||||
}
|
||||
|
||||
private handleChooseSource = (sourceID: string) => {
|
||||
this.props.getSource(sourceID)
|
||||
}
|
||||
|
||||
private handleChooseNamespace = (namespace: Namespace) => {
|
||||
// Do flip
|
||||
this.props.setNamespace(namespace)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({
|
||||
sources,
|
||||
logs: {currentSource, currentNamespaces, timeRange, currentNamespace},
|
||||
}) => ({
|
||||
sources,
|
||||
currentSource,
|
||||
currentNamespaces,
|
||||
timeRange,
|
||||
currentNamespace,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = {
|
||||
getSource: getSourceAsync,
|
||||
getSources: getSourcesAsync,
|
||||
setTimeRange,
|
||||
setNamespace,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(LogsPage)
|
|
@ -0,0 +1,74 @@
|
|||
export default [
|
||||
{
|
||||
defaultGroupBy: '10s',
|
||||
seconds: 60,
|
||||
inputValue: 'Past 1m',
|
||||
lower: 'now() - 1m',
|
||||
upper: null,
|
||||
menuOption: 'Past 1m',
|
||||
},
|
||||
{
|
||||
defaultGroupBy: '10s',
|
||||
seconds: 300,
|
||||
inputValue: 'Past 5m',
|
||||
lower: 'now() - 5m',
|
||||
upper: null,
|
||||
menuOption: 'Past 5m',
|
||||
},
|
||||
{
|
||||
defaultGroupBy: '1m',
|
||||
seconds: 900,
|
||||
inputValue: 'Past 15m',
|
||||
lower: 'now() - 15m',
|
||||
upper: null,
|
||||
menuOption: 'Past 15m',
|
||||
},
|
||||
{
|
||||
defaultGroupBy: '1m',
|
||||
seconds: 1800,
|
||||
inputValue: 'Past 30m',
|
||||
lower: 'now() - 30m',
|
||||
upper: null,
|
||||
menuOption: 'Past 30m',
|
||||
},
|
||||
{
|
||||
defaultGroupBy: '1m',
|
||||
seconds: 3600,
|
||||
inputValue: 'Past 1h',
|
||||
lower: 'now() - 1h',
|
||||
upper: null,
|
||||
menuOption: 'Past 1h',
|
||||
},
|
||||
{
|
||||
defaultGroupBy: '1m',
|
||||
seconds: 5200,
|
||||
inputValue: 'Past 2h',
|
||||
lower: 'now() - 2h',
|
||||
upper: null,
|
||||
menuOption: 'Past 2h',
|
||||
},
|
||||
{
|
||||
defaultGroupBy: '1m',
|
||||
seconds: 21600,
|
||||
inputValue: 'Past 6h',
|
||||
lower: 'now() - 6h',
|
||||
upper: null,
|
||||
menuOption: 'Past 6h',
|
||||
},
|
||||
{
|
||||
defaultGroupBy: '5m',
|
||||
seconds: 43200,
|
||||
inputValue: 'Past 12h',
|
||||
lower: 'now() - 12h',
|
||||
upper: null,
|
||||
menuOption: 'Past 12h',
|
||||
},
|
||||
{
|
||||
defaultGroupBy: '10m',
|
||||
seconds: 86400,
|
||||
inputValue: 'Past 24h',
|
||||
lower: 'now() - 24h',
|
||||
upper: null,
|
||||
menuOption: 'Past 24h',
|
||||
},
|
||||
]
|
|
@ -0,0 +1,3 @@
|
|||
import LogsPage from 'src/logs/containers/LogsPage'
|
||||
|
||||
export {LogsPage}
|
|
@ -0,0 +1,31 @@
|
|||
import {Source, Namespace, TimeRange} from 'src/types'
|
||||
import {ActionTypes, Action} from 'src/logs/actions'
|
||||
|
||||
interface LogsState {
|
||||
currentSource: Source | null
|
||||
currentNamespaces: Namespace[]
|
||||
currentNamespace: Namespace | null
|
||||
timeRange: TimeRange
|
||||
}
|
||||
|
||||
const defaultState = {
|
||||
currentSource: null,
|
||||
currentNamespaces: [],
|
||||
timeRange: {lower: 'now() - 1m', upper: null},
|
||||
currentNamespace: null,
|
||||
}
|
||||
|
||||
export default (state: LogsState = defaultState, action: Action) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.SetSource:
|
||||
return {...state, currentSource: action.payload.source}
|
||||
case ActionTypes.SetNamespaces:
|
||||
return {...state, currentNamespaces: action.payload.namespaces}
|
||||
case ActionTypes.SetTimeRange:
|
||||
return {...state, timeRange: action.payload.timeRange}
|
||||
case ActionTypes.SetNamespace:
|
||||
return {...state, currentNamespace: action.payload.namespace}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import _ from 'lodash'
|
||||
|
||||
import {showDatabases, showRetentionPolicies} from 'src/shared/apis/metaQuery'
|
||||
import showDatabasesParser from 'src/shared/parsing/showDatabases'
|
||||
import showRetentionPoliciesParser from 'src/shared/parsing/showRetentionPolicies'
|
||||
|
||||
import {Namespace} from 'src/types/query'
|
||||
|
||||
export const getDatabasesWithRetentionPolicies = async (
|
||||
proxy: string
|
||||
): Promise<Namespace[]> => {
|
||||
try {
|
||||
const {data} = await showDatabases(proxy)
|
||||
const {databases} = showDatabasesParser(data)
|
||||
const rps = await showRetentionPolicies(proxy, databases)
|
||||
const namespaces = rps.data.results.reduce((acc, result, index) => {
|
||||
const {retentionPolicies} = showRetentionPoliciesParser(result)
|
||||
|
||||
const dbrp = retentionPolicies.map(rp => ({
|
||||
database: databases[index],
|
||||
retentionPolicy: rp.name,
|
||||
}))
|
||||
|
||||
return [...acc, ...dbrp]
|
||||
}, [])
|
||||
|
||||
const sorted = _.sortBy(namespaces, ({database}: Namespace) =>
|
||||
database.toLowerCase()
|
||||
)
|
||||
|
||||
return sorted
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ class CustomTimeRange extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {timeRange} = this.props
|
||||
const {timeRange, timeInterval} = this.props
|
||||
|
||||
const lower = rome(this.lower, {
|
||||
dateValidator: rome.val.beforeEq(this.upper),
|
||||
|
@ -26,6 +26,7 @@ class CustomTimeRange extends Component {
|
|||
autoClose: false,
|
||||
autoHideOnBlur: false,
|
||||
autoHideOnClick: false,
|
||||
timeInterval,
|
||||
})
|
||||
|
||||
const upper = rome(this.upper, {
|
||||
|
@ -35,6 +36,7 @@ class CustomTimeRange extends Component {
|
|||
initialValue: this.getInitialDate(timeRange.upper),
|
||||
autoHideOnBlur: false,
|
||||
autoHideOnClick: false,
|
||||
timeInterval,
|
||||
})
|
||||
|
||||
this.lowerCal = lower
|
||||
|
@ -239,7 +241,11 @@ class CustomTimeRange extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {func, shape, string} = PropTypes
|
||||
CustomTimeRange.defaultProps = {
|
||||
timeInterval: 1800,
|
||||
}
|
||||
|
||||
const {func, shape, string, number} = PropTypes
|
||||
|
||||
CustomTimeRange.propTypes = {
|
||||
onApplyTimeRange: func.isRequired,
|
||||
|
@ -247,6 +253,7 @@ CustomTimeRange.propTypes = {
|
|||
lower: string.isRequired,
|
||||
upper: string,
|
||||
}).isRequired,
|
||||
timeInterval: number,
|
||||
onClose: func,
|
||||
page: string,
|
||||
}
|
||||
|
|
|
@ -6,14 +6,12 @@ import _ from 'lodash'
|
|||
import {QueryConfig, Source} from 'src/types'
|
||||
import {Namespace} from 'src/types/query'
|
||||
|
||||
import {showDatabases, showRetentionPolicies} from 'src/shared/apis/metaQuery'
|
||||
import showDatabasesParser from 'src/shared/parsing/showDatabases'
|
||||
import showRetentionPoliciesParser from 'src/shared/parsing/showRetentionPolicies'
|
||||
|
||||
import DatabaseListItem from 'src/shared/components/DatabaseListItem'
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
import {getDatabasesWithRetentionPolicies} from 'src/shared/apis/databases'
|
||||
|
||||
interface DatabaseListProps {
|
||||
query: QueryConfig
|
||||
querySource?: Source
|
||||
|
@ -80,24 +78,7 @@ class DatabaseList extends Component<DatabaseListProps, DatabaseListState> {
|
|||
const proxy = _.get(querySource, ['links', 'proxy'], source.links.proxy)
|
||||
|
||||
try {
|
||||
const {data} = await showDatabases(proxy)
|
||||
const {databases} = showDatabasesParser(data)
|
||||
const rps = await showRetentionPolicies(proxy, databases)
|
||||
const namespaces = rps.data.results.reduce((acc, result, index) => {
|
||||
const {retentionPolicies} = showRetentionPoliciesParser(result)
|
||||
|
||||
const dbrp = retentionPolicies.map(rp => ({
|
||||
database: databases[index],
|
||||
retentionPolicy: rp.name,
|
||||
}))
|
||||
|
||||
return [...acc, ...dbrp]
|
||||
}, [])
|
||||
|
||||
const sorted = _.sortBy(namespaces, ({database}: Namespace) =>
|
||||
database.toLowerCase()
|
||||
)
|
||||
|
||||
const sorted = await getDatabasesWithRetentionPolicies(proxy)
|
||||
this.setState({namespaces: sorted})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import _ from 'lodash'
|
||||
import React, {PureComponent} from 'react'
|
||||
import {withRouter, Link} from 'react-router'
|
||||
import {connect} from 'react-redux'
|
||||
|
@ -14,10 +15,13 @@ import {
|
|||
} from 'src/side_nav/components/NavItems'
|
||||
|
||||
import {DEFAULT_HOME_PAGE} from 'src/shared/constants'
|
||||
import {Params, Location, Links, Me} from 'src/types/sideNav'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
import {Params, Location, Links, Me} from 'src/types/sideNav'
|
||||
import {Source} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
sources: Source[]
|
||||
params: Params
|
||||
location: Location
|
||||
isHidden: boolean
|
||||
|
@ -42,9 +46,13 @@ class SideNav extends PureComponent<Props> {
|
|||
logoutLink,
|
||||
links,
|
||||
me,
|
||||
sources = [],
|
||||
} = this.props
|
||||
|
||||
const sourcePrefix = `/sources/${sourceID}`
|
||||
const defaultSource = sources.find(s => s.default)
|
||||
const id = sourceID || _.get(defaultSource, 'id', 0)
|
||||
|
||||
const sourcePrefix = `/sources/${id}`
|
||||
const dataExplorerLink = `${sourcePrefix}/chronograf/data-explorer`
|
||||
|
||||
const isDefaultPage = location.split('/').includes(DEFAULT_HOME_PAGE)
|
||||
|
@ -69,6 +77,16 @@ class SideNav extends PureComponent<Props> {
|
|||
>
|
||||
<NavHeader link={`${sourcePrefix}/hosts`} title="Host List" />
|
||||
</NavBlock>
|
||||
<FeatureFlag name="log-viewer">
|
||||
<NavBlock
|
||||
highlightWhen={['logs']}
|
||||
icon="cubo-node"
|
||||
link={'/logs'}
|
||||
location={location}
|
||||
>
|
||||
<NavHeader link={'/logs'} title="Log Viewer" />
|
||||
</NavBlock>
|
||||
</FeatureFlag>
|
||||
<NavBlock
|
||||
highlightWhen={['data-explorer', 'delorean']}
|
||||
icon="graphline"
|
||||
|
@ -164,12 +182,14 @@ class SideNav extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
const mapStateToProps = ({
|
||||
sources,
|
||||
auth: {isUsingAuth, logoutLink, me},
|
||||
app: {
|
||||
ephemeral: {inPresentationMode},
|
||||
},
|
||||
links,
|
||||
}) => ({
|
||||
sources,
|
||||
isHidden: inPresentationMode,
|
||||
isUsingAuth,
|
||||
logoutLink,
|
||||
|
|
|
@ -7,6 +7,7 @@ import errorsMiddleware from 'shared/middleware/errors'
|
|||
import {resizeLayout} from 'shared/middleware/resizeLayout'
|
||||
import {queryStringConfig} from 'shared/middleware/queryStringConfig'
|
||||
import statusReducers from 'src/status/reducers'
|
||||
import logsReducer from 'src/logs/reducers'
|
||||
import sharedReducers from 'shared/reducers'
|
||||
import dataExplorerReducers from 'src/data_explorer/reducers'
|
||||
import adminReducers from 'src/admin/reducers'
|
||||
|
@ -29,6 +30,7 @@ const rootReducer = combineReducers({
|
|||
cellEditorOverlay,
|
||||
overlayTechnology,
|
||||
dashTimeV1,
|
||||
logs: logsReducer,
|
||||
routing: routerReducer,
|
||||
services: servicesReducer,
|
||||
script: scriptReducer,
|
||||
|
|
Loading…
Reference in New Issue