DaPortal

<?php //$Id$
//Copyright (c) 2016 Pierre Pronchery <khorben@defora.org>
//This file is part of DeforaOS Web DaPortal
//
//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, version 3 of the License.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program. If not, see <http://www.gnu.org/licenses/>.
//ProbeModule
class ProbeModule extends Module
{
//public
//methods
//ProbeModule::call
function call(Engine $engine, Request $request, $internal = 0)
{
//XXX should be saved in the constructor
$this->engine = $engine;
$action = $request->getAction();
if(!is_string($action) || strlen($action) === 0)
$action = 'default';
if($internal)
switch($action)
{
case 'actions':
return $this->$action($request);
default:
return FALSE;
}
$method = 'call'.$action;
if(!method_exists($this, $method))
return new ErrorResponse(_('Invalid action'),
Response::$CODE_ENOENT);
return $this->$method($request);
}
//protected
//properties
protected $engine;
static protected $times = array('day', 'week', 'month', 'year');
//methods
//ProbeModule::ProbeModule
protected function __construct($id, $name, $title = FALSE)
{
$title = ($title === FALSE) ? _('Monitoring') : $title;
parent::__construct($id, $name, $title);
}
//methods
//accessors
protected function getRoot()
{
if(($root = $this->configGet('root')) === FALSE
|| strlen($root) == 0)
{
$this->engine->log(LOG_ERR,
'The RRD repository is not configured');
return FALSE;
}
return $root;
}
//actions
//ProbeModule::actions
protected function actions($request)
{
return array();
}
//calls
//ProbeModule::callDefault
protected function callDefault(Request $request)
{
$root = $this->getRoot();
$title = _('Monitoring');
$hostname = $request->get('host');
if($root === FALSE)
return new ErrorResponse(_('Internal server error'));
$page = new Page(array('title' => $title));
$page->append('title', array('stock' => 'monitor',
'text' => $title));
$vbox = $page->append('vbox');
$this->_defaultForm($hostname, $vbox);
if(!is_string($hostname) || strlen($hostname) == 0)
$this->_defaultList($root, $vbox);
else
$this->_defaultHost($request, $root, $hostname, $vbox);
return new PageResponse($page);
}
private function _defaultForm($hostname, PageElement $page)
{
$form = $page->append('form', array(
'idempotent' => TRUE,
'request' => $this->getRequest()));
$hbox = $form->append('hbox');
$hbox->append('entry', array('text' => _('Host: '),
'placeholder' => _('Hostname'),
'name' => 'host', 'value' => $hostname));
$hbox->append('button', array('type' => 'submit',
'text' => _('Monitor')));
}
private function _defaultHost(Request $request, $root, $hostname,
PageElement $page)
{
$time = $request->get('time');
$graphs = array(
'load' => array('title' => _('Load average')),
'procs' => array('title' => _('Process count')),
'upgrades' => array(
'title' => _('Package upgrades pending')),
'users' => array('title' => _('Users logged')),
'volume' => array('title' => _('Volume usage: ').'/')
);
$page->append('title', array('text' => $hostname));
$this->_defaultToolbar($page, $hostname);
if(!in_array($time, static::$times, TRUE))
$time = FALSE;
if(strpos($hostname, '/') !== FALSE
|| $hostname == '.' || $hostname == '..')
//FIXME report (error dialog)
return;
if(($dir = opendir($root.'/'.$hostname)) === FALSE)
{
//FIXME report (error dialog)
return;
}
while(($de = readdir($dir)) !== FALSE)
{
if($de == '.' || $de == '..')
continue;
if(is_dir($root.'/'.$hostname.'/'.$de))
if($de == 'volume')
$this->_defaultHostVolume($request,
$root, $hostname, $time,
$page, '');
else
continue;
if(substr($de, -4) != '.rrd')
continue;
$graph = substr($de, 0, -4);
$title = isset($graphs[$graph])
? $graphs[$graph]['title'] : FALSE;
$dialog = $page->append('dialog', array(
'type' => 'info',
'title' => $title,
'text' => ''));
$request = $this->getRequest('widget', array(
'host' => $hostname, 'type' => $graph,
'time' => $time));
$link = $dialog->append('link', array(
'request' => $request, 'text' => ''));
$link->append('image', array('request' => $request,
'text' => $title));
//FIXME also append links in the toolbar
}
closedir($dir);
}
private function _defaultHostVolume(Request $request, $root, $hostname,
$time, PageElement $page, $volume)
{
$parent = $root.'/'.$hostname.'/volume'.$volume;
if(($dir = opendir($parent)) === FALSE)
{
//FIXME report (error dialog)
return;
}
while(($de = readdir($dir)) !== FALSE)
{
if($de == '.' || $de == '..')
continue;
if(is_dir($parent.'/'.$de))
{
$this->_defaultHostVolume($request, $root,
$hostname, $time, $page,
$volume.'/'.$de);
continue;
}
if(substr($de, -4) != '.rrd')
continue;
$v = $volume.'/'.substr($de, 0, -4);
$title = _('Volume usage: ').$v;
$dialog = $page->append('dialog', array(
'type' => 'info',
'title' => $title,
'text' => ''));
$request = $this->getRequest('widget', array(
'host' => $hostname, 'type' => 'volume',
'time' => $time, 'volume' => $v));
$link = $dialog->append('link', array(
'request' => $request, 'text' => ''));
$link->append('image', array('request' => $request,
'text' => $title));
}
closedir($dir);
}
private function _defaultList($root, PageElement $page)
{
$hosts = array();
if(($dir = opendir($root)) === FALSE)
{
//FIXME report (error dialog)
return;
}
while(($de = readdir($dir)) !== FALSE)
{
if($de == '.' || $de == '..')
continue;
if(is_dir($root.'/'.$de))
$hosts[] = $de;
}
closedir($dir);
$view = $page->append('iconview');
$icon = new PageElement('image', array('stock' => 'server'));
foreach($hosts as $hostname)
{
$request = $this->getRequest(FALSE,
array('host' => $hostname));
$link = new PageElement('link', array(
'request' => $request,
'text' => $hostname));
$view->append('row', array('icon' => $icon,
'label' => $link));
}
}
private function _defaultToolbar(PageElement $page, $hostname)
{
$request = $this->getRequest();
$toolbar = $page->append('toolbar');
$toolbar->append('button', array('stock' => 'server',
'text' => _('List hosts'),
'request' => $request));
foreach(static::$times as $time)
{
$r = $this->getRequest(FALSE, array('host' => $hostname,
'time' => $time));
$text = sprintf(_('Last %s'), $time);
$toolbar->append('button', array('stock' => 'refresh',
'text' => $text, 'request' => $r));
}
}
//ProbeModule::callWidget
public function callWidget(Request $request)
{
$root = $this->getRoot();
$rrdtool = $this->configGet('rrdtool') ?: 'rrdtool';
$slope = $this->configGet('slope');
$width = $this->configGet('width');
$height = $this->configGet('height');
$rrdcached = $this->configGet('rrdcached');
$hostname = $request->get('host');
if($root === FALSE)
return new ErrorResponse(_('Internal server error'));
if(strpos($hostname, '/') !== FALSE
|| $hostname == '.' || $hostname == '..')
return new ErrorResponse('Invalid hostname');
$rrd = $root.'/'.$hostname;
$rrdtool .= ' graph -';
if($slope === FALSE || $slope)
$rrdtool .= ' --slope-mode';
$rrdtool .= ' --imgformat PNG';
if(is_string($rrdcached) && strlen($rrdcached) > 0)
$rrdtool .= ' --daemon '.escapeshellarg($rrdcached);
switch($request->get('time'))
{
case 'month':
$rrdtool .= ' --start -2419200';
$title = '(last month)';
break;
case 'week':
$rrdtool .= ' --start -604800';
$title = '(last week)';
break;
case 'year':
$rrdtool .= ' --start -31449600';
$title = '(last year)';
break;
case 'day':
default:
$rrdtool .= ' --start -86400';
$title = '(last day)';
break;
}
switch($request->get('type'))
{
case 'load':
$rrd .= '/load.rrd';
$title = _('load average').' '.$title;
$label = _('load');
$rrdtool .= ' --lower-limit 0.0'
.' '.escapeshellarg("DEF:load1=$rrd:load1:AVERAGE")
.' '.escapeshellarg("DEF:load5=$rrd:load5:AVERAGE")
.' '.escapeshellarg("DEF:load15=$rrd:load15:AVERAGE")
.' '.escapeshellarg('CDEF:rload1=load1,1000,/')
.' '.escapeshellarg('CDEF:rload5=load5,1000,/')
.' '.escapeshellarg('CDEF:rload15=load15,1000,/')
.' '.escapeshellarg('CDEF:r3load1=rload1,3,/')
.' '.escapeshellarg('CDEF:r3load5=rload5,3,/')
.' '.escapeshellarg('CDEF:r3load15=rload15,3,/')
.' '.escapeshellarg('AREA:r3load15#ff8f00')
.' '.escapeshellarg('STACK:r3load5#ffbf00')
.' '.escapeshellarg('STACK:r3load1#ffef00')
.' '.escapeshellarg('LINE1:rload1#ffdf00:Load 1 min')
.' '.escapeshellarg('GPRINT:rload1:LAST: %.2lf')
.' '.escapeshellarg('LINE1:rload5#ffaf00:Load 5 min')
.' '.escapeshellarg('GPRINT:rload5:LAST: %.2lf')
.' '.escapeshellarg('LINE1:rload15#ff7f00:Load 15 min')
.' '.escapeshellarg('GPRINT:rload15:LAST: %.2lf');
break;
case 'procs':
$rrd .= '/procs.rrd';
$title = _('process count').' '.$title;
$label = _('processes');
$rrdtool .= ' --lower-limit 0'
.' '.escapeshellarg("DEF:procs=$rrd:procs:AVERAGE")
.' '.escapeshellarg('AREA:procs#7f7fff')
.' '.escapeshellarg('LINE1:procs#4f4fff:Process count')
.' '.escapeshellarg('GPRINT:procs:LAST: %.0lf');
break;
case 'upgrades':
$rrd .= '/upgrades.rrd';
$title = _('package upgrades pending').' '
.$title;
$label = _('packages');
$rrdtool .= ' --lower-limit 0'
.' '.escapeshellarg("DEF:upgrades=$rrd:upgrades:AVERAGE")
.' '.escapeshellarg('AREA:upgrades#7f7fff')
.' '.escapeshellarg('LINE1:upgrades#4f4fff:Process count')
.' '.escapeshellarg('GPRINT:upgrades:LAST: %.0lf');
break;
case 'users':
$rrd .= '/users.rrd';
$title = _('users logged').' '.$title;
$label = _('users');
$rrdtool .= ' --lower-limit 0'
.' '.escapeshellarg("DEF:users=$rrd:users:AVERAGE")
.' '.escapeshellarg('AREA:users#ff7f7f')
.' '.escapeshellarg('LINE1:users#ff4f4f:Users logged')
.' '.escapeshellarg('GPRINT:users:LAST: %.0lf');
break;
case 'volume':
if(($volume = $request->get('volume')) === FALSE
|| strlen($volume) == 0)
{
$volume = '/';
$rrd .= '/volume.rrd';
}
else if(strpos($volume, '/..') !== FALSE)
return new ErrorResponse($error);
else
$rrd .= '/volume/'.$volume.'.rrd';
$title = _('volume used: ').$volume.' '.$title;
$label = _('bytes');
$rrdtool .= ' --lower-limit 0'
.' '.escapeshellarg("DEF:used=$rrd:used:AVERAGE")
.' '.escapeshellarg("DEF:total=$rrd:total:AVERAGE")
.' '.escapeshellarg("CDEF:gbused=used,1024,/,1024,/,1024,/")
.' '.escapeshellarg("CDEF:gbtotal=total,1024,/,1024,/,1024,/")
.' '.escapeshellarg('AREA:used#7f7fff')
.' '.escapeshellarg('LINE1:used#4f4fff:Volume used')
.' '.escapeshellarg('GPRINT:gbused:LAST:%.2lf GB')
.' '.escapeshellarg('LINE1:total#4f4fff:Volume total')
.' '.escapeshellarg('GPRINT:gbtotal:LAST:%.2lf GB');
break;
default:
$error = _('Could not create graph for this type');
return new ErrorResponse($error);
}
$title = $hostname.' '.$title;
$rrdtool .= ' --title '.escapeshellarg($title);
if(is_numeric($width) && $width > 0)
$rrdtool .= ' --width '.escapeshellarg($width);
if(is_numeric($height) && $height > 0)
$rrdtool .= ' --height '.escapeshellarg($height);
$rrdtool .= ' --vertical-label '.escapeshellarg($label);
//render the graph
$this->engine->log(LOG_DEBUG, $rrdtool);
if(($fp = popen($rrdtool, 'r')) === FALSE)
{
$error = _('Could not create the graph');
return new ErrorResponse($error);
}
$response = new CachePipeResponse($fp);
$response->setType('image/png');
return $response;
}
}
?>