From 1f4813c786d90d96c02d6ad86a6105c567fff1b4 Mon Sep 17 00:00:00 2001
From: "MUH. IQRAM BAHRING"
Date: Fri, 7 Aug 2026 08:11:44 +0800
Subject: [PATCH] feat(profile): Check Size Memory button + English geo-probe
hint
New /api/system/memory reports db + data-dir sizes and in-memory state
(fitness/geo registries, freebuff session/cooldown counts); profile page
'Local Mode' card gets a Check Size Memory button rendering the summary.
Geo-probe toggle hint localized to English.
---
open-sse/executors/freebuff.js | 10 +++
src/app/(dashboard)/dashboard/profile/page.js | 43 +++++++++++++
.../dashboard/proxy-fitness/page.js | 2 +-
src/app/api/system/memory/route.js | 62 +++++++++++++++++++
4 files changed, 116 insertions(+), 1 deletion(-)
create mode 100644 src/app/api/system/memory/route.js
diff --git a/open-sse/executors/freebuff.js b/open-sse/executors/freebuff.js
index 891a44a0..8bbf8d7b 100644
--- a/open-sse/executors/freebuff.js
+++ b/open-sse/executors/freebuff.js
@@ -369,6 +369,16 @@ export function resetSessionCache() {
inflight.clear();
}
+// Snapshot sizes of in-memory freebuff state (for the dashboard memory panel).
+export function sessionStateSize() {
+ return {
+ sessions: sessionCache.size,
+ inflight: inflight.size,
+ modelLocks: modelLockCooldowns.size,
+ poolLimits: poolLimitCooldowns.size,
+ };
+}
+
// Periodic sweeper: drop stale session rows + expired cooldowns so long-running
// servers never accumulate state for accounts/models no longer in use.
// Returns how many entries were removed.
diff --git a/src/app/(dashboard)/dashboard/profile/page.js b/src/app/(dashboard)/dashboard/profile/page.js
index 4df8cbef..b28d547e 100644
--- a/src/app/(dashboard)/dashboard/profile/page.js
+++ b/src/app/(dashboard)/dashboard/profile/page.js
@@ -10,6 +10,16 @@ import { APP_CONFIG } from "@/shared/constants/config";
import { LOCALE_COOKIE, normalizeLocale } from "@/i18n/config";
import { LOCALE_FLAGS } from "@/shared/constants/locales";
+function memoryLine(m) {
+ if (!m) return "";
+ const fb = m.inMemory?.freebuff || {};
+ return [
+ `DB: ${m.dbPath || "-"} (${m.dbSizeMB || "-"})`,
+ `Data dir: ${m.dataDirSizeMB || "-"}`,
+ `In-memory: fitness=${m.inMemory?.fitnessPools ?? 0} pool(s) · geo=${m.inMemory?.geoPools ?? 0} pool(s) · freebuff sessions=${fb.sessions ?? 0}, locks=${fb.modelLocks ?? 0}, pool-limits=${fb.poolLimits ?? 0}`,
+ ].join("\n");
+}
+
function getLocaleFromCookie() {
if (typeof document === "undefined") return "en";
const cookie = document.cookie
@@ -49,6 +59,8 @@ export default function ProfilePage() {
const [oidcRedirectUri, setOidcRedirectUri] = useState("/api/auth/oidc/callback");
const [oidcExpanded, setOidcExpanded] = useState(false);
const importFileRef = useRef(null);
+ const [memoryInfo, setMemoryInfo] = useState(null);
+ const [memoryLoading, setMemoryLoading] = useState(false);
const [proxyForm, setProxyForm] = useState({
outboundProxyEnabled: false,
outboundProxyUrl: "",
@@ -563,6 +575,19 @@ export default function ProfilePage() {
setShutdownOpen(false);
};
+ const handleMemoryCheck = async () => {
+ setMemoryLoading(true);
+ try {
+ const res = await fetch("/api/system/memory", { cache: "no-store" });
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ setMemoryInfo(await res.json());
+ } catch (err) {
+ setMemoryInfo({ error: err.message });
+ } finally {
+ setMemoryLoading(false);
+ }
+ };
+
const handleLogout = async () => {
try {
const res = await fetch("/api/auth/logout", { method: "POST" });
@@ -649,6 +674,24 @@ export default function ProfilePage() {
{dbStatus.message}
)}
+
+
+ {memoryInfo && (
+
+ {memoryInfo.error
+ ? `Error: ${memoryInfo.error}`
+ : memoryLine(memoryInfo)}
+
+ )}
+
diff --git a/src/app/(dashboard)/dashboard/proxy-fitness/page.js b/src/app/(dashboard)/dashboard/proxy-fitness/page.js
index 36a71044..cfe4a6e8 100644
--- a/src/app/(dashboard)/dashboard/proxy-fitness/page.js
+++ b/src/app/(dashboard)/dashboard/proxy-fitness/page.js
@@ -253,7 +253,7 @@ export default function ProxyFitnessPage() {
onChange={handleGeoToggle}
disabled={geoUpdating}
label="Geo probe"
- description="Probe egress IP/country per pool ±30 min (pemakaian kuota geo-API)"
+ description="Probe egress IP/country per pool every ~30 min (uses geo-API quota)"
/>
diff --git a/src/app/api/system/memory/route.js b/src/app/api/system/memory/route.js
new file mode 100644
index 00000000..41101d7c
--- /dev/null
+++ b/src/app/api/system/memory/route.js
@@ -0,0 +1,62 @@
+import { NextResponse } from "next/server";
+import fs from "node:fs";
+import path from "node:path";
+import { getDataDir } from "@/lib/dataDir";
+import { DATA_DIR, DATA_FILE } from "@/lib/db/paths";
+import { poolFitnessSnapshot } from "open-sse/services/proxyPoolFitness.js";
+import { poolGeoSnapshot } from "open-sse/services/poolGeo.js";
+
+function formatMB(bytes) {
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
+}
+
+function dirSizeMB(dir, maxDepth = 4) {
+ let total = 0;
+ const walk = (p, depth) => {
+ if (depth > maxDepth) return;
+ let entries = [];
+ try {
+ entries = fs.readdirSync(p, { withFileTypes: true });
+ } catch {
+ return;
+ }
+ for (const e of entries) {
+ const full = path.join(p, e.name);
+ try {
+ if (e.isDirectory()) walk(full, depth + 1);
+ else if (e.isFile()) total += fs.statSync(full).size;
+ } catch { /* skip */ }
+ }
+ };
+ walk(dir, 0);
+ return total;
+}
+
+// GET /api/system/memory — data + in-memory state sizes.
+export async function GET() {
+ try {
+ const dataDir = getDataDir();
+ const dbPath = DATA_FILE;
+ let dbFile = 0;
+ try { dbFile = fs.statSync(dbPath).size; } catch { dbFile = 0; }
+
+ const fitness = poolFitnessSnapshot();
+ const geo = poolGeoSnapshot();
+ const { sessionStateSize } = await import("open-sse/executors/freebuff.js");
+
+ return NextResponse.json({
+ dataDir,
+ dbPath,
+ dbSizeMB: formatMB(dbFile),
+ dataDirSizeMB: formatMB(dirSizeMB(dataDir)),
+ inMemory: {
+ fitnessPools: Object.keys(fitness).length,
+ geoPools: Object.keys(geo).length,
+ freebuff: sessionStateSize(),
+ },
+ });
+ } catch (error) {
+ console.log("Error reading memory sizes:", error);
+ return NextResponse.json({ error: "Failed to read memory sizes" }, { status: 500 });
+ }
+}
\ No newline at end of file