2019-10-23 13:18:41 -07:00
import React , { Component , ChangeEvent } from 'react' ;
2019-11-04 00:17:50 -08:00
import { RouteComponentProps } from '@reach/router' ;
2019-10-17 05:38:09 -07:00
import { Alert , Button , Col , Row } from 'reactstrap' ;
2019-10-27 14:03:39 -07:00
import Panel , { PanelOptions , PanelDefaultOptions } from '../Panel' ;
import { decodePanelOptionsFromQueryString , encodePanelOptionsToQueryString } from '../utils/urlParams' ;
import Checkbox from '../Checkbox' ;
2019-11-04 00:17:50 -08:00
import PathPrefixProps from '../PathPrefixProps' ;
2019-11-20 06:33:03 -08:00
import { generateID } from '../utils/func' ;
2019-10-17 05:38:09 -07:00
2019-10-26 10:50:22 -07:00
export type MetricGroup = { title : string ; items : string [ ] } ;
2019-11-20 06:33:03 -08:00
export type PanelMeta = { key : string ; options : PanelOptions ; id : string } ;
2019-10-26 10:50:22 -07:00
2019-10-17 05:38:09 -07:00
interface PanelListState {
2019-11-20 06:33:03 -08:00
panels : PanelMeta [ ] ;
2019-10-26 10:50:22 -07:00
pastQueries : string [ ] ;
2019-10-17 05:38:09 -07:00
metricNames : string [ ] ;
fetchMetricsError : string | null ;
timeDriftError : string | null ;
}
2019-11-04 00:17:50 -08:00
class PanelList extends Component < RouteComponentProps & PathPrefixProps , PanelListState > {
2019-11-20 06:33:03 -08:00
constructor ( props : RouteComponentProps & PathPrefixProps ) {
2019-10-17 05:38:09 -07:00
super ( props ) ;
this . state = {
2019-11-20 06:33:03 -08:00
panels : decodePanelOptionsFromQueryString ( window . location . search ) ,
2019-10-26 10:50:22 -07:00
pastQueries : [ ] ,
2019-10-17 05:38:09 -07:00
metricNames : [ ] ,
fetchMetricsError : null ,
timeDriftError : null ,
} ;
}
componentDidMount() {
2019-11-20 06:33:03 -08:00
! this . state . panels . length && this . addPanel ( ) ;
2019-11-04 00:17:50 -08:00
fetch ( ` ${ this . props . pathPrefix } /api/v1/label/__name__/values ` , { cache : 'no-store' } )
2019-10-28 07:02:42 -07:00
. then ( resp = > {
if ( resp . ok ) {
return resp . json ( ) ;
} else {
throw new Error ( 'Unexpected response status when fetching metric names: ' + resp . statusText ) ; // TODO extract error
}
} )
. then ( json = > {
this . setState ( { metricNames : json.data } ) ;
} )
. catch ( error = > this . setState ( { fetchMetricsError : error.message } ) ) ;
2019-10-17 05:38:09 -07:00
const browserTime = new Date ( ) . getTime ( ) / 1000 ;
2019-11-04 00:17:50 -08:00
fetch ( ` ${ this . props . pathPrefix } /api/v1/query?query=time() ` , { cache : 'no-store' } )
2019-10-28 07:02:42 -07:00
. then ( resp = > {
if ( resp . ok ) {
return resp . json ( ) ;
} else {
throw new Error ( 'Unexpected response status when fetching metric names: ' + resp . statusText ) ; // TODO extract error
}
} )
. then ( json = > {
const serverTime = json . data . result [ 0 ] ;
const delta = Math . abs ( browserTime - serverTime ) ;
if ( delta >= 30 ) {
throw new Error (
'Detected ' +
delta +
' seconds time difference between your browser and the server. Prometheus relies on accurate time and time drift might cause unexpected query results.'
) ;
}
} )
. catch ( error = > this . setState ( { timeDriftError : error.message } ) ) ;
2019-10-17 05:38:09 -07:00
window . onpopstate = ( ) = > {
const panels = decodePanelOptionsFromQueryString ( window . location . search ) ;
2019-11-20 06:33:03 -08:00
if ( panels . length > 0 ) {
this . setState ( { panels } ) ;
2019-10-17 05:38:09 -07:00
}
2019-10-28 07:02:42 -07:00
} ;
2019-10-26 13:20:52 -07:00
this . updatePastQueries ( ) ;
2019-10-17 05:38:09 -07:00
}
2019-10-23 13:18:41 -07:00
isHistoryEnabled = ( ) = > JSON . parse ( localStorage . getItem ( 'enable-query-history' ) || 'false' ) as boolean ;
getHistoryItems = ( ) = > JSON . parse ( localStorage . getItem ( 'history' ) || '[]' ) as string [ ] ;
toggleQueryHistory = ( e : ChangeEvent < HTMLInputElement > ) = > {
localStorage . setItem ( 'enable-query-history' , ` ${ e . target . checked } ` ) ;
2019-10-26 12:17:24 -07:00
this . updatePastQueries ( ) ;
2019-10-28 07:02:42 -07:00
} ;
2019-10-23 13:18:41 -07:00
2019-10-26 12:17:24 -07:00
updatePastQueries = ( ) = > {
2019-10-26 10:50:22 -07:00
this . setState ( {
2019-10-28 07:02:42 -07:00
pastQueries : this.isHistoryEnabled ( ) ? this . getHistoryItems ( ) : [ ] ,
2019-10-26 10:50:22 -07:00
} ) ;
2019-10-28 07:02:42 -07:00
} ;
2019-10-23 13:18:41 -07:00
2019-12-12 14:22:12 -08:00
handleExecuteQuery = ( query : string ) = > {
2019-10-26 12:17:24 -07:00
const isSimpleMetric = this . state . metricNames . indexOf ( query ) !== - 1 ;
2019-10-23 13:18:41 -07:00
if ( isSimpleMetric || ! query . length ) {
return ;
}
const historyItems = this . getHistoryItems ( ) ;
2019-10-28 07:02:42 -07:00
const extendedItems = historyItems . reduce (
( acc , metric ) = > {
return metric === query ? acc : [ . . . acc , metric ] ; // Prevent adding query twice.
} ,
[ query ]
) ;
2019-10-26 10:50:22 -07:00
localStorage . setItem ( 'history' , JSON . stringify ( extendedItems . slice ( 0 , 50 ) ) ) ;
2019-10-26 12:17:24 -07:00
this . updatePastQueries ( ) ;
2019-12-12 14:22:12 -08:00
this . updateURL ( ) ;
2019-10-28 07:02:42 -07:00
} ;
2019-10-23 13:18:41 -07:00
2019-11-20 06:33:03 -08:00
updateURL() {
2019-10-17 05:38:09 -07:00
const query = encodePanelOptionsToQueryString ( this . state . panels ) ;
2019-10-17 07:34:02 -07:00
window . history . pushState ( { } , '' , query ) ;
2019-10-17 05:38:09 -07:00
}
2019-11-20 06:33:03 -08:00
handleOptionsChanged = ( id : string , options : PanelOptions ) = > {
const updatedPanels = this . state . panels . map ( p = > ( id === p . id ? { . . . p , options } : p ) ) ;
2019-12-12 14:22:12 -08:00
this . setState ( { panels : updatedPanels } ) ;
2019-10-28 07:02:42 -07:00
} ;
2019-10-17 05:38:09 -07:00
2019-11-20 06:33:03 -08:00
addPanel = ( ) = > {
const { panels } = this . state ;
const nextPanels = [
. . . panels ,
{
id : generateID ( ) ,
key : ` ${ panels . length } ` ,
options : PanelDefaultOptions ,
} ,
] ;
this . setState ( { panels : nextPanels } , this . updateURL ) ;
} ;
removePanel = ( id : string ) = > {
this . setState (
{
panels : this.state.panels.reduce < PanelMeta [ ] > ( ( acc , panel ) = > {
return panel . id !== id ? [ . . . acc , { . . . panel , key : ` ${ acc . length } ` } ] : acc ;
} , [ ] ) ,
} ,
this . updateURL
) ;
2019-10-28 07:02:42 -07:00
} ;
2019-10-17 05:38:09 -07:00
render() {
2019-11-20 06:33:03 -08:00
const { metricNames , pastQueries , timeDriftError , fetchMetricsError , panels } = this . state ;
2019-11-04 00:17:50 -08:00
const { pathPrefix } = this . props ;
2019-10-17 05:38:09 -07:00
return (
< >
2019-10-23 13:18:41 -07:00
< Row className = "mb-2" >
< Checkbox
2019-10-26 10:50:22 -07:00
id = "query-history-checkbox"
wrapperStyles = { { margin : '0 0 0 15px' , alignSelf : 'center' } }
2019-10-23 13:18:41 -07:00
onChange = { this . toggleQueryHistory }
2019-10-28 07:02:42 -07:00
defaultChecked = { this . isHistoryEnabled ( ) }
>
2019-10-23 13:18:41 -07:00
Enable query history
< / Checkbox >
< / Row >
2019-10-17 05:38:09 -07:00
< Row >
< Col >
2019-10-28 07:02:42 -07:00
{ timeDriftError && (
< Alert color = "danger" >
2019-11-20 06:33:03 -08:00
< strong > Warning : < / strong > Error fetching server time : { timeDriftError }
2019-10-28 07:02:42 -07:00
< / Alert >
) }
2019-10-17 05:38:09 -07:00
< / Col >
< / Row >
< Row >
< Col >
2019-10-28 07:02:42 -07:00
{ fetchMetricsError && (
< Alert color = "danger" >
2019-11-20 06:33:03 -08:00
< strong > Warning : < / strong > Error fetching metrics list : { fetchMetricsError }
2019-10-28 07:02:42 -07:00
< / Alert >
) }
2019-10-17 05:38:09 -07:00
< / Col >
< / Row >
2019-11-20 06:33:03 -08:00
{ panels . map ( ( { id , options } ) = > (
2019-10-17 05:38:09 -07:00
< Panel
2019-12-12 14:22:12 -08:00
onExecuteQuery = { this . handleExecuteQuery }
2019-11-20 06:33:03 -08:00
key = { id }
options = { options }
onOptionsChanged = { opts = > this . handleOptionsChanged ( id , opts ) }
removePanel = { ( ) = > this . removePanel ( id ) }
2019-10-26 10:50:22 -07:00
metricNames = { metricNames }
pastQueries = { pastQueries }
2019-11-04 00:17:50 -08:00
pathPrefix = { pathPrefix }
2019-10-17 05:38:09 -07:00
/ >
2019-10-28 07:02:42 -07:00
) ) }
< Button color = "primary" className = "add-panel-btn" onClick = { this . addPanel } >
Add Panel
< / Button >
2019-10-17 05:38:09 -07:00
< / >
) ;
}
}
export default PanelList ;