diff --git a/.gitignore b/.gitignore index 1444ecfb2..55580cae9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ build/ +development/ + # ./configure output config.log autom4te.cache diff --git a/application/controllers/ListController.php b/application/controllers/ListController.php index 599df06a1..004c52148 100644 --- a/application/controllers/ListController.php +++ b/application/controllers/ListController.php @@ -8,7 +8,8 @@ use Icinga\Web\Url; use Icinga\Data\ResourceFactory; use Icinga\Logger\Logger; use Icinga\Logger\Writer\FileWriter; -use Icinga\Protocol\File\Reader as FileReader; +use Icinga\Protocol\File\FileReader; +use \Zend_Controller_Action_Exception as ActionError; /** * Class ListController @@ -38,20 +39,21 @@ class ListController extends Controller */ public function applicationlogAction() { + if (! Logger::writesToFile()) { + throw new ActionError('Site not found', 404); + } + $this->addTitleTab('application log'); + $pattern = '/^(?[0-9]{4}(-[0-9]{2}){2}' // date + . 'T[0-9]{2}(:[0-9]{2}){2}([\\+\\-][0-9]{2}:[0-9]{2})?)' // time + . ' - (?[A-Za-z]+)' // loglevel + . ' - (?.*)$/'; // message $loggerWriter = Logger::getInstance()->getWriter(); - if ($loggerWriter instanceof FileWriter) { - $resource = new FileReader(new Zend_Config(array( - 'filename' => $loggerWriter->getPath(), - 'fields' => '/^(?[0-9]{4}(-[0-9]{2}){2}' // date - . 'T[0-9]{2}(:[0-9]{2}){2}([\\+\\-][0-9]{2}:[0-9]{2})?)' // time - . ' - (?[A-Za-z]+)' // loglevel - . ' - (?.*)$/' // message - ))); - $this->view->logData = $resource->select()->order('DESC')->paginate(); - } else { - $this->view->logData = null; - } + $resource = new FileReader(new Zend_Config(array( + 'filename' => $loggerWriter->getPath(), + 'fields' => $pattern + ))); + $this->view->logData = $resource->select()->order('DESC')->paginate(); } } diff --git a/application/controllers/SearchController.php b/application/controllers/SearchController.php index f2bf1ceac..68fd5dd3d 100644 --- a/application/controllers/SearchController.php +++ b/application/controllers/SearchController.php @@ -3,9 +3,8 @@ // {{{ICINGA_LICENSE_HEADER}}} use Icinga\Web\Controller\ActionController; -use Icinga\Application\Icinga; use Icinga\Web\Widget; -use Icinga\Web\Url; +use Icinga\Web\Widget\SearchDashboard; /** * Search controller @@ -14,47 +13,13 @@ class SearchController extends ActionController { public function indexAction() { - $search = $this->_request->getParam('q'); - if (! $search) { - $this->view->tabs = Widget::create('tabs')->add( - 'search', - array( - 'title' => $this->translate('Search'), - 'url' => '/search', - ) - )->activate('search'); - $this->render('hint'); - return; - } - $dashboard = Widget::create('dashboard')->createPane($this->translate('Search')); - $pane = $dashboard->getPane($this->translate('Search')); - $suffix = strlen($search) ? ': ' . rtrim($search, '*') . '*' : ''; - $pane->addComponent( - $this->translate('Hosts') . $suffix, - Url::fromPath('monitoring/list/hosts', array( - 'host_name' => $search . '*', - 'sort' => 'host_severity', - 'limit' => 10, - ) - )); - $pane->addComponent( - $this->translate('Services') . $suffix, - Url::fromPath('monitoring/list/services', array( - 'service_description' => $search . '*', - 'sort' => 'service_severity', - 'limit' => 10, - ) - )); - $pane->addComponent('Hostgroups' . $suffix, Url::fromPath('monitoring/list/hostgroups', array( - 'hostgroup' => $search . '*', - 'limit' => 10, - ))); - $pane->addComponent('Servicegroups' . $suffix, Url::fromPath('monitoring/list/servicegroups', array( - 'servicegroup' => $search . '*', - 'limit' => 10, - ))); - $dashboard->activate($this->translate('Search')); - $this->view->dashboard = $dashboard; - $this->view->tabs = $dashboard->getTabs(); + $this->view->dashboard = SearchDashboard::search($this->params->get('q')); + + // NOTE: This renders the dashboard twice. Remove this once we can catch exceptions thrown in view scripts. + $this->view->dashboard->render(); + } + + public function hintAction() + { } } diff --git a/application/controllers/StaticController.php b/application/controllers/StaticController.php index c90272ffc..fec60a624 100644 --- a/application/controllers/StaticController.php +++ b/application/controllers/StaticController.php @@ -47,7 +47,7 @@ class StaticController extends ActionController $cache->send($cacheFile); return; } - $img = file_get_contents('http://www.gravatar.com/avatar/' . $filename . '?s=200&d=mm'); + $img = file_get_contents('http://www.gravatar.com/avatar/' . $filename . '?s=120&d=mm'); $cache->store($cacheFile, $img); header('ETag: "' . $cache->etagForCachedFile($cacheFile) . '"'); echo $img; diff --git a/application/layouts/scripts/layout.phtml b/application/layouts/scripts/layout.phtml index 32c4879b4..c4354cee2 100644 --- a/application/layouts/scripts/layout.phtml +++ b/application/layouts/scripts/layout.phtml @@ -29,17 +29,17 @@ $iframeClass = $isIframe ? ' iframe' : ''; <?= $this->title ? $this->escape($this->title) : 'Icinga Web' ?> - - + - + - + + - compact): ?> monitoringState($host, 'host')) ?>
- + host_state !== 99): ?> prefixedTimeSince($host->host_last_state_change, true) ?> host_state > 0 && (int) $host->host_state_type === 0): ?>
Soft host_attempt ?> + diff --git a/modules/monitoring/application/views/scripts/list/notifications.phtml b/modules/monitoring/application/views/scripts/list/notifications.phtml index 147b2ef67..ee414f502 100644 --- a/modules/monitoring/application/views/scripts/list/notifications.phtml +++ b/modules/monitoring/application/views/scripts/list/notifications.phtml @@ -1,15 +1,13 @@ compact): ?> -
- tabs ?> -
+
+ tabs ?> +
translate('Sort by') ?> sortControl->render($this) ?> - selectionToolbar('single') ?> -
- widget('limiter') ?> - count() >= 100): ?>
- paginationControl($notifications, null, null, array('preserve' => $this->preserve)) ?> -
+
+ widget('limiter') ?> + paginationControl($notifications, null, null, array('preserve' => $this->preserve)) ?> +
@@ -47,7 +45,7 @@ foreach ($notifications as $notification): ?> timeSince($notification->notification_start_time) ?> - + service ?> on host ?> @@ -56,12 +54,14 @@ foreach ($notifications as $notification):
escape(substr(strip_tags($notification->notification_output), 0, 10000)); ?>
+ contact): ?> Sent to escape($notification->notification_contact) ?> + diff --git a/modules/monitoring/application/views/scripts/list/services.phtml b/modules/monitoring/application/views/scripts/list/services.phtml index f2da63dca..dbb43217d 100644 --- a/modules/monitoring/application/views/scripts/list/services.phtml +++ b/modules/monitoring/application/views/scripts/list/services.phtml @@ -1,10 +1,13 @@ getHelper('MonitoringState'); +$selfUrl = 'monitoring/list/services'; + if (!$this->compact): ?>
tabs ?>
+render('list/components/servicesummary.phtml') ?> translate('Sort by') ?> sortControl ?> filterEditor): ?> filterPreview ?> @@ -21,6 +24,9 @@ if (!$this->compact): ?>
filterEditor ?> + + +
-compact): ?>
- diff --git a/modules/monitoring/application/views/scripts/list/statehistorysummary.phtml b/modules/monitoring/application/views/scripts/list/statehistorysummary.phtml index d7c5527d8..55c41ffcb 100644 --- a/modules/monitoring/application/views/scripts/list/statehistorysummary.phtml +++ b/modules/monitoring/application/views/scripts/list/statehistorysummary.phtml @@ -1,11 +1,15 @@ -
+
tabs ?>

