summaryrefslogtreecommitdiffstats
path: root/modules/monitoring/application
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:39:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:39:39 +0000
commit8ca6cc32b2c789a3149861159ad258f2cb9491e3 (patch)
tree2492de6f1528dd44eaa169a5c1555026d9cb75ec /modules/monitoring/application
parentInitial commit. (diff)
downloadicingaweb2-upstream.tar.xz
icingaweb2-upstream.zip
Adding upstream version 2.11.4.upstream/2.11.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--modules/monitoring/application/clicommands/ListCommand.php400
-rw-r--r--modules/monitoring/application/clicommands/NrpeCommand.php58
-rw-r--r--modules/monitoring/application/controllers/ActionsController.php135
-rw-r--r--modules/monitoring/application/controllers/CommentController.php91
-rw-r--r--modules/monitoring/application/controllers/CommentsController.php108
-rw-r--r--modules/monitoring/application/controllers/ConfigController.php298
-rw-r--r--modules/monitoring/application/controllers/DowntimeController.php108
-rw-r--r--modules/monitoring/application/controllers/DowntimesController.php108
-rw-r--r--modules/monitoring/application/controllers/EventController.php551
-rw-r--r--modules/monitoring/application/controllers/HealthController.php196
-rw-r--r--modules/monitoring/application/controllers/HostController.php185
-rw-r--r--modules/monitoring/application/controllers/HostsController.php260
-rw-r--r--modules/monitoring/application/controllers/ListController.php808
-rw-r--r--modules/monitoring/application/controllers/ServiceController.php147
-rw-r--r--modules/monitoring/application/controllers/ServicesController.php262
-rw-r--r--modules/monitoring/application/controllers/ShowController.php101
-rw-r--r--modules/monitoring/application/controllers/TacticalController.php128
-rw-r--r--modules/monitoring/application/controllers/TimelineController.php325
-rw-r--r--modules/monitoring/application/forms/Command/CommandForm.php92
-rw-r--r--modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php64
-rw-r--r--modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php279
-rw-r--r--modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php172
-rw-r--r--modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php148
-rw-r--r--modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php87
-rw-r--r--modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php109
-rw-r--r--modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php89
-rw-r--r--modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php129
-rw-r--r--modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php89
-rw-r--r--modules/monitoring/application/forms/Command/Object/ObjectsCommandForm.php47
-rw-r--r--modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php139
-rw-r--r--modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php122
-rw-r--r--modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php67
-rw-r--r--modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php178
-rw-r--r--modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php112
-rw-r--r--modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php263
-rw-r--r--modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php110
-rw-r--r--modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php187
-rw-r--r--modules/monitoring/application/forms/Config/BackendConfigForm.php367
-rw-r--r--modules/monitoring/application/forms/Config/SecurityConfigForm.php75
-rw-r--r--modules/monitoring/application/forms/Config/Transport/ApiTransportForm.php75
-rw-r--r--modules/monitoring/application/forms/Config/Transport/LocalTransportForm.php37
-rw-r--r--modules/monitoring/application/forms/Config/Transport/RemoteTransportForm.php185
-rw-r--r--modules/monitoring/application/forms/Config/TransportConfigForm.php392
-rw-r--r--modules/monitoring/application/forms/Config/TransportReorderForm.php87
-rw-r--r--modules/monitoring/application/forms/EventOverviewForm.php157
-rw-r--r--modules/monitoring/application/forms/Navigation/ActionForm.php79
-rw-r--r--modules/monitoring/application/forms/Navigation/HostActionForm.php8
-rw-r--r--modules/monitoring/application/forms/Navigation/ServiceActionForm.php8
-rw-r--r--modules/monitoring/application/forms/Setup/BackendPage.php51
-rw-r--r--modules/monitoring/application/forms/Setup/IdoResourcePage.php188
-rw-r--r--modules/monitoring/application/forms/Setup/SecurityPage.php27
-rw-r--r--modules/monitoring/application/forms/Setup/TransportPage.php55
-rw-r--r--modules/monitoring/application/forms/Setup/WelcomePage.php63
-rw-r--r--modules/monitoring/application/forms/StatehistoryForm.php141
-rw-r--r--modules/monitoring/application/views/helpers/CheckPerformance.php50
-rw-r--r--modules/monitoring/application/views/helpers/ContactFlags.php46
-rw-r--r--modules/monitoring/application/views/helpers/Customvar.php62
-rw-r--r--modules/monitoring/application/views/helpers/EscapeComment.php38
-rw-r--r--modules/monitoring/application/views/helpers/HostFlags.php33
-rw-r--r--modules/monitoring/application/views/helpers/IconImage.php64
-rw-r--r--modules/monitoring/application/views/helpers/Link.php72
-rw-r--r--modules/monitoring/application/views/helpers/MonitoringFlags.php40
-rw-r--r--modules/monitoring/application/views/helpers/Perfdata.php116
-rw-r--r--modules/monitoring/application/views/helpers/PluginOutput.php199
-rw-r--r--modules/monitoring/application/views/helpers/RuntimeVariables.php50
-rw-r--r--modules/monitoring/application/views/helpers/ServiceFlags.php33
-rw-r--r--modules/monitoring/application/views/scripts/comment/remove.phtml11
-rw-r--r--modules/monitoring/application/views/scripts/comment/show.phtml86
-rw-r--r--modules/monitoring/application/views/scripts/comments/delete-all.phtml12
-rw-r--r--modules/monitoring/application/views/scripts/comments/show.phtml19
-rw-r--r--modules/monitoring/application/views/scripts/config/form.phtml6
-rw-r--r--modules/monitoring/application/views/scripts/config/index.phtml78
-rw-r--r--modules/monitoring/application/views/scripts/config/security.phtml6
-rw-r--r--modules/monitoring/application/views/scripts/downtime/remove.phtml13
-rw-r--r--modules/monitoring/application/views/scripts/downtime/show.phtml173
-rw-r--r--modules/monitoring/application/views/scripts/downtimes/delete-all.phtml12
-rw-r--r--modules/monitoring/application/views/scripts/downtimes/show.phtml19
-rw-r--r--modules/monitoring/application/views/scripts/event/show.phtml34
-rw-r--r--modules/monitoring/application/views/scripts/form/reorder-command-transports.phtml93
-rw-r--r--modules/monitoring/application/views/scripts/health/disable-notifications.phtml20
-rw-r--r--modules/monitoring/application/views/scripts/health/info.phtml87
-rw-r--r--modules/monitoring/application/views/scripts/health/not-running.phtml8
-rw-r--r--modules/monitoring/application/views/scripts/health/stats.phtml150
-rw-r--r--modules/monitoring/application/views/scripts/host/services.phtml23
-rw-r--r--modules/monitoring/application/views/scripts/host/show.phtml14
-rw-r--r--modules/monitoring/application/views/scripts/hosts/show.phtml206
-rw-r--r--modules/monitoring/application/views/scripts/list/comments.phtml61
-rw-r--r--modules/monitoring/application/views/scripts/list/components/hostssummary.phtml92
-rw-r--r--modules/monitoring/application/views/scripts/list/components/selectioninfo.phtml15
-rw-r--r--modules/monitoring/application/views/scripts/list/components/servicesummary.phtml118
-rw-r--r--modules/monitoring/application/views/scripts/list/contactgroups.phtml53
-rw-r--r--modules/monitoring/application/views/scripts/list/contacts.phtml83
-rw-r--r--modules/monitoring/application/views/scripts/list/downtimes.phtml64
-rw-r--r--modules/monitoring/application/views/scripts/list/eventgrid.phtml123
-rw-r--r--modules/monitoring/application/views/scripts/list/eventhistory.phtml22
-rw-r--r--modules/monitoring/application/views/scripts/list/hostgroup-grid.phtml173
-rw-r--r--modules/monitoring/application/views/scripts/list/hostgroups.phtml296
-rw-r--r--modules/monitoring/application/views/scripts/list/hosts.phtml106
-rw-r--r--modules/monitoring/application/views/scripts/list/notifications.phtml124
-rw-r--r--modules/monitoring/application/views/scripts/list/servicegrid-flipped.phtml144
-rw-r--r--modules/monitoring/application/views/scripts/list/servicegrid.phtml144
-rw-r--r--modules/monitoring/application/views/scripts/list/servicegroup-grid.phtml217
-rw-r--r--modules/monitoring/application/views/scripts/list/servicegroups.phtml184
-rw-r--r--modules/monitoring/application/views/scripts/list/services.phtml161
-rw-r--r--modules/monitoring/application/views/scripts/object/detail-history.phtml13
-rw-r--r--modules/monitoring/application/views/scripts/object/detail-tabhook.phtml21
-rw-r--r--modules/monitoring/application/views/scripts/partials/command/object-command-form.phtml18
-rw-r--r--modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml15
-rw-r--r--modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml24
-rw-r--r--modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml82
-rw-r--r--modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml10
-rw-r--r--modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml32
-rw-r--r--modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml101
-rw-r--r--modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml40
-rw-r--r--modules/monitoring/application/views/scripts/partials/event-history.phtml267
-rw-r--r--modules/monitoring/application/views/scripts/partials/host/objects-header.phtml41
-rw-r--r--modules/monitoring/application/views/scripts/partials/object/detail-content.phtml53
-rw-r--r--modules/monitoring/application/views/scripts/partials/object/host-header.phtml51
-rw-r--r--modules/monitoring/application/views/scripts/partials/object/quick-actions.phtml144
-rw-r--r--modules/monitoring/application/views/scripts/partials/object/service-header.phtml72
-rw-r--r--modules/monitoring/application/views/scripts/partials/service/objects-header.phtml45
-rw-r--r--modules/monitoring/application/views/scripts/partials/show-more.phtml15
-rw-r--r--modules/monitoring/application/views/scripts/service/show.phtml8
-rw-r--r--modules/monitoring/application/views/scripts/services/show.phtml208
-rw-r--r--modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml94
-rw-r--r--modules/monitoring/application/views/scripts/show/components/actions.phtml43
-rw-r--r--modules/monitoring/application/views/scripts/show/components/checksource.phtml6
-rw-r--r--modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml85
-rw-r--r--modules/monitoring/application/views/scripts/show/components/checktimeperiod.phtml21
-rw-r--r--modules/monitoring/application/views/scripts/show/components/command.phtml52
-rw-r--r--modules/monitoring/application/views/scripts/show/components/comments.phtml86
-rw-r--r--modules/monitoring/application/views/scripts/show/components/contacts.phtml38
-rw-r--r--modules/monitoring/application/views/scripts/show/components/downtime.phtml109
-rw-r--r--modules/monitoring/application/views/scripts/show/components/extensions.phtml4
-rw-r--r--modules/monitoring/application/views/scripts/show/components/flags.phtml4
-rw-r--r--modules/monitoring/application/views/scripts/show/components/flapping.phtml14
-rw-r--r--modules/monitoring/application/views/scripts/show/components/grapher.phtml6
-rw-r--r--modules/monitoring/application/views/scripts/show/components/hostgroups.phtml19
-rw-r--r--modules/monitoring/application/views/scripts/show/components/notes.phtml48
-rw-r--r--modules/monitoring/application/views/scripts/show/components/notifications.phtml68
-rw-r--r--modules/monitoring/application/views/scripts/show/components/output.phtml5
-rw-r--r--modules/monitoring/application/views/scripts/show/components/perfdata.phtml4
-rw-r--r--modules/monitoring/application/views/scripts/show/components/reachable.phtml15
-rw-r--r--modules/monitoring/application/views/scripts/show/components/servicegroups.phtml20
-rw-r--r--modules/monitoring/application/views/scripts/show/components/status.phtml0
-rw-r--r--modules/monitoring/application/views/scripts/show/contact.phtml70
-rw-r--r--modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml131
-rw-r--r--modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml287
-rw-r--r--modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml81
-rw-r--r--modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml394
-rw-r--r--modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml74
-rw-r--r--modules/monitoring/application/views/scripts/tactical/index.phtml145
-rw-r--r--modules/monitoring/application/views/scripts/timeline/index.phtml145
153 files changed, 16128 insertions, 0 deletions
diff --git a/modules/monitoring/application/clicommands/ListCommand.php b/modules/monitoring/application/clicommands/ListCommand.php
new file mode 100644
index 0000000..6dc4193
--- /dev/null
+++ b/modules/monitoring/application/clicommands/ListCommand.php
@@ -0,0 +1,400 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Clicommands;
+
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Module\Monitoring\Cli\CliUtils;
+use Icinga\Date\DateFormatter;
+use Icinga\Cli\Command;
+use Icinga\File\Csv;
+use Icinga\Module\Monitoring\Plugin\PerfdataSet;
+use Exception;
+use Icinga\Util\Json;
+
+/**
+ * Icinga monitoring objects
+ *
+ * This module is your interface to the Icinga monitoring application.
+ */
+class ListCommand extends Command
+{
+ protected $backend;
+ protected $dumpSql;
+ protected $defaultActionName = 'status';
+
+ public function init()
+ {
+ $this->backend = MonitoringBackend::instance($this->params->shift('backend'));
+ $this->dumpSql = $this->params->shift('showsql');
+ }
+
+ protected function getQuery($table, $columns)
+ {
+ $limit = $this->params->shift('limit');
+ $format = $this->params->shift('format');
+ if ($format !== null) {
+ if ($this->params->has('columns')) {
+ $columnParams = preg_split(
+ '/,/',
+ $this->params->shift('columns')
+ );
+ $columns = array();
+ foreach ($columnParams as $col) {
+ if (false !== ($pos = strpos($col, '='))) {
+ $columns[substr($col, 0, $pos)] = substr($col, $pos + 1);
+ } else {
+ $columns[] = $col;
+ }
+ }
+ }
+ }
+
+ $query = $this->backend->select()->from($table, $columns);
+ if ($limit) {
+ $query->limit($limit, $this->params->shift('offset'));
+ }
+ foreach ($this->params->getParams() as $col => $filter) {
+ $query->where($col, $filter);
+ }
+ // $query->applyFilters($this->params->getParams());
+ if ($this->dumpSql) {
+ echo wordwrap($query->dump(), 72);
+ exit;
+ }
+
+ if ($format !== null) {
+ $this->showFormatted($query, $format, $columns);
+ }
+
+ return $query;
+ }
+
+ protected function showFormatted($query, $format, $columns)
+ {
+ $query = $query->getQuery();
+ switch ($format) {
+ case 'json':
+ echo Json::sanitize($query->fetchAll());
+ break;
+ case 'csv':
+ Csv::fromQuery($query)->dump();
+ break;
+ default:
+ preg_match_all('~\$([a-z0-9_-]+)\$~', $format, $m);
+ $words = array();
+ foreach ($columns as $key => $col) {
+ if (is_numeric($key)) {
+ if (in_array($col, $m[1])) {
+ $words[] = $col;
+ }
+ } else {
+ if (in_array($key, $m[1])) {
+ $words[] = $key;
+ }
+ }
+ }
+ foreach ($query->fetchAll() as $row) {
+ $output = $format;
+ foreach ($words as $word) {
+ $output = preg_replace(
+ '~\$' . $word . '\$~',
+ $row->{$word},
+ $output
+ );
+ }
+ echo $output . "\n";
+ }
+ }
+ exit;
+ }
+
+ /**
+ * List and filter hosts
+ *
+ * This command allows you to search and visualize your hosts in
+ * different ways.
+ *
+ * USAGE
+ *
+ * icingacli monitoring list hosts [options]
+ *
+ * OPTIONS
+ *
+ * --verbose Show detailled output
+ * --showsql Dump generated SQL query (DB backend only)
+ *
+ * --format=<csv|json|<custom>>
+ * Dump columns in the given format. <custom> format allows $column$
+ * placeholders, e.g. --format='$host$: $service$'. This requires
+ * that the columns are specified within the --columns parameter.
+ *
+ * --<column>[=filter]
+ * Filter given column by optional filter. Boolean (1/0) columns are
+ * true if no filter value is given.
+ *
+ * --problems
+ * Only show unhandled problems (HARD state and not acknowledged/in downtime).
+ *
+ * --columns='<comma separated list of host/service columns>'
+ * Add a limited set of columns to the output. The following host
+ * attributes can be fetched: state, handled, output, acknowledged, in_downtime, perfdata last_state_change
+ *
+ * EXAMPLES
+ *
+ * icingacli monitoring list hosts --problems
+ * icingacli monitoring list hosts --problems --host_state_type 0
+ * icingacli monitoring list hosts --host=local*
+ * icingacli monitoring list hosts --columns 'host,host_output' \
+ * --format='$host$ ($host_output$)'
+ */
+ public function hostsAction()
+ {
+ $columns = array(
+ 'host_name',
+ 'host_state',
+ 'host_output',
+ 'host_handled',
+ 'host_acknowledged',
+ 'host_in_downtime'
+ );
+ $query = $this->getQuery('hoststatus', $columns)
+ ->order('host_name');
+ echo $this->renderStatusQuery($query);
+ }
+
+ /**
+ * List and filter services
+ *
+ * This command allows you to search and visualize your services in
+ * different ways.
+ *
+ * USAGE
+ *
+ * icingacli monitoring list services [options]
+ *
+ * OPTIONS
+ *
+ * --verbose Show detailled output
+ * --showsql Dump generated SQL query (DB backend only)
+ *
+ * --format=<csv|json|<custom>>
+ * Dump columns in the given format. <custom> format allows $column$
+ * placeholders, e.g. --format='$host$: $service$'. This requires
+ * that the columns are specified within the --columns parameter.
+ *
+ * --<column>[=filter]
+ * Filter given column by optional filter. Boolean (1/0) columns are
+ * true if no filter value is given.
+ *
+ * --problems
+ * Only show unhandled problems (HARD state and not acknowledged/in downtime).
+ *
+ * --columns='<comma separated list of host/service columns>'
+ * Add a limited set of columns to the output. The following service
+ * attributes can be fetched: state, handled, output, acknowledged, in_downtime, perfdata last_state_change
+ *
+ * EXAMPLES
+ *
+ * icingacli monitoring list services --problems
+ * icingacli monitoring list services --problems --service_state_type 0
+ * icingacli monitoring list services --host=local* --service=*disk*
+ * icingacli monitoring list services --columns 'host,service,service_output' \
+ * --format='$host$: $service$ ($service_output$)'
+ */
+ public function servicesAction()
+ {
+ $columns = array(
+ 'host_name',
+ 'host_state',
+ 'host_output',
+ 'host_handled',
+ 'host_acknowledged',
+ 'host_in_downtime',
+ 'service_description',
+ 'service_state',
+ 'service_acknowledged',
+ 'service_in_downtime',
+ 'service_handled',
+ 'service_output',
+ 'service_perfdata',
+ 'service_last_state_change'
+ );
+ $query = $this->getQuery('servicestatus', $columns)
+ ->order('host_name');
+ echo $this->renderStatusQuery($query);
+ }
+
+ protected function renderStatusQuery($query)
+ {
+ $out = '';
+ $last_host = null;
+ $screen = $this->screen;
+ $utils = new CliUtils($screen);
+ $maxCols = $screen->getColumns();
+ $query = $query->getQuery();
+ $rows = $query->fetchAll();
+ $count = $query->count();
+ $count = count($rows);
+
+ for ($i = 0; $i < $count; $i++) {
+ $row = & $rows[$i];
+
+ $utils->setHostState($row->host_state);
+ if (! array_key_exists($i + 1, $rows)
+ || $row->host_name !== $rows[$i + 1]->host_name
+ ) {
+ $lastService = true;
+ } else {
+ $lastService = false;
+ }
+
+ $hostUnhandled = ! ($row->host_state == 0 || $row->host_handled);
+
+ if ($row->host_name !== $last_host) {
+ if (isset($row->service_description)) {
+ $out .= "\n";
+ }
+
+ $hostTxt = $utils->shortHostState();
+ if ($hostUnhandled) {
+ $out .= $utils->hostStateBackground(
+ sprintf(' %s ', $utils->shortHostState())
+ );
+ } else {
+ $out .= sprintf(
+ '%s %s ',
+ $utils->hostStateBackground(' '),
+ $utils->shortHostState()
+ );
+ }
+ $out .= sprintf(
+ " %s%s: %s\n",
+ $screen->underline($row->host_name),
+ $screen->colorize($utils->objectStateFlags('host', $row), 'lightblue'),
+ $row->host_output
+ );
+
+ if (isset($row->services_ok)) {
+ $out .= sprintf(
+ "%d services, %d problems (%d unhandled), %d OK\n",
+ $row->services_cnt,
+ $row->services_problem,
+ $row->services_problem_unhandled,
+ $row->services_ok
+ );
+ }
+ }
+
+ $last_host = $row->host_name;
+ if (! isset($row->service_description)) {
+ continue;
+ }
+
+ $utils->setServiceState($row->service_state);
+ $serviceUnhandled = ! (
+ $row->service_state == 0 || $row->service_handled
+ );
+
+ if ($lastService) {
+ $straight = ' ';
+ $leaf = '└';
+ } else {
+ $straight = '│';
+ $leaf = '├';
+ }
+ $out .= $utils->hostStateBackground(' ');
+
+ if ($serviceUnhandled) {
+ $out .= $utils->serviceStateBackground(
+ sprintf(' %s ', $utils->shortServiceState())
+ );
+ $emptyBg = ' ';
+ $emptySpace = '';
+ } else {
+ $out .= sprintf(
+ '%s %s ',
+ $utils->serviceStateBackground(' '),
+ $utils->shortServiceState()
+ );
+ $emptyBg = ' ';
+ $emptySpace = ' ';
+ }
+
+ $emptyLine = "\n"
+ . $utils->hostStateBackground(' ')
+ . $utils->serviceStateBackground($emptyBg)
+ . $emptySpace
+ . ' ' . $straight . ' ';
+
+ $perf = '';
+ try {
+ $pset = PerfdataSet::fromString($row->service_perfdata);
+ $perfs = array();
+ foreach ($pset as $p) {
+ if ($percent = $p->getPercentage()) {
+ if ($percent < 0 || $percent > 100) {
+ continue;
+ }
+ $perfs[] = ' '
+ . $p->getLabel()
+ . ': '
+ . $this->getPercentageSign($percent)
+ . ' '
+ . number_format($percent, 2, ',', '.')
+ . '%';
+ }
+ }
+ if (! empty($perfs)) {
+ $perf = ', ' . implode($perfs);
+ }
+ // TODO: fix wordwarp, then remove this line:
+ $perf = '';
+ } catch (Exception $e) {
+ // Ignoring perfdata errors right now, we could show some hint
+ }
+
+ $wrappedOutput = wordwrap(
+ preg_replace('~\@{3,}~', '@@@', $row->service_output),
+ $maxCols - 13
+ ) . "\n";
+ $out .= sprintf(
+ " %1s─ %s%s (%s)",
+ $leaf,
+ $screen->underline($row->service_description),
+ $screen->colorize($utils->objectStateFlags('service', $row) . $perf, 'lightblue'),
+ ucfirst(DateFormatter::timeSince($row->service_last_state_change))
+ );
+ if ($this->isVerbose) {
+ $out .= $emptyLine . preg_replace(
+ '/\n/',
+ $emptyLine,
+ $wrappedOutput
+ ) . "\n";
+ } else {
+ $out .= "\n";
+ }
+ }
+
+ $out .= "\n";
+ return $out;
+ }
+
+ protected function getPercentageSign($percent)
+ {
+ $circles = array(
+ 0 => '○',
+ 15 => '◔',
+ 40 => '◑',
+ 65 => '◕',
+ 90 => '●',
+ );
+ $last = $circles[0];
+ foreach ($circles as $cur => $circle) {
+ if ($percent < $cur) {
+ return $last;
+ }
+ $last = $circle;
+ }
+ }
+}
diff --git a/modules/monitoring/application/clicommands/NrpeCommand.php b/modules/monitoring/application/clicommands/NrpeCommand.php
new file mode 100644
index 0000000..fe82322
--- /dev/null
+++ b/modules/monitoring/application/clicommands/NrpeCommand.php
@@ -0,0 +1,58 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Clicommands;
+
+use Icinga\Protocol\Nrpe\Connection;
+use Icinga\Cli\Command;
+use Exception;
+
+/**
+ * NRPE
+ */
+class NrpeCommand extends Command
+{
+ protected $defaultActionName = 'check';
+
+ /**
+ * Execute an NRPE command
+ *
+ * This command will execute an NRPE check, fire it against the given host
+ * and also pass through all your parameters. Output will be shown, exit
+ * code respected.
+ *
+ * USAGE
+ *
+ * icingacli monitoring nrpe <host> <command> [--ssl] [nrpe options]
+ *
+ * EXAMPLE
+ *
+ * icingacli monitoring nrpe 127.0.0.1 CheckMEM --ssl --MaxWarn=80% \
+ * --MaxCrit=90% --type=physical
+ */
+ public function checkAction()
+ {
+ $host = $this->params->shift();
+ if (! $host) {
+ echo $this->showUsage();
+ exit(3);
+ }
+ $command = $this->params->shift(null, '_NRPE_CHECK');
+ $port = $this->params->shift('port', 5666);
+ try {
+ $nrpe = new Connection($host, $port);
+ if ($this->params->shift('ssl')) {
+ $nrpe->useSsl();
+ }
+ $args = array();
+ foreach ($this->params->getParams() as $k => $v) {
+ $args[] = $k . '=' . $v;
+ }
+ echo $nrpe->sendCommand($command, $args) . "\n";
+ exit($nrpe->getLastReturnCode());
+ } catch (Exception $e) {
+ echo $e->getMessage() . "\n";
+ exit(3);
+ }
+ }
+}
diff --git a/modules/monitoring/application/controllers/ActionsController.php b/modules/monitoring/application/controllers/ActionsController.php
new file mode 100644
index 0000000..bc13e21
--- /dev/null
+++ b/modules/monitoring/application/controllers/ActionsController.php
@@ -0,0 +1,135 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimesCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleHostDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleServiceDowntimeCommandForm;
+use Icinga\Module\Monitoring\Object\HostList;
+use Icinga\Module\Monitoring\Object\ServiceList;
+
+/**
+ * Monitoring API
+ */
+class ActionsController extends Controller
+{
+ /**
+ * Get the filter from URL parameters or exit immediately if the filter is empty
+ *
+ * @return Filter
+ */
+ protected function getFilterOrExitIfEmpty()
+ {
+ $filter = Filter::fromQueryString((string) $this->params);
+ if ($filter->isEmpty()) {
+ $this->getResponse()->json()
+ ->setFailData(array('filter' => 'Filter is required and must not be empty'))
+ ->sendResponse();
+ }
+ return $filter;
+ }
+
+ /**
+ * Schedule host downtimes
+ */
+ public function scheduleHostDowntimeAction()
+ {
+ $filter = $this->getFilterOrExitIfEmpty();
+ $hostList = new HostList($this->backend);
+ $hostList
+ ->applyFilter($this->getRestriction('monitoring/filter/objects'))
+ ->applyFilter($filter);
+ if (! $hostList->count()) {
+ $this->getResponse()->json()
+ ->setFailData(array('filter' => 'No hosts found matching the filter'))
+ ->sendResponse();
+ }
+ $form = new ScheduleHostDowntimeCommandForm();
+ $form
+ ->setIsApiTarget(true)
+ ->setBackend($this->backend)
+ ->setObjects($hostList->fetch())
+ ->handleRequest($this->getRequest());
+ }
+
+ /**
+ * Remove host downtimes
+ */
+ public function removeHostDowntimeAction()
+ {
+ $filter = $this->getFilterOrExitIfEmpty();
+ $downtimes = $this->backend
+ ->select()
+ ->from('downtime', array('host_name', 'id' => 'downtime_internal_id', 'name' => 'downtime_name'))
+ ->where('object_type', 'host')
+ ->applyFilter($this->getRestriction('monitoring/filter/objects'))
+ ->applyFilter($filter);
+ if (! $downtimes->count()) {
+ $this->getResponse()->json()
+ ->setFailData(array('filter' => 'No downtimes found matching the filter'))
+ ->sendResponse();
+ }
+ $form = new DeleteDowntimesCommandForm();
+ $form
+ ->setIsApiTarget(true)
+ ->setDowntimes($downtimes->fetchAll())
+ ->handleRequest($this->getRequest());
+ // @TODO(el): Respond w/ the downtimes deleted instead of the notifiaction added by
+ // DeleteDowntimesCommandForm::onSuccess().
+ }
+
+ /**
+ * Schedule service downtimes
+ */
+ public function scheduleServiceDowntimeAction()
+ {
+ $filter = $this->getFilterOrExitIfEmpty();
+ $serviceList = new ServiceList($this->backend);
+ $serviceList
+ ->applyFilter($this->getRestriction('monitoring/filter/objects'))
+ ->applyFilter($filter);
+ if (! $serviceList->count()) {
+ $this->getResponse()->json()
+ ->setFailData(array('filter' => 'No services found matching the filter'))
+ ->sendResponse();
+ }
+ $form = new ScheduleServiceDowntimeCommandForm();
+ $form
+ ->setIsApiTarget(true)
+ ->setBackend($this->backend)
+ ->setObjects($serviceList->fetch())
+ ->handleRequest($this->getRequest());
+ }
+
+ /**
+ * Remove service downtimes
+ */
+ public function removeServiceDowntimeAction()
+ {
+ $filter = $this->getFilterOrExitIfEmpty();
+ $downtimes = $this->backend
+ ->select()
+ ->from(
+ 'downtime',
+ array('host_name', 'service_description', 'id' => 'downtime_internal_id', 'name' => 'downtime_name')
+ )
+ ->where('object_type', 'service')
+ ->applyFilter($this->getRestriction('monitoring/filter/objects'))
+ ->applyFilter($filter);
+ if (! $downtimes->count()) {
+ $this->getResponse()->json()
+ ->setFailData(array('filter' => 'No downtimes found matching the filter'))
+ ->sendResponse();
+ }
+ $form = new DeleteDowntimesCommandForm();
+ $form
+ ->setIsApiTarget(true)
+ ->setDowntimes($downtimes->fetchAll())
+ ->handleRequest($this->getRequest());
+ // @TODO(el): Respond w/ the downtimes deleted instead of the notifiaction added by
+ // DeleteDowntimesCommandForm::onSuccess().
+ }
+}
diff --git a/modules/monitoring/application/controllers/CommentController.php b/modules/monitoring/application/controllers/CommentController.php
new file mode 100644
index 0000000..e50473f
--- /dev/null
+++ b/modules/monitoring/application/controllers/CommentController.php
@@ -0,0 +1,91 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Application\Hook;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+/**
+ * Display detailed information about a comment
+ */
+class CommentController extends Controller
+{
+ /**
+ * The fetched comment
+ *
+ * @var object
+ */
+ protected $comment;
+
+ /**
+ * Fetch the first comment with the given id and add tabs
+ */
+ public function init()
+ {
+ $commentId = $this->params->getRequired('comment_id');
+
+ $query = $this->backend->select()->from('comment', array(
+ 'id' => 'comment_internal_id',
+ 'objecttype' => 'object_type',
+ 'comment' => 'comment_data',
+ 'author' => 'comment_author_name',
+ 'timestamp' => 'comment_timestamp',
+ 'type' => 'comment_type',
+ 'persistent' => 'comment_is_persistent',
+ 'expiration' => 'comment_expiration',
+ 'name' => 'comment_name',
+ 'host_name',
+ 'service_description',
+ 'host_display_name',
+ 'service_display_name'
+ ))->where('comment_internal_id', $commentId);
+ $this->applyRestriction('monitoring/filter/objects', $query);
+
+ if (false === $this->comment = $query->fetchRow()) {
+ $this->httpNotFound($this->translate('Comment not found'));
+ }
+
+ $this->getTabs()->add(
+ 'comment',
+ array(
+ 'icon' => 'comment-empty',
+ 'label' => $this->translate('Comment'),
+ 'title' => $this->translate('Display detailed information about a comment.'),
+ 'url' =>'monitoring/comments/show'
+ )
+ )->activate('comment')->extend(new DashboardAction())->extend(new MenuAction());
+
+ if (Hook::has('ticket')) {
+ $this->view->tickets = Hook::first('ticket');
+ }
+ }
+
+ /**
+ * Display comment detail view
+ */
+ public function showAction()
+ {
+ $this->view->comment = $this->comment;
+ $this->view->title = $this->translate('Comments');
+
+ if ($this->hasPermission('monitoring/command/comment/delete')) {
+ $listUrl = Url::fromPath('monitoring/list/comments')
+ ->setQueryString('comment_type=comment|comment_type=ack');
+ $form = new DeleteCommentCommandForm();
+ $form
+ ->populate(array(
+ 'comment_id' => $this->comment->id,
+ 'comment_is_service' => isset($this->comment->service_description),
+ 'comment_name' => $this->comment->name,
+ 'redirect' => $listUrl
+ ))
+ ->handleRequest();
+ $this->view->delCommentForm = $form;
+ }
+ }
+}
diff --git a/modules/monitoring/application/controllers/CommentsController.php b/modules/monitoring/application/controllers/CommentsController.php
new file mode 100644
index 0000000..9de19a0
--- /dev/null
+++ b/modules/monitoring/application/controllers/CommentsController.php
@@ -0,0 +1,108 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentsCommandForm;
+use Icinga\Web\Url;
+
+/**
+ * Display detailed information about comments
+ */
+class CommentsController extends Controller
+{
+ /**
+ * The comments view
+ *
+ * @var \Icinga\Module\Monitoring\DataView\Comment
+ */
+ protected $comments;
+
+ /**
+ * Filter from request
+ *
+ * @var Filter
+ */
+ protected $filter;
+
+ /**
+ * Fetch all comments matching the current filter and add tabs
+ */
+ public function init()
+ {
+ $this->filter = Filter::fromQueryString(str_replace(
+ 'comment_id',
+ 'comment_internal_id',
+ (string) $this->params
+ ));
+ $query = $this->backend->select()->from('comment', array(
+ 'id' => 'comment_internal_id',
+ 'objecttype' => 'object_type',
+ 'comment' => 'comment_data',
+ 'author' => 'comment_author_name',
+ 'timestamp' => 'comment_timestamp',
+ 'type' => 'comment_type',
+ 'persistent' => 'comment_is_persistent',
+ 'expiration' => 'comment_expiration',
+ 'name' => 'comment_name',
+ 'host_name',
+ 'service_description',
+ 'host_display_name',
+ 'service_display_name'
+ ))->addFilter($this->filter);
+ $this->applyRestriction('monitoring/filter/objects', $query);
+
+ $this->comments = $query;
+
+ $this->view->title = $this->translate('Comments');
+ $this->getTabs()->add(
+ 'comments',
+ array(
+ 'icon' => 'comment-empty',
+ 'label' => $this->translate('Comments') . sprintf(' (%d)', $query->count()),
+ 'title' => $this->translate(
+ 'Display detailed information about multiple comments.'
+ ),
+ 'url' =>'monitoring/comments/show'
+ )
+ )->activate('comments');
+ }
+
+ /**
+ * Display the detail view for a comment list
+ */
+ public function showAction()
+ {
+ $this->view->comments = $this->comments;
+ $this->view->listAllLink = Url::fromPath('monitoring/list/comments')
+ ->setQueryString($this->filter->toQueryString());
+ $this->view->removeAllLink = Url::fromPath('monitoring/comments/delete-all')
+ ->setParams($this->params);
+ }
+
+ /**
+ * Display the form for removing a comment list
+ */
+ public function deleteAllAction()
+ {
+ $this->assertPermission('monitoring/command/comment/delete');
+
+ $listCommentsLink = Url::fromPath('monitoring/list/comments')
+ ->setQueryString('comment_type=(comment|ack)');
+ $delCommentForm = new DeleteCommentsCommandForm();
+ $delCommentForm->setTitle($this->view->translate('Remove all Comments'));
+ $delCommentForm->addDescription(sprintf(
+ $this->translate('Confirm removal of %d comments.'),
+ $this->comments->count()
+ ));
+ $delCommentForm->setComments($this->comments->fetchAll())
+ ->setRedirectUrl($listCommentsLink)
+ ->handleRequest();
+ $this->view->delCommentForm = $delCommentForm;
+ $this->view->comments = $this->comments;
+ $this->view->listAllLink = Url::fromPath('monitoring/list/comments')
+ ->setQueryString($this->filter->toQueryString());
+ }
+}
diff --git a/modules/monitoring/application/controllers/ConfigController.php b/modules/monitoring/application/controllers/ConfigController.php
new file mode 100644
index 0000000..b8ca0a1
--- /dev/null
+++ b/modules/monitoring/application/controllers/ConfigController.php
@@ -0,0 +1,298 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Exception;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\NotFoundError;
+use Icinga\Forms\ConfirmRemovalForm;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Module\Monitoring\Forms\Config\TransportReorderForm;
+use Icinga\Web\Controller;
+use Icinga\Web\Notification;
+use Icinga\Module\Monitoring\Forms\Config\BackendConfigForm;
+use Icinga\Module\Monitoring\Forms\Config\SecurityConfigForm;
+use Icinga\Module\Monitoring\Forms\Config\TransportConfigForm;
+
+/**
+ * Configuration controller for editing monitoring resources
+ */
+class ConfigController extends Controller
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->assertPermission('config/modules');
+ $this->view->title = $this->translate('Backends');
+ $this->view->defaultTitle = 'monitoring :: ' . $this->view->defaultTitle;
+ parent::init();
+ }
+
+ /**
+ * Display a list of available backends and command transports
+ */
+ public function indexAction()
+ {
+ $this->view->commandTransportReorderForm = $form = new TransportReorderForm();
+ $form->handleRequest();
+
+ $this->view->backendsConfig = $this->Config('backends');
+ $this->view->tabs = $this->Module()->getConfigTabs()->activate('backends');
+ }
+
+ /**
+ * Edit a monitoring backend
+ */
+ public function editbackendAction()
+ {
+ $backendName = $this->params->getRequired('backend-name');
+
+ $form = new BackendConfigForm();
+ $form->setRedirectUrl('monitoring/config');
+ $form->setTitle(sprintf($this->translate('Edit Monitoring Backend %s'), $backendName));
+ $form->setIniConfig($this->Config('backends'));
+ $form->setResourceConfig(ResourceFactory::getResourceConfigs());
+ $form->setOnSuccess(function (BackendConfigForm $form) use ($backendName) {
+ try {
+ $form->edit($backendName, array_map(
+ function ($v) {
+ return $v !== '' ? $v : null;
+ },
+ $form->getValues()
+ ));
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ Notification::success(sprintf(t('Monitoring backend "%s" successfully updated'), $backendName));
+ return true;
+ }
+
+ return false;
+ });
+
+ try {
+ $form->load($backendName);
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('Monitoring backend "%s" not found'), $backendName));
+ }
+
+ $this->view->form = $form;
+ $this->render('form');
+ }
+
+ /**
+ * Create a new monitoring backend
+ */
+ public function createbackendAction()
+ {
+ $form = new BackendConfigForm();
+ $form->setRedirectUrl('monitoring/config');
+ $form->setTitle($this->translate('Create New Monitoring Backend'));
+ $form->setIniConfig($this->Config('backends'));
+
+ try {
+ $form->setResourceConfig(ResourceFactory::getResourceConfigs());
+ } catch (ConfigurationError $e) {
+ if ($this->hasPermission('config/resources')) {
+ Notification::error($e->getMessage());
+ $this->redirectNow('config/createresource');
+ }
+
+ throw $e; // No permission for resource configuration, show the error
+ }
+
+ $form->setOnSuccess(function (BackendConfigForm $form) {
+ try {
+ $form->add($form::transformEmptyValuesToNull($form->getValues()));
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ Notification::success(t('Monitoring backend successfully created'));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->render('form');
+ }
+
+ /**
+ * Display a confirmation form to remove the backend identified by the 'backend' parameter
+ */
+ public function removebackendAction()
+ {
+ $backendName = $this->params->getRequired('backend-name');
+
+ $backendForm = new BackendConfigForm();
+ $backendForm->setIniConfig($this->Config('backends'));
+ $form = new ConfirmRemovalForm();
+ $form->setRedirectUrl('monitoring/config');
+ $form->setTitle(sprintf($this->translate('Remove Monitoring Backend %s'), $backendName));
+ $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($backendName, $backendForm) {
+ try {
+ $backendForm->delete($backendName);
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($backendForm->save()) {
+ Notification::success(sprintf(t('Monitoring backend "%s" successfully removed'), $backendName));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->render('form');
+ }
+
+ /**
+ * Remove a command transport
+ */
+ public function removetransportAction()
+ {
+ $transportName = $this->params->getRequired('transport');
+
+ $transportForm = new TransportConfigForm();
+ $transportForm->setIniConfig($this->Config('commandtransports'));
+ $form = new ConfirmRemovalForm();
+ $form->setRedirectUrl('monitoring/config');
+ $form->setTitle(sprintf($this->translate('Remove Command Transport %s'), $transportName));
+ $form->info(
+ $this->translate(
+ 'If you still have any environments or views referring to this transport, '
+ . 'you won\'t be able to send commands anymore after deletion.'
+ ),
+ false
+ );
+ $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($transportName, $transportForm) {
+ try {
+ $transportForm->delete($transportName);
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($transportForm->save()) {
+ Notification::success(sprintf(t('Command transport "%s" successfully removed'), $transportName));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->render('form');
+ }
+
+ /**
+ * Edit a command transport
+ */
+ public function edittransportAction()
+ {
+ $transportName = $this->params->getRequired('transport');
+
+ $form = new TransportConfigForm();
+ $form->setRedirectUrl('monitoring/config');
+ $form->setTitle(sprintf($this->translate('Edit Command Transport %s'), $transportName));
+ $form->setIniConfig($this->Config('commandtransports'));
+ $form->setInstanceNames(
+ MonitoringBackend::instance()->select()->from('instance', array('instance_name'))->fetchColumn()
+ );
+ $form->setOnSuccess(function (TransportConfigForm $form) use ($transportName) {
+ try {
+ $form->edit($transportName, array_map(
+ function ($v) {
+ return $v !== '' ? $v : null;
+ },
+ $form->getValues()
+ ));
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ Notification::success(sprintf(t('Command transport "%s" successfully updated'), $transportName));
+ return true;
+ }
+
+ return false;
+ });
+
+ try {
+ $form->load($transportName);
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('Command transport "%s" not found'), $transportName));
+ }
+
+ $this->view->form = $form;
+ $this->render('form');
+ }
+
+ /**
+ * Create a new command transport
+ */
+ public function createtransportAction()
+ {
+ $form = new TransportConfigForm();
+ $form->setRedirectUrl('monitoring/config');
+ $form->setTitle($this->translate('Create New Command Transport'));
+ $form->setIniConfig($this->Config('commandtransports'));
+ $form->setInstanceNames(
+ MonitoringBackend::instance()->select()->from('instance', array('instance_name'))->fetchColumn()
+ );
+ $form->setOnSuccess(function (TransportConfigForm $form) {
+ try {
+ $form->add($form::transformEmptyValuesToNull($form->getValues()));
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ Notification::success(t('Command transport successfully created'));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->render('form');
+ }
+
+ /**
+ * Display a form to adjust security relevant settings
+ */
+ public function securityAction()
+ {
+ $form = new SecurityConfigForm();
+ $form->setIniConfig($this->Config());
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->view->title = $this->translate('Security');
+ $this->view->tabs = $this->Module()->getConfigTabs()->activate('security');
+ }
+}
diff --git a/modules/monitoring/application/controllers/DowntimeController.php b/modules/monitoring/application/controllers/DowntimeController.php
new file mode 100644
index 0000000..83c03dd
--- /dev/null
+++ b/modules/monitoring/application/controllers/DowntimeController.php
@@ -0,0 +1,108 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Application\Hook;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm;
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+/**
+ * Display detailed information about a downtime
+ */
+class DowntimeController extends Controller
+{
+ /**
+ * The fetched downtime
+ *
+ * @var object
+ */
+ protected $downtime;
+
+ /**
+ * Fetch the downtime matching the given id and add tabs
+ */
+ public function init()
+ {
+ $downtimeId = $this->params->getRequired('downtime_id');
+
+ $query = $this->backend->select()->from('downtime', array(
+ 'id' => 'downtime_internal_id',
+ 'objecttype' => 'object_type',
+ 'comment' => 'downtime_comment',
+ 'author_name' => 'downtime_author_name',
+ 'start' => 'downtime_start',
+ 'scheduled_start' => 'downtime_scheduled_start',
+ 'scheduled_end' => 'downtime_scheduled_end',
+ 'end' => 'downtime_end',
+ 'duration' => 'downtime_duration',
+ 'is_flexible' => 'downtime_is_flexible',
+ 'is_fixed' => 'downtime_is_fixed',
+ 'is_in_effect' => 'downtime_is_in_effect',
+ 'entry_time' => 'downtime_entry_time',
+ 'name' => 'downtime_name',
+ 'host_state',
+ 'service_state',
+ 'host_name',
+ 'service_description',
+ 'host_display_name',
+ 'service_display_name'
+ ))->where('downtime_internal_id', $downtimeId);
+ $this->applyRestriction('monitoring/filter/objects', $query);
+
+ if (false === $this->downtime = $query->fetchRow()) {
+ $this->httpNotFound($this->translate('Downtime not found'));
+ }
+
+ $this->getTabs()->add(
+ 'downtime',
+ array(
+
+ 'icon' => 'plug',
+ 'label' => $this->translate('Downtime'),
+ 'title' => $this->translate('Display detailed information about a downtime.'),
+ 'url' =>'monitoring/downtimes/show'
+ )
+ )->activate('downtime')->extend(new DashboardAction())->extend(new MenuAction());
+
+ if (Hook::has('ticket')) {
+ $this->view->tickets = Hook::first('ticket');
+ }
+ }
+
+ /**
+ * Display the detail view for a downtime
+ */
+ public function showAction()
+ {
+ $isService = isset($this->downtime->service_description);
+ $this->view->downtime = $this->downtime;
+ $this->view->isService = $isService;
+ $this->view->listAllLink = Url::fromPath('monitoring/list/downtimes');
+ $this->view->showHostLink = Url::fromPath('monitoring/host/show')->setParam('host', $this->downtime->host_name);
+ $this->view->showServiceLink = Url::fromPath('monitoring/service/show')
+ ->setParam('host', $this->downtime->host_name)
+ ->setParam('service', $this->downtime->service_description);
+ $this->view->stateName = $isService ? Service::getStateText($this->downtime->service_state)
+ : Host::getStateText($this->downtime->host_state);
+
+ $this->view->title = $this->translate('Downtimes');
+ if ($this->hasPermission('monitoring/command/downtime/delete')) {
+ $form = new DeleteDowntimeCommandForm();
+ $form
+ ->populate(array(
+ 'downtime_id' => $this->downtime->id,
+ 'downtime_is_service' => $isService,
+ 'downtime_name' => $this->downtime->name,
+ 'redirect' => Url::fromPath('monitoring/list/downtimes'),
+ ))
+ ->handleRequest();
+ $this->view->delDowntimeForm = $form;
+ }
+ }
+}
diff --git a/modules/monitoring/application/controllers/DowntimesController.php b/modules/monitoring/application/controllers/DowntimesController.php
new file mode 100644
index 0000000..4891203
--- /dev/null
+++ b/modules/monitoring/application/controllers/DowntimesController.php
@@ -0,0 +1,108 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimesCommandForm;
+use Icinga\Web\Url;
+
+/**
+ * Display detailed information about downtimes
+ */
+class DowntimesController extends Controller
+{
+ /**
+ * The downtimes view
+ *
+ * @var \Icinga\Module\Monitoring\DataView\Downtime
+ */
+ protected $downtimes;
+
+ /**
+ * Filter from request
+ *
+ * @var Filter
+ */
+ protected $filter;
+
+ /**
+ * Fetch all downtimes matching the current filter and add tabs
+ */
+ public function init()
+ {
+ $this->filter = Filter::fromQueryString(str_replace(
+ 'downtime_id',
+ 'downtime_internal_id',
+ (string) $this->params
+ ));
+ $query = $this->backend->select()->from('downtime', array(
+ 'id' => 'downtime_internal_id',
+ 'objecttype' => 'object_type',
+ 'comment' => 'downtime_comment',
+ 'author_name' => 'downtime_author_name',
+ 'start' => 'downtime_start',
+ 'scheduled_start' => 'downtime_scheduled_start',
+ 'scheduled_end' => 'downtime_scheduled_end',
+ 'end' => 'downtime_end',
+ 'duration' => 'downtime_duration',
+ 'is_flexible' => 'downtime_is_flexible',
+ 'is_fixed' => 'downtime_is_fixed',
+ 'is_in_effect' => 'downtime_is_in_effect',
+ 'entry_time' => 'downtime_entry_time',
+ 'name' => 'downtime_name',
+ 'host_state',
+ 'service_state',
+ 'host_name',
+ 'service_description',
+ 'host_display_name',
+ 'service_display_name'
+ ))->addFilter($this->filter);
+ $this->applyRestriction('monitoring/filter/objects', $query);
+
+ $this->downtimes = $query;
+
+ $this->view->title = $this->translate('Downtimes');
+ $this->getTabs()->add(
+ 'downtimes',
+ array(
+ 'icon' => 'plug',
+ 'label' => $this->translate('Downtimes') . sprintf(' (%d)', $query->count()),
+ 'title' => $this->translate('Display detailed information about multiple downtimes.'),
+ 'url' =>'monitoring/downtimes/show'
+ )
+ )->activate('downtimes');
+ }
+
+ /**
+ * Display the detail view for a downtime list
+ */
+ public function showAction()
+ {
+ $this->view->downtimes = $this->downtimes;
+ $this->view->listAllLink = Url::fromPath('monitoring/list/downtimes')
+ ->setQueryString($this->filter->toQueryString());
+ $this->view->removeAllLink = Url::fromPath('monitoring/downtimes/delete-all')->setParams($this->params);
+ }
+
+ /**
+ * Display the form for removing a downtime list
+ */
+ public function deleteAllAction()
+ {
+ $this->assertPermission('monitoring/command/downtime/delete');
+ $this->view->downtimes = $this->downtimes;
+ $this->view->listAllLink = Url::fromPath('monitoring/list/downtimes')
+ ->setQueryString($this->filter->toQueryString());
+ $delDowntimeForm = new DeleteDowntimesCommandForm();
+ $delDowntimeForm->setTitle($this->view->translate('Remove all Downtimes'));
+ $delDowntimeForm->addDescription(sprintf(
+ $this->translate('Confirm removal of %d downtimes.'),
+ $this->downtimes->count()
+ ));
+ $delDowntimeForm->setRedirectUrl(Url::fromPath('monitoring/list/downtimes'));
+ $delDowntimeForm->setDowntimes($this->downtimes->fetchAll())->handleRequest();
+ $this->view->delAllDowntimeForm = $delDowntimeForm;
+ }
+}
diff --git a/modules/monitoring/application/controllers/EventController.php b/modules/monitoring/application/controllers/EventController.php
new file mode 100644
index 0000000..08ab1bc
--- /dev/null
+++ b/modules/monitoring/application/controllers/EventController.php
@@ -0,0 +1,551 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use DateTime;
+use DateTimeZone;
+use Icinga\Module\Monitoring\Hook\EventDetailsExtensionHook;
+use Icinga\Application\Hook;
+use InvalidArgumentException;
+use Icinga\Data\Queryable;
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Util\TimezoneDetect;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+use Icinga\Web\Widget\Tabextension\OutputFormat;
+
+class EventController extends Controller
+{
+ /**
+ * @var string[]
+ */
+ protected $dataViewsByType = array(
+ 'notify' => 'notificationevent',
+ 'comment' => 'commentevent',
+ 'comment_deleted' => 'commentevent',
+ 'ack' => 'commentevent',
+ 'ack_deleted' => 'commentevent',
+ 'dt_comment' => 'commentevent',
+ 'dt_comment_deleted' => 'commentevent',
+ 'flapping' => 'flappingevent',
+ 'flapping_deleted' => 'flappingevent',
+ 'hard_state' => 'statechangeevent',
+ 'soft_state' => 'statechangeevent',
+ 'dt_start' => 'downtimeevent',
+ 'dt_end' => 'downtimeevent'
+ );
+
+ public function init()
+ {
+ if (Hook::has('ticket')) {
+ $this->view->tickets = Hook::first('ticket');
+ }
+ }
+
+ public function showAction()
+ {
+ $type = $this->params->shiftRequired('type');
+ $id = $this->params->shiftRequired('id');
+
+ if (! isset($this->dataViewsByType[$type])
+ || $this->applyRestriction(
+ 'monitoring/filter/objects',
+ $this->backend->select()->from('eventhistory', array('id'))->where('id', $id)
+ )->fetchRow() === false
+ ) {
+ $this->httpNotFound($this->translate('Event not found'));
+ }
+
+ $event = $this->query($type, $id)->fetchRow();
+
+ if ($event === false) {
+ $this->httpNotFound($this->translate('Event not found'));
+ }
+
+ $this->view->object = $object = $event->service_description === null
+ ? new Host($this->backend, $event->host_name)
+ : new Service($this->backend, $event->host_name, $event->service_description);
+ $object->fetch();
+
+ list($icon, $label) = $this->getIconAndLabel($type);
+
+ $this->view->details = array_merge(
+ array(array($this->view->escape($this->translate('Type')), $label)),
+ $this->getDetails($type, $event)
+ );
+
+ $this->view->extensionsHtml = array();
+ /** @var EventDetailsExtensionHook $hook */
+ foreach (Hook::all('Monitoring\\EventDetailsExtension') as $hook) {
+ try {
+ $html = $hook->getHtmlForEvent($event);
+ } catch (\Exception $e) {
+ $html = $this->view->escape($e->getMessage());
+ }
+
+ if ($html) {
+ $module = $this->view->escape($hook->getModule()->getName());
+ $this->view->extensionsHtml[] =
+ '<div class="icinga-module module-' . $module . '" data-icinga-module="' . $module . '">'
+ . $html
+ . '</div>';
+ }
+ }
+
+ $this->view->title = $this->translate('Event Overview');
+ $this->getTabs()
+ ->add('event', array(
+ 'title' => $label,
+ 'label' => $label,
+ 'url' => Url::fromRequest(),
+ 'active' => true
+ ))
+ ->extend(new OutputFormat())
+ ->extend(new DashboardAction())
+ ->extend(new MenuAction());
+ }
+
+ /**
+ * Return translated and escaped 'Yes' if the given condition is true, 'No' otherwise, 'N/A' if NULL
+ *
+ * @param bool|null $condition
+ *
+ * @return string
+ */
+ protected function yesOrNo($condition)
+ {
+ if ($condition === null) {
+ return $this->view->escape($this->translate('N/A'));
+ }
+
+ return $this->view->escape($condition ? $this->translate('Yes') : $this->translate('No'));
+ }
+
+ /**
+ * Render the given duration in seconds as human readable HTML or 'N/A' if NULL
+ *
+ * @param int|null $seconds
+ *
+ * @return string
+ */
+ protected function duration($seconds)
+ {
+ return $this->view->escape(
+ $seconds === null ? $this->translate('N/A') : DateFormatter::formatDuration($seconds)
+ );
+ }
+
+ /**
+ * Render the given percent number as human readable HTML or 'N/A' if NULL
+ *
+ * @param float|null $percent
+ *
+ * @return string
+ */
+ protected function percent($percent)
+ {
+ return $this->view->escape(
+ $percent === null ? $this->translate('N/A') : sprintf($this->translate('%.2f%%'), $percent)
+ );
+ }
+
+ /**
+ * Render the given comment message as HTML or 'N/A' if NULL
+ *
+ * @param string|null $message
+ *
+ * @return string
+ */
+ protected function comment($message)
+ {
+ return $this->view->nl2br($this->view->createTicketLinks($this->view->markdown($message)));
+ }
+
+ /**
+ * Render a link to the given contact or 'N/A' if NULL
+ *
+ * @param string|null $name
+ *
+ * @return string
+ */
+ protected function contact($name)
+ {
+ return $name === null
+ ? $this->view->escape($this->translate('N/A'))
+ : $this->view->qlink($name, Url::fromPath('monitoring/show/contact', array('contact_name' => $name)));
+ }
+
+ /**
+ * Render the given monitored object state as human readable HTML or 'N/A' if NULL
+ *
+ * @param bool $isService
+ * @param int|null $state
+ *
+ * @return string
+ */
+ protected function state($isService, $state)
+ {
+ if ($state === null) {
+ return $this->view->escape($this->translate('N/A'));
+ }
+
+ try {
+ $stateText = $isService
+ ? Service::getStateText($state, true)
+ : Host::getStateText($state, true);
+ } catch (InvalidArgumentException $e) {
+ return $this->view->escape($this->translate('N/A'));
+ }
+
+ return '<span class="badge state-' . ($isService ? Service::getStateText($state) : Host::getStateText($state))
+ . '">&nbsp;</span><span class="state-label">' . $this->view->escape($stateText) . '</span>';
+ }
+
+ /**
+ * Render the given plugin output as human readable HTML
+ *
+ * @param string $output
+ *
+ * @return string
+ */
+ protected function pluginOutput($output)
+ {
+ return $this->view->getHelper('PluginOutput')->pluginOutput($output);
+ }
+
+ /**
+ * Return the icon and the label for the given event type
+ *
+ * @param string $eventType
+ *
+ * @return string[]
+ */
+ protected function getIconAndLabel($eventType)
+ {
+ switch ($eventType) {
+ case 'notify':
+ return array('bell', $this->translate('Notification', 'tooltip'));
+ case 'comment':
+ return array('comment-empty', $this->translate('Comment', 'tooltip'));
+ case 'comment_deleted':
+ return array('cancel', $this->translate('Comment removed', 'tooltip'));
+ case 'ack':
+ return array('ok', $this->translate('Acknowledged', 'tooltip'));
+ case 'ack_deleted':
+ return array('ok', $this->translate('Acknowledgement removed', 'tooltip'));
+ case 'dt_comment':
+ return array('plug', $this->translate('Downtime scheduled', 'tooltip'));
+ case 'dt_comment_deleted':
+ return array('plug', $this->translate('Downtime removed', 'tooltip'));
+ case 'flapping':
+ return array('flapping', $this->translate('Flapping started', 'tooltip'));
+ case 'flapping_deleted':
+ return array('flapping', $this->translate('Flapping stopped', 'tooltip'));
+ case 'hard_state':
+ return array('warning-empty', $this->translate('Hard state change'));
+ case 'soft_state':
+ return array('spinner', $this->translate('Soft state change'));
+ case 'dt_start':
+ return array('plug', $this->translate('Downtime started', 'tooltip'));
+ case 'dt_end':
+ return array('plug', $this->translate('Downtime ended', 'tooltip'));
+ }
+ }
+
+ /**
+ * Return a query for the given event ID of the given type
+ *
+ * @param string $type
+ * @param int $id
+ *
+ * @return Queryable
+ */
+ protected function query($type, $id)
+ {
+ switch ($this->dataViewsByType[$type]) {
+ case 'downtimeevent':
+ return $this->backend->select()
+ ->from('downtimeevent', array(
+ 'entry_time' => 'downtimeevent_entry_time',
+ 'author_name' => 'downtimeevent_author_name',
+ 'comment_data' => 'downtimeevent_comment_data',
+ 'is_fixed' => 'downtimeevent_is_fixed',
+ 'scheduled_start_time' => 'downtimeevent_scheduled_start_time',
+ 'scheduled_end_time' => 'downtimeevent_scheduled_end_time',
+ 'was_started' => 'downtimeevent_was_started',
+ 'actual_start_time' => 'downtimeevent_actual_start_time',
+ 'actual_end_time' => 'downtimeevent_actual_end_time',
+ 'was_cancelled' => 'downtimeevent_was_cancelled',
+ 'is_in_effect' => 'downtimeevent_is_in_effect',
+ 'trigger_time' => 'downtimeevent_trigger_time',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('downtimeevent_id', $id);
+ case 'commentevent':
+ return $this->backend->select()
+ ->from('commentevent', array(
+ 'entry_type' => 'commentevent_entry_type',
+ 'comment_time' => 'commentevent_comment_time',
+ 'author_name' => 'commentevent_author_name',
+ 'comment_data' => 'commentevent_comment_data',
+ 'is_persistent' => 'commentevent_is_persistent',
+ 'comment_source' => 'commentevent_comment_source',
+ 'expires' => 'commentevent_expires',
+ 'expiration_time' => 'commentevent_expiration_time',
+ 'deletion_time' => 'commentevent_deletion_time',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('commentevent_id', $id);
+ case 'flappingevent':
+ return $this->backend->select()
+ ->from('flappingevent', array(
+ 'event_time' => 'flappingevent_event_time',
+ 'reason_type' => 'flappingevent_reason_type',
+ 'percent_state_change' => 'flappingevent_percent_state_change',
+ 'low_threshold' => 'flappingevent_low_threshold',
+ 'high_threshold' => 'flappingevent_high_threshold',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('flappingevent_id', $id)
+ ->where('flappingevent_event_type', $type);
+ case 'notificationevent':
+ return $this->backend->select()
+ ->from('notificationevent', array(
+ 'notification_reason' => 'notificationevent_reason',
+ 'start_time' => 'notificationevent_start_time',
+ 'end_time' => 'notificationevent_end_time',
+ 'state' => 'notificationevent_state',
+ 'output' => 'notificationevent_output',
+ 'long_output' => 'notificationevent_long_output',
+ 'escalated' => 'notificationevent_escalated',
+ 'contacts_notified' => 'notificationevent_contacts_notified',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('notificationevent_id', $id);
+ case 'statechangeevent':
+ return $this->backend->select()
+ ->from('statechangeevent', array(
+ 'state_time' => 'statechangeevent_state_time',
+ 'state' => 'statechangeevent_state',
+ 'current_check_attempt' => 'statechangeevent_current_check_attempt',
+ 'max_check_attempts' => 'statechangeevent_max_check_attempts',
+ 'last_state' => 'statechangeevent_last_state',
+ 'last_hard_state' => 'statechangeevent_last_hard_state',
+ 'output' => 'statechangeevent_output',
+ 'long_output' => 'statechangeevent_long_output',
+ 'check_source' => 'statechangeevent_check_source',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('statechangeevent_id', $id)
+ ->where('statechangeevent_state_change', 1)
+ ->where('statechangeevent_state_type', $type);
+ }
+ }
+
+ /**
+ * Return the given event's data prepared for a name-value table
+ *
+ * @param string $type
+ * @param \stdClass $event
+ *
+ * @return string[][]
+ */
+ protected function getDetails($type, $event)
+ {
+ switch ($type) {
+ case 'dt_start':
+ case 'dt_end':
+ $details = array(array(
+ array($this->translate('Entry time'), DateFormatter::formatDateTime($event->entry_time)),
+ array($this->translate('Is fixed'), $this->yesOrNo($event->is_fixed)),
+ array($this->translate('Is in effect'), $this->yesOrNo($event->is_in_effect)),
+ array($this->translate('Was started'), $this->yesOrNo($event->was_started))
+ ));
+
+ if ($type === 'dt_end') {
+ $details[] = array(
+ array($this->translate('Was cancelled'), $this->yesOrNo($event->was_cancelled))
+ );
+ }
+
+ $details[] = array(
+ array($this->translate('Trigger time'), DateFormatter::formatDateTime($event->trigger_time)),
+ array(
+ $this->translate('Scheduled start time'),
+ DateFormatter::formatDateTime($event->scheduled_start_time)
+ ),
+ array(
+ $this->translate('Actual start time'),
+ DateFormatter::formatDateTime($event->actual_start_time)
+ ),
+ array(
+ $this->translate('Scheduled end time'),
+ DateFormatter::formatDateTime($event->scheduled_end_time)
+ )
+ );
+
+ if ($type === 'dt_end') {
+ $details[] = array(
+ array(
+ $this->translate('Actual end time'),
+ DateFormatter::formatDateTime($event->actual_end_time)
+ )
+ );
+ }
+
+ $details[] = array(
+ array($this->translate('Author'), $this->contact($event->author_name)),
+ array($this->translate('Comment'), $this->comment($event->comment_data))
+ );
+
+ return call_user_func_array('array_merge', $details);
+ case 'comment':
+ case 'comment_deleted':
+ case 'ack':
+ case 'ack_deleted':
+ case 'dt_comment':
+ case 'dt_comment_deleted':
+ switch ($event->entry_type) {
+ case 'comment':
+ $entryType = $this->translate('User comment');
+ break;
+ case 'downtime':
+ $entryType = $this->translate('Scheduled downtime');
+ break;
+ case 'flapping':
+ $entryType = $this->translate('Flapping');
+ break;
+ case 'ack':
+ $entryType = $this->translate('Acknowledgement');
+ break;
+ default:
+ $entryType = $this->translate('N/A');
+ }
+
+ switch ($event->comment_source) {
+ case 'icinga':
+ $commentSource = $this->translate('Icinga');
+ break;
+ case 'user':
+ $commentSource = $this->translate('User');
+ break;
+ default:
+ $commentSource = $this->translate('N/A');
+ }
+
+ return array(
+ array($this->translate('Time'), DateFormatter::formatDateTime($event->comment_time)),
+ array($this->translate('Source'), $this->view->escape($commentSource)),
+ array($this->translate('Entry type'), $this->view->escape($entryType)),
+ array($this->translate('Author'), $this->contact($event->author_name)),
+ array($this->translate('Is persistent'), $this->yesOrNo($event->is_persistent)),
+ array($this->translate('Expires'), $this->yesOrNo($event->expires)),
+ array($this->translate('Expiration time'), DateFormatter::formatDateTime($event->expiration_time)),
+ array($this->translate('Deletion time'), DateFormatter::formatDateTime($event->deletion_time)),
+ array($this->translate('Message'), $this->comment($event->comment_data))
+ );
+ case 'flapping':
+ case 'flapping_deleted':
+ switch ($event->reason_type) {
+ case 'stopped':
+ $reasonType = $this->translate('Flapping stopped normally');
+ break;
+ case 'disabled':
+ $reasonType = $this->translate('Flapping was disabled');
+ break;
+ default:
+ $reasonType = $this->translate('N/A');
+ }
+
+ return array(
+ array($this->translate('Event time'), DateFormatter::formatDateTime($event->event_time)),
+ array($this->translate('Reason'), $this->view->escape($reasonType)),
+ array($this->translate('State change'), $this->percent($event->percent_state_change)),
+ array($this->translate('Low threshold'), $this->percent($event->low_threshold)),
+ array($this->translate('High threshold'), $this->percent($event->high_threshold))
+ );
+ case 'notify':
+ switch ($event->notification_reason) {
+ case 'normal_notification':
+ $notificationReason = $this->translate('Normal notification');
+ break;
+ case 'ack':
+ $notificationReason = $this->translate('Problem acknowledgement');
+ break;
+ case 'flapping_started':
+ $notificationReason = $this->translate('Flapping started');
+ break;
+ case 'flapping_stopped':
+ $notificationReason = $this->translate('Flapping stopped');
+ break;
+ case 'flapping_disabled':
+ $notificationReason = $this->translate('Flapping was disabled');
+ break;
+ case 'dt_start':
+ $notificationReason = $this->translate('Downtime started');
+ break;
+ case 'dt_end':
+ $notificationReason = $this->translate('Downtime ended');
+ break;
+ case 'dt_cancel':
+ $notificationReason = $this->translate('Downtime was cancelled');
+ break;
+ case 'custom_notification':
+ $notificationReason = $this->translate('Custom notification');
+ break;
+ default:
+ $notificationReason = $this->translate('N/A');
+ }
+
+ $details = array(
+ array($this->translate('Start time'), DateFormatter::formatDateTime($event->start_time)),
+ array($this->translate('End time'), DateFormatter::formatDateTime($event->end_time)),
+ array($this->translate('Reason'), $this->view->escape($notificationReason)),
+ array(
+ $this->translate('State'),
+ $this->state($event->service_description !== null, $event->state)
+ ),
+ array($this->translate('Escalated'), $this->yesOrNo($event->escalated)),
+ array($this->translate('Contacts notified'), (int) $event->contacts_notified),
+ array(
+ $this->translate('Output'),
+ $this->pluginOutput($event->output) . $this->pluginOutput($event->long_output)
+ )
+ );
+
+ return $details;
+ case 'hard_state':
+ case 'soft_state':
+ $isService = $event->service_description !== null;
+
+ $details = array(
+ array($this->translate('State time'), DateFormatter::formatDateTime($event->state_time)),
+ array($this->translate('State'), $this->state($isService, $event->state)),
+ array($this->translate('Check source'), $event->check_source),
+ array($this->translate('Check attempt'), $this->view->escape(sprintf(
+ $this->translate('%d of %d'),
+ (int) $event->current_check_attempt,
+ (int) $event->max_check_attempts
+ ))),
+ array($this->translate('Last state'), $this->state($isService, $event->last_state)),
+ array($this->translate('Last hard state'), $this->state($isService, $event->last_hard_state)),
+ array(
+ $this->translate('Output'),
+ $this->pluginOutput($event->output) . $this->pluginOutput($event->long_output)
+ )
+ );
+
+ return $details;
+ }
+ }
+}
diff --git a/modules/monitoring/application/controllers/HealthController.php b/modules/monitoring/application/controllers/HealthController.php
new file mode 100644
index 0000000..48dd580
--- /dev/null
+++ b/modules/monitoring/application/controllers/HealthController.php
@@ -0,0 +1,196 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Instance\DisableNotificationsExpireCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Instance\ToggleInstanceFeaturesCommandForm;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+/**
+ * Display process and performance information of the monitoring host and program-wide commands
+ */
+class HealthController extends Controller
+{
+ /**
+ * Add tabs
+ *
+ * @see \Icinga\Web\Controller\ActionController::init()
+ */
+ public function init()
+ {
+ $this
+ ->getTabs()
+ ->add(
+ 'info',
+ array(
+ 'title' => $this->translate(
+ 'Show information about the current monitoring instance\'s process'
+ . ' and it\'s performance as well as available features'
+ ),
+ 'label' => $this->translate('Process Information'),
+ 'url' =>'monitoring/health/info'
+ )
+ )
+ ->add(
+ 'stats',
+ array(
+ 'title' => $this->translate(
+ 'Show statistics about the monitored objects'
+ ),
+ 'label' => $this->translate('Stats'),
+ 'url' =>'monitoring/health/stats'
+ )
+ )
+ ->extend(new DashboardAction())->extend(new MenuAction());
+ }
+
+ /**
+ * Display process information and program-wide commands
+ */
+ public function infoAction()
+ {
+ $this->view->title = $this->translate('Process Information');
+ $this->getTabs()->activate('info');
+ $this->setAutorefreshInterval(10);
+ $this->view->backendName = $this->backend->getName();
+ $programStatus = $this->backend
+ ->select()
+ ->from(
+ 'programstatus',
+ array(
+ 'is_currently_running',
+ 'process_id',
+ 'endpoint_name',
+ 'program_start_time',
+ 'status_update_time',
+ 'program_version',
+ 'last_command_check',
+ 'last_log_rotation',
+ 'global_service_event_handler',
+ 'global_host_event_handler',
+ 'notifications_enabled',
+ 'disable_notif_expire_time',
+ 'active_service_checks_enabled',
+ 'passive_service_checks_enabled',
+ 'active_host_checks_enabled',
+ 'passive_host_checks_enabled',
+ 'event_handlers_enabled',
+ 'obsess_over_services',
+ 'obsess_over_hosts',
+ 'flap_detection_enabled',
+ 'process_performance_data'
+ )
+ )
+ ->getQuery();
+ $this->handleFormatRequest($programStatus);
+ $programStatus = $programStatus->fetchRow();
+ if ($programStatus === false) {
+ return $this->render('not-running', true, null);
+ }
+ $this->view->programStatus = $programStatus;
+ $toggleFeaturesForm = new ToggleInstanceFeaturesCommandForm();
+ $toggleFeaturesForm
+ ->setBackend($this->backend)
+ ->setStatus($programStatus)
+ ->load($programStatus)
+ ->handleRequest();
+ $this->view->toggleFeaturesForm = $toggleFeaturesForm;
+
+ $this->view->runtimevariables = (object) $this->backend->select()
+ ->from('runtimevariables', array('varname', 'varvalue'))
+ ->getQuery()->fetchPairs();
+
+ $this->view->checkperformance = $this->backend->select()
+ ->from('runtimesummary')
+ ->getQuery()->fetchAll();
+ }
+
+ /**
+ * Display stats about current checks and monitored objects
+ */
+ public function statsAction()
+ {
+ $this->view->title = $this->translate('Stats');
+ $this->getTabs()->activate('stats');
+
+ $servicestats = $this->backend->select()->from('servicestatussummary', array(
+ 'services_critical',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_pending',
+ 'services_total',
+ 'services_unknown',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning',
+ 'services_warning_handled',
+ 'services_warning_unhandled'
+ ));
+ $this->applyRestriction('monitoring/filter/objects', $servicestats);
+ $this->view->servicestats = $servicestats->fetchRow();
+ $this->view->unhandledServiceProblems = $this->view->servicestats->services_critical_unhandled
+ + $this->view->servicestats->services_unknown_unhandled
+ + $this->view->servicestats->services_warning_unhandled;
+
+ $hoststats = $this->backend->select()->from('hoststatussummary', array(
+ 'hosts_total',
+ 'hosts_up',
+ 'hosts_down',
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_unreachable',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_pending',
+ ));
+ $this->applyRestriction('monitoring/filter/objects', $hoststats);
+ $this->view->hoststats = $hoststats->fetchRow();
+ $this->view->unhandledhostProblems = $this->view->hoststats->hosts_down_unhandled
+ + $this->view->hoststats->hosts_unreachable_unhandled;
+
+ $this->view->unhandledProblems = $this->view->unhandledhostProblems
+ + $this->view->unhandledServiceProblems;
+
+ $this->view->runtimevariables = (object) $this->backend->select()
+ ->from('runtimevariables', array('varname', 'varvalue'))
+ ->getQuery()->fetchPairs();
+
+ $this->view->checkperformance = $this->backend->select()
+ ->from('runtimesummary')
+ ->getQuery()->fetchAll();
+ }
+
+ /**
+ * Disable notifications w/ an optional expire time
+ */
+ public function disableNotificationsAction()
+ {
+ $this->assertPermission('monitoring/command/feature/instance');
+ $this->view->title = $this->translate('Disable Notifications');
+ $programStatus = $this->backend
+ ->select()
+ ->from(
+ 'programstatus',
+ array(
+ 'notifications_enabled',
+ 'disable_notif_expire_time'
+ )
+ )
+ ->getQuery()
+ ->fetchRow();
+ $this->view->programStatus = $programStatus;
+ if ((bool) $programStatus->notifications_enabled === false) {
+ return;
+ } else {
+ $form = new DisableNotificationsExpireCommandForm();
+ $form
+ ->setRedirectUrl('monitoring/health/info')
+ ->handleRequest();
+ $this->view->form = $form;
+ }
+ }
+}
diff --git a/modules/monitoring/application/controllers/HostController.php b/modules/monitoring/application/controllers/HostController.php
new file mode 100644
index 0000000..94f1a60
--- /dev/null
+++ b/modules/monitoring/application/controllers/HostController.php
@@ -0,0 +1,185 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Module\Monitoring\Forms\Command\Object\AcknowledgeProblemCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\AddCommentCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ProcessCheckResultCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleHostCheckCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleHostDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandForm;
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Web\Controller\MonitoredObjectController;
+use Icinga\Web\Hook;
+use Icinga\Web\Navigation\Navigation;
+
+class HostController extends MonitoredObjectController
+{
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $commandRedirectUrl = 'monitoring/host/show';
+
+ /**
+ * Fetch the requested host from the monitoring backend
+ */
+ public function init()
+ {
+ $host = new Host($this->backend, $this->params->getRequired('host'));
+ $this->applyRestriction('monitoring/filter/objects', $host);
+ if ($host->fetch() === false) {
+ $this->httpNotFound($this->translate('Host not found'));
+ }
+ $this->object = $host;
+ $this->createTabs();
+ $this->getTabs()->activate('host');
+ $this->view->title = $host->host_display_name;
+ $this->view->defaultTitle = $this->translate('Hosts') . ' :: ' . $this->view->defaultTitle;
+ }
+
+ /**
+ * Get host actions from hook
+ *
+ * @return Navigation
+ */
+ protected function getHostActions()
+ {
+ $navigation = new Navigation();
+ foreach (Hook::all('Monitoring\\HostActions') as $hook) {
+ $navigation->merge($hook->getNavigation($this->object));
+ }
+
+ return $navigation;
+ }
+
+ /**
+ * Show a host
+ */
+ public function showAction()
+ {
+ $this->view->actions = $this->getHostActions();
+ parent::showAction();
+ }
+
+ /**
+ * List a host's services
+ */
+ public function servicesAction()
+ {
+ $this->setAutorefreshInterval(10);
+ $this->getTabs()->activate('services');
+ $query = $this->backend->select()->from('servicestatus', array(
+ 'host_name',
+ 'host_display_name',
+ 'host_state',
+ 'host_state_type',
+ 'host_last_state_change',
+ 'host_address',
+ 'host_address6',
+ 'host_handled',
+ 'service_description',
+ 'service_display_name',
+ 'service_state',
+ 'service_in_downtime',
+ 'service_acknowledged',
+ 'service_handled',
+ 'service_output',
+ 'service_perfdata',
+ 'service_attempt',
+ 'service_last_state_change',
+ 'service_icon_image',
+ 'service_icon_image_alt',
+ 'service_is_flapping',
+ 'service_state_type',
+ 'service_handled',
+ 'service_severity',
+ 'service_last_check',
+ 'service_notifications_enabled',
+ 'service_action_url',
+ 'service_notes_url',
+ 'service_active_checks_enabled',
+ 'service_passive_checks_enabled',
+ 'current_check_attempt' => 'service_current_check_attempt',
+ 'max_check_attempts' => 'service_max_check_attempts',
+ 'service_check_command',
+ 'service_next_update'
+ ));
+ $this->applyRestriction('monitoring/filter/objects', $query);
+ $this->view->services = $query->where('host_name', $this->object->getName());
+ $this->view->object = $this->object;
+ }
+
+ /**
+ * Acknowledge a host problem
+ */
+ public function acknowledgeProblemAction()
+ {
+ $this->assertPermission('monitoring/command/acknowledge-problem');
+
+ $form = new AcknowledgeProblemCommandForm();
+ $form->setTitle($this->translate('Acknowledge Host Problem'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Add a host comment
+ */
+ public function addCommentAction()
+ {
+ $this->assertPermission('monitoring/command/comment/add');
+
+ $form = new AddCommentCommandForm();
+ $form->setTitle($this->translate('Add Host Comment'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Reschedule a host check
+ */
+ public function rescheduleCheckAction()
+ {
+ $this->assertPermission('monitoring/command/schedule-check');
+
+ $form = new ScheduleHostCheckCommandForm();
+ $form->setTitle($this->translate('Reschedule Host Check'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Schedule a host downtime
+ */
+ public function scheduleDowntimeAction()
+ {
+ $this->assertPermission('monitoring/command/downtime/schedule');
+
+ $form = new ScheduleHostDowntimeCommandForm();
+ $form->setTitle($this->translate('Schedule Host Downtime'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Submit a passive host check result
+ */
+ public function processCheckResultAction()
+ {
+ $this->assertPermission('monitoring/command/process-check-result');
+
+ $form = new ProcessCheckResultCommandForm();
+ $form->setTitle($this->translate('Submit Passive Host Check Result'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Send a custom notification for host
+ */
+ public function sendCustomNotificationAction()
+ {
+ $this->assertPermission('monitoring/command/send-custom-notification');
+
+ $form = new SendCustomNotificationCommandForm();
+ $form->setTitle($this->translate('Send Custom Host Notification'));
+ $this->handleCommandForm($form);
+ }
+}
diff --git a/modules/monitoring/application/controllers/HostsController.php b/modules/monitoring/application/controllers/HostsController.php
new file mode 100644
index 0000000..9219df8
--- /dev/null
+++ b/modules/monitoring/application/controllers/HostsController.php
@@ -0,0 +1,260 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Exception;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterEqual;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\AcknowledgeProblemCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\AddCommentCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\CheckNowCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ObjectsCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ProcessCheckResultCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\RemoveAcknowledgementCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleHostCheckCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleHostDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ToggleObjectFeaturesCommandForm;
+use Icinga\Module\Monitoring\Hook\DetailviewExtensionHook;
+use Icinga\Module\Monitoring\Object\HostList;
+use Icinga\Web\Hook;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+class HostsController extends Controller
+{
+ /**
+ * @var HostList
+ */
+ protected $hostList;
+
+ public function init()
+ {
+ $hostList = new HostList($this->backend);
+ $this->applyRestriction('monitoring/filter/objects', $hostList);
+ $hostList->addFilter(Filter::fromQueryString((string) $this->params));
+ $this->hostList = $hostList;
+ $this->hostList->setColumns(array(
+ 'host_acknowledged',
+ 'host_active_checks_enabled',
+ 'host_display_name',
+ 'host_event_handler_enabled',
+ 'host_flap_detection_enabled',
+ 'host_handled',
+ 'host_in_downtime',
+ 'host_is_flapping',
+ 'host_last_state_change',
+ 'host_name',
+ 'host_notifications_enabled',
+ 'host_obsessing',
+ 'host_passive_checks_enabled',
+ 'host_problem',
+ 'host_state',
+ 'instance_name'
+ ));
+ $this->view->baseFilter = $this->hostList->getFilter();
+ $this->getTabs()->add(
+ 'show',
+ array(
+ 'label' => $this->translate('Hosts') . sprintf(' (%d)', count($this->hostList)),
+ 'title' => sprintf(
+ $this->translate('Show summarized information for %u hosts'),
+ count($this->hostList)
+ ),
+ 'url' => Url::fromRequest()
+ )
+ )->extend(new DashboardAction())->extend(new MenuAction())->activate('show');
+ $this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/hosts');
+ $this->view->title = $this->translate('Hosts');
+ }
+
+ protected function handleCommandForm(ObjectsCommandForm $form)
+ {
+ $form
+ ->setBackend($this->backend)
+ ->setObjects($this->hostList)
+ ->setRedirectUrl(Url::fromPath('monitoring/hosts/show')->setParams(
+ $this->params->without('host_active_checks_enabled')
+ ))
+ ->handleRequest();
+
+ $this->view->form = $form;
+ $this->view->objects = $this->hostList;
+ $this->view->stats = $this->hostList->getStateSummary();
+ $this->_helper->viewRenderer('partials/command/objects-command-form', null, true);
+ return $form;
+ }
+
+ public function showAction()
+ {
+ $this->setAutorefreshInterval(15);
+ $activeChecksEnabled = $this->hostList->getFeatureStatus()['active_checks_enabled'] !== 0;
+ if ($this->Auth()->hasPermission('monitoring/command/schedule-check')
+ || ($this->Auth()->hasPermission('monitoring/command/schedule-check/active-only')
+ && $activeChecksEnabled
+ )
+ ) {
+ $checkNowForm = new CheckNowCommandForm();
+ $checkNowForm
+ ->setObjects($this->hostList)
+ ->handleRequest();
+ $this->view->checkNowForm = $checkNowForm;
+ }
+
+ $acknowledgedObjects = $this->hostList->getAcknowledgedObjects();
+ if (! empty($acknowledgedObjects)) {
+ $removeAckForm = new RemoveAcknowledgementCommandForm();
+ $removeAckForm
+ ->setObjects($acknowledgedObjects)
+ ->handleRequest();
+ $this->view->removeAckForm = $removeAckForm;
+ }
+
+ $featureStatus = $this->hostList->getFeatureStatus();
+ $toggleFeaturesForm = new ToggleObjectFeaturesCommandForm(array(
+ 'backend' => $this->backend,
+ 'objects' => $this->hostList
+ ));
+ $toggleFeaturesForm
+ ->load((object) $featureStatus)
+ ->handleRequest();
+ $this->view->toggleFeaturesForm = $toggleFeaturesForm;
+
+ $hostStates = $this->hostList->getStateSummary();
+
+ if ($activeChecksEnabled) {
+ $this->view->rescheduleAllLink = Url::fromRequest()
+ ->setPath('monitoring/hosts/reschedule-check')
+ ->addParams(['host_active_checks_enabled' => true]);
+ }
+
+ $this->view->downtimeAllLink = Url::fromRequest()->setPath('monitoring/hosts/schedule-downtime');
+ $this->view->processCheckResultAllLink = Url::fromRequest()->setPath('monitoring/hosts/process-check-result');
+ $this->view->addCommentLink = Url::fromRequest()->setPath('monitoring/hosts/add-comment');
+ $this->view->stats = $hostStates;
+ $this->view->objects = $this->hostList;
+ $this->view->unhandledObjects = $this->hostList->getUnhandledObjects();
+ $this->view->problemObjects = $this->hostList->getProblemObjects();
+ $this->view->acknowledgeUnhandledLink = Url::fromPath('monitoring/hosts/acknowledge-problem')
+ ->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()->toQueryString());
+ $this->view->downtimeUnhandledLink = Url::fromPath('monitoring/hosts/schedule-downtime')
+ ->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()->toQueryString());
+ $this->view->downtimeLink = Url::fromPath('monitoring/hosts/schedule-downtime')
+ ->setQueryString($this->hostList->getProblemObjects()->objectsFilter()->toQueryString());
+ $this->view->acknowledgedObjects = $this->hostList->getAcknowledgedObjects();
+ $this->view->acknowledgeLink = Url::fromPath('monitoring/hosts/acknowledge-problem')
+ ->setQueryString($this->hostList->getUnacknowledgedObjects()->objectsFilter()->toQueryString());
+ $this->view->unacknowledgedObjects = $this->hostList->getUnacknowledgedObjects();
+ $this->view->objectsInDowntime = $this->hostList->getObjectsInDowntime();
+ $this->view->inDowntimeLink = Url::fromPath('monitoring/list/hosts')
+ ->setQueryString(
+ $this->hostList
+ ->getObjectsInDowntime()
+ ->objectsFilter()
+ ->toQueryString()
+ );
+ $this->view->showDowntimesLink = Url::fromPath('monitoring/list/downtimes')
+ ->setQueryString(
+ $this->hostList
+ ->objectsFilter()
+ ->andFilter(FilterEqual::where('object_type', 'host'))
+ ->toQueryString()
+ );
+ $this->view->commentsLink = Url::fromRequest()->setPath('monitoring/list/comments');
+ $this->view->sendCustomNotificationLink = Url::fromRequest()
+ ->setPath('monitoring/hosts/send-custom-notification');
+
+ $this->view->extensionsHtml = array();
+ foreach (Hook::all('Monitoring\DetailviewExtension') as $hook) {
+ /** @var DetailviewExtensionHook $hook */
+ try {
+ $html = $hook->setView($this->view)->getHtmlForObjects($this->hostList);
+ } catch (Exception $e) {
+ $html = $this->view->escape($e->getMessage());
+ }
+
+ if ($html) {
+ $module = $this->view->escape($hook->getModule()->getName());
+ $this->view->extensionsHtml[] =
+ '<div class="icinga-module module-' . $module . '" data-icinga-module="' . $module . '">'
+ . $html
+ . '</div>';
+ }
+ }
+ }
+
+ /**
+ * Add a host comments
+ */
+ public function addCommentAction()
+ {
+ $this->assertPermission('monitoring/command/comment/add');
+
+ $form = new AddCommentCommandForm();
+ $form->setTitle($this->translate('Add Host Comments'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Acknowledge host problems
+ */
+ public function acknowledgeProblemAction()
+ {
+ $this->assertPermission('monitoring/command/acknowledge-problem');
+
+ $form = new AcknowledgeProblemCommandForm();
+ $form->setTitle($this->translate('Acknowledge Host Problems'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Reschedule host checks
+ */
+ public function rescheduleCheckAction()
+ {
+ $this->assertPermission('monitoring/command/schedule-check');
+
+ $form = new ScheduleHostCheckCommandForm();
+ $form->setTitle($this->translate('Reschedule Host Checks'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Schedule host downtimes
+ */
+ public function scheduleDowntimeAction()
+ {
+ $this->assertPermission('monitoring/command/downtime/schedule');
+
+ $form = new ScheduleHostDowntimeCommandForm();
+ $form->setTitle($this->translate('Schedule Host Downtimes'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Submit passive host check results
+ */
+ public function processCheckResultAction()
+ {
+ $this->assertPermission('monitoring/command/process-check-result');
+
+ $form = new ProcessCheckResultCommandForm();
+ $form->setTitle($this->translate('Submit Passive Host Check Results'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Send a custom notification for hosts
+ */
+ public function sendCustomNotificationAction()
+ {
+ $this->assertPermission('monitoring/command/send-custom-notification');
+
+ $form = new SendCustomNotificationCommandForm();
+ $form->setTitle($this->translate('Send Custom Host Notification'));
+ $this->handleCommandForm($form);
+ }
+}
diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php
new file mode 100644
index 0000000..0ccff99
--- /dev/null
+++ b/modules/monitoring/application/controllers/ListController.php
@@ -0,0 +1,808 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Security\SecurityException;
+use Icinga\Util\GlobFilter;
+use Icinga\Web\Form;
+use Zend_Form;
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\DataView\DataView;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\StatehistoryForm;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+use Icinga\Web\Widget\Tabextension\OutputFormat;
+use Icinga\Web\Widget\Tabs;
+
+class ListController extends Controller
+{
+ /**
+ * @see ActionController::init
+ */
+ public function init()
+ {
+ parent::init();
+ $this->createTabs();
+ }
+
+ /**
+ * Overwrite the backend to use (used for testing)
+ *
+ * @param MonitoringBackend $backend The Backend that should be used for querying
+ */
+ public function setBackend($backend)
+ {
+ $this->backend = $backend;
+ }
+
+ /**
+ * List hosts
+ */
+ public function hostsAction()
+ {
+ $this->addTitleTab(
+ 'hosts',
+ $this->translate('Hosts'),
+ $this->translate('List hosts')
+ );
+
+ $this->setAutorefreshInterval(10);
+
+ // Handle soft and hard states
+ if (strtolower($this->params->shift('stateType', 'soft')) === 'hard') {
+ $stateColumn = 'host_hard_state';
+ $stateChangeColumn = 'host_last_hard_state_change';
+ } else {
+ $stateColumn = 'host_state';
+ $stateChangeColumn = 'host_last_state_change';
+ }
+
+ $hosts = $this->backend->select()->from('hoststatus', array_merge(array(
+ 'host_icon_image',
+ 'host_icon_image_alt',
+ 'host_name',
+ 'host_display_name',
+ 'host_state' => $stateColumn,
+ 'host_acknowledged',
+ 'host_output',
+ 'host_attempt',
+ 'host_in_downtime',
+ 'host_is_flapping',
+ 'host_state_type',
+ 'host_handled',
+ 'host_last_state_change' => $stateChangeColumn,
+ 'host_notifications_enabled',
+ 'host_active_checks_enabled',
+ 'host_passive_checks_enabled',
+ 'host_check_command',
+ 'host_next_update'
+ ), $this->addColumns()));
+
+ $this->setupPaginationControl($hosts);
+ $this->setupSortControl(array(
+ 'host_severity' => $this->translate('Severity'),
+ 'host_state' => $this->translate('Current State'),
+ 'host_display_name' => $this->translate('Hostname'),
+ 'host_address' => $this->translate('Address'),
+ 'host_last_check' => $this->translate('Last Check'),
+ 'host_last_state_change' => $this->translate('Last State Change')
+ ), $hosts);
+ $this->filterQuery($hosts);
+ $this->setupLimitControl();
+
+ $stats = $this->backend->select()->from('hoststatussummary', array(
+ 'hosts_total',
+ 'hosts_up',
+ 'hosts_down',
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_unreachable',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_pending',
+ ));
+ $this->applyRestriction('monitoring/filter/objects', $stats);
+
+ $this->view->hosts = $hosts;
+ $this->view->stats = $stats;
+ }
+
+ /**
+ * List services
+ */
+ public function servicesAction()
+ {
+ $this->addTitleTab(
+ 'services',
+ $this->translate('Services'),
+ $this->translate('List services')
+ );
+
+ // Handle soft and hard states
+ if (strtolower($this->params->shift('stateType', 'soft')) === 'hard') {
+ $stateColumn = 'service_hard_state';
+ $stateChangeColumn = 'service_last_hard_state_change';
+ } else {
+ $stateColumn = 'service_state';
+ $stateChangeColumn = 'service_last_state_change';
+ }
+
+ $this->setAutorefreshInterval(10);
+
+ $services = $this->backend->select()->from('servicestatus', array_merge(array(
+ 'host_name',
+ 'host_display_name',
+ 'host_state',
+ 'service_description',
+ 'service_display_name',
+ 'service_state' => $stateColumn,
+ 'service_in_downtime',
+ 'service_acknowledged',
+ 'service_handled',
+ 'service_output',
+ 'service_perfdata',
+ 'service_attempt',
+ 'service_last_state_change' => $stateChangeColumn,
+ 'service_icon_image',
+ 'service_icon_image_alt',
+ 'service_is_flapping',
+ 'service_state_type',
+ 'service_handled',
+ 'service_severity',
+ 'service_notifications_enabled',
+ 'service_active_checks_enabled',
+ 'service_passive_checks_enabled',
+ 'service_check_command',
+ 'service_next_update'
+ ), $this->addColumns()));
+
+ $this->setupPaginationControl($services);
+ $this->setupSortControl(array(
+ 'service_severity' => $this->translate('Service Severity'),
+ 'service_state' => $this->translate('Current Service State'),
+ 'service_display_name' => $this->translate('Service Name'),
+ 'service_last_check' => $this->translate('Last Service Check'),
+ 'service_last_state_change' => $this->translate('Last State Change'),
+ 'host_severity' => $this->translate('Host Severity'),
+ 'host_state' => $this->translate('Current Host State'),
+ 'host_display_name' => $this->translate('Hostname'),
+ 'host_address' => $this->translate('Host Address'),
+ 'host_last_check' => $this->translate('Last Host Check')
+ ), $services);
+ $this->filterQuery($services);
+ $this->setupLimitControl();
+
+ $stats = $this->backend->select()->from('servicestatussummary', array(
+ 'services_critical',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_pending',
+ 'services_total',
+ 'services_unknown',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning',
+ 'services_warning_handled',
+ 'services_warning_unhandled'
+ ));
+ $this->applyRestriction('monitoring/filter/objects', $stats);
+
+ $this->view->services = $services;
+ $this->view->stats = $stats;
+ if (strpos($this->params->get('host_name', '*'), '*') === false) {
+ $this->view->showHost = false;
+ } else {
+ $this->view->showHost = true;
+ }
+ }
+
+ /**
+ * List downtimes
+ */
+ public function downtimesAction()
+ {
+ $this->addTitleTab(
+ 'downtimes',
+ $this->translate('Downtimes'),
+ $this->translate('List downtimes')
+ );
+
+ $this->setAutorefreshInterval(12);
+
+ $downtimes = $this->backend->select()->from('downtime', array(
+ 'id' => 'downtime_internal_id',
+ 'objecttype' => 'object_type',
+ 'comment' => 'downtime_comment',
+ 'author_name' => 'downtime_author_name',
+ 'start' => 'downtime_start',
+ 'scheduled_start' => 'downtime_scheduled_start',
+ 'scheduled_end' => 'downtime_scheduled_end',
+ 'end' => 'downtime_end',
+ 'duration' => 'downtime_duration',
+ 'is_flexible' => 'downtime_is_flexible',
+ 'is_fixed' => 'downtime_is_fixed',
+ 'is_in_effect' => 'downtime_is_in_effect',
+ 'entry_time' => 'downtime_entry_time',
+ 'name' => 'downtime_name',
+ 'host_state',
+ 'service_state',
+ 'host_name',
+ 'service_description',
+ 'host_display_name',
+ 'service_display_name'
+ ));
+
+ $this->setupPaginationControl($downtimes);
+ $this->setupSortControl(array(
+ 'downtime_is_in_effect' => $this->translate('Is In Effect'),
+ 'host_display_name' => $this->translate('Host'),
+ 'service_display_name' => $this->translate('Service'),
+ 'downtime_entry_time' => $this->translate('Entry Time'),
+ 'downtime_author' => $this->translate('Author'),
+ 'downtime_start' => $this->translate('Start Time'),
+ 'downtime_end' => $this->translate('End Time'),
+ 'downtime_scheduled_start' => $this->translate('Scheduled Start'),
+ 'downtime_scheduled_end' => $this->translate('Scheduled End'),
+ 'downtime_duration' => $this->translate('Duration')
+ ), $downtimes);
+ $this->filterQuery($downtimes);
+ $this->setupLimitControl();
+
+ $this->view->downtimes = $downtimes;
+
+ if ($this->Auth()->hasPermission('monitoring/command/downtime/delete')) {
+ $this->view->delDowntimeForm = new DeleteDowntimeCommandForm();
+ $this->view->delDowntimeForm->handleRequest();
+ }
+ }
+
+ /**
+ * List notifications
+ */
+ public function notificationsAction()
+ {
+ $this->addTitleTab(
+ 'notifications',
+ $this->translate('Notifications'),
+ $this->translate('List notifications')
+ );
+
+ $this->setAutorefreshInterval(15);
+
+ $notifications = $this->backend->select()->from('notification', array(
+ 'id',
+ 'host_display_name',
+ 'host_name',
+ 'notification_contact_name',
+ 'notification_output',
+ 'notification_state',
+ 'notification_timestamp',
+ 'service_description',
+ 'service_display_name'
+ ));
+
+ $this->setupPaginationControl($notifications);
+ $this->setupSortControl(array(
+ 'notification_timestamp' => $this->translate('Notification Start')
+ ), $notifications);
+ $this->filterQuery($notifications);
+ $this->setupLimitControl();
+
+ $this->view->notifications = $notifications;
+ }
+
+ /**
+ * List contacts
+ */
+ public function contactsAction()
+ {
+ if (! $this->hasPermission('*') && $this->hasPermission('no-monitoring/contacts')) {
+ throw new SecurityException('No permission for %s', 'monitoring/contacts');
+ }
+
+ $this->addTitleTab(
+ 'contacts',
+ $this->translate('Contacts'),
+ $this->translate('List contacts')
+ );
+
+ $contacts = $this->backend->select()->from('contact', array(
+ 'contact_name',
+ 'contact_alias',
+ 'contact_email',
+ 'contact_pager',
+ 'contact_notify_service_timeperiod',
+ 'contact_notify_host_timeperiod'
+ ));
+
+ $this->setupPaginationControl($contacts);
+ $this->setupSortControl(array(
+ 'contact_name' => $this->translate('Name'),
+ 'contact_alias' => $this->translate('Alias'),
+ 'contact_email' => $this->translate('Email'),
+ 'contact_pager' => $this->translate('Pager Address / Number')
+ ), $contacts);
+ $this->filterQuery($contacts);
+ $this->setupLimitControl();
+
+ $this->view->contacts = $contacts;
+ }
+
+ public function eventgridAction()
+ {
+ $this->addTitleTab('eventgrid', $this->translate('Event Grid'), $this->translate('Show the Event Grid'));
+
+ $form = new StatehistoryForm();
+ $form->setEnctype(Zend_Form::ENCTYPE_URLENCODED);
+ $form->setMethod('get');
+ $form->setTokenDisabled();
+ $form->setUidDisabled();
+ $form->render();
+ $this->view->form = $form;
+
+ $this->params
+ ->remove('showCompact')
+ ->remove('format');
+ $orientation = $this->params->shift('vertical', 0) ? 'vertical' : 'horizontal';
+/*
+ $orientationBox = new SelectBox(
+ 'orientation',
+ array(
+ '0' => mt('monitoring', 'Vertical'),
+ '1' => mt('monitoring', 'Horizontal')
+ ),
+ mt('monitoring', 'Orientation'),
+ 'horizontal'
+ );
+ $orientationBox->applyRequest($this->getRequest());
+*/
+ $objectType = $form->getValue('objecttype');
+ $from = $form->getValue('from');
+ $query = $this->backend->select()->from(
+ 'eventgrid' . $objectType,
+ array('day', $form->getValue('state'))
+ );
+ $this->params->remove(array('objecttype', 'from', 'to', 'state', 'btn_submit'));
+ $this->view->filter = Filter::fromQuerystring((string) $this->params);
+ $query->applyFilter($this->view->filter);
+ $query->applyFilter(Filter::fromQuerystring('timestamp>=' . $from));
+ $this->applyRestriction('monitoring/filter/objects', $query);
+ $this->view->summary = $query;
+ $this->view->column = $form->getValue('state');
+// $this->view->orientationBox = $orientationBox;
+ $this->view->orientation = $orientation;
+ }
+
+ /**
+ * List contact groups
+ */
+ public function contactgroupsAction()
+ {
+ if (! $this->hasPermission('*') && $this->hasPermission('no-monitoring/contacts')) {
+ throw new SecurityException('No permission for %s', 'monitoring/contacts');
+ }
+
+ $this->addTitleTab(
+ 'contactgroups',
+ $this->translate('Contact Groups'),
+ $this->translate('List contact groups')
+ );
+
+ $contactGroups = $this->backend->select()->from('contactgroup', array(
+ 'contactgroup_name',
+ 'contactgroup_alias',
+ 'contact_count'
+ ));
+
+ $this->setupPaginationControl($contactGroups);
+ $this->setupSortControl(array(
+ 'contactgroup_name' => $this->translate('Contactgroup Name'),
+ 'contactgroup_alias' => $this->translate('Contactgroup Alias')
+ ), $contactGroups);
+ $this->filterQuery($contactGroups);
+ $this->setupLimitControl();
+
+ $this->view->contactGroups = $contactGroups;
+ }
+
+ /**
+ * List all comments
+ */
+ public function commentsAction()
+ {
+ $this->addTitleTab(
+ 'comments',
+ $this->translate('Comments'),
+ $this->translate('List comments')
+ );
+
+ $this->setAutorefreshInterval(12);
+
+ $comments = $this->backend->select()->from('comment', array(
+ 'id' => 'comment_internal_id',
+ 'objecttype' => 'object_type',
+ 'comment' => 'comment_data',
+ 'author' => 'comment_author_name',
+ 'timestamp' => 'comment_timestamp',
+ 'type' => 'comment_type',
+ 'persistent' => 'comment_is_persistent',
+ 'expiration' => 'comment_expiration',
+ 'name' => 'comment_name',
+ 'host_name',
+ 'service_description',
+ 'host_display_name',
+ 'service_display_name'
+ ));
+
+ $this->setupPaginationControl($comments);
+ $this->setupSortControl(
+ array(
+ 'comment_timestamp' => $this->translate('Comment Timestamp'),
+ 'host_display_name' => $this->translate('Host'),
+ 'service_display_name' => $this->translate('Service'),
+ 'comment_type' => $this->translate('Comment Type'),
+ 'comment_expiration' => $this->translate('Expiration')
+ ),
+ $comments
+ );
+ $this->filterQuery($comments);
+ $this->setupLimitControl();
+
+ $this->view->comments = $comments;
+
+ if ($this->Auth()->hasPermission('monitoring/command/comment/delete')) {
+ $this->view->delCommentForm = new DeleteCommentCommandForm();
+ $this->view->delCommentForm->handleRequest();
+ }
+ }
+
+ /**
+ * List service groups
+ */
+ public function servicegroupsAction()
+ {
+ $this->addTitleTab(
+ 'servicegroups',
+ $this->translate('Service Groups'),
+ $this->translate('List service groups')
+ );
+
+ $this->setAutorefreshInterval(12);
+
+ $serviceGroups = $this->backend->select()->from('servicegroupsummary', array(
+ 'servicegroup_alias',
+ 'servicegroup_name',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_pending',
+ 'services_total',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning_handled',
+ 'services_warning_unhandled'
+ ));
+
+ $this->setupPaginationControl($serviceGroups);
+ $this->setupSortControl(array(
+ 'servicegroup_alias' => $this->translate('Service Group Name'),
+ 'services_severity' => $this->translate('Severity'),
+ 'services_total' => $this->translate('Total Services')
+ ), $serviceGroups);
+ $this->filterQuery($serviceGroups);
+ $this->setupLimitControl();
+
+ $this->view->serviceGroups = $serviceGroups;
+ }
+
+ /**
+ * List service groups
+ */
+ public function servicegroupGridAction()
+ {
+ $this->addTitleTab(
+ 'servicegroup-grid',
+ $this->translate('Service Group Grid'),
+ $this->translate('Show the Service Group Grid')
+ );
+
+ $this->setAutorefreshInterval(15);
+
+ $serviceGroups = $this->backend->select()->from('servicegroupsummary', array(
+ 'servicegroup_alias',
+ 'servicegroup_name',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_pending',
+ 'services_total',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning_handled',
+ 'services_warning_unhandled'
+ ));
+ $this->filterQuery($serviceGroups);
+
+ $this->setupSortControl(array(
+ 'servicegroup_alias' => $this->translate('Service Group Name'),
+ 'services_severity' => $this->translate('Severity'),
+ 'services_total' => $this->translate('Total Services')
+ ), $serviceGroups, ['services_severity' => 'desc']);
+
+ $this->view->serviceGroups = $serviceGroups;
+ }
+
+ /**
+ * List host groups
+ */
+ public function hostgroupsAction()
+ {
+ $this->addTitleTab(
+ 'hostgroups',
+ $this->translate('Host Groups'),
+ $this->translate('List host groups')
+ );
+
+ $this->setAutorefreshInterval(12);
+
+ $hostGroups = $this->backend->select()->from('hostgroupsummary', array(
+ 'hostgroup_alias',
+ 'hostgroup_name',
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_pending',
+ 'hosts_total',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_up',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_pending',
+ 'services_total',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning_handled',
+ 'services_warning_unhandled'
+ ));
+
+ $this->setupPaginationControl($hostGroups);
+ $this->setupSortControl(array(
+ 'hostgroup_alias' => $this->translate('Host Group Name'),
+ 'hosts_severity' => $this->translate('Severity'),
+ 'hosts_total' => $this->translate('Total Hosts'),
+ 'services_total' => $this->translate('Total Services')
+ ), $hostGroups);
+ $this->filterQuery($hostGroups);
+ $this->setupLimitControl();
+
+ $this->view->hostGroups = $hostGroups;
+ }
+
+ /**
+ * List host groups
+ */
+ public function hostgroupGridAction()
+ {
+ $this->addTitleTab(
+ 'hostgroup-grid',
+ $this->translate('Host Group Grid'),
+ $this->translate('Show the Host Group Grid')
+ );
+
+ $this->setAutorefreshInterval(15);
+
+ $hostGroups = $this->backend->select()->from('hostgroupsummary', [
+ 'hostgroup_alias',
+ 'hostgroup_name',
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_pending',
+ 'hosts_total',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_up'
+ ]);
+ $this->filterQuery($hostGroups);
+
+ $this->setupSortControl([
+ 'hosts_severity' => $this->translate('Severity'),
+ 'hostgroup_alias' => $this->translate('Host Group Name'),
+ 'hosts_total' => $this->translate('Total Hosts'),
+ 'services_total' => $this->translate('Total Services')
+ ], $hostGroups, ['hosts_severity' => 'desc']);
+
+ $this->view->hostGroups = $hostGroups;
+ }
+
+ public function eventhistoryAction()
+ {
+ $this->addTitleTab(
+ 'eventhistory',
+ $this->translate('Event Overview'),
+ $this->translate('List event records')
+ );
+
+ $query = $this->backend->select()->from('eventhistory', array(
+ 'id',
+ 'host_name',
+ 'host_display_name',
+ 'service_description',
+ 'service_display_name',
+ 'object_type',
+ 'timestamp',
+ 'state',
+ 'output',
+ 'type'
+ ));
+
+ $this->view->history = $query;
+
+ $this->setupSortControl(array(
+ 'timestamp' => $this->translate('Occurence')
+ ), $query);
+ $this->filterQuery($query);
+ $this->setupLimitControl();
+ }
+
+ public function servicegridAction()
+ {
+ if ($this->params->has('noscript_apply')) {
+ $this->redirectNow($this->getRequest()->getUrl()->without('noscript_apply'));
+ }
+
+ $this->addTitleTab('servicegrid', $this->translate('Service Grid'), $this->translate('Show the Service Grid'));
+ $this->setAutorefreshInterval(15);
+ $query = $this->backend->select()->from('servicestatus', array(
+ 'host_display_name',
+ 'host_name',
+ 'service_description',
+ 'service_display_name',
+ 'service_handled',
+ 'service_output',
+ 'service_state'
+ ));
+ $this->filterQuery($query);
+ $filter = (bool) $this->params->shift('problems', false) ? Filter::where('service_problem', 1) : null;
+
+ $this->view->problemToggle = $problemToggle = new Form(['method' => 'GET']);
+ $problemToggle->setUidDisabled();
+ $problemToggle->setTokenDisabled();
+ $problemToggle->setAttrib('class', 'filter-toggle inline icinga-controls');
+ $problemToggle->addElement('checkbox', 'problems', [
+ 'disableHidden' => true,
+ 'autosubmit' => true,
+ 'value' => $filter !== null,
+ 'label' => $this->translate('Problems Only'),
+ 'decorators' => ['ViewHelper', ['Label', ['placement' => 'APPEND']]]
+ ]);
+
+ if ($this->params->get('flipped', false)) {
+ $pivot = $query
+ ->pivot(
+ 'host_name',
+ 'service_description',
+ $filter,
+ $filter ? clone $filter : null
+ )
+ ->setYAxisHeader('service_display_name')
+ ->setXAxisHeader('host_display_name');
+ } else {
+ $pivot = $query
+ ->pivot(
+ 'service_description',
+ 'host_name',
+ $filter,
+ $filter ? clone $filter : null
+ )
+ ->setXAxisHeader('service_display_name')
+ ->setYAxisHeader('host_display_name');
+ }
+ $this->setupSortControl(array(
+ 'host_display_name' => $this->translate('Hostname'),
+ 'service_display_name' => $this->translate('Service Name')
+ ), $pivot);
+ $this->view->horizontalPaginator = $pivot->paginateXAxis();
+ $this->view->verticalPaginator = $pivot->paginateYAxis();
+ list($pivotData, $pivotHeader) = $pivot->toArray();
+ $this->view->pivotData = $pivotData;
+ $this->view->pivotHeader = $pivotHeader;
+ if ($this->params->get('flipped', false)) {
+ $this->render('servicegrid-flipped');
+ }
+ }
+
+ /**
+ * Apply filters on a DataView
+ *
+ * @param DataView $dataView The DataView to apply filters on
+ *
+ * @return DataView $dataView
+ */
+ protected function filterQuery(DataView $dataView)
+ {
+ $this->setupFilterControl($dataView, null, null, array(
+ 'format', // handleFormatRequest()
+ 'stateType', // hostsAction() and servicesAction()
+ 'addColumns', // addColumns()
+ 'problems', // servicegridAction()
+ 'flipped' // servicegridAction()
+ ));
+
+ if ($this->params->get('format') !== 'sql' || $this->hasPermission('config/authentication/roles/show')) {
+ $this->applyRestriction('monitoring/filter/objects', $dataView);
+ }
+
+ $this->handleFormatRequest($dataView);
+
+ return $dataView;
+ }
+
+ /**
+ * Get columns to be added from URL parameter 'addColumns'
+ * and assign to $this->view->addColumns (as array)
+ *
+ * @return array
+ */
+ protected function addColumns()
+ {
+ $columns = preg_split(
+ '~,~',
+ $this->params->shift('addColumns', ''),
+ -1,
+ PREG_SPLIT_NO_EMPTY
+ );
+
+ $customVars = [];
+ $additionalCols = [];
+ foreach ($columns as $column) {
+ if (preg_match('~^_(host|service)_([a-zA-Z0-9_]+)$~', $column, $m)) {
+ $customVars[$m[1]]['vars'][$m[2]] = null;
+ } else {
+ $additionalCols[] = $column;
+ }
+ }
+
+ if (! empty($customVars)) {
+ $blacklistedProperties = new GlobFilter(
+ $this->getRestrictions('monitoring/blacklist/properties')
+ );
+ $customVars = $blacklistedProperties->removeMatching($customVars);
+ foreach ($customVars as $type => $vars) {
+ foreach ($vars['vars'] as $var => $_) {
+ $additionalCols[] = '_' . $type . '_' . $var;
+ }
+ }
+ }
+
+ $this->view->addColumns = $additionalCols;
+ return $additionalCols;
+ }
+
+ protected function addTitleTab($action, $title, $tip)
+ {
+ $this->getTabs()->add($action, array(
+ 'title' => $tip,
+ 'label' => $title,
+ 'url' => Url::fromRequest()
+ ))->activate($action);
+ $this->view->title = $title;
+ }
+
+ /**
+ * Return all tabs for this controller
+ *
+ * @return Tabs
+ */
+ private function createTabs()
+ {
+ $this->getTabs()->extend(new OutputFormat())->extend(new DashboardAction())->extend(new MenuAction());
+ }
+}
diff --git a/modules/monitoring/application/controllers/ServiceController.php b/modules/monitoring/application/controllers/ServiceController.php
new file mode 100644
index 0000000..d3eeb1c
--- /dev/null
+++ b/modules/monitoring/application/controllers/ServiceController.php
@@ -0,0 +1,147 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Module\Monitoring\Forms\Command\Object\AcknowledgeProblemCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\AddCommentCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ProcessCheckResultCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleServiceCheckCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleServiceDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandForm;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Module\Monitoring\Web\Controller\MonitoredObjectController;
+use Icinga\Web\Hook;
+use Icinga\Web\Navigation\Navigation;
+
+class ServiceController extends MonitoredObjectController
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $commandRedirectUrl = 'monitoring/service/show';
+
+ /**
+ * Fetch the requested service from the monitoring backend
+ */
+ public function init()
+ {
+ $service = new Service(
+ $this->backend,
+ $this->params->getRequired('host'),
+ $this->params->getRequired('service')
+ );
+
+ $this->applyRestriction('monitoring/filter/objects', $service);
+
+ if ($service->fetch() === false) {
+ $this->httpNotFound($this->translate('Service not found'));
+ }
+ $this->object = $service;
+ $this->createTabs();
+ $this->getTabs()->activate('service');
+ $this->view->title = $service->service_display_name;
+ $this->view->defaultTitle = join(' :: ', [
+ $service->host_display_name,
+ $this->translate('Services'),
+ $this->view->defaultTitle
+ ]);
+ }
+
+ /**
+ * Get service actions from hook
+ *
+ * @return Navigation
+ */
+ protected function getServiceActions()
+ {
+ $navigation = new Navigation();
+ foreach (Hook::all('Monitoring\\ServiceActions') as $hook) {
+ $navigation->merge($hook->getNavigation($this->object));
+ }
+
+ return $navigation;
+ }
+
+ /**
+ * Show a service
+ */
+ public function showAction()
+ {
+ $this->view->actions = $this->getServiceActions();
+ parent::showAction();
+ }
+
+
+ /**
+ * Acknowledge a service problem
+ */
+ public function acknowledgeProblemAction()
+ {
+ $this->assertPermission('monitoring/command/acknowledge-problem');
+
+ $form = new AcknowledgeProblemCommandForm();
+ $form->setTitle($this->translate('Acknowledge Service Problem'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Add a service comment
+ */
+ public function addCommentAction()
+ {
+ $this->assertPermission('monitoring/command/comment/add');
+
+ $form = new AddCommentCommandForm();
+ $form->setTitle($this->translate('Add Service Comment'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Reschedule a service check
+ */
+ public function rescheduleCheckAction()
+ {
+ $this->assertPermission('monitoring/command/schedule-check');
+
+ $form = new ScheduleServiceCheckCommandForm();
+ $form->setTitle($this->translate('Reschedule Service Check'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Schedule a service downtime
+ */
+ public function scheduleDowntimeAction()
+ {
+ $this->assertPermission('monitoring/command/downtime/schedule');
+
+ $form = new ScheduleServiceDowntimeCommandForm();
+ $form->setTitle($this->translate('Schedule Service Downtime'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Submit a passive service check result
+ */
+ public function processCheckResultAction()
+ {
+ $this->assertPermission('monitoring/command/process-check-result');
+
+ $form = new ProcessCheckResultCommandForm();
+ $form->setTitle($this->translate('Submit Passive Service Check Result'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Send a custom notification for a service
+ */
+ public function sendCustomNotificationAction()
+ {
+ $this->assertPermission('monitoring/command/send-custom-notification');
+
+ $form = new SendCustomNotificationCommandForm();
+ $form->setTitle($this->translate('Send Custom Service Notification'));
+ $this->handleCommandForm($form);
+ }
+}
diff --git a/modules/monitoring/application/controllers/ServicesController.php b/modules/monitoring/application/controllers/ServicesController.php
new file mode 100644
index 0000000..6c65592
--- /dev/null
+++ b/modules/monitoring/application/controllers/ServicesController.php
@@ -0,0 +1,262 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Exception;
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\AcknowledgeProblemCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\AddCommentCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\CheckNowCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ObjectsCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ProcessCheckResultCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\RemoveAcknowledgementCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleServiceCheckCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleServiceDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ToggleObjectFeaturesCommandForm;
+use Icinga\Module\Monitoring\Hook\DetailviewExtensionHook;
+use Icinga\Module\Monitoring\Object\ServiceList;
+use Icinga\Web\Hook;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+class ServicesController extends Controller
+{
+ /**
+ * @var ServiceList
+ */
+ protected $serviceList;
+
+ public function init()
+ {
+ $serviceList = new ServiceList($this->backend);
+ $this->applyRestriction('monitoring/filter/objects', $serviceList);
+ $serviceList->addFilter(Filter::fromQueryString(
+ (string) $this->params->without(array('service_problem', 'service_handled', 'showCompact'))
+ ));
+ $this->serviceList = $serviceList;
+ $this->serviceList->setColumns(array(
+ 'host_display_name',
+ 'host_handled',
+ 'host_name',
+ 'host_problem',
+ 'host_state',
+ 'instance_name',
+ 'service_acknowledged',
+ 'service_active_checks_enabled',
+ 'service_description',
+ 'service_display_name',
+ 'service_event_handler_enabled',
+ 'service_flap_detection_enabled',
+ 'service_handled',
+ 'service_in_downtime',
+ 'service_is_flapping',
+ 'service_last_state_change',
+ 'service_notifications_enabled',
+ 'service_obsessing',
+ 'service_passive_checks_enabled',
+ 'service_problem',
+ 'service_state'
+ ));
+ $this->view->baseFilter = $this->serviceList->getFilter();
+ $this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/services');
+ $this->getTabs()->add(
+ 'show',
+ array(
+ 'label' => $this->translate('Services') . sprintf(' (%d)', count($this->serviceList)),
+ 'title' => sprintf(
+ $this->translate('Show summarized information for %u services'),
+ count($this->serviceList)
+ ),
+ 'url' => Url::fromRequest()
+ )
+ )->extend(new DashboardAction())->extend(new MenuAction())->activate('show');
+ $this->view->title = $this->translate('Services');
+ }
+
+ protected function handleCommandForm(ObjectsCommandForm $form)
+ {
+ $form
+ ->setBackend($this->backend)
+ ->setObjects($this->serviceList)
+ ->setRedirectUrl(Url::fromPath('monitoring/services/show')->setParams(
+ $this->params->without('service_active_checks_enabled')
+ ))
+ ->handleRequest();
+
+ $this->view->form = $form;
+ $this->view->objects = $this->serviceList;
+ $this->view->stats = $this->serviceList->getServiceStateSummary();
+ $this->view->serviceStates = true;
+ $this->_helper->viewRenderer('partials/command/objects-command-form', null, true);
+ return $form;
+ }
+
+ public function showAction()
+ {
+ $this->setAutorefreshInterval(15);
+ $activeChecksEnabled = $this->serviceList->getFeatureStatus()['active_checks_enabled'] !== 0;
+ if ($this->Auth()->hasPermission('monitoring/command/schedule-check')
+ || ($this->Auth()->hasPermission('monitoring/command/schedule-check/active-only')
+ && $activeChecksEnabled
+ )
+ ) {
+ $checkNowForm = new CheckNowCommandForm();
+ $checkNowForm
+ ->setObjects($this->serviceList)
+ ->handleRequest();
+ $this->view->checkNowForm = $checkNowForm;
+ }
+
+ $acknowledgedObjects = $this->serviceList->getAcknowledgedObjects();
+ if (! empty($acknowledgedObjects)) {
+ $removeAckForm = new RemoveAcknowledgementCommandForm();
+ $removeAckForm
+ ->setObjects($acknowledgedObjects)
+ ->handleRequest();
+ $this->view->removeAckForm = $removeAckForm;
+ }
+
+ $featureStatus = $this->serviceList->getFeatureStatus();
+ $toggleFeaturesForm = new ToggleObjectFeaturesCommandForm(array(
+ 'backend' => $this->backend,
+ 'objects' => $this->serviceList
+ ));
+ $toggleFeaturesForm
+ ->load((object) $featureStatus)
+ ->handleRequest();
+ $this->view->toggleFeaturesForm = $toggleFeaturesForm;
+
+ if ($activeChecksEnabled) {
+ $this->view->rescheduleAllLink = Url::fromRequest()
+ ->setPath('monitoring/services/reschedule-check')
+ ->addParams(['service_active_checks_enabled' => true]);
+ }
+
+ $this->view->downtimeAllLink = Url::fromRequest()->setPath('monitoring/services/schedule-downtime');
+ $this->view->processCheckResultAllLink = Url::fromRequest()->setPath(
+ 'monitoring/services/process-check-result'
+ );
+ $this->view->addCommentLink = Url::fromRequest()->setPath('monitoring/services/add-comment');
+ $this->view->deleteCommentLink = Url::fromRequest()->setPath('monitoring/services/delete-comment');
+ $this->view->stats = $this->serviceList->getServiceStateSummary();
+ $this->view->objects = $this->serviceList;
+ $this->view->unhandledObjects = $this->serviceList->getUnhandledObjects();
+ $this->view->problemObjects = $this->serviceList->getProblemObjects();
+ $this->view->downtimeUnhandledLink = Url::fromPath('monitoring/services/schedule-downtime')
+ ->setQueryString($this->serviceList->getUnhandledObjects()->objectsFilter()->toQueryString());
+ $this->view->downtimeLink = Url::fromPath('monitoring/services/schedule-downtime')
+ ->setQueryString($this->serviceList->getProblemObjects()->objectsFilter()->toQueryString());
+ $this->view->acknowledgedObjects = $acknowledgedObjects;
+ $this->view->acknowledgeLink = Url::fromPath('monitoring/services/acknowledge-problem')
+ ->setQueryString($this->serviceList->getUnacknowledgedObjects()->objectsFilter()->toQueryString());
+ $this->view->unacknowledgedObjects = $this->serviceList->getUnacknowledgedObjects();
+ $this->view->objectsInDowntime = $this->serviceList->getObjectsInDowntime();
+ $this->view->inDowntimeLink = Url::fromPath('monitoring/list/services')
+ ->setQueryString($this->serviceList->getObjectsInDowntime()
+ ->objectsFilter(array('host' => 'host_name', 'service' => 'service_description'))->toQueryString());
+ $this->view->showDowntimesLink = Url::fromPath('monitoring/downtimes/show')
+ ->setQueryString(
+ $this->serviceList->getObjectsInDowntime()
+ ->objectsFilter()->andFilter(Filter::where('object_type', 'service'))->toQueryString()
+ );
+ $this->view->commentsLink = Url::fromRequest()
+ ->setPath('monitoring/list/comments');
+ $this->view->sendCustomNotificationLink = Url::fromRequest()->setPath(
+ 'monitoring/services/send-custom-notification'
+ );
+
+ $this->view->extensionsHtml = array();
+ foreach (Hook::all('Monitoring\DetailviewExtension') as $hook) {
+ /** @var DetailviewExtensionHook $hook */
+ try {
+ $html = $hook->setView($this->view)->getHtmlForObjects($this->serviceList);
+ } catch (Exception $e) {
+ $html = $this->view->escape($e->getMessage());
+ }
+
+ if ($html) {
+ $module = $this->view->escape($hook->getModule()->getName());
+ $this->view->extensionsHtml[] =
+ '<div class="icinga-module module-' . $module . '" data-icinga-module="' . $module . '">'
+ . $html
+ . '</div>';
+ }
+ }
+ }
+
+ /**
+ * Add a service comment
+ */
+ public function addCommentAction()
+ {
+ $this->assertPermission('monitoring/command/comment/add');
+
+ $form = new AddCommentCommandForm();
+ $form->setTitle($this->translate('Add Service Comments'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Acknowledge service problems
+ */
+ public function acknowledgeProblemAction()
+ {
+ $this->assertPermission('monitoring/command/acknowledge-problem');
+
+ $form = new AcknowledgeProblemCommandForm();
+ $form->setTitle($this->translate('Acknowledge Service Problems'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Reschedule service checks
+ */
+ public function rescheduleCheckAction()
+ {
+ $this->assertPermission('monitoring/command/schedule-check');
+
+ $form = new ScheduleServiceCheckCommandForm();
+ $form->setTitle($this->translate('Reschedule Service Checks'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Schedule service downtimes
+ */
+ public function scheduleDowntimeAction()
+ {
+ $this->assertPermission('monitoring/command/downtime/schedule');
+
+ $form = new ScheduleServiceDowntimeCommandForm();
+ $form->setTitle($this->translate('Schedule Service Downtimes'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Submit passive service check results
+ */
+ public function processCheckResultAction()
+ {
+ $this->assertPermission('monitoring/command/process-check-result');
+
+ $form = new ProcessCheckResultCommandForm();
+ $form->setTitle($this->translate('Submit Passive Service Check Results'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Send a custom notification for services
+ */
+ public function sendCustomNotificationAction()
+ {
+ $this->assertPermission('monitoring/command/send-custom-notification');
+
+ $form = new SendCustomNotificationCommandForm();
+ $form->setTitle($this->translate('Send Custom Service Notification'));
+ $this->handleCommandForm($form);
+ }
+}
diff --git a/modules/monitoring/application/controllers/ShowController.php b/modules/monitoring/application/controllers/ShowController.php
new file mode 100644
index 0000000..f1da561
--- /dev/null
+++ b/modules/monitoring/application/controllers/ShowController.php
@@ -0,0 +1,101 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Data\Filter\FilterEqual;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Security\SecurityException;
+use Icinga\Web\Url;
+
+/**
+ * Class Monitoring_ShowController
+ *
+ * Actions for show context
+ */
+class ShowController extends Controller
+{
+ /**
+ * @var MonitoringBackend
+ */
+ protected $backend;
+
+ public function init()
+ {
+ $this->view->defaultTitle = $this->translate('Contacts') . ' :: ' . $this->view->defaultTitle;
+
+ parent::init();
+ }
+
+ public function contactAction()
+ {
+ if (! $this->hasPermission('*') && $this->hasPermission('no-monitoring/contacts')) {
+ throw new SecurityException('No permission for %s', 'monitoring/contacts');
+ }
+
+ $contactName = $this->params->getRequired('contact_name');
+
+ $this->getTabs()->add('contact-detail', [
+ 'title' => $this->translate('Contact details'),
+ 'label' => $this->translate('Contact'),
+ 'url' => Url::fromRequest(),
+ 'active' => true
+ ]);
+
+ $query = $this->backend->select()->from('contact', array(
+ 'contact_name',
+ 'contact_id',
+ 'contact_alias',
+ 'contact_email',
+ 'contact_pager',
+ 'contact_notify_service_timeperiod',
+ 'contact_notify_service_recovery',
+ 'contact_notify_service_warning',
+ 'contact_notify_service_critical',
+ 'contact_notify_service_unknown',
+ 'contact_notify_service_flapping',
+ 'contact_notify_service_downtime',
+ 'contact_notify_host_timeperiod',
+ 'contact_notify_host_recovery',
+ 'contact_notify_host_down',
+ 'contact_notify_host_unreachable',
+ 'contact_notify_host_flapping',
+ 'contact_notify_host_downtime',
+ ));
+ $this->applyRestriction('monitoring/filter/objects', $query);
+ $query->whereEx(new FilterEqual('contact_name', '=', $contactName));
+ $contact = $query->getQuery()->fetchRow();
+
+ if ($contact) {
+ $commands = $this->backend->select()->from('command', array(
+ 'command_line',
+ 'command_name'
+ ))->where('contact_id', $contact->contact_id);
+
+ $this->view->commands = $commands;
+
+ $notifications = $this->backend->select()->from('notification', array(
+ 'id',
+ 'host_name',
+ 'service_description',
+ 'notification_output',
+ 'notification_contact_name',
+ 'notification_timestamp',
+ 'notification_state',
+ 'host_display_name',
+ 'service_display_name'
+ ));
+
+ $notifications->where('notification_contact_name', $contactName);
+ $this->applyRestriction('monitoring/filter/objects', $notifications);
+ $this->view->notifications = $notifications;
+ $this->setupLimitControl();
+ $this->setupPaginationControl($this->view->notifications);
+ $this->view->title = $contact->contact_name;
+ }
+
+ $this->view->contact = $contact;
+ $this->view->contactName = $contactName;
+ }
+}
diff --git a/modules/monitoring/application/controllers/TacticalController.php b/modules/monitoring/application/controllers/TacticalController.php
new file mode 100644
index 0000000..b147d45
--- /dev/null
+++ b/modules/monitoring/application/controllers/TacticalController.php
@@ -0,0 +1,128 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Chart\Donut;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+class TacticalController extends Controller
+{
+ public function indexAction()
+ {
+ $this->setAutorefreshInterval(15);
+
+ $this->view->title = $this->translate('Tactical Overview');
+ $this->getTabs()->add(
+ 'tactical_overview',
+ array(
+ 'title' => $this->translate(
+ 'Show an overview of all hosts and services, their current'
+ . ' states and monitoring feature utilisation'
+ ),
+ 'label' => $this->translate('Tactical Overview'),
+ 'url' => Url::fromRequest()
+ )
+ )->extend(new DashboardAction())->extend(new MenuAction())->activate('tactical_overview');
+
+ $stats = $this->backend->select()->from(
+ 'statussummary',
+ array(
+ 'hosts_up',
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_pending',
+ 'hosts_pending_not_checked',
+ 'hosts_not_checked',
+
+ 'services_ok',
+ 'services_warning_handled',
+ 'services_warning_unhandled',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_pending',
+ 'services_pending_not_checked',
+ 'services_not_checked',
+ )
+ );
+ $this->applyRestriction('monitoring/filter/objects', $stats);
+
+ $this->setupFilterControl($stats, null, ['host', 'service'], ['format']);
+ $this->view->setHelperFunction('filteredUrl', function ($path, array $params) {
+ $filter = clone $this->view->filterEditor->getFilter();
+
+ return $this->view->url($path)->setParams($params)->addFilter($filter);
+ });
+
+ $this->handleFormatRequest($stats);
+ $summary = $stats->fetchRow();
+
+ // Correct pending counts. Done here instead of in the query for compatibility reasons.
+ $summary->hosts_pending -= $summary->hosts_pending_not_checked;
+ $summary->services_pending -= $summary->services_pending_not_checked;
+
+ $hostSummaryChart = new Donut();
+ $hostSummaryChart
+ ->addSlice($summary->hosts_up, array('class' => 'slice-state-ok'))
+ ->addSlice($summary->hosts_down_handled, array('class' => 'slice-state-critical-handled'))
+ ->addSlice($summary->hosts_down_unhandled, array('class' => 'slice-state-critical'))
+ ->addSlice($summary->hosts_unreachable_handled, array('class' => 'slice-state-unreachable-handled'))
+ ->addSlice($summary->hosts_unreachable_unhandled, array('class' => 'slice-state-unreachable'))
+ ->addSlice($summary->hosts_pending, array('class' => 'slice-state-pending'))
+ ->addSlice($summary->hosts_pending_not_checked, array('class' => 'slice-state-not-checked'))
+ ->setLabelBig($summary->hosts_down_unhandled)
+ ->setLabelBigEyeCatching($summary->hosts_down_unhandled > 0)
+ ->setLabelSmall($this->translate('Hosts Down'));
+
+ $serviceSummaryChart = new Donut();
+ $serviceSummaryChart
+ ->addSlice($summary->services_ok, array('class' => 'slice-state-ok'))
+ ->addSlice($summary->services_warning_handled, array('class' => 'slice-state-warning-handled'))
+ ->addSlice($summary->services_warning_unhandled, array('class' => 'slice-state-warning'))
+ ->addSlice($summary->services_critical_handled, array('class' => 'slice-state-critical-handled'))
+ ->addSlice($summary->services_critical_unhandled, array('class' => 'slice-state-critical'))
+ ->addSlice($summary->services_unknown_handled, array('class' => 'slice-state-unknown-handled'))
+ ->addSlice($summary->services_unknown_unhandled, array('class' => 'slice-state-unknown'))
+ ->addSlice($summary->services_pending, array('class' => 'slice-state-pending'))
+ ->addSlice($summary->services_pending_not_checked, array('class' => 'slice-state-not-checked'))
+ ->setLabelBig($summary->services_critical_unhandled ?: $summary->services_unknown_unhandled)
+ ->setLabelBigState($summary->services_critical_unhandled > 0 ? 'critical' : (
+ $summary->services_unknown_unhandled > 0 ? 'unknown' : null
+ ))
+ ->setLabelSmall($summary->services_critical_unhandled > 0 || $summary->services_unknown_unhandled < 1
+ ? $this->translate('Services Critical')
+ : $this->translate('Services Unknown'));
+
+ $this->view->hostStatusSummaryChart = $hostSummaryChart
+ ->setLabelBigUrl($this->view->filteredUrl(
+ 'monitoring/list/hosts',
+ array(
+ 'host_state' => 1,
+ 'host_handled' => 0,
+ 'sort' => 'host_last_check',
+ 'dir' => 'asc'
+ )
+ ))
+ ->render();
+ $this->view->serviceStatusSummaryChart = $serviceSummaryChart
+ ->setLabelBigUrl($this->view->filteredUrl(
+ 'monitoring/list/services',
+ array(
+ 'service_state' => $summary->services_critical_unhandled > 0
+ || ! $summary->services_unknown_unhandled ? 2 : 3,
+ 'service_handled' => 0,
+ 'sort' => 'service_last_check',
+ 'dir' => 'asc'
+ )
+ ))
+ ->render();
+ $this->view->statusSummary = $summary;
+ }
+}
diff --git a/modules/monitoring/application/controllers/TimelineController.php b/modules/monitoring/application/controllers/TimelineController.php
new file mode 100644
index 0000000..deeeb36
--- /dev/null
+++ b/modules/monitoring/application/controllers/TimelineController.php
@@ -0,0 +1,325 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use DateInterval;
+use DateTime;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Timeline\TimeLine;
+use Icinga\Module\Monitoring\Timeline\TimeRange;
+use Icinga\Module\Monitoring\Web\Widget\SelectBox;
+use Icinga\Util\Format;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+class TimelineController extends Controller
+{
+ public function indexAction()
+ {
+ $this->getTabs()->add(
+ 'timeline',
+ array(
+ 'title' => $this->translate('Show the number of historical event records grouped by time and type'),
+ 'label' => $this->translate('Timeline'),
+ 'url' => Url::fromRequest()
+ )
+ )->extend(new DashboardAction())->extend(new MenuAction())->activate('timeline');
+ $this->view->title = $this->translate('Timeline');
+
+ // TODO: filter for hard_states (precedence adjustments necessary!)
+ $this->setupIntervalBox();
+ list($displayRange, $forecastRange) = $this->buildTimeRanges();
+
+ $detailUrl = Url::fromPath('monitoring/list/eventhistory');
+
+ $timeline = new TimeLine(
+ $this->applyRestriction(
+ 'monitoring/filter/objects',
+ $this->backend->select()->from(
+ 'eventhistory',
+ array(
+ 'name' => 'type',
+ 'time' => 'timestamp'
+ )
+ )
+ ),
+ array(
+ 'notification_ack' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'notification_flapping' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'notification_flapping_end' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'notification_dt_start' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'notification_dt_end' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'notification_custom' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'notification_state' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'hard_state' => array(
+ 'class' => 'timeline-hard-state',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Hard state changes')
+ ),
+ 'comment' => array(
+ 'class' => 'timeline-comment',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Comments')
+ ),
+ 'ack' => array(
+ 'class' => 'timeline-ack',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Acknowledgements')
+ ),
+ 'dt_start' => array(
+ 'class' => 'timeline-downtime-start',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Started downtimes')
+ ),
+ 'dt_end' => array(
+ 'class' => 'timeline-downtime-end',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Ended downtimes')
+ )
+ )
+ );
+ $timeline->setMaximumCircleWidth('6em');
+ $timeline->setMinimumCircleWidth('0.3em');
+ $timeline->setDisplayRange($displayRange);
+ $timeline->setForecastRange($forecastRange);
+ $beingExtended = $this->getRequest()->getParam('extend') == 1;
+ $timeline->setSession($this->Window()->getSessionNamespace('timeline', !$beingExtended));
+
+ $this->view->timeline = $timeline;
+ $this->view->nextRange = $forecastRange;
+ $this->view->beingExtended = $beingExtended;
+ $this->view->intervalFormat = $this->getIntervalFormat();
+ $oldBase = $timeline->getCalculationBase(false);
+ $this->view->switchedContext = $oldBase !== null && $oldBase !== $timeline->getCalculationBase(true);
+ }
+
+ /**
+ * Create a select box the user can choose the timeline interval from
+ */
+ private function setupIntervalBox()
+ {
+ $box = new SelectBox(
+ 'intervalBox',
+ array(
+ '4h' => mt('monitoring', '4 Hours'),
+ '1d' => mt('monitoring', 'One day'),
+ '1w' => mt('monitoring', 'One week'),
+ '1m' => mt('monitoring', 'One month'),
+ '1y' => mt('monitoring', 'One year')
+ ),
+ mt('monitoring', 'TimeLine interval'),
+ 'interval'
+ );
+ $box->applyRequest($this->getRequest());
+ $this->view->intervalBox = $box;
+ }
+
+ /**
+ * Return the chosen interval
+ *
+ * @return DateInterval The chosen interval
+ */
+ private function getTimelineInterval()
+ {
+ switch ($this->view->intervalBox->getInterval()) {
+ case '1d':
+ return new DateInterval('P1D');
+ case '1w':
+ return new DateInterval('P1W');
+ case '1m':
+ return new DateInterval('P1M');
+ case '1y':
+ return new DateInterval('P1Y');
+ default:
+ return new DateInterval('PT4H');
+ }
+ }
+
+ /**
+ * Get an appropriate datetime format string for the chosen interval
+ *
+ * @return string
+ */
+ private function getIntervalFormat()
+ {
+ switch ($this->view->intervalBox->getInterval()) {
+ case '1d':
+ return $this->getDateFormat();
+ case '1w':
+ return '\W\e\ek W\<b\r\>\of Y';
+ case '1m':
+ return 'F Y';
+ case '1y':
+ return 'Y';
+ default:
+ return $this->getDateFormat() . '\<b\r\>' . $this->getTimeFormat();
+ }
+ }
+
+ /**
+ * Return a preload interval based on the chosen timeline interval and the given date and time
+ *
+ * @param DateTime $dateTime The date and time to use
+ *
+ * @return DateInterval The interval to pre-load
+ */
+ private function getPreloadInterval(DateTime $dateTime)
+ {
+ switch ($this->view->intervalBox->getInterval()) {
+ case '1d':
+ return DateInterval::createFromDateString('1 week -1 second');
+ case '1w':
+ return DateInterval::createFromDateString('8 weeks -1 second');
+ case '1m':
+ $dateCopy = clone $dateTime;
+ for ($i = 0; $i < 6; $i++) {
+ $dateCopy->sub(new DateInterval('PT' . Format::secondsByMonth($dateCopy) . 'S'));
+ }
+ return $dateCopy->add(new DateInterval('PT1S'))->diff($dateTime);
+ case '1y':
+ $dateCopy = clone $dateTime;
+ for ($i = 0; $i < 4; $i++) {
+ $dateCopy->sub(new DateInterval('PT' . Format::secondsByYear($dateCopy) . 'S'));
+ }
+ return $dateCopy->add(new DateInterval('PT1S'))->diff($dateTime);
+ default:
+ return DateInterval::createFromDateString('1 day -1 second');
+ }
+ }
+
+ /**
+ * Extrapolate the given datetime based on the chosen timeline interval
+ *
+ * @param DateTime $dateTime The datetime to extrapolate
+ */
+ private function extrapolateDateTime(DateTime &$dateTime)
+ {
+ switch ($this->view->intervalBox->getInterval()) {
+ case '1d':
+ $dateTime->setTimestamp(strtotime('tomorrow', $dateTime->getTimestamp()) - 1);
+ break;
+ case '1w':
+ $dateTime->setTimestamp(strtotime('next monday', $dateTime->getTimestamp()) - 1);
+ break;
+ case '1m':
+ $dateTime->setTimestamp(
+ strtotime(
+ 'last day of this month',
+ strtotime(
+ 'tomorrow',
+ $dateTime->getTimestamp()
+ ) - 1
+ )
+ );
+ break;
+ case '1y':
+ $dateTime->setTimestamp(strtotime('1 january next year', $dateTime->getTimestamp()) - 1);
+ break;
+ default:
+ $hour = $dateTime->format('G');
+ $end = $hour < 4 ? 4 : ($hour < 8 ? 8 : ($hour < 12 ? 12 : ($hour < 16 ? 16 : ($hour < 20 ? 20 : 24))));
+ $dateTime = DateTime::createFromFormat(
+ 'd/m/y G:i:s',
+ $dateTime->format('d/m/y') . ($end - 1) . ':59:59'
+ );
+ }
+ }
+
+ /**
+ * Return a display- and forecast time range
+ *
+ * Assembles a time range each for display and forecast purposes based on the start- and
+ * end time if given in the current request otherwise based on the current time and a
+ * end time that is calculated based on the chosen timeline interval.
+ *
+ * @return array The resulting time ranges
+ */
+ private function buildTimeRanges()
+ {
+ $startTime = new DateTime();
+ $startParam = $this->_request->getParam('start');
+ $startTimestamp = is_numeric($startParam) ? intval($startParam) : strtotime($startParam ?? '');
+ if ($startTimestamp !== false) {
+ $startTime->setTimestamp($startTimestamp);
+ } else {
+ $this->extrapolateDateTime($startTime);
+ }
+
+ $endTime = clone $startTime;
+ $endParam = $this->_request->getParam('end');
+ $endTimestamp = is_numeric($endParam) ? intval($endParam) : strtotime($endParam ?? '');
+ if ($endTimestamp !== false) {
+ $endTime->setTimestamp($endTimestamp);
+ } else {
+ $endTime->sub($this->getPreloadInterval($startTime));
+ }
+
+ $forecastStart = clone $endTime;
+ $forecastStart->sub(new DateInterval('PT1S'));
+ $forecastEnd = clone $forecastStart;
+ $forecastEnd->sub($this->getPreloadInterval($forecastStart));
+
+ $timelineInterval = $this->getTimelineInterval();
+ return array(
+ new TimeRange($startTime, $endTime, $timelineInterval),
+ new TimeRange($forecastStart, $forecastEnd, $timelineInterval)
+ );
+ }
+
+ /**
+ * Get the user's preferred time format or the application's default
+ *
+ * @return string
+ */
+ private function getTimeFormat()
+ {
+ return 'H:i';
+ }
+
+ /**
+ * Get the user's preferred date format or the application's default
+ *
+ * @return string
+ */
+ private function getDateFormat()
+ {
+ return 'Y-m-d';
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/CommandForm.php b/modules/monitoring/application/forms/Command/CommandForm.php
new file mode 100644
index 0000000..34391cf
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/CommandForm.php
@@ -0,0 +1,92 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command;
+
+use Icinga\Exception\ConfigurationError;
+use Icinga\Web\Form;
+use Icinga\Web\Request;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Module\Monitoring\Command\Transport\CommandTransport;
+use Icinga\Module\Monitoring\Command\Transport\CommandTransportInterface;
+
+/**
+ * Base class for command forms
+ */
+abstract class CommandForm extends Form
+{
+ /**
+ * Monitoring backend
+ *
+ * @var MonitoringBackend
+ */
+ protected $backend;
+
+ /**
+ * Set the monitoring backend
+ *
+ * @param MonitoringBackend $backend
+ *
+ * @return $this
+ */
+ public function setBackend(MonitoringBackend $backend)
+ {
+ $this->backend = $backend;
+ return $this;
+ }
+
+ /**
+ * Get the monitoring backend
+ *
+ * @return MonitoringBackend
+ */
+ public function getBackend()
+ {
+ return $this->backend;
+ }
+
+ /**
+ * Get the transport used to send commands
+ *
+ * @param Request $request
+ *
+ * @return CommandTransportInterface
+ *
+ * @throws ConfigurationError
+ */
+ public function getTransport(Request $request)
+ {
+ if (($transportName = $request->getParam('transport')) !== null) {
+ $config = CommandTransport::getConfig();
+ if ($config->hasSection($transportName)) {
+ $transport = CommandTransport::createTransport($config->getSection($transportName));
+ } else {
+ throw new ConfigurationError(sprintf(
+ mt('monitoring', 'Command transport "%s" not found.'),
+ $transportName
+ ));
+ }
+ } else {
+ $transport = new CommandTransport();
+ }
+
+ return $transport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRedirectUrl()
+ {
+ $redirectUrl = parent::getRedirectUrl();
+ // TODO(el): Forms should provide event handling. This is quite hackish
+ $formData = $this->getRequestData();
+ if ($this->wasSent($formData)
+ && (! $this->getSubmitLabel() || $this->isSubmitted())
+ && $this->isValid($formData)
+ ) {
+ $this->getResponse()->setAutoRefreshInterval(1);
+ }
+ return $redirectUrl;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php b/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php
new file mode 100644
index 0000000..ee49962
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php
@@ -0,0 +1,64 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Instance;
+
+use DateTime;
+use DateInterval;
+use Icinga\Module\Monitoring\Command\Instance\DisableNotificationsExpireCommand;
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Web\Notification;
+
+/**
+ * Form for disabling host and service notifications w/ an optional expire date and time on an Icinga instance
+ */
+class DisableNotificationsExpireCommandForm extends CommandForm
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Zend_Form::init() For the method documentation.
+ */
+ public function init()
+ {
+ $this->setRequiredCue(null);
+ $this->setSubmitLabel($this->translate('Disable Notifications'));
+ $this->addDescription($this->translate(
+ 'This command is used to disable host and service notifications for a specific time.'
+ ));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $expireTime = new DateTime();
+ $expireTime->add(new DateInterval('PT1H'));
+ $this->addElement(
+ 'dateTimePicker',
+ 'expire_time',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Expire Time'),
+ 'description' => $this->translate('Set the expire time.'),
+ 'value' => $expireTime
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ $disableNotifications = new DisableNotificationsExpireCommand();
+ $disableNotifications
+ ->setExpireTime($this->getElement('expire_time')->getValue()->getTimestamp());
+ $this->getTransport($this->request)->send($disableNotifications);
+ Notification::success($this->translate('Disabling host and service notifications..'));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php b/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php
new file mode 100644
index 0000000..8b01399
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php
@@ -0,0 +1,279 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Instance;
+
+use Icinga\Module\Monitoring\Command\Instance\ToggleInstanceFeatureCommand;
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Web\Notification;
+
+/**
+ * Form for enabling or disabling features of Icinga instances
+ */
+class ToggleInstanceFeaturesCommandForm extends CommandForm
+{
+ /**
+ * Instance status
+ *
+ * @var object
+ */
+ protected $status;
+
+ /**
+ * (non-PHPDoc)
+ * @see \Zend_Form::init() For the method documentation.
+ */
+ public function init()
+ {
+ $this->setUseFormAutosubmit();
+ $this->setAttrib('class', self::DEFAULT_CLASSES . ' instance-features');
+ }
+
+ /**
+ * Set the instance status
+ *
+ * @param object $status
+ *
+ * @return $this
+ */
+ public function setStatus($status)
+ {
+ $this->status = (object) $status;
+ return $this;
+ }
+
+ /**
+ * Get the instance status
+ *
+ * @return object
+ */
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $notificationDescription = null;
+ $isIcinga2 = $this->getBackend()->isIcinga2($this->status->program_version);
+
+ if (! $isIcinga2) {
+ if ((bool) $this->status->notifications_enabled) {
+ if ($this->hasPermission('monitoring/command/feature/instance')) {
+ $notificationDescription = sprintf(
+ '<a aria-label="%1$s" class="action-link" title="%1$s"'
+ . ' href="%2$s" data-base-target="_next">%3$s</a>',
+ $this->translate('Disable notifications for a specific time on a program-wide basis'),
+ $this->getView()->href('monitoring/health/disable-notifications'),
+ $this->translate('Disable temporarily')
+ );
+ } else {
+ $notificationDescription = null;
+ }
+ } elseif ($this->status->disable_notif_expire_time) {
+ $notificationDescription = sprintf(
+ $this->translate('Notifications will be re-enabled in <strong>%s</strong>'),
+ $this->getView()->timeUntil($this->status->disable_notif_expire_time)
+ );
+ }
+ }
+
+ $toggleDisabled = $this->hasPermission('monitoring/command/feature/instance') ? null : '';
+
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS,
+ array(
+ 'label' => $this->translate('Active Host Checks'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS,
+ array(
+ 'label' => $this->translate('Active Service Checks'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS,
+ array(
+ 'label' => $this->translate('Event Handlers'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION,
+ array(
+ 'label' => $this->translate('Flap Detection'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS,
+ array(
+ 'label' => $this->translate('Notifications'),
+ 'autosubmit' => true,
+ 'description' => $notificationDescription,
+ 'decorators' => array(
+ array('Label', array('tag'=>'span', 'separator' => '', 'class' => 'control-label')),
+ array(
+ 'Description',
+ array('tag' => 'span', 'class' => 'description', 'escape' => false)
+ ),
+ array(array('labelWrap' => 'HtmlTag'), array('tag' => 'div', 'class' => 'control-label-group')),
+ array('ViewHelper', array('separator' => '')),
+ array('Errors', array('separator' => '')),
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group'))
+ ),
+ 'disabled' => $toggleDisabled
+ )
+ );
+
+ if (! $isIcinga2) {
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING,
+ array(
+ 'label' => $this->translate('Obsessing Over Hosts'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_SERVICE_OBSESSING,
+ array(
+ 'label' => $this->translate('Obsessing Over Services'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS,
+ array(
+ 'label' => $this->translate('Passive Host Checks'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS,
+ array(
+ 'label' => $this->translate('Passive Service Checks'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ }
+
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA,
+ array(
+ 'label' => $this->translate('Performance Data'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ }
+
+ /**
+ * Load feature status
+ *
+ * @param object $instanceStatus
+ *
+ * @return $this
+ */
+ public function load($instanceStatus)
+ {
+ $this->create();
+ foreach ($this->getValues() as $feature => $enabled) {
+ $this->getElement($feature)->setChecked($instanceStatus->{$feature});
+ }
+
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ $this->assertPermission('monitoring/command/feature/instance');
+
+ $notifications = array(
+ ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS => array(
+ $this->translate('Enabling active host checks..'),
+ $this->translate('Disabling active host checks..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS => array(
+ $this->translate('Enabling active service checks..'),
+ $this->translate('Disabling active service checks..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS => array(
+ $this->translate('Enabling event handlers..'),
+ $this->translate('Disabling event handlers..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION => array(
+ $this->translate('Enabling flap detection..'),
+ $this->translate('Disabling flap detection..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS => array(
+ $this->translate('Enabling notifications..'),
+ $this->translate('Disabling notifications..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING => array(
+ $this->translate('Enabling obsessing over hosts..'),
+ $this->translate('Disabling obsessing over hosts..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_SERVICE_OBSESSING => array(
+ $this->translate('Enabling obsessing over services..'),
+ $this->translate('Disabling obsessing over services..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS => array(
+ $this->translate('Enabling passive host checks..'),
+ $this->translate('Disabling passive host checks..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS => array(
+ $this->translate('Enabling passive service checks..'),
+ $this->translate('Disabling passive service checks..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA => array(
+ $this->translate('Enabling performance data..'),
+ $this->translate('Disabling performance data..')
+ )
+ );
+
+ foreach ($this->getValues() as $feature => $enabled) {
+ if ((bool) $this->status->{$feature} !== (bool) $enabled) {
+ $toggleFeature = new ToggleInstanceFeatureCommand();
+ $toggleFeature
+ ->setFeature($feature)
+ ->setEnabled($enabled);
+ $this->getTransport($this->request)->send($toggleFeature);
+
+ Notification::success(
+ $notifications[$feature][$enabled ? 0 : 1]
+ );
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php b/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php
new file mode 100644
index 0000000..c7caf5d
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php
@@ -0,0 +1,172 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use DateTime;
+use DateInterval;
+use Icinga\Application\Config;
+use Icinga\Module\Monitoring\Command\Object\AcknowledgeProblemCommand;
+use Icinga\Web\Notification;
+
+/**
+ * Form for acknowledging host or service problems
+ */
+class AcknowledgeProblemCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->addDescription($this->translate(
+ 'This command is used to acknowledge host or service problems. When a problem is acknowledged,'
+ . ' future notifications about problems are temporarily disabled until the host or service'
+ . ' recovers.'
+ ));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation.
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Acknowledge problem', 'Acknowledge problems', count($this->objects));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $config = Config::module('monitoring');
+
+ $acknowledgeExpire = (bool) $config->get('settings', 'acknowledge_expire', false);
+
+ $this->addElements(array(
+ array(
+ 'textarea',
+ 'comment',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Comment'),
+ 'description' => $this->translate(
+ 'If you work with other administrators, you may find it useful to share information about'
+ . ' the host or service that is having problems. Make sure you enter a brief description of'
+ . ' what you are doing.'
+ ),
+ 'attribs' => array('class' => 'autofocus')
+ )
+ ),
+ array(
+ 'checkbox',
+ 'persistent',
+ array(
+ 'label' => $this->translate('Persistent Comment'),
+ 'value' => (bool) $config->get('settings', 'acknowledge_persistent', false),
+ 'description' => $this->translate(
+ 'If you would like the comment to remain even when the acknowledgement is removed, check this'
+ . ' option.'
+ )
+ )
+ ),
+ array(
+ 'checkbox',
+ 'expire',
+ array(
+ 'label' => $this->translate('Use Expire Time'),
+ 'value' => $acknowledgeExpire,
+ 'description' => $this->translate(
+ 'If the acknowledgement should expire, check this option.'
+ ),
+ 'autosubmit' => true
+ )
+ )
+ ));
+ $expire = isset($formData['expire']) ? $formData['expire'] : $acknowledgeExpire;
+ if ($expire) {
+ $expireTime = new DateTime();
+ $expireTime->add(new DateInterval($config->get('settings', 'acknowledge_expire_time', 'PT1H')));
+ $this->addElement(
+ 'dateTimePicker',
+ 'expire_time',
+ array(
+ 'label' => $this->translate('Expire Time'),
+ 'value' => $expireTime,
+ 'description' => $this->translate(
+ 'Enter the expire date and time for this acknowledgement here. Icinga will delete the'
+ . ' acknowledgement after this time expired.'
+ )
+ )
+ );
+ $this->addDisplayGroup(
+ array('expire', 'expire_time'),
+ 'expire-expire_time',
+ array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div'))
+ )
+ )
+ );
+ }
+ $this->addElements(array(
+ array(
+ 'checkbox',
+ 'sticky',
+ array(
+ 'label' => $this->translate('Sticky Acknowledgement'),
+ 'value' => (bool) $config->get('settings', 'acknowledge_sticky', false),
+ 'description' => $this->translate(
+ 'If you want the acknowledgement to remain until the host or service recovers even if the host'
+ . ' or service changes state, check this option.'
+ )
+ )
+ ),
+ array(
+ 'checkbox',
+ 'notify',
+ array(
+ 'label' => $this->translate('Send Notification'),
+ 'value' => (bool) $config->get('settings', 'acknowledge_notify', true),
+ 'description' => $this->translate(
+ 'If you do not want an acknowledgement notification to be sent out to the appropriate contacts,'
+ . ' uncheck this option.'
+ )
+ )
+ )
+ ));
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ $ack = new AcknowledgeProblemCommand();
+ $ack
+ ->setObject($object)
+ ->setComment($this->getElement('comment')->getValue())
+ ->setAuthor($this->request->getUser()->getUsername())
+ ->setPersistent($this->getElement('persistent')->isChecked())
+ ->setSticky($this->getElement('sticky')->isChecked())
+ ->setNotify($this->getElement('notify')->isChecked());
+ if ($this->getElement('expire')->isChecked()) {
+ $ack->setExpireTime($this->getElement('expire_time')->getValue()->getTimestamp());
+ }
+ $this->getTransport($this->request)->send($ack);
+ }
+ Notification::success($this->translatePlural(
+ 'Acknowledging problem..',
+ 'Acknowledging problems..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php b/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php
new file mode 100644
index 0000000..72133a0
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php
@@ -0,0 +1,148 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use DateInterval;
+use DateTime;
+use Icinga\Application\Config;
+use Icinga\Module\Monitoring\Command\Object\AddCommentCommand;
+use Icinga\Web\Notification;
+
+/**
+ * Form for adding host or service comments
+ */
+class AddCommentCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->addDescription($this->translate('This command is used to add host or service comments.'));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation.
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Add comment', 'Add comments', count($this->objects));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElement(
+ 'textarea',
+ 'comment',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Comment'),
+ 'description' => $this->translate(
+ 'If you work with other administrators, you may find it useful to share information about'
+ . ' the host or service that is having problems. Make sure you enter a brief description of'
+ . ' what you are doing.'
+ ),
+ 'attribs' => array('class' => 'autofocus')
+ )
+ );
+ if (! $this->getBackend()->isIcinga2()) {
+ $this->addElement(
+ 'checkbox',
+ 'persistent',
+ array(
+ 'label' => $this->translate('Persistent'),
+ 'value' => (bool) Config::module('monitoring')->get('settings', 'comment_persistent', true),
+ 'description' => $this->translate(
+ 'If you uncheck this option, the comment will automatically be deleted the next time Icinga is'
+ . ' restarted.'
+ )
+ )
+ );
+ }
+
+ if (version_compare($this->getBackend()->getProgramVersion(), '2.13.0', '>=')) {
+ $config = Config::module('monitoring');
+ $commentExpire = (bool) $config->get('settings', 'comment_expire', false);
+
+ $this->addElement(
+ 'checkbox',
+ 'expire',
+ [
+ 'label' => $this->translate('Use Expire Time'),
+ 'value' => $commentExpire,
+ 'description' => $this->translate('If the comment should expire, check this option.'),
+ 'autosubmit' => true
+ ]
+ );
+
+ if (isset($formData['expire']) ? $formData['expire'] : $commentExpire) {
+ $expireTime = new DateTime();
+ $expireTime->add(new DateInterval($config->get('settings', 'comment_expire_time', 'PT1H')));
+
+ $this->addElement(
+ 'dateTimePicker',
+ 'expire_time',
+ [
+ 'label' => $this->translate('Expire Time'),
+ 'value' => $expireTime,
+ 'description' => $this->translate(
+ 'Enter the expire date and time for this comment here. Icinga will delete the'
+ . ' comment after this time expired.'
+ )
+ ]
+ );
+
+ $this->addDisplayGroup(
+ ['expire', 'expire_time'],
+ 'expire-expire_time',
+ [
+ 'decorators' => [
+ 'FormElements',
+ ['HtmlTag', ['tag' => 'div']]
+ ]
+ ]
+ );
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ $comment = new AddCommentCommand();
+ $comment->setObject($object);
+ $comment->setComment($this->getElement('comment')->getValue());
+ $comment->setAuthor($this->request->getUser()->getUsername());
+ if (($persistent = $this->getElement('persistent')) !== null) {
+ $comment->setPersistent($persistent->isChecked());
+ }
+
+ $expire = $this->getElement('expire');
+
+ if ($expire !== null && $expire->isChecked()) {
+ $comment->setExpireTime($this->getElement('expire_time')->getValue()->getTimestamp());
+ }
+
+ $this->getTransport($this->request)->send($comment);
+ }
+ Notification::success($this->translatePlural(
+ 'Adding comment..',
+ 'Adding comments..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php b/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php
new file mode 100644
index 0000000..a586d2f
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php
@@ -0,0 +1,87 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\ScheduleHostCheckCommand;
+use Icinga\Module\Monitoring\Command\Object\ScheduleServiceCheckCommand;
+use Icinga\Web\Notification;
+
+/**
+ * Form for immediately checking hosts or services
+ */
+class CheckNowCommandForm extends ObjectsCommandForm
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Zend_Form::init() For the method documentation.
+ */
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ $this->setSubmitLabel($this->translate('Check now'));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::addSubmitButton() For the method documentation.
+ */
+ public function addSubmitButton()
+ {
+ $this->addElements(array(
+ array(
+ 'button',
+ 'btn_submit',
+ array(
+ 'class' => 'link-button spinner',
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ ),
+ 'escape' => false,
+ 'ignore' => true,
+ 'label' => $this->getView()->icon('arrows-cw') . $this->translate('Check now'),
+ 'type' => 'submit',
+ 'title' => $this->translate('Schedule the next active check to run immediately'),
+ 'value' => $this->translate('Check now')
+ )
+ )
+ ));
+
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ if (! $object->active_checks_enabled
+ && ! $this->Auth()->hasPermission('monitoring/command/schedule-check')
+ ) {
+ continue;
+ }
+
+ if ($object->getType() === $object::TYPE_HOST) {
+ $check = new ScheduleHostCheckCommand();
+ } else {
+ $check = new ScheduleServiceCheckCommand();
+ }
+ $check
+ ->setObject($object)
+ ->setForced()
+ ->setCheckTime(time());
+ $this->getTransport($this->request)->send($check);
+ }
+ Notification::success(mtp(
+ 'monitoring',
+ 'Scheduling check..',
+ 'Scheduling checks..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php
new file mode 100644
index 0000000..cd15b19
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php
@@ -0,0 +1,109 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\DeleteCommentCommand;
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Web\Notification;
+
+/**
+ * Form for deleting host or service comments
+ */
+class DeleteCommentCommandForm extends CommandForm
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubmitButton()
+ {
+ $this->addElement(
+ 'button',
+ 'btn_submit',
+ array(
+ 'class' => 'link-button spinner',
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ ),
+ 'escape' => false,
+ 'ignore' => true,
+ 'label' => $this->getView()->icon('cancel'),
+ 'title' => $this->translate('Delete this comment'),
+ 'type' => 'submit'
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElements(
+ array(
+ array(
+ 'hidden',
+ 'comment_id',
+ array(
+ 'required' => true,
+ 'validators' => array('NotEmpty'),
+ 'decorators' => array('ViewHelper')
+ )
+ ),
+ array(
+ 'hidden',
+ 'comment_is_service',
+ array(
+ 'filters' => array('Boolean'),
+ 'decorators' => array('ViewHelper')
+ )
+ ),
+ array(
+ 'hidden',
+ 'comment_name',
+ array(
+ 'decorators' => array('ViewHelper')
+ )
+ ),
+ array(
+ 'hidden',
+ 'redirect',
+ array(
+ 'decorators' => array('ViewHelper')
+ )
+ )
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ $cmd = new DeleteCommentCommand();
+ $cmd
+ ->setAuthor($this->Auth()->getUser()->getUsername())
+ ->setCommentId($this->getElement('comment_id')->getValue())
+ ->setCommentName($this->getElement('comment_name')->getValue())
+ ->setIsService($this->getElement('comment_is_service')->getValue());
+ $this->getTransport($this->request)->send($cmd);
+ $redirect = $this->getElement('redirect')->getValue();
+ if (! empty($redirect)) {
+ $this->setRedirectUrl($redirect);
+ }
+ Notification::success($this->translate('Deleting comment..'));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php
new file mode 100644
index 0000000..70ea7b8
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php
@@ -0,0 +1,89 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\DeleteCommentCommand;
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Web\Notification;
+
+/**
+ * Form for deleting host or service comments
+ */
+class DeleteCommentsCommandForm extends CommandForm
+{
+ /**
+ * The comments to delete
+ *
+ * @var array
+ */
+ protected $comments;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ }
+
+ /**
+ * Set the comments to delete
+ *
+ * @param iterable $comments
+ *
+ * @return $this
+ */
+ public function setComments($comments)
+ {
+ $this->comments = $comments;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElements(array(
+ array(
+ 'hidden',
+ 'redirect',
+ array('decorators' => array('ViewHelper'))
+ )
+ ));
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Remove', 'Remove All', count($this->comments));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ foreach ($this->comments as $comment) {
+ $cmd = new DeleteCommentCommand();
+ $cmd
+ ->setCommentId($comment->id)
+ ->setCommentName($comment->name)
+ ->setAuthor($this->Auth()->getUser()->getUsername())
+ ->setIsService(isset($comment->service_description));
+ $this->getTransport($this->request)->send($cmd);
+ }
+ $redirect = $this->getElement('redirect')->getValue();
+ if (! empty($redirect)) {
+ $this->setRedirectUrl($redirect);
+ }
+ Notification::success(
+ $this->translatePlural('Deleting comment..', 'Deleting comments..', count($this->comments))
+ );
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php
new file mode 100644
index 0000000..79700cb
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php
@@ -0,0 +1,129 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\DeleteDowntimeCommand;
+use Icinga\Module\Monitoring\Exception\CommandTransportException;
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Web\Notification;
+
+/**
+ * Form for deleting host or service downtimes
+ */
+class DeleteDowntimeCommandForm extends CommandForm
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubmitButton()
+ {
+ $this->addElement(
+ 'button',
+ 'btn_submit',
+ array(
+ 'class' => 'link-button spinner',
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ ),
+ 'escape' => false,
+ 'ignore' => true,
+ 'label' => $this->getView()->icon('cancel'),
+ 'title' => $this->translate('Delete this downtime'),
+ 'type' => 'submit'
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElements(
+ array(
+ array(
+ 'hidden',
+ 'downtime_id',
+ array(
+ 'decorators' => array('ViewHelper'),
+ 'required' => true,
+ 'validators' => array('NotEmpty')
+ )
+ ),
+ array(
+ 'hidden',
+ 'downtime_is_service',
+ array(
+ 'decorators' => array('ViewHelper'),
+ 'filters' => array('Boolean')
+ )
+ ),
+ array(
+ 'hidden',
+ 'downtime_name',
+ array(
+ 'decorators' => array('ViewHelper')
+ )
+ ),
+ array(
+ 'hidden',
+ 'redirect',
+ array(
+ 'decorators' => array('ViewHelper')
+ )
+ )
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ $cmd = new DeleteDowntimeCommand();
+ $cmd
+ ->setAuthor($this->Auth()->getUser()->getUsername())
+ ->setDowntimeId($this->getElement('downtime_id')->getValue())
+ ->setDowntimeName($this->getElement('downtime_name')->getValue())
+ ->setIsService($this->getElement('downtime_is_service')->getValue());
+
+ $errorMsg = null;
+
+ try {
+ $this->getTransport($this->request)->send($cmd);
+ } catch (CommandTransportException $e) {
+ $errorMsg = $e->getMessage();
+ }
+
+ if (! $errorMsg) {
+ $redirect = $this->getElement('redirect')->getValue();
+ Notification::success($this->translate('Deleting downtime.'));
+ } else {
+ if (! $this->getIsApiTarget()) {
+ $redirect = $this->getRequest()->getUrl();
+ }
+
+ Notification::error($errorMsg);
+ }
+
+ if (! empty($redirect)) {
+ $this->setRedirectUrl($redirect);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php
new file mode 100644
index 0000000..d4ee803
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php
@@ -0,0 +1,89 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\DeleteDowntimeCommand;
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Web\Notification;
+
+/**
+ * Form for deleting host or service downtimes
+ */
+class DeleteDowntimesCommandForm extends CommandForm
+{
+ /**
+ * The downtimes to delete
+ *
+ * @var array
+ */
+ protected $downtimes;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ }
+
+ /**
+ * Set the downtimes to delete
+ *
+ * @param iterable $downtimes
+ *
+ * @return $this
+ */
+ public function setDowntimes($downtimes)
+ {
+ $this->downtimes = $downtimes;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElements(array(
+ array(
+ 'hidden',
+ 'redirect',
+ array('decorators' => array('ViewHelper'))
+ )
+ ));
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Remove', 'Remove All', count($this->downtimes));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ foreach ($this->downtimes as $downtime) {
+ $delDowntime = new DeleteDowntimeCommand();
+ $delDowntime
+ ->setDowntimeId($downtime->id)
+ ->setDowntimeName($downtime->name)
+ ->setAuthor($this->Auth()->getUser()->getUsername())
+ ->setIsService(isset($downtime->service_description));
+ $this->getTransport($this->request)->send($delDowntime);
+ }
+ $redirect = $this->getElement('redirect')->getValue();
+ if (! empty($redirect)) {
+ $this->setRedirectUrl($redirect);
+ }
+ Notification::success(
+ $this->translatePlural('Deleting downtime..', 'Deleting downtimes..', count($this->downtimes))
+ );
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ObjectsCommandForm.php b/modules/monitoring/application/forms/Command/Object/ObjectsCommandForm.php
new file mode 100644
index 0000000..928c365
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ObjectsCommandForm.php
@@ -0,0 +1,47 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+
+/**
+ * Base class for Icinga object command forms
+ */
+abstract class ObjectsCommandForm extends CommandForm
+{
+ /**
+ * Involved Icinga objects
+ *
+ * @var array|\Traversable|\ArrayAccess
+ */
+ protected $objects;
+
+ /**
+ * Set the involved Icinga objects
+ *
+ * @param $objects MonitoredObject|array|\Traversable|\ArrayAccess
+ *
+ * @return $this
+ */
+ public function setObjects($objects)
+ {
+ if ($objects instanceof MonitoredObject) {
+ $this->objects = array($objects);
+ } else {
+ $this->objects = $objects;
+ }
+ return $this;
+ }
+
+ /**
+ * Get the involved Icinga objects
+ *
+ * @return array|\ArrayAccess|\Traversable
+ */
+ public function getObjects()
+ {
+ return $this->objects;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php b/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php
new file mode 100644
index 0000000..ab46071
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php
@@ -0,0 +1,139 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Web\Notification;
+use Icinga\Module\Monitoring\Command\Object\ProcessCheckResultCommand;
+
+/**
+ * Form for submitting a passive host or service check result
+ */
+class ProcessCheckResultCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->addDescription($this->translate(
+ 'This command is used to submit passive host or service check results.'
+ ));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation.
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural(
+ 'Submit Passive Check Result',
+ 'Submit Passive Check Results',
+ count($this->objects)
+ );
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData)
+ {
+ foreach ($this->getObjects() as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ // Nasty, but as getObjects() returns everything but an object with a real
+ // iterator interface this is the only way to fetch just the first element
+ break;
+ }
+
+ $this->addElement(
+ 'select',
+ 'status',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Status'),
+ 'description' => $this->translate('The state this check result should report'),
+ 'multiOptions' => $object->getType() === $object::TYPE_HOST ? $this->getHostMultiOptions() : array(
+ ProcessCheckResultCommand::SERVICE_OK => $this->translate('OK', 'icinga.state'),
+ ProcessCheckResultCommand::SERVICE_WARNING => $this->translate('WARNING', 'icinga.state'),
+ ProcessCheckResultCommand::SERVICE_CRITICAL => $this->translate('CRITICAL', 'icinga.state'),
+ ProcessCheckResultCommand::SERVICE_UNKNOWN => $this->translate('UNKNOWN', 'icinga.state')
+ )
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'output',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Output'),
+ 'description' => $this->translate('The plugin output of this check result')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'perfdata',
+ array(
+ 'allowEmpty' => true,
+ 'label' => $this->translate('Performance Data'),
+ 'description' => $this->translate(
+ 'The performance data of this check result. Leave empty'
+ . ' if this check result has no performance data'
+ )
+ )
+ );
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ if (! $object->passive_checks_enabled) {
+ continue;
+ }
+
+ $command = new ProcessCheckResultCommand();
+ $command->setObject($object);
+ $command->setStatus($this->getValue('status'));
+ $command->setOutput($this->getValue('output'));
+
+ if ($perfdata = $this->getValue('perfdata')) {
+ $command->setPerformanceData($perfdata);
+ }
+
+ $this->getTransport($this->request)->send($command);
+ }
+
+ Notification::success($this->translatePlural(
+ 'Processing check result..',
+ 'Processing check results..',
+ count($this->objects)
+ ));
+
+ return true;
+ }
+
+ /**
+ * Returns the available host options based on the program version
+ *
+ * @return array
+ */
+ protected function getHostMultiOptions()
+ {
+ $options = array(
+ ProcessCheckResultCommand::HOST_UP => $this->translate('UP', 'icinga.state'),
+ ProcessCheckResultCommand::HOST_DOWN => $this->translate('DOWN', 'icinga.state')
+ );
+
+ if (! $this->getBackend()->isIcinga2()) {
+ $options[ProcessCheckResultCommand::HOST_UNREACHABLE] = $this->translate('UNREACHABLE', 'icinga.state');
+ }
+
+ return $options;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php b/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php
new file mode 100644
index 0000000..e45a055
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php
@@ -0,0 +1,122 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\RemoveAcknowledgementCommand;
+use Icinga\Web\Notification;
+
+/**
+ * Form for removing host or service problem acknowledgements
+ */
+class RemoveAcknowledgementCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Whether to show the submit label next to the remove icon
+ *
+ * The submit label is disabled in detail views but should be enabled in multi-select views.
+ *
+ * @var bool
+ */
+ protected $labelEnabled = false;
+
+ /**
+ * Whether to show the submit label next to the remove icon
+ *
+ * @return bool
+ */
+ public function isLabelEnabled()
+ {
+ return $this->labelEnabled;
+ }
+
+ /**
+ * Set whether to show the submit label next to the remove icon
+ *
+ * @param bool $labelEnabled
+ *
+ * @return $this
+ */
+ public function setLabelEnabled($labelEnabled)
+ {
+ $this->labelEnabled = (bool) $labelEnabled;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubmitButton()
+ {
+ $this->addElement(
+ 'button',
+ 'btn_submit',
+ array(
+ 'class' => 'link-button spinner',
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ ),
+ 'escape' => false,
+ 'ignore' => true,
+ 'label' => $this->getSubmitLabel(),
+ 'title' => $this->translatePlural(
+ 'Remove acknowledgement',
+ 'Remove acknowledgements',
+ count($this->objects)
+ ),
+ 'type' => 'submit'
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSubmitLabel()
+ {
+ $label = $this->getView()->icon('cancel');
+ if ($this->isLabelEnabled()) {
+ $label .= $this->translatePlural(
+ 'Remove acknowledgement',
+ 'Remove acknowledgements',
+ count($this->objects)
+ );
+ }
+
+ return $label;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ $removeAck = new RemoveAcknowledgementCommand();
+ $removeAck->setObject($object);
+ $removeAck->setAuthor($this->Auth()->getUser()->getUsername());
+ $this->getTransport($this->request)->send($removeAck);
+ }
+ Notification::success(mtp(
+ 'monitoring',
+ 'Removing acknowledgement..',
+ 'Removing acknowledgements..',
+ count($this->objects)
+ ));
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php b/modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php
new file mode 100644
index 0000000..55b044f
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php
@@ -0,0 +1,67 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Application\Config;
+use Icinga\Module\Monitoring\Command\Object\ScheduleHostCheckCommand;
+use Icinga\Web\Notification;
+
+/**
+ * Form for scheduling host checks
+ */
+class ScheduleHostCheckCommandForm extends ScheduleServiceCheckCommandForm
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $config = Config::module('monitoring');
+
+ parent::createElements($formData);
+ $this->addElements(array(
+ array(
+ 'checkbox',
+ 'all_services',
+ array(
+ 'label' => $this->translate('All Services'),
+ 'value' => (bool) $config->get('settings', 'hostcheck_all_services', false),
+ 'description' => $this->translate(
+ 'Schedule check for all services on the hosts and the hosts themselves.'
+ )
+ )
+ )
+ ));
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ if (! $object->active_checks_enabled
+ && ! $this->Auth()->hasPermission('monitoring/command/schedule-check')
+ ) {
+ continue;
+ }
+
+ $check = new ScheduleHostCheckCommand();
+ $check
+ ->setObject($object)
+ ->setOfAllServices($this->getElement('all_services')->isChecked());
+ $this->scheduleCheck($check, $this->request);
+ }
+ Notification::success($this->translatePlural(
+ 'Scheduling host check..',
+ 'Scheduling host checks..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php b/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php
new file mode 100644
index 0000000..89db1ce
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php
@@ -0,0 +1,178 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use DateInterval;
+use DateTime;
+use Icinga\Application\Config;
+use Icinga\Module\Monitoring\Command\Object\ApiScheduleHostDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\PropagateHostDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\ScheduleHostDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\ScheduleServiceDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Transport\ApiCommandTransport;
+use Icinga\Module\Monitoring\Command\Transport\CommandTransport;
+use Icinga\Web\Notification;
+
+/**
+ * Form for scheduling host downtimes
+ */
+class ScheduleHostDowntimeCommandForm extends ScheduleServiceDowntimeCommandForm
+{
+ /** @var bool */
+ protected $hostDowntimeAllServices;
+
+ public function init()
+ {
+ $this->start = new DateTime();
+ $config = Config::module('monitoring');
+ $this->commentText = $config->get('settings', 'hostdowntime_comment_text');
+
+ $this->hostDowntimeAllServices = (bool) $config->get('settings', 'hostdowntime_all_services', false);
+
+ $fixedEnd = clone $this->start;
+ $fixed = $config->get('settings', 'hostdowntime_end_fixed', 'PT1H');
+ $this->fixedEnd = $fixedEnd->add(new DateInterval($fixed));
+
+ $flexibleEnd = clone $this->start;
+ $flexible = $config->get('settings', 'hostdowntime_end_flexible', 'PT1H');
+ $this->flexibleEnd = $flexibleEnd->add(new DateInterval($flexible));
+
+ $flexibleDuration = $config->get('settings', 'hostdowntime_flexible_duration', 'PT2H');
+ $this->flexibleDuration = new DateInterval($flexibleDuration);
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ parent::createElements($formData);
+
+ $this->addElement(
+ 'checkbox',
+ 'all_services',
+ array(
+ 'description' => $this->translate(
+ 'Schedule downtime for all services on the hosts and the hosts themselves.'
+ ),
+ 'label' => $this->translate('All Services'),
+ 'value' => $this->hostDowntimeAllServices
+ )
+ );
+
+ if (! $this->getBackend()->isIcinga2()
+ || version_compare($this->getBackend()->getProgramVersion(), '2.6.0', '>=')
+ ) {
+ $this->addElement(
+ 'select',
+ 'child_hosts',
+ array(
+ 'description' => $this->translate(
+ 'Define what should be done with the child hosts of the hosts.'
+ ),
+ 'label' => $this->translate('Child Hosts'),
+ 'multiOptions' => array(
+ 0 => $this->translate('Do nothing with child hosts'),
+ 1 => $this->translate('Schedule triggered downtime for all child hosts'),
+ 2 => $this->translate('Schedule non-triggered downtime for all child hosts')
+ ),
+ 'value' => 0
+ )
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ $end = $this->getValue('end')->getTimestamp();
+ if ($end <= $this->getValue('start')->getTimestamp()) {
+ $endElement = $this->_elements['end'];
+ $endElement->setValue($endElement->getValue()->format($endElement->getFormat()));
+ $endElement->addError($this->translate('The end time must be greater than the start time'));
+ return false;
+ }
+
+ $now = new DateTime;
+ if ($end <= $now->getTimestamp()) {
+ $endElement = $this->_elements['end'];
+ $endElement->setValue($endElement->getValue()->format($endElement->getFormat()));
+ $endElement->addError($this->translate('A downtime must not be in the past'));
+ return false;
+ }
+
+ // Send all_services API parameter if Icinga is equal to or greater than 2.11.0
+ $allServicesNative = version_compare($this->getBackend()->getProgramVersion(), '2.11.0', '>=');
+ // Use ApiScheduleHostDowntimeCommand only when Icinga is equal to or greater than 2.11.0 and
+ // when an API command transport is requested or only API command transports are configured:
+ $useApiDowntime = $allServicesNative;
+ if ($useApiDowntime) {
+ $transport = $this->getTransport($this->getRequest());
+ if ($transport instanceof CommandTransport) {
+ foreach ($transport::getConfig() as $config) {
+ if (strtolower($config->transport) !== 'api') {
+ $useApiDowntime = false;
+ break;
+ }
+ }
+ } elseif (! $transport instanceof ApiCommandTransport) {
+ $useApiDowntime = false;
+ }
+ }
+
+ foreach ($this->objects as $object) {
+ if ($useApiDowntime) {
+ $hostDowntime = (new ApiScheduleHostDowntimeCommand())
+ ->setForAllServices($this->getElement('all_services')->isChecked())
+ ->setChildOptions((int) $this->getElement('child_hosts')->getValue());
+ // Code duplicated for readability and scope
+ $hostDowntime->setObject($object);
+ $this->scheduleDowntime($hostDowntime, $this->request);
+
+ continue;
+ }
+
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ if (($childHostsEl = $this->getElement('child_hosts')) !== null) {
+ $childHosts = (int) $childHostsEl->getValue();
+ } else {
+ $childHosts = 0;
+ }
+ $allServices = $this->getElement('all_services')->isChecked();
+ if ($childHosts === 0) {
+ $hostDowntime = (new ScheduleHostDowntimeCommand())
+ ->setForAllServicesNative($allServicesNative);
+ if ($allServices === true) {
+ $hostDowntime->setForAllServices();
+ };
+ } else {
+ $hostDowntime = new PropagateHostDowntimeCommand();
+ if ($childHosts === 1) {
+ $hostDowntime->setTriggered();
+ }
+ if ($allServices === true) {
+ foreach ($object->services as $service) {
+ $serviceDowntime = new ScheduleServiceDowntimeCommand();
+ $serviceDowntime->setObject($service);
+ $this->scheduleDowntime($serviceDowntime, $this->request);
+ }
+ }
+ }
+ $hostDowntime->setObject($object);
+ $this->scheduleDowntime($hostDowntime, $this->request);
+ }
+ Notification::success($this->translatePlural(
+ 'Scheduling host downtime..',
+ 'Scheduling host downtimes..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php b/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php
new file mode 100644
index 0000000..f65aea8
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php
@@ -0,0 +1,112 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use DateTime;
+use DateInterval;
+use Icinga\Module\Monitoring\Command\Object\ScheduleServiceCheckCommand;
+use Icinga\Web\Notification;
+use Icinga\Web\Request;
+
+/**
+ * Form for scheduling service checks
+ */
+class ScheduleServiceCheckCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->addDescription($this->translate(
+ 'This command is used to schedule the next check of hosts or services. Icinga will re-queue the'
+ . ' hosts or services to be checked at the time you specify.'
+ ));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation.
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Schedule check', 'Schedule checks', count($this->objects));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $checkTime = new DateTime();
+ $checkTime->add(new DateInterval('PT1H'));
+ $this->addElements(array(
+ array(
+ 'dateTimePicker',
+ 'check_time',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Check Time'),
+ 'description' => $this->translate(
+ 'Set the date and time when the check should be scheduled.'
+ ),
+ 'value' => $checkTime
+ )
+ ),
+ array(
+ 'checkbox',
+ 'force_check',
+ array(
+ 'label' => $this->translate('Force Check'),
+ 'description' => $this->translate(
+ 'If you select this option, Icinga will force a check regardless of both what time the'
+ . ' scheduled check occurs and whether or not checks are enabled.'
+ )
+ )
+ )
+ ));
+ return $this;
+ }
+
+ /**
+ * Schedule a check
+ *
+ * @param ScheduleServiceCheckCommand $check
+ * @param Request $request
+ */
+ public function scheduleCheck(ScheduleServiceCheckCommand $check, Request $request)
+ {
+ $check
+ ->setForced($this->getElement('force_check')->isChecked())
+ ->setCheckTime($this->getElement('check_time')->getValue()->getTimestamp());
+ $this->getTransport($request)->send($check);
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ if (! $object->active_checks_enabled
+ && ! $this->Auth()->hasPermission('monitoring/command/schedule-check')
+ ) {
+ continue;
+ }
+
+ $check = new ScheduleServiceCheckCommand();
+ $check->setObject($object);
+ $this->scheduleCheck($check, $this->request);
+ }
+ Notification::success($this->translatePlural(
+ 'Scheduling service check..',
+ 'Scheduling service checks..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php b/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php
new file mode 100644
index 0000000..90d50d4
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php
@@ -0,0 +1,263 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use DateTime;
+use DateInterval;
+use Icinga\Application\Config;
+use Icinga\Module\Monitoring\Command\Object\ScheduleServiceDowntimeCommand;
+use Icinga\Web\Notification;
+use Icinga\Web\Request;
+
+/**
+ * Form for scheduling service downtimes
+ */
+class ScheduleServiceDowntimeCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Fixed downtime
+ */
+ const FIXED = 'fixed';
+
+ /**
+ * Flexible downtime
+ */
+ const FLEXIBLE = 'flexible';
+
+ /** @var DateTime downtime start */
+ protected $start;
+
+ /** @var DateTime fixed downtime end */
+ protected $fixedEnd;
+
+ /** @var DateTime flexible downtime end */
+ protected $flexibleEnd;
+
+ /** @var DateInterval flexible downtime duration */
+ protected $flexibleDuration;
+
+ /** @var mixed Comment text */
+ protected $commentText;
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->start = new DateTime();
+
+ $config = Config::module('monitoring');
+
+ $this->commentText = $config->get('settings', 'servicedowntime_comment_text');
+ $fixedEnd = clone $this->start;
+ $fixed = $config->get('settings', 'servicedowntime_end_fixed', 'PT1H');
+ $this->fixedEnd = $fixedEnd->add(new DateInterval($fixed));
+
+ $flexibleEnd = clone $this->start;
+ $flexible = $config->get('settings', 'servicedowntime_end_flexible', 'PT1H');
+ $this->flexibleEnd = $flexibleEnd->add(new DateInterval($flexible));
+
+ $flexibleDuration = $config->get('settings', 'servicedowntime_flexible_duration', 'PT2H');
+ $this->flexibleDuration = new DateInterval($flexibleDuration);
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation.
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Schedule downtime', 'Schedule downtimes', count($this->objects));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addDescription($this->translate(
+ 'This command is used to schedule host and service downtimes. During the specified downtime,'
+ . ' Icinga will not send notifications out about the hosts and services. When the scheduled'
+ . ' downtime expires, Icinga will send out notifications for the hosts and services as it'
+ . ' normally would. Scheduled downtimes are preserved across program shutdowns and'
+ . ' restarts.'
+ ));
+
+ $isFlexible = (bool) isset($formData['type']) && $formData['type'] === self::FLEXIBLE;
+
+ $this->addElements(array(
+ array(
+ 'textarea',
+ 'comment',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Comment'),
+ 'description' => $this->translate(
+ 'If you work with other administrators, you may find it useful to share information about'
+ . ' the host or service that is having problems. Make sure you enter a brief description of'
+ . ' what you are doing.'
+ ),
+ 'attribs' => array('class' => 'autofocus'),
+ 'value' => $this->commentText
+ )
+ ),
+ array(
+ 'dateTimePicker',
+ 'start',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Start Time'),
+ 'description' => $this->translate('Set the start date and time for the downtime.'),
+ 'value' => $this->start
+ )
+ ),
+ array(
+ 'dateTimePicker',
+ 'end',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('End Time'),
+ 'description' => $this->translate('Set the end date and time for the downtime.'),
+ 'preserveDefault' => true,
+ 'value' => $isFlexible ? $this->flexibleEnd : $this->fixedEnd
+ )
+ ),
+ array(
+ 'select',
+ 'type',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Type'),
+ 'description' => $this->translate(
+ 'If you select the fixed option, the downtime will be in effect between the start and end'
+ . ' times you specify whereas a flexible downtime starts when the host or service enters a'
+ . ' problem state sometime between the start and end times you specified and lasts as long'
+ . ' as the duration time you enter. The duration fields do not apply for fixed downtimes.'
+ ),
+ 'multiOptions' => array(
+ self::FIXED => $this->translate('Fixed'),
+ self::FLEXIBLE => $this->translate('Flexible')
+ ),
+ 'validators' => array(
+ array(
+ 'InArray',
+ true,
+ array(array(self::FIXED, self::FLEXIBLE))
+ )
+ )
+ )
+ )
+ ));
+ $this->addDisplayGroup(
+ array('start', 'end'),
+ 'start-end',
+ array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div'))
+ )
+ )
+ );
+ if ($isFlexible) {
+ $this->addElements(array(
+ array(
+ 'number',
+ 'hours',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Hours'),
+ 'value' => $this->flexibleDuration->h,
+ 'min' => -1
+ )
+ ),
+ array(
+ 'number',
+ 'minutes',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Minutes'),
+ 'value' => $this->flexibleDuration->m,
+ 'min' => -1
+ )
+ )
+ ));
+ $this->addDisplayGroup(
+ array('hours', 'minutes'),
+ 'duration',
+ array(
+ 'legend' => $this->translate('Flexible Duration'),
+ 'description' => $this->translate(
+ 'Enter here the duration of the downtime. The downtime will be automatically deleted after this'
+ . ' time expired.'
+ ),
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div')),
+ array(
+ 'Description',
+ array('tag' => 'span', 'class' => 'description', 'placement' => 'prepend')
+ ),
+ 'Fieldset'
+ )
+ )
+ );
+ }
+ return $this;
+ }
+
+ public function scheduleDowntime(ScheduleServiceDowntimeCommand $downtime, Request $request)
+ {
+ $downtime
+ ->setComment($this->getElement('comment')->getValue())
+ ->setAuthor($request->getUser()->getUsername())
+ ->setStart($this->getElement('start')->getValue()->getTimestamp())
+ ->setEnd($this->getElement('end')->getValue()->getTimestamp());
+ if ($this->getElement('type')->getValue() === self::FLEXIBLE) {
+ $downtime->setFixed(false);
+ $downtime->setDuration(
+ (float) $this->getElement('hours')->getValue() * 3600
+ + (float) $this->getElement('minutes')->getValue() * 60
+ );
+ }
+ $this->getTransport($request)->send($downtime);
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ $end = $this->getValue('end')->getTimestamp();
+ if ($end <= $this->getValue('start')->getTimestamp()) {
+ $endElement = $this->_elements['end'];
+ $endElement->setValue($endElement->getValue()->format($endElement->getFormat()));
+ $endElement->addError($this->translate('The end time must be greater than the start time'));
+ return false;
+ }
+
+ $now = new DateTime;
+ if ($end <= $now->getTimestamp()) {
+ $endElement = $this->_elements['end'];
+ $endElement->setValue($endElement->getValue()->format($endElement->getFormat()));
+ $endElement->addError($this->translate('A downtime must not be in the past'));
+ return false;
+ }
+
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ $downtime = new ScheduleServiceDowntimeCommand();
+ $downtime->setObject($object);
+ $this->scheduleDowntime($downtime, $this->request);
+ }
+ Notification::success($this->translatePlural(
+ 'Scheduling service downtime..',
+ 'Scheduling service downtimes..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php b/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php
new file mode 100644
index 0000000..0d1c393
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php
@@ -0,0 +1,110 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Application\Config;
+use Icinga\Module\Monitoring\Command\Object\SendCustomNotificationCommand;
+use Icinga\Web\Notification;
+
+/**
+ * Form to send custom notifications
+ */
+class SendCustomNotificationCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->addDescription(
+ $this->translate('This command is used to send custom notifications about hosts or services.')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Send custom notification', 'Send custom notifications', count($this->objects));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ $config = Config::module('monitoring');
+
+ $this->addElements(array(
+ array(
+ 'textarea',
+ 'comment',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Comment'),
+ 'description' => $this->translate(
+ 'If you work with other administrators, you may find it useful to share information about'
+ . ' the host or service that is having problems. Make sure you enter a brief description of'
+ . ' what you are doing.'
+ )
+ )
+ ),
+ array(
+ 'checkbox',
+ 'forced',
+ array(
+ 'label' => $this->translate('Forced'),
+ 'value' => (bool) $config->get('settings', 'custom_notification_forced', false),
+ 'description' => $this->translate(
+ 'If you check this option, the notification is sent out regardless of time restrictions and'
+ . ' whether or not notifications are enabled.'
+ )
+ )
+ )
+ ));
+
+ if (! $this->getBackend()->isIcinga2()) {
+ $this->addElement(
+ 'checkbox',
+ 'broadcast',
+ array(
+ 'label' => $this->translate('Broadcast'),
+ 'value' => (bool) $config->get('settings', 'custom_notification_broadcast', false),
+ 'description' => $this->translate(
+ 'If you check this option, the notification is sent out to all normal and escalated contacts.'
+ )
+ )
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ $notification = new SendCustomNotificationCommand();
+ $notification
+ ->setObject($object)
+ ->setComment($this->getElement('comment')->getValue())
+ ->setAuthor($this->request->getUser()->getUsername())
+ ->setForced($this->getElement('forced')->isChecked());
+ if (($broadcast = $this->getElement('broadcast')) !== null) {
+ $notification->setBroadcast($broadcast->isChecked());
+ }
+ $this->getTransport($this->request)->send($notification);
+ }
+ Notification::success($this->translatePlural(
+ 'Sending custom notification..',
+ 'Sending custom notifications..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php b/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php
new file mode 100644
index 0000000..e4aabb2
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php
@@ -0,0 +1,187 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\ToggleObjectFeatureCommand;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+use Icinga\Web\Notification;
+
+/**
+ * Form for enabling or disabling features of Icinga objects, i.e. hosts or services
+ */
+class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Feature to feature spec map
+ *
+ * @var string[]
+ */
+ protected $features;
+
+ /**
+ * Feature to feature status map
+ *
+ * @var int[]
+ */
+ protected $featureStatus;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setUseFormAutosubmit();
+ $this->setAttrib('class', self::DEFAULT_CLASSES . ' object-features');
+ $features = array(
+ ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS => array(
+ 'label' => $this->translate('Active Checks'),
+ 'permission' => 'monitoring/command/feature/object/active-checks'
+ ),
+ ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS => array(
+ 'label' => $this->translate('Passive Checks'),
+ 'permission' => 'monitoring/command/feature/object/passive-checks'
+ ),
+ ToggleObjectFeatureCommand::FEATURE_OBSESSING => array(
+ 'label' => $this->translate('Obsessing'),
+ 'permission' => 'monitoring/command/feature/object/obsessing'
+ ),
+ ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS => array(
+ 'label' => $this->translate('Notifications'),
+ 'permission' => 'monitoring/command/feature/object/notifications'
+ ),
+ ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER => array(
+ 'label' => $this->translate('Event Handler'),
+ 'permission' => 'monitoring/command/feature/object/event-handler'
+ ),
+ ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION => array(
+ 'label' => $this->translate('Flap Detection'),
+ 'permission' => 'monitoring/command/feature/object/flap-detection'
+ )
+ );
+ if ($this->getBackend()->isIcinga2()) {
+ unset($features[ToggleObjectFeatureCommand::FEATURE_OBSESSING]);
+ }
+ $this->features = $features;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ foreach ($this->features as $feature => $spec) {
+ $options = array(
+ 'autosubmit' => true,
+ 'disabled' => $this->hasPermission($spec['permission']) ? null : 'disabled',
+ 'label' => $spec['label']
+ );
+ if ($formData[$feature . '_changed']) {
+ $options['description'] = $this->translate('changed');
+ }
+ if ($formData[$feature] === 2) {
+ $this->addElement('select', $feature, $options + [
+ 'description' => $this->translate('Multiple Values'),
+ 'filters' => [['Null', ['type' => \Zend_Filter_Null::STRING]]],
+ 'multiOptions' => [
+ '' => $this->translate('Leave Unchanged'),
+ $this->translate('Disable All'),
+ $this->translate('Enable All')
+ ],
+ 'decorators' => array_merge(
+ array_slice(static::$defaultElementDecorators, 0, 3),
+ [['Description', ['tag' => 'span']]],
+ array_slice(static::$defaultElementDecorators, 4, 1),
+ [['HtmlTag', ['tag' => 'div', 'class' => 'control-group indeterminate']]]
+ )
+ ]);
+ } else {
+ $options['value'] = $formData[$feature];
+ $this->addElement('checkbox', $feature, $options);
+ }
+ }
+ }
+
+ /**
+ * Load feature status
+ *
+ * @param MonitoredObject|object $object
+ *
+ * @return $this
+ */
+ public function load($object)
+ {
+ $featureStatus = array();
+ foreach (array_keys($this->features) as $feature) {
+ $featureStatus[$feature] = $object->{$feature};
+ if (isset($object->{$feature . '_changed'})) {
+ $featureStatus[$feature . '_changed'] = (bool) $object->{$feature . '_changed'};
+ } else {
+ $featureStatus[$feature . '_changed'] = false;
+ }
+ }
+ $this->create($featureStatus);
+ $this->featureStatus = $featureStatus;
+
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ $notifications = array(
+ ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS => array(
+ $this->translate('Enabling active checks..'),
+ $this->translate('Disabling active checks..')
+ ),
+ ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS => array(
+ $this->translate('Enabling passive checks..'),
+ $this->translate('Disabling passive checks..')
+ ),
+ ToggleObjectFeatureCommand::FEATURE_OBSESSING => array(
+ $this->translate('Enabling obsessing..'),
+ $this->translate('Disabling obsessing..')
+ ),
+ ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS => array(
+ $this->translate('Enabling notifications..'),
+ $this->translate('Disabling notifications..')
+ ),
+ ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER => array(
+ $this->translate('Enabling event handler..'),
+ $this->translate('Disabling event handler..')
+ ),
+ ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION => array(
+ $this->translate('Enabling flap detection..'),
+ $this->translate('Disabling flap detection..')
+ )
+ );
+
+ foreach ($this->getValues() as $feature => $enabled) {
+ if ($this->getElement($feature)->getAttrib('disabled') !== null
+ || $enabled === null
+ || (int) $enabled === (int) $this->featureStatus[$feature]
+ ) {
+ continue;
+ }
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ if ((bool) $object->{$feature} !== (bool) $enabled) {
+ $toggleFeature = new ToggleObjectFeatureCommand();
+ $toggleFeature
+ ->setFeature($feature)
+ ->setObject($object)
+ ->setEnabled($enabled);
+ $this->getTransport($this->request)->send($toggleFeature);
+ }
+ }
+ Notification::success(
+ $notifications[$feature][$enabled ? 0 : 1]
+ );
+ }
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/BackendConfigForm.php b/modules/monitoring/application/forms/Config/BackendConfigForm.php
new file mode 100644
index 0000000..5ed42e1
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/BackendConfigForm.php
@@ -0,0 +1,367 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config;
+
+use Exception;
+use InvalidArgumentException;
+use Icinga\Application\Config;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotFoundError;
+use Icinga\Forms\ConfigForm;
+use Icinga\Web\Form;
+
+/**
+ * Form for managing monitoring backends
+ */
+class BackendConfigForm extends ConfigForm
+{
+ /**
+ * The available monitoring backend resources split by type
+ *
+ * @var array
+ */
+ protected $resources;
+
+ /**
+ * The backend to load when displaying the form for the first time
+ *
+ * @var string
+ */
+ protected $backendToLoad;
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_monitoring_backends');
+ $this->setSubmitLabel($this->translate('Save Changes'));
+ }
+
+ /**
+ * Set the resource configuration to use
+ *
+ * @param Config $resourceConfig The resource configuration
+ *
+ * @return $this
+ *
+ * @throws ConfigurationError In case there are no valid monitoring backend resources
+ */
+ public function setResourceConfig(Config $resourceConfig)
+ {
+ $resources = array();
+ foreach ($resourceConfig as $name => $resource) {
+ if ($resource->type === 'db') {
+ $resources['ido'][$name] = $name;
+ }
+ }
+
+ if (empty($resources)) {
+ throw new ConfigurationError($this->translate(
+ 'Could not find any valid monitoring backend resources. Please configure a database resource first.'
+ ));
+ }
+
+ $this->resources = $resources;
+ return $this;
+ }
+
+ /**
+ * Populate the form with the given backend's config
+ *
+ * @param string $name
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no backend with the given name is found
+ */
+ public function load($name)
+ {
+ if (! $this->config->hasSection($name)) {
+ throw new NotFoundError('No monitoring backend called "%s" found', $name);
+ }
+
+ $this->backendToLoad = $name;
+ return $this;
+ }
+
+ /**
+ * Add a new monitoring backend
+ *
+ * The backend to add is identified by the array-key `name'.
+ *
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException In case $data does not contain a backend name
+ * @throws IcingaException In case a backend with the same name already exists
+ */
+ public function add(array $data)
+ {
+ if (! isset($data['name'])) {
+ throw new InvalidArgumentException('Key \'name\' missing');
+ }
+
+ $backendName = $data['name'];
+ if ($this->config->hasSection($backendName)) {
+ throw new IcingaException(
+ $this->translate('A monitoring backend with the name "%s" does already exist'),
+ $backendName
+ );
+ }
+
+ unset($data['name']);
+ $this->config->setSection($backendName, $data);
+ return $this;
+ }
+
+ /**
+ * Edit a monitoring backend
+ *
+ * @param string $name
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no backend with the given name is found
+ */
+ public function edit($name, array $data)
+ {
+ if (! $this->config->hasSection($name)) {
+ throw new NotFoundError('No monitoring backend called "%s" found', $name);
+ }
+
+ $backendConfig = $this->config->getSection($name);
+ if (isset($data['name'])) {
+ if ($data['name'] !== $name) {
+ $this->config->removeSection($name);
+ $name = $data['name'];
+ }
+
+ unset($data['name']);
+ }
+
+ $backendConfig->merge($data);
+ $this->config->setSection($name, $backendConfig);
+ return $this;
+ }
+
+ /**
+ * Remove a monitoring backend
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function delete($name)
+ {
+ $this->config->removeSection($name);
+ return $this;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'checkbox',
+ 'disabled',
+ array(
+ 'label' => $this->translate('Disable This Backend')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Backend Name'),
+ 'description' => $this->translate(
+ 'The name of this monitoring backend that is used to differentiate it from others'
+ )
+ )
+ );
+
+ $resourceType = isset($formData['type']) ? $formData['type'] : null;
+
+ $resourceTypes = array();
+ if ($resourceType === 'ido' || array_key_exists('ido', $this->resources)) {
+ $resourceTypes['ido'] = 'IDO Backend';
+ }
+
+ if ($resourceType === null) {
+ $resourceType = key($resourceTypes);
+ }
+
+ $this->addElement(
+ 'select',
+ 'type',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Backend Type'),
+ 'description' => $this->translate(
+ 'The type of data source used for retrieving monitoring information'
+ ),
+ 'multiOptions' => $resourceTypes
+ )
+ );
+
+ $this->addElement(
+ 'select',
+ 'resource',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Resource'),
+ 'description' => $this->translate('The resource to use'),
+ 'multiOptions' => $this->resources[$resourceType],
+ 'value' => current($this->resources[$resourceType]),
+ 'autosubmit' => true
+ )
+ );
+ $resourceName = isset($formData['resource']) ? $formData['resource'] : $this->getValue('resource');
+ $this->addElement(
+ 'note',
+ 'resource_note',
+ array(
+ 'escape' => false,
+ 'value' => sprintf(
+ '<a href="%1$s" data-base-target="_next" title="%2$s" aria-label="%2$s">%3$s</a>',
+ $this->getView()->url('config/editresource', array('resource' => $resourceName)),
+ sprintf($this->translate('Show the configuration of the %s resource'), $resourceName),
+ $this->translate('Show resource configuration')
+ )
+ )
+ );
+
+ if (isset($formData['skip_validation']) && $formData['skip_validation']) {
+ // In case another error occured and the checkbox was displayed before
+ $this->addSkipValidationCheckbox();
+ }
+ }
+
+ /**
+ * Populate the configuration of the backend to load
+ */
+ public function onRequest()
+ {
+ if ($this->backendToLoad) {
+ $data = $this->config->getSection($this->backendToLoad)->toArray();
+ $data['name'] = $this->backendToLoad;
+ $this->populate($data);
+ }
+ }
+
+ /**
+ * Return whether the given values are valid
+ *
+ * @param array $formData The data to validate
+ *
+ * @return bool
+ */
+ public function isValid($formData)
+ {
+ if (! parent::isValid($formData)) {
+ return false;
+ }
+
+ if (($el = $this->getElement('skip_validation')) === null || false === $el->isChecked()) {
+ $resourceConfig = ResourceFactory::getResourceConfig($this->getValue('resource'));
+ if (! self::isValidIdoSchema($this, $resourceConfig)
+ || (! $this->getElement('disabled')->isChecked()
+ && ! self::isValidIdoInstance($this, $resourceConfig))
+ ) {
+ if ($el === null) {
+ $this->addSkipValidationCheckbox();
+ }
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a checkbox to the form by which the user can skip the schema validation
+ */
+ protected function addSkipValidationCheckbox()
+ {
+ $this->addElement(
+ 'checkbox',
+ 'skip_validation',
+ array(
+ 'order' => 0,
+ 'ignore' => true,
+ 'label' => $this->translate('Skip Validation'),
+ 'description' => $this->translate(
+ 'Check this to not to validate the IDO schema of the chosen resource.'
+ )
+ )
+ );
+ }
+
+ /**
+ * Return whether the given resource contains a valid IDO schema
+ *
+ * @param Form $form
+ * @param ConfigObject $resourceConfig
+ *
+ * @return bool
+ */
+ public static function isValidIdoSchema(Form $form, ConfigObject $resourceConfig)
+ {
+ try {
+ $db = ResourceFactory::createResource($resourceConfig);
+ $db->select()->from('icinga_dbversion', array('version'))->fetchOne();
+ } catch (Exception $_) {
+ $form->error($form->translate(
+ 'Cannot find the IDO schema. Please verify that the given database '
+ . 'contains the schema and that the configured user has access to it.'
+ ));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Return whether a single icinga instance is writing to the given resource
+ *
+ * @param Form $form
+ * @param ConfigObject $resourceConfig
+ *
+ * @return bool True if it's a single instance, false if none
+ * or multiple instances are writing to it
+ */
+ public static function isValidIdoInstance(Form $form, ConfigObject $resourceConfig)
+ {
+ $db = ResourceFactory::createResource($resourceConfig);
+ $rowCount = $db->select()->from('icinga_instances')->count();
+
+ if ($rowCount === 0) {
+ $form->warning($form->translate(
+ 'There is currently no icinga instance writing to the IDO. Make sure '
+ . 'that a icinga instance is configured and able to write to the IDO.'
+ ));
+ return false;
+ } elseif ($rowCount > 1) {
+ $form->warning($form->translate(
+ 'There is currently more than one icinga instance writing to the IDO. You\'ll see all objects from all'
+ . ' instances without any differentation. If this is not desired, consider setting up a separate IDO'
+ . ' for each instance.'
+ ));
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/SecurityConfigForm.php b/modules/monitoring/application/forms/Config/SecurityConfigForm.php
new file mode 100644
index 0000000..d57f985
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/SecurityConfigForm.php
@@ -0,0 +1,75 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config;
+
+use Icinga\Web\Notification;
+use Icinga\Forms\ConfigForm;
+
+/**
+ * Form for modifying security relevant settings
+ */
+class SecurityConfigForm extends ConfigForm
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_monitoring_security');
+ $this->setSubmitLabel($this->translate('Save Changes'));
+ }
+
+ /**
+ * @see Form::onSuccess()
+ */
+ public function onSuccess()
+ {
+ $this->config->setSection('security', $this->getValues());
+
+ if ($this->save()) {
+ Notification::success($this->translate('New security configuration has successfully been stored'));
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @see Form::onRequest()
+ */
+ public function onRequest()
+ {
+ $this->populate($this->config->getSection('security')->toArray());
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'protected_customvars',
+ array(
+ 'allowEmpty' => true,
+ 'attribs' => array('placeholder' => $this->getDefaultProtectedCustomvars()),
+ 'label' => $this->translate('Protected Custom Variables'),
+ 'description' => $this->translate(
+ 'Comma separated case insensitive list of protected custom variables.'
+ . ' Use * as a placeholder for zero or more wildcard characters.'
+ . ' Existence of those custom variables will be shown, but their values will be masked.'
+ )
+ )
+ );
+ }
+
+ /**
+ * Return the customvars to suggest to protect when none are protected
+ *
+ * @return string
+ */
+ public function getDefaultProtectedCustomvars()
+ {
+ return '*pw*,*pass*,community';
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/Transport/ApiTransportForm.php b/modules/monitoring/application/forms/Config/Transport/ApiTransportForm.php
new file mode 100644
index 0000000..3d501e0
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/Transport/ApiTransportForm.php
@@ -0,0 +1,75 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config\Transport;
+
+use Icinga\Web\Form;
+
+class ApiTransportForm extends Form
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setName('form_config_command_transport_api');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElements(array(
+ array(
+ 'text',
+ 'host',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Host'),
+ 'description' => $this->translate(
+ 'Hostname or address of the remote Icinga instance'
+ )
+ )
+ ),
+ array(
+ 'number',
+ 'port',
+ array(
+ 'required' => true,
+ 'preserveDefault' => true,
+ 'label' => $this->translate('Port'),
+ 'description' => $this->translate('SSH port to connect to on the remote Icinga instance'),
+ 'value' => 5665
+ )
+ ),
+ array(
+ 'text',
+ 'username',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('API Username'),
+ 'description' => $this->translate(
+ 'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be'
+ . ' possible for this user'
+ )
+ )
+ ),
+ array(
+ 'password',
+ 'password',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('API Password'),
+ 'description' => $this->translate(
+ 'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be'
+ . ' possible for this user'
+ ),
+ 'renderPassword' => true
+ )
+ )
+ ));
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/Transport/LocalTransportForm.php b/modules/monitoring/application/forms/Config/Transport/LocalTransportForm.php
new file mode 100644
index 0000000..15c7357
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/Transport/LocalTransportForm.php
@@ -0,0 +1,37 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config\Transport;
+
+use Icinga\Web\Form;
+
+class LocalTransportForm extends Form
+{
+ /**
+ * (non-PHPDoc)
+ * @see Form::init() For the method documentation.
+ */
+ public function init()
+ {
+ $this->setName('form_config_command_transport_local');
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElement(
+ 'text',
+ 'path',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Command File'),
+ 'value' => '/var/run/icinga2/cmd/icinga2.cmd',
+ 'description' => $this->translate('Path to the local Icinga command file')
+ )
+ );
+ return $this;
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/Transport/RemoteTransportForm.php b/modules/monitoring/application/forms/Config/Transport/RemoteTransportForm.php
new file mode 100644
index 0000000..7beeacf
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/Transport/RemoteTransportForm.php
@@ -0,0 +1,185 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config\Transport;
+
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Web\Form;
+
+class RemoteTransportForm extends Form
+{
+ /**
+ * The available resources split by type
+ *
+ * @var array
+ */
+ protected $resources;
+
+ /**
+ * (non-PHPDoc)
+ * @see Form::init() For the method documentation.
+ */
+ public function init()
+ {
+ $this->setName('form_config_command_transport_remote');
+ }
+
+ /**
+ * Load all available ssh identity resources
+ *
+ * @return $this
+ *
+ * @throws \Icinga\Exception\ConfigurationError
+ */
+ public function loadResources()
+ {
+ $resourceConfig = ResourceFactory::getResourceConfigs();
+
+ $resources = array();
+ foreach ($resourceConfig as $name => $resource) {
+ if ($resource->type === 'ssh') {
+ $resources['ssh'][$name] = $name;
+ }
+ }
+
+ if (empty($resources)) {
+ throw new ConfigurationError($this->translate('Could not find any valid SSH resources'));
+ }
+
+ $this->resources = $resources;
+
+ return $this;
+ }
+
+ /**
+ * Check whether ssh identity resources exists or not
+ *
+ * @return boolean
+ */
+ public function hasResources()
+ {
+ $resourceConfig = ResourceFactory::getResourceConfigs();
+
+ foreach ($resourceConfig as $name => $resource) {
+ if ($resource->type === 'ssh') {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $useResource = false;
+
+ if ($this->hasResources()) {
+ $useResource = isset($formData['use_resource'])
+ ? $formData['use_resource'] : $this->getValue('use_resource');
+
+ $this->addElement(
+ 'checkbox',
+ 'use_resource',
+ array(
+ 'label' => $this->translate('Use SSH Identity'),
+ 'description' => $this->translate('Make use of the ssh identity resource'),
+ 'autosubmit' => true,
+ 'ignore' => true
+ )
+ );
+ }
+
+ if ($useResource) {
+ $this->loadResources();
+
+ $decorators = static::$defaultElementDecorators;
+ array_pop($decorators); // Removes the HtmlTag decorator
+
+ $this->addElement(
+ 'select',
+ 'resource',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('SSH Identity'),
+ 'description' => $this->translate('The resource to use'),
+ 'decorators' => $decorators,
+ 'multiOptions' => $this->resources['ssh'],
+ 'value' => current($this->resources['ssh']),
+ 'autosubmit' => false
+ )
+ );
+ $resourceName = isset($formData['resource']) ? $formData['resource'] : $this->getValue('resource');
+ $this->addElement(
+ 'note',
+ 'resource_note',
+ array(
+ 'escape' => false,
+ 'decorators' => $decorators,
+ 'value' => sprintf(
+ '<a href="%1$s" data-base-target="_next" title="%2$s" aria-label="%2$s">%3$s</a>',
+ $this->getView()->url('config/editresource', array('resource' => $resourceName)),
+ sprintf($this->translate('Show the configuration of the %s resource'), $resourceName),
+ $this->translate('Show resource configuration')
+ )
+ )
+ );
+ }
+
+ $this->addElements(array(
+ array(
+ 'text',
+ 'host',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Host'),
+ 'description' => $this->translate(
+ 'Hostname or address of the remote Icinga instance'
+ )
+ )
+ ),
+ array(
+ 'number',
+ 'port',
+ array(
+ 'required' => true,
+ 'preserveDefault' => true,
+ 'label' => $this->translate('Port'),
+ 'description' => $this->translate('SSH port to connect to on the remote Icinga instance'),
+ 'value' => 22
+ )
+ )
+ ));
+
+ if (! $useResource) {
+ $this->addElement(
+ 'text',
+ 'user',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('User'),
+ 'description' => $this->translate(
+ 'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be'
+ . ' possible for this user'
+ )
+ )
+ );
+ }
+
+ $this->addElement(
+ 'text',
+ 'path',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Command File'),
+ 'value' => '/var/run/icinga2/cmd/icinga2.cmd',
+ 'description' => $this->translate('Path to the Icinga command file on the remote Icinga instance')
+ )
+ );
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/TransportConfigForm.php b/modules/monitoring/application/forms/Config/TransportConfigForm.php
new file mode 100644
index 0000000..c68e63d
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/TransportConfigForm.php
@@ -0,0 +1,392 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config;
+
+use Icinga\Data\ConfigObject;
+use Icinga\Module\Monitoring\Command\Transport\CommandTransport;
+use Icinga\Module\Monitoring\Exception\CommandTransportException;
+use InvalidArgumentException;
+use Icinga\Application\Platform;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotFoundError;
+use Icinga\Forms\ConfigForm;
+use Icinga\Module\Monitoring\Command\Transport\ApiCommandTransport;
+use Icinga\Module\Monitoring\Command\Transport\LocalCommandFile;
+use Icinga\Module\Monitoring\Command\Transport\RemoteCommandFile;
+use Icinga\Module\Monitoring\Forms\Config\Transport\ApiTransportForm;
+use Icinga\Module\Monitoring\Forms\Config\Transport\LocalTransportForm;
+use Icinga\Module\Monitoring\Forms\Config\Transport\RemoteTransportForm;
+
+/**
+ * Form for managing command transports
+ */
+class TransportConfigForm extends ConfigForm
+{
+ /**
+ * The transport to load when displaying the form for the first time
+ *
+ * @var string
+ */
+ protected $transportToLoad;
+
+ /**
+ * The names of all available Icinga instances
+ *
+ * @var array
+ */
+ protected $instanceNames;
+
+ /**
+ * @var bool
+ */
+ protected $validatePartial = true;
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_command_transports');
+ $this->setSubmitLabel($this->translate('Save Changes'));
+ }
+
+ /**
+ * Set the names of all available Icinga instances
+ *
+ * @param array $names
+ *
+ * @return $this
+ */
+ public function setInstanceNames(array $names)
+ {
+ $this->instanceNames = $names;
+ return $this;
+ }
+
+ /**
+ * Return the names of all available Icinga instances
+ *
+ * @return array
+ */
+ public function getInstanceNames()
+ {
+ return $this->instanceNames ?: array();
+ }
+
+ /**
+ * Return a form object for the given transport type
+ *
+ * @param string $type The transport type for which to return a form
+ *
+ * @return \Icinga\Web\Form
+ *
+ * @throws InvalidArgumentException In case the given transport type is invalid
+ */
+ public function getTransportForm($type)
+ {
+ switch (strtolower($type)) {
+ case LocalCommandFile::TRANSPORT:
+ return new LocalTransportForm();
+ case RemoteCommandFile::TRANSPORT:
+ return new RemoteTransportForm();
+ case ApiCommandTransport::TRANSPORT:
+ return new ApiTransportForm();
+ default:
+ throw new InvalidArgumentException(
+ sprintf($this->translate('Invalid command transport type "%s" given'), $type)
+ );
+ }
+ }
+
+ /**
+ * Populate the form with the given transport's config
+ *
+ * @param string $name
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no transport with the given name is found
+ */
+ public function load($name)
+ {
+ if (! $this->config->hasSection($name)) {
+ throw new NotFoundError('No command transport called "%s" found', $name);
+ }
+
+ $this->transportToLoad = $name;
+ return $this;
+ }
+
+ /**
+ * Add a new command transport
+ *
+ * The transport to add is identified by the array-key `name'.
+ *
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException In case $data does not contain a transport name
+ * @throws IcingaException In case a transport with the same name already exists
+ */
+ public function add(array $data)
+ {
+ if (! isset($data['name'])) {
+ throw new InvalidArgumentException('Key \'name\' missing');
+ }
+
+ $transportName = $data['name'];
+ if ($this->config->hasSection($transportName)) {
+ throw new IcingaException(
+ $this->translate('A command transport with the name "%s" does already exist'),
+ $transportName
+ );
+ }
+
+ unset($data['name']);
+ $this->config->setSection($transportName, $data);
+ return $this;
+ }
+
+ /**
+ * Edit an existing command transport
+ *
+ * @param string $name
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no transport with the given name is found
+ */
+ public function edit($name, array $data)
+ {
+ if (! $this->config->hasSection($name)) {
+ throw new NotFoundError('No command transport called "%s" found', $name);
+ }
+
+ $transportConfig = $this->config->getSection($name);
+ if (isset($data['name'])) {
+ if ($data['name'] !== $name) {
+ $this->config->removeSection($name);
+ $name = $data['name'];
+ }
+
+ unset($data['name']);
+ }
+
+ $transportConfig->merge($data);
+ $this->config->setSection($name, $transportConfig);
+ return $this;
+ }
+
+ /**
+ * Remove a command transport
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function delete($name)
+ {
+ $this->config->removeSection($name);
+ return $this;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ $instanceNames = $this->getInstanceNames();
+ if (count($instanceNames) > 1) {
+ $options = array('none' => $this->translate('None', 'command transport instance association'));
+ $this->addElement(
+ 'select',
+ 'instance',
+ array(
+ 'label' => $this->translate('Instance Link'),
+ 'description' => $this->translate(
+ 'The name of the Icinga instance this transport should exclusively transfer commands to.'
+ ),
+ 'multiOptions' => array_merge($options, array_combine($instanceNames, $instanceNames))
+ )
+ );
+ }
+
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Transport Name'),
+ 'description' => $this->translate(
+ 'The name of this command transport that is used to differentiate it from others'
+ )
+ )
+ );
+
+ $transportTypes = array(
+ ApiCommandTransport::TRANSPORT => $this->translate('Icinga 2 API'),
+ LocalCommandFile::TRANSPORT => $this->translate('Local Command File'),
+ RemoteCommandFile::TRANSPORT => $this->translate('Remote Command File')
+ );
+ if (! Platform::extensionLoaded('curl')) {
+ unset($transportTypes[ApiCommandTransport::TRANSPORT]);
+ }
+
+ $transportType = isset($formData['transport']) ? $formData['transport'] : null;
+ if ($transportType === null) {
+ $transportType = key($transportTypes);
+ }
+
+ $this->addElements(array(
+ array(
+ 'select',
+ 'transport',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Transport Type'),
+ 'multiOptions' => $transportTypes
+ )
+ )
+ ));
+
+ $this->addSubForm($this->getTransportForm($transportType)->create($formData), 'transport_form');
+ }
+
+ /**
+ * Add a submit button to this form and one to manually validate the configuration
+ *
+ * Calls parent::addSubmitButton() to add the submit button.
+ *
+ * @return $this
+ */
+ public function addSubmitButton()
+ {
+ parent::addSubmitButton();
+
+ if ($this->getSubForm('transport_form') instanceof ApiTransportForm) {
+ $btnSubmit = $this->getElement('btn_submit');
+
+ if ($btnSubmit !== null) {
+ // In the setup wizard $this is being used as a subform which doesn't have a submit button.
+ $this->addElement(
+ 'submit',
+ 'transport_validation',
+ array(
+ 'ignore' => true,
+ 'label' => $this->translate('Validate Configuration'),
+ 'data-progress-label' => $this->translate('Validation In Progress'),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+
+ $this->setAttrib('data-progress-element', 'transport-progress');
+ $this->addElement(
+ 'note',
+ 'transport-progress',
+ array(
+ 'decorators' => array(
+ 'ViewHelper',
+ array('Spinner', array('id' => 'transport-progress'))
+ )
+ )
+ );
+
+ $elements = array('transport_validation', 'transport-progress');
+
+ $btnSubmit->setDecorators(array('ViewHelper'));
+ array_unshift($elements, 'btn_submit');
+
+ $this->addDisplayGroup(
+ $elements,
+ 'submit_validation',
+ array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ )
+ )
+ );
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Populate the configuration of the transport to load
+ */
+ public function onRequest()
+ {
+ if ($this->transportToLoad) {
+ $data = $this->config->getSection($this->transportToLoad)->toArray();
+ $data['name'] = $this->transportToLoad;
+ $this->populate($data);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isValidPartial(array $formData)
+ {
+ $isValidPartial = parent::isValidPartial($formData);
+
+ $transportValidation = $this->getElement('transport_validation');
+ if ($transportValidation !== null && $transportValidation->isChecked() && $this->isValid($formData)) {
+ $this->info($this->translate('The configuration has been successfully validated.'));
+ }
+
+ return $isValidPartial;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isValid($formData)
+ {
+ if (! parent::isValid($formData)) {
+ return false;
+ }
+
+ if ($this->getSubForm('transport_form') instanceof ApiTransportForm) {
+ if (! isset($formData['transport_validation'])
+ && isset($formData['force_creation']) && $formData['force_creation']
+ ) {
+ // ignore any validation result
+ return true;
+ }
+
+ try {
+ CommandTransport::createTransport(new ConfigObject($this->getValues()))->probe();
+ } catch (CommandTransportException $e) {
+ $this->error(sprintf(
+ $this->translate('Failed to successfully validate the configuration: %s'),
+ $e->getMessage()
+ ));
+
+ $this->addElement(
+ 'checkbox',
+ 'force_creation',
+ array(
+ 'order' => 0,
+ 'ignore' => true,
+ 'label' => $this->translate('Force Changes'),
+ 'description' => $this->translate(
+ 'Check this box to enforce changes without connectivity validation'
+ )
+ )
+ );
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/TransportReorderForm.php b/modules/monitoring/application/forms/Config/TransportReorderForm.php
new file mode 100644
index 0000000..f3efe4c
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/TransportReorderForm.php
@@ -0,0 +1,87 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config;
+
+use Icinga\Application\Config;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+
+/**
+ * Form for reordering command transports
+ */
+class TransportReorderForm extends Form
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_reorder_command_transports');
+ $this->setViewScript('form/reorder-command-transports.phtml');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ // This adds just a dummy element to be able to utilize Form::getValue as part of onSuccess()
+ $this->addElement(
+ 'hidden',
+ 'transport_newpos',
+ array(
+ 'required' => true,
+ 'validators' => array(
+ array(
+ 'validator' => 'regex',
+ 'options' => array(
+ 'pattern' => '/\A\d+\|/'
+ )
+ )
+ )
+ )
+ );
+ }
+
+ /**
+ * Update the command transport order and save the configuration
+ */
+ public function onSuccess()
+ {
+ list($position, $transportName) = explode('|', $this->getValue('transport_newpos'), 2);
+ $config = $this->getConfig();
+ if (! $config->hasSection($transportName)) {
+ Notification::error(sprintf($this->translate('Command transport "%s" not found'), $transportName));
+ return false;
+ }
+
+ if ($config->count() > 1) {
+ $sections = $config->keys();
+ array_splice($sections, array_search($transportName, $sections, true), 1);
+ array_splice($sections, $position, 0, array($transportName));
+
+ $sectionsInNewOrder = array();
+ foreach ($sections as $section) {
+ $sectionsInNewOrder[$section] = $config->getSection($section);
+ $config->removeSection($section);
+ }
+ foreach ($sectionsInNewOrder as $name => $options) {
+ $config->setSection($name, $options);
+ }
+
+ $config->saveIni();
+ Notification::success($this->translate('Command transport order updated'));
+ }
+ }
+
+ /**
+ * Get the command transports config
+ *
+ * @return Config
+ */
+ public function getConfig()
+ {
+ return Config::module('monitoring', 'commandtransports');
+ }
+}
diff --git a/modules/monitoring/application/forms/EventOverviewForm.php b/modules/monitoring/application/forms/EventOverviewForm.php
new file mode 100644
index 0000000..db1511c
--- /dev/null
+++ b/modules/monitoring/application/forms/EventOverviewForm.php
@@ -0,0 +1,157 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms;
+
+use Icinga\Web\Url;
+use Icinga\Web\Form;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Configure the filter for the event overview
+ */
+class EventOverviewForm extends Form
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setName('form_event_overview');
+ $this->setDecorators(array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'hbox')),
+ 'Form'
+ ));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ $decorators = array(
+ array('Label', array('class' => 'optional')),
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'hbox-item optionbox')),
+ );
+
+ $url = Url::fromRequest()->getAbsoluteUrl();
+ $this->addElement(
+ 'checkbox',
+ 'statechange',
+ array(
+ 'label' => $this->translate('State Changes'),
+ 'class' => 'autosubmit',
+ 'decorators' => $decorators,
+ 'value' => strpos($url, $this->stateChangeFilter()->toQueryString()) === false ? 0 : 1
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ 'downtime',
+ array(
+ 'label' => $this->translate('Downtimes'),
+ 'class' => 'autosubmit',
+ 'decorators' => $decorators,
+ 'value' => strpos($url, $this->downtimeFilter()->toQueryString()) === false ? 0 : 1
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ 'comment',
+ array(
+ 'label' => $this->translate('Comments'),
+ 'class' => 'autosubmit',
+ 'decorators' => $decorators,
+ 'value' => strpos($url, $this->commentFilter()->toQueryString()) === false ? 0 : 1
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ 'notification',
+ array(
+ 'label' => $this->translate('Notifications'),
+ 'class' => 'autosubmit',
+ 'decorators' => $decorators,
+ 'value' => strpos($url, $this->notificationFilter()->toQueryString()) === false ? 0 : 1
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ 'flapping',
+ array(
+ 'label' => $this->translate('Flapping'),
+ 'class' => 'autosubmit',
+ 'decorators' => $decorators,
+ 'value' => strpos($url, $this->flappingFilter()->toQueryString()) === false ? 0 : 1
+ )
+ );
+ }
+
+ /**
+ * Return the corresponding filter-object
+ *
+ * @returns Filter
+ */
+ public function getFilter()
+ {
+ $filters = array();
+ if ($this->getValue('statechange', 1)) {
+ $filters[] = $this->stateChangeFilter();
+ }
+ if ($this->getValue('comment', 1)) {
+ $filters[] = $this->commentFilter();
+ }
+ if ($this->getValue('notification', 1)) {
+ $filters[] = $this->notificationFilter();
+ }
+ if ($this->getValue('downtime', 1)) {
+ $filters[] = $this->downtimeFilter();
+ }
+ if ($this->getValue('flapping', 1)) {
+ $filters[] = $this->flappingFilter();
+ }
+ return Filter::matchAny($filters);
+ }
+
+ public function stateChangeFilter()
+ {
+ return Filter::matchAny(
+ Filter::expression('type', '=', 'hard_state'),
+ Filter::expression('type', '=', 'soft_state')
+ );
+ }
+
+ public function commentFilter()
+ {
+ return Filter::matchAny(
+ Filter::expression('type', '=', 'comment'),
+ Filter::expression('type', '=', 'comment_deleted'),
+ Filter::expression('type', '=', 'dt_comment'),
+ Filter::expression('type', '=', 'dt_comment_deleted'),
+ Filter::expression('type', '=', 'ack')
+ );
+ }
+
+ public function notificationFilter()
+ {
+ return Filter::expression('type', '=', 'notify');
+ }
+
+ public function downtimeFilter()
+ {
+ return Filter::matchAny(
+ Filter::expression('type', '=', 'downtime_start'),
+ Filter::expression('type', '=', 'downtime_end')
+ );
+ }
+
+ public function flappingFilter()
+ {
+ return Filter::matchAny(
+ Filter::expression('type', '=', 'flapping'),
+ Filter::expression('type', '=', 'flapping_deleted')
+ );
+ }
+}
diff --git a/modules/monitoring/application/forms/Navigation/ActionForm.php b/modules/monitoring/application/forms/Navigation/ActionForm.php
new file mode 100644
index 0000000..81d5588
--- /dev/null
+++ b/modules/monitoring/application/forms/Navigation/ActionForm.php
@@ -0,0 +1,79 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Navigation;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Exception\QueryException;
+use Icinga\Forms\Navigation\NavigationItemForm;
+
+class ActionForm extends NavigationItemForm
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ parent::createElements($formData);
+
+ $this->addElement(
+ 'text',
+ 'filter',
+ array(
+ 'allowEmpty' => true,
+ 'label' => $this->translate('Filter'),
+ 'description' => $this->translate(
+ 'Display this action only for objects matching this filter. Leave it blank'
+ . ' if you want this action being displayed regardless of the object'
+ )
+ )
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isValid($formData)
+ {
+ if (! parent::isValid($formData)) {
+ return false;
+ }
+
+ if (($filterString = $this->getValue('filter')) !== null) {
+ $filter = Filter::matchAll();
+ $filter->setAllowedFilterColumns(array(
+ 'host_name',
+ 'hostgroup_name',
+ 'instance_name',
+ 'service_description',
+ 'servicegroup_name',
+ 'contact_name',
+ 'contactgroup_name',
+ function ($c) {
+ return preg_match('/^_(?:host|service)_/', $c);
+ }
+ ));
+
+ try {
+ $filter->addFilter(Filter::fromQueryString($filterString));
+ } catch (QueryException $_) {
+ $this->getElement('filter')->addError(sprintf(
+ $this->translate('Invalid filter provided. You can only use the following columns: %s'),
+ implode(', ', array(
+ 'instance_name',
+ 'host_name',
+ 'hostgroup_name',
+ 'service_description',
+ 'servicegroup_name',
+ 'contact_name',
+ 'contactgroup_name',
+ '_(host|service)_<customvar-name>'
+ ))
+ ));
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Navigation/HostActionForm.php b/modules/monitoring/application/forms/Navigation/HostActionForm.php
new file mode 100644
index 0000000..da237d4
--- /dev/null
+++ b/modules/monitoring/application/forms/Navigation/HostActionForm.php
@@ -0,0 +1,8 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Navigation;
+
+class HostActionForm extends ActionForm
+{
+}
diff --git a/modules/monitoring/application/forms/Navigation/ServiceActionForm.php b/modules/monitoring/application/forms/Navigation/ServiceActionForm.php
new file mode 100644
index 0000000..68314d1
--- /dev/null
+++ b/modules/monitoring/application/forms/Navigation/ServiceActionForm.php
@@ -0,0 +1,8 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Navigation;
+
+class ServiceActionForm extends ActionForm
+{
+}
diff --git a/modules/monitoring/application/forms/Setup/BackendPage.php b/modules/monitoring/application/forms/Setup/BackendPage.php
new file mode 100644
index 0000000..d5c7efb
--- /dev/null
+++ b/modules/monitoring/application/forms/Setup/BackendPage.php
@@ -0,0 +1,51 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Setup;
+
+use Icinga\Web\Form;
+use Icinga\Application\Platform;
+
+class BackendPage extends Form
+{
+ public function init()
+ {
+ $this->setName('setup_monitoring_backend');
+ $this->setTitle($this->translate('Monitoring Backend', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'Please configure below how Icinga Web 2 should retrieve monitoring information.'
+ ));
+ }
+
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'value' => 'icinga',
+ 'label' => $this->translate('Backend Name'),
+ 'description' => $this->translate('The identifier of this backend')
+ )
+ );
+
+ $resourceTypes = array();
+ if (Platform::hasMysqlSupport() || Platform::hasPostgresqlSupport()) {
+ $resourceTypes['ido'] = 'IDO';
+ }
+
+ $this->addElement(
+ 'select',
+ 'type',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Backend Type'),
+ 'description' => $this->translate(
+ 'The data source used for retrieving monitoring information'
+ ),
+ 'multiOptions' => $resourceTypes
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/application/forms/Setup/IdoResourcePage.php b/modules/monitoring/application/forms/Setup/IdoResourcePage.php
new file mode 100644
index 0000000..d648579
--- /dev/null
+++ b/modules/monitoring/application/forms/Setup/IdoResourcePage.php
@@ -0,0 +1,188 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Setup;
+
+use Icinga\Data\ConfigObject;
+use Icinga\Forms\Config\ResourceConfigForm;
+use Icinga\Forms\Config\Resource\DbResourceForm;
+use Icinga\Web\Form;
+use Icinga\Module\Monitoring\Forms\Config\BackendConfigForm;
+use Icinga\Module\Setup\Utils\DbTool;
+
+class IdoResourcePage extends Form
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('setup_monitoring_ido');
+ $this->setTitle($this->translate('Monitoring IDO Resource', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'Please fill out the connection details below to access the IDO database of your monitoring environment.'
+ ));
+ $this->setValidatePartial(true);
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'hidden',
+ 'type',
+ array(
+ 'required' => true,
+ 'value' => 'db'
+ )
+ );
+
+ if (isset($formData['skip_validation']) && $formData['skip_validation']) {
+ // In case another error occured and the checkbox was displayed before
+ $this->addSkipValidationCheckbox();
+ } else {
+ $this->addElement(
+ 'hidden',
+ 'skip_validation',
+ array(
+ 'required' => true,
+ 'value' => 0
+ )
+ );
+ }
+
+ $dbResourceForm = new DbResourceForm();
+ $this->addElements($dbResourceForm->createElements($formData)->getElements());
+ $this->getElement('name')->setValue('icinga_ido');
+ }
+
+ /**
+ * Return whether the given values are valid
+ *
+ * @param array $formData The data to validate
+ *
+ * @return bool
+ */
+ public function isValid($formData)
+ {
+ if (! parent::isValid($formData)) {
+ return false;
+ }
+
+ if (! isset($formData['skip_validation']) || !$formData['skip_validation']) {
+ if (! $this->validateConfiguration()) {
+ $this->addSkipValidationCheckbox();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Run the configured backend's inspection checks and show the result, if necessary
+ *
+ * This will only run any validation if the user pushed the 'backend_validation' button.
+ *
+ * @param array $formData
+ *
+ * @return bool
+ */
+ public function isValidPartial(array $formData)
+ {
+ if (isset($formData['backend_validation']) && parent::isValid($formData)) {
+ if (! $this->validateConfiguration(true)) {
+ return false;
+ }
+
+ $this->info($this->translate('The configuration has been successfully validated.'));
+ } elseif (! isset($formData['backend_validation'])) {
+ // This is usually done by isValid(Partial), but as we're not calling any of these...
+ $this->populate($formData);
+ }
+
+ return true;
+ }
+
+ /**
+ * Return whether the configuration is valid
+ *
+ * @param bool $showLog Whether to show the validation log
+ *
+ * @return bool
+ */
+ protected function validateConfiguration($showLog = false)
+ {
+ $inspection = ResourceConfigForm::inspectResource($this);
+ if ($inspection !== null) {
+ if ($showLog) {
+ $join = function ($e) use (&$join) {
+ return is_string($e) ? $e : join("\n", array_map($join, $e));
+ };
+ $this->addElement(
+ 'note',
+ 'inspection_output',
+ array(
+ 'order' => 0,
+ 'value' => '<strong>' . $this->translate('Validation Log') . "</strong>\n\n"
+ . join("\n", array_map($join, $inspection->toArray())),
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'pre', 'class' => 'log-output')),
+ )
+ )
+ );
+ }
+
+ if ($inspection->hasError()) {
+ $this->error(sprintf(
+ $this->translate('Failed to successfully validate the configuration: %s'),
+ $inspection->getError()
+ ));
+ return false;
+ }
+ }
+
+ $configObject = new ConfigObject($this->getValues());
+ if (! BackendConfigForm::isValidIdoSchema($this, $configObject)
+ || !BackendConfigForm::isValidIdoInstance($this, $configObject)
+ ) {
+ return false;
+ }
+
+ if ($this->getValue('db') === 'pgsql') {
+ $db = new DbTool($this->getValues());
+ $version = $db->connectToDb()->getServerVersion();
+ if (version_compare($version, '9.1', '<')) {
+ $this->error(sprintf(
+ $this->translate('The server\'s version %s is too old. The minimum required version is %s.'),
+ $version,
+ '9.1'
+ ));
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a checkbox to the form by which the user can skip the configuration validation
+ */
+ protected function addSkipValidationCheckbox()
+ {
+ $this->addElement(
+ 'checkbox',
+ 'skip_validation',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Skip Validation'),
+ 'description' => $this->translate('Check this to not to validate the configuration')
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/application/forms/Setup/SecurityPage.php b/modules/monitoring/application/forms/Setup/SecurityPage.php
new file mode 100644
index 0000000..999103c
--- /dev/null
+++ b/modules/monitoring/application/forms/Setup/SecurityPage.php
@@ -0,0 +1,27 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Setup;
+
+use Icinga\Web\Form;
+use Icinga\Module\Monitoring\Forms\Config\SecurityConfigForm;
+
+class SecurityPage extends Form
+{
+ public function init()
+ {
+ $this->setName('setup_monitoring_security');
+ $this->setTitle($this->translate('Monitoring Security', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'To protect your monitoring environment against prying eyes please fill out the settings below.'
+ ));
+ }
+
+ public function createElements(array $formData)
+ {
+ $securityConfigForm = new SecurityConfigForm();
+ $securityConfigForm->createElements($formData);
+ $this->addElements($securityConfigForm->getElements());
+ $this->getElement('protected_customvars')->setValue($securityConfigForm->getDefaultProtectedCustomvars());
+ }
+}
diff --git a/modules/monitoring/application/forms/Setup/TransportPage.php b/modules/monitoring/application/forms/Setup/TransportPage.php
new file mode 100644
index 0000000..9d0760a
--- /dev/null
+++ b/modules/monitoring/application/forms/Setup/TransportPage.php
@@ -0,0 +1,55 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Setup;
+
+use Icinga\Web\Form;
+use Icinga\Module\Monitoring\Forms\Config\TransportConfigForm;
+
+class TransportPage extends Form
+{
+ public function init()
+ {
+ $this->setName('setup_command_transport');
+ $this->setTitle($this->translate('Command Transport', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'Please define below how you want to send commands to your monitoring instance.'
+ ));
+ $this->setValidatePartial(true);
+ }
+
+ public function createElements(array $formData)
+ {
+ $transportConfigForm = new TransportConfigForm();
+ $this->addSubForm($transportConfigForm, 'transport_form');
+ $transportConfigForm->create($formData);
+ $transportConfigForm->removeElement('instance');
+ $transportConfigForm->getElement('name')->setValue('icinga2');
+ }
+
+ public function getValues($suppressArrayNotation = false)
+ {
+ return $this->getSubForm('transport_form')->getValues($suppressArrayNotation);
+ }
+
+ /**
+ * Run the configured backend's inspection checks and show the result, if necessary
+ *
+ * This will only run any validation if the user pushed the 'transport_validation' button.
+ *
+ * @param array $formData
+ *
+ * @return bool
+ */
+ public function isValidPartial(array $formData)
+ {
+ if (isset($formData['transport_validation']) && parent::isValid($formData)) {
+ $this->info($this->translate('The configuration has been successfully validated.'));
+ } elseif (! isset($formData['transport_validation'])) {
+ // This is usually done by isValid(Partial), but as we're not calling any of these...
+ $this->populate($formData);
+ }
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Setup/WelcomePage.php b/modules/monitoring/application/forms/Setup/WelcomePage.php
new file mode 100644
index 0000000..aa78db5
--- /dev/null
+++ b/modules/monitoring/application/forms/Setup/WelcomePage.php
@@ -0,0 +1,63 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Setup;
+
+use Icinga\Web\Form;
+
+class WelcomePage extends Form
+{
+ public function init()
+ {
+ $this->setName('setup_monitoring_welcome');
+ }
+
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'note',
+ 'welcome',
+ array(
+ 'value' => $this->translate(
+ 'Welcome to the configuration of the monitoring module for Icinga Web 2!'
+ ),
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'h2'))
+ )
+ )
+ );
+
+ $this->addElement(
+ 'note',
+ 'core_hint',
+ array(
+ 'value' => $this->translate('This is the core module for Icinga Web 2.'),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+
+ $this->addElement(
+ 'note',
+ 'description',
+ array(
+ 'value' => $this->translate(
+ 'It offers various status and reporting views with powerful filter capabilities that allow'
+ . ' you to keep track of the most important events in your monitoring environment.'
+ ),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+
+ $this->addDisplayGroup(
+ array('core_hint', 'description'),
+ 'info',
+ array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'info'))
+ )
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/application/forms/StatehistoryForm.php b/modules/monitoring/application/forms/StatehistoryForm.php
new file mode 100644
index 0000000..3a7c10d
--- /dev/null
+++ b/modules/monitoring/application/forms/StatehistoryForm.php
@@ -0,0 +1,141 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms;
+
+use Icinga\Web\Form;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Configure the filter for the event grid
+ */
+class StatehistoryForm extends Form
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setName('form_event_overview');
+ $this->setSubmitLabel($this->translate('Apply'));
+ }
+
+ /**
+ * Return the corresponding filter-object
+ *
+ * @returns Filter
+ */
+ public function getFilter()
+ {
+ $baseFilter = Filter::matchAny(
+ Filter::expression('type', '=', 'hard_state')
+ );
+
+ if ($this->getValue('objecttype', 'hosts') === 'hosts') {
+ $objectTypeFilter = Filter::expression('object_type', '=', 'host');
+ } else {
+ $objectTypeFilter = Filter::expression('object_type', '=', 'service');
+ }
+
+ $states = array(
+ 'cnt_down_hard' => Filter::expression('state', '=', '1'),
+ 'cnt_unreachable_hard' => Filter::expression('state', '=', '2'),
+ 'cnt_up' => Filter::expression('state', '=', '0'),
+ 'cnt_critical_hard' => Filter::expression('state', '=', '2'),
+ 'cnt_warning_hard' => Filter::expression('state', '=', '1'),
+ 'cnt_unknown_hard' => Filter::expression('state', '=', '3'),
+ 'cnt_ok' => Filter::expression('state', '=', '0')
+ );
+ $state = $this->getValue('state', 'cnt_critical_hard');
+ $stateFilter = $states[$state];
+ if (in_array($state, array('cnt_ok', 'cnt_up'))) {
+ return Filter::matchAll($objectTypeFilter, $stateFilter);
+ }
+ return Filter::matchAll($baseFilter, $objectTypeFilter, $stateFilter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'select',
+ 'from',
+ array(
+ 'label' => $this->translate('From'),
+ 'value' => $this->getRequest()->getParam('from', strtotime('3 months ago')),
+ 'multiOptions' => array(
+ strtotime('midnight 3 months ago') => $this->translate('3 Months'),
+ strtotime('midnight 4 months ago') => $this->translate('4 Months'),
+ strtotime('midnight 8 months ago') => $this->translate('8 Months'),
+ strtotime('midnight 12 months ago') => $this->translate('1 Year'),
+ strtotime('midnight 24 months ago') => $this->translate('2 Years')
+ )
+ )
+ );
+ $this->addElement(
+ 'select',
+ 'to',
+ array(
+ 'label' => $this->translate('To'),
+ 'value' => $this->getRequest()->getParam('to', time()),
+ 'multiOptions' => array(
+ time() => $this->translate('Today')
+ )
+ )
+ );
+
+ $objectType = $this->getRequest()->getParam('objecttype', 'services');
+ $this->addElement(
+ 'select',
+ 'objecttype',
+ array(
+ 'label' => $this->translate('Object type'),
+ 'value' => $objectType,
+ 'multiOptions' => array(
+ 'services' => $this->translate('Services'),
+ 'hosts' => $this->translate('Hosts')
+ )
+ )
+ );
+ if ($objectType === 'services') {
+ $serviceState = $this->getRequest()->getParam('state', 'cnt_critical_hard');
+ if (in_array($serviceState, array('cnt_down_hard', 'cnt_unreachable_hard', 'cnt_up'))) {
+ $serviceState = 'cnt_critical_hard';
+ }
+ $this->addElement(
+ 'select',
+ 'state',
+ array(
+ 'label' => $this->translate('State'),
+ 'value' => $serviceState,
+ 'multiOptions' => array(
+ 'cnt_critical_hard' => $this->translate('Critical'),
+ 'cnt_warning_hard' => $this->translate('Warning'),
+ 'cnt_unknown_hard' => $this->translate('Unknown'),
+ 'cnt_ok' => $this->translate('Ok')
+ )
+ )
+ );
+ } else {
+ $hostState = $this->getRequest()->getParam('state', 'cnt_down_hard');
+ if (in_array($hostState, array('cnt_ok', 'cnt_critical_hard', 'cnt_warning', 'cnt_unknown'))) {
+ $hostState = 'cnt_down_hard';
+ }
+ $this->addElement(
+ 'select',
+ 'state',
+ array(
+ 'label' => $this->translate('State'),
+ 'value' => $hostState,
+ 'multiOptions' => array(
+ 'cnt_up' => $this->translate('Up'),
+ 'cnt_down_hard' => $this->translate('Down'),
+ 'cnt_unreachable_hard' => $this->translate('Unreachable')
+ )
+ )
+ );
+ }
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/CheckPerformance.php b/modules/monitoring/application/views/helpers/CheckPerformance.php
new file mode 100644
index 0000000..feac4d8
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/CheckPerformance.php
@@ -0,0 +1,50 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+/**
+ * Convert check summary data into a simple usable stdClass
+ */
+class Zend_View_Helper_CheckPerformance extends Zend_View_Helper_Abstract
+{
+ /**
+ * Create dispatch instance
+ *
+ * @return $this
+ */
+ public function checkPerformance()
+ {
+ return $this;
+ }
+
+ /**
+ * Create a condensed row of object data
+ *
+ * @param array $results Array of stdClass
+ *
+ * @return stdClass Condensed row
+ */
+ public function create(array $results)
+ {
+ $out = new stdClass();
+ $out->host_passive_count = 0;
+ $out->host_passive_latency_avg = 0;
+ $out->host_passive_execution_avg = 0;
+ $out->service_passive_count = 0;
+ $out->service_passive_latency_avg = 0;
+ $out->service_passive_execution_avg = 0;
+ $out->service_active_count = 0;
+ $out->service_active_latency_avg = 0;
+ $out->service_active_execution_avg = 0;
+ $out->host_active_count = 0;
+ $out->host_active_latency_avg = 0;
+ $out->host_active_execution_avg = 0;
+
+ foreach ($results as $row) {
+ $key = $row->object_type . '_' . $row->check_type . '_';
+ $out->{$key . 'count'} = $row->object_count;
+ $out->{$key . 'latency_avg'} = $row->latency / $row->object_count;
+ $out->{$key . 'execution_avg'} = $row->execution_time / $row->object_count;
+ }
+ return $out;
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/ContactFlags.php b/modules/monitoring/application/views/helpers/ContactFlags.php
new file mode 100644
index 0000000..858c726
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/ContactFlags.php
@@ -0,0 +1,46 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+class Zend_View_Helper_ContactFlags extends Zend_View_Helper_Abstract
+{
+ /**
+ * Get the human readable flag name for the given contact notification option
+ *
+ * @param string $tableName The name of the option table
+ *
+ * @return string
+ */
+ public function getNotificationOptionName($tableName)
+ {
+ $exploded = explode('_', $tableName);
+ $name = end($exploded);
+ return ucfirst($name);
+ }
+
+ /**
+ * Build all active notification options to a readable string
+ *
+ * @param object $contact The contact retrieved from a backend
+ * @param string $type Whether to display the flags for 'host' or 'service'
+ * @param string $glue The symbol to use to concatenate the flag names
+ *
+ * @return string A string that contains a human readable list of active options
+ */
+ public function contactFlags($contact, $type, $glue = ', ')
+ {
+ $optionName = 'contact_' . $type . '_notification_options';
+ if (isset($contact->$optionName)) {
+ return $contact->$optionName;
+ }
+ $out = array();
+ foreach ($contact as $key => $value) {
+ if (preg_match('/^contact_notify_' . $type . '_.*/', $key) && $value == true) {
+ $option = $this->getNotificationOptionName($key);
+ if (strtolower($option) != 'timeperiod') {
+ array_push($out, $option);
+ }
+ }
+ }
+ return implode($glue, $out);
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/Customvar.php b/modules/monitoring/application/views/helpers/Customvar.php
new file mode 100644
index 0000000..8bfdc3a
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/Customvar.php
@@ -0,0 +1,62 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+class Zend_View_Helper_Customvar extends Zend_View_Helper_Abstract
+{
+ /**
+ * Create dispatch instance
+ *
+ * @return $this
+ */
+ public function checkPerformance()
+ {
+ return $this;
+ }
+
+ public function customvar($struct)
+ {
+ if (is_scalar($struct)) {
+ return nl2br($this->view->escape(
+ is_string($struct)
+ ? $struct
+ : var_export($struct, true)
+ ), false);
+ } elseif (is_array($struct)) {
+ return $this->renderArray($struct);
+ } elseif (is_object($struct)) {
+ return $this->renderObject($struct);
+ }
+ }
+
+ protected function renderArray($array)
+ {
+ if (empty($array)) {
+ return '[]';
+ }
+ $out = "<ul>\n";
+
+ foreach ($array as $val) {
+ $out .= '<li>' . $this->customvar($val) . "</li>\n";
+ }
+
+ return $out . "</ul>\n";
+ }
+
+ protected function renderObject($object)
+ {
+ if (0 === count((array) $object)) {
+ return '{}';
+ }
+ $out = "{<ul>\n";
+
+ foreach ($object as $key => $val) {
+ $out .= '<li>'
+ . $this->view->escape($key)
+ . ' => '
+ . $this->customvar($val)
+ . "</li>\n";
+ }
+
+ return $out . "</ul>}";
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/EscapeComment.php b/modules/monitoring/application/views/helpers/EscapeComment.php
new file mode 100644
index 0000000..be85a22
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/EscapeComment.php
@@ -0,0 +1,38 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+/**
+ * Helper for escaping comments, but preserving links
+ */
+class Zend_View_Helper_EscapeComment extends Zend_View_Helper_Abstract
+{
+ /**
+ * The purifier to use for escaping
+ *
+ * @var HTMLPurifier
+ */
+ protected static $purifier;
+
+ /**
+ * Escape any comment for being placed inside HTML, but preserve simple links (<a href="...">).
+ *
+ * @param string $comment
+ *
+ * @return string
+ */
+ public function escapeComment($comment)
+ {
+ if (self::$purifier === null) {
+ require_once 'HTMLPurifier/Bootstrap.php';
+ require_once 'HTMLPurifier.php';
+ require_once 'HTMLPurifier.autoload.php';
+
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('Core.EscapeNonASCIICharacters', true);
+ $config->set('HTML.Allowed', 'a[href]');
+ $config->set('Cache.DefinitionImpl', null);
+ self::$purifier = new HTMLPurifier($config);
+ }
+ return self::$purifier->purify($comment);
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/HostFlags.php b/modules/monitoring/application/views/helpers/HostFlags.php
new file mode 100644
index 0000000..81d8ebc
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/HostFlags.php
@@ -0,0 +1,33 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+class Zend_View_Helper_HostFlags extends Zend_View_Helper_Abstract
+{
+ public function hostFlags($host)
+ {
+ $icons = array();
+ if (! $host->host_handled && $host->host_state > 0) {
+ $icons[] = $this->view->icon('attention-alt', $this->view->translate('Unhandled'));
+ }
+ if ($host->host_acknowledged) {
+ $icons[] = $this->view->icon('ok', $this->view->translate('Acknowledged'));
+ }
+ if ($host->host_is_flapping) {
+ $icons[] = $this->view->icon('flapping', $this->view->translate('Flapping'));
+ }
+ if (! $host->host_notifications_enabled) {
+ $icons[] = $this->view->icon('bell-off-empty', $this->view->translate('Notifications Disabled'));
+ }
+ if ($host->host_in_downtime) {
+ $icons[] = $this->view->icon('plug', $this->view->translate('In Downtime'));
+ }
+ if (! $host->host_active_checks_enabled) {
+ if (! $host->host_passive_checks_enabled) {
+ $icons[] = $this->view->icon('eye-off', $this->view->translate('Active And Passive Checks Disabled'));
+ } else {
+ $icons[] = $this->view->icon('eye-off', $this->view->translate('Active Checks Disabled'));
+ }
+ }
+ return implode(' ', $icons);
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/IconImage.php b/modules/monitoring/application/views/helpers/IconImage.php
new file mode 100644
index 0000000..3c0ca43
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/IconImage.php
@@ -0,0 +1,64 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+use Icinga\Module\Monitoring\Object\Macro;
+
+/**
+ * Generate icons to describe a given hosts state
+ */
+class Zend_View_Helper_IconImage extends Zend_View_Helper_Abstract
+{
+ /**
+ * Create dispatch instance
+ *
+ * @return \Zend_View_Helper_IconImage
+ */
+ public function iconImage()
+ {
+ return $this;
+ }
+
+ /**
+ * Display the image_icon of a MonitoredObject
+ *
+ * @param MonitoredObject|stdClass $object The host or service
+ * @return string
+ */
+ public function host($object)
+ {
+ if ($object->host_icon_image && ! preg_match('/[\'"]/', $object->host_icon_image)) {
+ return $this->view->icon(
+ Macro::resolveMacros($object->host_icon_image, $object),
+ null,
+ array(
+ 'alt' => $object->host_icon_image_alt,
+ 'class' => 'host-icon-image',
+ 'title' => $object->host_icon_image_alt
+ )
+ );
+ }
+ return '';
+ }
+
+ /**
+ * Display the image_icon of a MonitoredObject
+ *
+ * @param MonitoredObject|stdClass $object The host or service
+ * @return string
+ */
+ public function service($object)
+ {
+ if ($object->service_icon_image && ! preg_match('/[\'"]/', $object->service_icon_image)) {
+ return $this->view->icon(
+ Macro::resolveMacros($object->service_icon_image, $object),
+ null,
+ array(
+ 'alt' => $object->service_icon_image_alt,
+ 'class' => 'service-icon-image',
+ 'title' => $object->service_icon_image_alt
+ )
+ );
+ }
+ return '';
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/Link.php b/modules/monitoring/application/views/helpers/Link.php
new file mode 100644
index 0000000..c5443a4
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/Link.php
@@ -0,0 +1,72 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+/**
+ * Helper for generating frequently used jump links
+ *
+ * Most of the monitoring overviews link to detail information, e.g. the full information of the involved monitored
+ * object. Instead of reintroducing link generation and translation in those views, this helper contains most
+ * frequently used jump links.
+ */
+class Zend_View_Helper_Link extends Zend_View_Helper_Abstract
+{
+ /**
+ * Helper entry point
+ *
+ * @return $this
+ */
+ public function link()
+ {
+ return $this;
+ }
+
+ /**
+ * Create a host link
+ *
+ * @param string $host Hostname
+ * @param string $linkText Link text, e.g. the host's display name
+ *
+ * @return string
+ */
+ public function host($host, $linkText)
+ {
+ return $this->view->qlink(
+ $linkText,
+ 'monitoring/host/show',
+ array('host' => $host),
+ array('title' => sprintf($this->view->translate('Show detailed information for host %s'), $linkText))
+ );
+ }
+
+ /**
+ * Create a service link
+ *
+ * @param string $service Service name
+ * @param string $serviceLinkText Text for the service link, e.g. the service's display name
+ * @param string $host Hostname
+ * @param string $hostLinkText Text for the host link, e.g. the host's display name
+ * @param string $class An optional class to use for this link
+ *
+ * @return string
+ */
+ public function service($service, $serviceLinkText, $host, $hostLinkText, $class = null)
+ {
+ return sprintf(
+ '%s&#58; %s',
+ $this->host($host, $hostLinkText),
+ $this->view->qlink(
+ $serviceLinkText,
+ 'monitoring/service/show',
+ array('host' => $host, 'service' => $service),
+ array(
+ 'title' => sprintf(
+ $this->view->translate('Show detailed information for service %s on host %s'),
+ $serviceLinkText,
+ $hostLinkText
+ ),
+ 'class' => $class
+ )
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/MonitoringFlags.php b/modules/monitoring/application/views/helpers/MonitoringFlags.php
new file mode 100644
index 0000000..8509e5b
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/MonitoringFlags.php
@@ -0,0 +1,40 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+/* use Icinga\Module\Monitoring\Object\MonitoredObject; */
+
+/**
+ * Rendering helper for object's properties which may be either enabled or disabled
+ */
+class Zend_View_Helper_MonitoringFlags extends Zend_View_Helper_Abstract
+{
+ /**
+ * Object's properties which may be either enabled or disabled and their human readable description
+ *
+ * @var string[]
+ */
+ private static $flags = array(
+ 'passive_checks_enabled' => 'Passive Checks',
+ 'active_checks_enabled' => 'Active Checks',
+ 'obsessing' => 'Obsessing',
+ 'notifications_enabled' => 'Notifications',
+ 'event_handler_enabled' => 'Event Handler',
+ 'flap_detection_enabled' => 'Flap Detection',
+ );
+
+ /**
+ * Retrieve flags as array with either true or false as value
+ *
+ * @param MonitoredObject $object
+ *
+ * @return array
+ */
+ public function monitoringFlags(/*MonitoredObject*/ $object)
+ {
+ $flags = array();
+ foreach (self::$flags as $column => $description) {
+ $flags[$description] = (bool) $object->{$column};
+ }
+ return $flags;
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/Perfdata.php b/modules/monitoring/application/views/helpers/Perfdata.php
new file mode 100644
index 0000000..e7bc72b
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/Perfdata.php
@@ -0,0 +1,116 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+use Icinga\Module\Monitoring\Plugin\Perfdata;
+use Icinga\Module\Monitoring\Plugin\PerfdataSet;
+use Icinga\Util\StringHelper;
+
+class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
+{
+ /**
+ * Display the given perfdata string to the user
+ *
+ * @param string $perfdataStr The perfdata string
+ * @param bool $compact Whether to display the perfdata in compact mode
+ * @param int $limit Max labels to show; 0 for no limit
+ * @param string $color The color indicating the perfdata state
+ *
+ * @return string
+ */
+ public function perfdata($perfdataStr, $compact = false, $limit = 0, $color = Perfdata::PERFDATA_OK)
+ {
+ $pieChartData = PerfdataSet::fromString($perfdataStr)->asArray();
+ uasort(
+ $pieChartData,
+ function ($a, $b) {
+ return $a->worseThan($b) ? -1 : ($b->worseThan($a) ? 1 : 0);
+ }
+ );
+ $results = array();
+ $keys = array('', 'label', 'value', 'min', 'max', 'warn', 'crit');
+ $columns = array();
+ $labels = array_combine(
+ $keys,
+ array(
+ '',
+ $this->view->translate('Label'),
+ $this->view->translate('Value'),
+ $this->view->translate('Min'),
+ $this->view->translate('Max'),
+ $this->view->translate('Warning'),
+ $this->view->translate('Critical')
+ )
+ );
+ foreach ($pieChartData as $perfdata) {
+ if ($perfdata->isVisualizable()) {
+ $columns[''] = '';
+ }
+ foreach ($perfdata->toArray() as $column => $value) {
+ if (empty($value) ||
+ $column === 'min' && floatval($value) === 0.0 ||
+ $column === 'max' && $perfdata->isPercentage() && floatval($value) === 100) {
+ continue;
+ }
+ $columns[$column] = $labels[$column];
+ }
+ }
+ // restore original column array sorting
+ $headers = array();
+ foreach ($keys as $column) {
+ if (isset($columns[$column])) {
+ $headers[$column] = $labels[$column];
+ }
+ }
+ $table = array('<thead><tr><th>' . implode('</th><th>', $headers) . '</th></tr></thead><tbody>');
+ foreach ($pieChartData as $perfdata) {
+ if ($compact && $perfdata->isVisualizable()) {
+ $results[] = $perfdata->asInlinePie($color)->render();
+ } else {
+ $data = array();
+ if ($perfdata->isVisualizable()) {
+ $data []= $perfdata->asInlinePie($color)->render();
+ } elseif (isset($columns[''])) {
+ $data []= '';
+ }
+ if (! $compact) {
+ foreach ($perfdata->toArray() as $column => $value) {
+ if (! isset($columns[$column])) {
+ continue;
+ }
+ $text = $this->view->escape(empty($value) ? '-' : $value);
+ $data []= sprintf(
+ '<span title="%s">%s</span>',
+ $text,
+ $text
+ );
+ }
+ }
+ $table []= '<tr><td class="sparkline-col">' . implode('</td><td>', $data) . '</td></tr>';
+ }
+ }
+ $table[] = '</tbody>';
+ if ($limit > 0) {
+ $count = $compact ? count($results) : count($table);
+ if ($count > $limit) {
+ if ($compact) {
+ $results = array_slice($results, 0, $limit);
+ $title = sprintf($this->view->translate('%d more ...'), $count - $limit);
+ $results[] = '<span aria-hidden="true" title="' . $title . '">...</span>';
+ } else {
+ $table = array_slice($table, 0, $limit);
+ }
+ }
+ }
+ if ($compact) {
+ return join('', $results);
+ } else {
+ if (empty($table)) {
+ return '';
+ }
+ return sprintf(
+ '<table class="performance-data-table collapsible" data-visible-rows="6">%s</table>',
+ implode("\n", $table)
+ );
+ }
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/PluginOutput.php b/modules/monitoring/application/views/helpers/PluginOutput.php
new file mode 100644
index 0000000..bcd3d9e
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/PluginOutput.php
@@ -0,0 +1,199 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+use Icinga\Web\Dom\DomNodeIterator;
+use Icinga\Web\View;
+use Icinga\Web\Helper\HtmlPurifier;
+
+/**
+ * Plugin output renderer
+ */
+class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
+{
+ /**
+ * Patterns to be replaced in plain text plugin output
+ *
+ * @var array
+ */
+ protected static $txtPatterns = array(
+ '~\\\t~',
+ '~\\\n~',
+ '~(\[|\()OK(\]|\))~',
+ '~(\[|\()WARNING(\]|\))~',
+ '~(\[|\()CRITICAL(\]|\))~',
+ '~(\[|\()UNKNOWN(\]|\))~',
+ '~(\[|\()UP(\]|\))~',
+ '~(\[|\()DOWN(\]|\))~',
+ '~\@{6,}~'
+ );
+
+ /**
+ * Replacements for $txtPatterns
+ *
+ * @var array
+ */
+ protected static $txtReplacements = array(
+ "\t",
+ "\n",
+ '<span class="state-ok">$1OK$2</span>',
+ '<span class="state-warning">$1WARNING$2</span>',
+ '<span class="state-critical">$1CRITICAL$2</span>',
+ '<span class="state-unknown">$1UNKNOWN$2</span>',
+ '<span class="state-up">$1UP$2</span>',
+ '<span class="state-down">$1DOWN$2</span>',
+ '@@@@@@',
+ );
+
+ /**
+ * Patterns to be replaced in html plugin output
+ *
+ * @var array
+ */
+ protected static $htmlPatterns = array(
+ '~\\\t~',
+ '~\\\n~',
+ '~<table~'
+ );
+
+ /**
+ * Replacements for $htmlPatterns
+ *
+ * @var array
+ */
+ protected static $htmlReplacements = array(
+ "\t",
+ "\n",
+ '<table style="font-size: 0.75em"'
+ );
+
+ /** @var \Icinga\Module\Monitoring\Web\Helper\PluginOutputHookRenderer */
+ protected $hookRenderer;
+
+ public function __construct()
+ {
+ $this->hookRenderer = (new \Icinga\Module\Monitoring\Web\Helper\PluginOutputHookRenderer())->registerHooks();
+ }
+
+ /**
+ * Render plugin output
+ *
+ * @param string $output
+ * @param bool $raw
+ * @param string $command Check command
+ *
+ * @return string
+ */
+ public function pluginOutput($output, $raw = false, $command = null)
+ {
+ if (empty($output)) {
+ return '';
+ }
+
+ if ($command !== null) {
+ $output = $this->hookRenderer->render($command, $output, ! $raw);
+ }
+
+ if (preg_match('~<\w+(?>\s\w+=[^>]*)?>~', $output)) {
+ // HTML
+ $output = HtmlPurifier::process(preg_replace(
+ self::$htmlPatterns,
+ self::$htmlReplacements,
+ $output
+ ));
+ $isHtml = true;
+ } else {
+ // Plaintext
+ $output = preg_replace(
+ self::$txtPatterns,
+ self::$txtReplacements,
+ // Not using the view here to escape this. The view sets `double_encode` to true
+ htmlspecialchars($output, ENT_COMPAT | ENT_SUBSTITUTE | ENT_HTML5, View::CHARSET, false)
+ );
+ $isHtml = false;
+ }
+
+ $output = trim($output);
+ // Add zero-width space after commas which are not followed by a whitespace character
+ // in oder to help browsers to break words in plugin output
+ $output = preg_replace('/,(?=[^\s])/', ',&#8203;', $output);
+ if (! $raw) {
+ if ($isHtml) {
+ $output = $this->processHtml($output);
+ $output = '<div class="plugin-output">' . $output . '</div>';
+ } else {
+ $output = '<div class="plugin-output preformatted">' . $output . '</div>';
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * Replace classic Icinga CGI links with Icinga Web 2 links and color state information, if any
+ *
+ * @param string $html
+ *
+ * @return string
+ */
+ protected function processHtml($html)
+ {
+ $pattern = '/[([](OK|WARNING|CRITICAL|UNKNOWN|UP|DOWN)[)\]]/';
+ $doc = new DOMDocument();
+ $doc->loadXML('<div>' . $html . '</div>', LIBXML_NOERROR | LIBXML_NOWARNING);
+ $dom = new RecursiveIteratorIterator(new DomNodeIterator($doc), RecursiveIteratorIterator::SELF_FIRST);
+ $nodesToRemove = array();
+ foreach ($dom as $node) {
+ /** @var \DOMNode $node */
+ if ($node->nodeType === XML_TEXT_NODE) {
+ $start = 0;
+ while (preg_match($pattern, $node->nodeValue, $match, PREG_OFFSET_CAPTURE, $start)) {
+ $offsetLeft = $match[0][1];
+ $matchLength = strlen($match[0][0]);
+ $leftLength = $offsetLeft - $start;
+ // if there is text before the match
+ if ($leftLength) {
+ // create node for leading text
+ $text = new DOMText(substr($node->nodeValue, $start, $leftLength));
+ $node->parentNode->insertBefore($text, $node);
+ }
+ // create the new element for the match
+ $span = $doc->createElement('span', $match[0][0]);
+ $span->setAttribute('class', 'state-' . strtolower($match[1][0]));
+ $node->parentNode->insertBefore($span, $node);
+
+ // start for next match
+ $start = $offsetLeft + $matchLength;
+ }
+ if ($start) {
+ // is there text left?
+ if (strlen($node->nodeValue) > $start) {
+ // create node for trailing text
+ $text = new DOMText(substr($node->nodeValue, $start));
+ $node->parentNode->insertBefore($text, $node);
+ }
+ // delete the old node later
+ $nodesToRemove[] = $node;
+ }
+ } elseif ($node->nodeType === XML_ELEMENT_NODE) {
+ /** @var \DOMElement $node */
+ if ($node->tagName === 'a'
+ && preg_match('~^/cgi\-bin/status\.cgi\?(.+)$~', $node->getAttribute('href'), $match)
+ ) {
+ parse_str($match[1], $params);
+ if (isset($params['host'])) {
+ $node->setAttribute(
+ 'href',
+ $this->view->baseUrl('/monitoring/host/show?host=' . urlencode($params['host']))
+ );
+ }
+ }
+ }
+ }
+ foreach ($nodesToRemove as $node) {
+ /** @var \DOMNode $node */
+ $node->parentNode->removeChild($node);
+ }
+
+ return substr($doc->saveHTML(), 5, -7);
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/RuntimeVariables.php b/modules/monitoring/application/views/helpers/RuntimeVariables.php
new file mode 100644
index 0000000..e80e8aa
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/RuntimeVariables.php
@@ -0,0 +1,50 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+/**
+ * Convert runtime summary data into a simple usable stdClass
+ */
+class Zend_View_Helper_RuntimeVariables extends Zend_View_Helper_Abstract
+{
+ /**
+ * Create dispatch instance
+ *
+ * @return $this
+ */
+ public function runtimeVariables()
+ {
+ return $this;
+ }
+
+ /**
+ * Create a condensed row of object data
+ *
+ * @param $result stdClass
+ *
+ * @return stdClass Condensed row
+ */
+ public function create(stdClass $result)
+ {
+ $out = new stdClass();
+ $out->total_hosts = isset($result->total_hosts)
+ ? $result->total_hosts
+ : 0;
+ $out->total_scheduled_hosts = isset($result->total_scheduled_hosts)
+ ? $result->total_scheduled_hosts
+ : 0;
+ $out->total_services = isset($result->total_services)
+ ? $result->total_services
+ : 0;
+ $out->total_scheduled_services = isset($result->total_scheduled_services)
+ ? $result->total_scheduled_services
+ : 0;
+ $out->average_services_per_host = $out->total_hosts > 0
+ ? $out->total_services / $out->total_hosts
+ : 0;
+ $out->average_scheduled_services_per_host = $out->total_scheduled_hosts > 0
+ ? $out->total_scheduled_services / $out->total_scheduled_hosts
+ : 0;
+
+ return $out;
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/ServiceFlags.php b/modules/monitoring/application/views/helpers/ServiceFlags.php
new file mode 100644
index 0000000..47a351c
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/ServiceFlags.php
@@ -0,0 +1,33 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+class Zend_View_Helper_ServiceFlags extends Zend_View_Helper_Abstract
+{
+ public function serviceFlags($service)
+ {
+ $icons = array();
+ if (! $service->service_handled && $service->service_state > 0) {
+ $icons[] = $this->view->icon('attention-alt', $this->view->translate('Unhandled'));
+ }
+ if ($service->service_acknowledged) {
+ $icons[] = $this->view->icon('ok', $this->view->translate('Acknowledged'));
+ }
+ if ($service->service_is_flapping) {
+ $icons[] = $this->view->icon('flapping', $this->view->translate('Flapping'));
+ }
+ if (! $service->service_notifications_enabled) {
+ $icons[] = $this->view->icon('bell-off-empty', $this->view->translate('Notifications Disabled'));
+ }
+ if ($service->service_in_downtime) {
+ $icons[] = $this->view->icon('plug', $this->view->translate('In Downtime'));
+ }
+ if (! $service->service_active_checks_enabled) {
+ if (! $service->service_passive_checks_enabled) {
+ $icons[] = $this->view->icon('eye-off', $this->view->translate('Active And Passive Checks Disabled'));
+ } else {
+ $icons[] = $this->view->icon('eye-off', $this->view->translate('Active Checks Disabled'));
+ }
+ }
+ return implode(' ', $icons);
+ }
+}
diff --git a/modules/monitoring/application/views/scripts/comment/remove.phtml b/modules/monitoring/application/views/scripts/comment/remove.phtml
new file mode 100644
index 0000000..73f8c68
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/comment/remove.phtml
@@ -0,0 +1,11 @@
+<div class="controls">
+
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+
+ <?= $this->render('partials/downtime/downtime-header.phtml'); ?>
+</div>
+<div class="content object-command">
+ <?= $delDowntimeForm; ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/comment/show.phtml b/modules/monitoring/application/views/scripts/comment/show.phtml
new file mode 100644
index 0000000..3cbfb76
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/comment/show.phtml
@@ -0,0 +1,86 @@
+<div class="controls">
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+
+ <div data-base-target='_next'>
+ <?= $this->render('partials/comment/comment-header.phtml'); ?>
+ </div>
+</div>
+<div class="content">
+
+<h2><?= $this->translate('Comment detail information') ?></h2>
+<table class="name-value-table">
+ <tbody>
+ <tr>
+ <?php if ($this->comment->objecttype === 'service'): ?>
+ <th> <?= $this->translate('Service') ?> </th>
+ <td>
+ <?= $this->icon('service', $this->translate('Service')); ?>
+ <?= $this->link()->service(
+ $this->comment->service_description,
+ $this->comment->service_display_name,
+ $this->comment->host_name,
+ $this->comment->host_display_name
+ );
+ ?>
+ </td>
+ <?php else: ?>
+ <th> <?= $this->translate('Host') ?> </th>
+ <td>
+ <?= $this->icon('host', $this->translate('Host')); ?>
+ <?= $this->link()->host(
+ $this->comment->host_name,
+ $this->comment->host_display_name
+ );
+ ?>
+ </td>
+ <?php endif ?>
+ </tr>
+
+ <tr>
+ <th><?= $this->translate('Author') ?></th>
+ <td><?= $this->icon('user', $this->translate('User')) ?> <?= $this->escape($this->comment->author) ?></td>
+ </tr>
+
+ <tr>
+ <th><?= $this->translate('Persistent') ?></th>
+ <td><?= $this->escape($this->comment->persistent) ? $this->translate('Yes') : $this->translate('No') ?></td>
+ </tr>
+
+ <tr>
+ <th><?= $this->translate('Created') ?></th>
+ <td><?= $this->formatDateTime($this->comment->timestamp) ?></td>
+ </tr>
+
+ <tr>
+ <th><?= $this->translate('Expires') ?></th>
+ <td>
+ <?= $this->comment->expiration ? sprintf(
+ $this->translate('This comment expires on %s at %s.'),
+ $this->formatDate($this->comment->expiration),
+ $this->formatTime($this->comment->expiration)
+ ) : $this->translate('This comment does not expire.');
+ ?>
+ </td>
+ </tr>
+
+ <tr>
+ <th><?= $this->translate('Comment') ?></th>
+ <td><?= $this->nl2br($this->createTicketLinks($this->markdown($comment->comment))) ?></td>
+ </tr>
+
+ <?php if (isset($delCommentForm)): // Form is unset if the current user lacks the respective permission ?>
+ <tr class="newsection">
+ <th><?= $this->translate('Commands') ?></th>
+ <td>
+ <?= $delCommentForm ?>
+ </td>
+ </tr>
+ <?php endif ?>
+
+ </tbody>
+</table>
+
+</div>
+
diff --git a/modules/monitoring/application/views/scripts/comments/delete-all.phtml b/modules/monitoring/application/views/scripts/comments/delete-all.phtml
new file mode 100644
index 0000000..698c4ee
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/comments/delete-all.phtml
@@ -0,0 +1,12 @@
+<div class="controls">
+
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+
+ <?= $this->render('partials/comment/comments-header.phtml'); ?>
+</div>
+
+<div class="content object-command">
+ <?= $delCommentForm ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/comments/show.phtml b/modules/monitoring/application/views/scripts/comments/show.phtml
new file mode 100644
index 0000000..67e1c6b
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/comments/show.phtml
@@ -0,0 +1,19 @@
+<div class="controls">
+<?php if (! $this->compact): ?>
+ <?= $this->tabs ?>
+<?php endif ?>
+ <?= $this->render('partials/comment/comments-header.phtml') ?>
+</div>
+
+<div class="content multi-commands">
+ <h2><?= $this->translate('Commands') ?></h2>
+ <?= $this->qlink(
+ sprintf($this->translate('Remove %d comments'), $comments->count()),
+ $removeAllLink,
+ null,
+ array(
+ 'icon' => 'trash',
+ 'title' => $this->translate('Remove all selected comments')
+ )
+ ) ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/config/form.phtml b/modules/monitoring/application/views/scripts/config/form.phtml
new file mode 100644
index 0000000..cbf0659
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/config/form.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $tabs->showOnlyCloseButton(); ?>
+</div>
+<div class="content">
+ <?= $form; ?>
+</div> \ No newline at end of file
diff --git a/modules/monitoring/application/views/scripts/config/index.phtml b/modules/monitoring/application/views/scripts/config/index.phtml
new file mode 100644
index 0000000..a1264c2
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/config/index.phtml
@@ -0,0 +1,78 @@
+<div class="controls">
+ <?= $tabs ?>
+</div>
+
+<div class="content" data-base-target="_next">
+ <div>
+ <h2><?= $this->translate('Monitoring Backends') ?></h2>
+ <?= $this->qlink(
+ $this->translate('Create a New Monitoring Backend') ,
+ 'monitoring/config/createbackend',
+ null,
+ array(
+ 'class' => 'button-link',
+ 'icon' => 'plus',
+ 'title' => $this->translate('Create a new monitoring backend')
+ )
+ ) ?>
+ <table class="table-row-selectable common-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Monitoring Backend') ?></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($this->backendsConfig as $backendName => $config): ?>
+ <tr>
+ <td>
+ <?= $this->qlink(
+ $backendName,
+ 'monitoring/config/editbackend',
+ array('backend-name' => $backendName),
+ array(
+ 'icon' => 'edit',
+ 'title' => sprintf($this->translate('Edit monitoring backend %s'), $backendName)
+ )
+ ) ?>
+ <span class="config-label-meta">&#40;<?= sprintf(
+ $this->translate('Type: %s'),
+ $this->escape($config->type === 'ido' ? 'IDO' : ucfirst($config->type))
+ ) ?>&#41;
+ </span>
+ </td>
+ <td class="text-right">
+ <?= $this->qlink(
+ '',
+ 'monitoring/config/removebackend',
+ array('backend-name' => $backendName),
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'cancel',
+ 'title' => sprintf($this->translate('Remove monitoring backend %s'), $backendName)
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+ </div>
+ <div>
+ <h2><?= $this->translate('Command Transports') ?></h2>
+ <?= $this->qlink(
+ $this->translate('Create a New Command Transport') ,
+ 'monitoring/config/createtransport',
+ null,
+ array(
+ 'class' => 'button-link',
+ 'icon' => 'plus',
+ 'title' => $this->translate('Create a new command transport')
+ )
+ ) ?>
+ <?php
+ /** @var \Icinga\Module\Monitoring\Forms\Config\TransportReorderForm $commandTransportReorderForm */
+ echo $commandTransportReorderForm;
+ ?>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/config/security.phtml b/modules/monitoring/application/views/scripts/config/security.phtml
new file mode 100644
index 0000000..3801678
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/config/security.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $tabs; ?>
+</div>
+<div class="content">
+ <?= $form; ?>
+</div> \ No newline at end of file
diff --git a/modules/monitoring/application/views/scripts/downtime/remove.phtml b/modules/monitoring/application/views/scripts/downtime/remove.phtml
new file mode 100644
index 0000000..34a7dbd
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/downtime/remove.phtml
@@ -0,0 +1,13 @@
+<div class="controls">
+
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+
+ <table>
+ <tr> <?= $this->render('partials/downtime/downtime-header.phtml') ?> </tr>
+ </table>
+</div>
+<div class="content object-command">
+ <?= $delDowntimeForm; ?>
+</div> \ No newline at end of file
diff --git a/modules/monitoring/application/views/scripts/downtime/show.phtml b/modules/monitoring/application/views/scripts/downtime/show.phtml
new file mode 100644
index 0000000..4db03cb
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/downtime/show.phtml
@@ -0,0 +1,173 @@
+<div class="controls">
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+
+ <table>
+ <tr> <?= $this->render('partials/downtime/downtime-header.phtml'); ?> </tr>
+ </table>
+</div>
+<div class="content"><h2><?= $this->translate('Details') ?></h2>
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th>
+ <?= $this->isService ? $this->translate('Service') : $this->translate('Host') ?>
+ </th>
+ <td data-base-target="_next">
+ <?php if ($this->isService): ?>
+ <?php
+ $link = $this->link()->service(
+ $downtime->service_description,
+ $downtime->service_display_name,
+ $downtime->host_name,
+ $downtime->host_display_name
+ );
+ $icon = $this->icon('service', $this->translate('Service'));
+ ?>
+ <?php else: ?>
+ <?php
+ $icon = $this->icon('host', $this->translate('Host'));
+ $link = $this->link()->host($downtime->host_name, $downtime->host_display_name)
+ ?>
+ <?php endif ?>
+ <?= $icon ?>
+ <?= $link ?>
+ </td>
+ </tr>
+ <tr title="<?= $this->translate('The name of the person who scheduled this downtime'); ?>">
+ <th><?= $this->translate('Author') ?></th>
+ <td><?= $this->icon('user', $this->translate('User')) ?> <?= $this->escape($this->downtime->author_name) ?></td>
+ </tr>
+ <tr title="<?= $this->translate('Date and time this downtime was entered'); ?>">
+ <th><?= $this->translate('Entry Time') ?></th>
+ <td><?= $this->formatDateTime($this->downtime->entry_time) ?></td>
+ </tr>
+ <tr title="<?= $this->translate('A comment, as entered by the author, associated with the scheduled downtime'); ?>">
+ <th><?= $this->translate('Comment') ?></th>
+ <td><?= $this->nl2br($this->createTicketLinks($this->markdown($downtime->comment))) ?></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h2> <?= $this->translate('Duration') ?> </h2>
+
+ <table class="name-value-table">
+ <tbody>
+ <tr class="newsection">
+ <th><?= $this->escape(
+ $this->downtime->is_flexible ?
+ $this->translate('Flexible') : $this->translate('Fixed')
+ ); ?>
+ <?= $this->icon('info-circled', $this->downtime->is_flexible ?
+ $this->translate('Flexible downtimes have a hard start and end time,'
+ . ' but also an additional restriction on the duration in which '
+ . ' the host or service may actually be down.') :
+ $this->translate('Fixed downtimes have a static start and end time.')) ?>
+ </th>
+ <td>
+ <?php if ($downtime->is_flexible): ?>
+ <?php if ($downtime->is_in_effect): ?>
+ <?= sprintf(
+ $isService
+ ? $this->translate('This flexible service downtime was started on %s at %s and lasts for %s until %s at %s.')
+ : $this->translate('This flexible host downtime was started on %s at %s and lasts for %s until %s at %s.'),
+ $this->formatDate($downtime->start),
+ $this->formatTime($downtime->start),
+ $this->formatDuration($downtime->duration),
+ $this->formatDate($downtime->end),
+ $this->formatTime($downtime->end)
+ ) ?>
+ <?php else: ?>
+ <?= sprintf(
+ $isService
+ ? $this->translate('This flexible service downtime has been scheduled to start between %s - %s and to last for %s.')
+ : $this->translate('This flexible host downtime has been scheduled to start between %s - %s and to last for %s.'),
+ $this->formatDateTime($downtime->scheduled_start),
+ $this->formatDateTime($downtime->scheduled_end),
+ $this->formatDuration($downtime->duration)
+ ) ?>
+ <?php endif ?>
+ <?php else: ?>
+ <?php if ($downtime->is_in_effect): ?>
+ <?= sprintf(
+ $isService
+ ? $this->translate('This fixed service downtime was started on %s at %s and expires on %s at %s.')
+ : $this->translate('This fixed host downtime was started on %s at %s and expires on %s at %s.'),
+ $this->formatDate($downtime->start),
+ $this->formatTime($downtime->start),
+ $this->formatDate($downtime->end),
+ $this->formatTime($downtime->end)
+ ) ?>
+ <?php else: ?>
+ <?= sprintf(
+ $isService
+ ? $this->translate('This fixed service downtime has been scheduled to start on %s at %s and to end on %s at %s.')
+ : $this->translate('This fixed host downtime has been scheduled to start on %s at %s and to end on %s at %s.'),
+ $this->formatDate($downtime->start),
+ $this->formatTime($downtime->start),
+ $this->formatDate($downtime->end),
+ $this->formatTime($downtime->end)
+ ) ?>
+ <?php endif ?>
+ <?php endif ?>
+ </td>
+ </tr>
+ <tr title="<?= $this->translate('The date/time the scheduled downtime is'
+ . ' supposed to start. If this is a flexible (non-fixed) downtime, '
+ . 'this refers to the earliest possible time that the downtime'
+ . ' can start'); ?>">
+ <th><?= $this->translate('Scheduled start') ?></th>
+ <td><?= $this->formatDateTime($this->downtime->scheduled_start) ?></td>
+ </tr>
+ <tr title="<?= $this->translate('The date/time the scheduled downtime is '
+ . 'supposed to end. If this is a flexible (non-fixed) downtime, '
+ . 'this refers to the last possible time that the downtime can '
+ . 'start'); ?>">
+ <th><?= $this->translate('Scheduled end') ?></th>
+ <td><?= $this->formatDateTime($this->downtime->scheduled_end) ?></td>
+ </tr>
+ <?php if ($this->downtime->is_flexible): ?>
+ <tr title="<?= $this->translate('Indicates the number of seconds that the '
+ . 'scheduled downtime should last. This is usually only needed if'
+ . ' this is a flexible downtime, which can start at a variable '
+ . 'time, but lasts for the specified duration'); ?>">
+ <th tit><?= $this->translate('Duration') ?></th>
+ <td><?= $this->formatDuration($this->downtime->duration) ?></td>
+ </tr>
+ <tr title="<?= $this->translate('he date/time the scheduled downtime was'
+ . ' actually started'); ?>">
+ <th><?= $this->translate('Actual start time') ?></th>
+ <td><?= $this->formatDateTime($downtime->start) ?></td>
+ </tr>
+ <tr title="<?= $this->translate('The date/time the scheduled downtime '
+ . 'actually ended'); ?>">
+ <th><?= $this->translate('Actual end time') ?></th>
+ <td><?= $this->formatDateTime($downtime->end) ?></td>
+ </tr>
+ <?php endif; ?>
+
+ <tr class="newsection">
+ <th><?= $this->translate('In effect') ?></th>
+ <td>
+ <?= $this->escape(
+ $this->downtime->is_in_effect ?
+ $this->translate('Yes') : $this->translate('No')
+ );
+ ?>
+ </td>
+ </tr>
+
+ <?php if (isset($delDowntimeForm)): // Form is unset if the current user lacks the respective permission ?>
+ <tr class="newsection">
+ <th><?= $this->translate('Commands') ?></th>
+ <td>
+ <?= $delDowntimeForm ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ </tbody>
+ </table>
+
+</div>
+
diff --git a/modules/monitoring/application/views/scripts/downtimes/delete-all.phtml b/modules/monitoring/application/views/scripts/downtimes/delete-all.phtml
new file mode 100644
index 0000000..e6435fe
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/downtimes/delete-all.phtml
@@ -0,0 +1,12 @@
+<div class="controls">
+
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+
+ <?= $this->render('partials/downtime/downtimes-header.phtml'); ?>
+</div>
+
+<div class="content object-command">
+ <?= $delAllDowntimeForm ?>
+</div> \ No newline at end of file
diff --git a/modules/monitoring/application/views/scripts/downtimes/show.phtml b/modules/monitoring/application/views/scripts/downtimes/show.phtml
new file mode 100644
index 0000000..73d9bf6
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/downtimes/show.phtml
@@ -0,0 +1,19 @@
+<div class="controls">
+<?php if (! $this->compact): ?>
+ <?= $this->tabs ?>
+<?php endif ?>
+ <?= $this->render('partials/downtime/downtimes-header.phtml') ?>
+</div>
+
+<div class="content multi-commands">
+ <h2> <?= $this->translate('Commands') ?> </h2>
+ <?= $this->qlink(
+ sprintf($this->translate('Remove all %d scheduled downtimes'), $downtimes->count()),
+ $removeAllLink,
+ null,
+ array(
+ 'icon' => 'trash',
+ 'title' => $this->translate('Remove all selected downtimes')
+ )
+ ) ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/event/show.phtml b/modules/monitoring/application/views/scripts/event/show.phtml
new file mode 100644
index 0000000..c844a6f
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/event/show.phtml
@@ -0,0 +1,34 @@
+<?php
+use Icinga\Module\Monitoring\Object\Service;
+
+/** @var string[][] $details */
+/** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+/** @var \Icinga\Web\View $this */
+?>
+<div class="controls">
+<?php
+if (! $this->compact) {
+ echo $this->tabs;
+}
+
+echo $object instanceof Service
+ ? '<h2>' . $this->translate('Current Service State') . '</h2>' . $this->render('partials/object/service-header.phtml')
+ : '<h2>' . $this->translate('Current Host State') . '</h2>' . $this->render('partials/object/host-header.phtml');
+?>
+</div>
+<div class="content">
+ <?php
+ foreach ($extensionsHtml as $extensionHtml) {
+ echo $extensionHtml;
+ }
+ ?>
+
+ <h2><?= $this->escape($this->translate('Event Details')) ?></h2>
+ <table class="event-details name-value-table" data-base-target="_next">
+ <?php
+ foreach ($details as $detail) {
+ echo '<tr><th>' . $this->escape($detail[0]) . '</th><td>' . $detail[1] . '</td></tr>';
+ }
+ ?>
+ </table>
+</div>
diff --git a/modules/monitoring/application/views/scripts/form/reorder-command-transports.phtml b/modules/monitoring/application/views/scripts/form/reorder-command-transports.phtml
new file mode 100644
index 0000000..2f81610
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/form/reorder-command-transports.phtml
@@ -0,0 +1,93 @@
+<?php
+/** @var \Icinga\Web\View $this */
+/** @var \Icinga\Module\Monitoring\Forms\Config\TransportReorderForm $form */
+?>
+<form id="<?=
+$this->escape($form->getId())
+?>" name="<?=
+$this->escape($form->getName())
+?>" enctype="<?=
+$this->escape($form->getEncType())
+?>" method="<?=
+$this->escape($form->getMethod())
+?>" action="<?=
+$this->escape($form->getAction())
+?>">
+ <table class="table-row-selectable common-table" data-base-target="_next">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Transport') ?></th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ $i = -1;
+ $transportConfig = $form->getConfig();
+ $total = $transportConfig->count();
+ foreach ($transportConfig as $transportName => $config):
+ ++$i;
+ ?>
+ <tr>
+ <td>
+ <?= $this->qlink(
+ $transportName,
+ 'monitoring/config/edittransport',
+ array('transport' => $transportName),
+ array(
+ 'icon' => 'edit',
+ 'title' => sprintf($this->translate('Edit command transport %s'), $transportName)
+ )
+ ); ?>
+ <span class="config-label-meta">&#40;<?= sprintf(
+ $this->translate('Type: %s'),
+ ucfirst($config->get('transport', 'local'))
+ ) ?>&#41;
+ </span>
+ </td>
+ <td class="text-right">
+ <?= $this->qlink(
+ '',
+ 'monitoring/config/removetransport',
+ array('transport' => $transportName),
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'cancel',
+ 'title' => sprintf($this->translate('Remove command transport %s'), $transportName)
+ )
+ ); ?>
+ </td>
+ <td class="icon-col text-right" data-base-target="_self">
+ <?php if ($i > 0): ?>
+ <button type="submit" name="transport_newpos" class="link-button icon-only animated move-up" value="<?= $this->escape(
+ ($i - 1) . '|' . $transportName
+ ) ?>" title="<?= $this->translate(
+ 'Move up in order'
+ ) ?>" aria-label="<?= $this->escape(sprintf(
+ $this->translate('Move command transport %s upwards'),
+ $transportName
+ )) ?>"><?=
+ $this->icon('up-small')
+ ?></button>
+ <?php endif ?>
+ <?php if ($i + 1 < $total): ?>
+ <button type="submit" name="transport_newpos" class="link-button icon-only animated move-down" value="<?= $this->escape(
+ ($i + 1) . '|' . $transportName
+ ) ?>" title="<?= $this->translate(
+ 'Move down in order'
+ ) ?>" aria-label="<?= $this->escape(sprintf(
+ $this->translate('Move command transport %s downwards'),
+ $transportName
+ )) ?>"><?=
+ $this->icon('down-small')
+ ?></button>
+ <?php endif ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+ <?= $form->getElement($form->getTokenElementName()) ?>
+ <?= $form->getElement($form->getUidElementName()) ?>
+</form>
diff --git a/modules/monitoring/application/views/scripts/health/disable-notifications.phtml b/modules/monitoring/application/views/scripts/health/disable-notifications.phtml
new file mode 100644
index 0000000..e8c75e5
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/health/disable-notifications.phtml
@@ -0,0 +1,20 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs->showOnlyCloseButton(); ?>
+</div>
+<?php endif ?>
+<div class="content">
+ <h1><?= $title; ?></h1>
+ <?php if ((bool) $programStatus->notifications_enabled === false): ?>
+ <div>
+ <?= $this->translate('Host and service notifications are already disabled.') ?>
+ <?php if ($this->programStatus->disable_notif_expire_time): ?>
+ <?= sprintf(
+ $this->translate('Notifications will be re-enabled in <strong>%s</strong>.'),
+ $this->timeUntil($this->programStatus->disable_notif_expire_time)); ?>
+ <?php endif; ?>
+ </div>
+ <?php else: ?>
+ <?= $form; ?>
+ <?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/health/info.phtml b/modules/monitoring/application/views/scripts/health/info.phtml
new file mode 100644
index 0000000..76d9ee3
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/health/info.phtml
@@ -0,0 +1,87 @@
+<?php
+$rv = $this->runtimeVariables()->create($this->runtimevariables);
+$cp = $this->checkPerformance()->create($this->checkperformance);
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs; ?>
+</div>
+<?php endif ?>
+
+<div class="content processinfo">
+ <div class="boxview">
+ <div class="box process">
+ <h2 tabindex="0"><?= $this->translate('Process Info') ?></h2>
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th><?= $this->translate('Program Version') ?></th>
+ <td><?= $this->programStatus->program_version
+ ? $this->programStatus->program_version
+ : $this->translate('N/A') ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Program Start Time') ?></th>
+ <td><?= $this->formatDateTime($this->programStatus->program_start_time) ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Last Status Update'); ?></th>
+ <td><?= $this->timeAgo($this->programStatus->status_update_time); ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Last External Command Check'); ?></th>
+ <td><?= $this->timeAgo($this->programStatus->last_command_check); ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Last Log File Rotation'); ?></th>
+ <td><?= $this->programStatus->last_log_rotation
+ ? $this->timeSince($this->programStatus->last_log_rotation)
+ : $this->translate('N/A') ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Global Service Event Handler'); ?></th>
+ <td><?= $this->programStatus->global_service_event_handler
+ ? $this->programStatus->global_service_event_handler
+ : $this->translate('N/A'); ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Global Host Event Handler'); ?></th>
+ <td><?= $this->programStatus->global_host_event_handler
+ ? $this->programStatus->global_host_event_handler
+ : $this->translate('N/A'); ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Active Endpoint'); ?></th>
+ <td><?= $this->programStatus->endpoint_name
+ ? $this->programStatus->endpoint_name
+ : $this->translate('N/A') ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Active Icinga Web 2 Endpoint'); ?></th>
+ <td><?= gethostname() ?: $this->translate('N/A') ?></td>
+ </tr>
+ </tbody>
+ </table>
+ <?php if ((bool) $this->programStatus->is_currently_running === true): ?>
+ <div class="backend-running">
+ <?= sprintf(
+ $this->translate(
+ '%1$s has been up and running with PID %2$d %3$s',
+ 'Last format parameter represents the time running'
+ ),
+ $this->backendName,
+ $this->programStatus->process_id,
+ $this->timeSince($this->programStatus->program_start_time)) ?>
+ </div>
+ <?php else: ?>
+ <div class="backend-not-running">
+ <?= sprintf($this->translate('Backend %s is not running'), $this->backendName) ?>
+ </div>
+ <?php endif ?>
+ </div>
+ <div class="box features">
+ <h2 tabindex="0"><?= $this->translate('Feature Commands') ?></h2>
+ <?= $this->toggleFeaturesForm ?>
+ </div>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/health/not-running.phtml b/modules/monitoring/application/views/scripts/health/not-running.phtml
new file mode 100644
index 0000000..8439fc4
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/health/not-running.phtml
@@ -0,0 +1,8 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs; ?>
+</div>
+<?php endif ?>
+<div class="content">
+ <?= sprintf($this->translate('%s is currently not up and running'), $this->backendName) ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/health/stats.phtml b/modules/monitoring/application/views/scripts/health/stats.phtml
new file mode 100644
index 0000000..5cfb8f9
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/health/stats.phtml
@@ -0,0 +1,150 @@
+<?php
+$rv = $this->runtimeVariables()->create($this->runtimevariables);
+$cp = $this->checkPerformance()->create($this->checkperformance);
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+</div>
+<?php endif ?>
+
+<div class="content stats">
+ <div class="boxview">
+ <div class="box stats">
+ <h2 tabindex="0"><?= $this->unhandledProblems ?> <?= $this->translate('Unhandled Problems:') ?></h2>
+ <table class="name-value-table">
+ <thead>
+ <th></th>
+ <th colspan="3"></th>
+ </thead>
+ <tbody>
+ <tr>
+ <th><?= $this->translate('Service Problems:') ?></th>
+ <td colspan="3">
+ <span class="badge state-critical">
+ <?=
+ $this->qlink(
+ $this->unhandledServiceProblems,
+ 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity',
+ null,
+ array('data-base-target' => '_next')
+ )
+ ?>
+ </span>
+ </td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Host Problems:') ?></th>
+ <td colspan="3">
+ <span class="badge state-critical">
+ <?=
+ $this->qlink(
+ $this->unhandledhostProblems,
+ 'monitoring/list/hosts?host_problem=1&host_handled=0',
+ null,
+ array('data-base-target' => '_next')
+ )
+ ?>
+ </span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h2 tabindex="0" class="tinystatesummary" data-base-target="_next">
+ <?php $this->stats = $hoststats ?>
+ <?= $this->render('list/components/hostssummary.phtml') ?>
+ </h2>
+ <table class="name-value-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Runtime Variables') ?></th>
+ <th colspan="3"><?= $this->translate('Host Checks') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th><?= $this->translate('Total') ?></th>
+ <td><?= $rv->total_scheduled_hosts ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Scheduled') ?></th>
+ <td><?= $rv->total_scheduled_hosts ?></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h2 class="tinystatesummary" data-base-target="_next">
+ <?php $this->stats = $servicestats ?>
+ <?= $this->render('list/components/servicesummary.phtml') ?>
+ </h2>
+ <table class="name-value-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Runtime Variables') ?></th>
+ <th><?= $this->translate('Service Checks') ?></th>
+ <th colspan="2"><?= $this->translate('Per Host') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th><?= $this->translate('Total') ?></th>
+ <td><?= $rv->total_services ?></td>
+ <td><?= sprintf('%.2f', $rv->average_services_per_host) ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Scheduled') ?></th>
+ <td><?= $rv->total_scheduled_services ?></td>
+ <td><?= sprintf('%.2f', $rv->average_scheduled_services_per_host) ?></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h2><?= $this->translate('Active checks') ?></h2>
+ <table class="name-value-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Check Performance') ?></th>
+ <th><?= $this->translate('Checks') ?></th>
+ <th><?= $this->translate('Latency') ?></th>
+ <th><?= $this->translate('Execution time') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th><?= $this->translate('Host Checks') ?></th>
+ <td><?= $cp->host_active_count; ?></td>
+ <td><?= sprintf('%.3f', $cp->host_active_latency_avg) ?>s</td>
+ <td><?= sprintf('%.3f', $cp->host_active_execution_avg) ?>s</td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Service Checks') ?></th>
+ <td><?= $cp->service_active_count; ?></td>
+ <td><?= sprintf('%.3f', $cp->service_active_latency_avg) ?>s</td>
+ <td><?= sprintf('%.3f', $cp->service_active_execution_avg) ?>s</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h2><?= $this->translate('Passive checks') ?></h2>
+ <table class="name-value-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Check Performance') ?></th>
+ <th colspan="3"><?= $this->translate('Passive Checks') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th><?= $this->translate('Host Checks') ?></th>
+ <td><?= $cp->host_passive_count ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Service Checks') ?></th>
+ <td><?= $cp->service_passive_count ?></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/host/services.phtml b/modules/monitoring/application/views/scripts/host/services.phtml
new file mode 100644
index 0000000..ac1dc5b
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/host/services.phtml
@@ -0,0 +1,23 @@
+<?php use Icinga\Data\Filter\Filter; ?>
+
+<div class="controls">
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+ <?= $this->render('partials/object/host-header.phtml') ?>
+ <?php
+ $this->baseFilter = Filter::where('host', $object->host_name);
+ $this->stats = $object->stats;
+ echo $this->render('list/components/servicesummary.phtml');
+ ?>
+</div>
+<?= $this->partial(
+ 'list/services.phtml',
+ 'monitoring',
+ array(
+ 'compact' => true,
+ 'showHost' => false,
+ 'services' => $services,
+ 'addColumns' => array()
+ )
+); ?>
diff --git a/modules/monitoring/application/views/scripts/host/show.phtml b/modules/monitoring/application/views/scripts/host/show.phtml
new file mode 100644
index 0000000..72f5af4
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/host/show.phtml
@@ -0,0 +1,14 @@
+<?php use Icinga\Data\Filter\Filter; ?>
+<div class="controls controls-separated">
+<?php if (! $this->compact): ?>
+ <?= $this->tabs ?>
+<?php endif ?>
+ <?= $this->render('partials/object/host-header.phtml') ?>
+<?php
+ $this->stats = $object->stats;
+ $this->baseFilter = Filter::where('host', $object->host_name);
+ echo $this->render('list/components/servicesummary.phtml');
+?>
+ <?= $this->render('partials/object/quick-actions.phtml') ?>
+</div>
+<?= $this->render('partials/object/detail-content.phtml') ?>
diff --git a/modules/monitoring/application/views/scripts/hosts/show.phtml b/modules/monitoring/application/views/scripts/hosts/show.phtml
new file mode 100644
index 0000000..97b8434
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/hosts/show.phtml
@@ -0,0 +1,206 @@
+<div class="controls">
+ <?php if (! $this->compact): ?>
+ <?= $tabs; ?>
+ <?php endif ?>
+ <?= $this->render('list/components/hostssummary.phtml') ?>
+ <?= $this->render('partials/host/objects-header.phtml'); ?>
+ <?php
+ $hostCount = count($objects);
+ $unhandledCount = count($unhandledObjects);
+ $problemCount = count($problemObjects);
+ $unackCount = count($unacknowledgedObjects);
+ $scheduledDowntimeCount = count($objects->getScheduledDowntimes());
+ ?>
+</div>
+
+<div class="content">
+ <?php if ($hostCount === 0): ?>
+ <?= $this->translate('No hosts found matching the filter'); ?>
+ <?php else: ?>
+ <?= $this->render('show/components/extensions.phtml') ?>
+ <h2><?= $this->translate('Problem Handling') ?></h2>
+ <table class="name-value-table">
+ <tbody>
+ <?php
+
+ if ($unackCount > 0): ?>
+ <tr>
+ <th> <?= sprintf($this->translate('%d unhandled problems'), $unackCount) ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Acknowledge'),
+ $acknowledgeLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'check'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif; ?>
+
+ <?php if (($acknowledgedCount = count($acknowledgedObjects)) > 0): ?>
+ <tr>
+ <th> <?= sprintf(
+ $this->translatePlural(
+ '%s acknowledgement',
+ '%s acknowledgements',
+ $acknowledgedCount
+ ),
+ '<b>' . $acknowledgedCount . '</b>'
+ ); ?> </th>
+ <td>
+ <?= $removeAckForm->setLabelEnabled(true) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+
+ <tr>
+ <th> <?= $this->translate('Comments') ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Add comments'),
+ $addCommentLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'comment-empty'
+ )
+ ) ?>
+ </td>
+ </tr>
+
+ <?php if (($commentCount = count($objects->getComments())) > 0): ?>
+ <tr>
+ <th></th>
+ <td>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%s comment',
+ '%s comments',
+ $commentCount
+ ),
+ $commentCount
+ ),
+ $commentsLink,
+ null,
+ array('data-base-target' => '_next')
+ ); ?>
+ </td>
+ </tr>
+ <?php endif ?>
+
+ <tr>
+ <th>
+ <?= $this->translate('Downtimes') ?>
+ </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Schedule downtimes'),
+ $downtimeAllLink,
+ null,
+ array(
+ 'icon' => 'plug',
+ 'class' => 'action-link'
+ )
+ ) ?>
+ </td>
+ </tr>
+
+ <?php if ($scheduledDowntimeCount > 0): ?>
+ <tr>
+ <th></th>
+ <td>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%d scheduled downtime',
+ '%d scheduled downtimes',
+ $scheduledDowntimeCount
+ ),
+ $scheduledDowntimeCount
+ ),
+ $showDowntimesLink,
+ null,
+ array(
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ </tbody>
+ </table>
+
+ <?php if ($this->hasPermission('monitoring/command/send-custom-notification')): ?>
+ <h2> <?= $this->translate('Notifications') ?> </h2>
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th> <?= $this->translate('Notifications') ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Send notifications'),
+ $sendCustomNotificationLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'bell'
+ )
+ ) ?>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <?php endif ?>
+
+ <h2> <?= $this->translate('Check Execution') ?> </h2>
+
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th> <?= $this->translate('Command') ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Process check result'),
+ $processCheckResultAllLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'edit'
+ )
+ ) ?>
+ </td>
+ </tr>
+
+ <?php if (isset($checkNowForm)): // Form is unset if the current user lacks the respective permission ?>
+ <tr>
+ <th> <?= $this->translate('Schedule Check') ?> </th>
+ <td> <?= $checkNowForm ?> </td>
+ </tr>
+ <?php endif ?>
+
+ <?php if (isset($rescheduleAllLink)): ?>
+ <tr>
+ <th></th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Reschedule'),
+ $rescheduleAllLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'calendar-empty'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ </tbody>
+ </table>
+ <h2><?= $this->translate('Feature Commands') ?></h2>
+ <?= $toggleFeaturesForm ?>
+ <?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/comments.phtml b/modules/monitoring/application/views/scripts/list/comments.phtml
new file mode 100644
index 0000000..c7fb86a
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/comments.phtml
@@ -0,0 +1,61 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->render('list/components/selectioninfo.phtml') ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $comments->hasResult()): ?>
+ <p><?= $this->translate('No comments found matching the filter') ?></p>
+</div>
+<?php return; endif ?>
+ <table data-base-target="_next"
+ class="table-row-selectable common-table multiselect"
+ data-icinga-multiselect-url="<?= $this->href('monitoring/comments/show') ?>"
+ data-icinga-multiselect-related="<?= $this->href("monitoring/comments") ?>"
+ data-icinga-multiselect-data="comment_id">
+ <thead class="print-only">
+ <tr>
+ <th><?= $this->translate('Type') ?></th>
+ <th><?= $this->translate('Comment') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($comments->peekAhead($this->compact) as $comment): ?>
+ <tr href="<?= $this->href('monitoring/comment/show', array('comment_id' => $comment->id)) ?>">
+ <td class="icon-col">
+ <?= $this->partial('partials/comment/comment-description.phtml', array('comment' => $comment)) ?>
+ </td>
+ <td>
+ <?= $this->partial(
+ 'partials/comment/comment-detail.phtml',
+ array(
+ 'comment' => $comment,
+ 'delCommentForm' => isset($delCommentForm) ? $delCommentForm : null
+ // Form is unset if the current user lacks the respective permission
+ )) ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($comments->hasMore()): ?>
+ <div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml b/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml
new file mode 100644
index 0000000..4b9f1cd
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml
@@ -0,0 +1,92 @@
+<?php
+use Icinga\Module\Monitoring\Web\Widget\StateBadges;
+use Icinga\Web\Url;
+
+// Don't fetch rows until they are actually needed to improve dashlet performance
+if (! $stats instanceof stdClass) {
+ $stats = $stats->fetchRow();
+}
+?>
+<div class="hosts-summary dont-print">
+ <span class="hosts-link"><?= $this->qlink(
+ sprintf($this->translatePlural('%u Host', '%u Hosts', $stats->hosts_total), $stats->hosts_total),
+ // @TODO(el): Fix that
+ Url::fromPath('monitoring/list/hosts')->setParams(isset($baseFilter) ? $baseFilter->getUrlParams() : array()),
+ null,
+ array('title' => sprintf(
+ $this->translatePlural('List %u host', 'List all %u hosts', $stats->hosts_total),
+ $stats->hosts_total
+ ))
+ ) ?>&#58;</span>
+<?php
+$stateBadges = new StateBadges();
+$stateBadges
+ ->setBaseFilter(isset($baseFilter) ? $baseFilter : null)
+ ->setUrl('monitoring/list/hosts')
+ ->add(
+ StateBadges::STATE_UP,
+ $stats->hosts_up,
+ array(
+ 'host_state' => 0
+ ),
+ 'List %u host that is currently in state UP',
+ 'List %u hosts which are currently in state UP',
+ array($stats->hosts_up)
+ )
+ ->add(
+ StateBadges::STATE_DOWN,
+ $stats->hosts_down_unhandled,
+ array(
+ 'host_state' => 1,
+ 'host_handled' => 0
+ ),
+ 'List %u host that is currently in state DOWN',
+ 'List %u hosts which are currently in state DOWN',
+ array($stats->hosts_down_unhandled)
+ )
+ ->add(
+ StateBadges::STATE_DOWN_HANDLED,
+ $stats->hosts_down_handled,
+ array(
+ 'host_state' => 1,
+ 'host_handled' => 1
+ ),
+ 'List %u host that is currently in state DOWN (Acknowledged)',
+ 'List %u hosts which are currently in state DOWN (Acknowledged)',
+ array($stats->hosts_down_handled)
+ )
+ ->add(
+ StateBadges::STATE_UNREACHABLE,
+ $stats->hosts_unreachable_unhandled,
+ array(
+ 'host_state' => 2,
+ 'host_handled' => 0
+ ),
+ 'List %u host that is currently in state UNREACHABLE',
+ 'List %u hosts which are currently in state UNREACHABLE',
+ array($stats->hosts_unreachable_unhandled)
+ )
+ ->add(
+ StateBadges::STATE_UNREACHABLE_HANDLED,
+ $stats->hosts_unreachable_handled,
+ array(
+ 'host_state' => 2,
+ 'host_handled' => 1
+ ),
+ 'List %u host that is currently in state UNREACHABLE (Acknowledged)',
+ 'List %u hosts which are currently in state UNREACHABLE (Acknowledged)',
+ array($stats->hosts_unreachable_handled)
+ )
+ ->add(
+ StateBadges::STATE_PENDING,
+ $stats->hosts_pending,
+ array(
+ 'host_state' => 99
+ ),
+ 'List %u host that is currently in state PENDING',
+ 'List %u hosts which are currently in state PENDING',
+ array($stats->hosts_pending)
+ );
+echo $stateBadges->render();
+?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/components/selectioninfo.phtml b/modules/monitoring/application/views/scripts/list/components/selectioninfo.phtml
new file mode 100644
index 0000000..ec0fb85
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/components/selectioninfo.phtml
@@ -0,0 +1,15 @@
+<?php
+$helpMessage = $this->translate(
+ 'Press and hold the Ctrl key while clicking on rows to select multiple rows or press and hold the Shift key to'
+ .' select a range of rows',
+ 'Multi-selection help'
+);
+?>
+<div class="selection-info" title="<?= $this->escape($helpMessage) ?>">
+ <?= sprintf(
+ /// TRANSLATORS: Please leave %s as it is because the selection counter is wrapped in a span tag for updating
+ /// the counter via JavaScript
+ $this->translate('%s row(s) selected', 'Multi-selection count'),
+ '<span class="selection-info-count">0</span>'
+ ) ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml b/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml
new file mode 100644
index 0000000..73a3b57
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml
@@ -0,0 +1,118 @@
+<?php
+use Icinga\Module\Monitoring\Web\Widget\StateBadges;
+use Icinga\Web\Url;
+
+// Don't fetch rows until they are actually needed, to improve dashlet performance
+if (! $stats instanceof stdClass) {
+ $stats = $stats->fetchRow();
+}
+?>
+<div class="services-summary dont-print">
+ <span class="services-link"><?= $this->qlink(
+ sprintf($this->translatePlural(
+ '%u Service', '%u Services', $stats->services_total),
+ $stats->services_total
+ ),
+ // @TODO(el): Fix that
+ Url::fromPath('monitoring/list/services')->setParams(isset($baseFilter) ? $baseFilter->getUrlParams() : array()),
+ null,
+ array('title' => sprintf(
+ $this->translatePlural('List %u service', 'List all %u services', $stats->services_total),
+ $stats->services_total
+ ))
+ ) ?>&#58;</span>
+<?php
+$stateBadges = new StateBadges();
+$stateBadges
+ ->setBaseFilter(isset($baseFilter) ? $baseFilter : null)
+ ->setUrl('monitoring/list/services')
+ ->add(
+ StateBadges::STATE_OK,
+ $stats->services_ok,
+ array(
+ 'service_state' => 0
+ ),
+ 'List %u service that is currently in state OK',
+ 'List %u services which are currently in state OK',
+ array($stats->services_ok)
+ )
+ ->add(
+ StateBadges::STATE_CRITICAL,
+ $stats->services_critical_unhandled,
+ array(
+ 'service_state' => 2,
+ 'service_handled' => 0
+ ),
+ 'List %u service that is currently in state CRITICAL',
+ 'List %u services which are currently in state CRITICAL',
+ array($stats->services_critical_unhandled)
+ )
+ ->add(
+ StateBadges::STATE_CRITICAL_HANDLED,
+ $stats->services_critical_handled,
+ array(
+ 'service_state' => 2,
+ 'service_handled' => 1
+ ),
+ 'List %u handled service that is currently in state CRITICAL',
+ 'List %u handled services which are currently in state CRITICAL',
+ array($stats->services_critical_handled)
+ )
+ ->add(
+ StateBadges::STATE_UNKNOWN,
+ $stats->services_unknown_unhandled,
+ array(
+ 'service_state' => 3,
+ 'service_handled' => 0
+ ),
+ 'List %u service that is currently in state UNKNOWN',
+ 'List %u services which are currently in state UNKNOWN',
+ array($stats->services_unknown_unhandled)
+ )
+ ->add(
+ StateBadges::STATE_UNKNOWN_HANDLED,
+ $stats->services_unknown_handled,
+ array(
+ 'service_state' => 3,
+ 'service_handled' => 1
+ ),
+ 'List %u handled service that is currently in state UNKNOWN',
+ 'List %u handled services which are currently in state UNKNOWN',
+ array($stats->services_unknown_handled)
+
+ )
+ ->add(
+ StateBadges::STATE_WARNING,
+ $stats->services_warning_unhandled,
+ array(
+ 'service_state' => 1,
+ 'service_handled' => 0
+ ),
+ 'List %u service that is currently in state WARNING',
+ 'List %u services which are currently in state WARNING',
+ array($stats->services_warning_unhandled)
+ )
+ ->add(
+ StateBadges::STATE_WARNING_HANDLED,
+ $stats->services_warning_handled,
+ array(
+ 'service_state' => 1,
+ 'service_handled' => 1
+ ),
+ 'List %u handled service that is currently in state WARNING',
+ 'List %u handled services which are currently in state WARNING',
+ array($stats->services_warning_handled)
+ )
+ ->add(
+ StateBadges::STATE_PENDING,
+ $stats->services_pending,
+ array(
+ 'service_state' => 99
+ ),
+ 'List %u handled service that is currently in state PENDING',
+ 'List %u handled services which are currently in state PENDING',
+ array($stats->services_pending)
+ );
+echo $stateBadges->render();
+?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/contactgroups.phtml b/modules/monitoring/application/views/scripts/list/contactgroups.phtml
new file mode 100644
index 0000000..125aeea
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/contactgroups.phtml
@@ -0,0 +1,53 @@
+<?php
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $contactGroups->hasResult()): ?>
+ <p><?= $this->translate('No contact groups found matching the filter') ?></p>
+</div>
+<?php return; endif ?>
+ <table class="common-table table-row-selectable" data-base-target="_next">
+ <thead>
+ <tr>
+ <th></th>
+ <th><?= $this->translate('Contact Group') ?></th>
+ <th><?= $this->translate('Alias') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($contactGroups as $contactGroup): ?>
+ <tr>
+ <td class="count-col">
+ <span class="badge"><?= $contactGroup->contact_count ?></span>
+ </td>
+ <th>
+ <?= $this->qlink(
+ $contactGroup->contactgroup_name,
+ 'monitoring/list/contacts',
+ array('contactgroup_name' => $contactGroup->contactgroup_name),
+ array('title' => sprintf(
+ $this->translate('Show detailed information about %s'),
+ $contactGroup->contactgroup_name
+ ))
+ ) ?>
+ </th>
+ <td>
+ <?php if ($contactGroup->contactgroup_name !== $contactGroup->contactgroup_alias): ?>
+ <?= $contactGroup->contactgroup_alias ?>
+ <?php endif ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/contacts.phtml b/modules/monitoring/application/views/scripts/list/contacts.phtml
new file mode 100644
index 0000000..42ec778
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/contacts.phtml
@@ -0,0 +1,83 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $contacts->hasResult()): ?>
+ <p><?= $this->translate('No contacts found matching the filter') ?></p>
+</div>
+<?php return; endif ?>
+ <table class="common-table table-row-selectable" data-base-target="_next">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Name') ?></th>
+ <th><?= $this->translate('Email') ?></th>
+ <th><?= $this->translate('Pager') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($contacts->peekAhead($this->compact) as $contact): ?>
+ <tr>
+ <th>
+ <?= $this->qlink(
+ $contact->contact_name,
+ 'monitoring/show/contact',
+ array('contact_name' => $contact->contact_name),
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information about %s'),
+ $contact->contact_alias
+ )
+ )
+ ) ?>
+ </th>
+ <td>
+ <?= $this->translate('Email') ?>:
+ <a href="mailto:<?= $contact->contact_email ?>"
+ title="<?= sprintf($this->translate('Send a mail to %s'), $contact->contact_alias) ?>"
+ aria-label="<?= sprintf($this->translate('Send a mail to %s'), $contact->contact_alias) ?>">
+ <?= $this->escape($contact->contact_email) ?>
+ </a>
+ </td>
+ <td>
+ <?php if ($contact->contact_pager): ?>
+ <?= $this->escape($contact->contact_pager) ?>
+ <?php endif ?>
+ </td>
+
+ <?php if ($contact->contact_notify_service_timeperiod): ?>
+ <td>
+ <?= $this->escape($contact->contact_notify_service_timeperiod) ?>
+ </td>
+ <?php endif ?>
+
+ <?php if ($contact->contact_notify_host_timeperiod): ?>
+ <td>
+ <?= $this->escape($contact->contact_notify_host_timeperiod) ?>
+ </td>
+ <?php endif ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($contacts->hasMore()): ?>
+ <div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/downtimes.phtml b/modules/monitoring/application/views/scripts/list/downtimes.phtml
new file mode 100644
index 0000000..46ce0bb
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/downtimes.phtml
@@ -0,0 +1,64 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->render('list/components/selectioninfo.phtml') ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $downtimes->hasResult()): ?>
+ <p><?= $this->translate('No downtimes found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <table class="common-table state-table table-row-selectable multiselect"
+ data-base-target="_next"
+ data-icinga-multiselect-url="<?= $this->href('monitoring/downtimes/show') ?>"
+ data-icinga-multiselect-controllers="<?= $this->href("monitoring/downtimes") ?>"
+ data-icinga-multiselect-data="downtime_id">
+ <thead class="print-only">
+ <tr>
+ <th><?= $this->translate('State') ?></th>
+ <th><?= $this->translate('Downtime') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($downtimes->peekAhead($this->compact) as $downtime):
+ if (isset($downtime->service_description)) {
+ $this->isService = true;
+ $this->stateName = Service::getStateText($downtime->service_state);
+ } else {
+ $this->isService = false;
+ $this->stateName = Host::getStateText($downtime->host_state);
+ }
+ // Set downtime for partials
+ $this->downtime = $downtime;
+ ?>
+ <tr href="<?= $this->href('monitoring/downtime/show', array('downtime_id' => $downtime->id)) ?>">
+ <?= $this->render('partials/downtime/downtime-header.phtml') ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($downtimes->hasMore()): ?>
+ <div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/eventgrid.phtml b/modules/monitoring/application/views/scripts/list/eventgrid.phtml
new file mode 100644
index 0000000..8809c53
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/eventgrid.phtml
@@ -0,0 +1,123 @@
+<?php
+use Icinga\Data\Filter\Filter;
+use Icinga\Web\Widget\Chart\HistoryColorGrid;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->form ?>
+</div>
+<?php endif ?>
+<div class="content" data-base-target="_next">
+<?php
+
+$settings = array(
+ 'cnt_up' => array(
+ 'tooltip' => $this->translate('%d hosts ok on %s'),
+ 'color' => '#49DF96',
+ 'opacity' => '0.55'
+ ),
+ 'cnt_unreachable_hard' => array(
+ 'tooltip' => $this->translate('%d hosts unreachable on %s'),
+ 'color' => '#77AAFF',
+ 'opacity' => '0.55'
+ ),
+ 'cnt_critical_hard' => array(
+ 'tooltip' => $this->translate('%d services critical on %s'),
+ 'color' => '#ff5566',
+ 'opacity' => '0.9'
+ ),
+
+ 'cnt_warning_hard' => array(
+ 'tooltip' => $this->translate('%d services warning on %s'),
+ 'color' => '#ffaa44',
+ 'opacity' => '1.0'
+ ),
+
+ 'cnt_down_hard' => array(
+ 'tooltip' => $this->translate('%d hosts down on %s'),
+ 'color' => '#ff5566',
+ 'opacity' => '0.9'
+ ),
+ 'cnt_unknown_hard' => array(
+ 'tooltip' => $this->translate('%d services unknown on %s'),
+ 'color' => '#cc77ff',
+ 'opacity' => '0.7'
+ ),
+ 'cnt_ok' => array(
+ 'tooltip' => $this->translate('%d services ok on %s'),
+ 'color' => '#49DF96',
+ 'opacity' => '0.55'
+ )
+);
+
+$data = array();
+foreach ($summary as $entry) {
+ $day = $entry->day;
+ $value = $entry->$column;
+ $caption = sprintf(
+ $settings[$column]['tooltip'],
+ $value,
+ $this->formatDate(strtotime($day ?? ''))
+ );
+ $linkFilter = Filter::matchAll(
+ Filter::expression('timestamp', '<', strtotime($day . ' 23:59:59')),
+ Filter::expression('timestamp', '>', strtotime($day . ' 00:00:00')),
+ $form->getFilter(),
+ $filter
+ );
+ $data[$day] = array(
+ 'value' => $value,
+ 'caption' => $caption,
+ 'url' => $this->href('monitoring/list/eventhistory?' . $linkFilter->toQueryString())
+ );
+}
+
+if (! $summary->hasResult()) {
+ echo $this->translate('No state changes in the selected time period.') . '</div>';
+ return;
+}
+
+$from = intval($form->getValue('from', strtotime('3 months ago')));
+$to = intval($form->getValue('to', time()));
+
+// don't display more than ten years, or else this will get really slow
+if ($to - $from > 315360000) {
+ $from = $to - 315360000;
+}
+
+$f = new DateTime();
+$f->setTimestamp($from);
+$t = new DateTime();
+$t->setTimestamp($to);
+$diff = $t->diff($f);
+$step = 124;
+
+for ($i = 0; $i < $diff->days; $i += $step) {
+ $end = clone $f;
+ if ($diff->days - $i > $step) {
+ // full range, move last day to next chunk
+ $end->add(new DateInterval('P' . ($step - 1) . 'D'));
+ } else {
+ // include last day
+ $end->add(new DateInterval('P' . ($diff->days - $i) . 'D'));
+ }
+ $grid = new HistoryColorGrid(null, $f->getTimestamp(), $end->getTimestamp());
+ $grid->setColor($settings[$column]['color']);
+ $grid->opacity = $settings[$column]['opacity'];
+ $grid->orientation = $orientation;
+ $grid->setData($data);
+ $grids[] = $grid;
+
+ $f->add(new DateInterval('P' . $step . 'D'));
+}
+?>
+<div style="width: 33.5em;">
+<?php foreach (array_reverse($grids) as $key => $grid): ?>
+ <div style=" <?= $this->orientation === 'horizontal' ? '' : 'display: inline-block; vertical-align: top; top; margin: 0.5em;' ?>">
+ <?= $grid; ?>
+ <?= $this->orientation === 'horizontal' ? '<br />' : '' ?>
+ </div>
+<?php endforeach ?>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/eventhistory.phtml b/modules/monitoring/application/views/scripts/list/eventhistory.phtml
new file mode 100644
index 0000000..0573e8a
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/eventhistory.phtml
@@ -0,0 +1,22 @@
+<?php
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<?= $this->partial(
+ 'partials/event-history.phtml',
+ array(
+ 'compact' => $this->compact,
+ 'history' => $history,
+ 'isOverview' => true,
+ 'translationDomain' => $this->translationDomain
+ )
+) ?>
+
diff --git a/modules/monitoring/application/views/scripts/list/hostgroup-grid.phtml b/modules/monitoring/application/views/scripts/list/hostgroup-grid.phtml
new file mode 100644
index 0000000..34498d0
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/hostgroup-grid.phtml
@@ -0,0 +1,173 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <div class="sort-controls-container">
+ <?= $this->sortBox ?>
+ <a href="<?= $this->href('monitoring/list/hostgroups')->addFilter($this->filterEditor->getFilter()) ?>" class="grid-toggle-link"
+ title="<?= $this->translate('Toogle grid view mode') ?>">
+ <?= $this->icon('th-list', null, ['class' => '-inactive']) ?>
+ <?= $this->icon('th-thumb-empty', null, ['class' => '-active']) ?>
+ </a>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content" data-base-target="_next">
+<?php /** @var \Icinga\Module\Monitoring\DataView\Hostgroup $hostGroups */
+if (! $hostGroups->hasResult()): ?>
+ <p><?= $this->translate('No host groups found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+<div class="group-grid">
+<?php foreach ($hostGroups as $hostGroup): ?>
+ <div class="group-grid-cell">
+ <?php if ($hostGroup->hosts_down_unhandled > 0): ?>
+ <?= $this->qlink(
+ $hostGroup->hosts_down_unhandled,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'host_handled' => 0,
+ 'host_state' => 1
+ ],
+ [
+ 'class' => 'state-down',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state DOWN in the host group "%s"',
+ 'List %u hosts which are currently in state DOWN in the host group "%s"',
+ $hostGroup->hosts_down_unhandled
+ ),
+ $hostGroup->hosts_down_unhandled,
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($hostGroup->hosts_unreachable_unhandled > 0): ?>
+ <?= $this->qlink(
+ $hostGroup->hosts_unreachable_unhandled,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'host_handled' => 0,
+ 'host_state' => 2
+ ],
+ [
+ 'class' => 'state-unreachable',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state UNREACHABLE in the host group "%s"',
+ 'List %u hosts which are currently in state UNREACHABLE in the host group "%s"',
+ $hostGroup->hosts_unreachable_unhandled
+ ),
+ $hostGroup->hosts_unreachable_unhandled,
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($hostGroup->hosts_down_handled > 0): ?>
+ <?= $this->qlink(
+ $hostGroup->hosts_down_handled,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'host_handled' => 1,
+ 'host_state' => 1
+ ],
+ [
+ 'class' => 'state-down handled',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state DOWN (Acknowledged) in the host group "%s"',
+ 'List %u hosts which are currently in state DOWN (Acknowledged) in the host group "%s"',
+ $hostGroup->hosts_down_handled
+ ),
+ $hostGroup->hosts_down_handled,
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($hostGroup->hosts_unreachable_handled > 0): ?>
+ <?= $this->qlink(
+ $hostGroup->hosts_unreachable_handled,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'host_handled' => 0,
+ 'host_state' => 2
+ ],
+ [
+ 'class' => 'state-unreachable handled',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state UNREACHABLE (Acknowledged) in the host group "%s"',
+ 'List %u hosts which are currently in state UNREACHABLE (Acknowledged) in the host group "%s"',
+ $hostGroup->hosts_unreachable_handled
+ ),
+ $hostGroup->hosts_unreachable_handled,
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($hostGroup->hosts_pending > 0): ?>
+ <?= $this->qlink(
+ $hostGroup->hosts_pending,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'host_state' => 99
+ ],
+ [
+ 'class' => 'state-pending',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state PENDING in the host group "%s"',
+ 'List %u hosts which are currently in state PENDING in the host group "%s"',
+ $hostGroup->hosts_pending
+ ),
+ $hostGroup->hosts_pending,
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($hostGroup->hosts_up > 0): ?>
+ <?= $this->qlink(
+ $hostGroup->hosts_up,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'host_state' => 0
+ ],
+ [
+ 'class' => 'state-up',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state UP in the host group "%s"',
+ 'List %u hosts which are currently in state UP in the host group "%s"',
+ $hostGroup->hosts_up
+ ),
+ $hostGroup->hosts_up,
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ <?php else: ?>
+ <div class="state-none">
+ 0
+ </div>
+ <?php endif ?>
+ <?= $this->qlink(
+ $hostGroup->hostgroup_alias,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ ['hostgroup_name' => $hostGroup->hostgroup_name],
+ [
+ 'title' => sprintf(
+ $this->translate('List all hosts in the group "%s"'),
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ </div>
+<?php endforeach ?>
+</div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/hostgroups.phtml b/modules/monitoring/application/views/scripts/list/hostgroups.phtml
new file mode 100644
index 0000000..a0592c8
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/hostgroups.phtml
@@ -0,0 +1,296 @@
+<?php
+
+use Icinga\Module\Monitoring\Web\Widget\StateBadges;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ <a href="<?= $this->href('monitoring/list/hostgroup-grid')->addFilter(clone $this->filterEditor->getFilter()) ?>" class="grid-toggle-link"
+ title="<?= $this->translate('Toogle grid view mode') ?>">
+ <?= $this->icon('th-list', null, ['class' => '-active']) ?>
+ <?= $this->icon('th-thumb-empty', null, ['class' => '-inactive']) ?>
+ </a>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+
+<div class="content">
+<?php /** @var \Icinga\Module\Monitoring\DataView\Hostgroup $hostGroups */ if (! $hostGroups->hasResult()): ?>
+ <p><?= $this->translate('No host groups found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <table class="common-table table-row-selectable" data-base-target="_next">
+ <thead>
+ <tr>
+ <th></th>
+ <th><?= $this->translate('Host Group') ?></th>
+ <th><?= $this->translate('Host States') ?></th>
+ <th></th>
+ <th><?= $this->translate('Service States') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($hostGroups->peekAhead($this->compact) as $hostGroup): ?>
+ <tr>
+ <td class="count-col">
+ <span class="badge"><?= $hostGroup->hosts_total ?></span>
+ </td>
+ <th>
+ <?= $this->qlink(
+ $hostGroup->hostgroup_alias,
+ $this
+ ->url('monitoring/list/hosts')
+ ->setParams(['hostgroup_name' => $hostGroup->hostgroup_name])
+ ->addFilter($this->filterEditor->getFilter()),
+ ['sort' => 'host_severity'],
+ ['title' => sprintf(
+ $this->translate('List all hosts in the group "%s"'),
+ $hostGroup->hostgroup_alias
+ )]
+ ) ?>
+ </th>
+ <td>
+ <?php
+ $stateBadges = new StateBadges();
+ $stateBadges
+ ->setUrl('monitoring/list/hosts')
+ ->setBaseFilter($this->filterEditor->getFilter())
+ ->add(
+ StateBadges::STATE_UP,
+ $hostGroup->hosts_up,
+ array(
+ 'host_state' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'host_severity'
+ ),
+ 'List %u host that is currently in state UP in the host group "%s"',
+ 'List %u hosts which are currently in state UP in the host group "%s"',
+ array($hostGroup->hosts_up, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_DOWN,
+ $hostGroup->hosts_down_unhandled,
+ array(
+ 'host_state' => 1,
+ 'host_acknowledged' => 0,
+ 'host_in_downtime' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'host_severity'
+ ),
+ 'List %u host that is currently in state DOWN in the host group "%s"',
+ 'List %u hosts which are currently in state DOWN in the host group "%s"',
+ array($hostGroup->hosts_down_unhandled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_DOWN_HANDLED,
+ $hostGroup->hosts_down_handled,
+ array(
+ 'host_state' => 1,
+ 'host_handled' => 1,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'host_severity'
+ ),
+ 'List %u host that is currently in state DOWN (Acknowledged) in the host group "%s"',
+ 'List %u hosts which are currently in state DOWN (Acknowledged) in the host group "%s"',
+ array($hostGroup->hosts_down_handled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_UNREACHABLE,
+ $hostGroup->hosts_unreachable_unhandled,
+ array(
+ 'host_state' => 2,
+ 'host_acknowledged' => 0,
+ 'host_in_downtime' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'host_severity'
+ ),
+ 'List %u host that is currently in state UNREACHABLE in the host group "%s"',
+ 'List %u hosts which are currently in state UNREACHABLE in the host group "%s"',
+ array($hostGroup->hosts_unreachable_unhandled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_UNREACHABLE_HANDLED,
+ $hostGroup->hosts_unreachable_handled,
+ array(
+ 'host_state' => 2,
+ 'host_handled' => 1,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'host_severity'
+ ),
+ 'List %u host that is currently in state UNREACHABLE (Acknowledged) in the host group "%s"',
+ 'List %u hosts which are currently in state UNREACHABLE (Acknowledged) in the host group "%s"',
+ array($hostGroup->hosts_unreachable_handled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_PENDING,
+ $hostGroup->hosts_pending,
+ array(
+ 'host_state' => 99,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'host_severity'
+ ),
+ 'List %u host that is currently in state PENDING in the host group "%s"',
+ 'List %u hosts which are currently in state PENDING in the host group "%s"',
+ array($hostGroup->hosts_pending, $hostGroup->hostgroup_alias)
+ );
+ echo $stateBadges->render();
+ ?>
+ </td>
+ <td class="count-col">
+ <?= $this->qlink(
+ $hostGroup->services_total,
+ $this
+ ->url('monitoring/list/services')
+ ->setParams(['hostgroup_name' => $hostGroup->hostgroup_name])
+ ->addFilter($this->filterEditor->getFilter()),
+ ['sort' => 'service_severity'],
+ [
+ 'title' => sprintf(
+ $this->translate('List all services of all hosts in host group "%s"'),
+ $hostGroup->hostgroup_alias
+ ),
+ 'class' => 'badge'
+ ]
+ ) ?>
+ </td>
+ <td>
+ <?php
+ $stateBadges = new StateBadges();
+ $stateBadges
+ ->setUrl('monitoring/list/services')
+ ->setBaseFilter($this->filterEditor->getFilter())
+ ->add(
+ StateBadges::STATE_OK,
+ $hostGroup->services_ok,
+ array(
+ 'service_state' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state OK on hosts in the host group "%s"',
+ 'List %u services which are currently in state OK on hosts in the host group "%s"',
+ array($hostGroup->services_ok, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_CRITICAL,
+ $hostGroup->services_critical_unhandled,
+ array(
+ 'service_state' => 2,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0,
+ 'host_problem' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state CRITICAL on hosts in the host group "%s"',
+ 'List %u services which are currently in state CRITICAL on hosts in the host group "%s"',
+ array($hostGroup->services_critical_unhandled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_CRITICAL_HANDLED,
+ $hostGroup->services_critical_handled,
+ array(
+ 'service_state' => 2,
+ 'service_handled' => 1,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state CRITICAL (Acknowledged) on hosts in the host group "%s"',
+ 'List %u services which are currently in state CRITICAL (Acknowledged) on hosts in the host group "%s"',
+ array($hostGroup->services_critical_handled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_UNKNOWN,
+ $hostGroup->services_unknown_unhandled,
+ array(
+ 'service_state' => 3,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0,
+ 'host_problem' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state UNKNOWN on hosts in the host group "%s"',
+ 'List %u services which are currently in state UNKNOWN on hosts in the host group "%s"',
+ array($hostGroup->services_unknown_unhandled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_UNKNOWN_HANDLED,
+ $hostGroup->services_unknown_handled,
+ array(
+ 'service_state' => 3,
+ 'service_handled' => 1,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state UNKNOWN (Acknowledged) on hosts in the host group "%s"',
+ 'List %u services which are currently in state UNKNOWN (Acknowledged) on hosts in the host group "%s"',
+ array($hostGroup->services_unknown_handled, $hostGroup->hostgroup_alias)
+
+ )
+ ->add(
+ StateBadges::STATE_WARNING,
+ $hostGroup->services_warning_unhandled,
+ array(
+ 'service_state' => 1,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0,
+ 'host_problem' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state WARNING on hosts in the host group "%s"',
+ 'List %u services which are currently in state WARNING on hosts in the host group "%s"',
+ array($hostGroup->services_warning_unhandled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_WARNING_HANDLED,
+ $hostGroup->services_warning_handled,
+ array(
+ 'service_state' => 1,
+ 'service_handled' => 1,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state WARNING (Acknowledged) on hosts in the host group "%s"',
+ 'List %u services which are currently in state WARNING (Acknowledged) on hosts in the host group "%s"',
+ array($hostGroup->services_warning_handled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_PENDING,
+ $hostGroup->services_pending,
+ array(
+ 'service_state' => 99,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state PENDING on hosts in the host group "%s"',
+ 'List %u services which are currently in state PENDING on hosts in the host group "%s"',
+ array($hostGroup->services_pending, $hostGroup->hostgroup_alias)
+ );
+ echo $stateBadges->render();
+ ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($hostGroups->hasMore()): ?>
+ <div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/hosts.phtml b/modules/monitoring/application/views/scripts/list/hosts.phtml
new file mode 100644
index 0000000..6d7674e
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/hosts.phtml
@@ -0,0 +1,106 @@
+<?php
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Monitoring\Object\Host;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $hosts->hasResult()): ?>
+ <p><?= $this->translate('No hosts found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <table data-base-target="_next"
+ class="table-row-selectable state-table multiselect"
+ data-icinga-multiselect-url="<?= $this->href('monitoring/hosts/show') ?>"
+ data-icinga-multiselect-controllers="<?= $this->href("monitoring/hosts") ?>"
+ data-icinga-multiselect-data="host">
+ <thead class="print-only">
+ <tr>
+ <th><?= $this->translate('State') ?></th>
+ <th><?= $this->translate('Host') ?></th>
+ <?php foreach($this->addColumns as $col): ?>
+ <th><?= $this->escape($col) ?></th>
+ <?php endforeach ?>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach($hosts->peekAhead($this->compact) as $host):
+ $hostStateName = Host::getStateText($host->host_state);
+ $hostLink = $this->href('monitoring/host/show', array('host' => $host->host_name));
+ $hostCheckOverdue = $host->host_next_update < time();?>
+ <tr<?= $hostCheckOverdue ? ' class="state-outdated"' : '' ?>>
+ <td class="state-col state-<?= $hostStateName ?><?= $host->host_handled ? ' handled' : '' ?>">
+ <div class="state-label">
+ <?php if ($hostCheckOverdue): ?>
+ <?= $this->icon('clock', sprintf($this->translate('Overdue %s'), DateFormatter::timeSince($host->host_next_update))) ?>
+ <?php endif ?>
+ <?= Host::getStateText($host->host_state, true) ?>
+ </div>
+ <?php if ((int) $host->host_state !== 99): ?>
+ <div class="state-meta">
+ <?= $this->timeSince($host->host_last_state_change, $this->compact) ?>
+ <?php if ((int) $host->host_state > 0 && (int) $host->host_state_type === 0): ?>
+ <div><?= $this->translate('Soft', 'Soft state') ?> <?= $host->host_attempt ?></div>
+ <?php endif ?>
+ </div>
+ <?php endif ?>
+ </td>
+ <td>
+ <div class="state-header">
+ <?= $this->iconImage()->host($host) ?>
+ <?= $this->qlink(
+ $host->host_display_name,
+ $hostLink,
+ null,
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $host->host_display_name
+ ),
+ 'class' => 'rowaction'
+ )
+ ) ?>
+ <span class="state-icons"><?= $this->hostFlags($host) ?></span>
+ </div>
+ <p class="overview-plugin-output"><?= $this->pluginOutput($this->ellipsis($host->host_output, 10000), true, $host->host_check_command) ?></p>
+ </td>
+ <?php foreach($this->addColumns as $col): ?>
+ <?php if ($host->$col && preg_match('~^_(host|service)_([a-zA-Z0-9_]+)$~', $col, $m)): ?>
+ <td><?= $this->escape(\Icinga\Module\Monitoring\Object\MonitoredObject::protectCustomVars([$m[2] => $host->$col])[$m[2]]) ?></td>
+ <?php else: ?>
+ <td><?= $this->escape($host->$col) ?></td>
+ <?php endif ?>
+ <?php endforeach ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($hosts->hasMore()): ?>
+ <div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </div>
+<?php endif ?>
+</div>
+<?php if (! $this->compact): ?>
+<div class="monitoring-statusbar dont-print">
+ <?= $this->render('list/components/hostssummary.phtml') ?>
+ <?= $this->render('list/components/selectioninfo.phtml') ?>
+</div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/list/notifications.phtml b/modules/monitoring/application/views/scripts/list/notifications.phtml
new file mode 100644
index 0000000..51ef432
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/notifications.phtml
@@ -0,0 +1,124 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $notifications->hasResult()): ?>
+ <p><?= $this->translate('No notifications found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <table data-base-target="_next" class="table-row-selectable state-table">
+ <tbody>
+ <?php foreach ($notifications->peekAhead($this->compact) as $notification):
+ if (isset($notification->service_description)) {
+ $isService = true;
+ $stateLabel = Service::getStateText($notification->notification_state, true);
+ $stateName = Service::getStateText($notification->notification_state);
+ } else {
+ $isService = false;
+ $stateLabel = Host::getStateText($notification->notification_state, true);
+ $stateName = Host::getStateText($notification->notification_state);
+ }
+ ?>
+ <tr href="<?= $this->href('monitoring/event/show', ['id' => $notification->id, 'type' => 'notify']) ?>">
+ <td class="state-col state-<?= $stateName ?>">
+ <div class="state-label"><?= $stateLabel ?></div>
+ <div class="state-meta">
+ <?= $this->formatDateTime($notification->notification_timestamp) ?>
+ </div>
+ </td>
+ <td>
+ <div class="state-header">
+ <?php if ($isService) {
+ echo '<span class="service-on">';
+ echo sprintf(
+ $this->translate('%s on %s', 'service on host'),
+ $this->qlink(
+ $notification->service_display_name,
+ 'monitoring/service/show',
+ [
+ 'host' => $notification->host_name,
+ 'service' => $notification->service_description
+ ],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $notification->service_display_name,
+ $notification->host_display_name
+ )
+ ]
+ ),
+ $this->qlink(
+ $notification->host_display_name,
+ 'monitoring/host/show',
+ ['host' => $notification->host_name],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $notification->host_display_name
+ )
+ ]
+ )
+ );
+ echo '</span>';
+ } else {
+ echo $this->qlink(
+ $notification->host_display_name,
+ 'monitoring/host/show',
+ ['host' => $notification->host_name],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $notification->host_display_name
+ )
+ ]
+ );
+ } ?>
+ <?php if (! $this->contact): ?>
+ <div class="notification-recipient">
+ <?php if ($notification->notification_contact_name): ?>
+ <?= sprintf(
+ $this->translate('Sent to %s'),
+ $this->qlink(
+ $notification->notification_contact_name,
+ 'monitoring/show/contact',
+ array('contact_name' => $notification->notification_contact_name)
+ )
+ ) ?>
+ <?php else: ?>
+ <?= $this->translate('Not sent out to any contact') ?>
+ <?php endif ?>
+ </div>
+ <?php endif ?>
+ </div>
+ <p class="overview-plugin-output"><?= $this->pluginOutput($this->ellipsis($notification->notification_output, 10000), true) ?></p>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($notifications->hasMore()): ?>
+ <div class="action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url(isset($notificationsUrl) ? $notificationsUrl : null)->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ); ?>
+ </div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/servicegrid-flipped.phtml b/modules/monitoring/application/views/scripts/list/servicegrid-flipped.phtml
new file mode 100644
index 0000000..d7b4c78
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/servicegrid-flipped.phtml
@@ -0,0 +1,144 @@
+<?php
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Web\Url;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->problemToggle ?>
+ <div class="sort-controls-container">
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content" data-base-target="_next">
+ <?php if (empty($pivotData)): ?>
+ <p><?= $this->translate('No services found matching the filter.') ?></p>
+</div>
+<?php return; endif;
+$serviceFilter = Filter::matchAny();
+foreach ($pivotData as $serviceDescription => $_) {
+ $serviceFilter->orFilter(Filter::where('service_description', $serviceDescription));
+}
+?>
+<table class="service-grid-table">
+ <thead>
+ <tr>
+ <th><?= $this->partial(
+ 'joystickPagination.phtml',
+ 'default',
+ array(
+ 'flippable' => true,
+ 'xAxisPaginator' => $horizontalPaginator,
+ 'yAxisPaginator' => $verticalPaginator
+ )
+ ) ?></th>
+ <?php foreach ($pivotHeader['cols'] as $hostName => $hostAlias): ?>
+ <th class="rotate-45"><div><span><?= $this->qlink(
+ $this->ellipsis($hostAlias, 24),
+ Url::fromPath('monitoring/list/services')->addFilter(
+ Filter::matchAll($serviceFilter, Filter::where('host_name', $hostName))
+ ),
+ null,
+ array('title' => sprintf($this->translate('List all reported services on host %s'), $hostAlias)),
+ false
+ ) ?></span></div></th>
+ <?php endforeach ?>
+ </tr>
+ </thead>
+ <tbody>
+
+ <?php $i = 0 ?>
+ <?php foreach ($pivotHeader['rows'] as $serviceDescription => $serviceDisplayName): ?>
+ <tr>
+ <th><?php
+ $hostFilter = Filter::matchAny();
+ foreach ($pivotData[$serviceDescription] as $hostName => $_) {
+ $hostFilter->orFilter(Filter::where('host_name', $hostName));
+ }
+ echo $this->qlink(
+ $serviceDisplayName,
+ Url::fromPath('monitoring/list/services')->addFilter(
+ Filter::matchAll($hostFilter, Filter::where('service_description', $serviceDescription))
+ ),
+ null,
+ array('title' => sprintf(
+ $this->translate('List all services with the name "%s" on all reported hosts'),
+ $serviceDisplayName
+ ))
+ );
+ ?></th>
+ <?php foreach (array_keys($pivotHeader['cols']) as $hostName): ?>
+ <td><?php
+ $service = $pivotData[$serviceDescription][$hostName];
+ if ($service === null): ?>
+ <span aria-hidden="true">&middot;</span>
+ <?php continue; endif ?>
+ <?php $ariaDescribedById = $this->protectId($service->host_name . '_' . $service->service_description . '_desc') ?>
+ <span class="sr-only" id="<?= $ariaDescribedById ?>">
+ <?= $this->escape($service->service_output) ?>
+ </span>
+ <?= $this->qlink(
+ '',
+ 'monitoring/service/show',
+ array(
+ 'host' => $hostName,
+ 'service' => $serviceDescription
+ ),
+ array(
+ 'aria-describedby' => $ariaDescribedById,
+ 'aria-label' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $service->service_display_name,
+ $service->host_display_name
+ ),
+ 'class' => 'service-grid-link state-' . Service::getStateText($service->service_state) . ($service->service_handled ? ' handled' : ''),
+ 'title' => $service->service_output
+ )
+ ) ?>
+ </td>
+ <?php endforeach ?>
+ <?php if (! $this->compact && $this->horizontalPaginator->getPages()->pageCount > 1): ?>
+ <td>
+ <?php $expandLink = $this->qlink(
+ $this->translate('Load more'),
+ Url::fromRequest(),
+ array(
+ 'limit' => ($this->horizontalPaginator->getItemCountPerPage() + 20)
+ . ','
+ . $this->verticalPaginator->getItemCountPerPage()
+ ),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self'
+ )
+ ) ?>
+ <?= ++$i === (int) ceil(count($pivotHeader['rows']) / 2) ? $expandLink : '' ?>
+ </td>
+ <?php endif ?>
+ </tr>
+ <?php endforeach ?>
+ <?php if (! $this->compact && $this->verticalPaginator->getPages()->pageCount > 1): ?>
+ <tr>
+ <td colspan="<?= count($pivotHeader['cols']) + 1?>" class="service-grid-table-more">
+ <?php echo $this->qlink(
+ $this->translate('Load more'),
+ Url::fromRequest(),
+ array(
+ 'limit' => $this->horizontalPaginator->getItemCountPerPage()
+ . ','
+ . ($this->verticalPaginator->getItemCountPerPage() + 20)
+ ),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ </tbody>
+</table>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/servicegrid.phtml b/modules/monitoring/application/views/scripts/list/servicegrid.phtml
new file mode 100644
index 0000000..d0ed4bc
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/servicegrid.phtml
@@ -0,0 +1,144 @@
+<?php
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Web\Url;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->problemToggle ?>
+ <div class="sort-controls-container">
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content" data-base-target="_next">
+<?php if (empty($pivotData)): ?>
+ <p><?= $this->translate('No services found matching the filter.') ?></p>
+</div>
+<?php return; endif;
+$hostFilter = Filter::matchAny();
+foreach ($pivotData as $hostName => $_) {
+ $hostFilter->orFilter(Filter::where('host_name', $hostName));
+}
+?>
+ <table class="service-grid-table">
+ <thead>
+ <tr>
+ <th><?= $this->partial(
+ 'joystickPagination.phtml',
+ 'default',
+ array(
+ 'flippable' => true,
+ 'xAxisPaginator' => $horizontalPaginator,
+ 'yAxisPaginator' => $verticalPaginator
+ )
+ ) ?></th>
+ <?php foreach ($pivotHeader['cols'] as $serviceDescription => $serviceDisplayName): ?>
+ <th class="rotate-45"><div><span><?= $this->qlink(
+ $this->ellipsis($serviceDisplayName, 24),
+ Url::fromPath('monitoring/list/services')->addFilter(
+ Filter::matchAll($hostFilter, Filter::where('service_description', $serviceDescription))
+ ),
+ null,
+ array('title' => sprintf(
+ $this->translate('List all services with the name "%s" on all reported hosts'),
+ $serviceDisplayName
+ )),
+ false
+ ) ?></span></div></th>
+ <?php endforeach ?>
+ </tr>
+ </thead>
+ <tbody>
+
+ <?php $i = 0 ?>
+ <?php foreach ($pivotHeader['rows'] as $hostName => $hostDisplayName): ?>
+ <tr>
+ <th><?php
+ $serviceFilter = Filter::matchAny();
+ foreach ($pivotData[$hostName] as $serviceName => $_) {
+ $serviceFilter->orFilter(Filter::where('service_description', $serviceName));
+ }
+ echo $this->qlink(
+ $hostDisplayName,
+ Url::fromPath('monitoring/list/services')->addFilter(
+ Filter::matchAll($serviceFilter, Filter::where('host_name', $hostName))
+ ),
+ null,
+ array('title' => sprintf($this->translate('List all reported services on host %s'), $hostDisplayName))
+ );
+ ?></th>
+ <?php foreach (array_keys($pivotHeader['cols']) as $serviceDescription): ?>
+ <td>
+ <?php
+ $service = $pivotData[$hostName][$serviceDescription];
+ if ($service === null): ?>
+ <span aria-hidden="true">&middot;</span>
+ <?php continue; endif ?>
+ <?php $ariaDescribedById = $this->protectId($service->host_name . '_' . $service->service_description . '_desc') ?>
+ <span class="sr-only" id="<?= $ariaDescribedById ?>">
+ <?= $this->escape($service->service_output) ?>
+ </span>
+ <?= $this->qlink(
+ '',
+ 'monitoring/service/show',
+ array(
+ 'host' => $hostName,
+ 'service' => $serviceDescription
+ ),
+ array(
+ 'aria-describedby' => $ariaDescribedById,
+ 'aria-label' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $service->service_display_name,
+ $service->host_display_name
+ ),
+ 'class' => 'service-grid-link state-' . Service::getStateText($service->service_state) . ($service->service_handled ? ' handled' : ''),
+ 'title' => $service->service_output
+ )
+ ) ?>
+ </td>
+ <?php endforeach ?>
+ <?php if (! $this->compact && $this->horizontalPaginator->getPages()->pageCount > 1): ?>
+ <td>
+ <?php $expandLink = $this->qlink(
+ $this->translate('Load more'),
+ Url::fromRequest(),
+ array(
+ 'limit' => (
+ $this->horizontalPaginator->getItemCountPerPage() + 20) . ','
+ . $this->verticalPaginator->getItemCountPerPage()
+ ),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self'
+ )
+ ) ?>
+ <?= ++$i === (int) (count($pivotHeader['rows']) / 2) ? $expandLink : '' ?>
+ </td>
+ <?php endif ?>
+ </tr>
+ <?php endforeach ?>
+ <?php if (! $this->compact && $this->verticalPaginator->getPages()->pageCount > 1): ?>
+ <tr>
+ <td colspan="<?= count($pivotHeader['cols']) + 1?>" class="service-grid-table-more">
+ <?php echo $this->qlink(
+ $this->translate('Load more'),
+ Url::fromRequest(),
+ array(
+ 'limit' => $this->horizontalPaginator->getItemCountPerPage() . ',' .
+ ($this->verticalPaginator->getItemCountPerPage() + 20)
+ ),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ </tbody>
+ </table>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/servicegroup-grid.phtml b/modules/monitoring/application/views/scripts/list/servicegroup-grid.phtml
new file mode 100644
index 0000000..5ea6d17
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/servicegroup-grid.phtml
@@ -0,0 +1,217 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <div class="sort-controls-container">
+ <?= $this->sortBox ?>
+ <a href="<?= $this->href('monitoring/list/servicegroups')->addFilter($this->filterEditor->getFilter()) ?>" class="grid-toggle-link"
+ title="<?= $this->translate('Toogle grid view mode') ?>">
+ <?= $this->icon('th-list', null, ['class' => '-inactive']) ?>
+ <?= $this->icon('th-thumb-empty', null, ['class' => '-active']) ?>
+ </a>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content" data-base-target="_next">
+<?php /** @var \Icinga\Module\Monitoring\DataView\Servicegroup $serviceGroups */
+if (! $serviceGroups->hasResult()): ?>
+ <p><?= $this->translate('No service groups found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+<div class="group-grid">
+<?php foreach ($serviceGroups as $serviceGroup): ?>
+ <div class="group-grid-cell">
+ <?php if ($serviceGroup->services_critical_unhandled > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_critical_unhandled,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_handled' => 0,
+ 'service_state' => 2
+ ],
+ [
+ 'class' => 'state-critical',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state CRITICAL in service group "%s"',
+ 'List %s services which are currently in state CRITICAL in service group "%s"',
+ $serviceGroup->services_critical_unhandled
+ ),
+ $serviceGroup->services_critical_unhandled,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_warning_unhandled > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_warning_unhandled,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_handled' => 0,
+ 'service_state' => 1
+ ],
+ [
+ 'class' => 'state-warning',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state WARNING in service group "%s"',
+ 'List %s services which are currently in state WARNING in service group "%s"',
+ $serviceGroup->services_warning_unhandled
+ ),
+ $serviceGroup->services_warning_unhandled,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_unknown_unhandled > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_unknown_unhandled,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_handled' => 0,
+ 'service_state' => 3
+ ],
+ [
+ 'class' => 'state-unknown',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state UNKNOWN in service group "%s"',
+ 'List %s services which are currently in state UNKNOWN in service group "%s"',
+ $serviceGroup->services_unknown_unhandled
+ ),
+ $serviceGroup->services_unknown_unhandled,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_critical_handled > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_critical_handled,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_handled' => 1,
+ 'service_state' => 2
+ ],
+ [
+ 'class' => 'state-critical handled',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state CRITICAL (Acknowledged) in service group "%s"',
+ 'List %s services which are currently in state CRITICAL (Acknowledged) in service group "%s"',
+ $serviceGroup->services_critical_handled
+ ),
+ $serviceGroup->services_critical_handled,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_warning_handled > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_warning_handled,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_handled' => 1,
+ 'service_state' => 1
+ ],
+ [
+ 'class' => 'state-warning handled',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state WARNING (Acknowledged) in service group "%s"',
+ 'List %s services which are currently in state WARNING (Acknowledged) in service group "%s"',
+ $serviceGroup->services_warning_handled
+ ),
+ $serviceGroup->services_warning_handled,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_unknown_handled > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_unknown_handled,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_handled' => 1,
+ 'service_state' => 3
+ ],
+ [
+ 'class' => 'state-unknown handled',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state UNKNOWN (Acknowledged) in service group "%s"',
+ 'List %s services which are currently in state UNKNOWN (Acknowledged) in service group "%s"',
+ $serviceGroup->services_unknown_handled
+ ),
+ $serviceGroup->services_unknown_handled,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_pending > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_pending,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_state' => 99
+ ],
+ [
+ 'class' => 'state-pending',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currenlty in state PENDING in service group "%s"',
+ 'List %s services which are currently in state PENDING in service group "%s"',
+ $serviceGroup->services_pending
+ ),
+ $serviceGroup->services_pending,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_ok > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_ok,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_state' => 0
+ ],
+ [
+ 'class' => 'state-ok',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state OK in service group "%s"',
+ 'List %s services which are currently in state OK in service group "%s"',
+ $serviceGroup->services_ok
+ ),
+ $serviceGroup->services_ok,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php else: ?>
+ <div class="state-none">
+ 0
+ </div>
+ <?php endif ?>
+ <?= $this->qlink(
+ $serviceGroup->servicegroup_alias,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ ['servicegroup_name' => $serviceGroup->servicegroup_name],
+ [
+ 'title' => sprintf(
+ $this->translate('List all services in the group "%s"'),
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ </div>
+<?php endforeach ?>
+</div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/servicegroups.phtml b/modules/monitoring/application/views/scripts/list/servicegroups.phtml
new file mode 100644
index 0000000..c915b30
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/servicegroups.phtml
@@ -0,0 +1,184 @@
+<?php use Icinga\Module\Monitoring\Web\Widget\StateBadges;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ <a href="<?= $this->href('monitoring/list/servicegroup-grid')->addFilter(clone $this->filterEditor->getFilter()) ?>" class="grid-toggle-link"
+ title="<?= $this->translate('Toogle grid view mode') ?>">
+ <?= $this->icon('th-list', null, ['class' => '-active']) ?>
+ <?= $this->icon('th-thumb-empty', null, ['class' => '-inactive']) ?>
+ </a>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $serviceGroups->hasResult()): ?>
+ <p><?= $this->translate('No service groups found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <table class="table-row-selectable common-table" data-base-target="_next">
+ <thead>
+ <tr>
+ <th></th>
+ <th><?= $this->translate('Service Group') ?></th>
+ <th><?= $this->translate('Service States') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($serviceGroups->peekAhead($this->compact) as $serviceGroup): ?>
+ <tr>
+ <td class="count-col">
+ <span class="badge"><?= $serviceGroup->services_total ?></span>
+ </td>
+ <th>
+ <?= $this->qlink(
+ $serviceGroup->servicegroup_alias,
+ $this
+ ->url('monitoring/list/services')
+ ->setParams(['servicegroup_name' => $serviceGroup->servicegroup_name])
+ ->addFilter($this->filterEditor->getFilter()),
+ ['sort' => 'service_severity'],
+ ['title' => sprintf($this->translate('List all services in the group "%s"'), $serviceGroup->servicegroup_alias)]
+ ) ?>
+ </th>
+ <td>
+ <?php
+ $stateBadges = new StateBadges();
+ $stateBadges
+ ->setUrl('monitoring/list/services')
+ ->setBaseFilter($this->filterEditor->getFilter())
+ ->add(
+ StateBadges::STATE_OK,
+ $serviceGroup->services_ok,
+ array(
+ 'service_state' => 0,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state OK in service group "%s"',
+ 'List %s services which are currently in state OK in service group "%s"',
+ array($serviceGroup->services_ok, $serviceGroup->servicegroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_CRITICAL,
+ $serviceGroup->services_critical_unhandled,
+ array(
+ 'service_state' => 2,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0,
+ 'host_problem' => 0,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state CRITICAL in service group "%s"',
+ 'List %s services which are currently in state CRITICAL in service group "%s"',
+ array($serviceGroup->services_critical_unhandled, $serviceGroup->servicegroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_CRITICAL_HANDLED,
+ $serviceGroup->services_critical_handled,
+ array(
+ 'service_state' => 2,
+ 'service_handled' => 1,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state CRITICAL (Acknowledged) in service group "%s"',
+ 'List %s services which are currently in state CRITICAL (Acknowledged) in service group "%s"',
+ array($serviceGroup->services_critical_handled, $serviceGroup->servicegroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_UNKNOWN,
+ $serviceGroup->services_unknown_unhandled,
+ array(
+ 'service_state' => 3,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0,
+ 'host_problem' => 0,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state UNKNOWN in service group "%s"',
+ 'List %s services which are currently in state UNKNOWN in service group "%s"',
+ array($serviceGroup->services_unknown_unhandled, $serviceGroup->servicegroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_UNKNOWN_HANDLED,
+ $serviceGroup->services_unknown_handled,
+ array(
+ 'service_state' => 3,
+ 'service_handled' => 1,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state UNKNOWN (Acknowledged) in service group "%s"',
+ 'List %s services which are currently in state UNKNOWN (Acknowledged) in service group "%s"',
+ array($serviceGroup->services_unknown_handled, $serviceGroup->servicegroup_alias)
+
+ )
+ ->add(
+ StateBadges::STATE_WARNING,
+ $serviceGroup->services_warning_unhandled,
+ array(
+ 'service_state' => 1,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0,
+ 'host_problem' => 0,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state WARNING in service group "%s"',
+ 'List %s services which are currently in state WARNING in service group "%s"',
+ array($serviceGroup->services_warning_unhandled, $serviceGroup->servicegroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_WARNING_HANDLED,
+ $serviceGroup->services_warning_handled,
+ array(
+ 'service_state' => 1,
+ 'service_handled' => 1,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state WARNING (Acknowledged) in service group "%s"',
+ 'List %s services which are currently in state WARNING (Acknowledged) in service group "%s"',
+ array($serviceGroup->services_warning_handled, $serviceGroup->servicegroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_PENDING,
+ $serviceGroup->services_pending,
+ array(
+ 'service_state' => 99,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currenlty in state PENDING in service group "%s"',
+ 'List %s services which are currently in state PENDING in service group "%s"',
+ array($serviceGroup->services_pending, $serviceGroup->servicegroup_alias)
+ );
+ echo $stateBadges->render();
+ ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($serviceGroups->hasMore()): ?>
+<div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+</div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/services.phtml b/modules/monitoring/application/views/scripts/list/services.phtml
new file mode 100644
index 0000000..b2088e9
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/services.phtml
@@ -0,0 +1,161 @@
+<?php
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $services->hasResult()): ?>
+ <p><?= $this->translate('No services found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <table data-base-target="_next"
+ class="table-row-selectable state-table multiselect<?php if ($this->compact): ?> compact<?php endif ?>"
+ data-icinga-multiselect-url="<?= $this->href('monitoring/services/show') ?>"
+ data-icinga-multiselect-controllers="<?= $this->href('monitoring/services') ?>"
+ data-icinga-multiselect-data="service,host">
+ <thead class="print-only">
+ <tr>
+ <th><?= $this->translate('State') ?></th>
+ <th><?= $this->translate('Service') ?></th>
+ <?php foreach($this->addColumns as $col): ?>
+ <th><?= $this->escape($col) ?></th>
+ <?php endforeach ?>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($services->peekAhead($this->compact) as $service):
+ $serviceLink = $this->href(
+ 'monitoring/service/show',
+ array(
+ 'host' => $service->host_name,
+ 'service' => $service->service_description
+ )
+ );
+ $hostLink = $this->href(
+ 'monitoring/host/show',
+ array(
+ 'host' => $service->host_name,
+ )
+ );
+ $serviceStateName = Service::getStateText($service->service_state);
+ $serviceCheckOverdue = $service->service_next_update < time(); ?>
+ <tr<?= $serviceCheckOverdue ? ' class="state-outdated"' : '' ?>>
+ <td class="state-col state-<?= $serviceStateName ?><?= $service->service_handled ? ' handled' : '' ?>">
+ <div class="state-label">
+ <?php if ($serviceCheckOverdue): ?>
+ <?= $this->icon('clock', sprintf($this->translate('Overdue %s'), DateFormatter::timeSince($service->service_next_update))) ?>
+ <?php endif ?>
+ <?= Service::getStateText($service->service_state, true) ?>
+ </div>
+ <?php if ((int) $service->service_state !== 99): ?>
+ <div class="state-meta">
+ <?= $this->timeSince($service->service_last_state_change, $this->compact) ?>
+ <?php if ((int) $service->service_state > 0 && (int) $service->service_state_type === 0): ?>
+ <div><?= $this->translate('Soft', 'Soft state') ?> <?= $service->service_attempt ?></div>
+ <?php endif ?>
+ </div>
+ <?php endif ?>
+ </td>
+
+ <td>
+ <div class="state-header">
+ <span class="service-on">
+ <?= $this->iconImage()->service($service) ?>
+ <?php
+ if ($this->showHost) {
+ echo sprintf(
+ $this->translate('%s on %s', 'service on host'),
+ $this->qlink(
+ $service->service_display_name,
+ $serviceLink,
+ null,
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $service->service_display_name,
+ $service->host_display_name
+ ),
+ 'class' => 'rowaction'
+ )
+ ),
+ $this->qlink(
+ $service->host_display_name
+ . ($service->host_state != 0 ? ' (' . Host::getStateText($service->host_state, true) . ')' : ''),
+ $hostLink,
+ null,
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $service->host_display_name
+ )
+ ]
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $service->service_display_name,
+ $serviceLink,
+ null,
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $service->service_display_name,
+ $service->host_display_name
+ ),
+ 'class' => 'rowaction'
+ )
+ );
+ }
+ ?>
+ </span>
+ <span class="state-icons"><?= $this->serviceFlags($service) ?></span>
+ </div>
+ <div class="overview-plugin-output-container">
+ <div class="overview-performance-data">
+ <?= $this->perfdata($service->service_perfdata, true, 5) ?>
+ </div>
+ <p class="overview-plugin-output"><?= $this->pluginOutput($this->ellipsis($service->service_output, 10000), true, $service->service_check_command) ?></p>
+ </div>
+ </td>
+ <?php foreach($this->addColumns as $col): ?>
+ <?php if ($service->$col && preg_match('~^_(host|service)_([a-zA-Z0-9_]+)$~', $col, $m)): ?>
+ <td><?= $this->escape(\Icinga\Module\Monitoring\Object\MonitoredObject::protectCustomVars([$m[2] => $service->$col])[$m[2]]) ?></td>
+ <?php else: ?>
+ <td><?= $this->escape($service->$col) ?></td>
+ <?php endif ?>
+ <?php endforeach ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($services->hasMore()): ?>
+<div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+</div>
+<?php endif ?>
+</div>
+<?php if (! $this->compact): ?>
+<div class="monitoring-statusbar dont-print">
+ <?= $this->render('list/components/servicesummary.phtml') ?>
+ <?= $this->render('list/components/selectioninfo.phtml') ?>
+</div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/object/detail-history.phtml b/modules/monitoring/application/views/scripts/object/detail-history.phtml
new file mode 100644
index 0000000..692d3e4
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/object/detail-history.phtml
@@ -0,0 +1,13 @@
+<?php
+
+if (! $this->compact): ?>
+<div class="controls separated">
+ <?= $this->tabs ?>
+<?php if ($object->type === 'service') {
+ echo $this->render('partials/object/service-header.phtml');
+} else {
+ echo $this->render('partials/object/host-header.phtml');
+} ?>
+</div>
+<?php endif ?>
+<?= $this->render('partials/event-history.phtml') ?>
diff --git a/modules/monitoring/application/views/scripts/object/detail-tabhook.phtml b/modules/monitoring/application/views/scripts/object/detail-tabhook.phtml
new file mode 100644
index 0000000..abcfcc1
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/object/detail-tabhook.phtml
@@ -0,0 +1,21 @@
+<?php
+
+if (! $this->compact): ?>
+<div class="controls separated">
+ <?= $this->tabs ?>
+<?php
+if ($this->header === true) {
+ if ($object->type === 'service') {
+ echo $this->render('partials/object/service-header.phtml');
+ } else {
+ echo $this->render('partials/object/host-header.phtml');
+ }
+} elseif ($this->header !== false) {
+ echo $this->header;
+}
+?>
+</div>
+<?php endif ?>
+<div class="content">
+ <?= $this->content ?>
+</div> \ No newline at end of file
diff --git a/modules/monitoring/application/views/scripts/partials/command/object-command-form.phtml b/modules/monitoring/application/views/scripts/partials/command/object-command-form.phtml
new file mode 100644
index 0000000..b4e5a9c
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/command/object-command-form.phtml
@@ -0,0 +1,18 @@
+<?php use Icinga\Data\Filter\Filter; ?>
+<div class="controls">
+<?php if (! $this->compact): ?>
+ <?= $this->tabs ?>
+<?php endif ?>
+<?php if ($object->getType() === $object::TYPE_HOST) {
+ echo $this->render('partials/object/host-header.phtml');
+ $this->baseFilter = Filter::where('host', $object->host_name);
+ $this->stats = $object->stats;
+ echo $this->render('list/components/servicesummary.phtml');
+} else {
+ echo $this->render('partials/object/service-header.phtml');
+} ?>
+<?= $this->render('partials/object/quick-actions.phtml') ?>
+</div>
+<div class="content object-command">
+ <?= $form ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml b/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml
new file mode 100644
index 0000000..8d241ee
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml
@@ -0,0 +1,15 @@
+<div class="controls">
+<?php if (! $this->compact): ?>
+ <?= $tabs ?>
+<?php endif ?>
+<?php if (isset($serviceStates)): ?>
+ <?= $this->render('list/components/servicesummary.phtml') ?>
+ <?= $this->render('partials/service/objects-header.phtml') ?>
+<?php else: ?>
+ <?= $this->render('list/components/hostssummary.phtml') ?>
+ <?= $this->render('partials/host/objects-header.phtml') ?>
+<?php endif ?>
+</div>
+<div class="content objects-command">
+ <?= $form ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml b/modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml
new file mode 100644
index 0000000..f35680c
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml
@@ -0,0 +1,24 @@
+<?php
+switch ($comment->type) {
+ case 'flapping':
+ $icon = 'flapping';
+ $title = $this->translate('Flapping');
+ $tooltip = $this->translate('Comment was caused by a flapping host or service');
+ break;
+ case 'comment':
+ $icon = 'user';
+ $title = $this->translate('User Comment');
+ $tooltip = $this->translate('Comment was created by an user');
+ break;
+ case 'downtime':
+ $icon = 'plug';
+ $title = $this->translate('Downtime');
+ $tooltip = $this->translate('Comment was caused by a downtime');
+ break;
+ case 'ack':
+ $icon = 'ok';
+ $title = $this->translate('Acknowledgement');
+ $tooltip = $this->translate('Comment was caused by an acknowledgement');
+ break;
+}
+echo $this->icon($icon, $tooltip, array('class' => 'large-icon'));
diff --git a/modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml b/modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml
new file mode 100644
index 0000000..c603d3c
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml
@@ -0,0 +1,82 @@
+<div class="comment-author">
+<?php if ($comment->objecttype === 'service') {
+ echo '<span class="service-on">';
+ echo sprintf(
+ $this->translate('%s on %s', 'service on host'),
+ $this->qlink(
+ $comment->service_display_name,
+ 'monitoring/service/show',
+ [
+ 'host' => $comment->host_name,
+ 'service' => $comment->service_description
+ ],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $comment->service_display_name,
+ $comment->host_display_name
+ )
+ ]
+ ),
+ $this->qlink(
+ $comment->host_display_name,
+ 'monitoring/host/show',
+ ['host' => $comment->host_name],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $comment->host_display_name
+ )
+ ]
+ )
+ );
+ echo '</span>';
+} else {
+ echo $this->qlink(
+ $comment->host_display_name,
+ 'monitoring/host/show',
+ array('host' => $comment->host_name),
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for this comment about host %s'),
+ $comment->host_display_name
+ )
+ )
+ );
+} ?>
+ <span class="comment-time">
+ <?= $this->translate('by') ?>
+ <?= $this->escape($comment->author) ?>
+ <?= $this->timeAgo($comment->timestamp) ?>
+ </span>
+ <span class="comment-icons" data-base-target="_self">
+ <?= $comment->persistent ? $this->icon('attach', 'This comment is persistent') : '' ?>
+ <?= $comment->expiration ? $this->icon('clock', sprintf(
+ $this->translate('This comment expires on %s at %s'),
+ $this->formatDate($comment->expiration),
+ $this->formatTime($comment->expiration)
+ )) : '' ?>
+ <?php if (isset($delCommentForm)) {
+ // Form is unset if the current user lacks the respective permission
+ $uniqId = uniqid();
+ $buttonId = 'delete-comment-' . $uniqId;
+ $textId = 'comment-' . $uniqId;
+ $deleteButton = clone $delCommentForm;
+ /** @var \Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm $deleteButton */
+ $deleteButton->setAttrib('class', $deleteButton->getAttrib('class') . ' remove-action dont-print');
+ $deleteButton->populate(
+ array(
+ 'comment_id' => $comment->id,
+ 'comment_is_service' => isset($comment->service_description),
+ 'comment_name' => $comment->name
+ )
+ );
+ $deleteButton->getElement('btn_submit')
+ ->setAttrib('aria-label', $this->translate('Delete comment'))
+ ->setAttrib('id', $buttonId)
+ ->setAttrib('aria-describedby', $buttonId . ' ' . $textId);
+ echo $deleteButton;
+ } ?>
+ </span>
+</div>
+<?= $this->nl2br($this->markdownLine($comment->comment, isset($textId) ? ['id' => $textId, 'class' => 'caption'] : [ 'class' => 'caption'])) ?>
diff --git a/modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml b/modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml
new file mode 100644
index 0000000..4472479
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml
@@ -0,0 +1,10 @@
+<table>
+ <tr>
+ <td class="icon-col">
+ <?= $this->render('partials/comment/comment-description.phtml') ?>
+ </td>
+ <td>
+ <?= $this->render('partials/comment/comment-detail.phtml') ?>
+ </td>
+ </tr>
+</table>
diff --git a/modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml b/modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml
new file mode 100644
index 0000000..c4c92da
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml
@@ -0,0 +1,32 @@
+<table>
+ <tbody>
+ <?php
+ foreach ($comments as $i => $comment):
+ if ($i === 5) {
+ break;
+ }
+ ?>
+ <tr>
+ <td class="icon-col">
+ <?= $this->partial('partials/comment/comment-description.phtml', array('comment' => $comment)) ?>
+ </td>
+ <td>
+ <?= $this->partial('partials/comment/comment-detail.phtml', array('comment' => $comment)) ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+</table>
+<?php if ($comments->count() > 5): ?>
+<p>
+ <?= $this->qlink(
+ sprintf($this->translate('List all %d comments'), $comments->count()),
+ $listAllLink,
+ null,
+ array(
+ 'data-base-target' => '_next',
+ 'icon' => 'down-open'
+ )
+ ) ?>
+</p>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml b/modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml
new file mode 100644
index 0000000..dae6caa
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml
@@ -0,0 +1,101 @@
+<td class="state-col state-<?= $stateName; ?><?= $downtime->is_in_effect ? ' handled' : ''; ?>">
+ <?php if ($downtime->start <= time() && ! $downtime->is_in_effect): ?>
+ <div class="state-label"><?= $this->translate('ENDS', 'Downtime status'); ?></div>
+ <div class="state-meta"><?= $this->timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, $this->compact, true) ?></div>
+ <?php else: ?>
+ <div class="state-label"><?= $downtime->is_in_effect ? $this->translate('EXPIRES', 'Downtime status') : $this->translate('STARTS', 'Downtime status'); ?></div>
+ <div class="state-meta"><?= $this->timeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start, $this->compact, true) ?></div>
+ <?php endif; ?>
+</td>
+<td>
+ <div class="comment-author">
+ <?php if ($isService) {
+ echo '<span class="service-on">';
+ echo sprintf(
+ $this->translate('%s on %s', 'service on host'),
+ $this->qlink(
+ $downtime->service_display_name,
+ 'monitoring/service/show',
+ [
+ 'host' => $downtime->host_name,
+ 'service' => $downtime->service_description
+ ],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $downtime->service_display_name,
+ $downtime->host_display_name
+ )
+ ]
+ ),
+ $this->qlink(
+ $downtime->host_display_name,
+ 'monitoring/host/show',
+ ['host' => $downtime->host_name],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $downtime->host_display_name
+ )
+ ]
+ )
+ );
+ echo '</span>';
+ } else {
+ echo $this->qlink(
+ $downtime->host_display_name,
+ 'monitoring/host/show',
+ array('host' => $downtime->host_name, 'downtime_id' => $downtime->id),
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for this downtime scheduled for host %s'),
+ $downtime->host_display_name
+ )
+ )
+ );
+ } ?>
+ <span class="comment-time">
+ <?= $this->escape(sprintf(
+ $downtime->is_flexible
+ ? $this->translate('Flexible downtime by %s')
+ : $this->translate('Fixed downtime by %s'),
+ $downtime->author_name
+ )) ?>
+ </span>
+ <?php if (! $downtime->is_in_effect && $downtime->start >= time()): ?>
+ <span><?= sprintf($this->translate('expires %s'), $this->timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, false, true)) ?></span>
+ <?php endif ?>
+ <span class="comment-icons">
+ <?php if ($downtime->is_flexible): ?>
+ <?= $this->icon('magic', $this->translate('This downtime is flexible')); ?>
+ <?php endif ?>
+
+ <?php if ($downtime->is_in_effect): ?>
+ <?= $this->icon('plug', $this->translate('This downtime is in effect')); ?>
+ <?php endif ?>
+
+ <?php if (isset($delDowntimeForm)) {
+ // Form is unset if the current user lacks the respective permission
+ $uniqId = uniqid();
+ $buttonId = 'delete-downtime-' . $uniqId;
+ $textId = 'downtime-' . $uniqId;
+ $deleteButton = clone $delDowntimeForm;
+ /** @var \Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm $deleteButton */
+ $deleteButton->setAttrib('class', $deleteButton->getAttrib('class') . ' remove-action dont-print');
+ $deleteButton->populate(
+ array(
+ 'downtime_id' => $downtime->id,
+ 'downtime_is_service' => isset($downtime->service_description),
+ 'downtime_name' => $downtime->name
+ )
+ );
+ $deleteButton->getElement('btn_submit')
+ ->setAttrib('aria-label', $this->translate('Delete downtime'))
+ ->setAttrib('id', $buttonId)
+ ->setAttrib('aria-describedby', $buttonId . ' ' . $textId);
+ echo $deleteButton;
+ } ?>
+ </span>
+ </div>
+ <?= $this->nl2br($this->markdown($downtime->comment, isset($textId) ? ['id' => $textId] : null)) ?>
+</td>
diff --git a/modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml b/modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml
new file mode 100644
index 0000000..e2582c1
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml
@@ -0,0 +1,40 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+?>
+<table class="state-table common-table" data-base-target="_next">
+ <tbody>
+ <?php
+ foreach ($this->downtimes as $i => $downtime):
+ if ($i > 5) {
+ break;
+ }
+ if ($downtime->objecttype === 'service') {
+ $this->isService = true;
+ $this->stateName = Service::getStateText($downtime->service_state);
+ } else {
+ $this->isService = false;
+ $this->stateName = Host::getStateText($downtime->host_state);
+ }
+ $this->downtime = $downtime;
+ $this->displayComment = false;
+ ?>
+ <tr>
+ <?= $this->render('partials/downtime/downtime-header.phtml') ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+</table>
+<?php if ($downtimes->count() > 5): ?>
+<p>
+ <?= $this->qlink(
+ sprintf($this->translate('List all %d downtimes'), $downtimes->count()),
+ $listAllLink,
+ null,
+ array(
+ 'data-base-target' => '_next',
+ 'icon' => 'down-open'
+ )
+ ) ?>
+</p>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/partials/event-history.phtml b/modules/monitoring/application/views/scripts/partials/event-history.phtml
new file mode 100644
index 0000000..b81c95d
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/event-history.phtml
@@ -0,0 +1,267 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Web\Url;
+use Icinga\Web\UrlParams;
+
+function contactsLink($match, $view) {
+ $links = array();
+ foreach (preg_split('/,\s/', $match[1]) as $contact) {
+ $links[] = $view->qlink(
+ $contact,
+ 'monitoring/show/contact',
+ array('contact_name' => $contact),
+ array('title' => sprintf($view->translate('Show detailed information about %s'), $contact))
+ );
+ }
+ return '[' . implode(', ', $links) . ']';
+}
+
+$self = $this;
+
+$url = $this->url();
+$limit = (int) $url->getParam('limit', 25);
+if (! $url->hasParam('page') || ($page = (int) $url->getParam('page')) < 1) {
+ $page = 1;
+}
+
+/** @var \Icinga\Module\Monitoring\DataView\EventHistory $history */
+$history->limit($limit * $page);
+?>
+<div class="content">
+<?php
+$dateFormatter = new IntlDateFormatter(setlocale(LC_TIME, 0), IntlDateFormatter::FULL, IntlDateFormatter::NONE);
+$lastDate = null;
+$flappingMsg = $this->translate('Flapping with a %.2f%% state change rate');
+$rowAction = Url::fromPath('monitoring/event/show');
+?>
+ <?php foreach ($history->peekAhead() as $event): ?>
+<?php if ($lastDate === null): ?>
+ <table class="table-row-selectable state-table" data-base-target="_next">
+ <tbody>
+<?php endif;
+ $icon = '';
+ $iconTitle = null;
+ $isService = isset($event->service_description);
+ $msg = $event->output;
+ $stateName = 'no-state';
+
+ $rowAction->setParams(new UrlParams())->addParams(array(
+ 'type' => $event->type,
+ 'id' => $event->id
+ ));
+ switch ($event->type) {
+ case substr($event->type, 0, 13) === 'notification_':
+ $rowAction->setParam('type', 'notify');
+ $icon = 'bell';
+ switch (substr($event->type, 13)) {
+ case 'state':
+ $iconTitle = $this->translate('State notification', 'tooltip');
+ $label = $this->translate('NOTIFICATION');
+ $stateName = $isService ? Service::getStateText($event->state) : Host::getStateText($event->state);
+ break;
+ case 'ack':
+ $iconTitle = $this->translate('Ack Notification', 'tooltip');
+ $label = $this->translate('ACK NOTIFICATION');
+ break;
+ case 'dt_start':
+ $iconTitle = $this->translate('Downtime start notification', 'tooltip');
+ $label = $this->translate('DOWNTIME START NOTIFICATION');
+ break;
+ case 'dt_end':
+ $iconTitle = $this->translate('Downtime end notification', 'tooltip');
+ $label = $this->translate('DOWNTIME END NOTIFICATION');
+ break;
+ case 'flapping':
+ $iconTitle = $this->translate('Flapping notification', 'tooltip');
+ $label = $this->translate('FLAPPING NOTIFICATION');
+ break;
+ case 'flapping_end':
+ $iconTitle = $this->translate('Flapping end notification', 'tooltip');
+ $label = $this->translate('FLAPPING END NOTIFICATION');
+ break;
+ case 'custom':
+ $iconTitle = $this->translate('Custom notification', 'tooltip');
+ $label = $this->translate('CUSTOM NOTIFICATION');
+ break;
+ }
+ $msg = $msg ? preg_replace_callback(
+ '/^\[([^\]]+)\]/',
+ function($match) use ($self) { return contactsLink($match, $self); },
+ $msg
+ ) : $this->translate('This notification was not sent out to any contact.');
+ break;
+ case 'comment':
+ $icon = 'comment-empty';
+ $iconTitle = $this->translate('Comment', 'tooltip');
+ $label = $this->translate('COMMENT');
+ break;
+ case 'comment_deleted':
+ $icon = 'cancel';
+ $iconTitle = $this->translate('Comment removed', 'tooltip');
+ $label = $this->translate('COMMENT DELETED');
+ break;
+ case 'ack':
+ $icon = 'ok';
+ $iconTitle = $this->translate('Acknowledged', 'tooltip');
+ $label = $this->translate('ACKNOWLEDGED');
+ break;
+ case 'ack_deleted':
+ $icon = 'ok';
+ $iconTitle = $this->translate('Acknowledgement removed', 'tooltip');
+ $label = $this->translate('ACKNOWLEDGEMENT REMOVED');
+ break;
+ case 'dt_comment':
+ $icon = 'plug';
+ $iconTitle = $this->translate('Downtime scheduled', 'tooltip');
+ $label = $this->translate('SCHEDULED DOWNTIME');
+ break;
+ case 'dt_comment_deleted':
+ $icon = 'plug';
+ $iconTitle = $this->translate('Downtime removed', 'tooltip');
+ $label = $this->translate('DOWNTIME DELETED');
+ break;
+ case 'flapping':
+ $icon = 'flapping';
+ $iconTitle = $this->translate('Flapping started', 'tooltip');
+ $label = $this->translate('FLAPPING');
+ $msg = sprintf($flappingMsg, $msg);
+ break;
+ case 'flapping_deleted':
+ $icon = 'flapping';
+ $iconTitle = $this->translate('Flapping stopped', 'tooltip');
+ $label = $this->translate('FLAPPING STOPPED');
+ $msg = sprintf($flappingMsg, $msg);
+ break;
+ case 'hard_state':
+ if ((int) $event->state === 0) {
+ $icon = 'thumbs-up';
+ } else {
+ $icon = 'warning-empty';
+ }
+ $iconTitle = $this->translate('Hard state', 'tooltip');
+ $label = $isService ? Service::getStateText($event->state, true) : Host::getStateText($event->state, true);
+ $stateName = $isService ? Service::getStateText($event->state) : Host::getStateText($event->state);
+ break;
+ case 'soft_state':
+ $icon = 'spinner';
+ $iconTitle = $this->translate('Soft state', 'tooltip');
+ $label = $isService ? Service::getStateText($event->state, true) : Host::getStateText($event->state, true);
+ $stateName = $isService ? Service::getStateText($event->state) : Host::getStateText($event->state);
+ break;
+ case 'dt_start':
+ $icon = 'plug';
+ $iconTitle = $this->translate('Downtime started', 'tooltip');
+ $label = $this->translate('DOWNTIME START');
+ break;
+ case 'dt_end':
+ $icon = 'plug';
+ $iconTitle = $this->translate('Downtime ended', 'tooltip');
+ $label = $this->translate('DOWNTIME END');
+ break;
+ } ?>
+ <?php
+ $currentDate = $dateFormatter->format($event->timestamp);
+ if ($currentDate !== $lastDate):
+ $lastDate = $currentDate;
+ ?>
+ <tr>
+ <th colspan="2"><?= $currentDate ?></th>
+ </tr>
+ <?php endif ?>
+ <tr href="<?= $rowAction ?>">
+ <td class="state-col state-<?= $stateName ?>">
+ <?php if ($history->getIteratorPosition() % $limit === 0): ?>
+ <a id="page-<?= $history->getIteratorPosition() / $limit + 1 ?>"></a>
+ <?php endif ?>
+ <div class="state-label"><?= $this->escape($label) ?></div>
+ <div class="state-meta"><?= $this->formatTime($event->timestamp) ?></div>
+ </td>
+ <td>
+ <div class="history-message-container">
+ <?php if ($icon): ?>
+ <div class="history-message-icon">
+ <?= $this->icon($icon, $iconTitle) ?>
+ </div>
+ <?php endif ?>
+ <div class="history-message-output">
+ <?php if ($this->isOverview): ?>
+ <?php if ($isService) {
+ echo '<span class="service-on">';
+ echo sprintf(
+ $this->translate('%s on %s', 'service on host'),
+ $this->qlink(
+ $event->service_display_name,
+ 'monitoring/service/show',
+ [
+ 'host' => $event->host_name,
+ 'service' => $event->service_description
+ ],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $event->service_display_name,
+ $event->host_display_name
+ )
+ ]
+ ),
+ $this->qlink(
+ $event->host_display_name,
+ 'monitoring/host/show',
+ ['host' => $event->host_name],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $event->host_display_name
+ )
+ ]
+ )
+ );
+ echo '</span>';
+ } else {
+ echo $this->qlink(
+ $event->host_display_name,
+ 'monitoring/host/show',
+ ['host' => $event->host_name],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $event->host_display_name
+ )
+ ]
+ );
+ } ?>
+ <?php endif ?>
+ <?= $this->nl2br($this->createTicketLinks($this->markdown($msg, ['class' => 'overview-plugin-output']))) ?>
+ </div>
+ </div>
+ </td>
+ </tr>
+ <?php endforeach ?>
+<?php if ($lastDate !== null): ?>
+ </tbody>
+ </table>
+<?php endif ?>
+<?php if ($history->hasMore()): ?>
+ <div class="action-links">
+ <?php if ($this->compact) {
+ echo $this->qlink(
+ $this->translate('Show More'),
+ $url->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Load More'),
+ $url->setAnchor('page-' . ($page + 1)),
+ array('page' => $page + 1,),
+ array('class' => 'action-link')
+ );
+ } ?>
+ </div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml b/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml
new file mode 100644
index 0000000..48141e2
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml
@@ -0,0 +1,41 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+
+if (! ($hostCount = count($objects))): return; endif ?>
+<table class="state-table host-detail-state">
+<tbody>
+<?php foreach ($objects as $i => $host): /** @var Host $host */
+ if ($i === 5) {
+ break;
+ } ?>
+ <tr>
+ <td class="state-col state-<?= Host::getStateText($host->host_state); ?><?= $host->host_handled ? ' handled' : '' ?>">
+ <span class="sr-only"><?= Host::getStateText($host->host_state) ?></span>
+ <div class="state-meta">
+ <?= $this->timeSince($host->host_last_state_change, $this->compact) ?>
+ </div>
+ </td>
+ <td>
+ <?= $this->link()->host(
+ $host->host_name,
+ $host->host_display_name
+ ) ?>
+ <?= $this->hostFlags($host) ?>
+ </td>
+ </tr>
+<?php endforeach ?>
+</tbody>
+</table>
+<?php if ($hostCount > 5): ?>
+<div class="hosts-link">
+ <?= $this->qlink(
+ sprintf($this->translate('List all %d hosts'), $hostCount),
+ $this->url()->setPath('monitoring/list/hosts'),
+ null,
+ array(
+ 'data-base-target' => '_next',
+ 'icon' => 'forward'
+ )
+ ) ?>
+</div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/partials/object/detail-content.phtml b/modules/monitoring/application/views/scripts/partials/object/detail-content.phtml
new file mode 100644
index 0000000..62bfd2c
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/object/detail-content.phtml
@@ -0,0 +1,53 @@
+<div class="content" data-base-target="_next">
+ <?= $this->render('show/components/output.phtml') ?>
+ <?= $this->render('show/components/grapher.phtml') ?>
+ <?= $this->render('show/components/extensions.phtml') ?>
+
+ <h2><?= $this->translate('Problem handling') ?></h2>
+ <table class="name-value-table">
+ <tbody>
+ <?= $this->render('show/components/acknowledgement.phtml') ?>
+ <?= $this->render('show/components/comments.phtml') ?>
+ <?= $this->render('show/components/downtime.phtml') ?>
+ <?= $this->render('show/components/notes.phtml') ?>
+ <?= $this->render('show/components/actions.phtml') ?>
+ <?= $this->render('show/components/flapping.phtml') ?>
+ <?php if ($object->type === 'service'): ?>
+ <?= $this->render('show/components/servicegroups.phtml') ?>
+ <?php else: ?>
+ <?= $this->render('show/components/hostgroups.phtml') ?>
+ <?php endif ?>
+ </tbody>
+ </table>
+
+ <?= $this->render('show/components/perfdata.phtml') ?>
+
+ <h2><?= $this->translate('Notifications') ?></h2>
+ <table class="name-value-table">
+ <tbody>
+ <?= $this->render('show/components/notifications.phtml') ?>
+ <?php if ($this->hasPermission('*') || ! $this->hasPermission('no-monitoring/contacts')): ?>
+ <?= $this->render('show/components/contacts.phtml') ?>
+ <?php endif ?>
+ </tbody>
+ </table>
+
+ <h2><?= $this->translate('Check execution') ?></h2>
+ <table class="name-value-table">
+ <tbody>
+ <?= $this->render('show/components/command.phtml') ?>
+ <?= $this->render('show/components/checksource.phtml') ?>
+ <?= $this->render('show/components/reachable.phtml') ?>
+ <?= $this->render('show/components/checkstatistics.phtml') ?>
+ <?= $this->render('show/components/checktimeperiod.phtml') ?>
+ </tbody>
+ </table>
+
+ <?php if (! empty($object->customvars)): ?>
+ <h2><?= $this->translate('Custom Variables') ?></h2>
+ <div id="<?= $object->type ?>-customvars" data-visible-height="200" class="collapsible">
+ <?= (new \Icinga\Module\Monitoring\Web\Widget\CustomVarTable($object->customvarsWithOriginalNames, $object)) ?>
+ </div>
+ <?php endif ?>
+ <?= $this->render('show/components/flags.phtml') ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/partials/object/host-header.phtml b/modules/monitoring/application/views/scripts/partials/object/host-header.phtml
new file mode 100644
index 0000000..4de4a01
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/object/host-header.phtml
@@ -0,0 +1,51 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Web\Url;
+
+/** @var Host $object */
+
+$url = Url::fromRequest();
+$linkHostName = ! ($url->getPath() === 'monitoring/host/show' && $url->getParam('host') === $object->host_name);
+?>
+<table class="state-table host-detail-state">
+ <tr>
+ <td class="state-col state-<?= Host::getStateText($object->host_state) ?><?= $object->host_handled ? ' handled' : '' ?>">
+ <div class="state-header"><?= Host::getStateText($object->host_state, true) ?></div>
+ <div class="state-meta">
+ <?= $this->timeSince($object->host_last_state_change) ?>
+ <?php if ((int) $object->host_state > 0 && (int) $object->host_state_type === 0): ?>
+ <div><?= $this->translate('Soft', 'Soft state') ?> <?= $object->host_attempt ?></div>
+ <?php endif ?>
+ </div>
+ </td>
+ <td>
+ <?= $this->iconImage()->host($object) ?>
+ <?php
+ if ($linkHostName) {
+ echo '<a href="' . Url::fromPath('monitoring/host/show', array('host' => $object->host_name)) . '">';
+ }
+ ?>
+ <span class="selectable"><strong><?= $this->escape($object->host_display_name) ?></strong></span>
+ <?php if ($object->host_display_name !== $object->host_name): ?>
+ <span class="selectable host-meta">&#40;<?= $this->escape($object->host_name) ?>&#41;</span>
+ <?php endif ?>
+ <?php
+ if ($linkHostName) {
+ echo '</a>';
+ }
+ ?>
+ <?php if ($object->host_alias !== $object->host_display_name && $object->host_alias !== $object->host_name): ?>
+ <div class="selectable host-meta">
+ <?= $this->escape($this->translate('Alias', 'host') . ': ' . $object->host_alias) ?>
+ </div>
+ <?php endif ?>
+ <?= $this->hostFlags($object) ?>
+ <?php if ($object->host_address6 && $object->host_address6 !== $object->host_name): ?>
+ <div class="selectable host-meta" title="<?= $this->translate('IPv6 address') ?>"><?= $this->escape($object->host_address6) ?></div>
+ <?php endif ?>
+ <?php if ($object->host_address && $object->host_address !== $object->host_name): ?>
+ <div class="selectable host-meta" title="<?= $this->translate('IPv4 address') ?>"><?= $this->escape($object->host_address) ?></div>
+ <?php endif ?>
+ </td>
+ </tr>
+</table>
diff --git a/modules/monitoring/application/views/scripts/partials/object/quick-actions.phtml b/modules/monitoring/application/views/scripts/partials/object/quick-actions.phtml
new file mode 100644
index 0000000..fe05a84
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/object/quick-actions.phtml
@@ -0,0 +1,144 @@
+<div class="quick-actions">
+ <ul class="nav tab-nav">
+ <?php if (isset($removeAckForm)): ?>
+ <li>
+ <?php
+ $removeAckForm = clone $removeAckForm;
+ $removeAckForm->setAttrib('id', 'quickAction_' . $removeAckForm->getName()); // Avoids id duplication
+ $removeAckForm->setLabelEnabled(true);
+ echo $removeAckForm;
+ ?>
+ </li>
+ <?php elseif /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ ($this->hasPermission('monitoring/command/acknowledge-problem') && ! (in_array((int) $object->state, array(0, 99))) ): ?>
+ <li>
+ <?php if ($object->getType() === $object::TYPE_HOST) {
+ echo $this->qlink(
+ $this->translate('Acknowledge'),
+ 'monitoring/host/acknowledge-problem',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'edit',
+ 'title' => $this->translate(
+ 'Acknowledge this problem, suppress all future notifications for it and tag it as being handled'
+ )
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Acknowledge'),
+ 'monitoring/service/acknowledge-problem',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'edit',
+ 'title' => $this->translate(
+ 'Acknowledge this problem, suppress all future notifications for it and tag it as being handled'
+ )
+ )
+ );
+ } ?>
+ </li>
+ <?php endif ?>
+ <?php if (isset($checkNowForm)): // Form is unset if the current user lacks the respective permission ?>
+ <?php ($checkNowForm = clone $checkNowForm)->setAttrib('id', 'quickAction_' . $checkNowForm->getName()); // Avoids id duplication ?>
+ <li><?= $checkNowForm ?></li>
+ <?php endif ?>
+ <?php if ($this->hasPermission('monitoring/command/comment/add')): ?>
+ <li>
+ <?php if ($object->getType() === $object::TYPE_HOST) {
+ echo $this->qlink(
+ $this->translate('Comment'),
+ 'monitoring/host/add-comment',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'comment-empty',
+ 'title' => $this->translate('Add a new comment to this host')
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Comment'),
+ 'monitoring/service/add-comment',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'comment-empty',
+ 'title' => $this->translate('Add a new comment to this service')
+ )
+ );
+ } ?>
+ </li>
+ <?php endif ?>
+ <?php if ($this->hasPermission('monitoring/command/send-custom-notification')): ?>
+ <li>
+ <?php if ($object->getType() === $object::TYPE_HOST) {
+ echo $this->qlink(
+ $this->translate('Notification'),
+ 'monitoring/host/send-custom-notification',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'bell',
+ 'title' => $this->translate(
+ 'Send a custom notification to contacts responsible for this host'
+ )
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Notification'),
+ 'monitoring/service/send-custom-notification',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'bell',
+ 'title' => $this->translate(
+ 'Send a custom notification to contacts responsible for this service'
+ )
+ )
+ );
+ } ?>
+ </li>
+ <?php endif ?>
+ <?php if ($this->hasPermission('monitoring/command/downtime/schedule')): ?>
+ <li><?php if ($object->getType() === $object::TYPE_HOST) {
+ echo $this->qlink(
+ $this->translate('Downtime'),
+ 'monitoring/host/schedule-downtime',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'plug',
+ 'title' => $this->translate(
+ 'Schedule a downtime to suppress all problem notifications within a specific period of time'
+ )
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Downtime'),
+ 'monitoring/service/schedule-downtime',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'plug',
+ 'title' => $this->translate(
+ 'Schedule a downtime to suppress all problem notifications within a specific period of time'
+ )
+ )
+ );
+ } ?>
+ </li>
+ <?php endif ?>
+ </ul>
+</div>
diff --git a/modules/monitoring/application/views/scripts/partials/object/service-header.phtml b/modules/monitoring/application/views/scripts/partials/object/service-header.phtml
new file mode 100644
index 0000000..318fe49
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/object/service-header.phtml
@@ -0,0 +1,72 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Web\Url;
+
+/** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+
+$url = Url::fromRequest();
+$linkServiceName = ! ($url->getPath() === 'monitoring/service/show' && $url->getParam('service') === $object->service_description);
+?>
+<table class="state-table service-detail-state">
+ <tr>
+ <td class="state-col state-<?= Host::getStateText($object->host_state) ?><?= $object->host_handled ? ' handled' : '' ?>">
+ <div class="state-label"><?= Host::getStateText($object->host_state, true) ?></div>
+ <div class="state-meta">
+ <?= $this->timeSince($object->host_last_state_change) ?>
+ <?php if ((int) $object->host_state > 0 && (int) $object->host_state_type === 0): ?>
+ <div><?= $this->translate('Soft', 'Soft state') ?> <?= $object->host_attempt ?></div>
+ <?php endif ?>
+ </div>
+ </td>
+ <td>
+ <?= $this->iconImage()->host($object) ?>
+ <a href="<?= Url::fromPath('monitoring/host/show', array('host' => $object->host_name)) ?>">
+ <span class="selectable"><strong><?= $this->escape($object->host_display_name) ?></strong></span>
+ <?php if ($object->host_display_name !== $object->host_name): ?>
+ <span class="selectable host-meta">&#40;<?= $this->escape($object->host_name) ?>&#41;</span>
+ <?php endif ?>
+ </a>
+ <?= $this->hostFlags($object) ?>
+ <?php if ($object->host_address6 && $object->host_address6 !== $object->host_name): ?>
+ <div class="selectable host-meta" title="<?= $this->translate('IPv6 address') ?>"><?= $this->escape($object->host_address6) ?></div>
+ <?php endif ?>
+ <?php if ($object->host_address && $object->host_address !== $object->host_name): ?>
+ <div class="selectable host-meta" title="<?= $this->translate('IPv4 address') ?>"><?= $this->escape($object->host_address) ?></div>
+ <?php endif ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="state-col state-<?= Service::getStateText($object->service_state) ?><?= $object->service_handled ? ' handled' : '' ?>">
+ <div class="state-label"><?= Service::getStateText($object->service_state, true) ?></div>
+ <div class="state-meta">
+ <?= $this->timeSince($object->service_last_state_change) ?>
+ <?php if ((int) $object->service_state > 0 && (int) $object->service_state_type === 0): ?>
+ <div><?= $this->translate('Soft', 'Soft state') ?> <?= $object->service_attempt ?></div>
+ <?php endif ?>
+ </div>
+ </td>
+ <td>
+ <?= $this->iconImage()->service($object) ?>
+ <?= $this->translate('Service') ?>&#58;
+ <?php
+ if ($linkServiceName) {
+ echo '<a href="' . Url::fromPath('monitoring/service/show', array(
+ 'host' => $object->host_name,
+ 'service' => $object->service_description
+ )) . '">';
+ }
+ ?>
+ <span class="selectable"><strong><?= $this->escape($object->service_display_name) ?></strong></span>
+ <?php if ($object->service_display_name !== $object->service_description): ?>
+ <span class="selectable service-meta">&#40;<?= $this->escape($object->service_description) ?>&#41;</span>
+ <?php endif ?>
+ <?php
+ if ($linkServiceName) {
+ echo '</a>';
+ }
+ ?>
+ <?= $this->serviceFlags($object) ?>
+ </td>
+ </tr>
+</table>
diff --git a/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml b/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml
new file mode 100644
index 0000000..d342d87
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml
@@ -0,0 +1,45 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+
+if (! ($serviceCount = count($objects))): return; endif ?>
+<table class="state-table service-detail-state">
+<tbody>
+<?php foreach ($objects as $i => $service): /** @var Service $service */
+ if ($i === 5) {
+ break;
+ } ?>
+ <tr>
+ <td class="state-col state-<?= Service::getStateText($service->service_state) ?><?= $service->service_handled ? ' handled' : '' ?>">
+ <span class="sr-only"><?= Service::getStateText($service->service_state) ?></span>
+ <div class="state-meta">
+ <?= $this->timeSince($service->service_last_state_change, $this->compact) ?>
+ </div>
+ </td>
+ <td>
+ <?= $this->link()->service(
+ $service->service_description,
+ $service->service_display_name,
+ $service->host_name,
+ $service->host_display_name
+ . ($service->host_state != 0 ? ' (' . Host::getStateText($service->host_state, true) . ')' : '')
+ ) ?>
+ <?= $this->serviceFlags($service) ?>
+ </td>
+ </tr>
+<?php endforeach ?>
+</tbody>
+</table>
+<?php if ($serviceCount > 5): ?>
+<div class="services-link">
+ <?= $this->qlink(
+ sprintf($this->translate('List all %d services'), $serviceCount),
+ $this->url()->setPath('monitoring/list/services'),
+ null,
+ array(
+ 'data-base-target' => '_next',
+ 'icon' => 'forward'
+ )
+ ) ?>
+</div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/partials/show-more.phtml b/modules/monitoring/application/views/scripts/partials/show-more.phtml
new file mode 100644
index 0000000..fd6a99d
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/show-more.phtml
@@ -0,0 +1,15 @@
+<?php
+/** @var \Icinga\Module\Monitoring\DataView\DataView $dataView */
+if ($dataView->hasMore()): ?>
+<div class="text-right">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'data-base-target' => '_next',
+ 'class' => 'action-link'
+ )
+ ) ?>
+</div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/service/show.phtml b/modules/monitoring/application/views/scripts/service/show.phtml
new file mode 100644
index 0000000..bc9c612
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/service/show.phtml
@@ -0,0 +1,8 @@
+<div class="controls controls-separated">
+<?php if (! $this->compact): ?>
+ <?= $this->tabs ?>
+<?php endif ?>
+ <?= $this->render('partials/object/service-header.phtml') ?>
+ <?= $this->render('partials/object/quick-actions.phtml') ?>
+</div>
+<?= $this->render('partials/object/detail-content.phtml') ?>
diff --git a/modules/monitoring/application/views/scripts/services/show.phtml b/modules/monitoring/application/views/scripts/services/show.phtml
new file mode 100644
index 0000000..e9fb56f
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/services/show.phtml
@@ -0,0 +1,208 @@
+<div class="controls">
+
+ <?php if (! $this->compact): ?>
+ <?= $tabs ?>
+ <?php endif ?>
+ <?= $this->render('list/components/servicesummary.phtml') ?>
+ <?= $this->render('partials/service/objects-header.phtml') ?>
+ <?php
+ $serviceCount = count($objects);
+ $unhandledCount = count($unhandledObjects);
+ $problemCount = count($problemObjects);
+ $unackCount = count($unacknowledgedObjects);
+ $scheduledDowntimeCount = count($objects->getScheduledDowntimes());
+ ?>
+</div>
+
+<div class="content">
+
+ <?php if ($serviceCount === 0): ?>
+ <?= $this->translate('No services found matching the filter') ?>
+ <?php else: ?>
+ <?= $this->render('show/components/extensions.phtml') ?>
+ <h2> <?= $this->translate('Problem handling') ?> </h2>
+ <table class="name-value-table">
+ <tbody>
+ <?php if ($unackCount > 0): ?>
+ <tr>
+ <th> <?= sprintf($this->translate('%d unhandled problems'), $unackCount) ?> </th>
+ <td> <?= $this->qlink(
+ $this->translate('Acknowledge'),
+ $acknowledgeLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'check'
+ )
+ ) ?> </td>
+ </tr>
+ <?php endif; ?>
+
+ <?php if (($acknowledgedCount = count($acknowledgedObjects)) > 0): ?>
+ <tr>
+ <th> <?= sprintf(
+ $this->translatePlural(
+ '%s acknowledgement',
+ '%s acknowledgements',
+ $acknowledgedCount
+ ),
+ '<b>' . $acknowledgedCount . '</b>'
+ ) ?>
+ </th>
+ <td>
+ <?= $removeAckForm->setLabelEnabled(true) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+
+ <tr>
+ <th> <?= $this->translate('Comments') ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Add comments'),
+ $addCommentLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'comment-empty'
+ )
+ ) ?>
+ </td>
+ </tr>
+
+ <?php if (($commentCount = count($objects->getComments())) > 0): ?>
+ <tr>
+ <th></th>
+ <td>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%s comment',
+ '%s comments',
+ $commentCount
+ ),
+ $commentCount
+ ),
+ $commentsLink,
+ null,
+ array('data-base-target' => '_next')
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+
+ <tr>
+ <th>
+ <?= $this->translate('Downtimes') ?>
+ </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Schedule downtimes'),
+ $downtimeAllLink,
+ null,
+ array(
+ 'icon' => 'plug',
+ 'class' => 'action-link'
+ )
+ ) ?>
+ </td>
+ </tr>
+
+ <?php if ($scheduledDowntimeCount > 0): ?>
+ <tr>
+ <th></th>
+ <td>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%d scheduled downtime',
+ '%d scheduled downtimes',
+ $scheduledDowntimeCount
+ ),
+ $scheduledDowntimeCount
+ ),
+ $showDowntimesLink,
+ null,
+ array(
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+
+ </tbody>
+ </table>
+
+ <?php if ($this->hasPermission('monitoring/command/send-custom-notification')): ?>
+
+ <h2> <?= $this->translate('Notifications') ?> </h2>
+
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th> <?= $this->translate('Notifications') ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Send notifications'),
+ $sendCustomNotificationLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'bell'
+ )
+ ) ?>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <?php endif ?>
+
+ <h2> <?= $this->translate('Check Execution') ?> </h2>
+
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th> <?= $this->translate('Command') ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Process check result'),
+ $processCheckResultAllLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'edit'
+ )
+ ) ?>
+ </td>
+ </tr>
+
+ <?php if (isset($checkNowForm)): // Form is unset if the current user lacks the respective permission ?>
+ <tr>
+ <th> <?= $this->translate('Schedule Check') ?> </th>
+ <td> <?= $checkNowForm ?> </td>
+ </tr>
+ <?php endif ?>
+
+ <?php if (isset($rescheduleAllLink)): ?>
+ <tr>
+ <th></th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Reschedule'),
+ $rescheduleAllLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'calendar-empty'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ </tbody>
+ </table>
+ <h2><?= $this->translate('Feature Commands') ?></h2>
+ <?= $toggleFeaturesForm ?>
+ <?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml b/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml
new file mode 100644
index 0000000..fd7f6bb
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml
@@ -0,0 +1,94 @@
+<?php
+
+/** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+
+if (in_array((int) $object->state, array(0, 99))) {
+ // Ignore this markup if the object is in a non-problem state or pending
+ return;
+}
+
+if ($object->acknowledged):
+$acknowledgement = $object->acknowledgement;
+/** @var \Icinga\Module\Monitoring\Object\Acknowledgement $acknowledgement */
+?>
+<tr>
+ <th><?= $this->translate('Acknowledged') ?></th>
+ <td data-base-target="_self">
+ <?php if ($acknowledgement): ?>
+ <dl class="comment-list">
+ <dt>
+ <?= $this->escape($acknowledgement->getAuthor()) ?>
+ <span class="comment-time">
+ <?= $this->translate('acknowledged') ?>
+ <?= $this->timeAgo($acknowledgement->getEntryTime()) ?>
+ <?php if ($acknowledgement->expires()): ?>
+ <span aria-hidden="true">&#448;</span>
+ <?= sprintf(
+ $this->translate('Expires %s'),
+ $this->timeUntil($acknowledgement->getExpirationTime())
+ ) ?>
+ <?php endif ?>
+ </span>
+ <?php if ($acknowledgement->getSticky()): ?>
+ <?= $this->icon('pin', sprintf(
+ $this->translate(
+ 'Acknowledgement remains until the %1$s recovers even if the %1$s changes state'
+ ),
+ $object->getType(true)
+ )) ?>
+ <?php endif ?>
+ <?php if (isset($removeAckForm)) {
+ // Form is unset if the current user lacks the respective permission
+ $removeAckForm->setAttrib('class', $removeAckForm->getAttrib('class') . ' remove-action');
+ echo $removeAckForm;
+ } ?>
+ </dt>
+ <dd>
+ <?= $this->nl2br($this->createTicketLinks($this->markdown($acknowledgement->getComment()))) ?>
+ </dd>
+ </dl>
+ <?php elseif (isset($removeAckForm)): ?>
+ <?= $removeAckForm ?>
+ <?php endif ?>
+ </td>
+</tr>
+<?php else: ?>
+<tr>
+ <th><?= $this->translate('Not acknowledged') ?></th>
+ <td>
+ <?php if ($this->hasPermission('monitoring/command/acknowledge-problem')) {
+ if ($object->getType() === $object::TYPE_HOST) {
+ $ackLink = $this->href(
+ 'monitoring/host/acknowledge-problem',
+ array('host' => $object->getName()),
+ null,
+ array('class' => 'action-link')
+ );
+ } else {
+ $ackLink = $this->href(
+ 'monitoring/service/acknowledge-problem',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ null,
+ array('class' => 'action-link')
+ );
+ }
+ ?>
+ <?= $this->qlink(
+ $this->translate('Acknowledge'),
+ $ackLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'edit',
+ 'title' => $this->translate(
+ 'Acknowledge this problem, suppress all future notifications for it and tag it as being handled'
+ )
+ )
+ ) ?>
+ <?php } else {
+ echo '&#45;';
+ } // endif ?>
+ </td>
+</tr>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/show/components/actions.phtml b/modules/monitoring/application/views/scripts/show/components/actions.phtml
new file mode 100644
index 0000000..938ab2a
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/actions.phtml
@@ -0,0 +1,43 @@
+<?php
+
+use Icinga\Web\Navigation\Navigation;
+
+$navigation = new Navigation();
+$navigation->load($object->getType() . '-action');
+foreach ($navigation as $item) {
+ $item->setObject($object);
+}
+
+foreach ($object->getActionUrls() as $i => $link) {
+ $navigation->addItem(
+
+ // add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201
+ $this->icon(
+ 'forward',
+ $this->translate('Link opens in new window'),
+ array('aria-label' => $this->translate('Link opens in new window'))
+ ) . ' Action ' . ($i + 1),
+ array(
+ 'url' => $link,
+ 'target' => '_blank',
+ 'renderer' => array(
+ 'NavigationItemRenderer',
+ 'escape_label' => false
+ )
+ )
+ );
+}
+
+if (isset($this->actions)) {
+ $navigation->merge($this->actions);
+}
+
+if ($navigation->isEmpty() || ! $navigation->hasRenderableItems()) {
+ return;
+}
+
+?>
+<tr>
+ <th><?= $this->translate('Actions'); ?></th>
+ <?= $navigation->getRenderer()->setElementTag('td')->setCssClass('actions go-ahead'); ?>
+</tr>
diff --git a/modules/monitoring/application/views/scripts/show/components/checksource.phtml b/modules/monitoring/application/views/scripts/show/components/checksource.phtml
new file mode 100644
index 0000000..ac9799f
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/checksource.phtml
@@ -0,0 +1,6 @@
+<?php if ($object->check_source !== null): ?>
+<tr>
+ <th><?= $this->translate('Check Source') ?></th>
+ <td><?= $this->escape($object->check_source) ?></td>
+</tr>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml b/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml
new file mode 100644
index 0000000..e37e30a
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml
@@ -0,0 +1,85 @@
+<?php
+/** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+$activeChecksEnabled = (bool) $object->active_checks_enabled;
+?>
+
+<tr>
+ <th><?= $activeChecksEnabled ? $this->translate('Last check') : $this->translate('Last update') ?></th>
+ <td data-base-target="_self">
+<?php if ((int) $object->state !== 99): ?>
+ <?= $this->timeAgo($object->last_check) ?>
+ <?php if ($object->next_update < time()): ?>
+ <?= $this->icon('circle', $this->translate('Check result is late'), array('class' => 'icon-stateful state-critical')) ?>
+ <?php endif ?>
+<?php endif ?>
+ <?php if (isset($checkNowForm)) { // Form is unset if the current user lacks the respective permission
+ echo $checkNowForm;
+ } ?>
+ </td>
+</tr>
+
+<tr>
+ <th><?= $activeChecksEnabled ? $this->translate('Next check') : $this->translate('Next update') ?></th>
+ <td>
+ <?php if ((int) $object->state !== 99) {
+ if ($activeChecksEnabled) {
+ echo $this->timeUntil($object->next_check);
+ } else {
+ echo sprintf($this->translate('expected %s'), $this->timeUntil($object->next_update));
+ }
+ } ?>
+ <?php if ($activeChecksEnabled && $this->hasPermission('monitoring/command/schedule-check')) {
+ if ($object->getType() === $object::TYPE_SERVICE) {
+ echo $this->qlink(
+ $this->translate('Reschedule'),
+ 'monitoring/service/reschedule-check',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'calendar-empty',
+ 'title' => $this->translate(
+ 'Schedule the next active check at a different time than the current one'
+ )
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Reschedule'),
+ 'monitoring/host/reschedule-check',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'calendar-empty',
+ 'title' => $this->translate(
+ 'Schedule the next active check at a different time than the current one'
+ )
+ )
+ );
+ }
+ } ?>
+ </td>
+</tr>
+
+<tr>
+ <th><?= $this->translate('Check attempts') ?></th>
+ <td>
+ <?= $object->attempt ?>
+ (<?= (int) $object->state_type === 0 ? $this->translate('soft state') : $this->translate('hard state') ?>)
+ </td>
+</tr>
+
+<?php if ($object->check_execution_time): ?>
+<tr>
+ <th><?= $this->translate('Check execution time') ?></th>
+ <td><?= round((float) $object->check_execution_time, 3) ?>s</td>
+</tr>
+<?php endif ?>
+
+<?php if ($object->check_latency): ?>
+<tr>
+ <th><?= $this->translate('Check latency') ?></th>
+ <td><?= $object->check_latency ?>s</td>
+</tr>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/show/components/checktimeperiod.phtml b/modules/monitoring/application/views/scripts/show/components/checktimeperiod.phtml
new file mode 100644
index 0000000..34c4eb9
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/checktimeperiod.phtml
@@ -0,0 +1,21 @@
+<?php if (isset($object->service_check_timeperiod)): ?>
+
+<tr>
+ <th><?= $this->translate('Check Timeperiod') ?></th>
+ <td>
+ <?= $object->service_check_timeperiod ?>
+ </td>
+</tr>
+
+<?php endif ?>
+
+<?php if (isset($object->host_check_timeperiod)): ?>
+
+ <tr>
+ <th><?= $this->translate('Check Timeperiod') ?></th>
+ <td>
+ <?= $object->host_check_timeperiod ?>
+ </td>
+ </tr>
+
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/show/components/command.phtml b/modules/monitoring/application/views/scripts/show/components/command.phtml
new file mode 100644
index 0000000..9b51458
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/command.phtml
@@ -0,0 +1,52 @@
+<?php
+$parts = explode('!', $object->check_command);
+$command = array_shift($parts);
+
+if ($showInstance): ?>
+<tr>
+ <th><?= $this->translate('Instance') ?></th>
+ <td><?= $this->escape($object->instance_name) ?></td>
+</tr>
+<?php endif ?>
+<tr>
+ <th><?= $this->translate('Command') ?></th>
+ <td>
+ <?= $this->escape($command) ?>
+ <?php if ($this->hasPermission('monitoring/command/process-check-result') && $object->passive_checks_enabled) {
+ $title = sprintf(
+ $this->translate('Submit a one time or so called passive result for the %s check'), $command
+ );
+ if ($object->getType() === $object::TYPE_HOST) {
+ echo $this->qlink(
+ $this->translate('Process check result'),
+ 'monitoring/host/process-check-result',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'edit',
+ 'title' => $title
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Process check result'),
+ 'monitoring/service/process-check-result',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'edit',
+ 'title' => $title
+ )
+ );
+ }
+ } ?>
+ </td>
+</tr>
+
+<?php
+$row = "<tr>\n <th>%s</th>\n <td>%s</td>\n</tr>\n";
+for ($i = 0; $i < count($parts); $i++) {
+ printf($row, '$ARG' . ($i + 1) . '$', $this->escape($parts[$i]));
+}
diff --git a/modules/monitoring/application/views/scripts/show/components/comments.phtml b/modules/monitoring/application/views/scripts/show/components/comments.phtml
new file mode 100644
index 0000000..fd980ee
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/comments.phtml
@@ -0,0 +1,86 @@
+<?php
+$addLink = false;
+if ($this->hasPermission('monitoring/command/comment/add')) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ if ($object->getType() === $object::TYPE_HOST) {
+ $addLink = $this->qlink(
+ $this->translate('Add comment'),
+ 'monitoring/host/add-comment',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'comment-empty',
+ 'title' => $this->translate('Add a new comment to this host')
+ )
+ );
+ } else {
+ $addLink = $this->qlink(
+ $this->translate('Add comment'),
+ 'monitoring/service/add-comment',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'comment-empty',
+ 'title' => $this->translate('Add a new comment to this service')
+ )
+ );
+ }
+}
+if (empty($object->comments) && ! $addLink) {
+ return;
+}
+?>
+<tr>
+ <th><?php
+ echo $this->translate('Comments');
+ if (! empty($object->comments) && $addLink) {
+ echo '<br>' . $addLink;
+ }
+ ?></th>
+ <td data-base-target="_self">
+ <?php if (empty($object->comments)):
+ echo $addLink;
+ else: ?>
+ <dl class="comment-list">
+ <?php foreach ($object->comments as $comment): ?>
+ <dt>
+ <a data-base-target="_next" href="<?= $this->href('monitoring/comment/show', array('comment_id' => $comment->id)) ?>">
+ <?= $this->escape($comment->author) ?>
+ <span class="comment-time">
+ <?= $this->translate('commented') ?>
+ <?= $this->timeAgo($comment->timestamp) ?>
+ <?php if ($comment->expiration): ?>
+ <span aria-hidden="true">ǀ</span>
+ <?= sprintf(
+ $this->translate('Expires %s'),
+ $this->timeUntil($comment->expiration)
+ ) ?>
+ <?php endif ?>
+ </span>
+ </a>
+ <?= $comment->persistent ? $this->icon('attach', 'This comment is persistent.') : '' ?>
+ <?php if (isset($delCommentForm)) {
+ // Form is unset if the current user lacks the respective permission
+ $deleteButton = clone($delCommentForm);
+ /** @var \Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm $deleteButton */
+ $deleteButton->setAttrib('class', $deleteButton->getAttrib('class') . ' remove-action');
+ $deleteButton->populate(
+ array(
+ 'comment_id' => $comment->id,
+ 'comment_is_service' => isset($comment->service_description),
+ 'comment_name' => $comment->name
+ )
+ );
+ echo $deleteButton;
+ } ?>
+ </dt>
+ <dd>
+ <?= $this->nl2br($this->createTicketLinks($this->markdownLine($comment->comment, [ 'class' => 'caption']))) ?>
+ </dd>
+ <?php endforeach ?>
+ </dl>
+ <?php endif ?>
+ </td>
+</tr>
diff --git a/modules/monitoring/application/views/scripts/show/components/contacts.phtml b/modules/monitoring/application/views/scripts/show/components/contacts.phtml
new file mode 100644
index 0000000..5661c1a
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/contacts.phtml
@@ -0,0 +1,38 @@
+<?php
+
+if ($object->contacts->hasResult()) {
+
+ $list = array();
+ foreach ($object->contacts as $contact) {
+ $list[] = $this->qlink(
+ $contact->contact_alias,
+ 'monitoring/show/contact',
+ array('contact_name' => $contact->contact_name),
+ array('title' => sprintf($this->translate('Show detailed information about %s'), $contact->contact_alias))
+ );
+ }
+
+ printf(
+ "<tr><th>%s</th><td class=\"go-ahead\">%s</td></tr>\n",
+ $this->translate('Contacts'),
+ implode(', ', $list)
+ );
+}
+
+if ($object->contactgroups->hasResult()) {
+ $list = array();
+ foreach ($object->contactgroups as $contactgroup) {
+ $list[] = $this->qlink(
+ $contactgroup->contactgroup_alias,
+ 'monitoring/list/contactgroups',
+ array('contactgroup_name' => $contactgroup->contactgroup_name),
+ array('title' => sprintf($this->translate('List contacts in contact-group "%s"'), $contactgroup->contactgroup_alias))
+ );
+ }
+
+ printf(
+ "<tr><th>%s</th><td class=\"go-ahead\">%s</td></tr>\n",
+ $this->translate('Contactgroups'),
+ implode(', ', $list)
+ );
+}
diff --git a/modules/monitoring/application/views/scripts/show/components/downtime.phtml b/modules/monitoring/application/views/scripts/show/components/downtime.phtml
new file mode 100644
index 0000000..618d4d9
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/downtime.phtml
@@ -0,0 +1,109 @@
+<?php
+$addLink = false;
+if ($this->hasPermission('monitoring/command/downtime/schedule')) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ if ($object->getType() === $object::TYPE_HOST) {
+ $addLink = $this->qlink(
+ $this->translate('Schedule downtime'),
+ 'monitoring/host/schedule-downtime',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'plug',
+ 'title' => $this->translate(
+ 'Schedule a downtime to suppress all problem notifications within a specific period of time'
+ )
+ )
+ );
+ } else {
+ $addLink = $this->qlink(
+ $this->translate('Schedule downtime'),
+ 'monitoring/service/schedule-downtime',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'plug',
+ 'title' => $this->translate(
+ 'Schedule a downtime to suppress all problem notifications within a specific period of time'
+ )
+ )
+ );
+ }
+}
+if (empty($object->downtimes) && ! $addLink) {
+ return;
+}
+?>
+<tr>
+ <th><?php
+ echo $this->translate('Downtimes');
+ if (! empty($object->downtimes) && $addLink) {
+ echo '<br>' . $addLink;
+ }
+ ?></th>
+ <td data-base-target="_self">
+ <?php if (empty($object->downtimes)):
+ echo $addLink;
+ else: ?>
+ <dl class="comment-list">
+ <?php foreach ($object->downtimes as $downtime):
+ if ((bool) $downtime->is_in_effect) {
+ $state = sprintf(
+ $this->translate('expires %s', 'Last format parameter represents the downtime expire time'),
+ $this->timeUntil($downtime->end, false, true)
+ );
+ } else {
+ if ($downtime->start <= time()) {
+ $state = sprintf(
+ $this->translate('ends %s', 'Last format parameter represents the end time'),
+ $this->timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, false, true)
+ );
+ } else {
+ $state = sprintf(
+ $this->translate('scheduled %s', 'Last format parameter represents the time scheduled'),
+ $this->timeUntil($downtime->start, false, true)
+ ) . ' ' . sprintf(
+ $this->translate('expires %s', 'Last format parameter represents the downtime expire time'),
+ $this->timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, false, true)
+ );
+ }
+ }
+ ?>
+ <dt>
+ <?= $this->escape(sprintf(
+ $downtime->is_flexible
+ ? $this->translate('Flexible downtime by %s')
+ : $this->translate('Fixed downtime by %s'),
+ $downtime->author_name
+ )) ?>
+ <span class="comment-time">
+ <?= $state ?>
+ <span aria-hidden="true">&#448;</span>
+ <?= $this->translate('created') ?>
+ <?= $this->timeAgo($downtime->entry_time) ?>
+ </span>
+ <?php if (isset($delDowntimeForm)) {
+ // Form is unset if the current user lacks the respective permission
+ $deleteButton = clone($delDowntimeForm);
+ /** @var \Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm $deleteButton */
+ $deleteButton->setAttrib('class', $deleteButton->getAttrib('class') . ' remove-action');
+ $deleteButton->populate(
+ array(
+ 'downtime_id' => $downtime->id,
+ 'downtime_is_service' => $object->getType() === $object::TYPE_SERVICE,
+ 'downtime_name' => $downtime->name
+ )
+ );
+ echo $deleteButton;
+ } ?>
+ </dt>
+ <dd>
+ <?= $this->nl2br($this->createTicketLinks($this->markdown($downtime->comment))) ?>
+ </dd>
+ <?php endforeach ?>
+ </dl>
+ <?php endif ?>
+ </td>
+</tr>
diff --git a/modules/monitoring/application/views/scripts/show/components/extensions.phtml b/modules/monitoring/application/views/scripts/show/components/extensions.phtml
new file mode 100644
index 0000000..263b7e4
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/extensions.phtml
@@ -0,0 +1,4 @@
+<?php
+foreach ($extensionsHtml as $extensionHtml) {
+ echo $extensionHtml;
+}
diff --git a/modules/monitoring/application/views/scripts/show/components/flags.phtml b/modules/monitoring/application/views/scripts/show/components/flags.phtml
new file mode 100644
index 0000000..871a4dd
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/flags.phtml
@@ -0,0 +1,4 @@
+<div data-base-target="_self">
+ <h2><?= $this->translate('Feature Commands') ?></h2>
+ <?= $toggleFeaturesForm ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/show/components/flapping.phtml b/modules/monitoring/application/views/scripts/show/components/flapping.phtml
new file mode 100644
index 0000000..f09b107
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/flapping.phtml
@@ -0,0 +1,14 @@
+<?php
+
+if ($object->is_flapping) {
+ printf(
+ "<tr><th>%s</th><td>%s %s</td></tr>\n",
+ 'Flapping',
+ $this->icon('flapping', 'Flapping'),
+ sprintf(
+ 'Currently flapping with a %.2f%% state change rate',
+ $object->percent_state_change
+ )
+ );
+}
+
diff --git a/modules/monitoring/application/views/scripts/show/components/grapher.phtml b/modules/monitoring/application/views/scripts/show/components/grapher.phtml
new file mode 100644
index 0000000..0b49e63
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/grapher.phtml
@@ -0,0 +1,6 @@
+<?php if (isset($graphers)) {
+ foreach ($graphers as $grapher) {
+ echo $grapher->getPreviewHtml($object);
+ }
+} ?>
+
diff --git a/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml b/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml
new file mode 100644
index 0000000..377b56f
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml
@@ -0,0 +1,19 @@
+<?php
+
+if (empty($object->hostgroups)) return;
+
+$list = array();
+foreach ($object->hostgroups as $name => $alias) {
+ $list[] = $this->qlink(
+ $alias,
+ 'monitoring/list/hosts',
+ array('hostgroup_name' => $name),
+ array('title' => sprintf($this->translate('List all hosts in the group "%s"'), $alias))
+ );
+}
+printf(
+ "<tr><th>%s</th><td class=\"go-ahead\">%s</td></tr>\n",
+ $this->translate('Hostgroups'),
+ implode(', ', $list)
+);
+
diff --git a/modules/monitoring/application/views/scripts/show/components/notes.phtml b/modules/monitoring/application/views/scripts/show/components/notes.phtml
new file mode 100644
index 0000000..c868c95
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/notes.phtml
@@ -0,0 +1,48 @@
+<?php
+
+use Icinga\Web\Navigation\Navigation;
+
+/** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+
+$navigation = new Navigation();
+$notes = trim($object->notes);
+
+$links = $object->getNotesUrls();
+if (! empty($links)) {
+ foreach ($links as $link) {
+ $navigation->addItem(
+ // add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201
+ $this->icon(
+ 'forward',
+ $this->translate('Link opens in new window'),
+ array('aria-label' => $this->translate('Link opens in new window'))
+ ) . ' ' . $this->escape($link),
+ array(
+ 'url' => $link,
+ 'target' => '_blank',
+ 'renderer' => array(
+ 'NavigationItemRenderer',
+ 'escape_label' => false
+ )
+ )
+ );
+ }
+}
+
+if (($navigation->isEmpty() || ! $navigation->hasRenderableItems()) && $notes === '') {
+ return;
+}
+?>
+<tr>
+ <th><?= $this->translate('Notes') ?></th>
+ <td>
+ <?= $navigation->getRenderer() ?>
+ <?php if ($notes !== ''): ?>
+ <?= $this->markdown($notes, [
+ 'id' => $object->type . '-notes',
+ 'class' => 'collapsible',
+ 'data-visible-height' => 200
+ ]) ?>
+ <?php endif ?>
+ </td>
+</tr> \ No newline at end of file
diff --git a/modules/monitoring/application/views/scripts/show/components/notifications.phtml b/modules/monitoring/application/views/scripts/show/components/notifications.phtml
new file mode 100644
index 0000000..3e8c665
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/notifications.phtml
@@ -0,0 +1,68 @@
+<tr>
+ <th><?= $this->translate('Notifications') ?></th>
+ <td>
+ <?php
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ if ($this->hasPermission('monitoring/command/send-custom-notification')) {
+ if ($object->getType() === $object::TYPE_HOST) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ echo $this->qlink(
+ $this->translate('Send notification'),
+ 'monitoring/host/send-custom-notification',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'bell',
+ 'title' => $this->translate(
+ 'Send a custom notification to contacts responsible for this host'
+ )
+ )
+ );
+ } else {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ echo $this->qlink(
+ $this->translate('Send notification'),
+ 'monitoring/service/send-custom-notification',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'bell',
+ 'title' => $this->translate(
+ 'Send a custom notification to contacts responsible for this service'
+ )
+ )
+ );
+ }
+ if (! in_array((int) $object->state, array(0, 99))) {
+ echo '<br>';
+ }
+ } elseif (in_array((int) $object->state, array(0, 99))) {
+ echo '&#45;';
+ }
+ // We are not interested in notifications for OK or pending objects
+ if (! in_array((int) $object->state, array(0, 99))) {
+ if ($object->current_notification_number > 0) {
+ if ((int) $object->current_notification_number === 1) {
+ $msg = sprintf(
+ $this->translate('A notification has been sent for this issue %s.'),
+ $this->timeAgo($object->last_notification)
+ );
+ } else {
+ $msg = sprintf(
+ $this->translate('%d notifications have been sent for this issue.'),
+ $object->current_notification_number
+ ) . '<br>' . sprintf(
+ $this->translate('The last one was sent %s.'),
+ $this->timeAgo($object->last_notification)
+ );
+ }
+ } else {
+ $msg = $this->translate('No notification has been sent for this issue.');
+ }
+ echo $msg;
+ }
+ ?>
+ </td>
+</tr>
diff --git a/modules/monitoring/application/views/scripts/show/components/output.phtml b/modules/monitoring/application/views/scripts/show/components/output.phtml
new file mode 100644
index 0000000..34d8268
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/output.phtml
@@ -0,0 +1,5 @@
+<h2><?= $this->translate('Plugin Output') ?></h2>
+<div id="check-output-<?= $this->escape(str_replace(' ', '-', $object->check_command)) ?>" class="collapsible" data-visible-height="100">
+ <?= $this->pluginOutput($object->output, false, $object->check_command) ?>
+ <?= $this->pluginOutput($object->long_output, false, $object->check_command) ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/show/components/perfdata.phtml b/modules/monitoring/application/views/scripts/show/components/perfdata.phtml
new file mode 100644
index 0000000..78ea6d2
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/perfdata.phtml
@@ -0,0 +1,4 @@
+<?php if ($object->perfdata): ?>
+<h2><?= $this->translate('Performance data') ?></h2>
+<div id="check-perfdata-<?= $this->escape(str_replace(' ', '-', $object->check_command)) ?>"><?= $this->perfdata($object->perfdata) ?></div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/show/components/reachable.phtml b/modules/monitoring/application/views/scripts/show/components/reachable.phtml
new file mode 100644
index 0000000..8d55e84
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/reachable.phtml
@@ -0,0 +1,15 @@
+<?php if ($object->is_reachable !== null): ?>
+<tr>
+ <th>
+ <?= $this->translate('Reachable') ?>
+ </th>
+ <td>
+ <span class="check-source-meta"><?= (bool) $object->is_reachable ? $this->translate('yes') : $this->translate('no') ?></span>
+ <?php if ((bool) $object->is_reachable) {
+ echo $this->icon('circle', $this->translate('Is reachable'), array('class' => 'icon-stateful state-ok'));
+ } else {
+ echo $this->icon('circle', $this->translate('Not reachable'), array('class' => 'icon-stateful state-critical'));
+ } ?>
+ </td>
+</tr>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml b/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml
new file mode 100644
index 0000000..09ff248
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml
@@ -0,0 +1,20 @@
+<?php
+
+if (empty($object->servicegroups)) return;
+
+$list = array();
+foreach ($object->servicegroups as $name => $alias) {
+ $list[] = $this->qlink(
+ $alias,
+ 'monitoring/list/services',
+ array('servicegroup_name' => $name),
+ array('title' => sprintf($this->translate('List all services in the group "%s"'), $alias))
+ );
+}
+
+printf(
+ "<tr><th>%s</th><td class=\"go-ahead\">%s</td></tr>\n",
+ $this->translate('Servicegroups'),
+ implode(', ', $list)
+);
+
diff --git a/modules/monitoring/application/views/scripts/show/components/status.phtml b/modules/monitoring/application/views/scripts/show/components/status.phtml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/status.phtml
diff --git a/modules/monitoring/application/views/scripts/show/contact.phtml b/modules/monitoring/application/views/scripts/show/contact.phtml
new file mode 100644
index 0000000..aa448ae
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/contact.phtml
@@ -0,0 +1,70 @@
+<?php $contactHelper = $this->getHelper('ContactFlags') ?>
+<div class="controls">
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+ <h1><?= $this->translate('Contact details') ?></h1>
+ <div class="circular" style="background-image: url('<?=
+ $this->href('static/gravatar', array('email' => $contact->contact_email))
+ ?>';width:120px;height:120px;)"></div>
+
+<?php if (! $contact): ?>
+ <?= $this->translate('No such contact') ?>: <?= $contactName ?>
+</div>
+<?php return; endif ?>
+
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th style="width: 20%"></th>
+ <td><strong><?= $this->escape($contact->contact_alias) ?></strong> (<?= $contact->contact_name ?>)</td>
+ </tr>
+<?php if ($contact->contact_email): ?>
+ <tr>
+ <th><?= $this->translate('Email') ?></th>
+ <td>
+ <a href="mailto:<?= $contact->contact_email; ?>" title="<?= sprintf($this->translate('Send a mail to %s'), $contact->contact_alias); ?>" aria-label="<?= sprintf($this->translate('Send a mail to %s'), $contact->contact_alias); ?>">
+ <?= $this->escape($contact->contact_email); ?>
+ </a>
+ </td>
+ </tr>
+<?php endif ?>
+<?php if ($contact->contact_pager): ?>
+ <tr>
+ <th><?= $this->translate('Pager') ?></th>
+ <td><?= $this->escape($contact->contact_pager) ?></td>
+ </tr>
+<?php endif ?>
+ <tr>
+ <th><?= $this->translate('Hosts') ?></th>
+ <td><?= $this->escape($contactHelper->contactFlags($contact, 'host')) ?><br />
+ <?= $this->escape($contact->contact_notify_host_timeperiod) ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Services') ?></th>
+ <td><?= $this->escape($contactHelper->contactFlags($contact, 'service')) ?><br />
+ <?= $this->escape($contact->contact_notify_service_timeperiod) ?></td>
+ </tr>
+ </tbody>
+ </table>
+ <?php if (count($commands)): ?>
+ <h1><?= $this->translate('Commands') ?>:</h1>
+ <ul>
+ <?php foreach ($commands as $command): ?>
+ <li><?= $command->command_name ?></li>
+ <?php endforeach ?>
+ </ul>
+ <?php endif ?>
+ <h1><?= $this->translate('Notifications sent to this contact') ?></h1>
+ <?= $this->limiter; ?>
+ <?= $this->paginator; ?>
+</div>
+
+<?php if (count($notifications)): ?>
+<?= $this->partial('list/notifications.phtml', array(
+ 'notifications' => $notifications,
+ 'compact' => true
+)); ?>
+<?php else: ?>
+<div class="content"><?= $this->translate('No notifications have been sent for this contact') ?></div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml b/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml
new file mode 100644
index 0000000..e6dc0be
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml
@@ -0,0 +1,131 @@
+<div class="box hostservicechecks col-1-2">
+ <div class="box header">
+ <h2><?= $this->translate('Host and Service Checks'); ?></h2>
+ </div>
+ <div class="box contents">
+ <table>
+ <thead>
+ <tr>
+ <th><?= $this->translate('Hosts'); ?></th>
+ <th><?= $this->translate('Services'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+<?php if ($this->statusSummary->hosts_active): ?>
+ <div class="box entry"><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Active', '%u Active', $this->statusSummary->hosts_active),
+ $this->statusSummary->hosts_active
+ ),
+ 'monitoring/list/hosts',
+ array('host_active_checks_enabled' => 1),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u actively checked host',
+ 'List %u actively checked hosts',
+ $this->statusSummary->hosts_active
+ ),
+ $this->statusSummary->hosts_active
+ ))
+ ); ?></div>
+<?php endif ?>
+<?php if ($this->statusSummary->hosts_passive): ?>
+ <div class="box entry"><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%d Passive', '%d Passive', $this->statusSummary->hosts_passive),
+ $this->statusSummary->hosts_passive
+ ),
+ 'monitoring/list/hosts',
+ array('host_active_checks_enabled' => 0, 'host_passive_checks_enabled' => 1),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u passively checked host',
+ 'List %u passively checked hosts',
+ $this->statusSummary->hosts_passive
+ ),
+ $this->statusSummary->hosts_passive
+ ))
+ ); ?></div>
+<?php endif ?>
+<?php if ($this->statusSummary->hosts_not_checked): ?>
+ <div class="box entry"><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%d Disabled', '%d Disabled', $this->statusSummary->hosts_not_checked),
+ $this->statusSummary->hosts_not_checked
+ ),
+ 'monitoring/list/hosts',
+ array('host_active_checks_enabled' => 0, 'host_passive_checks_enabled' => 0),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is not being checked at all',
+ 'List %u hosts which are not being checked at all',
+ $this->statusSummary->hosts_not_checked
+ ),
+ $this->statusSummary->hosts_not_checked
+ ))
+ ); ?></div>
+<?php endif ?>
+ </td>
+ <td>
+<?php if ($this->statusSummary->services_active): ?>
+ <div class="box entry"><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%d Active', '%d Active', $this->statusSummary->services_active),
+ $this->statusSummary->services_active
+ ),
+ 'monitoring/list/services',
+ array('service_active_checks_enabled' => 1),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u actively checked service',
+ 'List %u actively checked services',
+ $this->statusSummary->services_active
+ ),
+ $this->statusSummary->services_active
+ ))
+ ); ?></div>
+<?php endif ?>
+<?php if ($this->statusSummary->services_passive): ?>
+ <div class="box entry"><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%d Passive', '%d Passive', $this->statusSummary->services_passive),
+ $this->statusSummary->services_passive
+ ),
+ 'monitoring/list/services',
+ array('service_active_checks_enabled' => 0, 'service_passive_checks_enabled' => 1),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u passively checked service',
+ 'List %u passively checked services',
+ $this->statusSummary->services_passive
+ ),
+ $this->statusSummary->services_passive
+ ))
+ ); ?></div>
+<?php endif ?>
+<?php if ($this->statusSummary->services_not_checked): ?>
+ <div class="box entry"><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%d Disabled', '%d Disabled', $this->statusSummary->services_not_checked),
+ $this->statusSummary->services_not_checked
+ ),
+ 'monitoring/list/services',
+ array('service_active_checks_enabled' => 0, 'service_passive_checks_enabled' => 0),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is not being checked at all',
+ 'List %u services which are not being checked at all',
+ $this->statusSummary->services_not_checked
+ ),
+ $this->statusSummary->services_not_checked
+ ))
+ ); ?></div>
+<?php endif ?>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml b/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml
new file mode 100644
index 0000000..eeeec16
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml
@@ -0,0 +1,287 @@
+<div class="box monitoringfeatures col-1-2">
+ <div class="box header">
+ <h2><?= $this->translate('Monitoring Features'); ?></h2>
+ </div>
+ <div class="box contents">
+<?php if ($this->statusSummary->hosts_without_flap_detection || $this->statusSummary->services_without_flap_detection ||
+ $this->statusSummary->hosts_flapping || $this->statusSummary->services_flapping): ?>
+ <div class="box-separator badge feature-highlight"><?= $this->translate('Flap Detection'); ?></div>
+<?php else: ?>
+ <div class="box-separator badge"><?= $this->translate('Flap Detection'); ?></div>
+<?php endif ?>
+ <table>
+ <tbody>
+ <tr>
+ <td>
+ <div class="box entry">
+<?php if ($this->statusSummary->hosts_without_flap_detection): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host Disabled', '%u Hosts Disabled', $this->statusSummary->hosts_without_flap_detection),
+ $this->statusSummary->hosts_without_flap_detection
+ ),
+ 'monitoring/list/hosts',
+ array('host_flap_detection_enabled' => 0),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host for which flap detection has been disabled',
+ 'List %u hosts for which flap detection has been disabled',
+ $this->statusSummary->hosts_without_flap_detection
+ ),
+ $this->statusSummary->hosts_without_flap_detection
+ )
+ )
+ ); ?>
+<?php else: ?>
+ <?= $this->qlink(
+ $this->translate('All Hosts Enabled'),
+ 'monitoring/list/hosts',
+ array('host_flap_detection_enabled' => 1),
+ array('title' => $this->translate(
+ 'List all hosts, for which flap detection is enabled entirely'
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($this->statusSummary->hosts_flapping): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host Flapping', '%u Hosts Flapping', $this->statusSummary->hosts_flapping),
+ $this->statusSummary->hosts_flapping
+ ),
+ 'monitoring/list/hosts',
+ array('host_is_flapping' => 1),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently flapping',
+ 'List %u hosts which are currently flapping',
+ $this->statusSummary->hosts_flapping
+ ),
+ $this->statusSummary->hosts_flapping
+ )
+ )
+ ); ?>
+<?php endif ?>
+ </div>
+ </td>
+ <td>
+ <div class="box entry">
+<?php if ($this->statusSummary->services_without_flap_detection): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Service Disabled', '%u Services Disabled', $this->statusSummary->services_without_flap_detection),
+ $this->statusSummary->services_without_flap_detection
+ ),
+ 'monitoring/list/services',
+ array('service_flap_detection_enabled' => 0),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u service for which flap detection has been disabled',
+ 'List %u services for which flap detection has been disabled',
+ $this->statusSummary->services_without_flap_detection
+ ),
+ $this->statusSummary->services_without_flap_detection
+ )
+ )
+ ); ?>
+<?php else: ?>
+ <?= $this->qlink(
+ $this->translate('All Services Enabled'),
+ 'monitoring/list/services',
+ array('service_flap_detection_enabled' => 1),
+ array('title' => $this->translate(
+ 'List all services, for which flap detection is enabled entirely'
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($this->statusSummary->services_flapping): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Service Flapping', '%u Services Flapping', $this->statusSummary->services_flapping),
+ $this->statusSummary->services_flapping
+ ),
+ 'monitoring/list/services',
+ array('service_is_flapping' => 1),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently flapping',
+ 'List %u services which are currently flapping',
+ $this->statusSummary->services_flapping
+ ),
+ $this->statusSummary->services_flapping
+ )
+ )
+ ); ?>
+<?php endif ?>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+<?php if ($this->statusSummary->hosts_not_triggering_notifications || $this->statusSummary->services_not_triggering_notifications): ?>
+ <div class="box-separator badge feature-highlight"><?= $this->translate('Notifications'); ?></div>
+<?php else: ?>
+ <div class="box-separator badge"><?= $this->translate('Notifications'); ?></div>
+<?php endif ?>
+ <table>
+ <tbody>
+ <tr>
+ <td>
+ <div class="box entry">
+<?php if ($this->statusSummary->hosts_not_triggering_notifications): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host Disabled', '%u Hosts Disabled', $this->statusSummary->hosts_not_triggering_notifications),
+ $this->statusSummary->hosts_not_triggering_notifications
+ ),
+ 'monitoring/list/hosts',
+ array('host_notifications_enabled' => 0),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host for which notifications are suppressed',
+ 'List %u hosts for which notifications are suppressed',
+ $this->statusSummary->hosts_not_triggering_notifications
+ ),
+ $this->statusSummary->hosts_not_triggering_notifications
+ )
+ )
+ ); ?>
+<?php else: ?>
+ <?= $this->qlink(
+ $this->translate('All Hosts Enabled'),
+ 'monitoring/list/hosts',
+ array('host_notifications_enabled' => 1),
+ array('title' => $this->translate(
+ 'List all hosts, for which notifications are enabled entirely'
+ ))
+ ); ?>
+<?php endif ?>
+ </div>
+ </td>
+ <td>
+ <div class="box entry">
+<?php if ($this->statusSummary->services_not_triggering_notifications): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Service Disabled', '%u Services Disabled', $this->statusSummary->services_not_triggering_notifications),
+ $this->statusSummary->services_not_triggering_notifications
+ ),
+ 'monitoring/list/services',
+ array('service_notifications_enabled' => 0),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u service for which notifications are suppressed',
+ 'List %u services for which notifications are suppressed',
+ $this->statusSummary->services_not_triggering_notifications
+ ),
+ $this->statusSummary->services_not_triggering_notifications
+ )
+ )
+ ); ?>
+<?php else: ?>
+ <?= $this->qlink(
+ $this->translate('All Services Enabled'),
+ 'monitoring/list/services',
+ array('service_notifications_enabled' => 1),
+ array('title' => $this->translate(
+ 'List all services, for which notifications are enabled entirely'
+ ))
+ ); ?>
+<?php endif ?>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+<?php if ($this->statusSummary->hosts_not_processing_event_handlers || $this->statusSummary->services_not_processing_event_handlers): ?>
+ <div class="box-separator badge feature-highlight"><?= $this->translate('Event Handlers'); ?></div>
+<?php else: ?>
+ <div class="box-separator badge"><?= $this->translate('Event Handlers'); ?></div>
+<?php endif ?>
+ <table>
+ <tbody>
+ <tr>
+ <td>
+ <div class="box entry">
+<?php if ($this->statusSummary->hosts_not_processing_event_handlers): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host Disabled', '%u Hosts Disabled', $this->statusSummary->hosts_not_processing_event_handlers),
+ $this->statusSummary->hosts_not_processing_event_handlers
+ ),
+ 'monitoring/list/hosts',
+ array('host_event_handler_enabled' => 0),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is not processing any event handlers',
+ 'List %u hosts which are not processing any event handlers',
+ $this->statusSummary->hosts_not_processing_event_handlers
+ ),
+ $this->statusSummary->hosts_not_processing_event_handlers
+ )
+ )
+ ); ?>
+<?php else: ?>
+ <?= $this->qlink(
+ $this->translate('All Hosts Enabled'),
+ 'monitoring/list/hosts',
+ array('host_event_handler_enabled' => 1),
+ array('title' => $this->translate(
+ 'List all hosts, which are processing event handlers entirely'
+ ))
+ ); ?>
+<?php endif ?>
+ </div>
+ </td>
+ <td>
+ <div class="box entry">
+<?php if ($this->statusSummary->services_not_processing_event_handlers): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Service Disabled', '%u Services Disabled', $this->statusSummary->services_not_processing_event_handlers),
+ $this->statusSummary->services_not_processing_event_handlers
+ ),
+ 'monitoring/list/services',
+ array('service_event_handler_enabled' => 0),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is not processing any event handlers',
+ 'List %u services which are not processing any event handlers',
+ $this->statusSummary->services_not_processing_event_handlers
+ ),
+ $this->statusSummary->services_not_processing_event_handlers
+ )
+ )
+ ); ?>
+<?php else: ?>
+ <?= $this->qlink(
+ $this->translate('All Services Enabled'),
+ 'monitoring/list/services',
+ array('service_event_handler_enabled' => 1),
+ array('title' => $this->translate(
+ 'List all services, which are processing event handlers entirely'
+ ))
+ ); ?>
+<?php endif ?>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml b/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml
new file mode 100644
index 0000000..05ffd29
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml
@@ -0,0 +1,81 @@
+<?php
+$service_problems = (
+ $this->statusSummary->services_warning_handled_on_ok_hosts ||
+ $this->statusSummary->services_warning_unhandled_on_ok_hosts ||
+ $this->statusSummary->services_critical_handled_on_ok_hosts ||
+ $this->statusSummary->services_critical_unhandled_on_ok_hosts ||
+ $this->statusSummary->services_unknown_handled_on_ok_hosts ||
+ $this->statusSummary->services_unknown_unhandled_on_ok_hosts
+);
+?>
+<div class="box ok_hosts state_<?= $this->statusSummary->hosts_up ? 'up' : 'pending'; ?> col-1-2">
+ <div class="box header">
+ <?php if ($this->statusSummary->hosts_up): ?>
+ <h2><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host UP', '%u Hosts UP', $this->statusSummary->hosts_up),
+ $this->statusSummary->hosts_up
+ ),
+ 'monitoring/list/hosts',
+ array('host_state' => 0),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state UP',
+ 'List %u hosts which are currently in state UP',
+ $this->statusSummary->hosts_up
+ ),
+ $this->statusSummary->hosts_up
+ ))
+ ); ?></h2>
+ <?php endif ?>
+ <?php if ($this->statusSummary->hosts_pending): ?>
+ <h2><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host PENDING', '%u Hosts PENDING', $this->statusSummary->hosts_pending),
+ $this->statusSummary->hosts_pending
+ ),
+ 'monitoring/list/hosts',
+ array('host_state' => 99),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state PENDING',
+ 'List %u hosts which are currently in state PENDING',
+ $this->statusSummary->hosts_pending
+ ),
+ $this->statusSummary->hosts_pending
+ ))
+ ); ?></h2>
+ <?php endif ?>
+ </div>
+<?php if ($service_problems || $this->statusSummary->hosts_down || $this->statusSummary->hosts_unreachable): ?>
+ <div class="box contents">
+ <?= $this->partial(
+ 'tactical/components/parts/servicestatesummarybyhoststate.phtml',
+ array(
+ 'translationDomain' => $this->translationDomain,
+ 'host_problem' => 0,
+ 'services_ok' => $this->statusSummary->services_ok_on_ok_hosts,
+ 'services_ok_not_checked' => $this->statusSummary->services_ok_not_checked_on_ok_hosts,
+ 'services_pending' => $this->statusSummary->services_pending_on_ok_hosts,
+ 'services_pending_not_checked' => $this->statusSummary->services_pending_not_checked_on_ok_hosts,
+ 'services_warning_handled' => $this->statusSummary->services_warning_handled_on_ok_hosts,
+ 'services_warning_unhandled' => $this->statusSummary->services_warning_unhandled_on_ok_hosts,
+ 'services_warning_passive' => $this->statusSummary->services_warning_passive_on_ok_hosts,
+ 'services_warning_not_checked' => $this->statusSummary->services_warning_not_checked_on_ok_hosts,
+ 'services_critical_handled' => $this->statusSummary->services_critical_handled_on_ok_hosts,
+ 'services_critical_unhandled' => $this->statusSummary->services_critical_unhandled_on_ok_hosts,
+ 'services_critical_passive' => $this->statusSummary->services_critical_passive_on_ok_hosts,
+ 'services_critical_not_checked' => $this->statusSummary->services_critical_not_checked_on_ok_hosts,
+ 'services_unknown_handled' => $this->statusSummary->services_unknown_handled_on_ok_hosts,
+ 'services_unknown_unhandled' => $this->statusSummary->services_unknown_unhandled_on_ok_hosts,
+ 'services_unknown_passive' => $this->statusSummary->services_unknown_passive_on_ok_hosts,
+ 'services_unknown_not_checked' => $this->statusSummary->services_unknown_not_checked_on_ok_hosts
+ )
+ ); ?>
+<?php else: ?>
+ <div class="box contents zero">
+ <h3>0</h3>
+ <span><?= $this->translate('Service Problems'); ?></span>
+<?php endif ?>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml b/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml
new file mode 100644
index 0000000..4f32daf
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml
@@ -0,0 +1,394 @@
+<?php
+
+use Icinga\Module\Monitoring\Object\Service;
+
+?>
+<?php if ($services_critical_handled || $services_critical_unhandled): ?>
+<div class="box badge entry state-<?= Service::getStateText(2); ?> <?= $services_critical_unhandled ? '' : 'handled'; ?>">
+<?php if ($services_critical_unhandled): ?>
+ <?= $this->qlink(
+ $services_critical_unhandled . ' ' . Service::getStateText(2, true),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 2,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state CRITICAL',
+ 'List %u services which are currently in state CRITICAL',
+ $services_critical_unhandled
+ ),
+ $services_critical_unhandled
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_critical_handled): ?>
+ <?= $this->qlink(
+ $services_critical_handled . ' ' . (
+ $services_critical_unhandled ? $this->translate('Handled') : Service::getStateText(2, true)
+ ),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 2,
+ 'service_handled' => 1
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state CRITICAL (Handled)',
+ 'List %u services which are currently in state CRITICAL (Handled)',
+ $services_critical_handled
+ ),
+ $services_critical_handled
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_critical_passive): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is passively checked',
+ '%u are passively checked',
+ $services_critical_passive
+ ),
+ $services_critical_passive
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 2,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 1
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state CRITICAL and passively checked',
+ 'List %u services which are currently in state CRITICAL and passively checked',
+ $services_critical_passive
+ ),
+ $services_critical_passive
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_critical_not_checked): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is not checked at all',
+ '%u are not checked at all',
+ $services_critical_not_checked
+ ),
+ $services_critical_not_checked
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 2,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state CRITICAL and not checked at all',
+ 'List %u services which are currently in state CRITICAL and not checked at all',
+ $services_critical_not_checked
+ ),
+ $services_critical_not_checked
+ ))
+ ); ?>
+<?php endif ?>
+</div>
+<?php endif ?>
+<?php if ($services_warning_handled || $services_warning_unhandled): ?>
+<div class="box badge entry state-<?= Service::getStateText(1); ?> <?= $services_warning_unhandled ? '' : 'handled'; ?>">
+<?php if ($services_warning_unhandled): ?>
+ <?= $this->qlink(
+ $services_warning_unhandled . ' ' . Service::getStateText(1, true),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 1,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state WARNING',
+ 'List %u services which are currently in state WARNING',
+ $services_warning_unhandled
+ ),
+ $services_warning_unhandled
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_warning_handled): ?>
+ <?= $this->qlink(
+ $services_warning_handled . ' ' . (
+ $services_warning_unhandled ? $this->translate('Handled') : Service::getStateText(1, true)
+ ),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 1,
+ 'service_handled' => 1
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state WARNING (Handled)',
+ 'List %u services which are currently in state WARNING (Handled)',
+ $services_warning_handled
+ ),
+ $services_warning_handled
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_warning_passive): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is passively checked',
+ '%u are passively checked',
+ $services_warning_passive
+ ),
+ $services_warning_passive
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 1,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 1
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state WARNING and passively checked',
+ 'List %u services which are currently in state WARNING and passively checked',
+ $services_warning_passive
+ ),
+ $services_warning_passive
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_warning_not_checked): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is not checked at all',
+ '%u are not checked at all',
+ $services_warning_not_checked
+ ),
+ $services_warning_not_checked
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 1,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state WARNING and not checked at all',
+ 'List %u services which are currently in state WARNING and not checked at all',
+ $services_warning_not_checked
+ ),
+ $services_warning_not_checked
+ ))
+ ); ?>
+<?php endif ?>
+</div>
+<?php endif ?>
+<?php if ($services_unknown_handled || $services_unknown_unhandled): ?>
+<div class="box badge entry state-<?= Service::getStateText(3); ?> <?= $services_unknown_unhandled ? '' : 'handled'; ?>">
+<?php if ($services_unknown_unhandled): ?>
+ <?= $this->qlink(
+ $services_unknown_unhandled . ' ' . Service::getStateText(3, true),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 3,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state UNKNOWN',
+ 'List %u services which are currently in state UNKNOWN',
+ $services_unknown_unhandled
+ ),
+ $services_unknown_unhandled
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_unknown_handled): ?>
+ <?= $this->qlink(
+ $services_unknown_handled . ' ' . (
+ $services_unknown_unhandled ? $this->translate('Handled') : Service::getStateText(3, true)
+ ),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 3,
+ 'service_handled' => 1
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state UNKNOWN (Handled)',
+ 'List %u services which are currently in state UNKNOWN (Handled)',
+ $services_unknown_handled
+ ),
+ $services_unknown_handled
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_unknown_passive): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is passively checked',
+ '%u are passively checked',
+ $services_unknown_passive
+ ),
+ $services_unknown_passive
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 3,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 1
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state UNKNOWN and passively checked',
+ 'List %u services which are currently in state UNKNOWN and passively checked',
+ $services_unknown_passive
+ ),
+ $services_unknown_passive
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_unknown_not_checked): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is not checked at all',
+ '%u are not checked at all',
+ $services_unknown_not_checked
+ ),
+ $services_unknown_not_checked
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 3,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state UNKNOWN and not checked at all',
+ 'List %u services which are currently in state UNKNOWN and not checked at all',
+ $services_unknown_not_checked
+ ),
+ $services_unknown_not_checked
+ ))
+ ); ?>
+<?php endif ?>
+</div>
+<?php endif ?>
+<?php if ($services_ok): ?>
+<div class="box badge entry state-<?= Service::getStateText(0); ?>">
+ <?= $this->qlink(
+ $services_ok . ' ' . Service::getStateText(0, true),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state OK',
+ 'List %u services which are currently in state OK',
+ $services_ok
+ ),
+ $services_ok
+ ))
+ ); ?>
+<?php if ($services_ok_not_checked): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is not checked at all',
+ '%u are not checked at all',
+ $services_ok_not_checked
+ ),
+ $services_ok_not_checked
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 0,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state OK and not checked at all',
+ 'List %u services which are currently in state OK and not checked at all',
+ $services_ok_not_checked
+ ),
+ $services_ok_not_checked
+ ))
+ ); ?>
+<?php endif ?>
+</div>
+<?php endif ?>
+<?php if ($services_pending): ?>
+<div class="box badge entry state-<?= Service::getStateText(99); ?>">
+ <?= $this->qlink(
+ $services_pending . ' ' . Service::getStateText(99, true),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 99
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state PENDING',
+ 'List %u services which are currently in state PENDING',
+ $services_pending
+ ),
+ $services_pending
+ ))
+ ); ?>
+<?php if ($services_pending_not_checked): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is not checked at all',
+ '%u are not checked at all',
+ $services_pending_not_checked
+ ),
+ $services_pending_not_checked
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 99,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state PENDING and not checked at all',
+ 'List %u services which are currently in state PENDING and not checked at all',
+ $services_pending_not_checked
+ ),
+ $services_pending_not_checked
+ ))
+ ); ?>
+<?php endif ?>
+</div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml b/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml
new file mode 100644
index 0000000..6374ff8
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml
@@ -0,0 +1,74 @@
+<div class="box problem_hosts <?php
+ echo $this->statusSummary->hosts_down ? 'state_down' : 'state_unreachable';
+ if (!$this->statusSummary->hosts_down_unhandled && !$this->statusSummary->hosts_unreachable_unhandled) {
+ echo ' handled';
+ }
+?> col-1-2">
+ <div class="box header">
+ <?php if ($this->statusSummary->hosts_down): ?>
+ <h2><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host DOWN', '%u Hosts DOWN', $this->statusSummary->hosts_down),
+ $this->statusSummary->hosts_down
+ ),
+ 'monitoring/list/hosts',
+ array('host_state' => 1),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state DOWN',
+ 'List %u hosts which are currently in state DOWN',
+ $this->statusSummary->hosts_down
+ ),
+ $this->statusSummary->hosts_down
+ ))
+ ); ?></h2>
+ <?php endif ?>
+ <?php if ($this->statusSummary->hosts_unreachable): ?>
+ <h2><?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u Host UNREACHABLE',
+ '%u Hosts UNREACHABLE',
+ $this->statusSummary->hosts_unreachable
+ ),
+ $this->statusSummary->hosts_unreachable
+ ),
+ 'monitoring/list/hosts',
+ array('host_state' => 2),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state UNREACHABLE',
+ 'List %u hosts which are currently in state UNREACHABLE',
+ $this->statusSummary->hosts_unreachable
+ ),
+ $this->statusSummary->hosts_unreachable
+ ))
+ ); ?></h2>
+ <?php endif ?>
+ </div>
+ <div class="box contents">
+ <?= $this->partial(
+ 'tactical/components/parts/servicestatesummarybyhoststate.phtml',
+ array(
+ 'translationDomain' => $this->translationDomain,
+ 'host_problem' => 1,
+ 'services_ok' => $this->statusSummary->services_ok_on_problem_hosts,
+ 'services_ok_not_checked' => $this->statusSummary->services_ok_not_checked_on_problem_hosts,
+ 'services_pending' => $this->statusSummary->services_pending_on_problem_hosts,
+ 'services_pending_not_checked' => $this->statusSummary->services_pending_not_checked_on_problem_hosts,
+ 'services_warning_handled' => $this->statusSummary->services_warning_handled_on_problem_hosts,
+ 'services_warning_unhandled' => $this->statusSummary->services_warning_unhandled_on_problem_hosts,
+ 'services_warning_passive' => $this->statusSummary->services_warning_passive_on_problem_hosts,
+ 'services_warning_not_checked' => $this->statusSummary->services_warning_not_checked_on_problem_hosts,
+ 'services_critical_handled' => $this->statusSummary->services_critical_handled_on_problem_hosts,
+ 'services_critical_unhandled' => $this->statusSummary->services_critical_unhandled_on_problem_hosts,
+ 'services_critical_passive' => $this->statusSummary->services_critical_passive_on_problem_hosts,
+ 'services_critical_not_checked' => $this->statusSummary->services_critical_not_checked_on_problem_hosts,
+ 'services_unknown_handled' => $this->statusSummary->services_unknown_handled_on_problem_hosts,
+ 'services_unknown_unhandled' => $this->statusSummary->services_unknown_unhandled_on_problem_hosts,
+ 'services_unknown_passive' => $this->statusSummary->services_unknown_passive_on_problem_hosts,
+ 'services_unknown_not_checked' => $this->statusSummary->services_unknown_not_checked_on_problem_hosts
+ )
+ ); ?>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/tactical/index.phtml b/modules/monitoring/application/views/scripts/tactical/index.phtml
new file mode 100644
index 0000000..12f4bc5
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/tactical/index.phtml
@@ -0,0 +1,145 @@
+<?php
+use Icinga\Data\Filter\Filter;
+?>
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content tactical grid">
+<?php if (! count(array_filter((array) $statusSummary))): ?>
+ <p><?= $this->translate('No results found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <div class="boxview" data-base-target="_next">
+ <div class="donut-container">
+ <h2 aria-label="<?= $this->translate('Host Summary') ?>"><?= $this->translate('Host Summary') ?></h2>
+ <div class="donut">
+ <?= $hostStatusSummaryChart ?>
+ </div>
+ <ul class="donut-legend">
+ <?php if ($statusSummary->hosts_up): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 0, 'sort' => 'host_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-ok badge"><?= $statusSummary->hosts_up ?></span><?= $this->translate('Up') ?>
+ </a>
+ </li>
+ <?php endif ?>
+ <?php if ($statusSummary->hosts_down_handled): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 1, 'host_handled' => 1, 'sort' => 'host_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-critical handled badge"><?= $statusSummary->hosts_down_handled ?></span><?= $this->translate('Down') ?> (<?= $this->translate('Handled') ?>)
+ </a>
+ </li>
+ <?php endif ?>
+ <?php if ($statusSummary->hosts_down_unhandled): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 1, 'host_handled' => 0, 'sort' => 'host_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-critical badge"><?= $statusSummary->hosts_down_unhandled ?></span><?= $this->translate('Down') ?>
+ </a>
+ </li>
+ <?php endif ?>
+ <?php if ($statusSummary->hosts_unreachable_handled): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 2, 'host_handled' => 1, 'sort' => 'host_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-unreachable handled badge"><?= $statusSummary->hosts_unreachable_handled ?></span><?= $this->translate('Unreachable') ?> (<?= $this->translate('Handled') ?>)
+ </a>
+ </li>
+ <?php endif ?>
+ <?php if ($statusSummary->hosts_unreachable_unhandled): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 2, 'host_handled' => 0, 'sort' => 'host_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-unreachable badge"><?= $statusSummary->hosts_unreachable_unhandled ?></span><?= $this->translate('Unreachable') ?>
+ </a>
+ </li>
+ <?php endif ?>
+ <?php if ($statusSummary->hosts_pending): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 99, 'sort' => 'host_last_check', 'dir' => 'asc'))->addFilter(Filter::not(Filter::where('host_active_checks_enabled', 0), Filter::where('host_passive_checks_enabled', 0))) ?>">
+ <span class="state state-pending badge"><?= $statusSummary->hosts_pending ?></span><?= $this->translate('Pending') ?>
+ </a>
+ </li>
+ <?php endif ?>
+ <?php if ($statusSummary->hosts_pending_not_checked): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 99, 'host_active_checks_enabled' => 0, 'host_passive_checks_enabled' => 0, 'sort' => 'host_last_check', 'dir' => 'asc')) ?>">
+ <span class="state slice-state-not-checked badge"><?= $statusSummary->hosts_pending_not_checked ?></span><?= $this->translate('Not Checked') ?>
+ </a>
+ </li>
+ <?php endif ?>
+ </ul>
+ </div>
+ <div class="donut-container">
+ <h2 aria-label="<?= $this->translate('Service Summary') ?>"><?= $this->translate('Service Summary') ?></h2>
+ <div class="donut">
+ <?= $serviceStatusSummaryChart ?>
+ </div>
+ <ul class="donut-legend">
+ <?php if ($statusSummary->services_ok):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 0, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-ok badge"><?= $statusSummary->services_ok ?></span><?= $this->translate('Ok') ?>
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_warning_handled):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 1, 'service_handled' => 1, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-warning handled badge"><?= $statusSummary->services_warning_handled ?></span><?= $this->translate('Warning') ?> (<?= $this->translate('Handled') ?>)
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_warning_unhandled):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 1, 'service_handled' => 0, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-warning badge"><?= $statusSummary->services_warning_unhandled ?></span><?= $this->translate('Warning') ?>
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_critical_handled):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 2, 'service_handled' => 1, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-critical handled badge"><?= $statusSummary->services_critical_handled ?></span><?= $this->translate('Critical') ?> (<?= $this->translate('Handled') ?>)
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_critical_unhandled):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 2, 'service_handled' => 0, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-critical badge"><?= $statusSummary->services_critical_unhandled ?></span><?= $this->translate('Critical') ?>
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_unknown_handled):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 3, 'service_handled' => 1, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-unknown handled badge"><?= $statusSummary->services_unknown_handled ?></span><?= $this->translate('Unknown') ?> (<?= $this->translate('Handled') ?>)
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_unknown_unhandled):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 3, 'service_handled' => 0, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-unknown badge"><?= $statusSummary->services_unknown_unhandled ?></span><?= $this->translate('Unknown') ?>
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_pending):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 99, 'sort' => 'service_last_check', 'dir' => 'asc'))->addFilter(Filter::not(Filter::where('service_active_checks_enabled', 0), Filter::where('service_passive_checks_enabled', 0))) ?>">
+ <span class="state state-pending badge"><?= $statusSummary->services_pending ?></span><?= $this->translate('Pending') ?>
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_pending_not_checked):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 99, 'service_active_checks_enabled' => 0, 'service_passive_checks_enabled' => 0, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state slice-state-not-checked badge"><?= $statusSummary->services_pending_not_checked ?></span><?= $this->translate('Not Checked') ?>
+ </a>
+ </li>
+ <?php endif?>
+ </ul>
+ </div>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/timeline/index.phtml b/modules/monitoring/application/views/scripts/timeline/index.phtml
new file mode 100644
index 0000000..af3b406
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/timeline/index.phtml
@@ -0,0 +1,145 @@
+<?php
+use Icinga\Web\Url;
+use Icinga\Util\Color;
+
+$groupInfo = $timeline->getGroupInfo();
+$firstRow = ! $beingExtended;
+
+if (! $beingExtended && !$this->compact): ?>
+<div class="controls">
+ <?= $this->tabs; ?>
+ <div class="dontprint">
+ <?= $intervalBox; ?>
+ </div>
+ <div class="timeline-legend">
+ <h2><?= $this->translate('Legend'); ?></h2>
+<?php foreach ($groupInfo as $labelAndClass): ?>
+ <span class="<?= $labelAndClass['class'] ?>">
+ <span><?= $labelAndClass['label']; ?></span>
+ </span>
+<?php endforeach ?>
+ </div>
+</div>
+<?php endif ?>
+<?php if (! $beingExtended): ?>
+<div class="content" data-base-target="_next">
+ <div class="timeline">
+<?php endif ?>
+<?php if ($switchedContext): ?>
+ <hr>
+<?php endif ?>
+<?php foreach ($timeline as $timeInfo):
+ switch ($intervalBox->getInterval()) {
+ case '1d':
+ $titleTime = sprintf(
+ $this->translate('on %s', 'timeline.link.title.time'),
+ $timeInfo[0]->end->format('d/m/Y')
+ );
+ break;
+ case '1w':
+ $titleTime = sprintf(
+ $this->translate('in week %s of %s', 'timeline.link.title.week.and.year'),
+ $timeInfo[0]->end->format('W'),
+ $timeInfo[0]->end->format('Y')
+ );
+ break;
+ case '1m':
+ $titleTime = sprintf(
+ $this->translate('in %s', 'timeline.link.title.month.and.year'),
+ $timeInfo[0]->end->format('F Y')
+ );
+ break;
+ case '1y':
+ $titleTime = sprintf(
+ $this->translate('in %s', 'timeline.link.title.year'),
+ $timeInfo[0]->end->format('Y')
+ );
+ break;
+ default:
+ $titleTime = sprintf(
+ $this->translate('between %s and %s', 'timeline.link.title.datetime.twice'),
+ $timeInfo[0]->end->format('d/m/Y g:i A'),
+ $timeInfo[0]->start->format('d/m/Y g:i A')
+ );
+ } ?>
+ <div class="timeframe">
+ <span><?= $this->qlink(
+ $timeInfo[0]->end->format($intervalFormat),
+ 'monitoring/list/eventhistory',
+ array(
+ 'timestamp<' => $timeInfo[0]->start->getTimestamp(),
+ 'timestamp>' => $timeInfo[0]->end->getTimestamp()
+ ),
+ array('title' => sprintf(
+ $this->translate('List all event records registered %s', 'timeline.link.title'),
+ $titleTime
+ )),
+ false
+ ); ?></span>
+<?php foreach ($groupInfo as $groupName => $labelAndColor): ?>
+<?php if (array_key_exists($groupName, $timeInfo[1])): ?>
+<?php
+$circleWidth = $timeline->calculateCircleWidth($timeInfo[1][$groupName], 2);
+$extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$groupName], 2);
+?>
+<?php if ($firstRow && $extrapolatedCircleWidth !== $circleWidth): ?>
+ <div class="circle-box" style="width: <?= $extrapolatedCircleWidth; ?>;">
+ <div class="outer-circle extrapolated <?= $timeInfo[1][$groupName]->getClass() ?>" style="<?= sprintf(
+ 'width: %2$s; height: %2$s; margin-top: -%1$Fem;',
+ (float) substr($extrapolatedCircleWidth, 0, -2) / 2,
+ $extrapolatedCircleWidth
+ ); ?>">
+<?php else: ?>
+ <div class="circle-box" style="width: <?= $circleWidth; ?>;">
+ <div class="outer-circle" style="<?= sprintf(
+ 'width: %2$s; height: %2$s; margin-top: -%1$Fem;',
+ (float) substr($circleWidth, 0, -2) / 2,
+ $circleWidth
+ ); ?>">
+<?php endif ?>
+ <?= $this->qlink(
+ '',
+ $timeInfo[1][$groupName]->getDetailUrl(),
+ array(
+ 'type' => $groupName,
+ 'timestamp<' => $timeInfo[0]->start->getTimestamp(),
+ 'timestamp>' => $timeInfo[0]->end->getTimestamp()
+ ),
+ array(
+ 'title' => sprintf(
+ $this->translate('List %u %s registered %s', 'timeline.link.title'),
+ $timeInfo[1][$groupName]->getValue(),
+ strtolower($labelAndColor['label']),
+ $titleTime
+ ),
+ 'class' => 'inner-circle ' . $timeInfo[1][$groupName]->getClass(),
+ 'style' => sprintf(
+ 'width: %2$s; height: %2$s; margin-top: -%1$Fem; margin-left: -%1$Fem;',
+ (float) substr($circleWidth, 0, -2) / 2,
+ (string) $circleWidth
+ )
+ )
+ ); ?>
+ </div>
+ </div>
+<?php endif ?>
+<?php endforeach ?>
+ </div>
+ <?php $firstRow = false; ?>
+<?php endforeach ?>
+ <a aria-hidden="true" id="end" href="<?= Url::fromRequest()->remove(
+ array(
+ 'timestamp<',
+ 'timestamp>'
+ )
+ )->overwriteParams(
+ array(
+ 'start' => $nextRange->getStart()->getTimestamp(),
+ 'end' => $nextRange->getEnd()->getTimestamp(),
+ 'extend' => 1
+ )
+ ); ?>"></a>
+<?php if (!$beingExtended): ?>
+ </div>
+</div>
+<?php endif ?>