diff --git a/library/Icinga/Chart/Chart.php b/library/Icinga/Chart/Chart.php
index 13b0114e6..842bb56e8 100644
--- a/library/Icinga/Chart/Chart.php
+++ b/library/Icinga/Chart/Chart.php
@@ -38,11 +38,25 @@ abstract class Chart implements Drawable
*/
protected $palette;
+ /**
+ * The title of this chart, used for providing accessibility features
+ *
+ * @var string
+ */
+ public $title;
+
+ /**
+ * The description for this chart, mandatory for providing accessibility features
+ *
+ * @var string
+ */
+ public $description;
+
/**
* Create a new chart object and create internal objects
*
* If you want to extend this class use the init() method as an extension point,
- * as this will be called at the end o fthe construct call
+ * as this will be called at the end of the construct call
*/
public function __construct()
{
@@ -86,7 +100,6 @@ abstract class Chart implements Drawable
}
/**
- *
* Render this graph and return the created SVG
*
* @return string The SVG created by the SvgRenderer
@@ -105,6 +118,11 @@ abstract class Chart implements Drawable
$this->renderer->setXAspectRatioAlignment(SVGRenderer::X_ASPECT_RATIO_MIN);
$this->renderer->setYAspectRatioAlignment(SVGRenderer::Y_ASPECT_RATIO_MIN);
}
+
+ $this->renderer->setAriaDescription($this->description);
+ $this->renderer->setAriaTitle($this->title);
+ $this->renderer->getCanvas()->setAriaRole('presentation');
+
$this->renderer->getCanvas()->addElement($this);
return $this->renderer->render();
}
diff --git a/library/Icinga/Chart/Graph/BarGraph.php b/library/Icinga/Chart/Graph/BarGraph.php
index 2d20f8f22..dacc12878 100644
--- a/library/Icinga/Chart/Graph/BarGraph.php
+++ b/library/Icinga/Chart/Graph/BarGraph.php
@@ -65,7 +65,13 @@ class BarGraph extends Styleable implements Drawable
) {
$this->order = $order;
$this->dataSet = $dataSet;
+
$this->tooltips = $tooltips;
+ foreach ($this->tooltips as $value) {
+ $ts[] = $value;
+ }
+ $this->tooltips = $ts;
+
$this->graphs = $graphs;
}
diff --git a/library/Icinga/Chart/Graph/LineGraph.php b/library/Icinga/Chart/Graph/LineGraph.php
index 630223968..7b831d625 100644
--- a/library/Icinga/Chart/Graph/LineGraph.php
+++ b/library/Icinga/Chart/Graph/LineGraph.php
@@ -38,6 +38,13 @@ class LineGraph extends Styleable implements Drawable
*/
private $isDiscrete = false;
+ /**
+ * The tooltips
+ *
+ * @var
+ */
+ private $tooltips;
+
/**
* The default stroke width
* @var int
@@ -56,10 +63,22 @@ class LineGraph extends Styleable implements Drawable
*
* @param array $dataset An array of [x, y] arrays to display
*/
- public function __construct(array $dataset)
- {
+ public function __construct(
+ array $dataset,
+ array &$graphs,
+ $order,
+ array $tooltips = null
+ ) {
usort($dataset, array($this, 'sortByX'));
$this->dataset = $dataset;
+ $this->graphs = $graphs;
+
+ $this->tooltips = $tooltips;
+ foreach ($this->tooltips as $value) {
+ $ts[] = $value;
+ }
+ $this->tooltips = $ts;
+ $this->order = $order;
}
/**
@@ -142,14 +161,41 @@ class LineGraph extends Styleable implements Drawable
$path->setAdditionalStyle('clip-path: url(#clip);');
$path->setId($this->id);
$group = $path->toSvg($ctx);
- if ($this->showDataPoints === true) {
- foreach ($this->dataset as $point) {
- $dot = new Circle($point[0], $point[1], $this->dotWith);
- $dot->setFill($this->strokeColor);
- $group->appendChild($dot->toSvg($ctx));
+ foreach ($this->dataset as $x => $point) {
+
+ if ($this->showDataPoints === true) {
+ $dot = new Circle($point[0], $point[1], $this->dotWith);
+ $dot->setFill($this->strokeColor);
+ $group->appendChild($dot->toSvg($ctx));
+ }
+
+ // Draw invisible circle for tooltip hovering
+ $invisible = new Circle($point[0], $point[1], 20);
+ $invisible->setFill($this->strokeColor);
+ $invisible->setAdditionalStyle('opacity: 0.0;');
+ $invisible->setAttribute('class', 'chart-data');
+ if (isset($this->tooltips[$x])) {
+ $data = array(
+ 'label' => isset($this->graphs[$this->order]['label']) ?
+ strtolower($this->graphs[$this->order]['label']) : '',
+ 'color' => isset($this->graphs[$this->order]['color']) ?
+ strtolower($this->graphs[$this->order]['color']) : '#fff'
+ );
+ $format = isset($this->graphs[$this->order]['tooltip'])
+ ? $this->graphs[$this->order]['tooltip'] : null;
+ $invisible->setAttribute(
+ 'title',
+ $this->tooltips[$x]->renderNoHtml($this->order, $data, $format)
+ );
+ $invisible->setAttribute(
+ 'data-title-rich',
+ $this->tooltips[$x]->render($this->order, $data, $format)
+ );
+ }
+ $group->appendChild($invisible->toSvg($ctx));
}
- }
+
return $group;
}
}
diff --git a/library/Icinga/Chart/Graph/Tooltip.php b/library/Icinga/Chart/Graph/Tooltip.php
index 5fa5c3608..8b2efa613 100644
--- a/library/Icinga/Chart/Graph/Tooltip.php
+++ b/library/Icinga/Chart/Graph/Tooltip.php
@@ -66,7 +66,7 @@ class Tooltip
*/
public function __construct (
$data = array(),
- $format = '{title}
{value} of {sum} {label}'
+ $format = '{title}: {value} {label}'
) {
$this->data = array_merge($this->data, $data);
$this->defaultFormat = $format;
diff --git a/library/Icinga/Chart/GridChart.php b/library/Icinga/Chart/GridChart.php
index 7f2794282..9f867ac66 100644
--- a/library/Icinga/Chart/GridChart.php
+++ b/library/Icinga/Chart/GridChart.php
@@ -84,6 +84,13 @@ class GridChart extends Chart
*/
private $tooltips = array();
+ public function __construct()
+ {
+ $this->title = t('Grid Chart');
+ $this->description = t('Contains data in a bar or line chart.');
+ parent::__construct();
+ }
+
/**
* Check if the current dataset has the proper structure for this chart.
*
@@ -395,7 +402,12 @@ class GridChart extends Chart
);
break;
case self::TYPE_LINE:
- $graphObj = new LineGraph($axis->transform($graph['data']));
+ $graphObj = new LineGraph(
+ $axis->transform($graph['data']),
+ $graphs,
+ $dataset,
+ $this->tooltips
+ );
break;
default:
continue;
diff --git a/library/Icinga/Chart/PieChart.php b/library/Icinga/Chart/PieChart.php
index 33ce32927..6b19969c8 100644
--- a/library/Icinga/Chart/PieChart.php
+++ b/library/Icinga/Chart/PieChart.php
@@ -50,6 +50,13 @@ class PieChart extends Chart
*/
private $noCaption = false;
+ public function __construct()
+ {
+ $this->title = t('Pie Chart');
+ $this->description = t('Contains data in a pie chart.');
+ parent::__construct();
+ }
+
/**
* Test if the given pies have the correct format
*
diff --git a/library/Icinga/Chart/Primitive/Canvas.php b/library/Icinga/Chart/Primitive/Canvas.php
index c5617fa35..88e247f02 100644
--- a/library/Icinga/Chart/Primitive/Canvas.php
+++ b/library/Icinga/Chart/Primitive/Canvas.php
@@ -43,6 +43,13 @@ class Canvas implements Drawable
*/
private $rect;
+ /**
+ * The aria role used to describe this canvas' purpose in the accessibility tree
+ *
+ * @var string
+ */
+ private $ariaRole;
+
/**
* Create this canvas
*
@@ -111,6 +118,23 @@ class Canvas implements Drawable
$innerContainer->appendChild($child->toSvg($ctx));
}
+ if (isset($this->ariaRole)) {
+ $outer->setAttribute('role', $this->ariaRole);
+ }
return $outer;
}
+
+ /**
+ * Set the aria role used to determine the meaning of this canvas in the accessibility tree
+ *
+ * The role 'presentation' will indicate that the purpose of this canvas is entirely decorative, while the role
+ * 'img' will indicate that the canvas contains an image, with a possible title or a description. For other
+ * possible roles, see http://www.w3.org/TR/wai-aria/roles
+ *
+ * @param $role string The aria role to set
+ */
+ public function setAriaRole($role)
+ {
+ $this->ariaRole = $role;
+ }
}
diff --git a/library/Icinga/Chart/SVGRenderer.php b/library/Icinga/Chart/SVGRenderer.php
index 5cc223388..98eaa86d6 100644
--- a/library/Icinga/Chart/SVGRenderer.php
+++ b/library/Icinga/Chart/SVGRenderer.php
@@ -48,6 +48,27 @@ class SVGRenderer
*/
private $svg;
+ /**
+ * The description of this SVG, useful for screen readers
+ *
+ * @var string
+ */
+ private $ariaDescription;
+
+ /**
+ * The title of this SVG, useful for screen readers
+ *
+ * @var string
+ */
+ private $ariaTitle;
+
+ /**
+ * The aria role used by this svg element
+ *
+ * @var string
+ */
+ private $ariaRole = 'img';
+
/**
* The root layer for all elements
*
@@ -126,6 +147,7 @@ class SVGRenderer
$svg = $this->document->createElement('svg');
$svg->setAttribute('xmlns', 'http://www.w3.org/2000/svg');
$svg->setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $svg->setAttribute('role', $this->ariaRole);
$svg->setAttribute('width', '100%');
$svg->setAttribute('height', '100%');
$svg->setAttribute(
@@ -150,6 +172,42 @@ class SVGRenderer
return $svg;
}
+ /**
+ * Add aria title and description
+ *
+ * Adds an aria title and desc element to the given SVG node, which are used to describe this SVG by accessibility
+ * tools such as screen readers.
+ *
+ * @param DOMNode $svg The SVG DOMNode to which the aria attributes should be attached
+ * @param $title The title text
+ * @param $description The description text
+ */
+ private function addAriaDescription (DOMNode $svg, $titleText, $descriptionText)
+ {
+ $doc = $svg->ownerDocument;
+
+ $titleId = $descId = '';
+ if (isset ($this->ariaTitle)) {
+ $titleId = 'aria-title-' . $this->stripNonAlphanumeric($titleText);
+ $title = $doc->createElement('title');
+ $title->setAttribute('id', $titleId);
+
+ $title->appendChild($doc->createTextNode($titleText));
+ $svg->appendChild($title);
+ }
+
+ if (isset ($this->ariaDescription)) {
+ $descId = 'aria-desc-' . $this->stripNonAlphanumeric($descriptionText);
+ $desc = $doc->createElement('desc');
+ $desc->setAttribute('id', $descId);
+
+ $desc->appendChild($doc->createTextNode($descriptionText));
+ $svg->appendChild($desc);
+ }
+
+ $svg->setAttribute('aria-labelledby', join(' ', array($titleId, $descId)));
+ }
+
/**
* Initialises the XML-document, SVG-element and this figure's root canvas
*
@@ -172,6 +230,7 @@ class SVGRenderer
{
$this->createRootDocument();
$ctx = $this->createRenderContext();
+ $this->addAriaDescription($this->svg, $this->ariaTitle, $this->ariaDescription);
$this->svg->appendChild($this->rootCanvas->toSvg($ctx));
$this->document->formatOutput = true;
return $this->document->saveXML();
@@ -232,4 +291,40 @@ class SVGRenderer
{
$this->yAspectRatio = $alignment;
}
+
+ /**
+ * Set the aria description, that is used as a title for this SVG in screen readers
+ *
+ * @param $text
+ */
+ public function setAriaTitle($text)
+ {
+ $this->ariaTitle = $text;
+ }
+
+ /**
+ * Set the aria description, that is used to describe this SVG in screen readers
+ *
+ * @param $text
+ */
+ public function setAriaDescription($text)
+ {
+ $this->ariaDescription = $text;
+ }
+
+ /**
+ * Set the aria role, that is used to describe the purpose of this SVG in screen readers
+ *
+ * @param $text
+ */
+ public function setAriaRole($text)
+ {
+ $this->ariaRole = $text;
+ }
+
+
+ private function stripNonAlphanumeric($str)
+ {
+ return preg_replace('/[^A-Za-z]+/', '', $str);
+ }
}
diff --git a/library/Icinga/Web/Widget/Chart/InlinePie.php b/library/Icinga/Web/Widget/Chart/InlinePie.php
index 341322714..0bddc906d 100644
--- a/library/Icinga/Web/Widget/Chart/InlinePie.php
+++ b/library/Icinga/Web/Widget/Chart/InlinePie.php
@@ -44,7 +44,6 @@ EOD;
EOD;
-
/**
* @var Url
*/
diff --git a/modules/monitoring/application/controllers/AlertsummaryController.php b/modules/monitoring/application/controllers/AlertsummaryController.php
index b36f7ac3e..2c884e60f 100644
--- a/modules/monitoring/application/controllers/AlertsummaryController.php
+++ b/modules/monitoring/application/controllers/AlertsummaryController.php
@@ -343,6 +343,8 @@ class Monitoring_AlertsummaryController extends Controller
public function createHealingChart()
{
$gridChart = new GridChart();
+ $gridChart->title = t('Healing Chart');
+ $gridChart->description = t('Notifications and average reaction time per hour.');
$gridChart->alignTopLeft();
$gridChart->setAxisLabel($this->createPeriodDescription(), mt('monitoring', 'Notifications'))
@@ -446,7 +448,8 @@ class Monitoring_AlertsummaryController extends Controller
'label' => $this->translate('Notifications'),
'color' => '#07C0D9',
'data' => $notifications,
- 'showPoints' => true
+ 'showPoints' => true,
+ 'tooltip' => '{title}: {value} {label}'
)
);
@@ -455,7 +458,8 @@ class Monitoring_AlertsummaryController extends Controller
'label' => $this->translate('Avg (min)'),
'color' => '#ffaa44',
'data' => $dAvg,
- 'showPoints' => true
+ 'showPoints' => true,
+ 'tooltip' => t('{title}: {value}m min. reaction time')
)
);
@@ -464,7 +468,8 @@ class Monitoring_AlertsummaryController extends Controller
'label' => $this->translate('Max (min)'),
'color' => '#ff5566',
'data' => $dMax,
- 'showPoints' => true
+ 'showPoints' => true,
+ 'tooltip' => t('{title}: {value}m max. reaction time')
)
);
@@ -479,6 +484,8 @@ class Monitoring_AlertsummaryController extends Controller
public function createDefectImage()
{
$gridChart = new GridChart();
+ $gridChart->title = t('Defect Chart');
+ $gridChart->description = t('Notifications and defects per hour');
$gridChart->alignTopLeft();
$gridChart->setAxisLabel($this->createPeriodDescription(), mt('monitoring', 'Notifications'))
@@ -491,7 +498,8 @@ class Monitoring_AlertsummaryController extends Controller
'label' => $this->translate('Notifications'),
'color' => '#07C0D9',
'data' => $this->notificationData,
- 'showPoints' => true
+ 'showPoints' => true,
+ 'tooltip' => '{title}: {value} {label}'
)
);
@@ -500,7 +508,8 @@ class Monitoring_AlertsummaryController extends Controller
'label' => $this->translate('Defects'),
'color' => '#ff5566',
'data' => $this->problemData,
- 'showPoints' => true
+ 'showPoints' => true,
+ 'tooltip' => '{title}: {value} {label}'
)
);
diff --git a/modules/monitoring/application/controllers/ChartController.php b/modules/monitoring/application/controllers/ChartController.php
index b01702c04..f19d3f458 100644
--- a/modules/monitoring/application/controllers/ChartController.php
+++ b/modules/monitoring/application/controllers/ChartController.php
@@ -236,6 +236,9 @@ class Monitoring_ChartController extends Controller
$unknownBars[] = array($servicegroup->servicegroup, $servicegroup->services_unknown_unhandled);
}
$this->view->chart = new GridChart();
+ $this->view->chart->title = t('Service Group Chart');
+ $this->view->chart->description = t('Contains service states for each service group.');
+
$this->view->chart->alignTopLeft();
$this->view->chart->setAxisLabel('', mt('monitoring', 'Services'))
->setXAxis(new StaticAxis())
@@ -292,6 +295,9 @@ class Monitoring_ChartController extends Controller
}
$tooltip = mt('monitoring', '{title}:
{value} of {sum} hosts are {label}');
$this->view->chart = new GridChart();
+ $this->view->chart->title = t('Host Group Chart');
+ $this->view->chart->description = t('Contains host states of each service group.');
+
$this->view->chart->alignTopLeft();
$this->view->chart->setAxisLabel('', mt('monitoring', 'Hosts'))
->setXAxis(new StaticAxis())
diff --git a/modules/monitoring/application/forms/Config/BackendConfigForm.php b/modules/monitoring/application/forms/Config/BackendConfigForm.php
index e0f8e3e0d..265ab33bc 100644
--- a/modules/monitoring/application/forms/Config/BackendConfigForm.php
+++ b/modules/monitoring/application/forms/Config/BackendConfigForm.php
@@ -43,8 +43,11 @@ class BackendConfigForm extends ConfigForm
{
$resources = array();
foreach ($resourceConfig as $name => $resource) {
- if ($resource->type === 'db' || $resource->type === 'livestatus') {
- $resources[$resource->type === 'db' ? 'ido' : 'livestatus'][$name] = $name;
+// if ($resource->type === 'db' || $resource->type === 'livestatus') {
+// $resources[$resource->type === 'db' ? 'ido' : 'livestatus'][$name] = $name;
+// }
+ if ($resource->type === 'db') {
+ $resources['ido'][$name] = $name;
}
}
@@ -183,13 +186,19 @@ class BackendConfigForm extends ConfigForm
{
$resourceType = isset($formData['type']) ? $formData['type'] : key($this->resources);
+ if ($resourceType === 'livestatus') {
+ throw new ConfigurationError(
+ 'We\'ve disabled livestatus support for now because it\'s not feature complete yet'
+ );
+ }
+
$resourceTypes = array();
if ($resourceType === 'ido' || array_key_exists('ido', $this->resources)) {
$resourceTypes['ido'] = 'IDO Backend';
}
- if ($resourceType === 'livestatus' || array_key_exists('livestatus', $this->resources)) {
- $resourceTypes['livestatus'] = 'Livestatus';
- }
+// if ($resourceType === 'livestatus' || array_key_exists('livestatus', $this->resources)) {
+// $resourceTypes['livestatus'] = 'Livestatus';
+// }
$this->addElement(
'checkbox',
diff --git a/modules/monitoring/application/forms/Setup/BackendPage.php b/modules/monitoring/application/forms/Setup/BackendPage.php
index d8d4c7fd0..f2df42a1f 100644
--- a/modules/monitoring/application/forms/Setup/BackendPage.php
+++ b/modules/monitoring/application/forms/Setup/BackendPage.php
@@ -51,7 +51,7 @@ class BackendPage extends Form
if (Platform::hasMysqlSupport() || Platform::hasPostgresqlSupport()) {
$resourceTypes['ido'] = 'IDO';
}
- $resourceTypes['livestatus'] = 'Livestatus';
+ // $resourceTypes['livestatus'] = 'Livestatus';
$this->addElement(
'select',
diff --git a/public/js/icinga/behavior/tooltip.js b/public/js/icinga/behavior/tooltip.js
index b08b1f3c1..c9a64257f 100644
--- a/public/js/icinga/behavior/tooltip.js
+++ b/public/js/icinga/behavior/tooltip.js
@@ -27,7 +27,7 @@
var $el = $(this);
$el.attr('title', $el.data('title-rich') || $el.attr('title'));
});
- $('svg rect.chart-data[title]', el).tipsy({ gravity: 'se', html: true });
+ $('svg .chart-data', el).tipsy({ gravity: 'se', html: true });
$('.historycolorgrid a[title]', el).tipsy({ gravity: 's', offset: 2 });
$('img.icon[title]', el).tipsy({ gravity: $.fn.tipsy.autoNS, offset: 2 });
$('[title]', el).tipsy({ gravity: $.fn.tipsy.autoNS, delayIn: 500 });