*/
class BClientScript extends TClientScript {
- const SCRIPTS_VERSION = 15;
+ const SCRIPTS_VERSION = 16;
public function getScriptUrl()
{
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+Prado::using('Application.:Web.Class.WebModule');
+
+/**
+ * Module responsible for managing messages log.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @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;
+ }
+}
+?>
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] = '<span class="w3-orange">' + logs[i] + '</span>';
+ warning = true;
+ }
+ }
+ for (var j = 0; j < this.issue_regex.error.length; j++) {
+ if (this.issue_regex.error[j].test(logs[i])) {
+ logs[i] = '<span class="w3-red">' + logs[i] + '</span>';
+ error = true;
+ }
+ }
+ }
+
+ if (error) {
+ this.mark_envelope_error();
+ } else if (warning) {
+ this.mark_envelope_warning();
+ }
+ }
+};
var W3SideBar = {
ids: {
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"
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"
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"
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"
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"
*/
Prado::using('Application.Common.Class.Params');
+Prado::using('Application.Web.Class.WebUserRoles');
/**
* Main layout class.
<span class="w3-tag w3-large w3-purple w3-right w3-padding-small w3-margin-top w3-margin-right">
<i class="fa fa-cogs w3-large"></i> <%[ Running jobs: ]%> <span id="running_jobs"></span>
</span>
+ <span id="msg_envelope" class="w3-tag w3-large w3-green w3-text-white w3-right w3-padding-small w3-margin-top w3-margin-right" style="cursor: pointer;<%=$this->User->isInRole(WebUserRoles::ADMIN) === false ? 'display: none' : ''%>" title="<%[ Display messages log window ]%>">
+ <i class="fas fa-envelope w3-large"></i>
+ </span>
<script type="text/javascript">
var SIZE_VALUES_UNIT = '<%=(count($this->web_config) > 0 && key_exists('size_values_unit', $this->web_config['baculum'])) ? $this->web_config['baculum']['size_values_unit'] : WebConfig::DEF_SIZE_VAL_UNIT%>';
var DATE_TIME_FORMAT = '<%=(count($this->web_config) > 0 && key_exists('date_time_format', $this->web_config['baculum'])) ? $this->web_config['baculum']['date_time_format'] : WebConfig::DEF_DATE_TIME_FORMAT%>';
err_box.style.display = 'block';
}
</script>
+ <com:Application.Web.Portlets.MsgEnvelope Visible="<%=$this->User->isInRole(WebUserRoles::ADMIN)%>" />
</body>
</html>
* Bacula(R) is a registered trademark of Kern Sibbald.
*/
Prado::using('Application.Web.Class.BaculumWebPage');
+Prado::using('Application.Web.Class.WebUserRoles');
/**
* Monitor class.
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);
}
$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++) {
}
}
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 {
}
}
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 {
}
}
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 {
}
}
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 {
}
}
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();
<module id="users" class="Application.Web.Class.WebUserManager" UserClass="Application.Web.Class.WebUser" />
<!-- data modules -->
<module id="job_info" class="Application.Web.Class.JobInfo" />
+ <module id="messages_log" class="Application.Web.Class.MessagesLog" />
</modules>
</configuration>
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2021 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+
+Prado::using('System.Web.UI.ActiveControls.TCallback');
+Prado::using('Application.Web.Class.WebUserRoles');
+Prado::using('Application.Web.Portlets.Portlets');
+
+/**
+ * Message envelope control.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Control
+ * @package Baculum Web
+ */
+class MsgEnvelope extends Portlets {
+
+ public function truncate() {
+ if (!$this->User->isInRole(WebUserRoles::ADMIN)) {
+ return;
+ }
+ $this->getModule('messages_log')->truncate();
+ }
+}
+?>
--- /dev/null
+<div id="msg_envelope_modal" class="w3-modal">
+ <div class="w3-modal-content w3-animate-top w3-card-4">
+ <header class="w3-container w3-green">
+ <span onclick="MsgEnvelope.close();" class="w3-button w3-display-topright">×</span>
+ <h2><%[ Messages ]%></h2>
+ </header>
+ <div class="w3-container w3-margin-left w3-margin-right">
+ <div id="msg_envelope_container" class="w3-code" style="font-size: 12px; min-height: 50px; max-height: 610px; overflow-y: scroll; overflow-x: auto;">
+ <pre id="msg_envelope_content"></pre>
+ </div>
+ </div>
+ <footer class="w3-container w3-center">
+ <button class="w3-button w3-red w3-section w3-margin-right" onclick="msg_envelope_truncate();"><i class="fas fa-cut"></i> <%[ Truncate log ]%></button>
+ <button class="w3-button w3-red w3-section" onclick="MsgEnvelope.close();"><i class="fas fa-times"></i> <%[ Close ]%></button>
+ </footer>
+ </div>
+</div>
+<com:TCallback
+ ID="MsgEnvelopeTruncate"
+ OnCallback="truncate"
+ ClientSide.OnComplete="MsgEnvelope.set_logs([]); MsgEnvelope.mark_envelope_ok();"
+/>
+<script>
+function msg_envelope_truncate() {
+ var cb = <%=$this->MsgEnvelopeTruncate->ActiveControl->Javascript%>;
+ cb.dispatch();
+}
+MsgEnvelope.init();
+</script>
display: inline;
}
}
+
+@keyframes blinker {
+ 50% {
+ opacity: 0;
+ }
+}
+
+.blink {
+ animation: blinker 4s linear infinite;
+}