History - Critical Events

setColor('#FC0707'); +$grid->setColor('#f05060'); $data = array(); if (count($summary) === 0) { @@ -16,19 +20,20 @@ foreach ($summary as $entry) { $value = $entry->cnt_critical; $caption = $value . ' ' . t('critical events on ') . $this->dateFormat()->formatDate(strtotime($day)); + $filter = Filter::matchAll( + Filter::expression('timestamp', '<', strtotime($day . ' 23:59:59')), + Filter::expression('timestamp', '>', strtotime($day . ' 00:00:00')), + Filter::expression('object_type', '=', 'service'), + Filter::expression('state', '=', '2'), + Filter::matchAny( + Filter::expression('type', '=', 'hard_state'), + Filter::expression('type', '=', 'hard_state') + ) + ); $data[$day] = array( 'value' => $value, 'caption' => $caption, - 'url' => $this->href( - 'monitoring/list/eventhistory', - array( - 'timestamp<' => strtotime($day . ' 23:59:59'), - 'timestamp>' => strtotime($day . ' 00:00:00'), - 'object_type' => 'service', - 'state' => '2', - 'type' => '(hard_state|soft_state)' - ) - ) + 'url' => $this->href('monitoring/list/eventhistory?' . $filter->toQueryString()) ); } $grid->setData($data); diff --git a/modules/monitoring/application/views/scripts/show/components/hostservicesummary.phtml b/modules/monitoring/application/views/scripts/show/components/hostservicesummary.phtml new file mode 100644 index 000000000..0b4b9c9fe --- /dev/null +++ b/modules/monitoring/application/views/scripts/show/components/hostservicesummary.phtml @@ -0,0 +1,83 @@ + $this->object->host_name)); +$currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl(); + +?> +

compact ? ' data-base-target="col1"' : '' ?>> +qlink(sprintf($this->translate('%s service configured:'), $this->stats->services_total), $selfUrl) ?> +stats->services_ok > 0): ?> + qlink( + $this->stats->services_ok, + $selfUrl, + array('service_state' => 0), + array('title' => sprintf($this->translate('Services with state %s'), strtoupper($this->translate('ok')))) +) ?> + + 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $state) { + $pre = 'services_' . $state; + if ($this->stats->$pre) { + $handled = $pre . '_handled'; + $unhandled = $pre . '_unhandled'; + $paramsHandled = array('service_state' => $stateId, 'service_handled' => 1); + $paramsUnhandled = array('service_state' => $stateId, 'service_handled' => 0); + if ($this->stats->$unhandled) { + $compareUrl = $selfUrl->with($paramsUnhandled)->getRelativeUrl(); + } else { + $compareUrl = $selfUrl->with($paramsHandled)->getRelativeUrl(); + } + + if ($compareUrl === $currentUrl) { + $active = ' active'; + } else { + $active = ''; + } + + echo ''; + if ($this->stats->$unhandled) { + + echo $this->qlink( + $this->stats->$unhandled, + $selfUrl, + $paramsUnhandled, + array('title' => sprintf($this->translate('Unhandled services with state %s'), strtoupper($this->translate($state)))) + ); + } + if ($this->stats->$handled) { + + if ($selfUrl->with($paramsHandled)->getRelativeUrl() === $currentUrl) { + $active = ' active'; + } else { + $active = ''; + } + if ($this->stats->$unhandled) { + echo ''; + } + echo $this->qlink( + $this->stats->$handled, + $selfUrl, + $paramsHandled, + array('title' => sprintf($this->translate('Handled services with state %s'), strtoupper($this->translate($state)))) + ); + if ($this->stats->$unhandled) { + echo "\n"; + } + } + echo "\n"; + } +} + +?> +stats->services_pending): ?> + qlink( + $this->stats->services_pending, + $selfUrl, + array('service_state' => 99), + array('title' => sprintf($this->translate('Services with state %s'), strtoupper($this->translate('pending')))) +) ?> + +

+ diff --git a/modules/monitoring/application/views/scripts/show/contact.phtml b/modules/monitoring/application/views/scripts/show/contact.phtml index a2e720ec9..c4dc41b9e 100644 --- a/modules/monitoring/application/views/scripts/show/contact.phtml +++ b/modules/monitoring/application/views/scripts/show/contact.phtml @@ -1,69 +1,59 @@ -getHelper('ContactFlags'); -?> -
- - - - - - - - - - - - - contact_pager): ?> - - - - - - - - - - - - - - - - - - - - - - -
- escape($contact->contact_name) ?> (escape($contact->contact_alias) - ?>) -
%1$s', - $this->escape($contact->contact_email) - ); ?>
escape($contact->contact_pager) ?>
escape($contactHelper->contactFlags($contact, 'service')) ?>
escape($contactHelper->contactFlags($contact, 'host')) ?>
escape($contact->contact_notify_service_timeperiod) ?>
escape($contact->contact_notify_host_timeperiod) ?>
+getHelper('ContactFlags') ?> +
+

translate('Contact details') ?>

+
- -

translate('Commands'); ?>:

-
    - -
  • command_name; ?>
  • - -
- - - -

translate('Notifications'); ?>:

- - render('list/notifications.phtml') ?> - -

translate('No notifications for this contact'); ?>

- - - - translate('No such contact'); ?>: - + + translate('No such contact') ?>:
+ + + + + + + + +contact_email): ?> + + + + + +contact_pager): ?> + + + + + + + + + + + + + + +
escape($contact->contact_alias) ?> (contact_name ?>)
%1$s', $this->escape($contact->contact_email)) ?>
escape($contact->contact_pager) ?>
escape($contactHelper->contactFlags($contact, 'host')) ?>
+ escape($contact->contact_notify_host_timeperiod) ?>
escape($contactHelper->contactFlags($contact, 'service')) ?>
+ escape($contact->contact_notify_service_timeperiod) ?>
+ + +

translate('Commands') ?>:

+
    + +
  • command_name ?>
  • + +
+ +

translate('Notifications sent to this contact') ?>

