mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-18 02:25:05 +00:00
dashboard: system information widget
Minor restructuring of the BaseTableWidget as well
This commit is contained in:
parent
dbd1800584
commit
d267e33de4
1
plist
1
plist
@ -1930,6 +1930,7 @@
|
||||
/usr/local/opnsense/www/js/widgets/BaseWidget.js
|
||||
/usr/local/opnsense/www/js/widgets/Cpu.js
|
||||
/usr/local/opnsense/www/js/widgets/Interfaces.js
|
||||
/usr/local/opnsense/www/js/widgets/SystemInformation.js
|
||||
/usr/local/opnsense/www/themes/opnsense/LICENSE
|
||||
/usr/local/opnsense/www/themes/opnsense/assets/fonts/SourceSansPro-Bold/SourceSansPro-Bold.eot
|
||||
/usr/local/opnsense/www/themes/opnsense/assets/fonts/SourceSansPro-Bold/SourceSansPro-Bold.otf
|
||||
|
||||
@ -46,6 +46,15 @@ class DashboardController extends ApiControllerBase
|
||||
],
|
||||
'interfaces' => [
|
||||
'title' => gettext('Interfaces'),
|
||||
],
|
||||
'systeminformation' => [
|
||||
'title' => gettext('System Information'),
|
||||
'name' => gettext('Name'),
|
||||
'versions' => gettext('Versions'),
|
||||
'updates' => gettext('Updates'),
|
||||
'datetime' => gettext('Current date/time'),
|
||||
'uptime' => gettext('Uptime'),
|
||||
'config' => gettext('Last configuration change')
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ namespace OPNsense\Core\Api;
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\Core\ACL;
|
||||
use OPNsense\Core\Backend;
|
||||
use OPNsense\Core\Config;
|
||||
|
||||
/**
|
||||
* Class SystemController
|
||||
@ -40,6 +41,24 @@ use OPNsense\Core\Backend;
|
||||
*/
|
||||
class SystemController extends ApiControllerBase
|
||||
{
|
||||
private function formatUptime($uptime)
|
||||
{
|
||||
$days = floor($uptime / (3600 * 24));
|
||||
$hours = floor(($uptime % (3600 * 24)) / 3600);
|
||||
$minutes = floor(($uptime % 3600) / 60);
|
||||
$seconds = $uptime % 60;
|
||||
|
||||
if ($days > 0) {
|
||||
$plural = $days > 1 ? gettext("days") : gettext("day");
|
||||
return sprintf(
|
||||
"%d %s, %02d:%02d:%02d",
|
||||
$days, $plural, $hours, $minutes, $seconds
|
||||
);
|
||||
} else {
|
||||
return sprintf("%02d:%02d:%02d", $hours, $minutes, $seconds);
|
||||
}
|
||||
}
|
||||
|
||||
public function haltAction()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
@ -185,4 +204,56 @@ class SystemController extends ApiControllerBase
|
||||
|
||||
return ["status" => "failed"];
|
||||
}
|
||||
|
||||
public function systemInformationAction()
|
||||
{
|
||||
$config = Config::getInstance()->object();
|
||||
$backend = new Backend();
|
||||
|
||||
$product = json_decode($backend->configdRun('firmware product'), true);
|
||||
$current = explode('_', $product['product_version'])[0];
|
||||
/* information from changelog, more accurate for production release */
|
||||
$from_changelog = strpos($product['product_id'], '-devel') === false &&
|
||||
!empty($product['product_latest']) &&
|
||||
$product['product_latest'] != $current;
|
||||
|
||||
/* update status from last check, also includes major releases */
|
||||
$from_check = !empty($product['product_check']['upgrade_sets']) ||
|
||||
!empty($product['product_check']['downgrade_packages']) ||
|
||||
!empty($product['product_check']['new_packages']) ||
|
||||
!empty($product['product_check']['reinstall_packages']) ||
|
||||
!empty($product['product_check']['remove_packages']) ||
|
||||
!empty($product['product_check']['upgrade_packages']);
|
||||
|
||||
$response = [
|
||||
'name' => $config->system->hostname . '.' . $config->system->domain,
|
||||
'versions' => [
|
||||
sprintf('%s %s-%s', $product['product_name'], $product['product_version'], $product['product_arch']),
|
||||
php_uname('s') . ' ' . php_uname('r'),
|
||||
trim($backend->configdRun('system openssl version'))
|
||||
],
|
||||
'updates' => ($from_changelog || $from_check)
|
||||
? gettext('Click to view pending updates.')
|
||||
: gettext('Click to check for updates.'),
|
||||
];
|
||||
|
||||
return json_encode($response);
|
||||
}
|
||||
|
||||
public function systemTimeAction()
|
||||
{
|
||||
$config = Config::getInstance()->object();
|
||||
$boottime = json_decode((new Backend())->configdRun('system sysctl values kern.boottime'), true);
|
||||
preg_match("/sec = (\d+)/", $boottime['kern.boottime'], $matches);
|
||||
|
||||
$last_change = date("D M j G:i:s T Y", !empty($config->revision->time) ? intval($config->revision->time) : 0);
|
||||
|
||||
$response = [
|
||||
'uptime' => $this->formatUptime(time() - $matches[1]),
|
||||
'datetime' => date("D M j G:i:s T Y"),
|
||||
'config' => $last_change,
|
||||
];
|
||||
|
||||
return json_encode($response);
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,6 +32,8 @@
|
||||
<!-- gridstack core -->
|
||||
<script src="{{ cache_safe('/ui/js/gridstack-all.min.js') }}"></script>
|
||||
|
||||
<script src="{{ cache_safe('/ui/js/moment-with-locales.min.js') }}"></script>
|
||||
|
||||
<script src="{{ cache_safe('/ui/js/opnsense_widget_manager.js') }}"></script>
|
||||
<link rel="stylesheet" type="text/css" href="{{ cache_safe(theme_file_or_default('/css/dashboard.css', theme_name)) }}" rel="stylesheet" />
|
||||
|
||||
|
||||
@ -114,3 +114,9 @@ command:/usr/local/opnsense/scripts/system/cpu.py
|
||||
parameters:--interval %s
|
||||
type:stream_output
|
||||
message:Stream CPU stats
|
||||
|
||||
[openssl.version]
|
||||
command:/usr/local/bin/openssl version | cut -f -2 -d ' '
|
||||
parameters:
|
||||
type:script_output
|
||||
message:Show OpenSSL version
|
||||
|
||||
@ -20,6 +20,7 @@ export default class BaseTableWidget extends BaseWidget {
|
||||
'.flextable-row': {'padding': '0.5em 0.5em'},
|
||||
'.header .flex-row': {'border-bottom': ''},
|
||||
'.flex-row': {'width': this._calculateColumnWidth.bind(this)},
|
||||
'.column .flex-row': {'width': '100%'},
|
||||
'.column': {'width': ''},
|
||||
'.flex-cell': {'width': ''},
|
||||
}
|
||||
@ -47,11 +48,26 @@ export default class BaseTableWidget extends BaseWidget {
|
||||
return '';
|
||||
}
|
||||
|
||||
_constructTable() {
|
||||
if (this.options === null) {
|
||||
console.error('No table options set');
|
||||
return null;
|
||||
}
|
||||
|
||||
this.$flextable = $(`<div class="flextable-container" id="${this.flextableId}" role="table"></div>`)
|
||||
|
||||
if (this.options.headerPosition === 'top') {
|
||||
this.$headerContainer = $(`<div class="flextable-header"></div>`);
|
||||
this.$flextable.append(this.$headerContainer);
|
||||
}
|
||||
}
|
||||
|
||||
setTableOptions(options = {}) {
|
||||
/**
|
||||
* headerPosition: top, left or none.
|
||||
* top: headers are on top of the table. Data layout: [{header1: value1}, {header1: value2}, ...]
|
||||
* left: headers are on the left of the table (key-value). Data layout: [{header1: value1, header2: value2}, ...]
|
||||
* left: headers are on the left of the table (key-value). Data layout: [{header1: value1}, {header2: value2}, ...].
|
||||
* Supports nested columns (e.g. {header1: [value1, value2...]})
|
||||
* none: no headers. Data layout: [[value1, value2], ...]
|
||||
*/
|
||||
this.options = {
|
||||
@ -60,15 +76,12 @@ export default class BaseTableWidget extends BaseWidget {
|
||||
}
|
||||
}
|
||||
|
||||
updateTable(data = [], clear = true) {
|
||||
updateTable(data = []) {
|
||||
let $table = $(`#${this.flextableId}`);
|
||||
|
||||
if (clear) {
|
||||
$table.children('.flextable-row').remove();
|
||||
}
|
||||
$table.children('.flextable-row').remove();
|
||||
|
||||
for (const row of data) {
|
||||
this.data.push(row);
|
||||
let rowType = Array.isArray(row) && row !== null ? 'flat' : 'nested';
|
||||
if (rowType === 'flat' && this.options.headerPosition !== 'none') {
|
||||
console.error('Flat data is not supported with headers');
|
||||
@ -80,16 +93,17 @@ export default class BaseTableWidget extends BaseWidget {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (rowType === 'flat') {
|
||||
let $row = $(`<div class="flextable-row"></div>`)
|
||||
for (const item of row) {
|
||||
$row.append($(`
|
||||
<div class="flex-row" role="cell">${item}</div>
|
||||
`));
|
||||
}
|
||||
$table.append($row);
|
||||
} else {
|
||||
if (this.options.headerPosition === 'top') {
|
||||
switch (this.options.headerPosition) {
|
||||
case "none":
|
||||
let $row = $(`<div class="flextable-row"></div>`)
|
||||
for (const item of row) {
|
||||
$row.append($(`
|
||||
<div class="flex-row" role="cell">${item}</div>
|
||||
`));
|
||||
}
|
||||
$table.append($row);
|
||||
break;
|
||||
case "top":
|
||||
let $flextableRow = $(`<div class="flextable-row"></div>`);
|
||||
for (const [h, c] of Object.entries(row)) {
|
||||
if (!this.headers.has(h)) {
|
||||
@ -115,49 +129,35 @@ export default class BaseTableWidget extends BaseWidget {
|
||||
}
|
||||
}
|
||||
$table.append($flextableRow);
|
||||
} else if (this.options.headerPosition === 'left') {
|
||||
break;
|
||||
case "left":
|
||||
for (const [h, c] of Object.entries(row)) {
|
||||
if (Array.isArray(c)) {
|
||||
// nested column
|
||||
let $row = $('<div class="flextable-row" role="rowgroup"></div>');
|
||||
let $row = $('<div class="flextable-row"></div>');
|
||||
$row.append($(`
|
||||
<div class="flex-row rowspan first" role="cell"><b>${h}</b></div>
|
||||
<div class="flex-row rowspan first"><b>${h}</b></div>
|
||||
`));
|
||||
let $column = $('<div class="column"></div>');
|
||||
for (const item of c) {
|
||||
$column.append($(`
|
||||
<div class="flex-row">
|
||||
<div class="flex-cell" role="cell">${item}</div>
|
||||
<div class="flex-cell">${item}</div>
|
||||
</div>
|
||||
`));
|
||||
}
|
||||
$table.append($row.append($column));
|
||||
} else {
|
||||
$table.append($(`
|
||||
<div class="flextable-row" role="rowgroup">
|
||||
<div class="flex-row first" role="cell"><b>${h}</b></div>
|
||||
<div class="flex-row" role="cell">${c}</div>
|
||||
<div class="flextable-row">
|
||||
<div class="flex-row first"><b>${h}</b></div>
|
||||
<div class="flex-row">${c}</div>
|
||||
</div>
|
||||
`));
|
||||
`));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
_constructTable() {
|
||||
if (this.options === null) {
|
||||
console.error('No table options set');
|
||||
return null;
|
||||
}
|
||||
|
||||
this.$flextable = $(`<div class="flextable-container" id="${this.flextableId}" role="table"></div>`)
|
||||
|
||||
if (this.options.headerPosition === 'top') {
|
||||
this.$headerContainer = $(`<div class="flextable-header"></div>`);
|
||||
this.$flextable.append(this.$headerContainer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
75
src/opnsense/www/js/widgets/SystemInformation.js
Normal file
75
src/opnsense/www/js/widgets/SystemInformation.js
Normal file
@ -0,0 +1,75 @@
|
||||
// endpoint:/api/core/system/systemInformation
|
||||
|
||||
/**
|
||||
* Copyright (C) 2024 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.
|
||||
*/
|
||||
|
||||
import BaseTableWidget from "./BaseTableWidget.js";
|
||||
|
||||
export default class SystemInformation extends BaseTableWidget {
|
||||
constructor() {
|
||||
super();
|
||||
this.tickTimeout = 1000;
|
||||
}
|
||||
|
||||
getMarkup() {
|
||||
super.setTableOptions({
|
||||
headerPosition: 'left'
|
||||
});
|
||||
return super.getMarkup();
|
||||
}
|
||||
|
||||
async onWidgetTick() {
|
||||
await ajaxGet('/api/core/system/systemTime', {}, (data, status) => {
|
||||
$('#datetime').text(data['datetime']);
|
||||
$('#uptime').text(data['uptime']);
|
||||
$('#config').text(data['config']);
|
||||
});
|
||||
}
|
||||
|
||||
async onMarkupRendered() {
|
||||
await ajaxGet('/api/core/system/systemInformation', {}, (data, status) => {
|
||||
let rows = [];
|
||||
for (let [key, value] of Object.entries(data)) {
|
||||
if (!key in this.translations) {
|
||||
console.error('Missing translation for ' + key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === 'updates') {
|
||||
value = $('<a>').attr('href', '/ui/core/firmware#checkupdate').text(value).prop('outerHTML');
|
||||
}
|
||||
|
||||
rows.push({[this.translations[key]]: value});
|
||||
}
|
||||
|
||||
rows.push({[this.translations['uptime']]: $('<span id="uptime">').prop('outerHTML')});
|
||||
rows.push({[this.translations['datetime']]: $('<span id="datetime">').prop('outerHTML')});
|
||||
rows.push({[this.translations['config']]: $('<span id="config">').prop('outerHTML')});
|
||||
super.updateTable(rows);
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user