feat: add memory debug to gui

This commit is contained in:
Lloyd
2026-04-20 15:41:27 +01:00
parent 5eb1fc47ca
commit 68a461b965
28 changed files with 82 additions and 56 deletions
+51 -25
View File
@@ -1199,28 +1199,63 @@ class APIEndpoints:
@cherrypy.expose
@cherrypy.tools.json_out()
def memory_debug(self):
"""Diagnostic endpoint: show what is actually holding memory.
@cherrypy.tools.json_in()
def memory_debug(self, **kwargs):
"""Memory diagnostics endpoint.
First call starts tracemalloc and takes a baseline snapshot.
Subsequent calls compare against the baseline and return the top
allocations that have *grown* since startup — i.e. the real leaks.
GET — returns current status + data if tracing is active.
POST {"action": "start"} — starts tracemalloc and captures baseline.
POST {"action": "stop"} — stops tracemalloc and clears data.
"""
import tracemalloc
if not tracemalloc.is_tracing():
tracemalloc.start(10)
# Store baseline snapshot for future diffs
self._tracemalloc_baseline = tracemalloc.take_snapshot()
return self._success({
"status": "started",
"message": "tracemalloc started — call again after some time to see growth",
})
self._set_cors_headers()
if cherrypy.request.method == "OPTIONS":
return ""
# ---------- POST: start / stop ----------
if cherrypy.request.method == "POST":
data = cherrypy.request.json or {}
action = data.get("action")
if action == "start":
if not tracemalloc.is_tracing():
tracemalloc.start(10)
self._tracemalloc_baseline = tracemalloc.take_snapshot()
logger.info("Memory tracing started")
return self._success({
"tracing": True,
"message": "Tracing started — check again after some time to see growth",
})
if action == "stop":
if tracemalloc.is_tracing():
tracemalloc.stop()
self._tracemalloc_baseline = None
logger.info("Memory tracing stopped")
return self._success({"tracing": False})
return self._error("Invalid action — use 'start' or 'stop'")
# ---------- GET: status + data ----------
tracing = tracemalloc.is_tracing()
result = {"tracing": tracing}
# Always include RSS regardless of tracing state
try:
import resource
rusage = resource.getrusage(resource.RUSAGE_SELF)
result["rss_mb"] = round(rusage.ru_maxrss / 1024, 1)
except Exception:
pass
if not tracing:
return self._success(result)
current = tracemalloc.take_snapshot()
baseline = getattr(self, "_tracemalloc_baseline", None)
# Top 20 allocations right now (grouped by file + line)
# Top 20 allocations right now
top_current = current.statistics("lineno")[:20]
current_stats = []
for stat in top_current:
@@ -1229,10 +1264,9 @@ class APIEndpoints:
"size_kb": round(stat.size / 1024, 1),
"count": stat.count,
})
result["current_top_20"] = current_stats
result = {"current_top_20": current_stats}
# If we have a baseline, show what GREW (the actual leaks)
# Growth since baseline
if baseline:
diff = current.compare_to(baseline, "lineno")
growth = [d for d in diff if d.size_diff > 0]
@@ -1247,14 +1281,6 @@ class APIEndpoints:
})
result["growth_since_baseline"] = growth_stats
# Also include process-level memory for context
try:
import resource
rusage = resource.getrusage(resource.RUSAGE_SELF)
result["rss_mb"] = round(rusage.ru_maxrss / 1024, 1) # macOS=bytes, Linux=KB
except Exception:
pass
traced_current, traced_peak = tracemalloc.get_traced_memory()
result["traced_current_mb"] = round(traced_current / (1024 * 1024), 2)
result["traced_peak_mb"] = round(traced_peak / (1024 * 1024), 2)
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{dt as e,g as t,l as n,lt as r,s as i,u as a,w as o}from"./runtime-core.esm-bundler-IofF4kUm.js";import{h as s}from"./index-CmQtu2qv.js";var c={class:`flex items-center justify-between mb-4`},l={class:`text-xl font-semibold text-content-primary dark:text-content-primary`},u={class:`mb-6`},d={key:0,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},f={key:1,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},p={key:2,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},m={class:`text-content-secondary dark:text-content-primary/80 text-base leading-relaxed`},h={class:`flex gap-3`},g=t({__name:`ConfirmDialog`,props:{show:{type:Boolean},title:{default:`Confirm Action`},message:{},confirmText:{default:`Confirm`},cancelText:{default:`Cancel`},variant:{default:`warning`}},emits:[`close`,`confirm`],setup(t,{emit:g}){let _=t,v=g,y=e=>{e.target===e.currentTarget&&v(`close`)},b={danger:`bg-red-100 dark:bg-red-500/20 border-red-500/30 text-red-600 dark:text-red-400`,warning:`bg-yellow-100 dark:bg-yellow-500/20 border-yellow-500/30 text-yellow-600 dark:text-yellow-400`,info:`bg-blue-500/20 border-blue-500/30 text-blue-600 dark:text-blue-400`},x={danger:`bg-red-500 hover:bg-red-600`,warning:`bg-yellow-500 hover:bg-yellow-600`,info:`bg-blue-500 hover:bg-blue-600`};return(t,g)=>_.show?(o(),a(`div`,{key:0,onClick:y,class:`fixed inset-0 bg-black/40 backdrop-blur-lg z-[99999] flex items-center justify-center p-4`,style:{"backdrop-filter":`blur(8px) saturate(180%)`,position:`fixed`,top:`0`,left:`0`,right:`0`,bottom:`0`}},[i(`div`,{class:`bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] p-6 w-full max-w-md border border-stroke-subtle dark:border-white/10`,onClick:g[3]||=s(()=>{},[`stop`])},[i(`div`,c,[i(`h3`,l,e(_.title),1),i(`button`,{onClick:g[0]||=e=>v(`close`),class:`text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary transition-colors`},[...g[4]||=[i(`svg`,{class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M6 18L18 6M6 6l12 12`})],-1)]])]),i(`div`,u,[i(`div`,{class:r([`inline-flex p-3 rounded-xl mb-4`,b[_.variant]])},[_.variant===`danger`?(o(),a(`svg`,d,[...g[5]||=[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z`},null,-1)]])):_.variant===`warning`?(o(),a(`svg`,f,[...g[6]||=[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z`},null,-1)]])):(o(),a(`svg`,p,[...g[7]||=[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z`},null,-1)]]))],2),i(`p`,m,e(_.message),1)]),i(`div`,h,[i(`button`,{onClick:g[1]||=e=>v(`close`),class:`flex-1 px-4 py-3 rounded-xl bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary transition-all duration-200 border border-stroke-subtle dark:border-stroke/10`},e(_.cancelText),1),i(`button`,{onClick:g[2]||=e=>v(`confirm`),class:r([`flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200`,x[_.variant]])},e(_.confirmText),3)])])])):n(``,!0)}});export{g as t};
import{dt as e,g as t,l as n,lt as r,s as i,u as a,w as o}from"./runtime-core.esm-bundler-IofF4kUm.js";import{h as s}from"./index-COnQNCNU.js";var c={class:`flex items-center justify-between mb-4`},l={class:`text-xl font-semibold text-content-primary dark:text-content-primary`},u={class:`mb-6`},d={key:0,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},f={key:1,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},p={key:2,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},m={class:`text-content-secondary dark:text-content-primary/80 text-base leading-relaxed`},h={class:`flex gap-3`},g=t({__name:`ConfirmDialog`,props:{show:{type:Boolean},title:{default:`Confirm Action`},message:{},confirmText:{default:`Confirm`},cancelText:{default:`Cancel`},variant:{default:`warning`}},emits:[`close`,`confirm`],setup(t,{emit:g}){let _=t,v=g,y=e=>{e.target===e.currentTarget&&v(`close`)},b={danger:`bg-red-100 dark:bg-red-500/20 border-red-500/30 text-red-600 dark:text-red-400`,warning:`bg-yellow-100 dark:bg-yellow-500/20 border-yellow-500/30 text-yellow-600 dark:text-yellow-400`,info:`bg-blue-500/20 border-blue-500/30 text-blue-600 dark:text-blue-400`},x={danger:`bg-red-500 hover:bg-red-600`,warning:`bg-yellow-500 hover:bg-yellow-600`,info:`bg-blue-500 hover:bg-blue-600`};return(t,g)=>_.show?(o(),a(`div`,{key:0,onClick:y,class:`fixed inset-0 bg-black/40 backdrop-blur-lg z-[99999] flex items-center justify-center p-4`,style:{"backdrop-filter":`blur(8px) saturate(180%)`,position:`fixed`,top:`0`,left:`0`,right:`0`,bottom:`0`}},[i(`div`,{class:`bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] p-6 w-full max-w-md border border-stroke-subtle dark:border-white/10`,onClick:g[3]||=s(()=>{},[`stop`])},[i(`div`,c,[i(`h3`,l,e(_.title),1),i(`button`,{onClick:g[0]||=e=>v(`close`),class:`text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary transition-colors`},[...g[4]||=[i(`svg`,{class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M6 18L18 6M6 6l12 12`})],-1)]])]),i(`div`,u,[i(`div`,{class:r([`inline-flex p-3 rounded-xl mb-4`,b[_.variant]])},[_.variant===`danger`?(o(),a(`svg`,d,[...g[5]||=[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z`},null,-1)]])):_.variant===`warning`?(o(),a(`svg`,f,[...g[6]||=[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z`},null,-1)]])):(o(),a(`svg`,p,[...g[7]||=[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z`},null,-1)]]))],2),i(`p`,m,e(_.message),1)]),i(`div`,h,[i(`button`,{onClick:g[1]||=e=>v(`close`),class:`flex-1 px-4 py-3 rounded-xl bg-background-mute dark:bg-white/5 hover:bg-stroke-subtle dark:hover:bg-white/10 text-content-primary dark:text-content-primary transition-all duration-200 border border-stroke-subtle dark:border-stroke/10`},e(_.cancelText),1),i(`button`,{onClick:g[2]||=e=>v(`confirm`),class:r([`flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200`,x[_.variant]])},e(_.confirmText),3)])])])):n(``,!0)}});export{g as t};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{dt as e,g as t,l as n,lt as r,s as i,u as a,w as o}from"./runtime-core.esm-bundler-IofF4kUm.js";import{h as s}from"./index-CmQtu2qv.js";var c={class:`mb-6`},l={key:0,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},u={key:1,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},d={key:2,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},f={class:`text-content-secondary dark:text-content-primary/80 text-base leading-relaxed`},p={class:`flex`},m=t({__name:`MessageDialog`,props:{show:{type:Boolean},message:{},variant:{default:`success`}},emits:[`close`],setup(t,{emit:m}){let h=t,g=m,_=e=>{e.target===e.currentTarget&&g(`close`)},v={success:`bg-green-100 dark:bg-green-500/20 border-green-600/40 dark:border-green-500/30 text-green-600 dark:text-green-400`,error:`bg-red-100 dark:bg-red-500/20 border-red-500/30 text-red-600 dark:text-red-400`,info:`bg-blue-500/20 border-blue-500/30 text-blue-600 dark:text-blue-400`},y={success:`bg-green-500 hover:bg-green-600`,error:`bg-red-500 hover:bg-red-600`,info:`bg-blue-500 hover:bg-blue-600`};return(t,m)=>h.show?(o(),a(`div`,{key:0,onClick:_,class:`fixed inset-0 bg-black/40 backdrop-blur-lg z-[99999] flex items-center justify-center p-4`,style:{"backdrop-filter":`blur(8px) saturate(180%)`,position:`fixed`,top:`0`,left:`0`,right:`0`,bottom:`0`}},[i(`div`,{class:`bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] p-6 w-full max-w-md border border-stroke-subtle dark:border-white/10`,onClick:m[1]||=s(()=>{},[`stop`])},[i(`div`,c,[i(`div`,{class:r([`inline-flex p-3 rounded-xl mb-4`,v[h.variant]])},[h.variant===`success`?(o(),a(`svg`,l,[...m[2]||=[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M5 13l4 4L19 7`},null,-1)]])):h.variant===`error`?(o(),a(`svg`,u,[...m[3]||=[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M6 18L18 6M6 6l12 12`},null,-1)]])):(o(),a(`svg`,d,[...m[4]||=[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z`},null,-1)]]))],2),i(`p`,f,e(h.message),1)]),i(`div`,p,[i(`button`,{onClick:m[0]||=e=>g(`close`),class:r([`flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200`,y[h.variant]])},` OK `,2)])])])):n(``,!0)}});export{m as t};
import{dt as e,g as t,l as n,lt as r,s as i,u as a,w as o}from"./runtime-core.esm-bundler-IofF4kUm.js";import{h as s}from"./index-COnQNCNU.js";var c={class:`mb-6`},l={key:0,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},u={key:1,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},d={key:2,class:`w-6 h-6`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},f={class:`text-content-secondary dark:text-content-primary/80 text-base leading-relaxed`},p={class:`flex`},m=t({__name:`MessageDialog`,props:{show:{type:Boolean},message:{},variant:{default:`success`}},emits:[`close`],setup(t,{emit:m}){let h=t,g=m,_=e=>{e.target===e.currentTarget&&g(`close`)},v={success:`bg-green-100 dark:bg-green-500/20 border-green-600/40 dark:border-green-500/30 text-green-600 dark:text-green-400`,error:`bg-red-100 dark:bg-red-500/20 border-red-500/30 text-red-600 dark:text-red-400`,info:`bg-blue-500/20 border-blue-500/30 text-blue-600 dark:text-blue-400`},y={success:`bg-green-500 hover:bg-green-600`,error:`bg-red-500 hover:bg-red-600`,info:`bg-blue-500 hover:bg-blue-600`};return(t,m)=>h.show?(o(),a(`div`,{key:0,onClick:_,class:`fixed inset-0 bg-black/40 backdrop-blur-lg z-[99999] flex items-center justify-center p-4`,style:{"backdrop-filter":`blur(8px) saturate(180%)`,position:`fixed`,top:`0`,left:`0`,right:`0`,bottom:`0`}},[i(`div`,{class:`bg-white dark:bg-surface-elevated backdrop-blur-xl rounded-[20px] p-6 w-full max-w-md border border-stroke-subtle dark:border-white/10`,onClick:m[1]||=s(()=>{},[`stop`])},[i(`div`,c,[i(`div`,{class:r([`inline-flex p-3 rounded-xl mb-4`,v[h.variant]])},[h.variant===`success`?(o(),a(`svg`,l,[...m[2]||=[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M5 13l4 4L19 7`},null,-1)]])):h.variant===`error`?(o(),a(`svg`,u,[...m[3]||=[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M6 18L18 6M6 6l12 12`},null,-1)]])):(o(),a(`svg`,d,[...m[4]||=[i(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z`},null,-1)]]))],2),i(`p`,f,e(h.message),1)]),i(`div`,p,[i(`button`,{onClick:m[0]||=e=>g(`close`),class:r([`flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200`,y[h.variant]])},` OK `,2)])])])):n(``,!0)}});export{m as t};
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
import{n as e}from"./index-CmQtu2qv.js";export{e as default};
@@ -0,0 +1 @@
import{n as e}from"./index-COnQNCNU.js";export{e as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{o as e,z as t}from"./runtime-core.esm-bundler-IofF4kUm.js";import{n}from"./pinia-BrpcNUEi.js";import{t as r}from"./api-DegLD39Y.js";var i=n(`system`,()=>{let n=t(null),i=t(!1),a=t(null),o=t(null),s=t(`forward`),c=t(!0),l=t(0),u=t(10),d=t(!1),f=e(()=>n.value?.config?.node_name??`Unknown`),p=e(()=>{let e=n.value?.public_key;return!e||e===`Unknown`?`Unknown`:e.length>=16?`${e.slice(0,8)} ... ${e.slice(-8)}`:`${e}`}),m=e(()=>n.value!==null),h=e(()=>n.value?.version??`Unknown`),g=e(()=>n.value?.core_version??`Unknown`),_=e(()=>n.value?.noise_floor_dbm??null),v=e(()=>u.value>0?Math.min(l.value/u.value*100,100):0),y=e(()=>s.value===`no_tx`?{text:`No TX`,title:`No repeat, no local TX; adverts skipped`}:s.value===`monitor`?{text:`Monitor Mode`,title:`Monitoring only - not forwarding packets`}:c.value?{text:`Active`,title:`Forwarding with duty cycle enforcement`}:{text:`No Limits`,title:`Forwarding without duty cycle enforcement`}),b=e(()=>({mode:s.value})),x=e(()=>c.value?{active:!0,warning:!1}:{active:!1,warning:!0}),S=e=>{d.value=e};async function C(){try{i.value=!0,a.value=null;let e=await r.get(`/stats`);if(e.success&&e.data)return n.value=e.data,o.value=new Date,w(e.data),e.data;if(e&&`version`in e){let t=e;return n.value=t,o.value=new Date,w(t),t}else throw Error(e.error||`Failed to fetch stats`)}catch(e){throw a.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error fetching stats:`,e),e}finally{i.value=!1}}function w(e){if(e.config){let t=e.config.repeater?.mode;t===`forward`||t===`monitor`||t===`no_tx`?s.value=t:t!==void 0&&(s.value=`forward`);let n=e.config.duty_cycle;if(n){c.value=n.enforcement_enabled!==!1;let e=n.max_airtime_percent;typeof e==`number`?u.value=e:e&&typeof e==`object`&&`parsedValue`in e&&(u.value=e.parsedValue||10)}}let t=e.utilization_percent;typeof t==`number`?l.value=t:t&&typeof t==`object`&&`parsedValue`in t&&(l.value=t.parsedValue||0)}async function T(e){try{let t=await r.post(`/set_mode`,{mode:e});if(t.success)return s.value=e,!0;throw Error(t.error||`Failed to set mode`)}catch(e){throw a.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error setting mode:`,e),e}}async function E(e){try{let t=await r.post(`/set_duty_cycle`,{enabled:e});if(t.success)return c.value=e,!0;throw Error(t.error||`Failed to set duty cycle`)}catch(e){throw a.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error setting duty cycle:`,e),e}}async function D(){try{let e=await r.post(`/send_advert`,{},{timeout:1e4});if(e.success)return!0;throw Error(e.error||`Failed to send advert`)}catch(e){throw a.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error sending advert:`,e),e}}async function O(){return await E(!c.value)}function k(e){n.value?(e.uptime_seconds!==void 0&&(n.value.uptime_seconds=e.uptime_seconds),e.noise_floor_dbm!==void 0&&(n.value.noise_floor_dbm=e.noise_floor_dbm)):n.value=e,o.value=new Date,w(e)}async function A(e=5e3,t=!1){t||await C();let n=null;return t||(n=setInterval(async()=>{try{await C()}catch(e){console.error(`Auto-refresh error:`,e)}},e)),()=>{n&&clearInterval(n)}}function j(){n.value=null,a.value=null,o.value=null,i.value=!1,s.value=`forward`,c.value=!0,l.value=0,u.value=10}return{stats:n,isLoading:i,error:a,lastUpdated:o,currentMode:s,dutyCycleEnabled:c,dutyCycleUtilization:l,dutyCycleMax:u,cadCalibrationRunning:d,nodeName:f,pubKey:p,hasStats:m,version:h,coreVersion:g,noiseFloorDbm:_,dutyCyclePercentage:v,statusBadge:y,modeButtonState:b,dutyCycleButtonState:x,fetchStats:C,setMode:T,setDutyCycle:E,sendAdvert:D,toggleDutyCycle:O,startAutoRefresh:A,updateRealtimeStats:k,reset:j,setCadCalibrationRunning:S}});export{i as t};
import{o as e,z as t}from"./runtime-core.esm-bundler-IofF4kUm.js";import{n}from"./pinia-BrpcNUEi.js";import{t as r}from"./api-DGrRo_Ft.js";var i=n(`system`,()=>{let n=t(null),i=t(!1),a=t(null),o=t(null),s=t(`forward`),c=t(!0),l=t(0),u=t(10),d=t(!1),f=e(()=>n.value?.config?.node_name??`Unknown`),p=e(()=>{let e=n.value?.public_key;return!e||e===`Unknown`?`Unknown`:e.length>=16?`${e.slice(0,8)} ... ${e.slice(-8)}`:`${e}`}),m=e(()=>n.value!==null),h=e(()=>n.value?.version??`Unknown`),g=e(()=>n.value?.core_version??`Unknown`),_=e(()=>n.value?.noise_floor_dbm??null),v=e(()=>u.value>0?Math.min(l.value/u.value*100,100):0),y=e(()=>s.value===`no_tx`?{text:`No TX`,title:`No repeat, no local TX; adverts skipped`}:s.value===`monitor`?{text:`Monitor Mode`,title:`Monitoring only - not forwarding packets`}:c.value?{text:`Active`,title:`Forwarding with duty cycle enforcement`}:{text:`No Limits`,title:`Forwarding without duty cycle enforcement`}),b=e(()=>({mode:s.value})),x=e(()=>c.value?{active:!0,warning:!1}:{active:!1,warning:!0}),S=e=>{d.value=e};async function C(){try{i.value=!0,a.value=null;let e=await r.get(`/stats`);if(e.success&&e.data)return n.value=e.data,o.value=new Date,w(e.data),e.data;if(e&&`version`in e){let t=e;return n.value=t,o.value=new Date,w(t),t}else throw Error(e.error||`Failed to fetch stats`)}catch(e){throw a.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error fetching stats:`,e),e}finally{i.value=!1}}function w(e){if(e.config){let t=e.config.repeater?.mode;t===`forward`||t===`monitor`||t===`no_tx`?s.value=t:t!==void 0&&(s.value=`forward`);let n=e.config.duty_cycle;if(n){c.value=n.enforcement_enabled!==!1;let e=n.max_airtime_percent;typeof e==`number`?u.value=e:e&&typeof e==`object`&&`parsedValue`in e&&(u.value=e.parsedValue||10)}}let t=e.utilization_percent;typeof t==`number`?l.value=t:t&&typeof t==`object`&&`parsedValue`in t&&(l.value=t.parsedValue||0)}async function T(e){try{let t=await r.post(`/set_mode`,{mode:e});if(t.success)return s.value=e,!0;throw Error(t.error||`Failed to set mode`)}catch(e){throw a.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error setting mode:`,e),e}}async function E(e){try{let t=await r.post(`/set_duty_cycle`,{enabled:e});if(t.success)return c.value=e,!0;throw Error(t.error||`Failed to set duty cycle`)}catch(e){throw a.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error setting duty cycle:`,e),e}}async function D(){try{let e=await r.post(`/send_advert`,{},{timeout:1e4});if(e.success)return!0;throw Error(e.error||`Failed to send advert`)}catch(e){throw a.value=e instanceof Error?e.message:`Unknown error occurred`,console.error(`Error sending advert:`,e),e}}async function O(){return await E(!c.value)}function k(e){n.value?(e.uptime_seconds!==void 0&&(n.value.uptime_seconds=e.uptime_seconds),e.noise_floor_dbm!==void 0&&(n.value.noise_floor_dbm=e.noise_floor_dbm)):n.value=e,o.value=new Date,w(e)}async function A(e=5e3,t=!1){t||await C();let n=null;return t||(n=setInterval(async()=>{try{await C()}catch(e){console.error(`Auto-refresh error:`,e)}},e)),()=>{n&&clearInterval(n)}}function j(){n.value=null,a.value=null,o.value=null,i.value=!1,s.value=`forward`,c.value=!0,l.value=0,u.value=10}return{stats:n,isLoading:i,error:a,lastUpdated:o,currentMode:s,dutyCycleEnabled:c,dutyCycleUtilization:l,dutyCycleMax:u,cadCalibrationRunning:d,nodeName:f,pubKey:p,hasStats:m,version:h,coreVersion:g,noiseFloorDbm:_,dutyCyclePercentage:v,statusBadge:y,modeButtonState:b,dutyCycleButtonState:x,fetchStats:C,setMode:T,setDutyCycle:E,sendAdvert:D,toggleDutyCycle:O,startAutoRefresh:A,updateRealtimeStats:k,reset:j,setCadCalibrationRunning:S}});export{i as t};
@@ -1 +1 @@
import{o as e}from"./runtime-core.esm-bundler-IofF4kUm.js";import{t}from"./system-qCwV23PE.js";var n={7:-7.5,8:-10,9:-12.5,10:-15,11:-17.5,12:-20},r=-116,i=8,a=5;function o(e,t){return e-t}function s(e){return n[e]??n[i]}function c(e,t){let n=t+a;if(e<=t){let n=e<=t-5?0:1;return{bars:n,color:`text-red-600 dark:text-red-400`,snr:e,quality:n===0?`none`:`poor`}}if(e<n){let n=(e-t)/a<.5?2:3;return{bars:n,color:n===2?`text-orange-600 dark:text-orange-400`:`text-yellow-600 dark:text-yellow-400`,snr:e,quality:`fair`}}let r=e-n>=10?5:4;return{bars:r,color:r===5?`text-green-600 dark:text-green-400`:`text-green-600 dark:text-green-300`,snr:e,quality:r===5?`excellent`:`good`}}function l(){let n=t(),a=e(()=>n.noiseFloorDbm??r),l=e(()=>n.stats?.config?.radio?.spreading_factor??i),u=e(()=>s(l.value));return{getSignalQuality:e=>{if(!e||e>0||e<-120)return{bars:0,color:`text-gray-400 dark:text-gray-500`,snr:-999,quality:`none`};let t=o(e,a.value);return c(Math.max(-30,Math.min(20,t)),u.value)},noiseFloor:a,spreadingFactor:l,minSNR:u}}export{l as t};
import{o as e}from"./runtime-core.esm-bundler-IofF4kUm.js";import{t}from"./system-CBL1eQwL.js";var n={7:-7.5,8:-10,9:-12.5,10:-15,11:-17.5,12:-20},r=-116,i=8,a=5;function o(e,t){return e-t}function s(e){return n[e]??n[i]}function c(e,t){let n=t+a;if(e<=t){let n=e<=t-5?0:1;return{bars:n,color:`text-red-600 dark:text-red-400`,snr:e,quality:n===0?`none`:`poor`}}if(e<n){let n=(e-t)/a<.5?2:3;return{bars:n,color:n===2?`text-orange-600 dark:text-orange-400`:`text-yellow-600 dark:text-yellow-400`,snr:e,quality:`fair`}}let r=e-n>=10?5:4;return{bars:r,color:r===5?`text-green-600 dark:text-green-400`:`text-green-600 dark:text-green-300`,snr:e,quality:r===5?`excellent`:`good`}}function l(){let n=t(),a=e(()=>n.noiseFloorDbm??r),l=e(()=>n.stats?.config?.radio?.spreading_factor??i),u=e(()=>s(l.value));return{getSignalQuality:e=>{if(!e||e>0||e<-120)return{bars:0,color:`text-gray-400 dark:text-gray-500`,snr:-999,quality:`none`};let t=o(e,a.value);return c(Math.max(-30,Math.min(20,t)),u.value)},noiseFloor:a,spreadingFactor:l,minSNR:u}}export{l as t};
+5 -5
View File
@@ -8,17 +8,17 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-CmQtu2qv.js"></script>
<script type="module" crossorigin src="/assets/index-COnQNCNU.js"></script>
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-V-yks4gF.js">
<link rel="modulepreload" crossorigin href="/assets/chunk-DECur_0Z.js">
<link rel="modulepreload" crossorigin href="/assets/runtime-core.esm-bundler-IofF4kUm.js">
<link rel="modulepreload" crossorigin href="/assets/vue-router-BsDVl_JC.js">
<link rel="modulepreload" crossorigin href="/assets/api-DegLD39Y.js">
<link rel="modulepreload" crossorigin href="/assets/api-DGrRo_Ft.js">
<link rel="modulepreload" crossorigin href="/assets/pinia-BrpcNUEi.js">
<link rel="modulepreload" crossorigin href="/assets/useTheme-Dlt6-wEf.js">
<link rel="modulepreload" crossorigin href="/assets/packets-DmoWuBlc.js">
<link rel="modulepreload" crossorigin href="/assets/system-qCwV23PE.js">
<link rel="stylesheet" crossorigin href="/assets/index-B6bC_m88.css">
<link rel="modulepreload" crossorigin href="/assets/packets-BgkeSYWF.js">
<link rel="modulepreload" crossorigin href="/assets/system-CBL1eQwL.js">
<link rel="stylesheet" crossorigin href="/assets/index-B4rh8qna.css">
</head>
<body>
<div id="app"></div>