+
+ + +render('list/notifications.phtml') ?> + +
translate('No notifications have been sent for this contact') ?>
+ diff --git a/modules/monitoring/application/views/scripts/show/history.phtml b/modules/monitoring/application/views/scripts/show/history.phtml index f2cfacb23..e2c13aa8b 100644 --- a/modules/monitoring/application/views/scripts/show/history.phtml +++ b/modules/monitoring/application/views/scripts/show/history.phtml @@ -11,6 +11,16 @@
+qlink($contact, 'monitoring/show/contact', array('contact' => $contact)); + } + return '[' . implode(', ', $links) . ']'; +} +?> + @@ -21,51 +31,61 @@ case 'notify': $icon = 'notification'; $title = $this->translate('Notification'); - $msg = $event->output; + $stateClass = ( + $isService + ? strtolower($this->util()->getServiceStateName($event->state)) + : strtolower($this->util()->getHostStateName($event->state)) + ); + + $msg = preg_replace_callback( + '/^\[([^\]]+)\]/', + function($match) { return contactsLink($match, $this); }, + $this->escape($event->output) + ); break; case 'comment': $icon = 'comment'; $title = $this->translate('Comment'); - $msg = $event->output; + $msg = $this->escape($event->output); break; case 'comment_deleted': $icon = 'remove'; $title = $this->translate('Comment deleted'); - $msg = $event->output; + $msg = $this->escape($event->output); break; case 'ack': $icon = 'acknowledgement'; $title = $this->translate('Acknowledge'); - $msg = $event->output; + $msg = $this->escape($event->output); break; case 'ack_deleted': $icon = 'remove'; $title = $this->translate('Ack removed'); - $msg = $event->output; + $msg = $this->escape($event->output); break; case 'dt_comment': $icon = 'in_downtime'; $title = $this->translate('In Downtime'); - $msg = $event->output; + $msg = $this->escape($event->output); break; case 'dt_comment_deleted': $icon = 'remove'; $title = $this->translate('Downtime removed'); - $msg = $event->output; + $msg = $this->escape($event->output); break; case 'flapping': $icon = 'flapping'; $title = $this->translate('Flapping'); - $msg = $event->output; + $msg = $this->escape($event->output); break; case 'flapping_deleted': $icon = 'remove'; $title = $this->translate('Flapping stopped'); - $msg = $event->output; + $msg = $this->escape($event->output); break; case 'hard_state': $icon = $isService ? 'service' : 'host'; - $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output; + $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $this->escape($event->output); $stateClass = ( $isService ? strtolower($this->util()->getServiceStateName($event->state)) @@ -75,7 +95,7 @@ break; case 'soft_state': $icon = 'softstate'; - $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $event->output; + $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $this->escape($event->output); $stateClass = ( $isService ? strtolower($this->util()->getServiceStateName($event->state)) @@ -86,12 +106,12 @@ case 'dt_start': $icon = 'downtime_start'; $title = $this->translate('Downtime Start'); - $msg = $event->output; + $msg = $this->escape($event->output); break; case 'dt_end': $icon = 'downtime_end'; $title = $this->translate('Downtime End'); - $msg = $event->output; + $msg = $this->escape($event->output); break; } ?> @@ -106,8 +126,8 @@ $output = $this->tickets ? preg_replace_callback( $this->tickets->getPattern(), array($this->tickets, 'createLink'), - $this->escape($msg) -) : $this->escape($msg); + $msg +) : $msg; ?> diff --git a/modules/monitoring/application/views/scripts/show/host.phtml b/modules/monitoring/application/views/scripts/show/host.phtml index a0e50389d..c4e0ec9a8 100644 --- a/modules/monitoring/application/views/scripts/show/host.phtml +++ b/modules/monitoring/application/views/scripts/show/host.phtml @@ -1,7 +1,7 @@ host_name !== false): ?>
render('show/components/header.phtml') ?> -

translate("This host's current state") ?>

+ render('show/components/hostservicesummary.phtml') ?>
render('show/components/output.phtml') ?> diff --git a/modules/monitoring/application/views/scripts/show/services.phtml b/modules/monitoring/application/views/scripts/show/services.phtml index bd734b97b..a78b3215b 100644 --- a/modules/monitoring/application/views/scripts/show/services.phtml +++ b/modules/monitoring/application/views/scripts/show/services.phtml @@ -1,7 +1,5 @@
render('show/components/header.phtml') ?> -

translate('All services configured on this host') ?>

