From: Marcin Haba Date: Sun, 24 Jan 2021 19:57:56 +0000 (+0100) Subject: baculum: Add console messages log envelope X-Git-Tag: Release-11.3.2~274 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9241f696d1e9edebe40995ec3f8e94d0b5f10239;p=thirdparty%2Fbacula.git baculum: Add console messages log envelope --- diff --git a/gui/baculum/protected/Common/Class/BClientScript.php b/gui/baculum/protected/Common/Class/BClientScript.php index 79ce1d737..5de8b14d5 100644 --- a/gui/baculum/protected/Common/Class/BClientScript.php +++ b/gui/baculum/protected/Common/Class/BClientScript.php @@ -31,7 +31,7 @@ Prado::using('System.Web.UI.WebControls.TClientScript'); */ class BClientScript extends TClientScript { - const SCRIPTS_VERSION = 15; + const SCRIPTS_VERSION = 16; public function getScriptUrl() { diff --git a/gui/baculum/protected/Web/Class/MessagesLog.php b/gui/baculum/protected/Web/Class/MessagesLog.php new file mode 100644 index 000000000..24facd65a --- /dev/null +++ b/gui/baculum/protected/Web/Class/MessagesLog.php @@ -0,0 +1,172 @@ + + * @category Module + * @package Baculum Web + */ +class MessagesLog extends WebModule { + + /** + * Messages log file path. + */ + const LOG_FILE_PATH = 'Application.Web.Logs.messages'; + + /** + * Messages log file extension. + */ + const LOG_FILE_EXT = '.log'; + + /** + * Maximum number of lines to keep. + */ + const MAX_LINES = 1000; + + /** + * Append messages to messages log. + * NOTE: Max. lines limit is taken into acocunt. + * + * @param array $logs log messages + * @return array logs stored in log file + */ + public function append(array $logs) { + $logs_all = []; + $f = Prado::getPathOfNamespace(self::LOG_FILE_PATH, self::LOG_FILE_EXT); + $fp = fopen($f, 'c+'); + if (flock($fp, LOCK_EX)) { + $fsize = filesize($f); + $messages_file = $fsize > 0 ? fread($fp, $fsize) : ''; + $logs_file = explode(PHP_EOL, $messages_file); + $logs_all = array_merge($logs_file, $logs); + $all_len = count($logs_all); + if ($all_len > self::MAX_LINES) { + $len = $all_len - self::MAX_LINES; + array_splice($logs_all, 0, $len); + } + $messages = implode(PHP_EOL, $logs_all); + rewind($fp); + ftruncate($fp, 0); + fwrite($fp, $messages); + fflush($fp); + flock($fp, LOCK_UN); + } else { + $emsg = 'Could not get the exclusive lock: ' . $f; + $this->Application->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_APPLICATION, + __FILE__, + __LINE__ + ); + } + fclose($fp); + return $logs_all; + } + + /** + * Truncate messages log. + * + * @return none + */ + public function truncate() { + $f = Prado::getPathOfNamespace(self::LOG_FILE_PATH, self::LOG_FILE_EXT); + $fp = fopen($f, 'w'); + if (flock($fp, LOCK_EX)) { + fflush($fp); + flock($fp, LOCK_UN); + } else { + $emsg = 'Could not get the exclusive lock: ' . $f; + $this->Application->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_APPLICATION, + __FILE__, + __LINE__ + ); + } + fclose($fp); + } + + /** + * Save logs to file. + * + * @param array $logs log messages + * @return none + */ + public function save(array $logs) { + $f = Prado::getPathOfNamespace(self::LOG_FILE_PATH, self::LOG_FILE_EXT); + $fp = fopen($f, 'a'); + if (flock($fp, LOCK_EX)) { + $messages = implode(PHP_EOL, $logs); + fwrite($fp, $messages); + fflush($fp); + flock($fp, LOCK_UN); + } else { + $emsg = 'Could not get the exclusive lock: ' . $f; + $this->Application->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_APPLICATION, + __FILE__, + __LINE__ + ); + } + fclose($fp); + } + + /** + * Read logs from file. + * + * @return array log messages + */ + public function read() { + $logs = []; + $f = Prado::getPathOfNamespace(self::LOG_FILE_PATH, self::LOG_FILE_EXT); + if (!file_exists($f)) { + return $logs; + } + $fp = fopen($f, 'r'); + if (flock($fp, LOCK_SH)) { + $fsize = filesize($f); + $messages = $fsize > 0 ? fread($fp, $fsize) : ''; + $logs = explode(PHP_EOL, $messages); + flock($fp, LOCK_UN); + } else { + $emsg = 'Could not get the shared lock: ' . $f; + $this->Application->getModule('logging')->log( + __FUNCTION__, + $emsg, + Logging::CATEGORY_APPLICATION, + __FILE__, + __LINE__ + ); + } + fclose($fp); + return $logs; + } +} +?> diff --git a/gui/baculum/protected/Web/JavaScript/misc.js b/gui/baculum/protected/Web/JavaScript/misc.js index 2f429f1ff..59bea3c9b 100644 --- a/gui/baculum/protected/Web/JavaScript/misc.js +++ b/gui/baculum/protected/Web/JavaScript/misc.js @@ -802,7 +802,118 @@ var Dashboard = { container_id: this.ids.pie_summary }); } -} +}; + +var MsgEnvelope = { + ids: { + envelope: 'msg_envelope', + modal: 'msg_envelope_modal', + container: 'msg_envelope_container', + content: 'msg_envelope_content' + }, + issue_regex: { // @TODO: add more regexes + warning: [ + /Cannot find any appendable volumes/g + ], + error: [ + /ERR=/g + ] + }, + init: function() { + this.set_events(); + this.set_actions(); + }, + set_events: function() { + document.getElementById(this.ids.envelope).addEventListener('click', function(e) { + this.open(); + var container = document.getElementById(this.ids.container); + // set scroll to the bottom + container.scrollTop = container.scrollHeight; + }.bind(this)); + }, + set_actions: function() { + var monitor_func = function() { + var is_bottom = false; + var container = document.getElementById(this.ids.container); + + // detect if before adding content, scroll is at the bottom + if (container.scrollTop === (container.scrollHeight - container.offsetHeight)) { + is_bottom = true + } + + // add logs + var logs = oData.messages; + MsgEnvelope.set_logs(logs); + + // set scroll to the bottom + if (is_bottom) { + container.scrollTop = container.scrollHeight; + } + }.bind(this); + MonitorCallsInterval.push(monitor_func); + }, + open: function() { + document.getElementById(this.ids.modal).style.display = 'block'; + }, + close: function() { + document.getElementById(this.ids.modal).style.display = 'none'; + }, + set_logs: function(logs) { + this.find_issues(logs); + document.getElementById(this.ids.content).innerHTML = logs.join("\n"); + }, + mark_envelope_error: function() { + var envelope = document.getElementById(this.ids.envelope); + if (envelope.classList.contains('w3-green')) { + envelope.classList.replace('w3-green', 'w3-red'); + } + if (envelope.classList.contains('w3-orange')) { + envelope.classList.replace('w3-orange', 'w3-red'); + } + envelope.querySelector('I').classList.add('blink'); + }, + mark_envelope_warning: function() { + var envelope = document.getElementById(this.ids.envelope); + if (envelope.classList.contains('w3-green')) { + envelope.classList.replace('w3-green', 'w3-orange'); + } + envelope.querySelector('I').classList.add('blink'); + }, + mark_envelope_ok: function() { + var envelope = document.getElementById(this.ids.envelope); + if (envelope.classList.contains('w3-red')) { + envelope.classList.replace('w3-red', 'w3-green'); + } + if (envelope.classList.contains('w3-orange')) { + envelope.classList.replace('w3-orange', 'w3-green'); + } + envelope.querySelector('I').classList.remove('blink'); + }, + find_issues: function(logs) { + var error = warning = false; + var logs_len = logs.length; + for (var i = 0; i < logs_len; i++) { + for (var j = 0; j < this.issue_regex.warning.length; j++) { + if (this.issue_regex.warning[j].test(logs[i])) { + logs[i] = '' + logs[i] + ''; + warning = true; + } + } + for (var j = 0; j < this.issue_regex.error.length; j++) { + if (this.issue_regex.error[j].test(logs[i])) { + logs[i] = '' + logs[i] + ''; + error = true; + } + } + } + + if (error) { + this.mark_envelope_error(); + } else if (warning) { + this.mark_envelope_warning(); + } + } +}; var W3SideBar = { ids: { diff --git a/gui/baculum/protected/Web/Lang/en/messages.mo b/gui/baculum/protected/Web/Lang/en/messages.mo index 5c5c6d8b0..8bcba4f1b 100644 Binary files a/gui/baculum/protected/Web/Lang/en/messages.mo and b/gui/baculum/protected/Web/Lang/en/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/en/messages.po b/gui/baculum/protected/Web/Lang/en/messages.po index 2873ea5f4..e8a007494 100644 --- a/gui/baculum/protected/Web/Lang/en/messages.po +++ b/gui/baculum/protected/Web/Lang/en/messages.po @@ -3194,3 +3194,11 @@ msgstr "Graphical storage status is supported for Bacula storages version 9.0 an msgid "Status request timed out. The most probably the Bacula storage is not available or it is not running." msgstr "Status request timed out. The most probably the Bacula storage is not available or it is not running." +msgid "Display messages log window" +msgstr "Display messages log window" + +msgid "Truncate log" +msgstr "Truncate log" + +msgid "Messages" +msgstr "Messages" diff --git a/gui/baculum/protected/Web/Lang/ja/messages.mo b/gui/baculum/protected/Web/Lang/ja/messages.mo index 0f09865cd..2f9f65b36 100644 Binary files a/gui/baculum/protected/Web/Lang/ja/messages.mo and b/gui/baculum/protected/Web/Lang/ja/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/ja/messages.po b/gui/baculum/protected/Web/Lang/ja/messages.po index 168466d68..3f40e3251 100644 --- a/gui/baculum/protected/Web/Lang/ja/messages.po +++ b/gui/baculum/protected/Web/Lang/ja/messages.po @@ -3280,3 +3280,11 @@ msgstr "Graphical storage status is supported for Bacula storages version 9.0 an msgid "Status request timed out. The most probably the Bacula storage is not available or it is not running." msgstr "Status request timed out. The most probably the Bacula storage is not available or it is not running." +msgid "Display messages log window" +msgstr "Display messages log window" + +msgid "Truncate log" +msgstr "Truncate log" + +msgid "Messages" +msgstr "Messages" diff --git a/gui/baculum/protected/Web/Lang/pl/messages.mo b/gui/baculum/protected/Web/Lang/pl/messages.mo index df7d08979..781cd396b 100644 Binary files a/gui/baculum/protected/Web/Lang/pl/messages.mo and b/gui/baculum/protected/Web/Lang/pl/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/pl/messages.po b/gui/baculum/protected/Web/Lang/pl/messages.po index 6eb1629e8..132a3c473 100644 --- a/gui/baculum/protected/Web/Lang/pl/messages.po +++ b/gui/baculum/protected/Web/Lang/pl/messages.po @@ -3205,3 +3205,11 @@ msgstr "Graficzny status magazynu jest wspierany dla magazynów Bacula w wersji msgid "Status request timed out. The most probably the Bacula storage is not available or it is not running." msgstr "Przekroczono limit czasu żądania statusu. Najprawdopodobniej magazyn Bacula jest niedostępny lub nie jest uruchomiony." +msgid "Display messages log window" +msgstr "Wyświetl okno z wiadomościami" + +msgid "Truncate log" +msgstr "Przytnij dziennik" + +msgid "Messages" +msgstr "Wiadomości" diff --git a/gui/baculum/protected/Web/Lang/pt/messages.mo b/gui/baculum/protected/Web/Lang/pt/messages.mo index 741104abe..c06cc1ad9 100644 Binary files a/gui/baculum/protected/Web/Lang/pt/messages.mo and b/gui/baculum/protected/Web/Lang/pt/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/pt/messages.po b/gui/baculum/protected/Web/Lang/pt/messages.po index 525dc3fe5..e5f77bb31 100644 --- a/gui/baculum/protected/Web/Lang/pt/messages.po +++ b/gui/baculum/protected/Web/Lang/pt/messages.po @@ -3204,3 +3204,11 @@ msgstr "O status gráfico de armazenamento é suportado para armazenamento Bacul msgid "Status request timed out. The most probably the Bacula storage is not available or it is not running." msgstr "A solicitação de status expirou. O mais provavelmente o armazenamento Bacula não está disponível ou não está em execução." +msgid "Display messages log window" +msgstr "Display messages log window" + +msgid "Truncate log" +msgstr "Truncate log" + +msgid "Messages" +msgstr "Messages" diff --git a/gui/baculum/protected/Web/Lang/ru/messages.mo b/gui/baculum/protected/Web/Lang/ru/messages.mo index 8120e5642..3fb88d8f1 100644 Binary files a/gui/baculum/protected/Web/Lang/ru/messages.mo and b/gui/baculum/protected/Web/Lang/ru/messages.mo differ diff --git a/gui/baculum/protected/Web/Lang/ru/messages.po b/gui/baculum/protected/Web/Lang/ru/messages.po index bd4c92a75..3f0e32762 100644 --- a/gui/baculum/protected/Web/Lang/ru/messages.po +++ b/gui/baculum/protected/Web/Lang/ru/messages.po @@ -3204,3 +3204,11 @@ msgstr "Графическое отображение состояния хра msgid "Status request timed out. The most probably the Bacula storage is not available or it is not running." msgstr "Истекло время ожидания. Скорее всего, хранилище Bacula недоступно или не работает." +msgid "Display messages log window" +msgstr "Display messages log window" + +msgid "Truncate log" +msgstr "Truncate log" + +msgid "Messages" +msgstr "Messages" diff --git a/gui/baculum/protected/Web/Layouts/Main.php b/gui/baculum/protected/Web/Layouts/Main.php index 2f9c118fc..217b677a8 100644 --- a/gui/baculum/protected/Web/Layouts/Main.php +++ b/gui/baculum/protected/Web/Layouts/Main.php @@ -21,6 +21,7 @@ */ Prado::using('Application.Common.Class.Params'); +Prado::using('Application.Web.Class.WebUserRoles'); /** * Main layout class. diff --git a/gui/baculum/protected/Web/Layouts/Main.tpl b/gui/baculum/protected/Web/Layouts/Main.tpl index 7bf70b74e..2c3ace0a4 100644 --- a/gui/baculum/protected/Web/Layouts/Main.tpl +++ b/gui/baculum/protected/Web/Layouts/Main.tpl @@ -36,6 +36,9 @@ <%[ Running jobs: ]%> + + + + diff --git a/gui/baculum/protected/Web/Pages/Monitor.php b/gui/baculum/protected/Web/Pages/Monitor.php index 835e33f02..cb5908317 100644 --- a/gui/baculum/protected/Web/Pages/Monitor.php +++ b/gui/baculum/protected/Web/Pages/Monitor.php @@ -20,6 +20,7 @@ * Bacula(R) is a registered trademark of Kern Sibbald. */ Prado::using('Application.Web.Class.BaculumWebPage'); +Prado::using('Application.Web.Class.WebUserRoles'); /** * Monitor class. @@ -37,16 +38,17 @@ class Monitor extends BaculumPage { public function onInit($param) { parent::onInit($param); - $monitor_data = array( - 'jobs' => array(), - 'running_jobs' => array(), - 'terminated_jobs' => array(), - 'pools' => array(), - 'clients' => array(), - 'jobtotals' => array(), - 'dbsize' => array(), - 'error' => array('error' => 0, 'output' => '') - ); + $monitor_data = [ + 'jobs' => [], + 'running_jobs' => [], + 'terminated_jobs' => [], + 'pools' => [], + 'clients' => [], + 'jobtotals' => [], + 'dbsize' => [], + 'messages' => [], + 'error' => ['error' => 0, 'output' => ''] + ]; // Initialize session cache to have clear session for Monitor $this->getModule('api')->initSessionCache(true); @@ -58,10 +60,10 @@ class Monitor extends BaculumPage { } $error = null; - $params = $this->Request->contains('params') ? $this->Request['params'] : array(); + $params = $this->Request->contains('params') ? $this->Request['params'] : []; if (is_array($params) && key_exists('jobs', $params)) { - $job_params = array('jobs'); - $job_query = array(); + $job_params = ['jobs']; + $job_query = []; if (is_array($params['jobs'])) { if (key_exists('name', $params['jobs']) && is_array($params['jobs']['name'])) { for ($i = 0; $i < count($params['jobs']['name']); $i++) { @@ -88,7 +90,7 @@ class Monitor extends BaculumPage { } } if (!$error) { - $result = $this->getModule('api')->get(array('jobs', '?jobstatus=CR')); + $result = $this->getModule('api')->get(['jobs', '?jobstatus=CR']); if ($result->error === 0) { $monitor_data['running_jobs'] = $result->output; } else { @@ -96,7 +98,7 @@ class Monitor extends BaculumPage { } } if (!$error && key_exists('clients', $params)) { - $result = $this->getModule('api')->get(array('clients')); + $result = $this->getModule('api')->get(['clients']); if ($result->error === 0) { $monitor_data['clients'] = $result->output; } else { @@ -104,7 +106,7 @@ class Monitor extends BaculumPage { } } if (!$error && key_exists('pools', $params)) { - $result = $this->getModule('api')->get(array('pools')); + $result = $this->getModule('api')->get(['pools']); if ($result->error === 0) { $monitor_data['pools'] = $result->output; } else { @@ -112,7 +114,7 @@ class Monitor extends BaculumPage { } } if (!$error && key_exists('job_totals', $params)) { - $result = $this->getModule('api')->get(array('jobs', 'totals')); + $result = $this->getModule('api')->get(['jobs', 'totals']); if ($result->error === 0) { $monitor_data['jobtotals'] = $result->output; } else { @@ -120,13 +122,27 @@ class Monitor extends BaculumPage { } } if (!$error && key_exists('dbsize', $params)) { - $result = $this->getModule('api')->get(array('dbsize')); + $result = $this->getModule('api')->get(['dbsize']); if ($result->error === 0) { $monitor_data['dbsize'] = $result->output; } else { $error = $result; } } + if (!$error && $this->User->isInRole(WebUserRoles::ADMIN)) { + $result = $this->getModule('api')->get(['joblog', 'messages']); + if ($result->error === 0) { + $ml = []; + if (count($result->output) > 0 && $result->output[0] != 'You have no messages.') { + $ml = $this->getModule('messages_log')->append($result->output); + } else { + $ml = $this->getModule('messages_log')->read(); + } + $monitor_data['messages'] = $this->getModule('log_parser')->parse($ml); + } else { + $error = $result; + } + } $running_job_states = $this->Application->getModule('misc')->getRunningJobStates(); diff --git a/gui/baculum/protected/Web/Pages/config.xml b/gui/baculum/protected/Web/Pages/config.xml index 242e85334..9f0ff33c8 100644 --- a/gui/baculum/protected/Web/Pages/config.xml +++ b/gui/baculum/protected/Web/Pages/config.xml @@ -27,5 +27,6 @@ + diff --git a/gui/baculum/protected/Web/Portlets/MsgEnvelope.php b/gui/baculum/protected/Web/Portlets/MsgEnvelope.php new file mode 100644 index 000000000..eaf26bbc0 --- /dev/null +++ b/gui/baculum/protected/Web/Portlets/MsgEnvelope.php @@ -0,0 +1,44 @@ + + * @category Control + * @package Baculum Web + */ +class MsgEnvelope extends Portlets { + + public function truncate() { + if (!$this->User->isInRole(WebUserRoles::ADMIN)) { + return; + } + $this->getModule('messages_log')->truncate(); + } +} +?> diff --git a/gui/baculum/protected/Web/Portlets/MsgEnvelope.tpl b/gui/baculum/protected/Web/Portlets/MsgEnvelope.tpl new file mode 100644 index 000000000..22b62106e --- /dev/null +++ b/gui/baculum/protected/Web/Portlets/MsgEnvelope.tpl @@ -0,0 +1,29 @@ +
+
+
+ × +

<%[ Messages ]%>

+
+
+
+

+			
+
+
+ + +
+
+
+ + diff --git a/gui/baculum/themes/Baculum-v2/css/baculum.css b/gui/baculum/themes/Baculum-v2/css/baculum.css index 14dd9635e..38ad6a4dd 100644 --- a/gui/baculum/themes/Baculum-v2/css/baculum.css +++ b/gui/baculum/themes/Baculum-v2/css/baculum.css @@ -516,3 +516,13 @@ table.component td:nth-of-type(1) { display: inline; } } + +@keyframes blinker { + 50% { + opacity: 0; + } +} + +.blink { + animation: blinker 4s linear infinite; +}