2019-10-23 13:18:41 -07:00
import React , { Component , ChangeEvent } from 'react' ;
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-10-17 05:38:09 -07:00
2019-10-26 10:50:22 -07:00
export type MetricGroup = { title : string ; items : string [ ] } ;
2019-10-17 05:38:09 -07:00
interface PanelListState {
panels : {
key : string ;
options : PanelOptions ;
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 ;
}
class PanelList extends Component < any , PanelListState > {
2019-10-28 07:02:42 -07:00
private key = 0 ;
2019-10-17 05:38:09 -07:00
constructor ( props : any ) {
super ( props ) ;
const urlPanels = decodePanelOptionsFromQueryString ( window . location . search ) ;
this . state = {
2019-10-28 07:02:42 -07:00
panels :
urlPanels . length !== 0
? urlPanels
: [
{
key : this.getKey ( ) ,
options : PanelDefaultOptions ,
} ,
] ,
2019-10-26 10:50:22 -07:00
pastQueries : [ ] ,
2019-10-17 05:38:09 -07:00
metricNames : [ ] ,
fetchMetricsError : null ,
timeDriftError : null ,
} ;
}
componentDidMount() {
2019-10-28 07:02:42 -07:00
fetch ( '../../api/v1/label/__name__/values' , { cache : 'no-store' } )
. 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-10-28 07:02:42 -07:00
fetch ( '../../api/v1/query?query=time()' , { cache : 'no-store' } )
. 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 ) ;
if ( panels . length !== 0 ) {
2019-10-28 07:02:42 -07:00
this . setState ( { panels : 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
handleQueryHistory = ( 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-10-28 07:02:42 -07:00
} ;
2019-10-23 13:18:41 -07:00
2019-10-17 05:38:09 -07:00
getKey ( ) : string {
return ( this . key ++ ) . toString ( ) ;
}
handleOptionsChanged ( key : string , opts : PanelOptions ) : void {
const newPanels = this . state . panels . map ( p = > {
if ( key === p . key ) {
return {
key : key ,
options : opts ,
2019-10-28 07:02:42 -07:00
} ;
2019-10-17 05:38:09 -07:00
}
return p ;
} ) ;
2019-10-28 07:02:42 -07:00
this . setState ( { panels : newPanels } , this . updateURL ) ;
2019-10-17 05:38:09 -07:00
}
updateURL ( ) : void {
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
}
addPanel = ( ) : void = > {
const panels = this . state . panels . slice ( ) ;
panels . push ( {
key : this.getKey ( ) ,
options : PanelDefaultOptions ,
} ) ;
2019-10-28 07:02:42 -07:00
this . setState ( { panels : panels } , this . updateURL ) ;
} ;
2019-10-17 05:38:09 -07:00
removePanel = ( key : string ) : void = > {
const panels = this . state . panels . filter ( panel = > {
return panel . key !== key ;
} ) ;
2019-10-28 07:02:42 -07:00
this . setState ( { panels : panels } , this . updateURL ) ;
} ;
2019-10-17 05:38:09 -07:00
render() {
2019-10-26 10:50:22 -07:00
const { metricNames , pastQueries , timeDriftError , fetchMetricsError } = this . state ;
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" >
< strong > Warning : < / strong > Error fetching server time : { this . state . timeDriftError }
< / 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" >
< strong > Warning : < / strong > Error fetching metrics list : { this . state . fetchMetricsError }
< / Alert >
) }
2019-10-17 05:38:09 -07:00
< / Col >
< / Row >
2019-10-28 07:02:42 -07:00
{ this . state . panels . map ( p = > (
2019-10-17 05:38:09 -07:00
< Panel
2019-10-23 13:18:41 -07:00
onExecuteQuery = { this . handleQueryHistory }
2019-10-17 05:38:09 -07:00
key = { p . key }
options = { p . options }
onOptionsChanged = { ( opts : PanelOptions ) = > this . handleOptionsChanged ( p . key , opts ) }
removePanel = { ( ) = > this . removePanel ( p . key ) }
2019-10-26 10:50:22 -07:00
metricNames = { metricNames }
pastQueries = { pastQueries }
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 ;