+render('show/components/hostservicesummary.phtml') ?>
-
-
diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 412581d24..b40a8c282 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -19,18 +19,29 @@ $this->provideConfigTab('security', array( 'url' => 'config/security' )); +/* + * Available Search Urls + */ +$this->provideSearchUrl($this->translate('Hosts'), 'monitoring/list/hosts?sort=host_severity&limit=10'); +$this->provideSearchUrl($this->translate('Services'), 'monitoring/list/services?sort=service_severity&limit=10'); +$this->provideSearchUrl($this->translate('Hostgroups'), 'monitoring/list/hostgroups?limit=10'); +$this->provideSearchUrl($this->translate('Servicegroups'), 'monitoring/list/servicegroups?limit=10'); + /* * Problems Section */ $section = $this->menuSection($this->translate('Problems'), array( - 'icon' => 'img/icons/error.png', - 'priority' => 20 + 'renderer' => 'ProblemMenuItemRenderer', + 'icon' => 'img/icons/error.png', + 'priority' => 20 )); $section->add($this->translate('Unhandled Hosts'), array( + 'renderer' => 'UnhandledHostMenuItemRenderer', 'url' => 'monitoring/list/hosts?host_problem=1&host_handled=0', 'priority' => 40 )); $section->add($this->translate('Unhandled Services'), array( + 'renderer' => 'UnhandledServiceMenuItemRenderer', 'url' => 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity', 'priority' => 40 )); diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php index 64c7e69b8..4bf712f4c 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php @@ -42,15 +42,15 @@ class NotificationhistoryQuery extends IdoQuery { switch ($this->ds->getDbType()) { case 'mysql': - $concattedContacts = "GROUP_CONCAT(c.alias ORDER BY c.alias SEPARATOR ', ')"; + $concattedContacts = "GROUP_CONCAT(co.name1 ORDER BY co.name1 SEPARATOR ', ') COLLATE latin1_general_ci"; break; case 'pgsql': // TODO: Find a way to order the contact alias list: - $concattedContacts = "ARRAY_TO_STRING(ARRAY_AGG(c.alias), ', ')"; + $concattedContacts = "ARRAY_TO_STRING(ARRAY_AGG(co.name1), ', ')"; break; case 'oracle': // TODO: This is only valid for Oracle >= 11g Release 2 - $concattedContacts = "LISTAGG(c.alias, ', ') WITHIN GROUP (ORDER BY c.alias)"; + $concattedContacts = "LISTAGG(co.name1, ', ') WITHIN GROUP (ORDER BY co.name1)"; // Alternatives: // // RTRIM(XMLAGG(XMLELEMENT(e, column_name, ',').EXTRACT('//text()')), @@ -74,9 +74,13 @@ class NotificationhistoryQuery extends IdoQuery array('cn' => $this->prefix . 'contactnotifications'), 'cn.notification_id = n.notification_id', array() + )->joinLeft( + array('co' => $this->prefix . 'objects'), + 'cn.contact_object_id = co.object_id', + array() )->joinLeft( array('c' => $this->prefix . 'contacts'), - 'cn.contact_object_id = c.contact_object_id', + 'co.object_id = c.contact_object_id', array() )->group('cn.notification_id'); diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php index 81d7e662c..75b5ab284 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php @@ -6,6 +6,11 @@ namespace Icinga\Module\Monitoring\Backend\Ido\Query; class StatehistoryQuery extends IdoQuery { + protected $types = array( + 'soft_state' => 0, + 'hard_state' => 1, + ); + protected $columnMap = array( 'statehistory' => array( 'raw_timestamp' => 'sh.state_time', @@ -33,6 +38,8 @@ class StatehistoryQuery extends IdoQuery { if ($col === 'UNIX_TIMESTAMP(sh.state_time)') { return 'sh.state_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); + } elseif ($col === $this->columnMap['statehistory']['type']) { + return 'sh.state_type ' . $sign . ' ' . $this->types[$expression]; } else { return parent::whereToSql($col, $sign, $expression); } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php index 6555010c2..7d91f0f71 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php @@ -8,7 +8,15 @@ use Zend_Db_Select; class StatusSummaryQuery extends IdoQuery { + protected $subHosts; + + protected $subServices; + protected $columnMap = array( + 'services' => array( + 'service_host_name' => 'so.name1', + 'service_description' => 'so.name2', + ), 'hoststatussummary' => array( 'hosts_up' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 THEN 1 ELSE 0 END)', 'hosts_up_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', @@ -159,10 +167,24 @@ class StatusSummaryQuery extends IdoQuery 'object_type' => '(\'service\')' )); $union = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL); + $this->subHosts = $hosts; + $this->subServices = $services; $this->select->from(array('statussummary' => $union), array()); $this->joinedVirtualTables = array( - 'servicestatussummary' => true, - 'hoststatussummary' => true + 'services' => true, + 'servicestatussummary' => true, + 'hoststatussummary' => true ); } + + public function whereToSql($col, $sign, $expression) + { + if ($col === 'so.name1') { + $this->subServices->where('so.name1 ' . $sign . ' ?', $expression); + return ''; + return 'sh.state_time ' . $sign . ' ' . $this->timestampForSql($this->valueToTimestamp($expression)); + } else { + return parent::whereToSql($col, $sign, $expression); + } + } } diff --git a/modules/monitoring/library/Monitoring/Object/Host.php b/modules/monitoring/library/Monitoring/Object/Host.php index 8dbe1176e..1773337eb 100644 --- a/modules/monitoring/library/Monitoring/Object/Host.php +++ b/modules/monitoring/library/Monitoring/Object/Host.php @@ -7,7 +7,7 @@ namespace Icinga\Module\Monitoring\Object; use Icinga\Module\Monitoring\DataView\HostStatus; use Icinga\Data\Db\DbQuery; -class Host extends AbstractObject +class Host extends MonitoredObject { public $type = 'host'; public $prefix = 'host_'; @@ -69,7 +69,8 @@ class Host extends AbstractObject 'host_action_url', 'host_notes_url', 'host_modified_host_attributes', - 'host_problem' + 'host_problem', + 'process_perfdata' => 'host_process_performance_data', ))->where('host_name', $this->params->get('host')); return $this->view->getQuery()->fetchRow(); } diff --git a/modules/monitoring/library/Monitoring/Object/AbstractObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php similarity index 99% rename from modules/monitoring/library/Monitoring/Object/AbstractObject.php rename to modules/monitoring/library/Monitoring/Object/MonitoredObject.php index 703d05780..8ff79f7b8 100644 --- a/modules/monitoring/library/Monitoring/Object/AbstractObject.php +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -23,7 +23,7 @@ use Icinga\Web\UrlParams; use Icinga\Application\Config; -abstract class AbstractObject +abstract class MonitoredObject { public $type; public $prefix; diff --git a/modules/monitoring/library/Monitoring/Object/Service.php b/modules/monitoring/library/Monitoring/Object/Service.php index bfc8325db..3325ecc6b 100644 --- a/modules/monitoring/library/Monitoring/Object/Service.php +++ b/modules/monitoring/library/Monitoring/Object/Service.php @@ -7,7 +7,7 @@ namespace Icinga\Module\Monitoring\Object; use Icinga\Module\Monitoring\DataView\ServiceStatus; use Icinga\Data\Db\DbQuery; -class Service extends AbstractObject +class Service extends MonitoredObject { public $type = 'service'; public $prefix = 'service_'; @@ -114,6 +114,7 @@ class Service extends AbstractObject 'service_flap_detection_enabled', 'service_flap_detection_enabled_changed', 'service_modified_service_attributes', + 'process_perfdata' => 'service_process_performance_data', ))->where('host_name', $this->params->get('host')) ->where('service_description', $this->params->get('service')); diff --git a/modules/monitoring/public/css/module.less b/modules/monitoring/public/css/module.less index 1d60b75c7..4ee048529 100644 --- a/modules/monitoring/public/css/module.less +++ b/modules/monitoring/public/css/module.less @@ -53,3 +53,80 @@ div.contacts div.contact > img { div.contacts div.notification-periods { margin-top: 0.5em; } + +h3.tinystatesummary { + line-height: 2em; + font-size: 1em; + font-weight: bold; + padding-left: 1em; + background-color: #555; + color: white; + border: none; + border-radius: 0.2em; + -moz-border-radius: 0.2em; + -webkit-border-radius: 0.2em; +} + +h3.tinystatesummary a { + color: inherit; + text-decoration: none; + padding: 1px 3px; +} + +h3.tinystatesummary a:hover { + text-decoration: underline; +} + +/* State badges */ +span.state { + font-size: 0.8em; + color: white; + font-weight: bold; + padding: 1px 2px; + margin-right: 5px; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border: 2px solid transparent; +} + +span.state.active { + border: 2px solid white; +} + +span.state span.state { + font-size: 1em; + margin: 0 -3px 0 5px; +} + +span.state.ok { + background: @colorOk; +} + +span.state.critical { + background: @colorCritical; +} + +span.state.handled.critical { + background: @colorCriticalHandled; +} + +span.state.warning { + background: @colorWarning; +} + +span.state.handled.warning { + background: @colorWarningHandled; +} + +span.state.unknown { + background: @colorUnknown; +} + +span.state.handled.unknown { + background: @colorUnknownHandled; +} + +span.state.pending { + background: @colorPending; +} diff --git a/public/css/icinga/menu.less b/public/css/icinga/menu.less index 1f30b4c46..6f8b97fa9 100644 --- a/public/css/icinga/menu.less +++ b/public/css/icinga/menu.less @@ -26,7 +26,6 @@ #menu li { margin-left: 0.5em; - margin-right: 0.5em; } #menu > ul > li { @@ -159,7 +158,7 @@ } #menu li.hover > ul a { - width: 100%; + width: 95%; display: block; color: white; } diff --git a/public/css/icinga/pagination.less b/public/css/icinga/pagination.less index ac9ea9372..8d1f2ee55 100644 --- a/public/css/icinga/pagination.less +++ b/public/css/icinga/pagination.less @@ -5,6 +5,7 @@ ul.pagination { font-size: 0.68em; padding: 0; display: inline; + white-space: nowrap; -webkit-touch-callout: none; -webkit-user-select: none; diff --git a/public/css/icinga/widgets.less b/public/css/icinga/widgets.less index a5c540a1e..761c2ca0e 100644 --- a/public/css/icinga/widgets.less +++ b/public/css/icinga/widgets.less @@ -179,3 +179,68 @@ ul.tree li a.error:hover { padding: 1.5em; height: 80vh; } + +.badge-container { + font-size: 1em; + display: inline-block; + float: right; + margin-right: 0.6em; +} + + +li li .badge-container { + /* + fix margin for smaller font-size of list elements + 1 = 0,8em / 0.8em + */ + margin-right: 0.75em; +} + +#menu > ul > li.active > a > .badge-container { + display: none; +} + +#layout.hoveredmenu .hover > a > .badge-container { + margin-right: 14.15em; +} + +.badge { + position: relative; + top: -0.1em; + display: inline-block; + min-width: 1em; + padding: 3px 7px; + font-size: 0.8em; + font-weight: 700; + line-height: 1.1em; + color: white; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 1em; + background-color: @colorInvalid; +} + +li li .badge { + font-size: 0.975em; +} + +.badge-critical { + background-color: @colorCritical; +} + +.badge-warning { + background-color: @colorWarning; +} + +.badge-ok { + background-color: @colorOk; +} + +.badge-pending { + background-color: @colorPending; +} + +.badge-pending { + background-color: @colorUnknown; +} diff --git a/public/img/logo_icinga-inv.png b/public/img/logo_icinga-inv.png index 63a0e202e..d43d74b96 100644 Binary files a/public/img/logo_icinga-inv.png and b/public/img/logo_icinga-inv.png differ diff --git a/public/js/icinga.js b/public/js/icinga.js index b0b1977b3..04c7cdcba 100644 --- a/public/js/icinga.js +++ b/public/js/icinga.js @@ -82,14 +82,16 @@ return false; } - this.utils = new Icinga.Utils(this); - this.logger = new Icinga.Logger(this); - this.timer = new Icinga.Timer(this); - this.ui = new Icinga.UI(this); - this.loader = new Icinga.Loader(this); - this.events = new Icinga.Events(this); - this.history = new Icinga.History(this); + this.timezone = new Icinga.Timezone(); + this.utils = new Icinga.Utils(this); + this.logger = new Icinga.Logger(this); + this.timer = new Icinga.Timer(this); + this.ui = new Icinga.UI(this); + this.loader = new Icinga.Loader(this); + this.events = new Icinga.Events(this); + this.history = new Icinga.History(this); + this.timezone.initialize(); this.timer.initialize(); this.events.initialize(); this.history.initialize(); @@ -147,6 +149,7 @@ module.destroy(); }); + this.timezone.destroy(); this.timer.destroy(); this.events.destroy(); this.loader.destroy(); diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js index dfeec81cf..685b50423 100644 --- a/public/js/icinga/events.js +++ b/public/js/icinga/events.js @@ -10,6 +10,8 @@ 'use strict'; + var mouseX, mouseY; + Icinga.Events = function (icinga) { this.icinga = icinga; @@ -114,57 +116,37 @@ $('[title]').each(function () { var $el = $(this); - $el.attr('title', $el.attr('title-rich') || $el.attr('title')); - // $el.attr('title', null); + $el.attr('title', $el.data('title-rich') || $el.attr('title')); }); - - $('svg rect.chart-data[title]', el).tipsy({ gravity: 'e', html: true }); + $('svg rect.chart-data[title]', 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 }); - // Rescue or remove all orphaned tooltips + // migrate or remove all orphaned tooltips $('.tipsy').each(function () { - function isElementInDOM(ele) { - while (ele = ele.parentNode) { - if (ele == document) return true; - } - return false; - }; - var arrow = $('.tipsy-arrow', this)[0]; if (!icinga.utils.elementsOverlap(arrow, $('#main')[0])) { $(this).remove(); return; } - // all tooltips are direct children of the body - // so we need find out whether the tooltip belongs applied area, - // by checking if both areas overlap if (!icinga.utils.elementsOverlap(arrow, el)) { - // tooltip does not belong to this area return; } - - var pointee = $.data(this, 'tipsy-pointee'); - if (!pointee || !isElementInDOM(pointee)) { - var orphan = this; - var oldTitle = $(this).find('.tipsy-inner').html(); - var migrated = false; - // try to find an element with the same title - $('[original-title="' + oldTitle + '"]').each(function() { - // get stored instance of Tipsy from newly created element - // point it to the orphaned tooltip - var tipsy = $.data(this, 'tipsy'); - tipsy.$tip = $(orphan); - $.data(this, 'tipsy', tipsy); - - // orphaned tooltip has the new element as pointee - $.data(orphan, 'tipsy-pointee', this); - migrated = true; - }); - if (!migrated) { - $(orphan).remove(); - } + + var title = $(this).find('.tipsy-inner').html(); + var atMouse = document.elementFromPoint(mouseX, mouseY); + var nearestTip = $(atMouse) + .closest('[original-title="' + title + '"]')[0]; + if (nearestTip) { + console.log ('migrating orphan...'); + var tipsy = $.data(nearestTip, 'tipsy'); + tipsy.$tip = $(this); + $.data(this, 'tipsy-pointee', nearestTip); + } else { + // doesn't match delete + console.log ('deleting orphan...'); + $(this).remove(); } }); }, @@ -221,11 +203,9 @@ // $(document).on('change', 'form.auto input', this.formChanged); // $(document).on('change', 'form.auto select', this.submitForm); - $(document).on('mouseenter', '[title-original]', { gravity: 'n' }, function(event) { - icinga.ui.hoverTooltip (this, event.data); - }); - $(document).on('mouseleave', '[title-original]', {}, function() { - icinga.ui.unhoverTooltip (this); + $(document).on('mousemove', function (event) { + mouseX = event.pageX; + mouseY = event.pageY; }); }, @@ -535,7 +515,7 @@ var isMenuLink = $a.closest('#menu').length > 0; var formerUrl; var remote = /^(?:[a-z]+:)\/\//; - if (href.match(/^javascript:/)) { + if (href.match(/^(mailto|javascript):/)) { return true; } diff --git a/public/js/icinga/timezone.js b/public/js/icinga/timezone.js new file mode 100644 index 000000000..15205e9cb --- /dev/null +++ b/public/js/icinga/timezone.js @@ -0,0 +1,117 @@ +(function(Icinga, $) { + + 'use strict'; + + /** + * Get the maximum timezone offset + * + * @returns {Number} + */ + Date.prototype.getStdTimezoneOffset = function() { + if (Date.maxTimezoneOffset !== undefined) { + return Date.maxTimezoneOffset; + } + + var year = new Date().getYear(); + var previousOffset; + + for (var i=0; i<12; i++) { + var d = new Date(year, i, 1); + if (previousOffset !== undefined) { + previousOffset = Math.max(previousOffset, d.getTimezoneOffset()); + } else { + previousOffset = d.getTimezoneOffset(); + } + } + + Date.maxTimezoneOffset = previousOffset; + + return Date.maxTimezoneOffset; + }; + + /** + * Test for daylight saving time zone + * + * @returns {boolean} + */ + Date.prototype.isDst = function() { + return (this.getStdTimezoneOffset() === this.getTimezoneOffset()) ? false : true; + }; + + /** + * Write timezone information into a cookie + * + * @constructor + */ + Icinga.Timezone = function() { + this.cookieName = 'icingaweb2-tzo'; + }; + + Icinga.Timezone.prototype = { + /** + * Initialize interface method + */ + initialize: function () { + this.writeTimezone(); + }, + + destroy: function() { + // PASS + }, + + /** + * Write timezone information into cookie + */ + writeTimezone: function() { + var date = new Date(); + var timezoneOffset = (date.getTimezoneOffset()*60) * -1; + var dst = date.isDst(); + + if (this.readCookie(this.cookieName)) { + return; + } + + this.writeCookie(this.cookieName, timezoneOffset + ',' + Number(dst), 1); + }, + + /** + * Write cookie data + * + * @param {String} name + * @param {String} value + * @param {Number} days + */ + writeCookie: function(name, value, days) { + var expires = ''; + + if (days) { + var date = new Date(); + date.setTime(date.getTime()+(days*24*60*60*1000)); + var expires = '; expires=' + date.toGMTString(); + } + document.cookie = name + '=' + value + expires + '; path=/'; + }, + + /** + * Read cookie data + * + * @param {String} name + * @returns {*} + */ + readCookie: function(name) { + var nameEq = name + '='; + var ca = document.cookie.split(';'); + for(var i=0;i < ca.length;i++) { + var c = ca[i]; + while (c.charAt(0)==' ') { + c = c.substring(1,c.length); + } + if (c.indexOf(nameEq) == 0) { + return c.substring(nameEq.length,c.length); + } + } + return null; + } + }; + +})(Icinga, jQuery); \ No newline at end of file diff --git a/public/js/vendor/SOURCE.jquery.tipsy b/public/js/vendor/SOURCE.jquery.tipsy index 5df83c9c9..665f25df5 100644 --- a/public/js/vendor/SOURCE.jquery.tipsy +++ b/public/js/vendor/SOURCE.jquery.tipsy @@ -25,9 +25,27 @@ This file contains information about how to acquire and install the source files https://github.com/jaz303/tipsy.git -# installation +# apply hotfix (firefox SVG bound calculation) +--- jquery.tipsy.js ++++ jquery.tipsy.js +@@ -34,8 +34,8 @@ + $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body); + + var pos = $.extend({}, this.$element.offset(), { +- width: this.$element[0].offsetWidth, +- height: this.$element[0].offsetHeight ++ width: this.$element[0].offsetWidth || this.$element[0].getBoundingClientRect().width, ++ height: this.$element[0].offsetHeight || this.$element[0].getBoundingClientRect().height + }); + + var actualWidth = $tip[0].offsetWidth, + + +# installation mv src/javascript/tipsy.css ICINGAWEB/public/css/vendor/tipsy.css mv src/javascript/jquery.tipsy.js ICINGAWEB/public/js/vendor/jquery.tipsy.js - uglifyjs src/javascript/jquery.tipsy.js ICINGAWEB/public/js/vendor/jquery.tipsy.min.js \ No newline at end of file + uglifyjs src/javascript/jquery.tipsy.js ICINGAWEB/public/js/vendor/jquery.tipsy.min.js + + diff --git a/public/js/vendor/jquery.tipsy.js b/public/js/vendor/jquery.tipsy.js index a17d7eeca..eeb5e67d8 100644 --- a/public/js/vendor/jquery.tipsy.js +++ b/public/js/vendor/jquery.tipsy.js @@ -34,8 +34,8 @@ $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body); var pos = $.extend({}, this.$element.offset(), { - width: this.$element[0].offsetWidth, - height: this.$element[0].offsetHeight + width: this.$element[0].offsetWidth || this.$element[0].getBoundingClientRect().width, + height: this.$element[0].offsetHeight || this.$element[0].getBoundingClientRect().height }); var actualWidth = $tip[0].offsetWidth, diff --git a/public/js/vendor/jquery.tipsy.min.js b/public/js/vendor/jquery.tipsy.min.js index 59f6c2375..66b085755 100644 --- a/public/js/vendor/jquery.tipsy.min.js +++ b/public/js/vendor/jquery.tipsy.min.js @@ -1,2 +1,6 @@ -/* tipsy, facebook style tooltips for jquery / version 1.0.0a / (c) 2008-2010 jason frame [jason@onehackoranother.com] / released under the MIT license / minified with uglifyjs */ -(function($){function maybeCall(thing,ctx){return typeof thing=="function"?thing.call(ctx):thing}function isElementInDOM(ele){while(ele=ele.parentNode){if(ele==document)return true}return false}function Tipsy(element,options){this.$element=$(element);this.options=options;this.enabled=true;this.fixTitle()}Tipsy.prototype={show:function(){var title=this.getTitle();if(title&&this.enabled){var $tip=this.tip();$tip.find(".tipsy-inner")[this.options.html?"html":"text"](title);$tip[0].className="tipsy";$tip.remove().css({top:0,left:0,visibility:"hidden",display:"block"}).prependTo(document.body);var pos=$.extend({},this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight});var actualWidth=$tip[0].offsetWidth,actualHeight=$tip[0].offsetHeight,gravity=maybeCall(this.options.gravity,this.$element[0]);var tp;switch(gravity.charAt(0)){case"n":tp={top:pos.top+pos.height+this.options.offset,left:pos.left+pos.width/2-actualWidth/2};break;case"s":tp={top:pos.top-actualHeight-this.options.offset,left:pos.left+pos.width/2-actualWidth/2};break;case"e":tp={top:pos.top+pos.height/2-actualHeight/2,left:pos.left-actualWidth-this.options.offset};break;case"w":tp={top:pos.top+pos.height/2-actualHeight/2,left:pos.left+pos.width+this.options.offset};break}if(gravity.length==2){if(gravity.charAt(1)=="w"){tp.left=pos.left+pos.width/2-15}else{tp.left=pos.left+pos.width/2-actualWidth+15}}$tip.css(tp).addClass("tipsy-"+gravity);$tip.find(".tipsy-arrow")[0].className="tipsy-arrow tipsy-arrow-"+gravity.charAt(0);if(this.options.className){$tip.addClass(maybeCall(this.options.className,this.$element[0]))}if(this.options.fade){$tip.stop().css({opacity:0,display:"block",visibility:"visible"}).animate({opacity:this.options.opacity})}else{$tip.css({visibility:"visible",opacity:this.options.opacity})}}},hide:function(){if(this.options.fade){this.tip().stop().fadeOut(function(){$(this).remove()})}else{this.tip().remove()}},fixTitle:function(){var $e=this.$element;if($e.attr("title")||typeof $e.attr("original-title")!="string"){$e.attr("original-title",$e.attr("title")||"").removeAttr("title")}},getTitle:function(){var title,$e=this.$element,o=this.options;this.fixTitle();var title,o=this.options;if(typeof o.title=="string"){title=$e.attr(o.title=="title"?"original-title":o.title)}else if(typeof o.title=="function"){title=o.title.call($e[0])}title=(""+title).replace(/(^\s*|\s*$)/,"");return title||o.fallback},tip:function(){if(!this.$tip){this.$tip=$('
').html('
');this.$tip.data("tipsy-pointee",this.$element[0])}return this.$tip},validate:function(){if(!this.$element[0].parentNode){this.hide();this.$element=null;this.options=null}},enable:function(){this.enabled=true},disable:function(){this.enabled=false},toggleEnabled:function(){this.enabled=!this.enabled}};$.fn.tipsy=function(options){if(options===true){return this.data("tipsy")}else if(typeof options=="string"){var tipsy=this.data("tipsy");if(tipsy)tipsy[options]();return this}options=$.extend({},$.fn.tipsy.defaults,options);function get(ele){var tipsy=$.data(ele,"tipsy");if(!tipsy){tipsy=new Tipsy(ele,$.fn.tipsy.elementOptions(ele,options));$.data(ele,"tipsy",tipsy)}return tipsy}function enter(){var tipsy=get(this);var ele=this;tipsy.hoverState="in";if(options.delayIn==0){tipsy.show()}else{tipsy.fixTitle();setTimeout(function(){if(tipsy.hoverState=="in"&&ele&&isElementInDOM(ele))tipsy.show()},options.delayIn)}}function leave(){var tipsy=get(this);tipsy.hoverState="out";if(options.delayOut==0){tipsy.hide()}else{setTimeout(function(){if(tipsy.hoverState=="out")tipsy.hide()},options.delayOut)}}if(!options.live)this.each(function(){get(this)});if(options.trigger!="manual"){var binder=options.live?"live":"bind",eventIn=options.trigger=="hover"?"mouseenter":"focus",eventOut=options.trigger=="hover"?"mouseleave":"blur";this[binder](eventIn,enter)[binder](eventOut,leave)}return this};$.fn.tipsy.defaults={className:null,delayIn:0,delayOut:0,fade:false,fallback:"",gravity:"n",html:false,live:false,offset:0,opacity:.8,title:"title",trigger:"hover"};$.fn.tipsy.revalidate=function(){$(".tipsy").each(function(){var pointee=$.data(this,"tipsy-pointee");if(!pointee||!isElementInDOM(pointee)){$(this).remove()}})};$.fn.tipsy.elementOptions=function(ele,options){return $.metadata?$.extend({},options,$(ele).metadata()):options};$.fn.tipsy.autoNS=function(){return $(this).offset().top>$(document).scrollTop()+$(window).height()/2?"s":"n"};$.fn.tipsy.autoWE=function(){return $(this).offset().left>$(document).scrollLeft()+$(window).width()/2?"e":"w"};$.fn.tipsy.autoBounds=function(margin,prefer){return function(){var dir={ns:prefer[0],ew:prefer.length>1?prefer[1]:false},boundTop=$(document).scrollTop()+margin,boundLeft=$(document).scrollLeft()+margin,$this=$(this);if($this.offset().top
').html('
');this.$tip.data("tipsy-pointee",this.$element[0])}return this.$tip},validate:function(){if(!this.$element[0].parentNode){this.hide();this.$element=null;this.options=null}},enable:function(){this.enabled=true},disable:function(){this.enabled=false},toggleEnabled:function(){this.enabled=!this.enabled}};$.fn.tipsy=function(options){if(options===true){return this.data("tipsy")}else if(typeof options=="string"){var tipsy=this.data("tipsy");if(tipsy)tipsy[options]();return this}options=$.extend({},$.fn.tipsy.defaults,options);function get(ele){var tipsy=$.data(ele,"tipsy");if(!tipsy){tipsy=new Tipsy(ele,$.fn.tipsy.elementOptions(ele,options));$.data(ele,"tipsy",tipsy)}return tipsy}function enter(){var tipsy=get(this);var ele=this;tipsy.hoverState="in";if(options.delayIn==0){tipsy.show()}else{tipsy.fixTitle();setTimeout(function(){if(tipsy.hoverState=="in"&&ele&&isElementInDOM(ele))tipsy.show()},options.delayIn)}}function leave(){var tipsy=get(this);tipsy.hoverState="out";if(options.delayOut==0){tipsy.hide()}else{setTimeout(function(){if(tipsy.hoverState=="out")tipsy.hide()},options.delayOut)}}if(!options.live)this.each(function(){get(this)});if(options.trigger!="manual"){var binder=options.live?"live":"bind",eventIn=options.trigger=="hover"?"mouseenter":"focus",eventOut=options.trigger=="hover"?"mouseleave":"blur";this[binder](eventIn,enter)[binder](eventOut,leave)}return this};$.fn.tipsy.defaults={className:null,delayIn:0,delayOut:0,fade:false,fallback:"",gravity:"n",html:false,live:false,offset:0,opacity:.8,title:"title",trigger:"hover"};$.fn.tipsy.revalidate=function(){$(".tipsy").each(function(){var pointee=$.data(this,"tipsy-pointee");if(!pointee||!isElementInDOM(pointee)){$(this).remove()}})};$.fn.tipsy.elementOptions=function(ele,options){return $.metadata?$.extend({},options,$(ele).metadata()):options};$.fn.tipsy.autoNS=function(){return $(this).offset().top>$(document).scrollTop()+$(window).height()/2?"s":"n"};$.fn.tipsy.autoWE=function(){return $(this).offset().left>$(document).scrollLeft()+$(window).width()/2?"e":"w"};$.fn.tipsy.autoBounds=function(margin,prefer){return function(){var dir={ns:prefer[0],ew:prefer.length>1?prefer[1]:false},boundTop=$(document).scrollTop()+margin,boundLeft=$(document).scrollLeft()+margin,$this=$(this);if($this.offset().topoldConfigDir = Config::$configDir; - Config::$configDir = dirname(__FILE__) . '/DateFormatTest'; - } - public function tearDown() { - parent::tearDown(); - Config::$configDir = $this->oldConfigDir; DateTimeFactory::setConfig(array('timezone' => date_default_timezone_get())); } @@ -145,21 +135,6 @@ class DateFormatTest extends BaseTestCase protected function getRequestMock($dateFormat = null, $timeFormat = null) { - $mock = Mockery::mock('\Zend_Controller_Request_Abstract'); - $mock->shouldReceive('getUser->getPreferences->get') - ->with(Mockery::type('string'), Mockery::type('string')) - ->andReturnUsing( - function ($ident, $default) use ($dateFormat, $timeFormat) { - if ($dateFormat !== null && $ident === 'app.dateFormat') { - return $dateFormat; - } elseif ($timeFormat !== null && $ident === 'app.timeFormat') { - return $timeFormat; - } else { - return $default; - } - } - ); - - return $mock; + return Mockery::mock('\Zend_Controller_Request_Abstract'); } } diff --git a/test/php/library/Icinga/Chart/GraphChartTest.php b/test/php/library/Icinga/Chart/GraphChartTest.php index 6a7f73864..9ffdcb66c 100644 --- a/test/php/library/Icinga/Chart/GraphChartTest.php +++ b/test/php/library/Icinga/Chart/GraphChartTest.php @@ -27,7 +27,7 @@ class GraphChartTest extends BaseTestCase $xpath = new DOMXPath($doc); $xpath->registerNamespace('x', 'http://www.w3.org/2000/svg'); $path = $xpath->query('//x:rect[@data-icinga-graph-type="bar"]'); - $this->assertEquals(3, $path->length, 'Assert the correct number of datapoints being drawn as SVG bars'); + $this->assertEquals(6, $path->length, 'Assert the correct number of datapoints being drawn as SVG bars'); } public function testLineChartCreation() @@ -48,4 +48,4 @@ class GraphChartTest extends BaseTestCase $path = $xpath->query('//x:path[@data-icinga-graph-type="line"]'); $this->assertEquals(1, $path->length, 'Assert the correct number of datapoints being drawn as SVG lines'); } -} \ No newline at end of file +} diff --git a/test/php/library/Icinga/Util/TranslatorTest.php b/test/php/library/Icinga/Util/TranslatorTest.php index 23bc154dd..9e341d545 100644 --- a/test/php/library/Icinga/Util/TranslatorTest.php +++ b/test/php/library/Icinga/Util/TranslatorTest.php @@ -26,9 +26,15 @@ class TranslatorTest extends BaseTestCase public function testWhetherGetAvailableLocaleCodesReturnsAllAvailableLocaleCodes() { + $expected = array(Translator::DEFAULT_LOCALE, 'de_DE', 'fr_FR'); + $result = Translator::getAvailableLocaleCodes(); + + sort($expected); + sort($result); + $this->assertEquals( - array(Translator::DEFAULT_LOCALE, 'de_DE', 'fr_FR'), - Translator::getAvailableLocaleCodes(), + $expected, + $result, 'Translator::getAvailableLocaleCodes does not return all available locale codes' ); } @@ -44,7 +50,7 @@ class TranslatorTest extends BaseTestCase } /** - * @expectedException \Exception + * @expectedException Icinga\Exception\IcingaException */ public function testWhetherSetupLocaleThrowsAnExceptionWhenGivenAnInvalidLocale() { diff --git a/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php b/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php index db7be8c64..11dcb23ec 100644 --- a/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php +++ b/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php @@ -4,7 +4,6 @@ namespace Tests\Icinga\Web\Session; -use Exception; use Mockery; use Icinga\Test\BaseTestCase; use Icinga\Web\Session\SessionNamespace; @@ -66,7 +65,7 @@ class SessionNamespaceTest extends BaseTestCase } /** - * @expectedException Exception + * @expectedException Icinga\Exception\IcingaException */ public function testFailingPropertyAccess() { @@ -88,7 +87,7 @@ class SessionNamespaceTest extends BaseTestCase } /** - * @expectedException Exception + * @expectedException Icinga\Exception\IcingaException * @expectedExceptionMessage Cannot save, session not set */ public function testInvalidParentWrite() diff --git a/test/php/library/Icinga/Web/UrlTest.php b/test/php/library/Icinga/Web/UrlTest.php index 183f33369..5da50c3f8 100644 --- a/test/php/library/Icinga/Web/UrlTest.php +++ b/test/php/library/Icinga/Web/UrlTest.php @@ -132,6 +132,8 @@ class UrlTest extends BaseTestCase $url->getParam('param2', 'wrongval'), 'Url::fromPath does not properly decode aliases characters in query parameter values' ); + /* + // Temporarily disabled, no [] support right now $this->assertEquals( array('1', '2', '3'), $url->getParam('param3'), @@ -142,6 +144,7 @@ class UrlTest extends BaseTestCase $url->getParam('param4'), 'Url::fromPath does not properly reassemble query parameters as associative arrays' ); + */ } /** @@ -152,7 +155,7 @@ class UrlTest extends BaseTestCase $url = Url::fromPath('/my/test/url.html?param=val¶m2=val2'); $this->assertEquals( - '/my/test/url.html?param=val&param2=val2', + '/my/test/url.html?param=val¶m2=val2', $url->getAbsoluteUrl(), 'Url::getAbsoluteUrl does not return the absolute url' ); @@ -166,7 +169,7 @@ class UrlTest extends BaseTestCase $url = Url::fromPath('/my/test/url.html?param=val¶m2=val2'); $this->assertEquals( - 'my/test/url.html?param=val&param2=val2', + 'my/test/url.html?param=val¶m2=val2', $url->getRelativeUrl(), 'Url::getRelativeUrl does not return the relative url' ); @@ -251,8 +254,8 @@ class UrlTest extends BaseTestCase $this->assertNotSame($url, $url2, 'Url::getUrlWithout does not return a new copy of the url'); $this->assertEquals( - array('param3' => 'val3'), - $url2->getParams(), + array(array('param3', 'val3')), + $url2->getParams()->toArray(), 'Url::getUrlWithout does not remove a given set of parameters from the url' ); } @@ -271,9 +274,14 @@ class UrlTest extends BaseTestCase 'Url::addParams does not add new parameters' ); $this->assertEquals( - 'val3', + 'newval3', $url->getParam('param3', 'wrongval'), - 'Url::addParams overwrites existing parameters' + 'Url::addParams does not overwrite existing existing parameters' + ); + $this->assertEquals( + array('val3', 'newval3'), + $url->getParams()->getValues('param3'), + 'Url::addParams does not overwrite existing existing parameters' ); } @@ -297,15 +305,35 @@ class UrlTest extends BaseTestCase ); } + public function testWhetherEqualUrlMaches() + { + $url1 = '/whatever/is/here?a=b&c=d'; + $url2 = Url::fromPath('whatever/is/here', array('a' => 'b', 'c' => 'd')); + $this->assertEquals( + true, + $url2->matches($url1) + ); + } + + public function testWhetherDifferentUrlDoesNotMatch() + { + $url1 = '/whatever/is/here?a=b&d=d'; + $url2 = Url::fromPath('whatever/is/here', array('a' => 'b', 'c' => 'd')); + $this->assertEquals( + false, + $url2->matches($url1) + ); + } + /** - * @depends testWhetherGetAbsoluteUrlReturnsTheAbsoluteUrl + * @depends testWhetherGetAbsoluteUrlReturnsTheAbsoluteUrlForHtmlAttributes */ - public function testWhetherToStringConversionReturnsTheAbsoluteUrl() + public function testWhetherToStringConversionReturnsTheAbsoluteUrlForHtmlAttribures() { $url = Url::fromPath('/my/test/url.html?param=val¶m2=val2¶m3=val3'); $this->assertEquals( - $url->getAbsoluteUrl(), + 'my/test/url.html?param=val&param2=val2&param3=val3', (string) $url, 'Converting a url to string does not return the absolute url' ); diff --git a/test/php/library/Icinga/Web/Widget/SearchDashboardTest.php b/test/php/library/Icinga/Web/Widget/SearchDashboardTest.php new file mode 100644 index 000000000..d42862ff6 --- /dev/null +++ b/test/php/library/Icinga/Web/Widget/SearchDashboardTest.php @@ -0,0 +1,73 @@ + 'Hosts', + 'url' => 'monitoring/list/hosts?sort=host_severity&limit=10' + ); + $moduleMock->shouldReceive('getSearchUrls')->andReturn(array( + $searchUrl + )); + + $moduleManagerMock = Mockery::mock('Icinga\Application\Modules\Manager'); + $moduleManagerMock->shouldReceive('getLoadedModules')->andReturn(array( + 'test-module' => $moduleMock + )); + + $bootstrapMock = Mockery::mock('Icinga\Application\ApplicationBootstrap')->shouldDeferMissing(); + $bootstrapMock->shouldReceive('getFrontController->getRequest')->andReturnUsing( + function () use ($request) { return $request; } + )->shouldReceive('getApplicationDir')->andReturn(self::$appDir); + + $bootstrapMock->shouldReceive('getModuleManager')->andReturn($moduleManagerMock); + + Icinga::setApp($bootstrapMock, true); + } + + /** + * @expectedException Zend_Controller_Action_Exception + */ + public function testWhetherRenderThrowsAnExceptionWhenHasNoComponents() + { + $dashboard = SearchDashboard::search('pending'); + $dashboard->getPane('search')->removeComponents(); + $dashboard->render(); + } + + public function testWhetherSearchLoadsSearchDashletsFromModules() + { + $dashboard = SearchDashboard::search('pending'); + + $result = $dashboard->getPane('search')->hasComponent('Hosts: pending'); + + $this->assertTrue($result, 'Dashboard::search() could not load search dashlets from modules'); + } + + public function testWhetherSearchProvidesHintWhenSearchStringIsEmpty() + { + $dashboard = SearchDashboard::search(); + + $result = $dashboard->getPane('search')->hasComponent('Ready to search'); + + $this->assertTrue($result, 'Dashboard::search() could not get hint for search'); + } +}