From 3893a4fb9f9b69ade9c94ae7c29914951925af0f Mon Sep 17 00:00:00 2001 From: Keysat Date: Sat, 6 Jun 2026 13:34:18 -0500 Subject: [PATCH] =?UTF-8?q?system-status:=20show=20storage=20usage=20(DB,?= =?UTF-8?q?=20attachments,=20backups,=20disk=20free)=20=E2=80=94=20v0.1.0:?= =?UTF-8?q?63?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /api/system/status now returns a best-effort storage block: database file size (crm.db + WAL + SHM), the email_attachments dir, the backups dir, and disk total/used/free via shutil.disk_usage(DATA_DIR). System Status renders a Storage section with human-readable sizes so growth can be watched over time. Co-Authored-By: Claude Opus 4.8 --- backend/server.py | 32 ++++++++++++++++++++++ frontend/index.html | 34 ++++++++++++++++++++++++ start9/0.4/startos/utils.ts | 5 ++-- start9/0.4/startos/versions/index.ts | 5 ++-- start9/0.4/startos/versions/v0.1.0.63.ts | 15 +++++++++++ 5 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 start9/0.4/startos/versions/v0.1.0.63.ts diff --git a/backend/server.py b/backend/server.py index 56fddd0..3d04070 100644 --- a/backend/server.py +++ b/backend/server.py @@ -3601,6 +3601,38 @@ class CRMHandler(BaseHTTPRequestHandler): } except Exception: out['source_counts'] = None + # Storage usage — DB file(s), email attachments, backups, and disk headroom, + # so growth can be watched over time. Best-effort; never fails the status call. + try: + import shutil + + def _fsize(p): + try: + return os.path.getsize(p) + except OSError: + return 0 + + def _dirsize(d): + total = 0 + for root, _dirs, files in os.walk(d): + for f in files: + try: + total += os.path.getsize(os.path.join(root, f)) + except OSError: + pass + return total + + du = shutil.disk_usage(DATA_DIR) + out['storage'] = { + 'database_bytes': sum(_fsize(DB_PATH + s) for s in ("", "-wal", "-shm")), + 'attachments_bytes': _dirsize(os.path.join(DATA_DIR, "email_attachments")), + 'backups_bytes': _dirsize(os.path.join(DATA_DIR, "backups")), + 'disk_total_bytes': du.total, + 'disk_used_bytes': du.used, + 'disk_free_bytes': du.free, + } + except Exception: + out['storage'] = None conn.close() self.send_json({"data": out}) diff --git a/frontend/index.html b/frontend/index.html index 841d095..4e0f0d7 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -10258,6 +10258,15 @@ if (error) return
{error}
; if (!data) return
No data
; + const fmtBytes = (n) => { + if (n == null) return '—'; + if (n < 1024) return n + ' B'; + const u = ['KB', 'MB', 'GB', 'TB']; let i = -1; let v = n; + do { v /= 1024; i++; } while (v >= 1024 && i < u.length - 1); + return v.toFixed(v < 10 ? 1 : 0) + ' ' + u[i]; + }; + const storage = data.storage; + const entities = data.canonical_entities || {}; const sync = data.last_index_sync; const thesis = data.thesis || {}; @@ -10317,6 +10326,31 @@ )} + {storage && ( +
+
Storage
+
+
+
Database
+
{fmtBytes(storage.database_bytes)}
+
+
+
Email attachments
+
{fmtBytes(storage.attachments_bytes)}
+
+
+
Backups
+
{fmtBytes(storage.backups_bytes)}
+
+
+
Disk free
+
{fmtBytes(storage.disk_free_bytes)}
+
of {fmtBytes(storage.disk_total_bytes)} total
+
+
+
+ )} + {isAdmin && (
Index Actions
diff --git a/start9/0.4/startos/utils.ts b/start9/0.4/startos/utils.ts index 27506ac..c0c2e79 100644 --- a/start9/0.4/startos/utils.ts +++ b/start9/0.4/startos/utils.ts @@ -27,8 +27,9 @@ export const PACKAGE_TITLE = 'Ten31 Database' // * 0.1.0:59 (Email Capture admin panel + matched email into the grounding corpus) // * 0.1.0:60 (Email Capture: single-mailbox enroll field for testing) // * 0.1.0:61 (Email Capture: live backfill progress + auto-refresh) -// * Current: 0.1.0:62 (fix backfill crash on no-Reply-To emails; Sync now retries errored mailboxes) -export const PACKAGE_VERSION = '0.1.0:62' +// * 0.1.0:62 (fix backfill crash on no-Reply-To emails; Sync now retries errored mailboxes) +// * Current: 0.1.0:63 (System Status: storage usage — DB, attachments, backups, disk free) +export const PACKAGE_VERSION = '0.1.0:63' export const DATA_MOUNT_PATH = '/data' export const WEB_PORT = 8080 diff --git a/start9/0.4/startos/versions/index.ts b/start9/0.4/startos/versions/index.ts index 5da9d9d..8401642 100644 --- a/start9/0.4/startos/versions/index.ts +++ b/start9/0.4/startos/versions/index.ts @@ -23,8 +23,9 @@ import { v_0_1_0_59 } from './v0.1.0.59' import { v_0_1_0_60 } from './v0.1.0.60' import { v_0_1_0_61 } from './v0.1.0.61' import { v_0_1_0_62 } from './v0.1.0.62' +import { v_0_1_0_63 } from './v0.1.0.63' export const versionGraph = VersionGraph.of({ - current: v_0_1_0_62, - other: [v_0_1_0_39, v_0_1_0_40, v_0_1_0_41, v_0_1_0_42, v_0_1_0_43, v_0_1_0_44, v_0_1_0_45, v_0_1_0_46, v_0_1_0_47, v_0_1_0_48, v_0_1_0_49, v_0_1_0_50, v_0_1_0_51, v_0_1_0_52, v_0_1_0_53, v_0_1_0_54, v_0_1_0_55, v_0_1_0_56, v_0_1_0_57, v_0_1_0_58, v_0_1_0_59, v_0_1_0_60, v_0_1_0_61], + current: v_0_1_0_63, + other: [v_0_1_0_39, v_0_1_0_40, v_0_1_0_41, v_0_1_0_42, v_0_1_0_43, v_0_1_0_44, v_0_1_0_45, v_0_1_0_46, v_0_1_0_47, v_0_1_0_48, v_0_1_0_49, v_0_1_0_50, v_0_1_0_51, v_0_1_0_52, v_0_1_0_53, v_0_1_0_54, v_0_1_0_55, v_0_1_0_56, v_0_1_0_57, v_0_1_0_58, v_0_1_0_59, v_0_1_0_60, v_0_1_0_61, v_0_1_0_62], }) diff --git a/start9/0.4/startos/versions/v0.1.0.63.ts b/start9/0.4/startos/versions/v0.1.0.63.ts new file mode 100644 index 0000000..5ed1b63 --- /dev/null +++ b/start9/0.4/startos/versions/v0.1.0.63.ts @@ -0,0 +1,15 @@ +import { VersionInfo } from '@start9labs/start-sdk' + +// System Status now shows a Storage section: database file size, email attachments, +// backups, and disk free/total — so growth can be watched over time. Read-only, +// best-effort (never fails the status call). No schema migration. +export const v_0_1_0_63 = VersionInfo.of({ + version: '0.1.0:63', + releaseNotes: { + en_US: [ + 'System Status now shows storage usage: how much space the database, email attachments,', + 'and backups are using, plus free disk on the server, so you can watch it grow over time.', + ].join(' '), + }, + migrations: { up: async () => {}, down: async () => {} }, +})