2013-04-08 15:40:06 +00:00
< ? php
2020-09-18 23:30:17 +00:00
namespace Amnuts\Opcache ;
2017-03-27 14:16:00 +00:00
2013-08-09 14:06:45 +00:00
/**
* OPcache GUI
2014-11-23 01:07:38 +00:00
*
2013-08-09 14:06:45 +00:00
* A simple but effective single - file GUI for the OPcache PHP extension .
2014-11-23 01:07:38 +00:00
*
2013-08-09 14:06:45 +00:00
* @ author Andrew Collington , andy @ amnuts . com
2020-11-28 16:16:58 +00:00
* @ version 3.1 . 0
2014-11-26 21:42:23 +00:00
* @ link https :// github . com / amnuts / opcache - gui
2013-08-09 14:06:45 +00:00
* @ license MIT , http :// acollington . mit - license . org /
*/
2015-05-08 16:04:40 +00:00
/*
* User configuration
2020-09-18 23:30:17 +00:00
* These are all the default values ; you only really need to supply the ones
* that you wish to change .
2015-05-08 16:04:40 +00:00
*/
$options = [
2017-12-28 19:37:52 +00:00
'allow_filelist' => true , // show/hide the files tab
'allow_invalidate' => true , // give a link to invalidate files
'allow_reset' => true , // give option to reset the whole cache
'allow_realtime' => true , // give option to enable/disable real-time updates
'refresh_time' => 5 , // how often the data will refresh, in seconds
2020-04-06 22:17:29 +00:00
'size_precision' => 2 , // Digits after decimal point
2017-12-28 19:37:52 +00:00
'size_space' => false , // have '1MB' or '1 MB' when showing sizes
'charts' => true , // show gauge chart or just big numbers
'debounce_rate' => 250 , // milliseconds after key press to send keyup event when filtering
2020-09-18 23:30:17 +00:00
'per_page' => 200 , // How many results per page to show in the file list, false for no pagination
2017-12-28 19:37:52 +00:00
'cookie_name' => 'opcachegui' , // name of cookie
2019-07-13 16:17:52 +00:00
'cookie_ttl' => 365 , // days to store cookie
2020-09-18 23:30:17 +00:00
'highlight' => [
'memory' => true , // show the memory chart/big number
'hits' => true , // show the hit rate chart/big number
'keys' => true // show the keys used chart/big number
2019-07-13 16:17:52 +00:00
]
2015-05-08 16:04:40 +00:00
];
/*
* Shouldn ' t need to alter anything else below here
*/
2014-11-23 01:07:38 +00:00
if ( ! extension_loaded ( 'Zend OPcache' )) {
2013-04-26 10:56:54 +00:00
die ( 'The Zend OPcache extension does not appear to be installed' );
2013-04-08 15:40:06 +00:00
}
2017-06-07 10:59:24 +00:00
$ocEnabled = ini_get ( 'opcache.enable' );
if ( empty ( $ocEnabled )) {
2019-07-12 00:41:26 +00:00
die ( 'The Zend OPcache extension is installed but not active' );
2017-01-06 13:40:59 +00:00
}
2019-07-12 00:41:26 +00:00
header ( 'Cache-Control: no-cache, must-revalidate' );
header ( 'Pragma: no-cache' );
2020-09-18 23:30:17 +00:00
class Service
2014-11-23 01:07:38 +00:00
{
2020-11-28 16:16:58 +00:00
const VERSION = '3.1.0' ;
2020-02-22 18:15:23 +00:00
2014-11-25 13:20:33 +00:00
protected $data ;
2015-05-08 16:04:40 +00:00
protected $options ;
2020-02-22 19:51:12 +00:00
protected $optimizationLevels ;
2015-05-08 16:04:40 +00:00
protected $defaults = [
2020-09-18 23:30:17 +00:00
'allow_filelist' => true , // show/hide the files tab
'allow_invalidate' => true , // give a link to invalidate files
'allow_reset' => true , // give option to reset the whole cache
'allow_realtime' => true , // give option to enable/disable real-time updates
'refresh_time' => 5 , // how often the data will refresh, in seconds
'size_precision' => 2 , // Digits after decimal point
'size_space' => false , // have '1MB' or '1 MB' when showing sizes
'charts' => true , // show gauge chart or just big numbers
'debounce_rate' => 250 , // milliseconds after key press to send keyup event when filtering
'per_page' => 200 , // How many results per page to show in the file list, false for no pagination
'cookie_name' => 'opcachegui' , // name of cookie
'cookie_ttl' => 365 , // days to store cookie
'highlight' => [
'memory' => true , // show the memory chart/big number
'hits' => true , // show the hit rate chart/big number
'keys' => true // show the keys used chart/big number
]
2014-11-25 13:20:33 +00:00
];
2013-04-26 10:56:54 +00:00
2020-09-18 23:30:17 +00:00
/**
* Service constructor .
* @ param array $options
*/
public function __construct ( array $options = [])
2014-11-23 01:07:38 +00:00
{
2020-02-22 19:51:12 +00:00
$this -> optimizationLevels = [
2020-09-18 23:30:17 +00:00
1 << 0 => 'CSE, STRING construction' ,
1 << 1 => 'Constant conversion and jumps' ,
1 << 2 => '++, +=, series of jumps' ,
1 << 3 => 'INIT_FCALL_BY_NAME -> DO_FCALL' ,
1 << 4 => 'CFG based optimization' ,
1 << 5 => 'DFA based optimization' ,
1 << 6 => 'CALL GRAPH optimization' ,
1 << 7 => 'SCCP (constant propagation)' ,
1 << 8 => 'TMP VAR usage' ,
1 << 9 => 'NOP removal' ,
1 << 10 => 'Merge equal constants' ,
1 << 11 => 'Adjust used stack' ,
1 << 12 => 'Remove unused variables' ,
1 << 13 => 'DCE (dead code elimination)' ,
1 << 14 => '(unsafe) Collect constants' ,
1 << 15 => 'Inline functions'
2020-02-22 19:51:12 +00:00
];
2020-02-22 20:15:06 +00:00
2015-05-08 16:04:40 +00:00
$this -> options = array_merge ( $this -> defaults , $options );
2014-11-23 01:07:38 +00:00
$this -> data = $this -> compileState ();
2013-08-09 14:06:45 +00:00
}
2013-04-08 15:40:06 +00:00
2020-09-18 23:30:17 +00:00
/**
* @ return $this
*/
public function handle () : Service
2014-11-23 01:07:38 +00:00
{
2020-09-18 23:30:17 +00:00
$response = function ( $success ) {
if ( $this -> isJsonRequest ()) {
echo '{ "success": "' . ( $success ? 'yes' : 'no' ) . '" }' ;
2014-11-25 17:11:37 +00:00
} else {
2020-09-18 23:30:17 +00:00
header ( 'Location: ?' );
2014-11-25 17:11:37 +00:00
}
2014-11-23 01:07:38 +00:00
exit ;
2020-09-18 23:30:17 +00:00
};
if ( isset ( $_GET [ 'reset' ]) && $this -> getOption ( 'allow_reset' )) {
$response ( $this -> resetCache ());
} else if ( isset ( $_GET [ 'invalidate' ]) && $this -> getOption ( 'allow_invalidate' )) {
$response ( $this -> resetCache ( $_GET [ 'invalidate' ]));
} else if ( isset ( $_GET [ 'invalidate_searched' ]) && $this -> getOption ( 'allow_invalidate' )) {
$response ( $this -> resetSearched ( $_GET [ 'invalidate_searched' ]));
} else if ( isset ( $_GET [ 'invalidate_searched' ]) && $this -> getOption ( 'allow_invalidate' )) {
$response ( $this -> resetSearched ( $_GET [ 'invalidate_searched' ]));
} else if ( $this -> isJsonRequest () && $this -> getOption ( 'allow_realtime' )) {
2020-11-28 16:16:58 +00:00
echo json_encode ( $this -> getData ( $_GET [ 'section' ] ? ? null ));
2016-02-10 00:09:49 +00:00
exit ;
2014-11-23 01:07:38 +00:00
}
2020-09-18 23:30:17 +00:00
return $this ;
2014-11-23 01:07:38 +00:00
}
2013-04-08 15:40:06 +00:00
2020-09-18 23:30:17 +00:00
/**
* @ param string | null $name
* @ return array | mixed | null
*/
public function getOption ( ? string $name = null )
2014-11-25 13:20:33 +00:00
{
if ( $name === null ) {
return $this -> options ;
}
return ( isset ( $this -> options [ $name ])
? $this -> options [ $name ]
: null
);
}
2020-09-18 23:30:17 +00:00
/**
* @ param string | null $section
* @ param string | null $property
* @ return array | mixed | null
*/
public function getData ( ? string $section = null , ? string $property = null )
2014-11-23 01:07:38 +00:00
{
if ( $section === null ) {
return $this -> data ;
}
$section = strtolower ( $section );
2014-11-26 21:42:23 +00:00
if ( isset ( $this -> data [ $section ])) {
if ( $property === null || ! isset ( $this -> data [ $section ][ $property ])) {
return $this -> data [ $section ];
}
return $this -> data [ $section ][ $property ];
}
return null ;
}
2020-09-18 23:30:17 +00:00
/**
* @ return bool
*/
public function canInvalidate () : bool
2014-11-26 21:42:23 +00:00
{
return ( $this -> getOption ( 'allow_invalidate' ) && function_exists ( 'opcache_invalidate' ));
2013-04-08 15:40:06 +00:00
}
2020-09-18 23:30:17 +00:00
/**
* @ param string | null $file
* @ return bool
*/
public function resetCache ( ? string $file = null ) : bool
2014-11-23 01:07:38 +00:00
{
$success = false ;
if ( $file === null ) {
$success = opcache_reset ();
} else if ( function_exists ( 'opcache_invalidate' )) {
$success = opcache_invalidate ( urldecode ( $file ), true );
}
if ( $success ) {
$this -> compileState ();
}
return $success ;
2013-04-08 15:40:06 +00:00
}
2020-09-18 23:30:17 +00:00
/**
* @ param string $search
* @ return bool
*/
public function resetSearched ( string $search ) : bool
{
$found = $success = 0 ;
$search = urldecode ( $search );
foreach ( $this -> getData ( 'files' ) as $file ) {
if ( strpos ( $file [ 'full_path' ], $search ) !== false ) {
++ $found ;
$success += ( int ) opcache_invalidate ( $file [ 'full_path' ], true );
}
}
if ( $success ) {
$this -> compileState ();
}
return $found === $success ;
}
/**
* @ param mixed $size
* @ return string
*/
protected function size ( $size ) : string
2015-05-08 16:04:40 +00:00
{
$i = 0 ;
2020-09-18 23:30:17 +00:00
$val = [ 'b' , 'KB' , 'MB' , 'GB' , 'TB' , 'PB' , 'EB' , 'ZB' , 'YB' ];
2015-05-08 16:04:40 +00:00
while (( $size / 1024 ) > 1 ) {
$size /= 1024 ;
++ $i ;
}
2020-09-18 23:30:17 +00:00
return sprintf ( '%.' . $this -> getOption ( 'size_precision' ) . 'f%s%s' ,
2016-02-09 23:51:09 +00:00
$size , ( $this -> getOption ( 'size_space' ) ? ' ' : '' ), $val [ $i ]
2015-05-08 16:04:40 +00:00
);
}
2020-09-18 23:30:17 +00:00
/**
* @ return bool
*/
protected function isJsonRequest () : bool
{
return ! empty ( $_SERVER [ 'HTTP_ACCEPT' ])
&& stripos ( $_SERVER [ 'HTTP_ACCEPT' ], 'application/json' ) !== false ;
}
/**
* @ return array
*/
protected function compileState () : array
2014-11-23 01:07:38 +00:00
{
2014-11-25 13:20:33 +00:00
$status = opcache_get_status ();
2014-11-23 01:07:38 +00:00
$config = opcache_get_configuration ();
2019-07-11 22:55:54 +00:00
$missingConfig = array_diff_key ( ini_get_all ( 'zend opcache' , false ), $config [ 'directives' ]);
if ( ! empty ( $missingConfig )) {
$config [ 'directives' ] = array_merge ( $config [ 'directives' ], $missingConfig );
}
2013-04-22 12:28:29 +00:00
2014-11-25 13:20:33 +00:00
$files = [];
2015-12-30 18:18:03 +00:00
if ( ! empty ( $status [ 'scripts' ]) && $this -> getOption ( 'allow_filelist' )) {
2020-09-18 23:30:17 +00:00
uasort ( $status [ 'scripts' ], function ( $a , $b ) {
2020-11-28 16:16:58 +00:00
return $a [ 'hits' ] < $b [ 'hits' ];
2014-11-23 01:07:38 +00:00
});
2014-11-25 13:20:33 +00:00
foreach ( $status [ 'scripts' ] as & $file ) {
$file [ 'full_path' ] = str_replace ( '\\' , '/' , $file [ 'full_path' ]);
$file [ 'readable' ] = [
2020-09-18 23:30:17 +00:00
'hits' => number_format ( $file [ 'hits' ]),
2015-05-08 16:04:40 +00:00
'memory_consumption' => $this -> size ( $file [ 'memory_consumption' ])
2014-11-25 13:20:33 +00:00
];
}
$files = array_values ( $status [ 'scripts' ]);
2014-11-23 01:07:38 +00:00
}
2020-02-22 17:26:43 +00:00
if ( $config [ 'directives' ][ 'opcache.file_cache_only' ] || ! empty ( $status [ 'file_cache_only' ])) {
2019-07-12 00:31:05 +00:00
$overview = false ;
} else {
$overview = array_merge (
$status [ 'memory_usage' ], $status [ 'opcache_statistics' ], [
'used_memory_percentage' => round ( 100 * (
2020-09-18 23:30:17 +00:00
( $status [ 'memory_usage' ][ 'used_memory' ] + $status [ 'memory_usage' ][ 'wasted_memory' ])
/ $config [ 'directives' ][ 'opcache.memory_consumption' ]
)),
2019-07-12 00:31:05 +00:00
'hit_rate_percentage' => round ( $status [ 'opcache_statistics' ][ 'opcache_hit_rate' ]),
2019-07-13 16:17:52 +00:00
'used_key_percentage' => round ( 100 * (
2020-09-18 23:30:17 +00:00
$status [ 'opcache_statistics' ][ 'num_cached_keys' ]
/ $status [ 'opcache_statistics' ][ 'max_cached_keys' ]
)),
2019-07-12 00:31:05 +00:00
'wasted_percentage' => round ( $status [ 'memory_usage' ][ 'current_wasted_percentage' ], 2 ),
'readable' => [
'total_memory' => $this -> size ( $config [ 'directives' ][ 'opcache.memory_consumption' ]),
'used_memory' => $this -> size ( $status [ 'memory_usage' ][ 'used_memory' ]),
'free_memory' => $this -> size ( $status [ 'memory_usage' ][ 'free_memory' ]),
'wasted_memory' => $this -> size ( $status [ 'memory_usage' ][ 'wasted_memory' ]),
'num_cached_scripts' => number_format ( $status [ 'opcache_statistics' ][ 'num_cached_scripts' ]),
'hits' => number_format ( $status [ 'opcache_statistics' ][ 'hits' ]),
'misses' => number_format ( $status [ 'opcache_statistics' ][ 'misses' ]),
'blacklist_miss' => number_format ( $status [ 'opcache_statistics' ][ 'blacklist_misses' ]),
'num_cached_keys' => number_format ( $status [ 'opcache_statistics' ][ 'num_cached_keys' ]),
'max_cached_keys' => number_format ( $status [ 'opcache_statistics' ][ 'max_cached_keys' ]),
'interned' => null ,
'start_time' => date ( 'Y-m-d H:i:s' , $status [ 'opcache_statistics' ][ 'start_time' ]),
'last_restart_time' => ( $status [ 'opcache_statistics' ][ 'last_restart_time' ] == 0
2014-11-23 01:07:38 +00:00
? 'never'
2016-09-23 15:39:17 +00:00
: date ( 'Y-m-d H:i:s' , $status [ 'opcache_statistics' ][ 'last_restart_time' ])
2014-11-23 01:07:38 +00:00
)
2019-07-12 00:31:05 +00:00
]
2014-11-23 01:07:38 +00:00
]
2019-07-12 00:31:05 +00:00
);
}
2018-02-08 20:23:48 +00:00
2020-09-18 23:30:17 +00:00
$preload = [];
if ( ! empty ( $status [ 'preload_statistics' ][ 'scripts' ]) && $this -> getOption ( 'allow_filelist' )) {
$preload = $status [ 'preload_statistics' ][ 'scripts' ];
sort ( $preload , SORT_STRING );
if ( $overview ) {
$overview [ 'preload_memory' ] = $status [ 'preload_statistics' ][ 'memory_consumption' ];
$overview [ 'readable' ][ 'preload_memory' ] = $this -> size ( $status [ 'preload_statistics' ][ 'memory_consumption' ]);
}
}
2017-07-31 13:25:14 +00:00
if ( ! empty ( $status [ 'interned_strings_usage' ])) {
$overview [ 'readable' ][ 'interned' ] = [
'buffer_size' => $this -> size ( $status [ 'interned_strings_usage' ][ 'buffer_size' ]),
'strings_used_memory' => $this -> size ( $status [ 'interned_strings_usage' ][ 'used_memory' ]),
'strings_free_memory' => $this -> size ( $status [ 'interned_strings_usage' ][ 'free_memory' ]),
'number_of_strings' => number_format ( $status [ 'interned_strings_usage' ][ 'number_of_strings' ])
];
}
2013-04-08 15:40:06 +00:00
2014-11-24 00:09:48 +00:00
$directives = [];
2014-11-23 01:07:38 +00:00
ksort ( $config [ 'directives' ]);
2014-11-24 00:09:48 +00:00
foreach ( $config [ 'directives' ] as $k => $v ) {
2019-07-11 18:04:14 +00:00
if ( in_array ( $k , [ 'opcache.max_file_size' , 'opcache.memory_consumption' ]) && $v ) {
$v = $this -> size ( $v ) . " ( { $v } ) " ;
2020-02-22 19:51:12 +00:00
} elseif ( $k == 'opcache.optimization_level' ) {
$levels = [];
foreach ( $this -> optimizationLevels as $level => $info ) {
if ( $level & $v ) {
$levels [] = $info ;
}
}
2020-09-18 23:30:17 +00:00
$v = $levels ? : 'none' ;
2019-07-11 18:04:14 +00:00
}
$directives [] = [
'k' => $k ,
'v' => $v
];
2014-11-24 00:09:48 +00:00
}
2014-11-23 01:07:38 +00:00
$version = array_merge (
$config [ 'version' ],
[
2020-09-18 23:30:17 +00:00
'php' => phpversion (),
'server' => $_SERVER [ 'SERVER_SOFTWARE' ] ? : '' ,
'host' => ( function_exists ( 'gethostname' )
2014-11-23 01:07:38 +00:00
? gethostname ()
: ( php_uname ( 'n' )
? : ( empty ( $_SERVER [ 'SERVER_NAME' ])
? $_SERVER [ 'HOST_NAME' ]
: $_SERVER [ 'SERVER_NAME' ]
)
)
2020-09-18 23:30:17 +00:00
),
'gui' => self :: VERSION
2014-11-23 01:07:38 +00:00
]
);
return [
2020-09-18 23:30:17 +00:00
'version' => $version ,
'overview' => $overview ,
'files' => $files ,
'preload' => $preload ,
2014-11-24 00:09:48 +00:00
'directives' => $directives ,
2020-09-18 23:30:17 +00:00
'blacklist' => $config [ 'blacklist' ],
'functions' => get_extension_funcs ( 'Zend OPcache' )
2014-11-23 01:07:38 +00:00
];
}
2013-04-08 15:40:06 +00:00
}
2020-09-18 23:30:17 +00:00
$opcache = ( new Service ( $options )) -> handle ();
2013-04-09 15:44:50 +00:00
2013-04-08 15:40:06 +00:00
?>
2017-02-10 15:22:49 +00:00
<! DOCTYPE html >
2013-04-08 15:40:06 +00:00
< html >
< head >
2017-02-10 15:22:49 +00:00
< meta charset = " UTF-8 " >
2013-04-08 15:40:06 +00:00
< meta name = " viewport " content = " width=device-width,initial-scale=1.0 " >
2020-09-18 23:30:17 +00:00
< title > OPcache statistics on < ? = $opcache -> getData ( 'version' , 'host' ); ?> </title>
2020-09-25 18:23:43 +00:00
< script src = " //unpkg.com/react@16/umd/react.development.js " crossorigin ></ script >
< script src = " //unpkg.com/react-dom@16/umd/react-dom.development.js " crossorigin ></ script >
< script src = " //unpkg.com/axios/dist/axios.min.js " crossorigin ></ script >
2013-04-08 15:40:06 +00:00
< style type = " text/css " >
2020-11-28 16:16:58 +00:00
: root { -- opcache - gui - graph - track - fill - color : #6CA6EF;--opcache-gui-graph-track-background-color: rgba(229,231,231,0.905882)}.opcache-gui{font-family:sans-serif;font-size:90%;padding:0;margin:0}.opcache-gui .hide{display:none}.opcache-gui .sr-only{border:0 !important;clip:rect(1px, 1px, 1px, 1px) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;margin:-1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.opcache-gui .main-nav{padding-top:20px}.opcache-gui .nav-tab-list{list-style-type:none;padding-left:8px;margin:0;border-bottom:1px solid #CCC}.opcache-gui .nav-tab{display:inline-block;margin:0 0 -1px 0;padding:15px 30px;border:1px solid transparent;border-bottom-color:#CCC;text-decoration:none;background-color:#fff;cursor:pointer;user-select:none}.opcache-gui .nav-tab:hover{background-color:#F4F4F4;text-decoration:underline}.opcache-gui .nav-tab.active{border:1px solid #CCC;border-bottom-color:#fff;border-top:3px solid #6CA6EF}.opcache-gui .nav-tab.active:hover{background-color:initial}.opcache-gui .nav-tab:focus{outline:0;text-decoration:underline}.opcache-gui .nav-tab-link-reset{background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="1.5em" height="1.5em" viewBox="0 0 24 24"><path d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" fill="rgb(98, 98, 98)"/></svg>')}.opcache-gui .nav-tab-link-reset.is-resetting{background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="1.5em" height="1.5em" viewBox="0 0 24 24"><path d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" fill="rgb(0, 186, 0)"/></svg>')}.opcache-gui .nav-tab-link-realtime{background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="1.5em" height="1.5em" viewBox="0 0 24 24"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8s8 3.58 8 8s-3.58 8-8 8z" fill="rgb(98, 98, 98)"/><path d="M12.5 7H11v6l5.25 3.15l.75-1.23l-4.5-2.67z" fill="rgb(98, 98, 98)"/></svg>')}.opcache-gui .nav-tab-link-realtime.live-update{background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="1.5em" height="1.5em" viewBox="0 0 24 24"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8s8 3.58 8 8s-3.58 8-8 8z" fill="rgb(0, 186, 0)"/><path d="M12.5 7H11v6l5.25 3.15l.75-1.23l-4.5-2.67z" fill="rgb(0, 186, 0)"/></svg>')}.opcache-gui .nav-tab-link-reset,.opcache-gui .nav-tab-link-realtime{position:relative;padding-left:50px}.opcache-gui .nav-tab-link-reset.pulse::before,.opcache-gui .nav-tab-link-realtime.pulse::before{content:"";position:absolute;top:12px;left:25px;width:18px;height:18px;z-index:10;opacity:0;background-color:transparent;border:2px solid #00ba00;border-radius:100%;animation:pulse 2s linear infinite}.opcache-gui .tab-content{padding:2em}.opcache-gui .tab-content-overview-counts{width:270px;float:right}.opcache-gui .tab-content-overview-info{margin-right:280px}.opcache-gui .graph-widget{max-width:100%;height:auto;margin:0 auto;display:flex;position:relative}.opcache-gui .graph-widget .widget-value{display:flex;align-items:center;justify-content:center;text-align:center;position:absolute;top:0;width:100%;height:100%;margin:0
2013-04-08 15:40:06 +00:00
</ style >
</ head >
2020-04-07 21:43:20 +00:00
< body style = " padding: 0; margin: 0; " >
2020-09-18 23:30:17 +00:00
< div class = " opcache-gui " id = " interface " />
< script type = " text/javascript " >
function _extends () { _extends = Object . assign || function ( target ) { for ( var i = 1 ; i < arguments . length ; i ++ ) { var source = arguments [ i ]; for ( var key in source ) { if ( Object . prototype . hasOwnProperty . call ( source , key )) { target [ key ] = source [ key ]; } } } return target ; }; return _extends . apply ( this , arguments ); }
function _defineProperty ( obj , key , value ) { if ( key in obj ) { Object . defineProperty ( obj , key , { value : value , enumerable : true , configurable : true , writable : true }); } else { obj [ key ] = value ; } return obj ; }
class Interface extends React . Component {
constructor ( props ) {
super ( props );
_defineProperty ( this , " startTimer " , () => {
this . setState ({
realtime : true
});
this . polling = setInterval (() => {
this . setState ({
fetching : true ,
resetting : false
2016-02-23 15:46:34 +00:00
});
2020-09-18 23:30:17 +00:00
axios . get ( '?' , {
time : Date . now ()
}) . then ( response => {
this . setState ({
opstate : response . data
});
});
}, this . props . realtimeRefresh * 1000 );
});
_defineProperty ( this , " stopTimer " , () => {
this . setState ({
realtime : false ,
resetting : false
});
clearInterval ( this . polling );
});
_defineProperty ( this , " realtimeHandler " , () => {
const realtime = ! this . state . realtime ;
if ( ! realtime ) {
this . stopTimer ();
this . removeCookie ();
} else {
this . startTimer ();
this . setCookie ();
}
});
_defineProperty ( this , " resetHandler " , () => {
if ( this . state . realtime ) {
this . setState ({
resetting : true
});
axios . get ( '?' , {
params : {
reset : 1
}
}) . then ( response => {
console . log ( 'success: ' , response . data );
});
} else {
window . location . href = '?reset=1' ;
}
});
_defineProperty ( this , " 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' : ''}` ;
});
_defineProperty ( this , " removeCookie " , () => {
document . cookie = `${this.props.cookie.name}=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/${this.isSecure ? ';secure' : ''}` ;
});
_defineProperty ( this , " getCookie " , () => {
const v = document . cookie . match ( `(^|;) ?${this.props.cookie.name}=([^;]*)(;|$)` );
return v ? !! v [ 2 ] : false ;
});
this . state = {
realtime : this . getCookie (),
resetting : false ,
opstate : props . opstate
2016-02-23 15:46:34 +00:00
};
2020-09-18 23:30:17 +00:00
this . polling = false ;
this . isSecure = window . location . protocol === 'https:' ;
if ( this . getCookie ()) {
this . startTimer ();
2015-05-08 16:04:40 +00:00
}
2020-09-18 23:30:17 +00:00
}
render () {
const {
opstate ,
realtimeRefresh ,
... otherProps
} = this . props ;
return /*#__PURE__*/ React . createElement ( React . Fragment , null , /*#__PURE__*/ React . createElement ( " header " , null , /*#__PURE__*/ React . createElement ( MainNavigation , _extends ({}, otherProps , {
opstate : this . state . opstate ,
realtime : this . state . realtime ,
resetting : this . state . resetting ,
realtimeHandler : this . realtimeHandler ,
resetHandler : this . resetHandler
}))), /*#__PURE__*/ React . createElement ( Footer , {
version : this . props . opstate . version . gui
}));
}
}
function MainNavigation ( props ) {
return /*#__PURE__*/ React . createElement ( " nav " , {
className : " main-nav "
}, /*#__PURE__*/ React . createElement ( Tabs , null , /*#__PURE__*/ React . createElement ( " div " , {
label : " Overview " ,
tabId : " overview " ,
tabIndex : 1
}, /*#__PURE__*/ React . createElement ( OverviewCounts , {
overview : props . opstate . overview ,
highlight : props . highlight ,
useCharts : props . useCharts
}), /*#__PURE__*/ React . createElement ( " div " , {
id : " info " ,
className : " tab-content-overview-info "
}, /*#__PURE__*/ React . createElement ( 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
}), /*#__PURE__*/ React . createElement ( Directives , {
directives : props . opstate . directives
}), /*#__PURE__*/ React . createElement ( Functions , {
functions : props . opstate . functions
}))), props . allow . filelist && /*#__PURE__*/ React . createElement ( " div " , {
label : " Cached " ,
tabId : " cached " ,
tabIndex : 2
}, /*#__PURE__*/ React . createElement ( 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
})), props . allow . filelist && props . opstate . blacklist . length && /*#__PURE__*/ React . createElement ( " div " , {
label : " Ignored " ,
tabId : " ignored " ,
tabIndex : 3
}, /*#__PURE__*/ React . createElement ( IgnoredFiles , {
perPageLimit : props . perPageLimit ,
allFiles : props . opstate . blacklist ,
allow : {
fileList : props . allow . filelist
}
})), props . allow . filelist && props . opstate . preload . length && /*#__PURE__*/ React . createElement ( " div " , {
label : " Preloaded " ,
tabId : " preloaded " ,
tabIndex : 4
}, /*#__PURE__*/ React . createElement ( PreloadedFiles , {
perPageLimit : props . perPageLimit ,
allFiles : props . opstate . preload ,
allow : {
fileList : props . allow . filelist
}
})), props . allow . reset && /*#__PURE__*/ React . createElement ( " div " , {
label : " Reset cache " ,
tabId : " resetCache " ,
className : `nav-tab-link-reset${props.resetting ? ' is-resetting pulse' : ''}` ,
handler : props . resetHandler ,
tabIndex : 5
}), props . allow . realtime && /*#__PURE__*/ React . createElement ( " div " , {
label : `${props.realtime ? 'Disable' : 'Enable'} real-time update` ,
tabId : " toggleRealtime " ,
className : `nav-tab-link-realtime${props.realtime ? ' live-update pulse' : ''}` ,
handler : props . realtimeHandler ,
tabIndex : 6
})));
}
class Tabs extends React . Component {
constructor ( props ) {
super ( props );
_defineProperty ( this , " onClickTabItem " , tab => {
this . setState ({
activeTab : tab
});
});
this . state = {
activeTab : this . props . children [ 0 ] . props . label
};
}
render () {
const {
onClickTabItem ,
state : {
activeTab
}
} = this ;
const children = this . props . children . filter ( Boolean );
return /*#__PURE__*/ React . createElement ( React . Fragment , null , /*#__PURE__*/ React . createElement ( " ul " , {
className : " nav-tab-list "
}, children . map ( child => {
const {
tabId ,
label ,
className ,
handler ,
tabIndex
} = child . props ;
return /*#__PURE__*/ React . createElement ( Tab , {
activeTab : activeTab ,
key : tabId ,
label : label ,
onClick : handler || onClickTabItem ,
className : className ,
tabIndex : tabIndex ,
tabId : tabId
});
})), /*#__PURE__*/ React . createElement ( " div " , {
className : " tab-content "
}, children . map ( child => /*#__PURE__*/ React . createElement ( " div " , {
key : child . props . label ,
style : {
display : child . props . label === activeTab ? 'block' : 'none'
},
id : `${child.props.tabId}-content`
}, child . props . children ))));
}
}
class Tab extends React . Component {
constructor ( ... args ) {
super ( ... args );
_defineProperty ( this , " 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 /*#__PURE__*/ React . createElement ( " li " , {
className : className ,
onClick : onClick ,
tabIndex : tabIndex ,
role : " tab " ,
" aria-controls " : `${tabId}-content`
}, label );
}
}
function OverviewCounts ( props ) {
if ( props . overview === false ) {
return /*#__PURE__*/ React . createElement ( " p " , {
class : " file-cache-only "
}, " You have " , /*#__PURE__*/ React . createElement ( " i " , null , " opcache.file_cache_only " ), " turned on. As a result, the memory information is not available. Statistics and file list may also not be returned by " , /*#__PURE__*/ React . createElement ( " i " , null , " opcache_get_statistics() " ), " . " );
}
const graphList = [{
id : 'memoryUsageCanvas' ,
title : 'memory' ,
show : props . highlight . memory ,
value : props . overview . used_memory_percentage
}, {
id : 'hitRateCanvas' ,
title : 'hit rate' ,
show : props . highlight . hits ,
value : props . overview . hit_rate_percentage
}, {
id : 'keyUsageCanvas' ,
title : 'keys' ,
show : props . highlight . keys ,
value : props . overview . used_key_percentage
}];
return /*#__PURE__*/ React . createElement ( " div " , {
id : " counts " ,
className : " tab-content-overview-counts "
}, graphList . map ( graph => {
if ( ! graph . show ) {
return null ;
}
return /*#__PURE__*/ React . createElement ( " div " , {
className : " widget-panel " ,
key : graph . id
}, /*#__PURE__*/ React . createElement ( " h3 " , {
className : " widget-header "
}, graph . title ), /*#__PURE__*/ React . createElement ( UsageGraph , {
charts : props . useCharts ,
value : graph . value ,
gaugeId : graph . id
}));
}), /*#__PURE__*/ React . createElement ( 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
}), /*#__PURE__*/ React . createElement ( 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
}), props . overview . readable . interned && /*#__PURE__*/ React . createElement ( 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
}));
}
function GeneralInfo ( props ) {
return /*#__PURE__*/ React . createElement ( " table " , {
className : " tables general-info-table "
}, /*#__PURE__*/ React . createElement ( " thead " , null , /*#__PURE__*/ React . createElement ( " tr " , null , /*#__PURE__*/ React . createElement ( " th " , {
colSpan : " 2 "
}, " General info " ))), /*#__PURE__*/ React . createElement ( " tbody " , null , /*#__PURE__*/ React . createElement ( " tr " , null , /*#__PURE__*/ React . createElement ( " td " , null , " Zend OPcache " ), /*#__PURE__*/ React . createElement ( " td " , null , props . version . version )), /*#__PURE__*/ React . createElement ( " tr " , null , /*#__PURE__*/ React . createElement ( " td " , null , " PHP " ), /*#__PURE__*/ React . createElement ( " td " , null , props . version . php )), /*#__PURE__*/ React . createElement ( " tr " , null , /*#__PURE__*/ React . createElement ( " td " , null , " Host " ), /*#__PURE__*/ React . createElement ( " td " , null , props . version . host )), /*#__PURE__*/ React . createElement ( " tr " , null , /*#__PURE__*/ React . createElement ( " td " , null , " Server Software " ), /*#__PURE__*/ React . createElement ( " td " , null , props . version . server )), props . start ? /*#__PURE__*/ React . createElement ( " tr " , null , /*#__PURE__*/ React . createElement ( " td " , null , " Start time " ), /*#__PURE__*/ React . createElement ( " td " , null , props . start )) : null , props . reset ? /*#__PURE__*/ React . createElement ( " tr " , null , /*#__PURE__*/ React . createElement ( " td " , null , " Last reset " ), /*#__PURE__*/ React . createElement ( " td " , null , props . reset )) : null ));
}
function Directives ( props ) {
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 ) {
vShow = React . createElement ( 'i' , {}, directive . v . toString ());
} else if ( directive . v === '' ) {
vShow = React . createElement ( 'i' , {}, 'no value' );
} else {
if ( Array . isArray ( directive . v )) {
vShow = directive . v . map (( item , key ) => {
return /*#__PURE__*/ React . createElement ( " span " , {
key : key
}, item , /*#__PURE__*/ React . createElement ( " br " , null ));
2014-11-25 17:11:37 +00:00
});
2020-09-18 23:30:17 +00:00
} else {
vShow = directive . v ;
}
}
return /*#__PURE__*/ React . createElement ( " tr " , {
key : directive . k
}, /*#__PURE__*/ React . createElement ( " td " , {
title : 'View ' + directive . k + ' manual entry'
}, /*#__PURE__*/ React . createElement ( " a " , {
href : 'http://php.net/manual/en/opcache.configuration.php#ini.' + directive . k . replace ( / _ / g , '-' ),
target : " _blank "
}, dShow )), /*#__PURE__*/ React . createElement ( " td " , null , vShow ));
});
return /*#__PURE__*/ React . createElement ( " table " , {
className : " tables directives-table "
}, /*#__PURE__*/ React . createElement ( " thead " , null , /*#__PURE__*/ React . createElement ( " tr " , null , /*#__PURE__*/ React . createElement ( " th " , {
colSpan : " 2 "
}, " Directives " ))), /*#__PURE__*/ React . createElement ( " tbody " , null , directiveNodes ));
}
function Functions ( props ) {
return /*#__PURE__*/ React . createElement ( " div " , {
id : " functions "
}, /*#__PURE__*/ React . createElement ( " table " , {
className : " tables "
}, /*#__PURE__*/ React . createElement ( " thead " , null , /*#__PURE__*/ React . createElement ( " tr " , null , /*#__PURE__*/ React . createElement ( " th " , null , " Available functions " ))), /*#__PURE__*/ React . createElement ( " tbody " , null , props . functions . map ( f => /*#__PURE__*/ React . createElement ( " tr " , {
key : f
}, /*#__PURE__*/ React . createElement ( " td " , null , /*#__PURE__*/ React . createElement ( " a " , {
href : " http://php.net/ " + f ,
title : " View manual page " ,
target : " _blank "
}, f )))))));
}
function UsageGraph ( props ) {
const percentage = Math . round ( 3.6 * props . value / 360 * 100 );
return props . charts ? /*#__PURE__*/ React . createElement ( 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
}) : /*#__PURE__*/ React . createElement ( " p " , {
className : " widget-value "
}, /*#__PURE__*/ React . createElement ( " span " , {
className : " large "
}, percentage ), /*#__PURE__*/ React . createElement ( " span " , null , " % " ));
}
/**
* 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 );
_defineProperty ( this , " initAnimation " , () => {
this . setState ({
animationInited : true
});
});
_defineProperty ( this , " getProgress " , () => {
const {
initialAnimation ,
progress
} = this . props ;
const {
animationInited
} = this . state ;
return initialAnimation && ! animationInited ? 0 : progress ;
});
_defineProperty ( this , " 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 ;
});
_defineProperty ( this , " 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}` ;
});
_defineProperty ( this , " getTrackStrokeDashArray " , ( strokeLength , circumference ) => {
const {
initialAnimation
} = this . props ;
const {
animationInited
} = this . state ;
if ( initialAnimation && ! animationInited ) return `0, ${circumference}` ;
return `${strokeLength}, ${circumference}` ;
});
_defineProperty ( this , " 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 ;
});
_defineProperty ( this , " getPointerAngle " , () => {
const {
cut ,
counterClockwise ,
steps
} = this . props ;
const progress = this . getProgress ();
return counterClockwise ? ( 360 - cut ) / steps * ( steps - progress ) : ( 360 - cut ) / steps * progress ;
});
this . state = {
animationInited : false
};
}
componentDidMount () {
const {
initialAnimation ,
initialAnimationDelay
} = this . props ;
if ( initialAnimation ) setTimeout ( this . initAnimation , initialAnimationDelay );
}
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 /*#__PURE__*/ React . createElement ( " figure " , {
className : `graph-widget` ,
style : {
width : `${width || 250}px`
},
" data-value " : progress ,
id : this . props . guageId
}, /*#__PURE__*/ React . createElement ( " svg " , {
width : width ,
height : width ,
viewBox : `0 0 ${width} ${width}` ,
style : {
transform : `rotate(${rotate}deg)`
}
}, trackStrokeWidth > 0 && /*#__PURE__*/ React . createElement ( " 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 && /*#__PURE__*/ React . createElement ( " 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 && /*#__PURE__*/ React . createElement ( " 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
}
})), /*#__PURE__*/ React . createElement ( " figcaption " , {
className : `widget-value`
}, progress , " % " ));
}
}
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 /*#__PURE__*/ React . createElement ( " div " , {
className : " widget-panel "
}, /*#__PURE__*/ React . createElement ( " h3 " , {
className : " widget-header "
}, " memory usage " ), /*#__PURE__*/ React . createElement ( " div " , {
className : " widget-value widget-info "
}, /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " total memory: " ), " " , props . total ), /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " used memory: " ), " " , props . used ), /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " free memory: " ), " " , props . free ), props . preload && /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " preload memory: " ), " " , props . preload ), /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " wasted memory: " ), " " , props . wasted , " ( " , props . wastedPercent , " %) " )));
}
function StatisticsPanel ( props ) {
return /*#__PURE__*/ React . createElement ( " div " , {
className : " widget-panel "
}, /*#__PURE__*/ React . createElement ( " h3 " , {
className : " widget-header "
}, " opcache statistics " ), /*#__PURE__*/ React . createElement ( " div " , {
className : " widget-value widget-info "
}, /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " number of cached files: " ), " " , props . num_cached_scripts ), /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " number of hits: " ), " " , props . hits ), /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " number of misses: " ), " " , props . misses ), /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " blacklist misses: " ), " " , props . blacklist_miss ), /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " number of cached keys: " ), " " , props . num_cached_keys ), /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " max cached keys: " ), " " , props . max_cached_keys )));
}
function InternedStringsPanel ( props ) {
return /*#__PURE__*/ React . createElement ( " div " , {
className : " widget-panel "
}, /*#__PURE__*/ React . createElement ( " h3 " , {
className : " widget-header "
}, " interned strings usage " ), /*#__PURE__*/ React . createElement ( " div " , {
className : " widget-value widget-info "
}, /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " buffer size: " ), " " , props . buffer_size ), /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " used memory: " ), " " , props . strings_used_memory ), /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " free memory: " ), " " , props . strings_free_memory ), /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " b " , null , " number of strings: " ), " " , props . number_of_strings )));
}
class CachedFiles extends React . Component {
constructor ( props ) {
super ( props );
_defineProperty ( this , " setSearchTerm " , debounce ( searchTerm => {
this . setState ({
searchTerm ,
refreshPagination : ! this . state . refreshPagination
});
}, this . props . debounceRate ));
_defineProperty ( this , " onPageChanged " , currentPage => {
this . setState ({
currentPage
});
});
_defineProperty ( this , " handleInvalidate " , e => {
e . preventDefault ();
if ( this . props . realtime ) {
axios . get ( '?' , {
params : {
invalidate_searched : this . state . searchTerm
}
}) . then ( response => {
console . log ( 'success: ' , response . data );
2014-11-25 17:11:37 +00:00
});
2020-09-18 23:30:17 +00:00
} else {
window . location . href = e . currentTarget . href ;
}
});
2020-11-28 16:16:58 +00:00
_defineProperty ( this , " changeSort " , e => {
this . setState ({
[ e . target . name ] : e . target . value
});
});
_defineProperty ( this , " 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
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
};
}
render () {
if ( ! this . props . allow . fileList ) {
return null ;
}
if ( this . props . allFiles . length === 0 ) {
return /*#__PURE__*/ React . createElement ( " p " , null , " No files have been cached or you have " , /*#__PURE__*/ React . createElement ( " i " , null , " opcache.file_cache_only " ), " turned on " );
}
const {
searchTerm ,
currentPage
} = this . state ;
const offset = ( currentPage - 1 ) * this . props . perPageLimit ;
const filesInSearch = searchTerm ? this . props . allFiles . filter ( file => {
return ! ( file . full_path . indexOf ( searchTerm ) == - 1 );
}) : 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 ;
return /*#__PURE__*/ React . createElement ( " div " , null , /*#__PURE__*/ React . createElement ( " form " , {
action : " # "
}, /*#__PURE__*/ React . createElement ( " label " , {
htmlFor : " frmFilter "
}, " Start typing to filter on script path " ), /*#__PURE__*/ React . createElement ( " br " , null ), /*#__PURE__*/ React . createElement ( " input " , {
type : " text " ,
name : " filter " ,
id : " frmFilter " ,
className : " file-filter " ,
onChange : e => {
this . setSearchTerm ( e . target . value );
}
})), /*#__PURE__*/ React . createElement ( " h3 " , null , allFilesTotal , " files cached " , showingTotal !== allFilesTotal && `, ${showingTotal} showing due to filter '${this.state.searchTerm}'` ), this . state . searchTerm && showingTotal !== allFilesTotal && /*#__PURE__*/ React . createElement ( " p " , null , /*#__PURE__*/ React . createElement ( " a " , {
href : `?invalidate_searched=${encodeURIComponent(this.state.searchTerm)}` ,
onClick : this . handleInvalidate
2020-11-28 16:16:58 +00:00
}, " Invalidate all matching files " )), /*#__PURE__*/ React . createElement ( " div " , {
className : " paginate-filter "
}, this . doPagination && /*#__PURE__*/ React . createElement ( Pagination , {
2020-09-18 23:30:17 +00:00
totalRecords : filesInSearch . length ,
pageLimit : this . props . perPageLimit ,
pageNeighbours : 2 ,
onPageChanged : this . onPageChanged ,
refresh : this . state . refreshPagination
2020-11-28 16:16:58 +00:00
}), /*#__PURE__*/ React . createElement ( " nav " , {
className : " filter " ,
" aria-label " : " Sort order "
}, /*#__PURE__*/ React . createElement ( " select " , {
name : " sortBy " ,
onChange : this . changeSort ,
value : this . state . sortBy
}, /*#__PURE__*/ React . createElement ( " option " , {
value : " last_used_timestamp "
}, " Last used " ), /*#__PURE__*/ React . createElement ( " option " , {
value : " full_path "
}, " Path " ), /*#__PURE__*/ React . createElement ( " option " , {
value : " hits "
}, " Number of hits " ), /*#__PURE__*/ React . createElement ( " option " , {
value : " memory_consumption "
}, " Memory consumption " )), /*#__PURE__*/ React . createElement ( " select " , {
name : " sortDir " ,
onChange : this . changeSort ,
value : this . state . sortDir
}, /*#__PURE__*/ React . createElement ( " option " , {
value : " desc "
}, " Descending " ), /*#__PURE__*/ React . createElement ( " option " , {
value : " asc "
}, " Ascending " )))), /*#__PURE__*/ React . createElement ( " table " , {
2020-09-18 23:30:17 +00:00
className : " tables cached-list-table "
}, /*#__PURE__*/ React . createElement ( " thead " , null , /*#__PURE__*/ React . createElement ( " tr " , null , /*#__PURE__*/ React . createElement ( " th " , null , " Script " ))), /*#__PURE__*/ React . createElement ( " tbody " , null , filesInPage . map (( file , index ) => {
return /*#__PURE__*/ React . createElement ( CachedFile , _extends ({
key : file . full_path ,
canInvalidate : this . props . allow . invalidate ,
realtime : this . props . realtime
}, file ));
}))));
}
}
class CachedFile extends React . Component {
constructor ( ... args ) {
super ( ... args );
_defineProperty ( this , " handleInvalidate " , e => {
e . preventDefault ();
if ( this . props . realtime ) {
axios . get ( '?' , {
params : {
invalidate : e . currentTarget . getAttribute ( 'data-file' )
}
}) . then ( response => {
console . log ( 'success: ' , response . data );
2014-11-25 17:11:37 +00:00
});
2020-09-18 23:30:17 +00:00
} else {
window . location . href = e . currentTarget . href ;
}
2014-11-25 17:11:37 +00:00
});
2020-09-18 23:30:17 +00:00
}
2014-11-25 17:11:37 +00:00
2020-09-18 23:30:17 +00:00
render () {
return /*#__PURE__*/ React . createElement ( " tr " , {
" data-path " : this . props . full_path . toLowerCase ()
}, /*#__PURE__*/ React . createElement ( " td " , null , /*#__PURE__*/ React . createElement ( " span " , {
className : " file-pathname "
}, this . props . full_path ), /*#__PURE__*/ React . createElement ( " span " , {
className : " file-metainfo "
}, /*#__PURE__*/ React . createElement ( " b " , null , " hits: " ), /*#__PURE__*/ React . createElement ( " span " , null , this . props . readable . hits , " , " ), /*#__PURE__*/ React . createElement ( " b " , null , " memory: " ), /*#__PURE__*/ React . createElement ( " span " , null , this . props . readable . memory_consumption , " , " ), /*#__PURE__*/ React . createElement ( " b " , null , " last used: " ), /*#__PURE__*/ React . createElement ( " span " , null , this . props . last_used )), ! this . props . timestamp && /*#__PURE__*/ React . createElement ( " span " , {
className : " invalid file-metainfo "
}, " - has been invalidated " ), this . props . canInvalidate && /*#__PURE__*/ React . createElement ( " span " , null , " , \xA0 " , /*#__PURE__*/ React . createElement ( " a " , {
className : " file-metainfo " ,
href : '?invalidate=' + this . props . full_path ,
" data-file " : this . props . full_path ,
onClick : this . handleInvalidate
}, " force file invalidation " ))));
}
}
class IgnoredFiles extends React . Component {
constructor ( props ) {
super ( props );
_defineProperty ( this , " onPageChanged " , currentPage => {
this . setState ({
currentPage
});
2017-07-31 13:25:14 +00:00
});
2020-09-18 23:30:17 +00:00
this . doPagination = typeof props . perPageLimit === " number " && props . perPageLimit > 0 ;
this . state = {
currentPage : 1 ,
refreshPagination : 0
};
}
render () {
if ( ! this . props . allow . fileList ) {
return null ;
}
if ( this . props . allFiles . length === 0 ) {
return /*#__PURE__*/ React . createElement ( " p " , null , " No files have been ignored via " , /*#__PURE__*/ React . createElement ( " i " , null , " opcache.blacklist_filename " ));
}
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 /*#__PURE__*/ React . createElement ( " div " , null , /*#__PURE__*/ React . createElement ( " h3 " , null , allFilesTotal , " ignore file locations " ), this . doPagination && /*#__PURE__*/ React . createElement ( Pagination , {
totalRecords : allFilesTotal ,
pageLimit : this . props . perPageLimit ,
pageNeighbours : 2 ,
onPageChanged : this . onPageChanged ,
refresh : this . state . refreshPagination
}), /*#__PURE__*/ React . createElement ( " table " , {
className : " tables ignored-list-table "
}, /*#__PURE__*/ React . createElement ( " thead " , null , /*#__PURE__*/ React . createElement ( " tr " , null , /*#__PURE__*/ React . createElement ( " th " , null , " Path " ))), /*#__PURE__*/ React . createElement ( " tbody " , null , filesInPage . map (( file , index ) => {
return /*#__PURE__*/ React . createElement ( " tr " , {
key : file
}, /*#__PURE__*/ React . createElement ( " td " , null , file ));
}))));
}
}
class PreloadedFiles extends React . Component {
constructor ( props ) {
super ( props );
_defineProperty ( this , " onPageChanged " , currentPage => {
this . setState ({
currentPage
});
2015-05-08 16:04:40 +00:00
});
2020-09-18 23:30:17 +00:00
this . doPagination = typeof props . perPageLimit === " number " && props . perPageLimit > 0 ;
this . state = {
currentPage : 1 ,
refreshPagination : 0
};
}
render () {
if ( ! this . props . allow . fileList ) {
return null ;
}
if ( this . props . allFiles . length === 0 ) {
return /*#__PURE__*/ React . createElement ( " p " , null , " No files have been preloaded " , /*#__PURE__*/ React . createElement ( " i " , null , " opcache.preload " ));
}
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 /*#__PURE__*/ React . createElement ( " div " , null , /*#__PURE__*/ React . createElement ( " h3 " , null , allFilesTotal , " preloaded files " ), this . doPagination && /*#__PURE__*/ React . createElement ( Pagination , {
totalRecords : allFilesTotal ,
pageLimit : this . props . perPageLimit ,
pageNeighbours : 2 ,
onPageChanged : this . onPageChanged ,
refresh : this . state . refreshPagination
}), /*#__PURE__*/ React . createElement ( " table " , {
className : " tables preload-list-table "
}, /*#__PURE__*/ React . createElement ( " thead " , null , /*#__PURE__*/ React . createElement ( " tr " , null , /*#__PURE__*/ React . createElement ( " th " , null , " Path " ))), /*#__PURE__*/ React . createElement ( " tbody " , null , filesInPage . map (( file , index ) => {
return /*#__PURE__*/ React . createElement ( " tr " , {
key : file
}, /*#__PURE__*/ React . createElement ( " td " , null , file ));
}))));
}
}
class Pagination extends React . Component {
constructor ( props ) {
super ( props );
_defineProperty ( this , " gotoPage " , page => {
const {
onPageChanged = f => f
} = this . props ;
const currentPage = Math . max ( 0 , Math . min ( page , this . totalPages ()));
this . setState ({
currentPage
}, () => onPageChanged ( currentPage ));
2017-07-31 13:25:14 +00:00
});
2020-09-18 23:30:17 +00:00
_defineProperty ( this , " totalPages " , () => {
return Math . ceil ( this . props . totalRecords / this . props . pageLimit );
2017-07-31 13:25:14 +00:00
});
2020-09-18 23:30:17 +00:00
_defineProperty ( this , " handleClick " , ( page , evt ) => {
evt . preventDefault ();
this . gotoPage ( page );
2014-11-24 00:09:48 +00:00
});
2020-09-18 23:30:17 +00:00
_defineProperty ( this , " handleJumpLeft " , evt => {
evt . preventDefault ();
this . gotoPage ( this . state . currentPage - this . pageNeighbours * 2 - 1 );
2014-11-24 00:09:48 +00:00
});
2020-09-18 23:30:17 +00:00
_defineProperty ( this , " handleJumpRight " , evt => {
evt . preventDefault ();
this . gotoPage ( this . state . currentPage + this . pageNeighbours * 2 + 1 );
2014-11-24 00:09:48 +00:00
});
2020-09-18 23:30:17 +00:00
_defineProperty ( this , " handleMoveLeft " , evt => {
evt . preventDefault ();
this . gotoPage ( this . state . currentPage - 1 );
2014-11-25 13:20:33 +00:00
});
2020-09-18 23:30:17 +00:00
_defineProperty ( this , " handleMoveRight " , evt => {
evt . preventDefault ();
this . gotoPage ( this . state . currentPage + 1 );
2014-11-26 11:13:39 +00:00
});
2020-09-18 23:30:17 +00:00
_defineProperty ( this , " range " , ( from , to , step = 1 ) => {
let i = from ;
const range = [];
while ( i <= to ) {
range . push ( i );
i += step ;
}
return range ;
});
_defineProperty ( this , " 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 ];
2014-11-25 17:11:37 +00:00
}
2020-09-18 23:30:17 +00:00
return [ 1 , ... pages , totalPages ];
}
return this . range ( 1 , totalPages );
2014-11-26 11:13:39 +00:00
});
2014-11-25 17:11:37 +00:00
2020-09-18 23:30:17 +00:00
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 );
}
}
render () {
if ( ! this . props . totalRecords || this . totalPages () === 1 ) {
return null ;
}
const {
currentPage
} = this . state ;
const pages = this . fetchPageNumbers ();
return /*#__PURE__*/ React . createElement ( " nav " , {
" aria-label " : " File list pagination "
}, /*#__PURE__*/ React . createElement ( " ul " , {
className : " pagination "
}, pages . map (( page , index ) => {
if ( page === " LEFT " ) {
return /*#__PURE__*/ React . createElement ( React . Fragment , {
key : index
}, /*#__PURE__*/ React . createElement ( " li " , {
className : " page-item arrow "
}, /*#__PURE__*/ React . createElement ( " a " , {
className : " page-link " ,
href : " # " ,
" aria-label " : " Previous " ,
onClick : this . handleJumpLeft
}, /*#__PURE__*/ React . createElement ( " span " , {
" aria-hidden " : " true "
}, " \ u219E " ), /*#__PURE__*/ React . createElement ( " span " , {
className : " sr-only "
}, " Jump back " ))), /*#__PURE__*/ React . createElement ( " li " , {
className : " page-item arrow "
}, /*#__PURE__*/ React . createElement ( " a " , {
className : " page-link " ,
href : " # " ,
" aria-label " : " Previous " ,
onClick : this . handleMoveLeft
}, /*#__PURE__*/ React . createElement ( " span " , {
" aria-hidden " : " true "
}, " \ u21E0 " ), /*#__PURE__*/ React . createElement ( " span " , {
className : " sr-only "
}, " Previous page " ))));
}
if ( page === " RIGHT " ) {
return /*#__PURE__*/ React . createElement ( React . Fragment , {
key : index
}, /*#__PURE__*/ React . createElement ( " li " , {
className : " page-item arrow "
}, /*#__PURE__*/ React . createElement ( " a " , {
className : " page-link " ,
href : " # " ,
" aria-label " : " Next " ,
onClick : this . handleMoveRight
}, /*#__PURE__*/ React . createElement ( " span " , {
" aria-hidden " : " true "
}, " \ u21E2 " ), /*#__PURE__*/ React . createElement ( " span " , {
className : " sr-only "
}, " Next page " ))), /*#__PURE__*/ React . createElement ( " li " , {
className : " page-item arrow "
}, /*#__PURE__*/ React . createElement ( " a " , {
className : " page-link " ,
href : " # " ,
" aria-label " : " Next " ,
onClick : this . handleJumpRight
}, /*#__PURE__*/ React . createElement ( " span " , {
" aria-hidden " : " true "
}, " \ u21A0 " ), /*#__PURE__*/ React . createElement ( " span " , {
className : " sr-only "
}, " Jump forward " ))));
}
return /*#__PURE__*/ React . createElement ( " li " , {
key : index ,
className : " page-item "
}, /*#__PURE__*/ React . createElement ( " a " , {
className : `page-link${currentPage === page ? " active" : ""}` ,
href : " # " ,
onClick : e => this . handleClick ( page , e )
}, page ));
})));
}
}
function Footer ( props ) {
return /*#__PURE__*/ React . createElement ( " footer " , {
className : " main-footer "
}, /*#__PURE__*/ React . createElement ( " 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 ));
}
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 );
}
};
}
ReactDOM . render ( React . createElement ( Interface , {
allow : {
filelist : < ? = $opcache -> getOption ( 'allow_filelist' ) ? 'true' : 'false' ; ?> ,
invalidate : < ? = $opcache -> getOption ( 'allow_invalidate' ) ? 'true' : 'false' ; ?> ,
reset : < ? = $opcache -> getOption ( 'allow_reset' ) ? 'true' : 'false' ; ?> ,
realtime : < ? = $opcache -> getOption ( 'allow_realtime' ) ? 'true' : 'false' ; ?>
},
cookie : {
name : '<?= $opcache->getOption(' cookie_name '); ?>' ,
ttl : < ? = $opcache -> getOption ( 'cookie_ttl' ); ?>
},
opstate : < ? = json_encode ( $opcache -> getData ()); ?> ,
useCharts : < ? = json_encode ( $opcache -> getOption ( 'charts' )); ?> ,
highlight : < ? = json_encode ( $opcache -> getOption ( 'highlight' )); ?> ,
debounceRate : < ? = $opcache -> getOption ( 'debounce_rate' ); ?> ,
perPageLimit : < ? = json_encode ( $opcache -> getOption ( 'per_page' )); ?> ,
realtimeRefresh : < ? = json_encode ( $opcache -> getOption ( 'refresh_time' )); ?>
}), document . getElementById ( 'interface' ));
</ script >
2013-04-08 15:40:06 +00:00
</ body >
2020-09-18 23:30:17 +00:00
</ html >