diff --git a/application/bootstrap.php b/application/bootstrap.php
index 7bd97b8..73ea5f6 100644
--- a/application/bootstrap.php
+++ b/application/bootstrap.php
@@ -114,7 +114,7 @@ Kohana::modules(array(
'cron' => SMDPATH.'cron', // Kohana Cron Module
// 'codebench' => SMDPATH.'codebench', // Benchmarking tool
'database' => SMDPATH.'database', // Database access
- // 'gchart' => MODPATH.'gchart', // Google Chart Module
+ 'gchart' => MODPATH.'gchart', // Google Chart Module
// 'image' => SMDPATH.'image', // Image manipulation
'khemail' => SMDPATH.'khemail', // Email module for Kohana 3 PHP Framework
'minion' => SMDPATH.'minion', // CLI Tasks
diff --git a/application/classes/Controller/Node.php b/application/classes/Controller/Node.php
index 389ac1c..5586208 100644
--- a/application/classes/Controller/Node.php
+++ b/application/classes/Controller/Node.php
@@ -14,5 +14,43 @@ class Controller_Node extends Controller_TemplateDefault_View {
protected $index_title = 'TSM Nodes';
protected $detail_title = 'Information on Node';
protected $orm = 'NODE';
+
+ public function action_ajax_jsonbackup() {
+ $id = $this->request->param('id');
+ $c = (isset($_REQUEST['c'])) ? $_REQUEST['c'] : null;
+ $s = (isset($_REQUEST['s'])) ? $_REQUEST['s'] : null;
+ $t = (isset($_REQUEST['t'])) ? $_REQUEST['t'] : null;
+
+ $no = ORM::factory('NODE',$id);
+
+ if (! $id OR ! $no->loaded() OR ! $t OR ! $c)
+ return '';
+
+ $result = array();
+
+ $google = GoogleChart::factory($c)
+ ->logy(TRUE)
+ ->title(sprintf('%s Activity',$t).($s ? sprintf(' for Schedule %s',$s) : ' outside of TSM schedules'));
+
+ foreach ($no->act_bybtype($t) as $aso) {
+ if ($aso->SCHEDULE_NAME != $s)
+ continue;
+
+ $google->pdata($aso->display('START_TIME'),array(
+ 'yr'=>array(
+ 'BYTES'=>$aso->BYTES,
+ ),
+ 'yl'=>array(
+ 'AFFECTED'=>$aso->AFFECTED,
+ 'EXAMINED'=>$aso->EXAMINED,
+ 'FAILED'=>$aso->FAILED,
+ ),
+ ));
+ }
+
+ $this->auto_render = FALSE;
+ $this->response->headers('Content-Type','application/json');
+ $this->response->body($google->json());
+ }
}
?>
diff --git a/application/classes/Model/NODE.php b/application/classes/Model/NODE.php
index 2532ff1..b8c3803 100644
--- a/application/classes/Model/NODE.php
+++ b/application/classes/Model/NODE.php
@@ -129,7 +129,7 @@ class Model_NODE extends ORM_TSM {
// In the interest of performance, we load all the records and get PHP to process it.
// Our ORM caching we reduce the hit on TSM.
- foreach (ORM::factory('ACTSUM')->order_by('SCHEDULE_NAME')->find_all() as $o)
+ foreach (ORM::factory('ACTSUM')->find_all() as $o)
if ($o->ENTITY == $this->NODE_NAME)
array_push($result,$o);
@@ -330,6 +330,7 @@ class Model_NODE extends ORM_TSM {
if ($aso->ACTIVITY == $this->datatypemap($type))
array_push($result,$aso);
+ Sort::MASort($result,'SCHEDULE_NAME,START_TIME');
// @todo Cache time should be configurble
Cache::instance($c)->set($k,$result,300);
}
@@ -338,6 +339,20 @@ class Model_NODE extends ORM_TSM {
return $result;
}
+ /**
+ * Return the Schedules for all activites of type
+ * @param $type is Bkup/Arch/SpMg
+ */
+ public function act_schedules($type) {
+ $result = array();
+
+ foreach ($this->act_bybtype($type) as $ao)
+ if (! in_array($ao->SCHEDULE_NAME,$result))
+ array_push($result,$ao->SCHEDULE_NAME);
+
+ return $result;
+ }
+
/**
* Return the BACKUP TYPES used by this NODE
* ie: Bkup/Arch/SpMg
@@ -460,6 +475,22 @@ class Model_NODE extends ORM_TSM {
return $this->_filespaces();
}
+ /**
+ * Return a Graph of the Schedule Backup Activity
+ * @param $type is Bkup/Arch/SpMg
+ * @see [node/json_backup]
+ */
+ public function graph_backup($type='Bkup',$schedule='') {
+ $chart = 'ComboChart';
+
+ $google = GoogleChart::factory($chart)
+ ->title(sprintf('%s Activity',$type))
+ ->div(sprintf('%s_%s',$type,$schedule))
+ ->dataurl(URL::site(sprintf('node/ajax_jsonbackup/%s?c=%s&s=%s&t=%s',$this->NODE_NAME,$chart,$schedule,$type)));
+
+ return (string)$google;
+ }
+
/**
* Return the LOGICAL_MB that this NODE has by backup TYPE
* @param $type is Bkup/Arch/SpMg
diff --git a/application/classes/lnApp/Script.php b/application/classes/lnApp/Script.php
index 237acd5..696774b 100644
--- a/application/classes/lnApp/Script.php
+++ b/application/classes/lnApp/Script.php
@@ -40,6 +40,9 @@ abstract class lnApp_Script extends HTMLRender {
case 'file':
$foutput .= HTML::script($mediapath->uri(array('file'=>$value['data'])));
break;
+ case 'src':
+ $foutput .= HTML::script($value['data']);
+ break;
case 'stdin':
$soutput .= sprintf("",$value['data']);
break;
diff --git a/application/views/node/detail.php b/application/views/node/detail.php
index 2ad0823..60788f3 100644
--- a/application/views/node/detail.php
+++ b/application/views/node/detail.php
@@ -3,6 +3,9 @@
set('o',$o); ?> |
set('o',$o); ?> |
+
+ set('o',$o); ?> |
+
set('o',$o); ?> |
|
diff --git a/application/views/node/graph_backup.php b/application/views/node/graph_backup.php
new file mode 100644
index 0000000..366f1d5
--- /dev/null
+++ b/application/views/node/graph_backup.php
@@ -0,0 +1,18 @@
+
+
+
+ Schedule Results |
+
+
+ |
+
+ btypes() as $btype) { ?>
+ act_bybtype($btype)) { ?>
+ act_schedules($btype) as $schedule) { ?>
+
+ graph_backup($btype,$schedule); ?> |
+
+
+
+
+
diff --git a/modules/gchart/classes/GoogleChart.php b/modules/gchart/classes/GoogleChart.php
new file mode 100644
index 0000000..16808c5
--- /dev/null
+++ b/modules/gchart/classes/GoogleChart.php
@@ -0,0 +1,214 @@
+_plotdata);
+ }
+ public function current() {
+ return current($this->_plotdata);
+ }
+ public function key() {
+ return key($this->_plotdata);
+ }
+ public function next() {
+ return next($this->_plotdata);
+ }
+ public function rewind() {
+ reset($this->_plotdata);
+ }
+ public function valid() {
+ return key($this->_plotdata) ? TRUE : FALSE;
+ }
+
+ public function __call($name,$args) {
+ switch ($name) {
+ case 'dataurl': $this->_dataurl = array_shift($args);
+ break;
+ case 'div': $this->_divname = array_shift($args);
+ break;
+ case 'title': $this->_title = array_shift($args);
+ break;
+ default:
+ throw new Kohana_Exception('Unknown method :name',array(':name'=>$name));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Render the URL that generates the graph
+ */
+ final public function __toString() {
+ try {
+ return (string)$this->render();
+ } catch (Exception $e) {
+ echo Debug::vars($e);
+ }
+ }
+
+ /**
+ * Pick the driver that will render the graph
+ * @param $class The child class to invoke
+ */
+ final public static function factory($class) {
+ $c = sprintf('%s_%s',get_called_class(),$class);
+
+ if (! class_exists($c))
+ throw new Kohana_Exception('Unknown Google Chart Type :class',array(':class'=>$class));
+ else
+ return new $c();
+ }
+
+ // Render the chart data in a json format
+ abstract public function json();
+
+ // Our child class should define how to render as a string
+ abstract public function render();
+
+ /**
+ * Record one series of data
+ * @param $axis Axis used and legend title, eg: (yr->"Right Y",yl->"Left Y")
+ * @param $data Data for Axis in the format ("x label"=>value);
+ *
+ * Example:
+ * $this->data('yl'=>'Base Down Peak',array('11-12'=>1,'11-11'=>2));
+ */
+ public function sdata(array $axis,array $data) {
+ // Some sanity checking
+ if (count($axis) != 1)
+ throw new Kohana_Exception('We can only take 1 series at time.');
+
+ // This should only iterate once
+ foreach ($axis as $key => $l) {
+ if (! in_array($key,array('yr','yl')))
+ throw new Kohaan_Exception('Unknown AXIS :axis',array(':axis'=>$key));
+
+ $this->_axis[$l] = $key;
+ $this->_data[$l] = $data[$l];
+
+ // Upate our plot data
+ foreach ($data[$l] as $k=>$v)
+ $this->_plotdata[$k][$l] = $v;
+
+ ksort($this->_plotdata);
+
+ $this->_max[$l] = max($data[$l]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Record on plot event
+ * @param $data Should contain an "X" with a "YL" and/or "YR"
+ */
+ public function pdata($x,array $data) {
+ if (! is_string($x))
+ throw new Kohana_Exception('X should be a string');
+
+ foreach ($data as $key => $values) {
+ switch ($key) {
+ case 'yr':
+ case 'yl':
+ foreach ($values as $k=>$v) {
+ if (! in_array($k,$this->_axis))
+ $this->_axis[$k] = $key;
+
+ $this->_data[$k][$x] = $v;
+ $this->_plotdata[$x][$k] = $v;
+ }
+
+ break;
+
+ default:
+ throw new Kohana_Exception('Unknown key :key',array(':key'=>$key));
+ }
+ }
+ }
+
+ /**
+ * Return the colors that will be used for this series
+ */
+ protected function seriescolors() {
+ return array_slice($this->series_colors,0,count($this->_axis));
+ }
+
+ /**
+ * Render the chart data in a table format
+ */
+ public function table($vertical=TRUE,$class=array()) {
+ if (! $this->_data)
+ return sprintf('',_('No Data'));
+
+ $output = sprintf('',
+ isset($class['table']) ? $class['table'] : 'class="google-data-table"');
+
+ if ($vertical) {
+ $output .= '';
+ $output .= ' | ';
+ foreach ($this->_axis as $l => $axis)
+ $output .= sprintf('%s | ',$l);
+
+ $output .= '
';
+
+ foreach ($this as $k => $details) {
+ $output .= '';
+ $output .= sprintf('%s | ',$k);
+ foreach ($this->_axis as $l => $axis)
+ $output .= sprintf('%s | ',$details[$l]);
+ $output .= '
';
+ }
+
+ // Horizontal table
+ } else {
+ $output .= '';
+ $output .= ' | ';
+ foreach ($this as $k => $details)
+ $output .= sprintf('%s | ',$k);
+ $output .= '
';
+
+ foreach ($this->_axis as $l => $axis) {
+ $output .= '';
+
+ $output .= sprintf('%s | ',$l);
+
+ foreach ($this as $k => $v)
+ $output .= sprintf('%s | ',$v[$l]);
+
+ $output .= '
';
+ }
+ }
+
+ $output .= '
';
+
+ return $output;
+ }
+}
+?>
diff --git a/modules/gchart/classes/GoogleChart/ComboChart.php b/modules/gchart/classes/GoogleChart/ComboChart.php
new file mode 100644
index 0000000..fdef02f
--- /dev/null
+++ b/modules/gchart/classes/GoogleChart/ComboChart.php
@@ -0,0 +1,155 @@
+_logy = $value;
+
+ return $this;
+ }
+
+ public function stacked($value) {
+ $this->_stacked = $value;
+
+ return $this;
+ }
+
+ /**
+ * Set the type of the chart
+ * @param $type Chart type as per $this->cht
+ */
+ public function ltitle($side,$title) {
+ if (! in_array($side,array('yl','yr','x')))
+ throw new Kohana_Exception('Unknown side :side',array(':side'=>$side));
+
+ $this->_ltitle[$side] = $title;
+
+ return $this;
+ }
+
+ public function json() {
+ $return = array();
+
+ $return['cols'][] = array(
+ 'id'=>'date',
+ 'label'=>'date',
+ 'type'=>'string',
+ );
+
+ // Columns
+ foreach (array_keys($this->_axis) as $l) {
+ $return['cols'][] = array(
+ 'id'=>$l,
+ 'label'=>$l,
+ 'type'=>'number',
+ );
+ }
+
+ // Values
+ foreach ($this as $k => $v) {
+ $data = array();
+
+ array_push($data,array('v'=>$k));
+
+ foreach ($this->_axis as $l => $axis)
+ array_push($data,array('v'=>$v[$l]));
+
+ $return['rows'][] = array('c'=>$data);
+ }
+
+ $options = array(
+ 'bar' => array('groupWidth'=>'75%'),
+ 'vAxis' => array('logScale'=>$this->_logy ? 1:0),
+ 'title' => $this->_title,
+ 'isStacked' => $this->_stacked ? 1:0,
+ 'seriesType' => $this->_type,
+ 'series' => $this->series(),
+ );
+
+ return json_encode(array('data'=>$return,'options'=>$options));
+ }
+
+ public function render() {
+ Script::add(array(
+ 'type'=>'src',
+ 'data'=>'https://www.google.com/jsapi',
+ ));
+
+ Script::add(array(
+ 'type'=>'stdin',
+ 'data'=>'google.load("visualization", "1", {packages: ["corechart"]});',
+ ));
+
+ Script::add(array(
+ 'type'=>'stdin',
+ 'data'=>"
+function drawChart".$this->_divname."() {
+ var jsonData = $.ajax({
+ url: '".$this->_dataurl."',
+ dataType:'json',
+ async: false,
+ }).responseText;
+
+ var x = JSON.parse(jsonData);
+ for(var key in x) {
+ if (key == 'data')
+ data = x[key];
+ else if (key == 'options')
+ options = x[key];
+ else
+ alert('UNKNOWN Key: '+key);
+ }
+
+ // Create our data table out of JSON data loaded from server.
+ var data = new google.visualization.DataTable(data);
+
+ // Instantiate and draw our chart, passing in some options.
+ var chart = new google.visualization.ComboChart(document.getElementById('".$this->_divname."'));
+ chart.draw(data, options);
+}
+",
+ ));
+
+ Script::add(array(
+ 'type'=>'stdin',
+ 'data'=>'google.setOnLoadCallback(drawChart'.$this->_divname.');',
+ ));
+
+ return sprintf('',$this->_divname,$this->_width,$this->_height);
+ }
+
+ private function series() {
+ $return = array();
+ $c = $this->seriescolors();
+
+ $i = 0;
+ foreach ($this->_axis as $l => $axis) {
+ // @todo This shouldnt be hard coded
+ if ($axis == 'yl')
+ array_push($return,array('type'=>'bar','color'=>$c[$i],'targetAxisIndex'=>0));
+ else
+ array_push($return,array('type'=>'line','color'=>$c[$i],'targetAxisIndex'=>1));
+
+ $i++;
+ }
+
+ return $return;
+ }
+}
+?>
diff --git a/modules/gchart/classes/GoogleChart/Legacy.php b/modules/gchart/classes/GoogleChart/Legacy.php
new file mode 100644
index 0000000..0ad104d
--- /dev/null
+++ b/modules/gchart/classes/GoogleChart/Legacy.php
@@ -0,0 +1,221 @@
+ 'lc',
+// 'sparkline' => 'ls',
+// 'line_xy' => 'lxy',
+ // Bar
+// 'horizontal_bar' => 'bhs',
+ 'vertical_bar' => 'bvs',
+// 'horizontal_bar_grp' => 'bhg',
+// 'vertical_bar_grp' => 'bvg',
+ // Pie
+// 'pie' => 'p',
+// 'pie_3d' => 'p3',
+// 'pie_concentric' => 'pc',
+ // Venn
+// 'venn' => 'v',
+ // Scatter
+// 'scatter' => 's',
+ // Radar
+// 'radar' => 'r',
+// 'radar_fill' => 'rs',
+ // Maps
+// 'map' => 't',
+ // Google-o-meter
+// 'google_o_meter' => 'gom',
+ // QR
+// 'qr' => 'qr',
+ );
+
+ /**
+ * Set the type of the chart
+ * @param $type Chart type as per $this->cht
+ */
+ public function type($type) {
+ if (empty($this->cht[$type]))
+ throw new Kohana_Exception('Unknown chart type :type for :class',array(':type'=>$type,':class'=>get_class($this)));
+
+ $this->_type = $this->cht[$type];
+
+ return $this;
+ }
+
+ /**
+ * Count how many metrics are being graphed per side
+ * @param $side Side YL (left) OR YR (right)
+ */
+ private function axiscount($side) {
+ $i = 0;
+
+ foreach ($this->_axis as $l => $axis)
+ if ($axis == $side)
+ $i++;
+
+ return $i;
+ }
+
+ /**
+ * Calculate our maximum for each side of the chart
+ */
+ private function maxes() {
+ $return = array();
+
+ foreach ($this->_axis as $l => $axis) {
+ if (! isset($return[$axis]))
+ $return[$axis] = 0;
+
+ $return[$axis] += $this->_max[$l]*1.1; // @todo This scaleup should be configurable
+ }
+
+ return $return;
+ }
+
+ /** CHART FIELDS **/
+
+ private function chd() {
+ $return = array();
+ $maxes = $this->maxes();
+
+ // Perform our encoding
+ foreach ($this->_axis as $l => $axis)
+ array_push($return,$this->encode($this->_data[$l],$maxes[$axis]));
+
+ $prefix = (count($maxes) > 1) ? sprintf('%s:',$this->axiscount('yl')) : ':';
+
+ // If encoding is text, we need to separate the series with a |
+ return ($this->_encodetype == 't') ? $prefix.implode('|',$return) : $prefix.implode(',',$return);
+ }
+
+ private function chm() {
+ $return = array();
+ $sc = $this->seriescolors();
+ $i = 0;
+
+ foreach ($this->_axis as $l => $axis) {
+ if ($axis == 'yr')
+ array_push($return,sprintf('%s,%s,%s,%s,%s,%s','D',$sc[$i],$i,0,2,2));// @todo 'D,0,2,2' May need to be configurable
+
+ $i++;
+ }
+
+ return count($return) ? implode('|',$return) : '';
+ }
+
+ private function chxl() {
+ $return = array();
+
+ // @todo This should be performed better - it may be a wrong assumption that all keys in the series have data.
+ foreach ($this->_data as $series => $data)
+ // @todo Why the 0:?
+ return '0:|'.implode('|',array_keys($data));
+ }
+
+ private function chxr() {
+ $return = array();
+ $i = 1;
+
+ foreach ($this->maxes() as $key => $value)
+ array_push($return,sprintf('%s,0,%s,0',$i++,$value));
+
+ return implode('|',$return);
+ }
+
+ public function json() {}
+
+ /**
+ * Return URL that renders the chart
+ */
+ public function render() {
+ return sprintf('',$this->_url,http_build_query($this->build()),_('Google Chart'));
+ }
+
+ /**
+ * Build the chart
+ */
+ private function build() {
+ if ($this->_data ) {
+ return array(
+ 'chf'=>'bg,s,FFFFFF00',
+ 'cht'=>$this->_type,
+ 'chs'=>sprintf('%sx%s',$this->_width,$this->_height),
+ 'chtt'=>$this->_title,
+ 'chbh'=>'a', // @todo This might need to be calculated, valid options (a,r);
+ 'chg'=>'7.7,12.5,1,5', // @todo This should be calculated
+ 'chco'=>implode(',',$this->seriescolors()),
+ 'chdl'=>implode('|',array_keys($this->_axis)),
+ 'chd'=>$this->_encodetype.$this->chd(),
+ 'chm'=>$this->chm(),
+ 'chxt'=>'x,y,r', // @todo configurable?
+ 'chxl'=>$this->chxl(),
+ 'chxr'=>$this->chxr(),
+ );
+
+ } else
+ return array();
+ }
+
+ /**
+ * Encode the series data
+ * @param $data String of data to encode
+ * @param $max The maximum to scale to
+ */
+ private function encode($data,$max=NULL) {
+ $table = array();
+ $table['simple'] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ $table['extend'] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.';
+
+ $size = array();
+ $size['simple'] = strlen($table['simple']);
+ $size['extend'] = strlen($table['extend']);
+
+ if (is_null($max) OR $max == 0)
+ $max = max($data) > 0 ? max($data) : 1;
+
+ $encode = '';
+
+ switch ($this->_encodetype) {
+ case 't' :
+ return join(',',$data);
+
+ case 's' :
+ foreach ($data as $v)
+ $encode .= ($v > -1) ? substr($table['simple'],($size['simple']-1)*($v/$max),1) : '_';
+
+ break;
+
+ case 'e' :
+ foreach ($data as $v) {
+ # Convert to a 0-4095 data range
+ $y = 4095*$v/$max;
+ $first = substr($table['extend'],floor($y/$size['extend']),1);
+ $second = substr($table['extend'],$y%$size['extend'],1);
+ $encode .= "$first$second";
+ }
+
+ break;
+ }
+
+ return $encode;
+ }
+}
+?>