diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/DashboardController.php b/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/DashboardController.php index f418633d7..47446187a 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/DashboardController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/DashboardController.php @@ -71,6 +71,12 @@ class DashboardController extends ApiControllerBase 'trafficin' => gettext('Traffic In'), 'trafficout' => gettext('Traffic Out'), ], + 'memory' => [ + 'title' => gettext('Memory'), + 'used' => gettext('Used'), + 'free' => gettext('Free'), + 'arc' => gettext('ARC'), + ] ]; } 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 89504f520..0320b6c3c 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/SystemController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Core/Api/SystemController.php @@ -260,4 +260,39 @@ class SystemController extends ApiControllerBase return json_encode($response); } + + public function systemResourcesAction() + { + $result = []; + + $mem = json_decode((new Backend())->configdpRun('system sysctl values', implode(',', [ + 'hw.physmem', + 'vm.stats.vm.v_page_count', + 'vm.stats.vm.v_inactive_count', + 'vm.stats.vm.v_cache_count', + 'vm.stats.vm.v_free_count', + 'kstat.zfs.misc.arcstats.size' + ])), true); + + if (!empty($mem['vm.stats.vm.v_page_count'])) { + $pc = $mem['vm.stats.vm.v_page_count']; + $ic = $mem['vm.stats.vm.v_inactive_count']; + $cc = $mem['vm.stats.vm.v_cache_count']; + $fc = $mem['vm.stats.vm.v_free_count']; + $result['memory']['total'] = $mem['hw.physmem']; + $result['memory']['total_frmt'] = sprintf('%d', $mem['hw.physmem'] / 1024 / 1024); + $result['memory']['used'] = round(((($pc - ($ic + $cc + $fc))) / $pc) * $mem['hw.physmem'], 0); + $result['memory']['used_frmt'] = sprintf('%d', $result['memory']['used'] / 1024 / 1024); + if (!empty($mem['kstat.zfs.misc.arcstats.size'])) { + $arc_size = $mem['kstat.zfs.misc.arcstats.size']; + $result['memory']['arc'] = $arc_size; + $result['memory']['arc_frmt'] = sprintf('%d', $arc_size / 1024 / 1024); + $result['memory']['arc_txt'] = sprintf(gettext('ARC size %d MB'), $arc_size / 1024 / 1024); + } + } else { + $result['memory']['used'] = gettext('N/A'); + } + + return json_encode($result); + } } diff --git a/src/opnsense/mvc/app/views/OPNsense/Core/dashboard.volt b/src/opnsense/mvc/app/views/OPNsense/Core/dashboard.volt index 8e8c2e9e1..7f9ee5079 100644 --- a/src/opnsense/mvc/app/views/OPNsense/Core/dashboard.volt +++ b/src/opnsense/mvc/app/views/OPNsense/Core/dashboard.volt @@ -44,7 +44,7 @@ $( document ).ready(function() { let widgetManager = new WidgetManager({ float: false, - column: 4, + column: 5, margin: 10, alwaysShowResizeHandle: false, sizeToContent: true, diff --git a/src/opnsense/www/css/dashboard.css b/src/opnsense/www/css/dashboard.css index 8be60fa6a..0ef067e12 100644 --- a/src/opnsense/www/css/dashboard.css +++ b/src/opnsense/www/css/dashboard.css @@ -172,26 +172,6 @@ td { z-index: 20; } -/* Gauge */ -#gauge-container { - width: 80%; - margin: 5px auto; - background-color: #ccc; - height: 10px; - position: relative; - border-radius: 5px; - overflow: hidden; -} - -#gauge-fill { - height: 100%; - width: 0; - background-color: green; - border-radius: 5px; - transition: width 0.5s, background-color 0.5s; -} - - /* Custom flex table */ div { box-sizing: border-box; diff --git a/src/opnsense/www/js/widgets/BaseWidget.js b/src/opnsense/www/js/widgets/BaseWidget.js index 8fa1cbc64..bf74ce76a 100644 --- a/src/opnsense/www/js/widgets/BaseWidget.js +++ b/src/opnsense/www/js/widgets/BaseWidget.js @@ -53,4 +53,9 @@ export default class BaseWidget { sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } + + _setAlpha(color, opacity) { + const op = Math.round(Math.min(Math.max(opacity || 1, 0), 1) * 255); + return color + op.toString(16).toUpperCase(); + } } diff --git a/src/opnsense/www/js/widgets/InterfaceStatistics.js b/src/opnsense/www/js/widgets/InterfaceStatistics.js index 7568c2a4b..d4aee100e 100644 --- a/src/opnsense/www/js/widgets/InterfaceStatistics.js +++ b/src/opnsense/www/js/widgets/InterfaceStatistics.js @@ -51,11 +51,6 @@ export default class InterfaceStatistics extends BaseWidget { `); } - _setAlpha(color, opacity) { - const op = Math.round(Math.min(Math.max(opacity || 1, 0), 1) * 255); - return color + op.toString(16).toUpperCase(); - } - _getIndexedData(data) { let indexedData = Array(this.labels.length).fill(null); let indexedColors = Array(this.labels.length).fill(null); @@ -133,6 +128,7 @@ export default class InterfaceStatistics extends BaseWidget { layout: { padding: 10 }, + normalized: true, parsing: false, plugins: { legend: { diff --git a/src/opnsense/www/js/widgets/Memory.js b/src/opnsense/www/js/widgets/Memory.js new file mode 100644 index 000000000..eb7831371 --- /dev/null +++ b/src/opnsense/www/js/widgets/Memory.js @@ -0,0 +1,131 @@ +/** + * 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 BaseWidget from "./BaseWidget.js"; + +export default class Memory extends BaseWidget { + constructor() { + super(); + this.tickTimeout = 15000; + + this.chart = null; + this.curMemUsed = null; + this.curMemTotal = null; + } + + getMarkup() { + return $(` +