diff --git a/library/Icinga/Timeline/TimeLine.php b/library/Icinga/Timeline/TimeLine.php
index e14cc42f0..b968f15b7 100644
--- a/library/Icinga/Timeline/TimeLine.php
+++ b/library/Icinga/Timeline/TimeLine.php
@@ -28,10 +28,12 @@
namespace Icinga\Timeline;
+use \Zend_Config;
use \DateInterval;
use \Zend_View_Interface;
use \Icinga\Web\Form;
use \Icinga\Web\Form\Element\Note;
+use \Icinga\Timeline\TimeEntry;
/**
* Represents a set of events in a specific time range
@@ -68,6 +70,27 @@ class TimeLine extends Form
*/
private $hideOuterElements = false;
+ /**
+ * The maximum diameter each circle can have
+ *
+ * @var int
+ */
+ private $circleDiameter = 250;
+
+ /**
+ * The unit of a circle's diameter
+ *
+ * @var string
+ */
+ private $diameterUnit = 'px';
+
+ /**
+ * The base that is used to calculate each circle's diameter
+ *
+ * @var float
+ */
+ private $calculationBase;
+
/**
* Set the range of time to represent
*
@@ -106,6 +129,23 @@ class TimeLine extends Form
$this->hideOuterElements = true;
}
+ /**
+ * Set the maximum diameter each circle can have
+ *
+ * @param string $width The diameter to set, suffixed with its unit
+ * @throws Exception If the given diameter is invalid
+ */
+ public function setMaximumCircleWidth($width)
+ {
+ $matches = array();
+ if (preg_match('#([\d]+)([a-z]+|%)#', $width, $matches)) {
+ $this->circleDiameter = intval($matches[1]);
+ $this->diameterUnit = $matches[2];
+ } else {
+ throw new Exception('Width "' . $width . '" is not a valid width');
+ }
+ }
+
/**
* Return the chosen interval
*
@@ -114,7 +154,7 @@ class TimeLine extends Form
*/
public function getInterval()
{
- switch ($this->getRequest()->getPost('timelineInterval', '4h'))
+ switch ($this->getRequest()->getParam('timelineInterval', '4h'))
{
case '4h':
return new DateInterval('PT4H');
@@ -208,14 +248,13 @@ class TimeLine extends Form
private function buildLegend()
{
// TODO: Put this in some sort of dedicated stylesheet
- $circleStyle = 'width:100%;height:90px;border-radius:50%;box-shadow:4px 4px 8px grey;border:2px solid;';
+ $circleStyle = 'width:75px;height:75px;border-radius:50%;box-shadow:4px 4px 8px grey;border:2px solid;margin:auto;';
$labelStyle = 'font-size:12px;margin-top:10px;text-align:center;';
$titleStyle = 'margin-left:25px;';
$elements = array();
foreach ($this->getGroups() as $groupName => $groupInfo) {
- $groupColor = $groupInfo['color'] ? $groupInfo['color'] :
- ('#' . str_pad(dechex(rand(256,16777215)), 6, '0', STR_PAD_LEFT)); // TODO: This should be kind of cached!
+ $groupColor = $groupInfo['color'] !== null ? $groupInfo['color'] : $this->getRandomCssColor();
$elements[] = '' .
'
' .
'' . $groupName . '
';
@@ -224,7 +263,6 @@ class TimeLine extends Form
$legend = '' .
'' . t('Shown event groups') . '
' .
'' .
- '
' .
implode(
'',
array_map(
@@ -232,7 +270,6 @@ class TimeLine extends Form
$elements
)
) .
- '
' .
'
';
return $legend;
@@ -243,7 +280,53 @@ class TimeLine extends Form
*/
private function buildTimeline()
{
- return '';
+ $timelineGroups = array();
+ foreach ($this->displayData as $group) {
+ $timestamp = $group->getDateTime()->getTimestamp();
+
+ if (!array_key_exists($timestamp, $timelineGroups)) {
+ $timelineGroups[$timestamp] = array();
+ }
+
+ $timelineGroups[$timestamp][] = $group;
+ }
+
+ $elements = array();
+ foreach ($this->range as $timestamp => $timeframe) {
+ $elementGroups = array();
+
+ if (array_key_exists($timestamp, $timelineGroups)) {
+ foreach ($timelineGroups[$timestamp] as $group) {
+ $eventCount = empty($elements) ? $this->extrapolateEventCount($group, 4) : $group->getValue();
+ $groupColor = $group->getColor() !== null ? $group->getColor() : $this->getRandomCssColor();
+ $elementGroups[] = sprintf(
+ '',
+ $this->calculateCircleWidth($eventCount),
+ $group->getDetailUrl(),
+ $this->diameterUnit,
+ $group->getValue(),
+ $groupColor
+ );
+ }
+ }
+
+ $timeframeUrl = $this->getRequest()->getBaseUrl() . '/monitoring/list/eventhistory?timestamp<=' .
+ $timeframe->start->getTimestamp() . '×tamp>=' . $timeframe->end->getTimestamp();
+ $elements[] = '' . implode('', $elementGroups) . '
';
+ $elements[] = '';
+ }
+
+ return implode('', $elements);
}
/**
@@ -257,7 +340,7 @@ class TimeLine extends Form
private function getGroups()
{
$groups = array();
- foreach ($this->displayData as $group) {
+ foreach (array_merge($this->displayData, $this->forecastData) as $group) {
if (!array_key_exists($group->getName(), $groups)) {
$groups[$group->getName()] = array(
'color' => $group->getColor(),
@@ -268,4 +351,137 @@ class TimeLine extends Form
return $groups;
}
+
+ /**
+ * Return the circle's diameter for the given amount of events
+ *
+ * @param int $eventCount The amount of events represented by the circle
+ * @return int
+ */
+ private function calculateCircleWidth($eventCount)
+ {
+ if (!isset($this->calculationBase)) {
+ $highestValue = max(
+ array_map(
+ function ($g) { return $g->getValue(); },
+ array_merge($this->displayData, $this->forecastData)
+ )
+ );
+
+ $this->calculationBase = $this->getRequest()->getParam('calculationBase', 1);
+ while (log($highestValue, $this->calculationBase) > 100) {
+ $this->calculationBase += 0.01;
+ }
+
+ $this->addElement(
+ 'hidden',
+ 'calculationBase',
+ array(
+ 'value' => $this->calculationBase
+ )
+ );
+ }
+
+ return intval($this->circleDiameter * (log($eventCount, $this->calculationBase) / 100));
+ }
+
+ /**
+ * Return an extrapolated event count for the given event group
+ *
+ * @param TimeEntry $eventGroup The event group for which to return an extrapolated event count
+ * @param int $offset The amount of intervals to consider for the extrapolation
+ * @return int
+ */
+ private function extrapolateEventCount(TimeEntry $eventGroup, $offset)
+ {
+ $start = $eventGroup->getDateTime();
+ $end = clone $start;
+
+ for ($i = 0; $i < $offset; $i++) {
+ $end->sub($this->range->getInterval());
+ }
+
+ $eventCount = 0;
+ foreach ($this->displayData as $group) {
+ if ($group->getDateTime() <= $start && $group->getDateTime() > $end) {
+ $eventCount += $group->getValue();
+ }
+ }
+
+ $extrapolatedCount = (int) $eventCount / $offset;
+ return $extrapolatedCount > $eventGroup->getValue() ? $extrapolatedCount : $eventGroup->getValue();
+ }
+
+ /**
+ * Return a random generated CSS color hex code
+ *
+ * @return string
+ */
+ private function getRandomCssColor()
+ {
+ return '#' . str_pad(dechex(rand(256,16777215)), 6, '0', STR_PAD_LEFT);
+ }
+
+ /**
+ * Get an appropriate datetime format string for the current interval
+ *
+ * @return string
+ */
+ private function getIntervalFormat()
+ {
+ $interval = $this->range->getInterval();
+
+ if ($interval->h == 4) {
+ return $this->getDateFormat() . ' ' . $this->getTimeFormat();
+ } elseif ($interval->d == 1) {
+ return $this->getDateFormat();
+ } elseif ($interval->d == 7) {
+ return '\W\e\ek #W \of Y';
+ } elseif ($interval->m == 1) {
+ return 'F Y';
+ } else { // $interval->y == 1
+ return 'Y';
+ }
+ }
+
+ /**
+ * Get the application's global configuration or an empty one
+ *
+ * @return Zend_Config
+ */
+ private function getGlobalConfiguration()
+ {
+ $config = $this->getConfiguration();
+ $global = $config->global;
+
+ if ($global === null) {
+ $global = new Zend_Config(array());
+ }
+
+ return $global;
+ }
+
+ /**
+ * Get the user's preferred time format or the application's default
+ *
+ * @return string
+ */
+ private function getTimeFormat()
+ {
+ $globalConfig = $this->getGlobalConfiguration();
+ $preferences = $this->getUserPreferences();
+ return $preferences->get('app.timeFormat', $globalConfig->get('timeFormat', 'g:i A'));
+ }
+
+ /**
+ * Get the user's preferred date format or the application's default
+ *
+ * @return string
+ */
+ private function getDateFormat()
+ {
+ $globalConfig = $this->getGlobalConfiguration();
+ $preferences = $this->getUserPreferences();
+ return $preferences->get('app.dateFormat', $globalConfig->get('dateFormat', 'd/m/Y'));
+ }
}
diff --git a/modules/monitoring/application/controllers/TimelineController.php b/modules/monitoring/application/controllers/TimelineController.php
index 3fc588701..53a0a642d 100644
--- a/modules/monitoring/application/controllers/TimelineController.php
+++ b/modules/monitoring/application/controllers/TimelineController.php
@@ -33,6 +33,7 @@ use \Icinga\Web\Hook;
use \Icinga\Timeline\TimeLine;
use \Icinga\Timeline\TimeEntry;
use \Icinga\Timeline\TimeRange;
+use \Icinga\Application\Config;
use \Icinga\Exception\ProgrammingError;
use \Icinga\Web\Controller\ActionController;
use \Icinga\Module\Monitoring\DataView\EventHistory as EventHistoryView;
@@ -44,6 +45,7 @@ class Monitoring_TimelineController extends ActionController
$timeline = new TimeLine();
$timeline->setName('Timeline');
$timeline->setRequest($this->_request);
+ $timeline->setConfiguration(Config::app());
$timeline->buildForm(); // Necessary in order to populate request parameters
$timeline->populate($this->_request->getParams());
list($displayRange, $forecastRange) = $this->buildTimeRanges($timeline->getInterval());
@@ -58,6 +60,7 @@ class Monitoring_TimelineController extends ActionController
$timeline = new TimeLine();
$timeline->showLineOnly();
$timeline->setRequest($this->_request);
+ $timeline->setConfiguration(Config::app());
list($displayRange, $forecastRange) = $this->buildTimeRanges($timeline->getInterval());
$timeline->setTimeRange($displayRange);
$timeline->setDisplayData($this->loadData($displayRange));