mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-28 16:24:04 +02:00
Monitoring/register: Add status summary to the top bar
This commit is contained in:
parent
278f975c48
commit
36e9cdb549
@ -1,26 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
use Icinga\Web\Topbar;
|
||||||
|
?>
|
||||||
<nav class="navbar navbar-default navbar-fixed-top" id="icingatopbar" role="navigation">
|
<nav class="navbar navbar-default navbar-fixed-top" id="icingatopbar" role="navigation">
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
<a href="<?= $this->baseUrl('/') ?>" class="navbar-brand icinga-logo">Icinga</a>
|
<a href="<?= $this->baseUrl('/') ?>" class="navbar-brand icinga-logo">Icinga</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav navbar-nav icinga-navbar-hosts-container">
|
<?php foreach (Topbar::getPartials() as $partial) {
|
||||||
<span class="icinga-navbar-pills icinga-navbar-pills-up">999/999</span>
|
echo $this->partial($partial['viewScriptName'], $partial['moduleName'], $partial['data']);
|
||||||
<span class="icinga-navbar-pills icinga-navbar-pills-unreachable">999/999</span>
|
} ?>
|
||||||
<span class="icinga-navbar-pills icinga-navbar-pills-down">999/999</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nav navbar-nav icinga-navbar-services-container">
|
|
||||||
<span class="icinga-navbar-pills icinga-navbar-pills-ok">999/999</span>
|
|
||||||
<span class="icinga-navbar-pills icinga-navbar-pills-critical">999/999</span>
|
|
||||||
<span class="icinga-navbar-pills icinga-navbar-pills-warning">999/999</span>
|
|
||||||
<span class="icinga-navbar-pills icinga-navbar-pills-unknown">999/999</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nav navbar-nav icinga-navbar-reload">
|
|
||||||
<a class="button btn-common btn-small" href="#">
|
|
||||||
<i class="icinga-icon-refresh"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form class="navbar-form navbar-left">
|
<form class="navbar-form navbar-left">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
namespace Icinga\Application\Modules;
|
namespace Icinga\Application\Modules;
|
||||||
|
|
||||||
|
use \Exception;
|
||||||
use \Icinga\Application\ApplicationBootstrap;
|
use \Icinga\Application\ApplicationBootstrap;
|
||||||
use \Icinga\Application\Config;
|
use \Icinga\Application\Config;
|
||||||
use \Icinga\Application\Icinga;
|
use \Icinga\Application\Icinga;
|
||||||
@ -133,8 +134,12 @@ class Module
|
|||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
$this->registerAutoloader()
|
$this->registerAutoloader()
|
||||||
->registerWebIntegration()
|
->registerWebIntegration();
|
||||||
->runRegisterScript();
|
try {
|
||||||
|
$this->runRegisterScript();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
<?php
|
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
|
||||||
/**
|
|
||||||
* This file is part of Icinga 2 Web.
|
|
||||||
*
|
|
||||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
|
||||||
* Copyright (C) 2013 Icinga Development Team
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
|
||||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
|
||||||
* @author Icinga Development Team <info@icinga.org>
|
|
||||||
*/
|
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
|
||||||
|
|
||||||
namespace Icinga\Web\Hook;
|
|
||||||
|
|
||||||
use Icinga\Application\Logger as Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class Toptray
|
|
||||||
* @package Icinga\Web\Hook
|
|
||||||
*/
|
|
||||||
abstract class Toptray
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
const ALIGN_LEFT = "pull-left";
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
const ALIGN_NONE = "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
const ALIGN_RIGHT = "pull-right";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $align = self::ALIGN_NONE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $align
|
|
||||||
*/
|
|
||||||
public function setAlignment($align)
|
|
||||||
{
|
|
||||||
$this->align = $align;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
final public function getWidgetDOM()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return '<ul class="nav ' . $this->align . '" >' . $this->buildDOM() . '</ul>';
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Logger::error("Could not create tray widget : %s", $e->getMessage());
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
abstract protected function buildDOM();
|
|
||||||
}
|
|
22
library/Icinga/Web/Topbar.php
Normal file
22
library/Icinga/Web/Topbar.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Icinga\Web;
|
||||||
|
|
||||||
|
class Topbar
|
||||||
|
{
|
||||||
|
private static $partials = array();
|
||||||
|
|
||||||
|
public static function addPartial($viewScriptName, $moduleName, $data)
|
||||||
|
{
|
||||||
|
self::$partials[] = array(
|
||||||
|
'viewScriptName' => $viewScriptName,
|
||||||
|
'moduleName' => $moduleName,
|
||||||
|
'data' => $data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPartials()
|
||||||
|
{
|
||||||
|
return self::$partials;
|
||||||
|
}
|
||||||
|
}
|
65
modules/monitoring/application/views/scripts/topbar.phtml
Normal file
65
modules/monitoring/application/views/scripts/topbar.phtml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<div class="nav navbar-nav topbar-host-status-summary">
|
||||||
|
<span class="topbar-status-up">
|
||||||
|
<a href="<?= $this->href('monitoring/list/hosts', array('host_severity' => 4)); ?>" title="Hosts Up">
|
||||||
|
<?= $hostSummary->hosts_up ?>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span class="topbar-status-down">
|
||||||
|
<a href="<?= $this->href('monitoring/list/hosts', array('host_severity' => 36)); ?>" title="Hosts Down Unhandled">
|
||||||
|
<?= $hostSummary->hosts_down_unhandled ?>
|
||||||
|
</a>/
|
||||||
|
<a href="<?= $this->href('monitoring/list/hosts', array('host_severity' => array(33, 34))); ?>" title="Hosts Down Handled">
|
||||||
|
<?= $hostSummary->hosts_down_handled ?>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span class="topbar-status-unreachable">
|
||||||
|
<a href="<?= $this->href('monitoring/list/hosts', array('host_severity' => 68)); ?>" title="Hosts Unreachable Unhandled">
|
||||||
|
<?= $hostSummary->hosts_unreachable_unhandled ?>
|
||||||
|
</a>/
|
||||||
|
<a href="<?= $this->href('monitoring/list/hosts', array('host_severity' => array(65, 66))); ?>" title="Hosts Unreachable Handled">
|
||||||
|
<?= $hostSummary->hosts_unreachable_handled ?>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span class="topbar-status-pending">
|
||||||
|
<a href="<?= $this->href('monitoring/list/hosts', array('host_severity' => 20)); ?>" title="Hosts Pending">
|
||||||
|
<?= $hostSummary->hosts_pending ?>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav navbar-nav topbar-service-status-summary">
|
||||||
|
<span class="topbar-status-ok">
|
||||||
|
<a href="<?= $this->href('monitoring/list/services', array('service_severity' => 4)); ?>" title="Services Ok">
|
||||||
|
<?= $serviceSummary->services_ok ?>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span class="topbar-status-critical">
|
||||||
|
<a href="<?= $this->href('monitoring/list/services', array('service_severity' => 132)); ?>" title="Services Critical Unhandled">
|
||||||
|
<?= $serviceSummary->services_critical_unhandled ?>
|
||||||
|
</a>/
|
||||||
|
<a href="<?= $this->href('monitoring/list/services', array('service_severity' => array(129, 130))); ?>" title="Services Critical Handled">
|
||||||
|
<?= $serviceSummary->services_critical_handled ?>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span class="topbar-status-warning">
|
||||||
|
<a href="<?= $this->href('monitoring/list/services', array('service_severity' => 36)); ?>" title="Services Warning Unhandled">
|
||||||
|
<?= $serviceSummary->services_warning_unhandled ?>
|
||||||
|
</a>/
|
||||||
|
<a href="<?= $this->href('monitoring/list/services', array('service_severity' => array(33, 34))); ?>" title="Services Warning Handled">
|
||||||
|
<?= $serviceSummary->services_warning_handled ?>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span class="topbar-status-unknown">
|
||||||
|
<a href="<?= $this->href('monitoring/list/services', array('service_severity' => 68)); ?>" title="Services Unknown Unhandled">
|
||||||
|
<?= $serviceSummary->services_unknown_unhandled ?>
|
||||||
|
</a>/
|
||||||
|
<a href="<?= $this->href('monitoring/list/services', array('service_severity' => array(65, 66))); ?>" title="Services Unknown Handled">
|
||||||
|
<?= $serviceSummary->services_unknown_handled ?>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span class="topbar-status-pending">
|
||||||
|
<a href="<?= $this->href('monitoring/list/services', array('service_severity' => 20)); ?>" title="Services Pending">
|
||||||
|
<?= $serviceSummary->services_pending ?>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
@ -41,6 +41,7 @@ class DowntimeQuery extends IdoQuery
|
|||||||
'downtime_entry_time' => 'UNIX_TIMESTAMP(sd.entry_time)',
|
'downtime_entry_time' => 'UNIX_TIMESTAMP(sd.entry_time)',
|
||||||
'downtime_is_fixed' => 'sd.is_fixed',
|
'downtime_is_fixed' => 'sd.is_fixed',
|
||||||
'downtime_is_flexible' => 'CASE WHEN sd.is_fixed = 0 THEN 1 ELSE 0 END',
|
'downtime_is_flexible' => 'CASE WHEN sd.is_fixed = 0 THEN 1 ELSE 0 END',
|
||||||
|
'downtime_scheduled_start_time' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)',
|
||||||
'downtime_start' => "UNIX_TIMESTAMP(CASE WHEN sd.trigger_time != '0000-00-00 00:00:00' then sd.trigger_time ELSE sd.scheduled_start_time END)",
|
'downtime_start' => "UNIX_TIMESTAMP(CASE WHEN sd.trigger_time != '0000-00-00 00:00:00' then sd.trigger_time ELSE sd.scheduled_start_time END)",
|
||||||
'downtime_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)',
|
'downtime_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)',
|
||||||
'downtime_duration' => 'sd.duration',
|
'downtime_duration' => 'sd.duration',
|
||||||
@ -50,7 +51,10 @@ class DowntimeQuery extends IdoQuery
|
|||||||
),
|
),
|
||||||
'objects' => array(
|
'objects' => array(
|
||||||
'host' => 'o.name1 COLLATE latin1_general_ci',
|
'host' => 'o.name1 COLLATE latin1_general_ci',
|
||||||
'service' => 'o.name2 COLLATE latin1_general_ci'
|
'host_name' => 'o.name1 COLLATE latin1_general_ci',
|
||||||
|
'service' => 'o.name2 COLLATE latin1_general_ci',
|
||||||
|
'service_description' => 'o.name2 COLLATE latin1_general_ci'
|
||||||
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -0,0 +1,347 @@
|
|||||||
|
<?php
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
/**
|
||||||
|
* This file is part of Icinga 2 Web.
|
||||||
|
*
|
||||||
|
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||||
|
* Copyright (C) 2013 Icinga Development Team
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||||
|
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||||
|
* @author Icinga Development Team <info@icinga.org>
|
||||||
|
*/
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||||
|
|
||||||
|
class HoststatusQuery extends IdoQuery
|
||||||
|
{
|
||||||
|
protected $allowCustomVars = true;
|
||||||
|
protected $columnMap = array(
|
||||||
|
'hosts' => array(
|
||||||
|
'host' => 'ho.name1 COLLATE latin1_general_ci',
|
||||||
|
'host_name' => 'ho.name1 COLLATE latin1_general_ci',
|
||||||
|
'host_display_name' => 'h.display_name',
|
||||||
|
'host_alias' => 'h.alias',
|
||||||
|
'host_address' => 'h.address',
|
||||||
|
'host_ipv4' => 'INET_ATON(h.address)',
|
||||||
|
'host_icon_image' => 'h.icon_image',
|
||||||
|
'hosts_up' => 'SUM(CASE WHEN hs.has_been_checked != 0 AND hs.has_been_checked IS NOT NULL AND hs.current_state = 0 THEN 1 ELSE 0 END)',
|
||||||
|
'hosts_unreachable_handled' => 'SUM(CASE WHEN hs.has_been_checked != 0 AND hs.has_been_checked IS NOT NULL AND hs.current_state = 2 AND hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth != 0 THEN 1 ELSE 0 END)',
|
||||||
|
'hosts_unreachable_unhandled' => 'SUM(CASE WHEN hs.has_been_checked != 0 AND hs.has_been_checked IS NOT NULL AND hs.current_state = 2 AND hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth = 0 THEN 1 ELSE 0 END)',
|
||||||
|
'hosts_down_handled' => 'SUM(CASE WHEN hs.has_been_checked != 0 AND hs.has_been_checked IS NOT NULL AND hs.current_state = 1 AND hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth != 0 THEN 1 ELSE 0 END)',
|
||||||
|
'hosts_down_unhandled' => 'SUM(CASE WHEN hs.has_been_checked != 0 AND hs.has_been_checked IS NOT NULL AND hs.current_state = 1 AND hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth = 0 THEN 1 ELSE 0 END)',
|
||||||
|
'hosts_pending' => 'SUM(CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 1 ELSE 0 END)'
|
||||||
|
),
|
||||||
|
'hoststatus' => array(
|
||||||
|
'problems' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END',
|
||||||
|
'handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END',
|
||||||
|
'unhandled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) = 0 THEN 1 ELSE 0 END',
|
||||||
|
'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END',
|
||||||
|
'host_output' => 'hs.output',
|
||||||
|
'host_long_output' => 'hs.long_output',
|
||||||
|
'host_perfdata' => 'hs.perfdata',
|
||||||
|
'host_problem' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END',
|
||||||
|
'host_acknowledged' => 'hs.problem_has_been_acknowledged',
|
||||||
|
'host_in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
|
||||||
|
'host_handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END',
|
||||||
|
'host_does_active_checks' => 'hs.active_checks_enabled',
|
||||||
|
'host_accepts_passive_checks' => 'hs.passive_checks_enabled',
|
||||||
|
'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)',
|
||||||
|
'host_last_hard_state' => 'hs.last_hard_state',
|
||||||
|
'host_check_command' => 'hs.check_command',
|
||||||
|
'host_last_check' => 'UNIX_TIMESTAMP(hs.last_check)',
|
||||||
|
'host_next_check' => 'CASE WHEN hs.should_be_scheduled THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END',
|
||||||
|
'host_check_execution_time' => 'hs.execution_time',
|
||||||
|
'host_check_latency' => 'hs.latency',
|
||||||
|
'host_notifications_enabled' => 'hs.notifications_enabled',
|
||||||
|
'host_last_time_up' => 'hs.last_time_up',
|
||||||
|
'host_last_time_down' => 'hs.last_time_down',
|
||||||
|
'host_last_time_unreachable' => 'hs.last_time_unreachable',
|
||||||
|
'host_current_check_attempt' => 'hs.current_check_attempt',
|
||||||
|
'host_max_check_attempts' => 'hs.max_check_attempts',
|
||||||
|
'host_severity' => 'CASE WHEN hs.current_state = 0
|
||||||
|
THEN
|
||||||
|
CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL
|
||||||
|
THEN 16
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
+
|
||||||
|
CASE WHEN hs.problem_has_been_acknowledged = 1
|
||||||
|
THEN 2
|
||||||
|
ELSE
|
||||||
|
CASE WHEN hs.scheduled_downtime_depth > 0
|
||||||
|
THEN 1
|
||||||
|
ELSE 4
|
||||||
|
END
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 16
|
||||||
|
WHEN hs.current_state = 1 THEN 32
|
||||||
|
WHEN hs.current_state = 2 THEN 64
|
||||||
|
ELSE 256
|
||||||
|
END
|
||||||
|
+
|
||||||
|
CASE WHEN hs.problem_has_been_acknowledged = 1
|
||||||
|
THEN 2
|
||||||
|
ELSE
|
||||||
|
CASE WHEN hs.scheduled_downtime_depth > 0
|
||||||
|
THEN 1
|
||||||
|
ELSE 4
|
||||||
|
END
|
||||||
|
END
|
||||||
|
END',
|
||||||
|
),
|
||||||
|
'hostgroups' => array(
|
||||||
|
'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
|
||||||
|
),
|
||||||
|
'contactgroups' => array(
|
||||||
|
'contactgroup' => 'contactgroup',
|
||||||
|
),
|
||||||
|
'contacts' => array(
|
||||||
|
'contact' => 'hco.name1 COLLATE latin1_general_ci',
|
||||||
|
),
|
||||||
|
'services' => array(
|
||||||
|
'services_cnt' => 'SUM(1)',
|
||||||
|
'services_ok' => 'SUM(CASE WHEN ss.current_state = 0 THEN 1 ELSE 0 END)',
|
||||||
|
'services_warning' => 'SUM(CASE WHEN ss.current_state = 1 THEN 1 ELSE 0 END)',
|
||||||
|
'services_critical' => 'SUM(CASE WHEN ss.current_state = 2 THEN 1 ELSE 0 END)',
|
||||||
|
'services_unknown' => 'SUM(CASE WHEN ss.current_state = 3 THEN 1 ELSE 0 END)',
|
||||||
|
'services_pending' => 'SUM(CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 1 ELSE 0 END)',
|
||||||
|
'services_problem' => 'SUM(CASE WHEN ss.current_state > 0 THEN 1 ELSE 0 END)',
|
||||||
|
'services_problem_handled' => 'SUM(CASE WHEN ss.current_state > 0 AND (ss.problem_has_been_acknowledged = 1 OR ss.scheduled_downtime_depth > 0) THEN 1 ELSE 0 END)',
|
||||||
|
'services_problem_unhandled' => 'SUM(CASE WHEN ss.current_state > 0 AND (ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0) THEN 1 ELSE 0 END)',
|
||||||
|
'services_warning_handled' => 'SUM(CASE WHEN ss.current_state = 1 AND (ss.problem_has_been_acknowledged = 1 OR ss.scheduled_downtime_depth > 0) THEN 1 ELSE 0 END)',
|
||||||
|
'services_critical_handled' => 'SUM(CASE WHEN ss.current_state = 2 AND (ss.problem_has_been_acknowledged = 1 OR ss.scheduled_downtime_depth > 0) THEN 1 ELSE 0 END)',
|
||||||
|
'services_unknown_handled' => 'SUM(CASE WHEN ss.current_state = 3 AND (ss.problem_has_been_acknowledged = 1 OR ss.scheduled_downtime_depth > 0) THEN 1 ELSE 0 END)',
|
||||||
|
'services_warning_unhandled' => 'SUM(CASE WHEN ss.current_state = 1 AND (ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0) THEN 1 ELSE 0 END)',
|
||||||
|
'services_critical_unhandled' => 'SUM(CASE WHEN ss.current_state = 2 AND (ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0) THEN 1 ELSE 0 END)',
|
||||||
|
'services_unknown_unhandled' => 'SUM(CASE WHEN ss.current_state = 3 AND (ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0) THEN 1 ELSE 0 END)',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
protected $aggregateColumnIdx = array(
|
||||||
|
'services_cnt' => true,
|
||||||
|
'services_problem' => true,
|
||||||
|
'services_problem_handled' => true,
|
||||||
|
'services_problem_unhandled' => true,
|
||||||
|
);
|
||||||
|
protected $hcgSub;
|
||||||
|
|
||||||
|
public function getDefaultColumns()
|
||||||
|
{
|
||||||
|
return $this->columnMap['hosts'] + $this->columnMap['hoststatus'];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function joinBaseTables()
|
||||||
|
{
|
||||||
|
// TODO: Shall we always add hostobject?
|
||||||
|
$this->baseQuery = $this->db->select()->from(
|
||||||
|
array('ho' => $this->prefix . 'objects'),
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('hs' => $this->prefix . 'hoststatus'),
|
||||||
|
'ho.' . $this->object_id . ' = hs.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('h' => $this->prefix . 'hosts'),
|
||||||
|
'hs.host_object_id = h.host_object_id',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
$this->joinedVirtualTables = array(
|
||||||
|
'hosts' => true,
|
||||||
|
'hoststatus' => true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function joinStatus()
|
||||||
|
{
|
||||||
|
$this->requireVirtualTable('services');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function joinServiceStatus()
|
||||||
|
{
|
||||||
|
$this->requireVirtualTable('services');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function joinServices()
|
||||||
|
{
|
||||||
|
$this->baseQuery->join(
|
||||||
|
array('s' => $this->prefix . 'services'),
|
||||||
|
's.host_object_id = h.host_object_id',
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('so' => $this->prefix . 'objects'),
|
||||||
|
"so.$this->object_id = s.service_object_id AND so.is_active = 1",
|
||||||
|
array()
|
||||||
|
)->joinLeft(
|
||||||
|
array('ss' => $this->prefix . 'servicestatus'),
|
||||||
|
"so.$this->object_id = ss.service_object_id",
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
foreach ($this->getColumns() as $col) {
|
||||||
|
$real = $this->aliasToColumnName($col);
|
||||||
|
if (substr($real, 0, 4) === 'SUM(') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->baseQuery->group($real);
|
||||||
|
}
|
||||||
|
$this->useSubqueryCount = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function joinHostgroups()
|
||||||
|
{
|
||||||
|
if ($this->hasJoinedVirtualTable('services')) {
|
||||||
|
return $this->joinServiceHostgroups();
|
||||||
|
} else {
|
||||||
|
return $this->joinHostHostgroups();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function joinServiceHostgroups()
|
||||||
|
{
|
||||||
|
$this->baseQuery->join(
|
||||||
|
array('hgm' => $this->prefix . 'hostgroup_members'),
|
||||||
|
'hgm.host_object_id = s.host_object_id',
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('hg' => $this->prefix . 'hostgroups'),
|
||||||
|
'hgm.hostgroup_id = hg.' . $this->hostgroup_id,
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('hgo' => $this->prefix . 'objects'),
|
||||||
|
'hgo.' . $this->object_id . ' = hg.hostgroup_object_id'
|
||||||
|
. ' AND hgo.is_active = 1',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function joinHostHostgroups()
|
||||||
|
{
|
||||||
|
$this->baseQuery->join(
|
||||||
|
array('hgm' => $this->prefix . 'hostgroup_members'),
|
||||||
|
'hgm.host_object_id = h.host_object_id',
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('hg' => $this->prefix . 'hostgroups'),
|
||||||
|
"hgm.hostgroup_id = hg.$this->hostgroup_id",
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('hgo' => $this->prefix . 'objects'),
|
||||||
|
'hgo.' . $this->object_id . ' = hg.hostgroup_object_id'
|
||||||
|
. ' AND hgo.is_active = 1',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function joinContacts()
|
||||||
|
{
|
||||||
|
$this->hcgcSub = $this->db->select()->distinct()->from(
|
||||||
|
array('hcgc' => $this->prefix . 'host_contactgroups'),
|
||||||
|
array('host_name' => 'ho.name1')
|
||||||
|
)->join(
|
||||||
|
array('cgo' => $this->prefix . 'objects'),
|
||||||
|
'hcg.contactgroup_object_id = cgo.' . $this->object_id
|
||||||
|
. ' AND cgo.is_active = 1',
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('h' => $this->prefix . 'hosts'),
|
||||||
|
'hcg.host_id = h.host_id',
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('ho' => $this->prefix . 'objects'),
|
||||||
|
'h.host_object_id = ho.' . $this->object_id . ' AND ho.is_active = 1',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
$this->baseQuery->join(
|
||||||
|
array('hcg' => $this->hcgSub),
|
||||||
|
'hcg.host_name = ho.name1',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function filterContactgroup($value)
|
||||||
|
{
|
||||||
|
$this->hcgSub->where(
|
||||||
|
$this->prepareFilterStringForColumn(
|
||||||
|
'cgo.name1 COLLATE latin1_general_ci',
|
||||||
|
$value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function joinContactgroups()
|
||||||
|
{
|
||||||
|
$this->hcgSub = $this->createContactgroupFilterSubselect();
|
||||||
|
$this->baseQuery->join(
|
||||||
|
array('hcg' => $this->hcgSub),
|
||||||
|
'hcg.object_id = ho.object_id',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createContactgroupFilterSubselect()
|
||||||
|
{
|
||||||
|
die((string)$this->db->select()->distinct()->from(
|
||||||
|
array('hcg' => $this->prefix . 'host_contactgroups'),
|
||||||
|
array('object_id' => 'ho.object_id')
|
||||||
|
)->join(
|
||||||
|
array('cgo' => $this->prefix . 'objects'),
|
||||||
|
'hcg.contactgroup_object_id = cgo.' . $this->object_id
|
||||||
|
. ' AND cgo.is_active = 1',
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('h' => $this->prefix . 'hosts'),
|
||||||
|
'hcg.host_id = h.host_id',
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('ho' => $this->prefix . 'objects'),
|
||||||
|
'h.host_object_id = ho.' . $this->object_id . ' AND ho.is_active = 1',
|
||||||
|
array()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function joinServicegroups()
|
||||||
|
{
|
||||||
|
// TODO: Only hosts with services having such servicegroups
|
||||||
|
$this->requireVirtualTable('services');
|
||||||
|
$this->baseQuery->join(
|
||||||
|
array('sgm' => $this->prefix . 'servicegroup_members'),
|
||||||
|
'sgm.service_object_id = s.service_object_id',
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('sg' => $this->prefix . 'servicegroups'),
|
||||||
|
'sgm.servicegroup_id = sg.' . $this->servicegroup_id,
|
||||||
|
array()
|
||||||
|
)->join(
|
||||||
|
array('sgo' => $this->prefix . 'objects'),
|
||||||
|
'sgo.' . $this->object_id . ' = sg.servicegroup_object_id'
|
||||||
|
. ' AND sgo.is_active = 1',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -71,6 +71,7 @@ abstract class DataView implements Filterable
|
|||||||
*/
|
*/
|
||||||
public static function fromRequest($request, array $columns = null)
|
public static function fromRequest($request, array $columns = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
$view = new static(Backend::createBackend($request->getParam('backend')), $columns);
|
$view = new static(Backend::createBackend($request->getParam('backend')), $columns);
|
||||||
$parser = new UrlViewFilter($view);
|
$parser = new UrlViewFilter($view);
|
||||||
$view->getQuery()->setFilter($parser->fromRequest($request));
|
$view->getQuery()->setFilter($parser->fromRequest($request));
|
||||||
@ -107,7 +108,6 @@ abstract class DataView implements Filterable
|
|||||||
$view->getQuery()->where($key, $value);
|
$view->getQuery()->where($key, $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$order = isset($params['order']) ? $params['order'] : null;
|
$order = isset($params['order']) ? $params['order'] : null;
|
||||||
if ($order !== null) {
|
if ($order !== null) {
|
||||||
if (strtolower($order) === 'desc') {
|
if (strtolower($order) === 'desc') {
|
||||||
@ -154,7 +154,7 @@ abstract class DataView implements Filterable
|
|||||||
public function sort($column = null, $order = null)
|
public function sort($column = null, $order = null)
|
||||||
{
|
{
|
||||||
$sortRules = $this->getSortRules();
|
$sortRules = $this->getSortRules();
|
||||||
|
if ($sortRules !== null) {
|
||||||
if ($column === null) {
|
if ($column === null) {
|
||||||
$sortColumns = reset($sortRules);
|
$sortColumns = reset($sortRules);
|
||||||
if (!isset($sortColumns['columns'])) {
|
if (!isset($sortColumns['columns'])) {
|
||||||
@ -180,6 +180,7 @@ abstract class DataView implements Filterable
|
|||||||
foreach ($sortColumns['columns'] as $column) {
|
foreach ($sortColumns['columns'] as $column) {
|
||||||
$this->query->order($column, $order);
|
$this->query->order($column, $order);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +189,10 @@ abstract class DataView implements Filterable
|
|||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
abstract public function getSortRules();
|
public function getSortRules()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public function getMappedField($field)
|
public function getMappedField($field)
|
||||||
{
|
{
|
||||||
|
@ -15,7 +15,9 @@ class Downtime extends DataView
|
|||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
'host',
|
'host',
|
||||||
|
'host_name',
|
||||||
'service',
|
'service',
|
||||||
|
'service_description',
|
||||||
'downtime_author',
|
'downtime_author',
|
||||||
'downtime_comment',
|
'downtime_comment',
|
||||||
'downtime_entry_time',
|
'downtime_entry_time',
|
||||||
|
@ -58,7 +58,8 @@ class HostStatus extends DataView
|
|||||||
'host_action_url',
|
'host_action_url',
|
||||||
'host_notes_url',
|
'host_notes_url',
|
||||||
'host_percent_state_change',
|
'host_percent_state_change',
|
||||||
'host_modified_host_attributes'
|
'host_modified_host_attributes',
|
||||||
|
'host_severity'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
namespace Icinga\Module\Monitoring\DataView;
|
||||||
|
|
||||||
|
class StatusSummary extends DataView
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Retrieve columns provided by this view
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getColumns()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'hosts_up',
|
||||||
|
'hosts_unreachable_handled',
|
||||||
|
'hosts_unreachable_unhandled',
|
||||||
|
'hosts_down_handled',
|
||||||
|
'hosts_down_unhandled',
|
||||||
|
'hosts_pending',
|
||||||
|
'services_ok',
|
||||||
|
'services_critical_handled',
|
||||||
|
'services_critical_unhandled',
|
||||||
|
'services_warning_handled',
|
||||||
|
'services_warning_unhandled',
|
||||||
|
'services_unknown_handled',
|
||||||
|
'services_unknown_unhandled',
|
||||||
|
'services_pending'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getTableName()
|
||||||
|
{
|
||||||
|
return 'hoststatus';
|
||||||
|
}
|
||||||
|
}
|
@ -197,40 +197,24 @@
|
|||||||
padding-left: 25px !important;
|
padding-left: 25px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icinga-navbar-pills-mixin (@color) {
|
.topbar-status-summary-mixin (@backgroundImage) {
|
||||||
border-color: @color;
|
background-image: url(@backgroundImage);
|
||||||
color: @color;
|
background-repeat: no-repeat;
|
||||||
|
background-position: 5px 50%;
|
||||||
|
padding-left: 30px !important;
|
||||||
|
margin-top: 15px;
|
||||||
|
span:not(:last-child) {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.topbar-host-status-summary {
|
||||||
|
.topbar-status-summary-mixin('../../img/icons/host.png');
|
||||||
|
}
|
||||||
|
.topbar-service-status-summary {
|
||||||
|
.topbar-status-summary-mixin('../../img/icons/service.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
.icinga-navbar-pills-critical {
|
.topbar-status-mixin (@color) {
|
||||||
.icinga-navbar-pills-mixin(@colorCritical);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icinga-navbar-pills-ok {
|
|
||||||
.icinga-navbar-pills-mixin(@colorOk);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icinga-navbar-pills-warning {
|
|
||||||
.icinga-navbar-pills-mixin(@colorWarning);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icinga-navbar-pills-unknown {
|
|
||||||
.icinga-navbar-pills-mixin(@colorWarning);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icinga-navbar-pills-unreachable {
|
|
||||||
.icinga-navbar-pills-mixin(@colorUnreachable);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icinga-navbar-pills-down {
|
|
||||||
.icinga-navbar-pills-mixin(@colorDown);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icinga-navbar-pills-up {
|
|
||||||
.icinga-navbar-pills-mixin(@colorUp);
|
|
||||||
}
|
|
||||||
|
|
||||||
.state-tile (@color) {
|
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
padding: 3px 5px 3px 5px;
|
padding: 3px 5px 3px 5px;
|
||||||
@ -238,20 +222,39 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
border-color: @color;
|
border-color: @color;
|
||||||
color: @color;
|
color: @color;
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.state-tile-up {
|
.topbar-status-up {
|
||||||
.navigation-tile-mixin(@colorUp);
|
.topbar-status-mixin(@colorUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
.state-tile-unreachable {
|
.topbar-status-down {
|
||||||
.navigation-tile-mixin(@colorUnreachable);
|
.topbar-status-mixin(@colorDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
.state-tile-down {
|
.topbar-status-unreachable {
|
||||||
.navigation-tile-mixin(@colorDown);
|
.topbar-status-mixin(@colorUnreachable);
|
||||||
}
|
}
|
||||||
|
|
||||||
.state-tile-pending {
|
.topbar-status-pending {
|
||||||
.navigation-tile-mixin(@colorPending);
|
.topbar-status-mixin(@colorPending);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-status-ok {
|
||||||
|
.topbar-status-mixin(@colorOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-status-critical {
|
||||||
|
.topbar-status-mixin(@colorCritical);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-status-warning {
|
||||||
|
.topbar-status-mixin(@colorWarning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-status-unknown {
|
||||||
|
.topbar-status-mixin(@colorUnknown);
|
||||||
}
|
}
|
44
modules/monitoring/register.php
Normal file
44
modules/monitoring/register.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Icinga\Authentication\Manager as AuthManager;
|
||||||
|
use Icinga\Application\Icinga;
|
||||||
|
use Icinga\Module\Monitoring\DataView\StatusSummary as StatusSummaryView;
|
||||||
|
use Icinga\Web\Topbar;
|
||||||
|
|
||||||
|
$request = Icinga::app()->getFrontController()->getRequest();
|
||||||
|
|
||||||
|
if (AuthManager::getInstance()->isAuthenticated()) {
|
||||||
|
$hostSummary = StatusSummaryView::fromRequest(
|
||||||
|
$request,
|
||||||
|
array(
|
||||||
|
'hosts_up',
|
||||||
|
'hosts_unreachable_handled',
|
||||||
|
'hosts_unreachable_unhandled',
|
||||||
|
'hosts_down_handled',
|
||||||
|
'hosts_down_unhandled',
|
||||||
|
'hosts_pending'
|
||||||
|
)
|
||||||
|
)->getQuery()->fetchRow();
|
||||||
|
|
||||||
|
$serviceSummary = StatusSummaryView::fromRequest(
|
||||||
|
$request,
|
||||||
|
array(
|
||||||
|
'services_ok',
|
||||||
|
'services_critical_handled',
|
||||||
|
'services_critical_unhandled',
|
||||||
|
'services_warning_handled',
|
||||||
|
'services_warning_unhandled',
|
||||||
|
'services_unknown_handled',
|
||||||
|
'services_unknown_unhandled',
|
||||||
|
'services_pending'
|
||||||
|
)
|
||||||
|
)->getQuery()->fetchRow();
|
||||||
|
|
||||||
|
Topbar::addPartial(
|
||||||
|
'topbar.phtml',
|
||||||
|
'monitoring',
|
||||||
|
array('hostSummary' => $hostSummary, 'serviceSummary' => $serviceSummary)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
@ -169,26 +169,3 @@ ul.icinga-subnavigation {
|
|||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icinga-navbar-hosts-container {
|
|
||||||
background-image: url('../../img/icons/host.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 5px 50%;
|
|
||||||
padding-left: 30px !important;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
.icinga-navbar-services-container {
|
|
||||||
background-image: url('../../img/icons/service.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 5px 50%;
|
|
||||||
padding-left: 25px !important;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icinga-navbar-pills {
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
padding: 3px 5px 3px 5px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user