From 18bd6e3f9abfb7a2d73e5db7dc04de9c427d6dc9 Mon Sep 17 00:00:00 2001 From: c4ch3c4d3 <23181631+c4ch3c4d3@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:12:32 -0600 Subject: [PATCH] feat(webui,dashboard): add RAM Usage card between Disk Usage and Uptime, live-verified on Pager --- .../user/remote_access/pager-webui/server.py | 18 ++++++++++++++++++ .../remote_access/pager-webui/www/js/views.js | 5 ++++- tests/test_status.py | 16 +++++++++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/payload/user/remote_access/pager-webui/server.py b/payload/user/remote_access/pager-webui/server.py index 63746a9..4348be2 100644 --- a/payload/user/remote_access/pager-webui/server.py +++ b/payload/user/remote_access/pager-webui/server.py @@ -3025,6 +3025,23 @@ def disk_data(): return {} +def mem_data(path='/proc/meminfo'): + try: + vals = {} + with open(path) as f: + for line in f: + k, v = line.split(':') + vals[k] = int(v.strip().split()[0]) + total = vals.get('MemTotal', 0) + avail = vals.get('MemAvailable', vals.get('MemFree', 0)) + if not total: + return {} + used = max(total - avail, 0) + return {'size': total * 1024, 'used': used * 1024, 'avail': avail * 1024} + except (OSError, ValueError): + return {} + + def uptime_data(): rc, out, err = device_run(['cat', '/proc/uptime']) try: @@ -3066,6 +3083,7 @@ def status_data(): 'wifi': [wifi_iface_info(n) for n in ifaces], 'clients': assoc_clients(ifaces), 'disk': disk_data(), + 'mem': mem_data(), 'uptime': uptime_data(), 'hostname': hostname_data(), } diff --git a/payload/user/remote_access/pager-webui/www/js/views.js b/payload/user/remote_access/pager-webui/www/js/views.js index ef8137b..38d7a7f 100644 --- a/payload/user/remote_access/pager-webui/www/js/views.js +++ b/payload/user/remote_access/pager-webui/www/js/views.js @@ -192,7 +192,7 @@ views.dashboard = (root) => { root.appendChild(grid); const defs = [ ['clients', 'Clients Connected'], ['handshakes', 'Handshakes Captured'], - ['disk', 'Disk Usage'], ['uptime', 'Uptime'] + ['disk', 'Disk Usage'], ['mem', 'RAM Usage'], ['uptime', 'Uptime'] ]; const cards = {}; defs.forEach(([k, label]) => { @@ -318,6 +318,8 @@ views.dashboard = (root) => { cards.uptime.textContent = s.uptime == null ? 'Unavailable' : fmtDur(s.uptime); cards.disk.textContent = s.disk && s.disk.size != null ? fmtBytes(s.disk.used) + ' / ' + fmtBytes(s.disk.size) : 'Unavailable'; + cards.mem.textContent = s.mem && s.mem.size != null + ? fmtBytes(s.mem.used) + ' / ' + fmtBytes(s.mem.size) : 'Unavailable'; if (typeof MiniChart !== 'undefined') { MiniChart.draw(canvas, [ { label: 'Clients', color: '#1976d2', points: history.clients } @@ -329,6 +331,7 @@ views.dashboard = (root) => { .catch(() => { cards.clients.textContent = '0'; cards.disk.textContent = '—'; + cards.mem.textContent = '—'; cards.uptime.textContent = '—'; }); PagerAPI.get('/api/pineap/handshakes').then((r) => { diff --git a/tests/test_status.py b/tests/test_status.py index 6852519..498d162 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -43,6 +43,20 @@ class StatusTest(unittest.TestCase): d = server.disk_data() self.assertEqual(d['avail'], 7364608 * 1024) + def test_mem_parses_meminfo(self): + import tempfile + base = tempfile.mkdtemp() + with open(os.path.join(base, 'meminfo'), 'w') as f: + f.write('MemTotal: 262144 kB\nMemFree: 102400 kB\nMemAvailable: 122880 kB\nBuffers: 20480 kB\n') + d = server.mem_data(os.path.join(base, 'meminfo')) + self.assertEqual(d['size'], 262144 * 1024) + self.assertEqual(d['used'], (262144 - 122880) * 1024) + self.assertEqual(d['avail'], 122880 * 1024) + + def test_mem_missing_returns_empty(self): + import tempfile + self.assertEqual(server.mem_data(os.path.join(tempfile.mkdtemp(), 'nope')), {}) + def test_wifi_ifaces_extracts_names(self): server.device_run = lambda args, timeout=20: (0, 'wlan0 ESSID: "Pineapple"\nwlan1 ESSID: "Pineapple"\n', '') if args == ['iwinfo'] else (0, '', '') self.assertEqual(server.wifi_ifaces(), ['wlan0', 'wlan1']) @@ -89,7 +103,7 @@ class StatusTest(unittest.TestCase): args = () status, payload = server.h_status(Ctx()) self.assertEqual(status, 200) - for k in ('battery', 'firmware', 'daemon', 'wifi', 'clients', 'disk', 'uptime', 'hostname'): + for k in ('battery', 'firmware', 'daemon', 'wifi', 'clients', 'disk', 'mem', 'uptime', 'hostname'): self.assertIn(k, payload) def test_status_discovers_wifi_interfaces_once(self):