2020-09-18 23:30:17 +00:00
class Interface extends React . Component {
constructor ( props ) {
super ( props ) ;
this . state = {
realtime : this . getCookie ( ) ,
resetting : false ,
opstate : props . opstate
}
this . polling = false ;
this . isSecure = ( window . location . protocol === 'https:' ) ;
if ( this . getCookie ( ) ) {
this . startTimer ( ) ;
}
}
startTimer = ( ) => {
this . setState ( { realtime : true } )
this . polling = setInterval ( ( ) => {
this . setState ( { fetching : true , resetting : false } ) ;
2020-12-16 11:10:48 +00:00
axios . get ( window . location . pathname , { time : Date . now ( ) } )
2020-09-18 23:30:17 +00:00
. then ( ( response ) => {
this . setState ( { opstate : response . data } ) ;
} ) ;
} , this . props . realtimeRefresh * 1000 ) ;
}
stopTimer = ( ) => {
this . setState ( { realtime : false , resetting : false } )
clearInterval ( this . polling )
}
realtimeHandler = ( ) => {
const realtime = ! this . state . realtime ;
if ( ! realtime ) {
this . stopTimer ( ) ;
this . removeCookie ( ) ;
} else {
this . startTimer ( ) ;
this . setCookie ( ) ;
}
}
resetHandler = ( ) => {
if ( this . state . realtime ) {
this . setState ( { resetting : true } ) ;
2020-12-16 11:10:48 +00:00
axios . get ( window . location . pathname , { params : { reset : 1 } } )
2020-09-18 23:30:17 +00:00
. then ( ( response ) => {
console . log ( 'success: ' , response . data ) ;
} ) ;
} else {
window . location . href = '?reset=1' ;
}
}
setCookie = ( ) => {
let d = new Date ( ) ;
d . setTime ( d . getTime ( ) + ( this . props . cookie . ttl * 86400000 ) ) ;
document . cookie = ` ${ this . props . cookie . name } =true;expires= ${ d . toUTCString ( ) } ;path=/ ${ this . isSecure ? ';secure' : '' } ` ;
}
removeCookie = ( ) => {
document . cookie = ` ${ this . props . cookie . name } =;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/ ${ this . isSecure ? ';secure' : '' } ` ;
}
getCookie = ( ) => {
const v = document . cookie . match ( ` (^|;) ? ${ this . props . cookie . name } =([^;]*)(;| $ ) ` ) ;
return v ? ! ! v [ 2 ] : false ;
} ;
2022-07-17 01:43:40 +00:00
txt = ( text , ... args ) => {
if ( this . props . language !== null && this . props . language . hasOwnProperty ( text ) && this . props . language [ text ] ) {
text = this . props . language [ text ] ;
}
args . forEach ( ( arg , i ) => {
text = text . replaceAll ( ` { ${ i } } ` , arg ) ;
} ) ;
return text ;
} ;
2020-09-18 23:30:17 +00:00
render ( ) {
const { opstate , realtimeRefresh , ... otherProps } = this . props ;
return (
< >
< header >
< MainNavigation { ...otherProps }
opstate = { this . state . opstate }
realtime = { this . state . realtime }
resetting = { this . state . resetting }
realtimeHandler = { this . realtimeHandler }
resetHandler = { this . resetHandler }
2022-07-17 01:43:40 +00:00
txt = { this . txt }
2020-09-18 23:30:17 +00:00
/ >
< / header >
< Footer version = { this . props . opstate . version . gui } / >
< / >
) ;
}
}
function MainNavigation ( props ) {
return (
< nav className = "main-nav" >
< Tabs >
2022-07-17 01:43:40 +00:00
< div label = { props . txt ( "Overview" ) } tabId = "overview" tabIndex = { 1 } >
2020-09-18 23:30:17 +00:00
< OverviewCounts
overview = { props . opstate . overview }
highlight = { props . highlight }
useCharts = { props . useCharts }
2022-07-17 01:43:40 +00:00
txt = { props . txt }
2020-09-18 23:30:17 +00:00
/ >
< div id = "info" className = "tab-content-overview-info" >
< GeneralInfo
start = { props . opstate . overview && props . opstate . overview . readable . start _time || null }
reset = { props . opstate . overview && props . opstate . overview . readable . last _restart _time || null }
version = { props . opstate . version }
2022-07-17 01:43:40 +00:00
txt = { props . txt }
2020-09-18 23:30:17 +00:00
/ >
< Directives
directives = { props . opstate . directives }
2022-07-17 01:43:40 +00:00
txt = { props . txt }
2020-09-18 23:30:17 +00:00
/ >
< Functions
functions = { props . opstate . functions }
2022-07-17 01:43:40 +00:00
txt = { props . txt }
2020-09-18 23:30:17 +00:00
/ >
< / div >
< / div >
{
props . allow . filelist &&
2022-07-17 01:43:40 +00:00
< div label = { props . txt ( "Cached" ) } tabId = "cached" tabIndex = { 2 } >
2020-09-18 23:30:17 +00:00
< CachedFiles
perPageLimit = { props . perPageLimit }
allFiles = { props . opstate . files }
searchTerm = { props . searchTerm }
debounceRate = { props . debounceRate }
allow = { { fileList : props . allow . filelist , invalidate : props . allow . invalidate } }
realtime = { props . realtime }
2022-07-17 01:43:40 +00:00
txt = { props . txt }
2020-09-18 23:30:17 +00:00
/ >
< / div >
}
{
( props . allow . filelist && props . opstate . blacklist . length &&
2022-07-17 01:43:40 +00:00
< div label = { props . txt ( "Ignored" ) } tabId = "ignored" tabIndex = { 3 } >
2020-09-18 23:30:17 +00:00
< IgnoredFiles
perPageLimit = { props . perPageLimit }
allFiles = { props . opstate . blacklist }
allow = { { fileList : props . allow . filelist } }
2022-07-17 01:43:40 +00:00
txt = { props . txt }
2020-09-18 23:30:17 +00:00
/ >
< / div > )
}
{
( props . allow . filelist && props . opstate . preload . length &&
2022-07-17 01:43:40 +00:00
< div label = { props . txt ( "Preloaded" ) } tabId = "preloaded" tabIndex = { 4 } >
2020-09-18 23:30:17 +00:00
< PreloadedFiles
perPageLimit = { props . perPageLimit }
allFiles = { props . opstate . preload }
allow = { { fileList : props . allow . filelist } }
2022-07-17 01:43:40 +00:00
txt = { props . txt }
2020-09-18 23:30:17 +00:00
/ >
< / div > )
}
{
props . allow . reset &&
2022-07-17 01:43:40 +00:00
< div label = { props . txt ( "Reset cache" ) } tabId = "resetCache"
2020-09-18 23:30:17 +00:00
className = { ` nav-tab-link-reset ${ props . resetting ? ' is-resetting pulse' : '' } ` }
handler = { props . resetHandler }
tabIndex = { 5 }
> < / div >
}
{
props . allow . realtime &&
2022-07-17 01:43:40 +00:00
< div label = { props . txt ( ` ${ props . realtime ? 'Disable' : 'Enable' } real-time update ` ) } tabId = "toggleRealtime"
2020-09-18 23:30:17 +00:00
className = { ` nav-tab-link-realtime ${ props . realtime ? ' live-update pulse' : '' } ` }
handler = { props . realtimeHandler }
tabIndex = { 6 }
> < / div >
}
< / Tabs >
< / nav >
) ;
}
class Tabs extends React . Component {
constructor ( props ) {
super ( props ) ;
this . state = {
activeTab : this . props . children [ 0 ] . props . label ,
} ;
}
onClickTabItem = ( tab ) => {
this . setState ( { activeTab : tab } ) ;
}
render ( ) {
const {
onClickTabItem ,
state : { activeTab }
} = this ;
const children = this . props . children . filter ( Boolean ) ;
return (
< >
< ul className = "nav-tab-list" >
{ children . map ( ( child ) => {
const { tabId , label , className , handler , tabIndex } = child . props ;
return (
< Tab
activeTab = { activeTab }
key = { tabId }
label = { label }
onClick = { handler || onClickTabItem }
className = { className }
tabIndex = { tabIndex }
tabId = { tabId }
/ >
) ;
} ) }
< / ul >
< div className = "tab-content" >
{ children . map ( ( child ) => (
< div key = { child . props . label }
style = { { display : child . props . label === activeTab ? 'block' : 'none' } }
id = { ` ${ child . props . tabId } -content ` }
>
{ child . props . children }
< / div >
) ) }
< / div >
< / >
) ;
}
}
class Tab extends React . Component {
onClick = ( ) => {
const { label , onClick } = this . props ;
onClick ( label ) ;
}
render ( ) {
const {
onClick ,
props : { activeTab , label , tabIndex , tabId } ,
} = this ;
let className = 'nav-tab' ;
if ( this . props . className ) {
className += ` ${ this . props . className } ` ;
}
if ( activeTab === label ) {
className += ' active' ;
}
return (
< li className = { className }
onClick = { onClick }
tabIndex = { tabIndex }
role = "tab"
aria - controls = { ` ${ tabId } -content ` }
> { label } < / li >
) ;
}
}
function OverviewCounts ( props ) {
if ( props . overview === false ) {
return (
< p class = "file-cache-only" >
2022-07-17 01:43:40 +00:00
{ props . txt ( ` You have <i>opcache.file_cache_only</i> turned on. As a result, the memory information is not available. Statistics and file list may also not be returned by <i>opcache_get_statistics()</i>. ` ) }
2020-09-18 23:30:17 +00:00
< / p >
) ;
}
const graphList = [
2022-07-17 01:43:40 +00:00
{ id : 'memoryUsageCanvas' , title : props . txt ( 'memory' ) , show : props . highlight . memory , value : props . overview . used _memory _percentage } ,
{ id : 'hitRateCanvas' , title : props . txt ( 'hit rate' ) , show : props . highlight . hits , value : props . overview . hit _rate _percentage } ,
{ id : 'keyUsageCanvas' , title : props . txt ( 'keys' ) , show : props . highlight . keys , value : props . overview . used _key _percentage } ,
{ id : 'jitUsageCanvas' , title : props . txt ( 'jit buffer' ) , show : props . highlight . jit , value : props . overview . jit _buffer _used _percentage }
2020-09-18 23:30:17 +00:00
] ;
return (
< div id = "counts" className = "tab-content-overview-counts" >
{ graphList . map ( ( graph ) => {
if ( ! graph . show ) {
return null ;
}
return (
< div className = "widget-panel" key = { graph . id } >
< h3 className = "widget-header" > { graph . title } < / h3 >
< UsageGraph charts = { props . useCharts } value = { graph . value } gaugeId = { graph . id } / >
< / div >
) ;
} ) }
< MemoryUsagePanel
total = { props . overview . readable . total _memory }
used = { props . overview . readable . used _memory }
free = { props . overview . readable . free _memory }
wasted = { props . overview . readable . wasted _memory }
preload = { props . overview . readable . preload _memory || null }
wastedPercent = { props . overview . wasted _percentage }
2021-06-27 11:47:40 +00:00
jitBuffer = { props . overview . readable . jit _buffer _size || null }
jitBufferFree = { props . overview . readable . jit _buffer _free || null }
jitBufferFreePercentage = { props . overview . jit _buffer _used _percentage || null }
2022-07-17 01:43:40 +00:00
txt = { props . txt }
2020-09-18 23:30:17 +00:00
/ >
< StatisticsPanel
num _cached _scripts = { props . overview . readable . num _cached _scripts }
hits = { props . overview . readable . hits }
misses = { props . overview . readable . misses }
blacklist _miss = { props . overview . readable . blacklist _miss }
num _cached _keys = { props . overview . readable . num _cached _keys }
max _cached _keys = { props . overview . readable . max _cached _keys }
2022-07-17 01:43:40 +00:00
txt = { props . txt }
2020-09-18 23:30:17 +00:00
/ >
{ props . overview . readable . interned &&
< InternedStringsPanel
buffer _size = { props . overview . readable . interned . buffer _size }
strings _used _memory = { props . overview . readable . interned . strings _used _memory }
strings _free _memory = { props . overview . readable . interned . strings _free _memory }
number _of _strings = { props . overview . readable . interned . number _of _strings }
2022-07-17 01:43:40 +00:00
txt = { props . txt }
2020-09-18 23:30:17 +00:00
/ >
}
< / div >
) ;
}
function GeneralInfo ( props ) {
return (
< table className = "tables general-info-table" >
< thead >
2022-07-17 01:43:40 +00:00
< tr > < th colSpan = "2" > { props . txt ( 'General info' ) } < / th > < / tr >
2020-09-18 23:30:17 +00:00
< / thead >
< tbody >
< tr > < td > Zend OPcache < / td > < td > { props . version . version } < / td > < / tr >
< tr > < td > PHP < / td > < td > { props . version . php } < / td > < / tr >
2022-07-17 01:43:40 +00:00
< tr > < td > { props . txt ( 'Host' ) } < / td > < td > { props . version . host } < / td > < / tr >
< tr > < td > { props . txt ( 'Server Software' ) } < / td > < td > { props . version . server } < / td > < / tr >
{ props . start ? < tr > < td > { props . txt ( 'Start time' ) } < / td > < td > { props . start } < / td > < / tr > : null }
{ props . reset ? < tr > < td > { props . txt ( 'Last reset' ) } < / td > < td > { props . reset } < / td > < / tr > : null }
2020-09-18 23:30:17 +00:00
< / tbody >
< / table >
) ;
}
function Directives ( props ) {
2020-12-16 11:10:48 +00:00
let directiveList = ( directive ) => {
return (
< ul className = "directive-list" > {
directive . v . map ( ( item , key ) => {
2022-01-09 23:48:23 +00:00
return Array . isArray ( item )
? < li key = { "sublist_" + key } > { directiveList ( { v : item } ) } < / li >
: < li key = { key } > { item } < / li >
2020-12-16 11:10:48 +00:00
} )
} < / ul >
) ;
} ;
2020-09-18 23:30:17 +00:00
let directiveNodes = props . directives . map ( function ( directive ) {
let map = { 'opcache.' : '' , '_' : ' ' } ;
let dShow = directive . k . replace ( /opcache\.|_/gi , function ( matched ) {
return map [ matched ] ;
} ) ;
let vShow ;
if ( directive . v === true || directive . v === false ) {
2022-07-17 01:43:40 +00:00
vShow = React . createElement ( 'i' , { } , props . txt ( directive . v . toString ( ) ) ) ;
2020-09-18 23:30:17 +00:00
} else if ( directive . v === '' ) {
2022-07-17 01:43:40 +00:00
vShow = React . createElement ( 'i' , { } , props . txt ( 'no value' ) ) ;
2020-09-18 23:30:17 +00:00
} else {
if ( Array . isArray ( directive . v ) ) {
2020-12-16 11:10:48 +00:00
vShow = directiveList ( directive ) ;
2020-09-18 23:30:17 +00:00
} else {
vShow = directive . v ;
}
}
return (
< tr key = { directive . k } >
2022-07-17 01:43:40 +00:00
< td title = { props . txt ( 'View {0} manual entry' , directive . k ) } > < a href = { 'https://php.net/manual/en/opcache.configuration.php#ini.'
2020-09-18 23:30:17 +00:00
+ ( directive . k ) . replace ( /_/g , '-' ) } target = "_blank" > { dShow } < / a > < / td >
< td > { vShow } < / td >
< / tr >
) ;
} ) ;
return (
< table className = "tables directives-table" >
2022-07-17 01:43:40 +00:00
< thead > < tr > < th colSpan = "2" > { props . txt ( 'Directives' ) } < / th > < / tr > < / thead >
2020-09-18 23:30:17 +00:00
< tbody > { directiveNodes } < / tbody >
< / table >
) ;
}
function Functions ( props ) {
return (
< div id = "functions" >
< table className = "tables" >
2022-07-17 01:43:40 +00:00
< thead > < tr > < th > { props . txt ( 'Available functions' ) } < / th > < / tr > < / thead >
2020-09-18 23:30:17 +00:00
< tbody >
{ props . functions . map ( f =>
2022-07-17 01:43:40 +00:00
< tr key = { f } > < td > < a href = { "https://php.net/" + f } title = { props . txt ( 'View manual page' ) } target = "_blank" > { f } < / a > < / td > < / tr >
2020-09-18 23:30:17 +00:00
) }
< / tbody >
< / table >
< / div >
) ;
}
function UsageGraph ( props ) {
const percentage = Math . round ( ( ( 3.6 * props . value ) / 360 ) * 100 ) ;
return ( props . charts
? < ReactCustomizableProgressbar
progress = { percentage }
radius = { 100 }
strokeWidth = { 30 }
trackStrokeWidth = { 30 }
strokeColor = { getComputedStyle ( document . documentElement ) . getPropertyValue ( '--opcache-gui-graph-track-fill-color' ) || "#6CA6EF" }
trackStrokeColor = { getComputedStyle ( document . documentElement ) . getPropertyValue ( '--opcache-gui-graph-track-background-color' ) || "#CCC" }
gaugeId = { props . gaugeId }
/ >
: < p className = "widget-value" > < span className = "large" > { percentage } < / span > < span > % < / span > < / p >
) ;
}
/ * *
* This component is from < https : / / github.com / martyan / react - customizable - progressbar / >
* MIT License ( MIT ) , Copyright ( c ) 2019 Martin Juzl
* /
class ReactCustomizableProgressbar extends React . Component {
constructor ( props ) {
super ( props ) ;
this . state = {
animationInited : false
} ;
}
componentDidMount ( ) {
const { initialAnimation , initialAnimationDelay } = this . props
if ( initialAnimation )
setTimeout ( this . initAnimation , initialAnimationDelay )
}
initAnimation = ( ) => {
this . setState ( { animationInited : true } )
}
getProgress = ( ) => {
const { initialAnimation , progress } = this . props
const { animationInited } = this . state
return initialAnimation && ! animationInited ? 0 : progress
}
getStrokeDashoffset = strokeLength => {
const { counterClockwise , inverse , steps } = this . props
const progress = this . getProgress ( )
const progressLength = ( strokeLength / steps ) * ( steps - progress )
if ( inverse ) return counterClockwise ? 0 : progressLength - strokeLength
return counterClockwise ? - 1 * progressLength : progressLength
}
getStrokeDashArray = ( strokeLength , circumference ) => {
const { counterClockwise , inverse , steps } = this . props
const progress = this . getProgress ( )
const progressLength = ( strokeLength / steps ) * ( steps - progress )
if ( inverse ) return ` ${ progressLength } , ${ circumference } `
return counterClockwise
? ` ${ strokeLength * ( progress / 100 ) } , ${ circumference } `
: ` ${ strokeLength } , ${ circumference } `
}
getTrackStrokeDashArray = ( strokeLength , circumference ) => {
const { initialAnimation } = this . props
const { animationInited } = this . state
if ( initialAnimation && ! animationInited ) return ` 0, ${ circumference } `
return ` ${ strokeLength } , ${ circumference } `
}
getExtendedWidth = ( ) => {
const {
strokeWidth ,
pointerRadius ,
pointerStrokeWidth ,
trackStrokeWidth
} = this . props
const pointerWidth = pointerRadius + pointerStrokeWidth
if ( pointerWidth > strokeWidth && pointerWidth > trackStrokeWidth ) return pointerWidth * 2
else if ( strokeWidth > trackStrokeWidth ) return strokeWidth * 2
else return trackStrokeWidth * 2
}
getPointerAngle = ( ) => {
const { cut , counterClockwise , steps } = this . props
const progress = this . getProgress ( )
return counterClockwise
? ( ( 360 - cut ) / steps ) * ( steps - progress )
: ( ( 360 - cut ) / steps ) * progress
}
render ( ) {
const {
radius ,
pointerRadius ,
pointerStrokeWidth ,
pointerFillColor ,
pointerStrokeColor ,
fillColor ,
trackStrokeWidth ,
trackStrokeColor ,
trackStrokeLinecap ,
strokeColor ,
strokeWidth ,
strokeLinecap ,
rotate ,
cut ,
trackTransition ,
transition ,
progress
} = this . props
const d = 2 * radius
const width = d + this . getExtendedWidth ( )
const circumference = 2 * Math . PI * radius
const strokeLength = ( circumference / 360 ) * ( 360 - cut )
return (
< figure
className = { ` graph-widget ` }
style = { { width : ` ${ width || 250 } px ` } }
data - value = { progress }
id = { this . props . guageId }
>
< svg width = { width } height = { width }
viewBox = { ` 0 0 ${ width } ${ width } ` }
style = { { transform : ` rotate( ${ rotate } deg) ` } }
>
{ trackStrokeWidth > 0 && (
< circle
cx = { width / 2 }
cy = { width / 2 }
r = { radius }
fill = "none"
stroke = { trackStrokeColor }
strokeWidth = { trackStrokeWidth }
strokeDasharray = { this . getTrackStrokeDashArray (
strokeLength ,
circumference
) }
strokeLinecap = { trackStrokeLinecap }
style = { { transition : trackTransition } }
/ >
) }
{ strokeWidth > 0 && (
< circle
cx = { width / 2 }
cy = { width / 2 }
r = { radius }
fill = { fillColor }
stroke = { strokeColor }
strokeWidth = { strokeWidth }
strokeDasharray = { this . getStrokeDashArray (
strokeLength ,
circumference
) }
strokeDashoffset = { this . getStrokeDashoffset (
strokeLength
) }
strokeLinecap = { strokeLinecap }
style = { { transition } }
/ >
) }
{ pointerRadius > 0 && (
< circle
cx = { d }
cy = "50%"
r = { pointerRadius }
fill = { pointerFillColor }
stroke = { pointerStrokeColor }
strokeWidth = { pointerStrokeWidth }
style = { {
transformOrigin : '50% 50%' ,
transform : ` rotate( ${ this . getPointerAngle ( ) } deg) translate( ${ this . getExtendedWidth ( ) /
2 } px ) ` ,
transition
} }
/ >
) }
< / svg >
< figcaption className = { ` widget-value ` } >
{ progress } %
< / figcaption >
< / figure >
)
}
}
ReactCustomizableProgressbar . defaultProps = {
radius : 100 ,
progress : 0 ,
steps : 100 ,
cut : 0 ,
rotate : - 90 ,
strokeWidth : 20 ,
strokeColor : 'indianred' ,
fillColor : 'none' ,
strokeLinecap : 'round' ,
transition : '.3s ease' ,
pointerRadius : 0 ,
pointerStrokeWidth : 20 ,
pointerStrokeColor : 'indianred' ,
pointerFillColor : 'white' ,
trackStrokeColor : '#e6e6e6' ,
trackStrokeWidth : 20 ,
trackStrokeLinecap : 'round' ,
trackTransition : '.3s ease' ,
counterClockwise : false ,
inverse : false ,
initialAnimation : false ,
initialAnimationDelay : 0
} ;
function MemoryUsagePanel ( props ) {
return (
< div className = "widget-panel" >
< h3 className = "widget-header" > memory usage < / h3 >
< div className = "widget-value widget-info" >
2022-07-17 01:43:40 +00:00
< p > < b > { props . txt ( 'total memory' ) } : < / b > { props . total } < / p >
< p > < b > { props . txt ( 'used memory' ) } : < / b > { props . used } < / p >
< p > < b > { props . txt ( 'free memory' ) } : < / b > { props . free } < / p >
{ props . preload && < p > < b > { props . txt ( 'preload memory' ) } : < / b > { props . preload } < / p > }
< p > < b > { props . txt ( 'wasted memory' ) } : < / b > { props . wasted } ( { props . wastedPercent } % ) < / p >
{ props . jitBuffer && < p > < b > { props . txt ( 'jit buffer' ) } : < / b > { props . jitBuffer } < / p > }
{ props . jitBufferFree && < p > < b > { props . txt ( 'jit buffer free' ) } : < / b > { props . jitBufferFree } ( { 100 - props . jitBufferFreePercentage } % ) < / p > }
2020-09-18 23:30:17 +00:00
< / div >
< / div >
) ;
}
function StatisticsPanel ( props ) {
return (
< div className = "widget-panel" >
2022-07-17 01:43:40 +00:00
< h3 className = "widget-header" > { props . txt ( 'opcache statistics' ) } < / h3 >
2020-09-18 23:30:17 +00:00
< div className = "widget-value widget-info" >
2022-07-17 01:43:40 +00:00
< p > < b > { props . txt ( 'number of cached' ) } files : < / b > { props . num _cached _scripts } < / p >
< p > < b > { props . txt ( 'number of hits' ) } : < / b > { props . hits } < / p >
< p > < b > { props . txt ( 'number of misses' ) } : < / b > { props . misses } < / p >
< p > < b > { props . txt ( 'blacklist misses' ) } : < / b > { props . blacklist _miss } < / p >
< p > < b > { props . txt ( 'number of cached keys' ) } : < / b > { props . num _cached _keys } < / p >
< p > < b > { props . txt ( 'max cached keys' ) } : < / b > { props . max _cached _keys } < / p >
2020-09-18 23:30:17 +00:00
< / div >
< / div >
) ;
}
function InternedStringsPanel ( props ) {
return (
< div className = "widget-panel" >
2022-07-17 01:43:40 +00:00
< h3 className = "widget-header" > { props . txt ( 'interned strings usage' ) } < / h3 >
2020-09-18 23:30:17 +00:00
< div className = "widget-value widget-info" >
2022-07-17 01:43:40 +00:00
< p > < b > { props . txt ( 'buffer size' ) } : < / b > { props . buffer _size } < / p >
< p > < b > { props . txt ( 'used memory' ) } : < / b > { props . strings _used _memory } < / p >
< p > < b > { props . txt ( 'free memory' ) } : < / b > { props . strings _free _memory } < / p >
< p > < b > { props . txt ( 'number of strings' ) } : < / b > { props . number _of _strings } < / p >
2020-09-18 23:30:17 +00:00
< / div >
< / div >
) ;
}
class CachedFiles extends React . Component {
constructor ( props ) {
super ( props ) ;
this . doPagination = ( typeof props . perPageLimit === "number"
&& props . perPageLimit > 0
) ;
this . state = {
currentPage : 1 ,
searchTerm : props . searchTerm ,
2020-11-28 16:16:58 +00:00
refreshPagination : 0 ,
sortBy : ` last_used_timestamp ` ,
sortDir : ` desc `
2020-09-18 23:30:17 +00:00
}
}
setSearchTerm = debounce ( searchTerm => {
this . setState ( {
searchTerm ,
refreshPagination : ! ( this . state . refreshPagination )
} ) ;
} , this . props . debounceRate ) ;
onPageChanged = currentPage => {
this . setState ( { currentPage } ) ;
}
handleInvalidate = e => {
e . preventDefault ( ) ;
if ( this . props . realtime ) {
2020-12-16 11:10:48 +00:00
axios . get ( window . location . pathname , { params : { invalidate _searched : this . state . searchTerm } } )
2020-09-18 23:30:17 +00:00
. then ( ( response ) => {
console . log ( 'success: ' , response . data ) ;
} ) ;
} else {
window . location . href = e . currentTarget . href ;
}
}
2020-11-28 16:16:58 +00:00
changeSort = e => {
this . setState ( { [ e . target . name ] : e . target . value } ) ;
}
compareValues = ( key , order = 'asc' ) => {
return function innerSort ( a , b ) {
if ( ! a . hasOwnProperty ( key ) || ! b . hasOwnProperty ( key ) ) {
return 0 ;
}
const varA = ( typeof a [ key ] === 'string' ) ? a [ key ] . toUpperCase ( ) : a [ key ] ;
const varB = ( typeof b [ key ] === 'string' ) ? b [ key ] . toUpperCase ( ) : b [ key ] ;
let comparison = 0 ;
if ( varA > varB ) {
comparison = 1 ;
} else if ( varA < varB ) {
comparison = - 1 ;
}
return (
( order === 'desc' ) ? ( comparison * - 1 ) : comparison
) ;
} ;
}
2020-09-18 23:30:17 +00:00
render ( ) {
if ( ! this . props . allow . fileList ) {
return null ;
}
if ( this . props . allFiles . length === 0 ) {
2022-07-17 01:43:40 +00:00
return < p > { this . props . txt ( 'No files have been cached or you have <i>opcache.file_cache_only</i> turned on' ) } < / p > ;
2020-09-18 23:30:17 +00:00
}
const { searchTerm , currentPage } = this . state ;
const offset = ( currentPage - 1 ) * this . props . perPageLimit ;
const filesInSearch = ( searchTerm
? this . props . allFiles . filter ( file => {
2020-12-08 21:59:47 +00:00
return ! ( file . full _path . indexOf ( searchTerm ) === - 1 ) ;
2020-09-18 23:30:17 +00:00
} )
: this . props . allFiles
) ;
2020-11-28 16:16:58 +00:00
filesInSearch . sort ( this . compareValues ( this . state . sortBy , this . state . sortDir ) ) ;
2020-09-18 23:30:17 +00:00
const filesInPage = ( this . doPagination
? filesInSearch . slice ( offset , offset + this . props . perPageLimit )
: filesInSearch
) ;
const allFilesTotal = this . props . allFiles . length ;
const showingTotal = filesInSearch . length ;
2022-07-17 01:43:40 +00:00
const showing = showingTotal !== allFilesTotal ? ", {1} showing due to filter '{2}'" : "" ;
2020-09-18 23:30:17 +00:00
return (
< div >
< form action = "#" >
2022-07-17 01:43:40 +00:00
< label htmlFor = "frmFilter" > { this . props . txt ( 'Start typing to filter on script path' ) } < / label > < br / >
2020-09-18 23:30:17 +00:00
< input type = "text" name = "filter" id = "frmFilter" className = "file-filter" onChange = { e => { this . setSearchTerm ( e . target . value ) } } / >
< / form >
2022-07-17 01:43:40 +00:00
< h3 > { this . props . txt ( ` {0} files cached ${ showing } ` , allFilesTotal , showingTotal , this . state . searchTerm ) } < / h3 >
2020-09-18 23:30:17 +00:00
2021-06-27 11:47:40 +00:00
{ this . props . allow . invalidate && this . state . searchTerm && showingTotal !== allFilesTotal &&
2022-07-17 01:43:40 +00:00
< p > < a href = { ` ?invalidate_searched= ${ encodeURIComponent ( this . state . searchTerm ) } ` } onClick = { this . handleInvalidate } > { this . props . txt ( 'Invalidate all matching files' ) } < / a > < / p >
2020-09-18 23:30:17 +00:00
}
2020-11-28 16:16:58 +00:00
< div className = "paginate-filter" >
{ this . doPagination && < Pagination
totalRecords = { filesInSearch . length }
pageLimit = { this . props . perPageLimit }
pageNeighbours = { 2 }
onPageChanged = { this . onPageChanged }
refresh = { this . state . refreshPagination }
2022-07-17 01:43:40 +00:00
txt = { this . props . txt }
2020-11-28 16:16:58 +00:00
/ > }
2022-07-17 01:43:40 +00:00
< nav className = "filter" aria - label = { this . props . txt ( 'Sort order' ) } >
2020-11-28 16:16:58 +00:00
< select name = "sortBy" onChange = { this . changeSort } value = { this . state . sortBy } >
2022-07-17 01:43:40 +00:00
< option value = "last_used_timestamp" > { this . props . txt ( 'Last used' ) } < / option >
< option value = "last_modified" > { this . props . txt ( 'Last modified' ) } < / option >
< option value = "full_path" > { this . props . txt ( 'Path' ) } < / option >
< option value = "hits" > { this . props . txt ( 'Number of hits' ) } < / option >
< option value = "memory_consumption" > { this . props . txt ( 'Memory consumption' ) } < / option >
2020-11-28 16:16:58 +00:00
< / select >
< select name = "sortDir" onChange = { this . changeSort } value = { this . state . sortDir } >
2022-07-17 01:43:40 +00:00
< option value = "desc" > { this . props . txt ( 'Descending' ) } < / option >
< option value = "asc" > { this . props . txt ( 'Ascending' ) } < / option >
2020-11-28 16:16:58 +00:00
< / select >
< / nav >
< / div >
2020-09-18 23:30:17 +00:00
< table className = "tables cached-list-table" >
< thead >
< tr >
2022-07-17 01:43:40 +00:00
< th > { this . props . txt ( 'Script' ) } < / th >
2020-09-18 23:30:17 +00:00
< / tr >
< / thead >
< tbody >
{ filesInPage . map ( ( file , index ) => {
return < CachedFile
key = { file . full _path }
canInvalidate = { this . props . allow . invalidate }
realtime = { this . props . realtime }
2022-07-17 01:43:40 +00:00
txt = { this . props . txt }
2020-09-18 23:30:17 +00:00
{ ... file }
/ >
} ) }
< / tbody >
< / table >
< / div >
) ;
}
}
class CachedFile extends React . Component {
handleInvalidate = e => {
e . preventDefault ( ) ;
if ( this . props . realtime ) {
2020-12-16 11:10:48 +00:00
axios . get ( window . location . pathname , { params : { invalidate : e . currentTarget . getAttribute ( 'data-file' ) } } )
2020-09-18 23:30:17 +00:00
. then ( ( response ) => {
console . log ( 'success: ' , response . data ) ;
} ) ;
} else {
window . location . href = e . currentTarget . href ;
}
}
render ( ) {
return (
< tr data - path = { this . props . full _path . toLowerCase ( ) } >
< td >
< span className = "file-pathname" > { this . props . full _path } < / span >
< span className = "file-metainfo" >
2022-07-17 01:43:40 +00:00
< b > { this . props . txt ( 'hits' ) } : < / b > < span > { this . props . readable . hits } , < / span >
< b > { this . props . txt ( 'memory' ) } : < / b > < span > { this . props . readable . memory _consumption } , < / span >
{ this . props . last _modified && < > < b > { this . props . txt ( 'last modified' ) } : < / b > < span > { this . props . last _modified } , < / span > < / > }
< b > { this . props . txt ( 'last used' ) } : < / b > < span > { this . props . last _used } < / span >
2020-09-18 23:30:17 +00:00
< / span >
2022-07-17 01:43:40 +00:00
{ ! this . props . timestamp && < span className = "invalid file-metainfo" > - { this . props . txt ( 'has been invalidated' ) } < / span > }
2020-09-18 23:30:17 +00:00
{ this . props . canInvalidate && < span > , & nbsp ; < a className = "file-metainfo"
href = { '?invalidate=' + this . props . full _path } data - file = { this . props . full _path }
2022-07-17 01:43:40 +00:00
onClick = { this . handleInvalidate } > { this . props . txt ( 'force file invalidation' ) } < / a > < / span > }
2020-09-18 23:30:17 +00:00
< / td >
< / tr >
) ;
}
}
class IgnoredFiles extends React . Component {
constructor ( props ) {
super ( props ) ;
this . doPagination = ( typeof props . perPageLimit === "number"
&& props . perPageLimit > 0
) ;
this . state = {
currentPage : 1 ,
refreshPagination : 0
}
}
onPageChanged = currentPage => {
this . setState ( { currentPage } ) ;
}
render ( ) {
if ( ! this . props . allow . fileList ) {
return null ;
}
if ( this . props . allFiles . length === 0 ) {
2022-07-17 01:43:40 +00:00
return < p > { this . props . txt ( 'No files have been ignored via <i>opcache.blacklist_filename</i>' ) } < / p > ;
2020-09-18 23:30:17 +00:00
}
const { currentPage } = this . state ;
const offset = ( currentPage - 1 ) * this . props . perPageLimit ;
const filesInPage = ( this . doPagination
? this . props . allFiles . slice ( offset , offset + this . props . perPageLimit )
: this . props . allFiles
) ;
const allFilesTotal = this . props . allFiles . length ;
return (
< div >
2022-07-17 01:43:40 +00:00
< h3 > { this . props . txt ( '{0} ignore file locations' , allFilesTotal ) } < / h3 >
2020-09-18 23:30:17 +00:00
{ this . doPagination && < Pagination
totalRecords = { allFilesTotal }
pageLimit = { this . props . perPageLimit }
pageNeighbours = { 2 }
onPageChanged = { this . onPageChanged }
refresh = { this . state . refreshPagination }
2022-07-17 01:43:40 +00:00
txt = { this . props . txt }
2020-09-18 23:30:17 +00:00
/ > }
< table className = "tables ignored-list-table" >
2022-07-17 01:43:40 +00:00
< thead > < tr > < th > { this . props . txt ( 'Path' ) } < / th > < / tr > < / thead >
2020-09-18 23:30:17 +00:00
< tbody >
{ filesInPage . map ( ( file , index ) => {
return < tr key = { file } > < td > { file } < / td > < / tr >
} ) }
< / tbody >
< / table >
< / div >
) ;
}
}
class PreloadedFiles extends React . Component {
constructor ( props ) {
super ( props ) ;
this . doPagination = ( typeof props . perPageLimit === "number"
&& props . perPageLimit > 0
) ;
this . state = {
currentPage : 1 ,
refreshPagination : 0
}
}
onPageChanged = currentPage => {
this . setState ( { currentPage } ) ;
}
render ( ) {
if ( ! this . props . allow . fileList ) {
return null ;
}
if ( this . props . allFiles . length === 0 ) {
2022-07-17 01:43:40 +00:00
return < p > { this . props . txt ( 'No files have been preloaded <i>opcache.preload</i>' ) } < / p > ;
2020-09-18 23:30:17 +00:00
}
const { currentPage } = this . state ;
const offset = ( currentPage - 1 ) * this . props . perPageLimit ;
const filesInPage = ( this . doPagination
? this . props . allFiles . slice ( offset , offset + this . props . perPageLimit )
: this . props . allFiles
) ;
const allFilesTotal = this . props . allFiles . length ;
return (
< div >
2022-07-17 01:43:40 +00:00
< h3 > { this . props . txt ( '{0} preloaded files' , allFilesTotal ) } < / h3 >
2020-09-18 23:30:17 +00:00
{ this . doPagination && < Pagination
totalRecords = { allFilesTotal }
pageLimit = { this . props . perPageLimit }
pageNeighbours = { 2 }
onPageChanged = { this . onPageChanged }
refresh = { this . state . refreshPagination }
2022-07-17 01:43:40 +00:00
txt = { this . props . txt }
2020-09-18 23:30:17 +00:00
/ > }
< table className = "tables preload-list-table" >
2022-07-17 01:43:40 +00:00
< thead > < tr > < th > { this . props . txt ( 'Path' ) } < / th > < / tr > < / thead >
2020-09-18 23:30:17 +00:00
< tbody >
{ filesInPage . map ( ( file , index ) => {
return < tr key = { file } > < td > { file } < / td > < / tr >
} ) }
< / tbody >
< / table >
< / div >
) ;
}
}
class Pagination extends React . Component {
constructor ( props ) {
super ( props ) ;
this . state = { currentPage : 1 } ;
this . pageNeighbours =
typeof props . pageNeighbours === "number"
? Math . max ( 0 , Math . min ( props . pageNeighbours , 2 ) )
: 0 ;
}
componentDidMount ( ) {
this . gotoPage ( 1 ) ;
}
componentDidUpdate ( props ) {
const { refresh } = this . props ;
if ( props . refresh !== refresh ) {
this . gotoPage ( 1 ) ;
}
}
gotoPage = page => {
const { onPageChanged = f => f } = this . props ;
const currentPage = Math . max ( 0 , Math . min ( page , this . totalPages ( ) ) ) ;
this . setState ( { currentPage } , ( ) => onPageChanged ( currentPage ) ) ;
} ;
totalPages = ( ) => {
return Math . ceil ( this . props . totalRecords / this . props . pageLimit ) ;
}
handleClick = ( page , evt ) => {
evt . preventDefault ( ) ;
this . gotoPage ( page ) ;
} ;
handleJumpLeft = evt => {
evt . preventDefault ( ) ;
this . gotoPage ( this . state . currentPage - this . pageNeighbours * 2 - 1 ) ;
} ;
handleJumpRight = evt => {
evt . preventDefault ( ) ;
this . gotoPage ( this . state . currentPage + this . pageNeighbours * 2 + 1 ) ;
} ;
handleMoveLeft = evt => {
evt . preventDefault ( ) ;
this . gotoPage ( this . state . currentPage - 1 ) ;
} ;
handleMoveRight = evt => {
evt . preventDefault ( ) ;
this . gotoPage ( this . state . currentPage + 1 ) ;
} ;
range = ( from , to , step = 1 ) => {
let i = from ;
const range = [ ] ;
while ( i <= to ) {
range . push ( i ) ;
i += step ;
}
return range ;
}
fetchPageNumbers = ( ) => {
const totalPages = this . totalPages ( ) ;
const pageNeighbours = this . pageNeighbours ;
const totalNumbers = this . pageNeighbours * 2 + 3 ;
const totalBlocks = totalNumbers + 2 ;
if ( totalPages > totalBlocks ) {
let pages = [ ] ;
const leftBound = this . state . currentPage - pageNeighbours ;
const rightBound = this . state . currentPage + pageNeighbours ;
const beforeLastPage = totalPages - 1 ;
const startPage = leftBound > 2 ? leftBound : 2 ;
const endPage = rightBound < beforeLastPage ? rightBound : beforeLastPage ;
pages = this . range ( startPage , endPage ) ;
const pagesCount = pages . length ;
const singleSpillOffset = totalNumbers - pagesCount - 1 ;
const leftSpill = startPage > 2 ;
const rightSpill = endPage < beforeLastPage ;
const leftSpillPage = "LEFT" ;
const rightSpillPage = "RIGHT" ;
if ( leftSpill && ! rightSpill ) {
const extraPages = this . range ( startPage - singleSpillOffset , startPage - 1 ) ;
pages = [ leftSpillPage , ... extraPages , ... pages ] ;
} else if ( ! leftSpill && rightSpill ) {
const extraPages = this . range ( endPage + 1 , endPage + singleSpillOffset ) ;
pages = [ ... pages , ... extraPages , rightSpillPage ] ;
} else if ( leftSpill && rightSpill ) {
pages = [ leftSpillPage , ... pages , rightSpillPage ] ;
}
return [ 1 , ... pages , totalPages ] ;
}
return this . range ( 1 , totalPages ) ;
} ;
render ( ) {
if ( ! this . props . totalRecords || this . totalPages ( ) === 1 ) {
return null
}
const { currentPage } = this . state ;
const pages = this . fetchPageNumbers ( ) ;
return (
< nav aria - label = "File list pagination" >
< ul className = "pagination" >
{ pages . map ( ( page , index ) => {
if ( page === "LEFT" ) {
return (
< React.Fragment key = { index } >
< li className = "page-item arrow" >
2022-07-17 01:43:40 +00:00
< a className = "page-link" href = "#" aria - label = { this . props . txt ( 'Previous' ) } onClick = { this . handleJumpLeft } >
2020-09-18 23:30:17 +00:00
< span aria - hidden = "true" > ↞ < / span >
2022-07-17 01:43:40 +00:00
< span className = "sr-only" > { this . props . txt ( 'Jump back' ) } < / span >
2020-09-18 23:30:17 +00:00
< / a >
< / li >
< li className = "page-item arrow" >
2022-07-17 01:43:40 +00:00
< a className = "page-link" href = "#" aria - label = { this . props . txt ( 'Previous' ) } onClick = { this . handleMoveLeft } >
2020-09-18 23:30:17 +00:00
< span aria - hidden = "true" > ⇠ < / span >
2022-07-17 01:43:40 +00:00
< span className = "sr-only" > { this . props . txt ( 'Previous page' ) } < / span >
2020-09-18 23:30:17 +00:00
< / a >
< / li >
< / React.Fragment >
) ;
}
if ( page === "RIGHT" ) {
return (
< React.Fragment key = { index } >
< li className = "page-item arrow" >
2022-07-17 01:43:40 +00:00
< a className = "page-link" href = "#" aria - label = { this . props . txt ( 'Next' ) } onClick = { this . handleMoveRight } >
2020-09-18 23:30:17 +00:00
< span aria - hidden = "true" > ⇢ < / span >
2022-07-17 01:43:40 +00:00
< span className = "sr-only" > { this . props . txt ( 'Next page' ) } < / span >
2020-09-18 23:30:17 +00:00
< / a >
< / li >
< li className = "page-item arrow" >
2022-07-17 01:43:40 +00:00
< a className = "page-link" href = "#" aria - label = { this . props . txt ( 'Next' ) } onClick = { this . handleJumpRight } >
2020-09-18 23:30:17 +00:00
< span aria - hidden = "true" > ↠ < / span >
2022-07-17 01:43:40 +00:00
< span className = "sr-only" > { this . props . txt ( 'Jump forward' ) } < / span >
2020-09-18 23:30:17 +00:00
< / a >
< / li >
< / React.Fragment >
) ;
}
return (
< li key = { index } className = "page-item" >
< a className = { ` page-link ${ currentPage === page ? " active" : "" } ` } href = "#" onClick = { e => this . handleClick ( page , e ) } >
{ page }
< / a >
< / li >
) ;
} ) }
< / ul >
< / nav >
) ;
}
}
function Footer ( props ) {
return (
< footer className = "main-footer" >
< a className = "github-link" href = "https://github.com/amnuts/opcache-gui"
target = "_blank"
title = "opcache-gui (currently version {props.version}) on GitHub"
> https : //github.com/amnuts/opcache-gui - version {props.version}</a>
< / footer >
) ;
}
function debounce ( func , wait , immediate ) {
let timeout ;
wait = wait || 250 ;
return function ( ) {
let context = this , args = arguments ;
let later = function ( ) {
timeout = null ;
if ( ! immediate ) {
func . apply ( context , args ) ;
}
} ;
let callNow = immediate && ! timeout ;
clearTimeout ( timeout ) ;
timeout = setTimeout ( later , wait ) ;
if ( callNow ) {
func . apply ( context , args ) ;
}
} ;
}