diff --git a/plist b/plist index 9c3735674..78452bdbb 100644 --- a/plist +++ b/plist @@ -575,6 +575,7 @@ /usr/local/opnsense/mvc/app/library/OPNsense/System/Status/DiskSpaceStatus.php /usr/local/opnsense/mvc/app/library/OPNsense/System/Status/FirewallStatus.php /usr/local/opnsense/mvc/app/library/OPNsense/System/Status/LiveMediaStatus.php +/usr/local/opnsense/mvc/app/library/OPNsense/System/Status/OpensshOverrideStatus.php /usr/local/opnsense/mvc/app/library/OPNsense/System/Status/SystemBootingStatus.php /usr/local/opnsense/mvc/app/library/OPNsense/System/SystemStatus.php /usr/local/opnsense/mvc/app/library/OPNsense/System/SystemStatusCode.php 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 a9dabb3c2..97efbf0c3 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/SystemController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/SystemController.php @@ -75,8 +75,8 @@ class SystemController extends ApiControllerBase foreach ($statuses as $subsystem => $status) { $statuses[$subsystem]['status'] = $order[$status['statusCode']]; - if (!empty($status['logLocation'])) { - if (!$acl->isPageAccessible($this->getUserName(), $status['logLocation'])) { + if (!empty($status['location'])) { + if (!$acl->isPageAccessible($this->getUserName(), $status['location'])) { unset($statuses[$subsystem]); continue; } @@ -181,8 +181,8 @@ class SystemController extends ApiControllerBase $subsystem = $this->request->getPost("subject"); $system = json_decode(trim($backend->configdRun('system status')), true); if (array_key_exists($subsystem, $system)) { - if (!empty($system[$subsystem]['logLocation'])) { - $aclCheck = $system[$subsystem]['logLocation']; + if (!empty($system[$subsystem]['location'])) { + $aclCheck = $system[$subsystem]['location']; if ( $acl->isPageAccessible($this->getUserName(), $aclCheck) || !$acl->hasPrivilege($this->getUserName(), 'user-config-readonly') diff --git a/src/opnsense/mvc/app/library/OPNsense/System/AbstractStatus.php b/src/opnsense/mvc/app/library/OPNsense/System/AbstractStatus.php index d91a38e45..7fc742a03 100644 --- a/src/opnsense/mvc/app/library/OPNsense/System/AbstractStatus.php +++ b/src/opnsense/mvc/app/library/OPNsense/System/AbstractStatus.php @@ -32,9 +32,10 @@ abstract class AbstractStatus { protected $internalPriority = 100; protected $internalPersistent = false; + protected $internalIsBanner = false; protected $internalTitle = null; protected $internalMessage = null; - protected $internalLogLocation = null; + protected $internalLocation = null; protected $internalStatus = SystemStatusCode::OK; protected $internalTimestamp = null; @@ -48,6 +49,11 @@ abstract class AbstractStatus return $this->internalPersistent; } + public function isBanner() + { + return $this->internalIsBanner; + } + public function getTitle() { return $this->internalTitle; @@ -63,9 +69,9 @@ abstract class AbstractStatus return $this->internalMessage ?? gettext('No problems were detected.'); } - public function getLogLocation() + public function getLocation() { - return $this->internalLogLocation; + return $this->internalLocation; } public function getTimestamp() 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 47d554d0d..e75cc8275 100644 --- a/src/opnsense/mvc/app/library/OPNsense/System/Status/CrashReporterStatus.php +++ b/src/opnsense/mvc/app/library/OPNsense/System/Status/CrashReporterStatus.php @@ -41,7 +41,7 @@ class CrashReporterStatus extends AbstractStatus $this->internalPriority = 10; $this->internalTitle = gettext('Crash Reporter'); - $this->internalLogLocation = '/crash_reporter.php'; + $this->internalLocation = '/crash_reporter.php'; $src_errors = count($src_logs) > 0; if ($src_errors) { 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 0fd4f6007..8743f9d93 100644 --- a/src/opnsense/mvc/app/library/OPNsense/System/Status/FirewallStatus.php +++ b/src/opnsense/mvc/app/library/OPNsense/System/Status/FirewallStatus.php @@ -39,7 +39,7 @@ class FirewallStatus extends AbstractStatus { $this->internalPriority = 20; $this->internalTitle = gettext('Firewall'); - $this->internalLogLocation = '/ui/diagnostics/log/core/firewall'; + $this->internalLocation = '/ui/diagnostics/log/core/firewall'; if (file_exists($this->rules_error)) { $this->internalMessage = file_get_contents($this->rules_error); /* XXX */ 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 cdab2c0e8..37075c7b0 100644 --- a/src/opnsense/mvc/app/library/OPNsense/System/Status/LiveMediaStatus.php +++ b/src/opnsense/mvc/app/library/OPNsense/System/Status/LiveMediaStatus.php @@ -38,6 +38,7 @@ class LiveMediaStatus extends AbstractStatus { $this->internalPriority = 2; $this->internalPersistent = true; + $this->internalIsBanner = true; $this->internalTitle = gettext('Live Media'); /* diff --git a/src/opnsense/mvc/app/library/OPNsense/System/Status/OpensshOverrideStatus.php b/src/opnsense/mvc/app/library/OPNsense/System/Status/OpensshOverrideStatus.php new file mode 100644 index 000000000..484656619 --- /dev/null +++ b/src/opnsense/mvc/app/library/OPNsense/System/Status/OpensshOverrideStatus.php @@ -0,0 +1,48 @@ +internalPriority = 2; + $this->internalPersistent = true; + $this->internalTitle = gettext('OpenSSH config override'); + $this->internalLocation = '/system_advanced_admin.php'; + + if (count(glob('/usr/local/etc/ssh/sshd_config.d/*.conf'))) { + $this->internalMessage = gettext('The OpenSSH GUI configuration may be overridden by currently provided files on the disk.'); + $this->internalStatus = SystemStatusCode::NOTICE; + } + } +} 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 25de9ea10..ee6c069be 100644 --- a/src/opnsense/mvc/app/library/OPNsense/System/Status/SystemBootingStatus.php +++ b/src/opnsense/mvc/app/library/OPNsense/System/Status/SystemBootingStatus.php @@ -37,6 +37,7 @@ class SystemBootingStatus extends AbstractStatus { $this->internalPriority = 1; $this->internalPersistent = true; + $this->internalIsBanner = true; $this->internalTitle = gettext('System Booting'); /* XXX boot detection from final class product in config.inc */ diff --git a/src/opnsense/mvc/app/library/OPNsense/System/SystemStatus.php b/src/opnsense/mvc/app/library/OPNsense/System/SystemStatus.php index 9457b5b77..453c822ad 100644 --- a/src/opnsense/mvc/app/library/OPNsense/System/SystemStatus.php +++ b/src/opnsense/mvc/app/library/OPNsense/System/SystemStatus.php @@ -75,9 +75,10 @@ class SystemStatus 'title' => $obj->getTitle(), 'statusCode' => $obj->getStatus(), 'message' => $obj->getMessage(), - 'logLocation' => $obj->getLogLocation(), + 'location' => $obj->getLocation(), 'timestamp' => $obj->getTimestamp(), 'persistent' => $obj->getPersistent(), + 'isBanner' => $obj->isBanner(), 'priority' => $obj->getPriority(), ]; } diff --git a/src/opnsense/www/js/opnsense_status.js b/src/opnsense/www/js/opnsense_status.js index 2362f0859..cb01516b6 100644 --- a/src/opnsense/www/js/opnsense_status.js +++ b/src/opnsense/www/js/opnsense_status.js @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022-2024 Deciso B.V. + * Copyright (C) 2022-2025 Deciso B.V. * * All rights reserved. * @@ -25,181 +25,224 @@ * POSSIBILITY OF SUCH DAMAGE. */ -function updateStatusDialog(dialog, status) { - let $ret = $(` -
- `); +class Status { + constructor() { + this.observers = []; + this.data = null; + } - let $message = $( - '${subject.message}
- - `) - - $message.find('#opn-status-list').append($listItem); - - $message.find('#dismiss-' + shortname).on('click', function (e) { - e.preventDefault(); - $.ajax('/api/core/system/dismissStatus', { - type: 'post', - data: {'subject': shortname}, - dialogRef: dialog, - success: function() { - updateSystemStatus(this.dialogRef); - } + updateStatus() { + const fetch = new Promise((resolve, reject) => { + ajaxGet('/api/core/system/status', {}, function (data) { + resolve(data); }); }); - $ret = $message; - } - return $ret; -} - -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 fetchSystemStatus() { - return new Promise((resolve, reject) => { - ajaxGet('/api/core/system/status', {}, function (data) { - resolve(data); + fetch.then((data) => { + this.notify(data); }); - }); + } + + attach(observer) { + this.observers.push(observer); + } + + notify(data) { + this.observers.forEach(observer => observer.update(data)); + } } -function parseStatus(data) { - let system = data.metadata.system; +class StatusIcon { + update(status) { + const icon = this._parseStatusIcon(status.metadata.system.status); + $('#system_status').removeClass().addClass(icon); + } - // handle initial page load status icon - parseStatusIcon(system); - $('#system_status').removeClass().addClass(system.icon); + _parseStatusIcon(statusCode) { + switch (statusCode) { + case "ERROR": + return 'fa fa-circle text-danger'; + case "WARNING": + return 'fa fa-circle text-warning'; + case "NOTICE": + return 'fa fa-circle text-info'; + default: + return 'fa fa-circle text-muted'; + } + } - let notifications = {}; - let bannerMessages = {}; - for (let [shortname, subject] of Object.entries(data.subsystems)) { - parseStatusIcon(subject); + asClass(statusCode) { + return this._parseStatusIcon(statusCode); + } +} - if (subject.status == "OK") - continue; +class StatusDialog { + constructor() { + this.clickHandlerRegistered = false; + this.dialogOpen = false; + this.currentStatus = null; + this.dialog = null; + } - if (subject.persistent) { - bannerMessages[shortname] = subject; + update(status) { + this.currentStatus = status; + if (!this.clickHandlerRegistered) { + this.clickHandlerRegistered = true; + const translations = status.metadata.translations; + $('#system_status').click(() => { + this.dialog = new BootstrapDialog({ + title: translations.dialogTitle, + draggable: true, + buttons: [{ + id: 'close', + label: translations.dialogCloseButton, + action: (dialogRef) => { + dialogRef.close(); + this.dialogOpen = false; + } + }], + }); + + this._setDialogContent(this.currentStatus); + + this.dialog.open(); + this.dialogOpen = true; + }); } else { - notifications[shortname] = subject; + this._setDialogContent(this.currentStatus); + + if (!this.dialogOpen) { + this.dialog.open(); + this.dialogOpen = true; + } } } - 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]; - $('.page-content-main > .container-fluid > .row').prepend($(` -${subject.message}
+ + `) + + $message.find('#opn-status-list').append($listItem); + + $message.find('#dismiss-' + shortname).on('click', function (e) { + e.preventDefault(); + $.ajax('/api/core/system/dismissStatus', { + type: 'post', + data: {'subject': shortname}, + dialogRef: dialog, + itemRef: $listItem, + success: function() { + statusObj.updateStatus(); + } + }); + }); + + $ret = $message; + } + + return $ret; }); - }); - + } +} + +class StatusBanner { + constructor() { + this.bannerActive = false; + } + + update(status) { + for (let [name, subject] of Object.entries(status.subsystems)) { + if (subject.status == "OK") + continue; + + if (subject.isBanner && !this.bannerActive) { + if (!this.bannerActive) { + $('.page-content-main > .container-fluid > .row').prepend($(` +