diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/SystemController.php b/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/SystemController.php
index 935e8bd13..f4dd4d0b6 100644
--- a/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/SystemController.php
+++ b/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/SystemController.php
@@ -1,7 +1,7 @@
configdRun('system status')), true);
if ($statuses) {
- $order = [-1 => 'Error', 0 => 'Warning', 1 => 'Notice', 2 => 'OK'];
-
+ $order = SystemStatusCode::toValueNameArray();
$acl = new ACL();
+
foreach ($statuses as $subsystem => $status) {
$statuses[$subsystem]['status'] = $order[$status['statusCode']];
if (!empty($status['logLocation'])) {
if (!$acl->isPageAccessible($this->getUserName(), $status['logLocation'])) {
unset($statuses[$subsystem]);
+ continue;
}
- } else {
- /* XXX exiting loop causing global endpoint failure */
- return $response;
}
}
- /* Sort on the highest error level after the ACL check */
+ /* Sort on the highest notification (non-persistent) error level after the ACL check */
$statusCodes = array_map(function ($v) {
- return $v['statusCode'];
+ return $v['persistent'] ? SystemStatusCode::OK : $v['statusCode'];
}, array_values($statuses));
sort($statusCodes);
- $statuses['System'] = [
- 'status' => $order[$statusCodes[0] ?? 2]
+
+ $response['metadata'] = [
+ /* 'system' represents the status of the top notification after sorting */
+ 'system' => [
+ 'status' => $order[$statusCodes[0] ?? 2],
+ 'message' => gettext('No pending messages'),
+ 'title' => gettext('System'),
+ ],
+ 'translations' => [
+ 'dialogTitle' => gettext('System Status'),
+ 'dialogCloseButton' => gettext('Close')
+ ]
];
+ // sort on status code and priority where priority is the tie breaker
+ uasort($statuses, function ($a, $b) {
+ if ($a['statusCode'] === $b['statusCode']) {
+ return $a['priority'] <=> $b['priority'];
+ }
+ return $a['statusCode'] <=> $b['statusCode'];
+ });
+
foreach ($statuses as &$status) {
if (!empty($status['timestamp'])) {
$age = time() - $status['timestamp'];
@@ -150,7 +168,8 @@ class SystemController extends ApiControllerBase
}
}
- $response = $statuses;
+ $response['subsystems'] = $statuses;
+ unset($response['status']);
}
return $response;
diff --git a/src/opnsense/mvc/app/library/OPNsense/System/AbstractStatus.php b/src/opnsense/mvc/app/library/OPNsense/System/AbstractStatus.php
index 3a4a4a90b..d91a38e45 100644
--- a/src/opnsense/mvc/app/library/OPNsense/System/AbstractStatus.php
+++ b/src/opnsense/mvc/app/library/OPNsense/System/AbstractStatus.php
@@ -1,7 +1,7 @@
internalPriority;
+ }
+
+ public function getPersistent()
+ {
+ return $this->internalPersistent;
+ }
+
+ public function getTitle()
+ {
+ return $this->internalTitle;
+ }
public function getStatus()
{
return $this->internalStatus;
}
- public function getMessage($verbose = false)
+ public function getMessage()
{
- return $this->internalMessage;
+ return $this->internalMessage ?? gettext('No problems were detected.');
}
public function getLogLocation()
diff --git a/src/opnsense/mvc/app/library/OPNsense/System/Status/CrashReporterStatus.php b/src/opnsense/mvc/app/library/OPNsense/System/Status/CrashReporterStatus.php
index 69a7a363c..47d554d0d 100644
--- a/src/opnsense/mvc/app/library/OPNsense/System/Status/CrashReporterStatus.php
+++ b/src/opnsense/mvc/app/library/OPNsense/System/Status/CrashReporterStatus.php
@@ -1,7 +1,7 @@
internalPriority = 10;
+ $this->internalTitle = gettext('Crash Reporter');
$this->internalLogLocation = '/crash_reporter.php';
$src_errors = count($src_logs) > 0;
@@ -61,10 +64,10 @@ class CrashReporterStatus extends AbstractStatus
if ($php_errors || $src_errors) {
$this->internalMessage = gettext('An issue was detected and can be reviewed using the firmware crash reporter.');
if ($php_errors) {
- $this->internalStatus = Config::getInstance()->object()->system->deployment != 'development' ? static::STATUS_ERROR : static::STATUS_NOTICE;
+ $this->internalStatus = Config::getInstance()->object()->system->deployment != 'development' ? SystemStatusCode::ERROR : SystemStatusCode::NOTICE;
}
- if ($src_errors && $this->internalStatus != static::STATUS_ERROR) {
- $this->internalStatus = static::STATUS_WARNING;
+ if ($src_errors && $this->internalStatus != SystemStatusCode::ERROR) {
+ $this->internalStatus = SystemStatusCode::WARNING;
}
}
}
diff --git a/src/opnsense/mvc/app/library/OPNsense/System/Status/FirewallStatus.php b/src/opnsense/mvc/app/library/OPNsense/System/Status/FirewallStatus.php
index 58742894b..0fd4f6007 100644
--- a/src/opnsense/mvc/app/library/OPNsense/System/Status/FirewallStatus.php
+++ b/src/opnsense/mvc/app/library/OPNsense/System/Status/FirewallStatus.php
@@ -1,7 +1,7 @@
internalPriority = 20;
+ $this->internalTitle = gettext('Firewall');
$this->internalLogLocation = '/ui/diagnostics/log/core/firewall';
if (file_exists($this->rules_error)) {
- $this->internalMessage = file_get_contents($this->rules_error);
- $this->internalStatus = static::STATUS_ERROR;
+ $this->internalMessage = file_get_contents($this->rules_error); /* XXX */
+ $this->internalStatus = SystemStatusCode::ERROR;
$info = stat($this->rules_error);
if (!empty($info['mtime'])) {
$this->internalTimestamp = $info['mtime'];
diff --git a/src/opnsense/mvc/app/library/OPNsense/System/Status/LiveMediaStatus.php b/src/opnsense/mvc/app/library/OPNsense/System/Status/LiveMediaStatus.php
index 587cfdd6e..7107286e6 100644
--- a/src/opnsense/mvc/app/library/OPNsense/System/Status/LiveMediaStatus.php
+++ b/src/opnsense/mvc/app/library/OPNsense/System/Status/LiveMediaStatus.php
@@ -29,14 +29,16 @@
namespace OPNsense\System\Status;
use OPNsense\System\AbstractStatus;
+use OPNsense\System\SystemStatusCode;
use OPNsense\Core\Config;
class LiveMediaStatus extends AbstractStatus
{
public function __construct()
{
- /* XXX historically tied to the dashboard but only given because controller will not allow an omission */
- $this->internalLogLocation = '/ui/core/dashboard';
+ $this->internalPriority = 2;
+ $this->internalPersistent = true;
+ $this->internalTitle = gettext('Live Media');
/*
* Despite unionfs underneath, / is still not writeable,
@@ -54,7 +56,7 @@ class LiveMediaStatus extends AbstractStatus
return;
}
- $this->internalStatus = static::STATUS_WARNING;
+ $this->internalStatus = SystemStatusCode::NOTICE;
$this->internalMessage = gettext('You are currently running in live media mode. A reboot will reset the configuration.');
if (empty(Config::getInstance()->object()->system->ssh->noauto)) {
exec('/bin/pgrep -anx sshd', $output, $retval); /* XXX portability shortcut */
diff --git a/src/opnsense/mvc/app/library/OPNsense/System/Status/SystemBootingStatus.php b/src/opnsense/mvc/app/library/OPNsense/System/Status/SystemBootingStatus.php
index f10064e53..9db3e3445 100644
--- a/src/opnsense/mvc/app/library/OPNsense/System/Status/SystemBootingStatus.php
+++ b/src/opnsense/mvc/app/library/OPNsense/System/Status/SystemBootingStatus.php
@@ -29,20 +29,22 @@
namespace OPNsense\System\Status;
use OPNsense\System\AbstractStatus;
+use OPNsense\System\SystemStatusCode;
class SystemBootingStatus extends AbstractStatus
{
public function __construct()
{
- /* XXX historically tied to the dashboard but only given because controller will not allow an omission */
- $this->internalLogLocation = '/ui/core/dashboard';
+ $this->internalPriority = 1;
+ $this->internalPersistent = true;
+ $this->internalTitle = gettext('System Booting');
/* XXX boot detection from final class product in config.inc */
$fp = fopen('/var/run/booting', 'a+e');
if ($fp) {
if (!flock($fp, LOCK_SH | LOCK_NB)) {
$this->internalMessage = gettext('The system is currently booting. Not all services have been started yet.');
- $this->internalStatus = static::STATUS_WARNING;
+ $this->internalStatus = SystemStatusCode::WARNING;
}
fclose($fp);
}
diff --git a/src/opnsense/mvc/app/library/OPNsense/System/SystemStatus.php b/src/opnsense/mvc/app/library/OPNsense/System/SystemStatus.php
index 840230458..9457b5b77 100644
--- a/src/opnsense/mvc/app/library/OPNsense/System/SystemStatus.php
+++ b/src/opnsense/mvc/app/library/OPNsense/System/SystemStatus.php
@@ -1,7 +1,7 @@
getShortName());
+ $shortName = strtolower(str_replace('Status', '', $reflect->getShortName()));
$this->objectMap[$shortName] = $obj;
if ($shortName == 'System') {
@@ -72,10 +72,13 @@ class SystemStatus
}
$result[$shortName] = [
+ 'title' => $obj->getTitle(),
'statusCode' => $obj->getStatus(),
'message' => $obj->getMessage(),
'logLocation' => $obj->getLogLocation(),
'timestamp' => $obj->getTimestamp(),
+ 'persistent' => $obj->getPersistent(),
+ 'priority' => $obj->getPriority(),
];
}
diff --git a/src/opnsense/mvc/app/library/OPNsense/System/SystemStatusCode.php b/src/opnsense/mvc/app/library/OPNsense/System/SystemStatusCode.php
new file mode 100644
index 000000000..dac190f6c
--- /dev/null
+++ b/src/opnsense/mvc/app/library/OPNsense/System/SystemStatusCode.php
@@ -0,0 +1,45 @@
+value] = $case->name;
+ }
+ return $result;
+ }
+}
diff --git a/src/opnsense/mvc/app/views/layouts/default.volt b/src/opnsense/mvc/app/views/layouts/default.volt
index 80a8881c8..789fa8dc8 100644
--- a/src/opnsense/mvc/app/views/layouts/default.volt
+++ b/src/opnsense/mvc/app/views/layouts/default.volt
@@ -96,22 +96,9 @@
initFormAdvancedUI();
addMultiSelectClearUI();
- // Create status dialog instance
- let dialog = new BootstrapDialog({
- title: '{{ lang._('System Status')}}',
- buttons: [{
- label: '{{ lang._('Close') }}',
- action: function(dialogRef) {
- dialogRef.close();
- }
- }],
- });
-
+ // artificial delay for UX reasons
setTimeout(function () {
- updateSystemStatus().then((data) => {
- let status = parseStatus(data);
- registerStatusDelegate(dialog, status);
- });
+ updateSystemStatus();
}, 500);
// Register collapsible table headers
@@ -279,6 +266,12 @@
+
+
+
+
diff --git a/src/opnsense/www/js/opnsense_status.js b/src/opnsense/www/js/opnsense_status.js
index 903dadb16..ac1054185 100644
--- a/src/opnsense/www/js/opnsense_status.js
+++ b/src/opnsense/www/js/opnsense_status.js
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2022 Deciso B.V.
+ * Copyright (C) 2022-2024 Deciso B.V.
*
* All rights reserved.
*
@@ -25,53 +25,63 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
-function updateStatusDialog(dialog, status, subjectRef = null) {
- let $ret = $('
');
+function updateStatusDialog(dialog, status) {
+ let $ret = $(`
+
+ `);
+
let $message = $(
'
'
);
- for (let subject in status.data) {
- if (subject === 'System') {
- continue;
- }
- let statusObject = status.data[subject];
- if (status.data[subject].status == "OK") {
- continue;
- }
-
+ for (let [shortname, subject] of Object.entries(status)) {
$message.find('a').last().addClass('__mb');
- let formattedSubject = subject.replace(/([A-Z])/g, ' $1').trim();
- if (status.data[subject].age != undefined) {
- formattedSubject += '
(' + status.data[subject].age + ')';
+ let formattedSubject = subject.title;
+ if (subject.age != undefined) {
+ formattedSubject += '
(' + subject.age + ')';
}
- let listItem = '
' +
- ' ' + formattedSubject +
- '
' +
- '' + statusObject.message + '
';
- let referral = statusObject.logLocation;
+ let ref = subject.logLocation != null ? `href="${subject.logLocation}"` : '';
+ let $closeBtn = `
+
+ `;
+ let $listItem = $(`
+
+
+
+
+ ${formattedSubject}
+ ${subject.persistent ? '' : $closeBtn}
+
+ ${subject.message}
+
+ `)
- $message.find('#opn-status-list').append(listItem);
+ $message.find('#opn-status-list').append($listItem);
- $message.find('#dismiss-' + subject).on('click', function (e) {
+ $message.find('#dismiss-' + shortname).on('click', function (e) {
e.preventDefault();
$.ajax('/api/core/system/dismissStatus', {
type: 'post',
- data: {'subject': subject},
+ data: {'subject': shortname},
dialogRef: dialog,
- subjectRef: subject,
success: function() {
- updateSystemStatus().then((data) => {
- let newStatus = parseStatus(data);
- let $newMessage = updateStatusDialog(this.dialogRef, newStatus, this.subjectRef);
- this.dialogRef.setMessage($newMessage);
- $('#system_status').attr("class", newStatus.data['System'].icon);
- registerStatusDelegate(this.dialogRef, newStatus);
- });
+ updateSystemStatus(this.dialogRef);
}
});
});
@@ -81,49 +91,107 @@ function updateStatusDialog(dialog, status, subjectRef = null) {
return $ret;
}
-function parseStatus(data) {
- let status = {};
- let severity = BootstrapDialog.TYPE_PRIMARY;
- $.each(data, function(subject, statusObject) {
- switch (statusObject.status) {
- case "Error":
- statusObject.icon = 'fa fa-circle text-danger'
- if (subject != 'System') break;
- severity = BootstrapDialog.TYPE_DANGER;
- break;
- case "Warning":
- statusObject.icon = 'fa fa-circle text-warning';
- if (subject != 'System') break;
- severity = BootstrapDialog.TYPE_WARNING;
- break;
- case "Notice":
- statusObject.icon = 'fa fa-circle text-info';
- if (subject != 'System') break;
- severity = BootstrapDialog.TYPE_INFO;
- break;
- default:
- statusObject.icon = 'fa fa-circle text-muted';
- if (subject != 'System') break;
- break;
- }
- $('#system_status').removeClass().addClass(statusObject.icon);
- });
- status.severity = severity;
- status.data = data;
-
- return status;
+function parseStatusIcon(subject) {
+ switch (subject.status) {
+ case "ERROR":
+ subject.icon = 'fa fa-circle text-danger';
+ subject.banner = 'alert-danger';
+ subject.severity = BootstrapDialog.TYPE_DANGER;
+ break;
+ case "WARNING":
+ subject.icon = 'fa fa-circle text-warning';
+ subject.banner ='alert-warning';
+ subject.severity = BootstrapDialog.TYPE_WARNING;
+ break;
+ case "NOTICE":
+ subject.icon = 'fa fa-circle text-info';
+ subject.banner = 'alert-info';
+ subject.severity = BootstrapDialog.TYPE_INFO;
+ break;
+ default:
+ subject.icon = 'fa fa-circle text-muted';
+ subject.banner = 'alert-info';
+ subject.severity = BootstrapDialog.TYPE_PRIMARY;
+ break;
+ }
}
-function registerStatusDelegate(dialog, status) {
- $("#system_status").click(function() {
- dialog.setMessage(function(dialogRef) {
- let $message = updateStatusDialog(dialogRef, status);
- return $message;
+function fetchSystemStatus() {
+ return new Promise((resolve, reject) => {
+ ajaxGet('/api/core/system/status', {}, function (data) {
+ resolve(data);
});
- dialog.open();
});
}
-function updateSystemStatus() {
- return $.ajax('/api/core/system/status', { type: 'get', dataType: 'json' });
+function parseStatus(data) {
+ let system = data.metadata.system;
+
+ // handle initial page load status icon
+ parseStatusIcon(system);
+ $('#system_status').removeClass().addClass(system.icon);
+
+ let notifications = {};
+ let bannerMessages = {};
+ for (let [shortname, subject] of Object.entries(data.subsystems)) {
+ parseStatusIcon(subject);
+
+ if (subject.status == "OK")
+ continue;
+
+ if (subject.persistent) {
+ bannerMessages[shortname] = subject;
+ } else {
+ notifications[shortname] = subject;
+ }
+ }
+
+ return {
+ 'banners': bannerMessages,
+ 'notifications': notifications
+ };
+}
+
+function updateSystemStatus(dialog = null) {
+ fetchSystemStatus().then((data) => {
+ let status = parseStatus(data); // will also update status icon
+
+ if (dialog != null) {
+ dialog.setMessage(function(dialogRef) {
+ return updateStatusDialog(dialogRef, status.notifications);
+ })
+ }
+
+ if (!$.isEmptyObject(status.banners)) {
+ let banner = Object.values(status.banners)[0];
+ $('#notification-banner').addClass(banner.banner).show().html(banner.message);
+ }
+ });
+
+ $("#system_status").click(function() {
+ fetchSystemStatus().then((data) => {
+ let translations = data.metadata.translations;
+ let status = parseStatus(data);
+
+ dialog = new BootstrapDialog({
+ title: translations.dialogTitle,
+ buttons: [{
+ id: 'close',
+ label: translations.dialogCloseButton,
+ action: function(dialogRef) {
+ dialogRef.close();
+ }
+ }],
+ });
+
+ dialog.setMessage(function(dialogRef) {
+ // intentionally do banners first, as these should always show on top
+ // in both cases normal backend sorting applies
+ return updateStatusDialog(dialogRef, {...status.banners, ...status.notifications});
+ })
+
+ dialog.open();
+ });
+ });
+
}
diff --git a/src/www/fbegin.inc b/src/www/fbegin.inc
index 7f6072247..f99e46644 100644
--- a/src/www/fbegin.inc
+++ b/src/www/fbegin.inc
@@ -198,3 +198,8 @@ $aclObj = new \OPNsense\Core\ACL();
+
+
+
diff --git a/src/www/head.inc b/src/www/head.inc
index 4c4fa5460..ae0309e14 100644
--- a/src/www/head.inc
+++ b/src/www/head.inc
@@ -183,22 +183,9 @@ $pagetitle .= html_safe(sprintf(' | %s.%s', $config['system']['hostname'], $conf
$('html,aside').scrollTop(($(this).offset().top - navbar_center));
});
- // Create status dialog instance
- let dialog = new BootstrapDialog({
- title: "= html_safe(gettext('System Status')) ?>",
- buttons: [{
- label: "= html_safe(gettext('Close')) ?>",
- action: function(dialogRef) {
- dialogRef.close();
- }
- }],
- });
-
+ // artifical delay for UX reasons
setTimeout(function () {
- updateSystemStatus().then((data) => {
- let status = parseStatus(data);
- registerStatusDelegate(dialog, status);
- });
+ updateSystemStatus();
}, 500);
// hook in live menu search