Прежде чем вдаваться в описание программы, укажу на ситуацию которая привела к созданию очередного велосипеда.
Есть группа в которую входит Ваш покорный слуга + два сотрудника. В наше присутствие на рабочем месте, события в Zabbix (2.2) отслеживаются постоянно, а если быть точным то 8 часов в день, 5 дней в неделю. Исключения составляют праздничные дни и отпуска совпавшие с изменениями графика работы у коллег.
В наше отсутствие мониторингом инфраструктуры занимается (по крайней мере должен заниматься) дежурный персонал. В этом и кроется проблема!
Как выясняется, люди работающие в режиме день - ночь - 48 часов дома, не проявляют особого интереса к мониторингу состояния триггеров со всеми вытекающими из этого последствиями. Ну да Бог с ними, воспитательные работы - задача административного персонала.
Наша задача более явно оповещать об изменениях в активных триггерах на машинах дежурного персонала под управлением ОС Windows.
Под этим я подразумеваю:
1. Оповещать о переходе активного триггера в состояние TRUE
- - Звуковым сигналом
- Всплывающей подсказкой в трее
Велосипед заключается в том, что мы будем использовать PHP для реализации GUI под Windows. Да, да, да ... можете плевать в мою сторону и я полностью согласен, что C++ намного эффективнее, но мои познаний в C++ еще не достаточно, для перехода на новую ступень эволюции.
И так, принято решение разделить программу на две части. Первая будет выполнена в виде PHP скрипта находящегося на сервере и по переходу по ссылке вида "http://server/trigger/index.php" мы будем получать список проблемных триггеров в формате JSON. Вторая часть программы будет выполнена в виде клиентского приложения разработанного в IDE DevelStudio (если этот продукт можно считать таковым) и например раз в минуту забирать JSON строку с сервера, проводить необходимые преобразования с информацией и оповещать конечного пользователя если это необходимо.
Скрипт на сервере
Изначально была попытка использовать Zabbix API (на базе http://zabbixapi.confirm.ch/), но информацию приходилось вытаскивать за несколько шагов, что было не очень удобно, хотя более корректно.
Принято решение забирать данные напрямую из MySQL одним запросом, преобразовывать их в общий массив, конвертировать в JSON и отдавать во внешнюю среду. Скрипт легко модифицировать и в случае необходимости добавить ограничение по количеству запросов в минуту с одного хоста и естественно организовать доступ к данным всем IP адресам кроме избранных.
Для более корректной и правильной работы необходимо завести нового пользователя в MySQL и дать ему права только на выполнение SELECT в базе zabbix
Вам необходимо изменить соответствующую информацию сразу после комментария "# Укажите нужные данные для авторизации"
Сам скрипт:
Код: Выделить всё
<?PHP
# Обработка ошибок
function error($message)
{
return json_encode(array('error' => $message));
}
# Работа с БД
class db extends PDO
{
# Начальные данные для подключения к серверу
private $settings = array(
'host' => 'localhost',
'db' => '',
'login' => 'root',
'pass' => '',
);
#
function __construct($settings = array())
{
# Изменяем конфигурацию
if(is_array($settings) and 0 != count($settings)) {
foreach($settings as $key => $val) {
if(isset($this->settings[$key])) $this->settings[$key] = $val;
}
}
# Подключаемся к базе данных
try {
parent::__construct('mysql:host='.$this->settings['host'].';dbname='.$this->settings['db'].';charset=utf8', $this->settings['login'], $this->settings['pass']);
}
catch(PDOException $error) {
exit(error($error->getMessage()));
}
}
}
# Укажите нужные данные для авторизации
$settings = array(
'db' => 'zabbix',
'login' => 'login',
'pass' => 'password'
);
$db = new db($settings);
$query = $db->query("
SELECT
t.triggerid,
h.host,
t.priority,
t.description,
t.lastchange
FROM
triggers t,
hosts h,
items i,
functions f
WHERE
t.value=1 AND
t.status=0 AND
f.triggerid=t.triggerid AND
i.itemid=f.itemid AND
i.status=0 AND
h.hostid=i.hostid AND
h.status=0
ORDER BY
t.lastchange DESC
");
$message = array();
if($query->rowCount()) {
foreach($query->fetchAll(PDO::FETCH_ASSOC) as $var => $val) {
$message[$val['triggerid']] = $val;
}
}
echo json_encode($message);
?>
Код: Выделить всё
SELECT
t.triggerid,
h.host,
t.priority,
t.description,
t.lastchange
FROM
triggers t,
hosts h,
items i,
functions f
WHERE
t.value=1 AND
t.status=0 AND
f.triggerid=t.triggerid AND
i.itemid=f.itemid AND
i.status=0 AND
h.hostid=i.hostid AND
h.status=0
ORDER BY
t.lastchange DESC
Теперь о второй части велосипеда.
Большая часть программы (до компиляции) лежит в каталоге с проектом, в файле /scripts/thread.php и представляет из себя набор функций:
Код: Выделить всё
<?php
function _constructor()
{
if(($settings = read_config('config.ini')) == false) {
$settings['server']['trigger'] = 'http://localhost/trigger/index.php';
ini::open("config.ini");
ini::write("server", "trigger", $settings['server']['trigger']);
messageDlg("Not performed the initial configuration.\r\nCheck config.ini and restart the program!", mtWarning, MB_OK);
}
# Устанавливаем doubleBuffered для всех объектов на форме
foreach(c("Main")->componentList as $obj) $obj->doubleBuffered = true;
# Загружаем изображения статусов триггеров
for($i=1; $i<=6; $i++) c("ImageList1")->addFromFile('icons\\'.$i.'.png');
# Действия с TrayIcon
_eventTrayIcon(c("Main->trayIcon"));
# Запускаем фоновый поток занимающийся обработкой информации полученной от сервера
thread($settings['server']['trigger']);
}
function _gui($name, $key, $val = false, $func = false)
{
if($val) {
if($func) return c($name)->{$key}->$val(); else c($name)->{$key} = $val;
}
else return c($name)->{$key};
}
function _alert($title, $text)
{
# Всплывающее окно
$trayIcon = c("trayIcon");
$trayIcon->title = $title;
$trayIcon->text = $text;
$trayIcon->hideBalloonTip(); # Если висит предыдущее сообщение, убираем его
$trayIcon->showBalloonTip();
# Звуковой сигнал
$sqPlayer = c('sqPlayer');
$sqPlayer->fileName = '/audio/alarm.ogg';
if(!$sqPlayer->isPlay()) $sqPlayer->play();
}
function zabbix($param)
{
$param = TThread::get($param);
while(true) {
if(($json = file_get_contents($param->serverUrl)) == false) {
syncEx('_gui', array('statusBar', 'simpleText', 'No connection to the server'));
}
else {
if(($trigger = json_decode($json)) == null) {
syncEx('_gui', array('statusBar', 'simpleText', 'json can not be converted'));
}
else {
# Очищаем список триггеров
syncEx('_gui', array('listViewLastStatus', 'items', 'clear', true));
foreach($trigger as $key => $val) {
# Различные преобразования
$val->lastchange = date("d.m.Y H:i", $val->lastchange);
$val->description = iconv('UTF-8', 'CP1251//IGNORE', $val->description);
$val->description = str_replace('{HOST.NAME}', $val->host, $val->description);
# Попытка найти новый триггер
if(is_array($oldTrigger) and $oldTrigger[$key] == null) {
syncEx('_alert', array($val->host, $val->description));
}
# Добавление нового триггера в GUI
$item = syncEx('_gui', array('listViewLastStatus', 'items', 'add', true));
$item->caption = $val->host;
$item->subItems = array($val->lastchange, $val->description);
$item->imageIndex = $val->priority;
# Временный массив
$tmp[$key] = $val->host;
}
$oldTrigger = $tmp; unset($tmp);
# Чистим статус бар
syncEx('_gui', array('statusBar', 'simpleText', null));
}
}
# Интервал обновления
delay(60000);
}
}
function thread($url)
{
$daemon = new TTHread('zabbix');
$daemon->serverUrl = $url;
$daemon->priority = tpLower;
$daemon->resume();
}
function read_config($file) {
if(!file_exists($file)) return false;
$settings = array();
ini::open($file);
ini::readSections($section);
foreach($section as $id => $sec) {
ini::readKeys($sec, $keys);
foreach($keys as $id => $key) {
ini::read($sec, $key, $settings[$sec][$key]);
}
}
return $settings;
}
function _eventTrayIcon($trayIcon)
{
global $trayPopup;
$trayPopup = new TPopupMenu;
$trayPopup->name = 'trayPopup';
$itemPopup = new TMenuItem;
$itemPopup->caption = 'Выход';
$itemPopup->onClick = function() {
exit();
};
$trayPopup->addItem($itemPopup);
$trayIcon->onClick = function() {
global $APPLICATION;
// Показываем окно и выводим его поверх всех окон
$APPLICATION->restore();
$APPLICATION->toFront();
};
$trayIcon->onDblClick = function() {
global $APPLICATION;
// Показываем окно и выводим его поверх всех окон
$APPLICATION->restore();
$APPLICATION->toFront();
};
$trayIcon->onMouseDown = function() use($trayPopup) {
if(get_key_state(2) < 0) $trayPopup->popup(cursor_pos_x(), cursor_pos_y());
};
}
?>
Код: Выделить всё
[server]
trigger=http://server/trigger/index.php
В общем то, вот и вся суть. Теперь немного картинок.
Я полностью открыт для Ваших плевков и негодования, но кому интересно, исходники и скомпилированная программа прикреплены к посту.
В архиве dvs.zip только исходник для DevelStudio, недостающие каталоги audio и icons можно взять из скомпилированного проекта: https://www.dropbox.com/s/v5af9qn3ofqof ... d.zip?dl=0
В архиве index.zip серверная часть.
Всем спасибо, все свободны.