mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-13 00:07:26 +00:00
system: adjust for overrides banner
Introduces the isBanner property, which explicitly defines the message as a banner, which doesn't necessarily have a relation to the persistent property. While here, update the UI to remove cursor events when the message doesn't have a location set.
This commit is contained in:
parent
1850661335
commit
fd39bafe72
1
plist
1
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
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -38,6 +38,7 @@ class LiveMediaStatus extends AbstractStatus
|
||||
{
|
||||
$this->internalPriority = 2;
|
||||
$this->internalPersistent = true;
|
||||
$this->internalIsBanner = true;
|
||||
$this->internalTitle = gettext('Live Media');
|
||||
|
||||
/*
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2025 Deciso B.V.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
namespace OPNsense\System\Status;
|
||||
|
||||
use OPNsense\System\AbstractStatus;
|
||||
use OPNsense\System\SystemStatusCode;
|
||||
|
||||
class OpensshOverrideStatus extends AbstractStatus
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 */
|
||||
|
||||
@ -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(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -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 = $(`
|
||||
<div>
|
||||
<a data-dismiss="modal" class="btn btn-default" style="width:100%; text-align: left;" href="#">
|
||||
<h4>
|
||||
<span class="fa fa-circle text-muted">
|
||||
</span>
|
||||
|
||||
System
|
||||
</h4>
|
||||
<p>No pending messages.</p>
|
||||
</a>
|
||||
</div>
|
||||
`);
|
||||
class Status {
|
||||
constructor() {
|
||||
this.observers = [];
|
||||
this.data = null;
|
||||
}
|
||||
|
||||
let $message = $(
|
||||
'<div>' +
|
||||
'<div id="opn-status-list"></div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
for (let [shortname, subject] of Object.entries(status)) {
|
||||
$message.find('a').last().addClass('__mb');
|
||||
|
||||
let formattedSubject = subject.title;
|
||||
if (subject.age != undefined) {
|
||||
formattedSubject += ' <small>(' + subject.age + ')</small>';
|
||||
}
|
||||
|
||||
let ref = subject.logLocation != null ? `href="${subject.logLocation}"` : '';
|
||||
let $closeBtn = `
|
||||
<button id="dismiss-${shortname}" class="close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
`;
|
||||
let $listItem = $(`
|
||||
<a class="btn btn-default" style="width:100%; text-align: left;" ${ref}>
|
||||
<h4>
|
||||
<span class="${subject.icon}"></span>
|
||||
|
||||
${formattedSubject}
|
||||
${subject.persistent ? '' : $closeBtn}
|
||||
</h4>
|
||||
<p style="white-space: pre-wrap;">${subject.message}</p>
|
||||
</a>
|
||||
`)
|
||||
|
||||
$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($(`
|
||||
<div class="container-fluid">
|
||||
<div id="notification-banner" class="alert alert-info ${banner.banner}"
|
||||
style="padding: 10px; text-align: center;">
|
||||
${banner.message}
|
||||
</div>
|
||||
_setDialogContent(status) {
|
||||
this.dialog.setMessage((dialog) => {
|
||||
let $ret = $(`
|
||||
<div>
|
||||
<a data-dismiss="modal" class="btn btn-default" style="width:100%; text-align: left;" href="#">
|
||||
<h4>
|
||||
<span class="fa fa-circle text-muted">
|
||||
</span>
|
||||
|
||||
System
|
||||
</h4>
|
||||
<p>No pending messages.</p>
|
||||
</a>
|
||||
</div>
|
||||
`));
|
||||
`);
|
||||
|
||||
}
|
||||
});
|
||||
let $message = $(
|
||||
'<div>' +
|
||||
'<div id="opn-status-list"></div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
$("#system_status").click(function() {
|
||||
fetchSystemStatus().then((data) => {
|
||||
let translations = data.metadata.translations;
|
||||
let status = parseStatus(data);
|
||||
for (let [shortname, subject] of Object.entries(status.subsystems)) {
|
||||
if (subject.status == "OK")
|
||||
continue;
|
||||
|
||||
dialog = new BootstrapDialog({
|
||||
title: translations.dialogTitle,
|
||||
buttons: [{
|
||||
id: 'close',
|
||||
label: translations.dialogCloseButton,
|
||||
action: function(dialogRef) {
|
||||
dialogRef.close();
|
||||
}
|
||||
}],
|
||||
});
|
||||
$message.find('a').last().addClass('__mb');
|
||||
|
||||
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});
|
||||
})
|
||||
let formattedSubject = subject.title;
|
||||
if (subject.age != undefined) {
|
||||
formattedSubject += ' <small>(' + subject.age + ')</small>';
|
||||
}
|
||||
|
||||
dialog.open();
|
||||
let ref = subject.location != null ? `href="${subject.location}"` : '';
|
||||
let hoverStyle = subject.location == null ? 'cursor: default; pointer-events: none;' : '';
|
||||
|
||||
let $closeBtn = `
|
||||
<button id="dismiss-${shortname}" class="close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
let $listItem = $(`
|
||||
<a class="btn btn-default" style="width:100%; text-align: left; ${hoverStyle}" ${ref}>
|
||||
<h4>
|
||||
<span class="${(new StatusIcon()).asClass(subject.status)}"></span>
|
||||
|
||||
${formattedSubject}
|
||||
${subject.persistent ? '' : $closeBtn}
|
||||
</h4>
|
||||
<p style="white-space: pre-wrap;">${subject.message}</p>
|
||||
</a>
|
||||
`)
|
||||
|
||||
$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($(`
|
||||
<div class="container-fluid">
|
||||
<div id="notification-banner" class="alert alert-info ${this.parseStatusBanner(subject.status)}"
|
||||
style="padding: 10px; text-align: center;">
|
||||
${subject.message}
|
||||
</div>
|
||||
</div>
|
||||
`));
|
||||
this.bannerActive = true;
|
||||
break;
|
||||
} else {
|
||||
$('#notification-banner').text(subject.message);
|
||||
$('#notification-banner').removeClass().addClass(`alert alert-info ${this.parseStatusBanner(subject.status)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parseStatusBanner(statusCode) {
|
||||
switch (statusCode) {
|
||||
case "ERROR":
|
||||
return 'alert-danger';
|
||||
case "WARNING":
|
||||
return 'alert-warning';
|
||||
default:
|
||||
return 'alert-info';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const statusObj = new Status();
|
||||
|
||||
function updateSystemStatus() {
|
||||
statusObj.attach(new StatusIcon());
|
||||
statusObj.attach(new StatusDialog());
|
||||
statusObj.attach(new StatusBanner());
|
||||
|
||||
statusObj.updateStatus();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user