mirror of
https://github.com/rightup/pyMC_Repeater.git
synced 2026-05-18 07:16:20 +02:00
Merge pull request #175 from rightup/feat/fix-regions-unscoped-flood
fix: rename global flood to unscoped flood, fix region handling
This commit is contained in:
+3
-4
@@ -121,10 +121,9 @@ repeater:
|
||||
|
||||
# Mesh Network Configuration
|
||||
mesh:
|
||||
# Global flood policy - controls whether the repeater allows or denies flooding by default
|
||||
# true = allow flooding globally, false = deny flooding globally
|
||||
# Individual transport keys can override this setting
|
||||
global_flood_allow: true
|
||||
# Unscoped flood policy - controls whether the repeater allows or denies unscoped flooding
|
||||
# true = allow unscoped flooding, false = deny flooding globally
|
||||
unscoped_flood_allow: true
|
||||
|
||||
# Path hash mode for flood packets (0-hop): per-hop hash size in path encoding
|
||||
# 0 = 1-byte hashes (legacy), 1 = 2-byte, 2 = 3-byte. Must match mesh convention.
|
||||
|
||||
+5
-4
@@ -152,12 +152,12 @@ def save_config(config_data: Dict[str, Any], config_path: Optional[str] = None)
|
||||
return False
|
||||
|
||||
|
||||
def update_global_flood_policy(allow: bool, config_path: Optional[str] = None) -> bool:
|
||||
def update_unscoped_flood_policy(allow: bool, config_path: Optional[str] = None) -> bool:
|
||||
"""
|
||||
Update the global flood policy in the configuration.
|
||||
Update the unscoped flood policy in the configuration.
|
||||
|
||||
Args:
|
||||
allow: True to allow flooding globally, False to deny
|
||||
allow: True to allow unscoped flooding, False to deny
|
||||
config_path: Path to config file (uses default if None)
|
||||
|
||||
Returns:
|
||||
@@ -173,12 +173,13 @@ def update_global_flood_policy(allow: bool, config_path: Optional[str] = None) -
|
||||
|
||||
# Set global flood policy
|
||||
config["mesh"]["global_flood_allow"] = allow
|
||||
config["mesh"]["unscoped_flood_allow"] = allow
|
||||
|
||||
# Save updated config
|
||||
return save_config(config, config_path)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update global flood policy: {e}")
|
||||
logger.error(f"Failed to update unscoped flood policy: {e}")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
+18
-17
@@ -623,10 +623,10 @@ class RepeaterHandler(BaseHandler):
|
||||
route_type = packet.header & PH_ROUTE_MASK
|
||||
|
||||
if route_type == ROUTE_TYPE_FLOOD:
|
||||
# Check if global flood policy blocked it
|
||||
global_flood_allow = self.config.get("mesh", {}).get("global_flood_allow", True)
|
||||
if not global_flood_allow:
|
||||
return "Global flood policy disabled"
|
||||
# Check if unscoped flood policy blocked it
|
||||
unscoped_flood_allow = self.config.get("mesh", {}).get("unscoped_flood_allow", self.config.get("mesh", {}).get("global_flood_allow", True))
|
||||
if not unscoped_flood_allow:
|
||||
return "Unscoped flood policy disabled"
|
||||
|
||||
if route_type == ROUTE_TYPE_DIRECT:
|
||||
hash_size = packet.get_path_hash_size()
|
||||
@@ -800,19 +800,20 @@ class RepeaterHandler(BaseHandler):
|
||||
if not packet.drop_reason:
|
||||
packet.drop_reason = "Marked do not retransmit"
|
||||
return None
|
||||
|
||||
# Check unscoped flood policy
|
||||
unscoped_flood_allow = self.config.get("mesh", {}).get("unscoped_flood_allow", self.config.get("mesh", {}).get("global_flood_allow", True))
|
||||
route_type = packet.header & PH_ROUTE_MASK
|
||||
if route_type == ROUTE_TYPE_FLOOD:
|
||||
if not unscoped_flood_allow:
|
||||
packet.drop_reason = "Unscoped flood policy disabled"
|
||||
return None
|
||||
|
||||
# Check global flood policy
|
||||
global_flood_allow = self.config.get("mesh", {}).get("global_flood_allow", True)
|
||||
if not global_flood_allow:
|
||||
route_type = packet.header & PH_ROUTE_MASK
|
||||
if route_type == ROUTE_TYPE_FLOOD or route_type == ROUTE_TYPE_TRANSPORT_FLOOD:
|
||||
|
||||
allowed, check_reason = self._check_transport_codes(packet)
|
||||
if not allowed:
|
||||
packet.drop_reason = check_reason
|
||||
return None
|
||||
else:
|
||||
packet.drop_reason = "Global flood policy disabled"
|
||||
#Check transport scopes flood policy
|
||||
if route_type == ROUTE_TYPE_TRANSPORT_FLOOD:
|
||||
allowed, check_reason = self._check_transport_codes(packet)
|
||||
if not allowed:
|
||||
packet.drop_reason = "Transport code not allowed to flood"
|
||||
return None
|
||||
|
||||
mode = self._get_loop_detect_mode()
|
||||
@@ -1134,7 +1135,7 @@ class RepeaterHandler(BaseHandler):
|
||||
"web": self.config.get("web", {}), # Include web configuration
|
||||
"mesh": {
|
||||
"loop_detect": self.config.get("mesh", {}).get("loop_detect", "off"),
|
||||
"global_flood_allow": self.config.get("mesh", {}).get("global_flood_allow", True),
|
||||
"unscoped_flood_allow": self.config.get("mesh", {}).get("unscoped_flood_allow", self.config.get("mesh", {}).get("global_flood_allow", True)),
|
||||
"path_hash_mode": self.config.get("mesh", {}).get("path_hash_mode", 0),
|
||||
},
|
||||
"letsmesh": self.config.get("letsmesh", {}),
|
||||
|
||||
@@ -15,7 +15,7 @@ from repeater.companion.identity_resolve import (
|
||||
find_companion_index,
|
||||
heal_companion_empty_names,
|
||||
)
|
||||
from repeater.config import update_global_flood_policy
|
||||
from repeater.config import update_unscoped_flood_policy
|
||||
|
||||
from .auth.middleware import require_auth
|
||||
from .auth_endpoints import AuthAPIEndpoints
|
||||
@@ -93,8 +93,8 @@ logger = logging.getLogger("HTTPServer")
|
||||
# DELETE /api/transport_key?key_id=X - Delete transport key
|
||||
|
||||
# Network Policy
|
||||
# GET /api/global_flood_policy - Get global flood policy
|
||||
# POST /api/global_flood_policy - Update global flood policy
|
||||
# GET /api/unscoped_flood_policy - Get unscoped flood policy
|
||||
# POST /api/unscoped_flood_policy - Update unscoped flood policy
|
||||
# POST /api/ping_neighbor - Ping a neighbor node
|
||||
|
||||
# Identity Management
|
||||
@@ -2153,57 +2153,57 @@ class APIEndpoints:
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@cherrypy.tools.json_in()
|
||||
def global_flood_policy(self):
|
||||
def unscoped_flood_policy(self):
|
||||
"""
|
||||
Update global flood policy configuration
|
||||
Update unscoped flood policy configuration
|
||||
|
||||
POST /global_flood_policy
|
||||
Body: {"global_flood_allow": true/false}
|
||||
POST /unscoped_flood_policy
|
||||
Body: {"unscoped_flood_allow": true/false}
|
||||
"""
|
||||
if cherrypy.request.method == "POST":
|
||||
try:
|
||||
data = cherrypy.request.json or {}
|
||||
global_flood_allow = data.get("global_flood_allow")
|
||||
unscoped_flood_allow = data.get("unscoped_flood_allow")
|
||||
|
||||
if global_flood_allow is None:
|
||||
return self._error("Missing required field: global_flood_allow")
|
||||
if unscoped_flood_allow is None:
|
||||
return self._error("Missing required field: unscoped_flood_allow")
|
||||
|
||||
if not isinstance(global_flood_allow, bool):
|
||||
return self._error("global_flood_allow must be a boolean value")
|
||||
if not isinstance(unscoped_flood_allow, bool):
|
||||
return self._error("unscoped_flood_allow must be a boolean value")
|
||||
|
||||
# Update the running configuration first (like CAD settings)
|
||||
if "mesh" not in self.config:
|
||||
self.config["mesh"] = {}
|
||||
self.config["mesh"]["global_flood_allow"] = global_flood_allow
|
||||
self.config["mesh"]["unscoped_flood_allow"] = unscoped_flood_allow
|
||||
|
||||
# Get the actual config path from daemon instance (same as CAD settings)
|
||||
config_path = getattr(self, "_config_path", "/etc/pymc_repeater/config.yaml")
|
||||
if self.daemon_instance and hasattr(self.daemon_instance, "config_path"):
|
||||
config_path = self.daemon_instance.config_path
|
||||
|
||||
logger.info(f"Using config path for global flood policy: {config_path}")
|
||||
logger.info(f"Using config path for unscoped flood policy: {config_path}")
|
||||
|
||||
# Update the configuration file using ConfigManager
|
||||
try:
|
||||
saved = self.config_manager.save_to_file()
|
||||
if saved:
|
||||
logger.info(
|
||||
f"Updated running config and saved global flood policy to file: {'allow' if global_flood_allow else 'deny'}"
|
||||
f"Updated running config and saved unscoped flood policy to file: {'allow' if unscoped_flood_allow else 'deny'}"
|
||||
)
|
||||
else:
|
||||
logger.error("Failed to save global flood policy to file")
|
||||
logger.error("Failed to save unscoped flood policy to file")
|
||||
return self._error("Failed to save configuration to file")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save global flood policy to file: {e}")
|
||||
logger.error(f"Failed to save unscoped flood policy to file: {e}")
|
||||
return self._error(f"Failed to save configuration to file: {e}")
|
||||
|
||||
return self._success(
|
||||
{"global_flood_allow": global_flood_allow},
|
||||
message=f"Global flood policy updated to {'allow' if global_flood_allow else 'deny'} (live and saved)",
|
||||
{"unscoped_flood_allow": unscoped_flood_allow},
|
||||
message=f"Unscoped flood policy updated to {'allow' if unscoped_flood_allow else 'deny'} (live and saved)",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating global flood policy: {e}")
|
||||
logger.error(f"Error updating unscoped flood policy: {e}")
|
||||
return self._error(e)
|
||||
else:
|
||||
return self._error("Method not supported")
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
.glass-card[data-v-c30e5f38]{background:var(--color-glass-bg);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid var(--color-glass-border);box-shadow:var(--color-glass-shadow)}
|
||||
.glass-card[data-v-60d82848]{background:var(--color-glass-bg);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid var(--color-glass-border);box-shadow:var(--color-glass-shadow)}
|
||||
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
@@ -0,0 +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{m as s}from"./index-CPWfwDmA.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};
|
||||
@@ -1 +0,0 @@
|
||||
import{a as m,e as n,h as p,f as t,x as g,t as s,k as d,q as l}from"./index-xzvnOpJo.js";const f={class:"flex items-center justify-between mb-4"},w={class:"text-xl font-semibold text-content-primary dark:text-content-primary"},v={class:"mb-6"},h={key:0,class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},y={key:1,class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},C={key:2,class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},B={class:"text-content-secondary dark:text-content-primary/80 text-base leading-relaxed"},M={class:"flex gap-3"},j=m({__name:"ConfirmDialog",props:{show:{type:Boolean},title:{default:"Confirm Action"},message:{},confirmText:{default:"Confirm"},cancelText:{default:"Cancel"},variant:{default:"warning"}},emits:["close","confirm"],setup(c,{emit:b}){const o=c,r=b,k=i=>{i.target===i.currentTarget&&r("close")},u={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(i,e)=>o.show?(l(),n("div",{key:0,onClick:k,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"}},[t("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:e[3]||(e[3]=g(()=>{},["stop"]))},[t("div",f,[t("h3",w,s(o.title),1),t("button",{onClick:e[0]||(e[0]=a=>r("close")),class:"text-content-secondary dark:text-content-muted hover:text-content-primary dark:hover:text-content-primary transition-colors"},e[4]||(e[4]=[t("svg",{class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),t("div",v,[t("div",{class:d(["inline-flex p-3 rounded-xl mb-4",u[o.variant]])},[o.variant==="danger"?(l(),n("svg",h,e[5]||(e[5]=[t("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.variant==="warning"?(l(),n("svg",y,e[6]||(e[6]=[t("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)]))):(l(),n("svg",C,e[7]||(e[7]=[t("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),t("p",B,s(o.message),1)]),t("div",M,[t("button",{onClick:e[1]||(e[1]=a=>r("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"},s(o.cancelText),1),t("button",{onClick:e[2]||(e[2]=a=>r("confirm")),class:d(["flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200",x[o.variant]])},s(o.confirmText),3)])])])):p("",!0)}});export{j as _};
|
||||
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
@@ -0,0 +1 @@
|
||||
import{f as e,g as t,u as n,w as r}from"./runtime-core.esm-bundler-IofF4kUm.js";var i=t({name:`HelpView`,__name:`Help`,setup(t){return(t,i)=>(r(),n(`div`,null,[...i[0]||=[e(`<div class="glass-card backdrop-blur border border-stroke-subtle dark:border-white/10 rounded-[15px] p-8"><h1 class="text-content-primary dark:text-content-primary text-2xl font-semibold mb-6"> Help & Documentation </h1><div class="text-center py-12"><div class="text-primary mb-6"><svg class="w-20 h-20 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg></div><h2 class="text-content-primary dark:text-content-primary text-xl font-medium mb-3"> pyMC Repeater Wiki </h2><p class="text-content-secondary dark:text-content-muted mb-8 max-w-md mx-auto"> Access documentation, setup guides, troubleshooting tips, and community resources on our official wiki. </p><a href="https://github.com/rightup/pyMC_Repeater/wiki" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 bg-primary hover:bg-primary/80 text-white dark:text-background font-medium py-3 px-6 rounded-xl transition-colors duration-200"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path></svg> Visit Wiki Documentation </a><div class="mt-8 text-xs text-content-muted dark:text-content-muted"> Opens in a new tab </div></div></div>`,1)]]))}});export{i as default};
|
||||
@@ -1 +0,0 @@
|
||||
import{a as e,e as r,j as o,q as n}from"./index-xzvnOpJo.js";const d=e({name:"HelpView",__name:"Help",setup(a){return(i,t)=>(n(),r("div",null,t[0]||(t[0]=[o('<div class="glass-card backdrop-blur border border-stroke-subtle dark:border-white/10 rounded-[15px] p-8"><h1 class="text-content-primary dark:text-content-primary text-2xl font-semibold mb-6">Help & Documentation</h1><div class="text-center py-12"><div class="text-primary mb-6"><svg class="w-20 h-20 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg></div><h2 class="text-content-primary dark:text-content-primary text-xl font-medium mb-3">pyMC Repeater Wiki</h2><p class="text-content-secondary dark:text-content-muted mb-8 max-w-md mx-auto"> Access documentation, setup guides, troubleshooting tips, and community resources on our official wiki. </p><a href="https://github.com/rightup/pyMC_Repeater/wiki" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 bg-primary hover:bg-primary/80 text-white dark:text-background font-medium py-3 px-6 rounded-xl transition-colors duration-200"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path></svg> Visit Wiki Documentation </a><div class="mt-8 text-xs text-content-muted dark:text-content-muted"> Opens in a new tab </div></div></div>',1)])))}});export{d as default};
|
||||
@@ -0,0 +1 @@
|
||||
.bg-gradient-light[data-v-63d1c99c]{background:linear-gradient(#0ea5e966,#06b6d44d)}.bg-gradient-dark[data-v-63d1c99c]{background:linear-gradient(#67e8f94d,#a5f3fc26)}.login-card[data-v-63d1c99c]{-webkit-backdrop-filter:blur(40px)saturate(180%);background:#ffffffb3}.dark .login-card[data-v-63d1c99c]{background:#11191c66}.input-glass[data-v-63d1c99c]{-webkit-backdrop-filter:blur(20px);background:#ffffffe6;border:1px solid #d1d5db}.dark .input-glass[data-v-63d1c99c]{background:#ffffff0d;border-color:#ffffff1a}.input-glass[data-v-63d1c99c]:focus{background:#fff}.dark .input-glass[data-v-63d1c99c]:focus{background:#ffffff1a}.input-glass[data-v-63d1c99c]:focus{box-shadow:0 0 0 1px #aae8e833,0 0 20px #aae8e826,inset 0 1px #ffffff1a}.input-glow[data-v-63d1c99c]{opacity:0;transition:opacity .3s;box-shadow:inset 0 1px #ffffff0d}.input-glass:focus+.input-glow[data-v-63d1c99c]{opacity:1;box-shadow:0 0 20px #aae8e833,inset 0 1px #ffffff1a}.button-glass[data-v-63d1c99c]{-webkit-backdrop-filter:blur(20px);position:relative}.button-glass[data-v-63d1c99c]:before{content:"";-webkit-mask-composite:xor;background:linear-gradient(90deg,#0000 0%,#aae8e84d 50%,#0000 100%);border-radius:12px;padding:1px;transition:transform 1s;position:absolute;inset:0;transform:translate(-100%);-webkit-mask-image:linear-gradient(#fff 0 0),linear-gradient(#fff 0 0);-webkit-mask-position:0 0,0 0;-webkit-mask-size:auto,auto;-webkit-mask-repeat:repeat,repeat;-webkit-mask-clip:content-box,border-box;-webkit-mask-origin:content-box,border-box;-webkit-mask-composite:xor;mask-composite:exclude;-webkit-mask-source-type:auto,auto;mask-mode:match-source,match-source}.button-glass[data-v-63d1c99c]:hover:not(:disabled):before{transform:translate(100%)}.button-glass[data-v-63d1c99c]{box-shadow:0 0 0 1px #aae8e833,0 4px 16px #0003,inset 0 1px #ffffff1a}.button-glass[data-v-63d1c99c]:hover:not(:disabled){box-shadow:0 0 0 1px #aae8e866,0 0 30px #aae8e84d,0 4px 20px #0000004d,inset 0 1px #ffffff26}.login-content:has(.button-glass:hover:not(:disabled)) .logo-image[data-v-63d1c99c]{filter:brightness(1.4)drop-shadow(0 0 12px #aae8e8b3);transform:scale(1.02)}.login-content:has(.button-glass:hover:not(:disabled)) .logo-glow[data-v-63d1c99c]{opacity:.6;transform:scale(1.15)}.logo-glow[data-v-63d1c99c]{opacity:0}.dark .logo-glow[data-v-63d1c99c]{opacity:1}@keyframes float-63d1c99c{0%,to{transform:translateY(0)}50%{transform:translateY(-10px)}}@keyframes pulse-slow-63d1c99c{0%,to{opacity:.8;transform:scale(1)}50%{opacity:.6;transform:scale(1.05)}}@keyframes pulse-slower-63d1c99c{0%,to{opacity:.75;transform:scale(1)}50%{opacity:.5;transform:scale(1.08)}}@keyframes pulse-slowest-63d1c99c{0%,to{opacity:.8;transform:scale(1)}50%{opacity:.6;transform:scale(1.06)}}.animate-pulse-slow[data-v-63d1c99c]{animation:8s ease-in-out infinite pulse-slow-63d1c99c}.animate-pulse-slower[data-v-63d1c99c]{animation:10s ease-in-out infinite pulse-slower-63d1c99c}.animate-pulse-slowest[data-v-63d1c99c]{animation:12s ease-in-out infinite pulse-slowest-63d1c99c}@keyframes shake-63d1c99c{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-5px)}20%,40%,60%,80%{transform:translate(5px)}}.animate-shake[data-v-63d1c99c]{animation:.5s ease-in-out shake-63d1c99c}.form-group[data-v-63d1c99c]{position:relative}.form-group:hover label[data-v-63d1c99c]{color:#aae8e8e6;transition:color .3s}
|
||||
@@ -1 +0,0 @@
|
||||
.bg-gradient-light[data-v-7d3a3377]{background:linear-gradient(to bottom,#0ea5e966,#06b6d44d)}.bg-gradient-dark[data-v-7d3a3377]{background:linear-gradient(to bottom,#67e8f94d,#a5f3fc26)}.login-card[data-v-7d3a3377]{background:#11191c66;backdrop-filter:blur(40px) saturate(180%);-webkit-backdrop-filter:blur(40px) saturate(180%)}.login-card[data-v-7d3a3377]{background:#ffffffb3}.dark .login-card[data-v-7d3a3377]{background:#11191c66}.input-glass[data-v-7d3a3377]{backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px)}.input-glass[data-v-7d3a3377]{background:#ffffffe6;border:1px solid #D1D5DB}.dark .input-glass[data-v-7d3a3377]{background:#ffffff0d;border-color:#ffffff1a}.input-glass[data-v-7d3a3377]:focus{background:#fff}.dark .input-glass[data-v-7d3a3377]:focus{background:#ffffff1a}.input-glass[data-v-7d3a3377]:focus{box-shadow:0 0 0 1px #aae8e833,0 0 20px #aae8e826,inset 0 1px #ffffff1a}.input-glow[data-v-7d3a3377]{opacity:0;transition:opacity .3s ease;box-shadow:inset 0 1px #ffffff0d}.input-glass:focus+.input-glow[data-v-7d3a3377]{opacity:1;box-shadow:0 0 20px #aae8e833,inset 0 1px #ffffff1a}.button-glass[data-v-7d3a3377]{backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);position:relative}.button-glass[data-v-7d3a3377]:before{content:"";position:absolute;inset:0;border-radius:12px;padding:1px;background:linear-gradient(90deg,transparent 0%,rgba(170,232,232,.3) 50%,transparent 100%);-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude;transform:translate(-100%);transition:transform 1s ease}.button-glass[data-v-7d3a3377]:hover:not(:disabled):before{transform:translate(100%)}.button-glass[data-v-7d3a3377]{box-shadow:0 0 0 1px #aae8e833,0 4px 16px #0003,inset 0 1px #ffffff1a}.button-glass[data-v-7d3a3377]:hover:not(:disabled){box-shadow:0 0 0 1px #aae8e866,0 0 30px #aae8e84d,0 4px 20px #0000004d,inset 0 1px #ffffff26}.login-content:has(.button-glass:hover:not(:disabled)) .logo-image[data-v-7d3a3377]{filter:brightness(1.4) drop-shadow(0 0 12px rgba(170,232,232,.7));transform:scale(1.02)}.login-content:has(.button-glass:hover:not(:disabled)) .logo-glow[data-v-7d3a3377]{opacity:.6;transform:scale(1.15)}.logo-glow[data-v-7d3a3377]{opacity:0}.dark .logo-glow[data-v-7d3a3377]{opacity:1}@keyframes float-7d3a3377{0%,to{transform:translateY(0)}50%{transform:translateY(-10px)}}@keyframes pulse-slow-7d3a3377{0%,to{opacity:.8;transform:scale(1)}50%{opacity:.6;transform:scale(1.05)}}@keyframes pulse-slower-7d3a3377{0%,to{opacity:.75;transform:scale(1)}50%{opacity:.5;transform:scale(1.08)}}@keyframes pulse-slowest-7d3a3377{0%,to{opacity:.8;transform:scale(1)}50%{opacity:.6;transform:scale(1.06)}}.animate-pulse-slow[data-v-7d3a3377]{animation:pulse-slow-7d3a3377 8s ease-in-out infinite}.animate-pulse-slower[data-v-7d3a3377]{animation:pulse-slower-7d3a3377 10s ease-in-out infinite}.animate-pulse-slowest[data-v-7d3a3377]{animation:pulse-slowest-7d3a3377 12s ease-in-out infinite}@keyframes shake-7d3a3377{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-5px)}20%,40%,60%,80%{transform:translate(5px)}}.animate-shake[data-v-7d3a3377]{animation:shake-7d3a3377 .5s ease-in-out}.form-group[data-v-7d3a3377]{position:relative}.form-group:hover label[data-v-7d3a3377]{color:#aae8e8e6;transition:color .3s ease}
|
||||
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
@@ -0,0 +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{m as s}from"./index-CPWfwDmA.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};
|
||||
@@ -1 +0,0 @@
|
||||
import{a as k,e as o,h as g,f as r,k as a,t as p,x,q as s}from"./index-xzvnOpJo.js";const f={class:"mb-6"},m={key:0,class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},v={key:1,class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},h={key:2,class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},w={class:"text-content-secondary dark:text-content-primary/80 text-base leading-relaxed"},C={class:"flex"},B=k({__name:"MessageDialog",props:{show:{type:Boolean},message:{},variant:{default:"success"}},emits:["close"],setup(i,{emit:d}){const t=i,l=d,c=n=>{n.target===n.currentTarget&&l("close")},u={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"},b={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(n,e)=>t.show?(s(),o("div",{key:0,onClick:c,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"}},[r("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:e[1]||(e[1]=x(()=>{},["stop"]))},[r("div",f,[r("div",{class:a(["inline-flex p-3 rounded-xl mb-4",u[t.variant]])},[t.variant==="success"?(s(),o("svg",m,e[2]||(e[2]=[r("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M5 13l4 4L19 7"},null,-1)]))):t.variant==="error"?(s(),o("svg",v,e[3]||(e[3]=[r("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"},null,-1)]))):(s(),o("svg",h,e[4]||(e[4]=[r("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),r("p",w,p(t.message),1)]),r("div",C,[r("button",{onClick:e[0]||(e[0]=y=>l("close")),class:a(["flex-1 px-4 py-3 rounded-xl text-white transition-all duration-200",b[t.variant]])}," OK ",2)])])])):g("",!0)}});export{B as _};
|
||||
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
@@ -0,0 +1 @@
|
||||
import{n as e}from"./index-CPWfwDmA.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
@@ -0,0 +1 @@
|
||||
.glass-card[data-v-a201f2f2]{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffff0d;border:1px solid #ffffff1a}.modal-enter-active[data-v-a201f2f2],.modal-leave-active[data-v-a201f2f2]{transition:opacity .3s}.modal-enter-from[data-v-a201f2f2],.modal-leave-to[data-v-a201f2f2]{opacity:0}.modal-enter-active .glass-card[data-v-a201f2f2],.modal-leave-active .glass-card[data-v-a201f2f2]{transition:transform .3s}.modal-enter-from .glass-card[data-v-a201f2f2],.modal-leave-to .glass-card[data-v-a201f2f2]{transform:scale(.9)}.slide-enter-active[data-v-a201f2f2],.slide-leave-active[data-v-a201f2f2]{transition:all .3s}.slide-enter-from[data-v-a201f2f2],.slide-leave-to[data-v-a201f2f2]{opacity:0;transform:translateY(-10px)}@keyframes float-slow-a201f2f2{0%,to{opacity:.8;transform:translate(0)scale(1)rotate(-24.22deg)}50%{opacity:.6;transform:translate(20px,-20px)scale(1.05)rotate(-24.22deg)}}@keyframes float-slower-a201f2f2{0%,to{opacity:.75;transform:translate(0)scale(1)rotate(-24.22deg)}50%{opacity:.5;transform:translate(-30px,20px)scale(1.08)rotate(-24.22deg)}}@keyframes float-slowest-a201f2f2{0%,to{opacity:.8;transform:translate(0)scale(1)rotate(-24.22deg)}50%{opacity:.55;transform:translate(25px,25px)scale(1.1)rotate(-24.22deg)}}.animate-pulse-slow[data-v-a201f2f2]{will-change:transform, opacity;animation:15s ease-in-out infinite float-slow-a201f2f2}.animate-pulse-slower[data-v-a201f2f2]{will-change:transform, opacity;animation:18s ease-in-out infinite float-slower-a201f2f2}.animate-pulse-slowest[data-v-a201f2f2]{will-change:transform, opacity;animation:20s ease-in-out infinite float-slowest-a201f2f2}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
.glass-card[data-v-693a052e]{background:#ffffff0d;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.1)}.modal-enter-active[data-v-693a052e],.modal-leave-active[data-v-693a052e]{transition:opacity .3s ease}.modal-enter-from[data-v-693a052e],.modal-leave-to[data-v-693a052e]{opacity:0}.modal-enter-active .glass-card[data-v-693a052e],.modal-leave-active .glass-card[data-v-693a052e]{transition:transform .3s ease}.modal-enter-from .glass-card[data-v-693a052e],.modal-leave-to .glass-card[data-v-693a052e]{transform:scale(.9)}.slide-enter-active[data-v-693a052e],.slide-leave-active[data-v-693a052e]{transition:all .3s ease}.slide-enter-from[data-v-693a052e],.slide-leave-to[data-v-693a052e]{opacity:0;transform:translateY(-10px)}@keyframes float-slow-693a052e{0%,to{opacity:.8;transform:translate(0) scale(1) rotate(-24.22deg)}50%{opacity:.6;transform:translate(20px,-20px) scale(1.05) rotate(-24.22deg)}}@keyframes float-slower-693a052e{0%,to{opacity:.75;transform:translate(0) scale(1) rotate(-24.22deg)}50%{opacity:.5;transform:translate(-30px,20px) scale(1.08) rotate(-24.22deg)}}@keyframes float-slowest-693a052e{0%,to{opacity:.8;transform:translate(0) scale(1) rotate(-24.22deg)}50%{opacity:.55;transform:translate(25px,25px) scale(1.1) rotate(-24.22deg)}}.animate-pulse-slow[data-v-693a052e]{animation:float-slow-693a052e 15s ease-in-out infinite;will-change:transform,opacity}.animate-pulse-slower[data-v-693a052e]{animation:float-slower-693a052e 18s ease-in-out infinite;will-change:transform,opacity}.animate-pulse-slowest[data-v-693a052e]{animation:float-slowest-693a052e 20s ease-in-out infinite;will-change:transform,opacity}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.plotly-chart[data-v-bf282927]{background:0 0!important}
|
||||
@@ -1 +0,0 @@
|
||||
.plotly-chart[data-v-8daccd7e]{background:transparent!important}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
.glass-card[data-v-eab6d04d]{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffffbf;border:1px solid rgba(0,0,0,.06);box-shadow:0 2px 8px #0000000a}.dark .glass-card[data-v-eab6d04d]{background:#0000004d;border:1px solid rgba(255,255,255,.1);box-shadow:none}.chart-updating[data-v-eab6d04d]{animation:subtle-pulse-eab6d04d .8s ease-in-out}@keyframes subtle-pulse-eab6d04d{0%{transform:scale(1)}50%{transform:scale(1.02)}to{transform:scale(1)}}.chart-container[data-v-eab6d04d]{position:relative;transition:all .3s ease}.chart-container[data-v-eab6d04d]:hover{background:#0000000a}.dark .chart-container[data-v-eab6d04d]:hover{background:#ffffff14}.process-row[data-v-eab6d04d]{transition:all .3s ease}.process-row[data-v-eab6d04d]:hover{background:#00000005;transform:translate(2px)}.dark .process-row[data-v-eab6d04d]:hover{background:#ffffff0d}.process-row-enter-active[data-v-eab6d04d],.process-row-leave-active[data-v-eab6d04d]{transition:all .4s ease}.process-row-enter-from[data-v-eab6d04d]{opacity:0;transform:translateY(-10px) scale(.95)}.process-row-leave-to[data-v-eab6d04d]{opacity:0;transform:translateY(10px) scale(.95)}.process-row-move[data-v-eab6d04d]{transition:transform .4s ease}.cpu-value[data-v-eab6d04d],.memory-value[data-v-eab6d04d]{transition:all .3s ease;padding:2px 6px;border-radius:4px}.cpu-value[data-v-eab6d04d]:hover,.memory-value[data-v-eab6d04d]:hover{background:#f59e0b1a;transform:scale(1.05)}@keyframes value-update-eab6d04d{0%{background:#f59e0b4d}to{background:transparent}}.value-updated[data-v-eab6d04d]{animation:value-update-eab6d04d .6s ease-out}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.glass-card[data-v-fda01968]{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffffbf;border:1px solid #0000000f;box-shadow:0 2px 8px #0000000a}.dark .glass-card[data-v-fda01968]{box-shadow:none;background:#0000004d;border:1px solid #ffffff1a}.chart-updating[data-v-fda01968]{animation:.8s ease-in-out subtle-pulse-fda01968}@keyframes subtle-pulse-fda01968{0%{transform:scale(1)}50%{transform:scale(1.02)}to{transform:scale(1)}}.chart-container[data-v-fda01968]{transition:all .3s;position:relative}.chart-container[data-v-fda01968]:hover{background:#0000000a}.dark .chart-container[data-v-fda01968]:hover{background:#ffffff14}.process-row[data-v-fda01968]{transition:all .3s}.process-row[data-v-fda01968]:hover{background:#00000005;transform:translate(2px)}.dark .process-row[data-v-fda01968]:hover{background:#ffffff0d}.process-row-enter-active[data-v-fda01968],.process-row-leave-active[data-v-fda01968]{transition:all .4s}.process-row-enter-from[data-v-fda01968]{opacity:0;transform:translateY(-10px)scale(.95)}.process-row-leave-to[data-v-fda01968]{opacity:0;transform:translateY(10px)scale(.95)}.process-row-move[data-v-fda01968]{transition:transform .4s}.cpu-value[data-v-fda01968],.memory-value[data-v-fda01968]{border-radius:4px;padding:2px 6px;transition:all .3s}.cpu-value[data-v-fda01968]:hover,.memory-value[data-v-fda01968]:hover{background:#f59e0b1a;transform:scale(1.05)}@keyframes value-update-fda01968{0%{background:#f59e0b4d}to{background:0 0}}.value-updated[data-v-fda01968]{animation:.6s ease-out value-update-fda01968}
|
||||
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 +0,0 @@
|
||||
function e(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}export{e as g};
|
||||
@@ -0,0 +1 @@
|
||||
var e=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n};export{e 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
@@ -0,0 +1 @@
|
||||
.sparkline-card[data-v-dfc36682]{-webkit-backdrop-filter:blur(50px);backdrop-filter:blur(50px);background:#ffffffbf;border:1px solid #0000000f;border-radius:12px;padding:12px 14px;transition:background .3s,border-color .3s,box-shadow .3s;overflow:hidden;box-shadow:0 4px 16px #0000000a,0 1px 3px #00000005}.dark .sparkline-card[data-v-dfc36682]{background:#0006;border:1px solid #ffffff0d;box-shadow:0 4px 16px #0003}.card-header[data-v-dfc36682]{justify-content:space-between;align-items:baseline;margin-bottom:8px;display:flex}.card-title[data-v-dfc36682]{color:#4b5563b3;text-transform:uppercase;letter-spacing:.05em;font-size:11px;font-weight:500;transition:color .3s}.dark .card-title[data-v-dfc36682]{color:#fff9}.card-subtitle[data-v-dfc36682]{color:#4b556380;margin-top:2px;font-size:9px;font-weight:400;transition:color .3s}.dark .card-subtitle[data-v-dfc36682]{color:#fff6}.card-value[data-v-dfc36682]{font-variant-numeric:tabular-nums;font-size:22px;font-weight:700;line-height:1}.card-chart[data-v-dfc36682]{width:100%;height:28px;overflow:hidden}.chart-svg[data-v-dfc36682]{width:100%;height:100%}.chart-loader[data-v-dfc36682]{justify-content:center;align-items:center;height:100%;display:flex}.loader-spinner[data-v-dfc36682]{border:2px solid #fff3;border-radius:50%;width:18px;height:18px;animation:1s linear infinite spin-dfc36682}.chart-text[data-v-dfc36682]{justify-content:center;align-items:center;height:100%;display:flex}.percent-value[data-v-dfc36682]{color:#ffffff80;font-variant-numeric:tabular-nums;font-size:20px;font-weight:500}.sparkline-path[data-v-dfc36682]{transition:d 1s ease-out}@keyframes spin-dfc36682{to{transform:rotate(360deg)}}@media (width>=1024px){.sparkline-card[data-v-dfc36682]{padding:14px 16px}.card-header[data-v-dfc36682]{margin-bottom:10px}.card-title[data-v-dfc36682]{font-size:12px}.card-value[data-v-dfc36682]{font-size:26px}.card-chart[data-v-dfc36682]{height:32px}.percent-value[data-v-dfc36682]{font-size:24px}}
|
||||
@@ -1 +0,0 @@
|
||||
.sparkline-card[data-v-257cbdca]{background:#ffffffbf;border:1px solid rgba(0,0,0,.06);border-radius:12px;padding:12px 14px;-webkit-backdrop-filter:blur(50px);backdrop-filter:blur(50px);overflow:hidden;transition:background .3s ease,border-color .3s ease,box-shadow .3s ease;box-shadow:0 4px 16px #0000000a,0 1px 3px #00000005}.dark .sparkline-card[data-v-257cbdca]{background:#0006;border:1px solid rgba(255,255,255,.05);box-shadow:0 4px 16px #0003}.card-header[data-v-257cbdca]{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:8px}.card-title[data-v-257cbdca]{color:#4b5563b3;font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:.05em;transition:color .3s ease}.dark .card-title[data-v-257cbdca]{color:#fff9}.card-subtitle[data-v-257cbdca]{color:#4b556380;font-size:9px;font-weight:400;margin-top:2px;transition:color .3s ease}.dark .card-subtitle[data-v-257cbdca]{color:#fff6}.card-value[data-v-257cbdca]{font-size:22px;font-weight:700;line-height:1;font-variant-numeric:tabular-nums}.card-chart[data-v-257cbdca]{width:100%;height:28px;overflow:hidden}.chart-svg[data-v-257cbdca]{width:100%;height:100%}.chart-loader[data-v-257cbdca]{display:flex;align-items:center;justify-content:center;height:100%}.loader-spinner[data-v-257cbdca]{width:18px;height:18px;border:2px solid rgba(255,255,255,.2);border-radius:50%;animation:spin-257cbdca 1s linear infinite}.chart-text[data-v-257cbdca]{display:flex;align-items:center;justify-content:center;height:100%}.percent-value[data-v-257cbdca]{font-size:20px;font-weight:500;color:#ffffff80;font-variant-numeric:tabular-nums}.sparkline-path[data-v-257cbdca]{transition:d 1s ease-out}@keyframes spin-257cbdca{to{transform:rotate(360deg)}}@media (min-width: 1024px){.sparkline-card[data-v-257cbdca]{padding:14px 16px}.card-header[data-v-257cbdca]{margin-bottom:10px}.card-title[data-v-257cbdca]{font-size:12px}.card-value[data-v-257cbdca]{font-size:26px}.card-chart[data-v-257cbdca]{height:32px}.percent-value[data-v-257cbdca]{font-size:24px}}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),s=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},c=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},l=(n,r,a)=>(a=n==null?{}:e(i(n)),c(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));export{s as n,l as r,o 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
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 +0,0 @@
|
||||
const n="pymc_pref_";function c(r,t){try{const e=localStorage.getItem(n+r);return e===null?t:JSON.parse(e)}catch(e){return console.warn(`Failed to get preference ${r}:`,e),t}}function o(r,t){try{localStorage.setItem(n+r,JSON.stringify(t))}catch(e){console.warn(`Failed to set preference ${r}:`,e)}}export{c as g,o as s};
|
||||
@@ -0,0 +1 @@
|
||||
var e=`pymc_pref_`;function t(t,n){try{let r=localStorage.getItem(e+t);return r===null?n:JSON.parse(r)}catch(e){return console.warn(`Failed to get preference ${t}:`,e),n}}function n(t,n){try{localStorage.setItem(e+t,JSON.stringify(n))}catch(e){console.warn(`Failed to set preference ${t}:`,e)}}export{n,t};
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +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-CrUX-ZnK.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 +0,0 @@
|
||||
import{M as x,c as s}from"./index-xzvnOpJo.js";const l={7:-7.5,8:-10,9:-12.5,10:-15,11:-17.5,12:-20},d=-116,i=8,u=5;function y(t,e){return t-e}function S(t){return l[t]??l[i]}function f(t,e){const r=e+u;if(t<=e){const o=t<=e-5?0:1;return{bars:o,color:"text-red-600 dark:text-red-400",snr:t,quality:o===0?"none":"poor"}}if(t<r){const n=(t-e)/u<.5?2:3;return{bars:n,color:n===2?"text-orange-600 dark:text-orange-400":"text-yellow-600 dark:text-yellow-400",snr:t,quality:"fair"}}const a=t-r>=10?5:4;return{bars:a,color:a===5?"text-green-600 dark:text-green-400":"text-green-600 dark:text-green-300",snr:t,quality:a===5?"excellent":"good"}}function N(){const t=x(),e=s(()=>t.noiseFloorDbm??d),r=s(()=>t.stats?.config?.radio?.spreading_factor??i),c=s(()=>S(r.value));return{getSignalQuality:o=>{if(!o||o>0||o<-120)return{bars:0,color:"text-gray-400 dark:text-gray-500",snr:-999,quality:"none"};const n=y(o,e.value),g=Math.max(-30,Math.min(20,n));return f(g,c.value)},noiseFloor:e,spreadingFactor:r,minSNR:c}}export{N as u};
|
||||
@@ -0,0 +1 @@
|
||||
import{o as e}from"./runtime-core.esm-bundler-IofF4kUm.js";import{t}from"./system-CCY_Ibb-.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};
|
||||
@@ -0,0 +1 @@
|
||||
import{k as e,z as t}from"./runtime-core.esm-bundler-IofF4kUm.js";var n=`theme-preference`,r=t(`dark`),i=t(!1);function a(e){let t=document.documentElement;e===`dark`?t.classList.add(`dark`):t.classList.remove(`dark`)}function o(){if(i.value)return;let e=localStorage.getItem(n);e&&(e===`light`||e===`dark`)?r.value=e:window.matchMedia(`(prefers-color-scheme: light)`).matches?r.value=`light`:r.value=`dark`,a(r.value),i.value=!0}typeof window<`u`&&o(),e(r,e=>{localStorage.setItem(n,e),a(e)});function s(){return{theme:r,toggleTheme:()=>{r.value=r.value===`dark`?`light`:`dark`},setTheme:e=>{r.value=e},isDark:()=>r.value===`dark`}}export{s as t};
|
||||
File diff suppressed because one or more lines are too long
@@ -8,8 +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-xzvnOpJo.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-p7qyPaY4.css">
|
||||
<script type="module" crossorigin src="/assets/index-CPWfwDmA.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-CrUX-ZnK.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-BxrAyCoo.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/system-CCY_Ibb-.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-C29IW84J.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -1235,10 +1235,10 @@ paths:
|
||||
# ============================================================================
|
||||
# Network Policy
|
||||
# ============================================================================
|
||||
/global_flood_policy:
|
||||
/unscoped_flood_policy:
|
||||
get:
|
||||
tags: [Network Policy]
|
||||
summary: Get global flood policy
|
||||
summary: Get unscoped flood policy
|
||||
description: Retrieve current network flood policy configuration
|
||||
security:
|
||||
- BearerAuth: []
|
||||
@@ -1252,7 +1252,7 @@ paths:
|
||||
type: object
|
||||
post:
|
||||
tags: [Network Policy]
|
||||
summary: Update global flood policy
|
||||
summary: Update unscoped flood policy
|
||||
description: Modify network flood policy settings
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
+39
-41
@@ -46,7 +46,7 @@ def _make_config(**overrides) -> dict:
|
||||
"node_name": "test-node",
|
||||
},
|
||||
"mesh": {
|
||||
"global_flood_allow": True,
|
||||
"unscoped_flood_allow": True,
|
||||
"loop_detect": "off",
|
||||
},
|
||||
"delays": {
|
||||
@@ -228,11 +228,10 @@ class TestFloodForward:
|
||||
assert result is None
|
||||
assert "do not retransmit" in pkt.drop_reason.lower()
|
||||
|
||||
def test_global_flood_deny_plain_flood(self, handler):
|
||||
handler.config["mesh"]["global_flood_allow"] = False
|
||||
def test_unscoped_flood_deny_plain_flood(self, handler):
|
||||
handler.config["mesh"]["unscoped_flood_allow"] = False
|
||||
pkt = _make_flood_packet()
|
||||
# When global_flood_allow=False, flood_forward calls _check_transport_codes
|
||||
# which will fail because there are no transport codes on a plain flood
|
||||
# When unscoped_flood_allow=False, flood_forward should fail on a packet type without a transport code defined
|
||||
result = handler.flood_forward(pkt)
|
||||
assert result is None
|
||||
|
||||
@@ -366,7 +365,8 @@ class TestProcessPacket:
|
||||
|
||||
def test_transport_flood_dispatched(self, handler):
|
||||
pkt = _make_transport_flood_packet()
|
||||
result = handler.process_packet(pkt, snr=5.0)
|
||||
with patch.object(handler, '_check_transport_codes', return_value=(True, "")):
|
||||
result = handler.process_packet(pkt, snr=5.0)
|
||||
assert result is not None
|
||||
fwd_pkt, _ = result
|
||||
assert fwd_pkt.path[-1] == LOCAL_HASH
|
||||
@@ -656,30 +656,40 @@ class TestHashStabilityThroughForwarding:
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# 9. Global flood policy
|
||||
# 9. unscoped flood policy
|
||||
# ===================================================================
|
||||
|
||||
class TestGlobalFloodPolicy:
|
||||
"""global_flood_allow=False blocks plain flood, transport checked."""
|
||||
class TestUnscopedFloodPolicy:
|
||||
"""unscoped_flood_allow=False blocks plain flood, transport checked."""
|
||||
|
||||
def test_flood_blocked_by_policy(self, handler):
|
||||
handler.config["mesh"]["global_flood_allow"] = False
|
||||
handler.config["mesh"]["unscoped_flood_allow"] = False
|
||||
pkt = _make_flood_packet()
|
||||
result = handler.flood_forward(pkt)
|
||||
assert result is None
|
||||
|
||||
def test_direct_unaffected_by_flood_policy(self, handler):
|
||||
handler.config["mesh"]["global_flood_allow"] = False
|
||||
handler.config["mesh"]["unscoped_flood_allow"] = False
|
||||
pkt = _make_direct_packet()
|
||||
result = handler.direct_forward(pkt)
|
||||
assert result is not None # direct is not blocked by flood policy
|
||||
|
||||
def test_transport_flood_checked_when_policy_off(self, handler):
|
||||
handler.config["mesh"]["global_flood_allow"] = False
|
||||
def test_transport_flood_unaffected_by_unscoped_policy(self, handler):
|
||||
# unscoped_flood_allow controls only plain FLOOD packets.
|
||||
# Transport floods are validated via _check_transport_codes regardless.
|
||||
# With a configured scope that allows, transport flood passes even when
|
||||
# unscoped traffic is denied — the two settings are fully independent.
|
||||
handler.config["mesh"]["unscoped_flood_allow"] = False
|
||||
pkt = _make_transport_flood_packet()
|
||||
# Will call _check_transport_codes which will fail (no storage keys)
|
||||
result = handler.flood_forward(pkt)
|
||||
assert result is None
|
||||
with patch.object(handler, '_check_transport_codes', return_value=(True, "")):
|
||||
result = handler.flood_forward(pkt)
|
||||
assert result is not None # transport flood passes; unscoped=False did not block it
|
||||
|
||||
def test_transport_flood_denied_with_no_keys(self, handler):
|
||||
# Scope Not Configured = denied, regardless of unscoped_flood_allow.
|
||||
pkt = _make_transport_flood_packet()
|
||||
result = handler.flood_forward(pkt) # no mocking — real _check_transport_codes
|
||||
assert result is None # denied because no transport keys configured
|
||||
|
||||
|
||||
class TestFloodLoopDetection:
|
||||
@@ -812,7 +822,7 @@ class TestGetDropReason:
|
||||
assert "Path too long" in reason
|
||||
|
||||
def test_flood_policy_reason(self, handler):
|
||||
handler.config["mesh"]["global_flood_allow"] = False
|
||||
handler.config["mesh"]["unscoped_flood_allow"] = False
|
||||
pkt = _make_flood_packet()
|
||||
reason = handler._get_drop_reason(pkt)
|
||||
assert "flood" in reason.lower()
|
||||
@@ -838,7 +848,8 @@ class TestTransportForwarding:
|
||||
|
||||
def test_transport_flood_appends_path(self, handler):
|
||||
pkt = _make_transport_flood_packet(path=b"\x11")
|
||||
result = handler.process_packet(pkt, snr=5.0)
|
||||
with patch.object(handler, '_check_transport_codes', return_value=(True, "")):
|
||||
result = handler.process_packet(pkt, snr=5.0)
|
||||
assert result is not None
|
||||
fwd_pkt, _ = result
|
||||
assert fwd_pkt.path[-1] == LOCAL_HASH
|
||||
@@ -853,7 +864,8 @@ class TestTransportForwarding:
|
||||
|
||||
def test_transport_codes_preserved_after_flood(self, handler):
|
||||
pkt = _make_transport_flood_packet(transport_codes=(0xAAAA, 0xBBBB))
|
||||
result = handler.process_packet(pkt, snr=5.0)
|
||||
with patch.object(handler, '_check_transport_codes', return_value=(True, "")):
|
||||
result = handler.process_packet(pkt, snr=5.0)
|
||||
assert result is not None
|
||||
fwd_pkt, _ = result
|
||||
assert fwd_pkt.transport_codes == [0xAAAA, 0xBBBB]
|
||||
@@ -1127,23 +1139,9 @@ GOOD_PACKETS = [
|
||||
lambda: _make_direct_packet(payload=b"\xFF\xEE",
|
||||
path=bytes([LOCAL_HASH] + list(range(10))))),
|
||||
|
||||
("good_transport_flood_basic",
|
||||
"Transport flood, basic payload + transport codes",
|
||||
lambda: _make_transport_flood_packet(payload=b"\x01\x02\x03\x04")),
|
||||
|
||||
("good_transport_flood_with_path",
|
||||
"Transport flood, existing 3-hop path",
|
||||
lambda: _make_transport_flood_packet(payload=b"\xAA\xBB", path=b"\x11\x22\x33")),
|
||||
|
||||
("good_transport_flood_max_codes",
|
||||
"Transport flood, max uint16 transport codes",
|
||||
lambda: _make_transport_flood_packet(payload=b"\xFF",
|
||||
transport_codes=(0xFFFF, 0xFFFF))),
|
||||
|
||||
("good_transport_direct_basic",
|
||||
"Transport direct, basic hop to us",
|
||||
lambda: _make_transport_direct_packet(payload=b"\x01\x02")),
|
||||
|
||||
("good_transport_direct_long_path",
|
||||
"Transport direct, 5 remaining hops",
|
||||
lambda: _make_transport_direct_packet(
|
||||
@@ -1202,12 +1200,12 @@ BAD_PACKETS = [
|
||||
"no path"),
|
||||
|
||||
("bad_flood_policy_off",
|
||||
"Plain flood when global_flood_allow=False (needs config override)",
|
||||
"Plain flood when unscoped_flood_allow=False (needs config override)",
|
||||
lambda: _make_flood_packet(payload=b"\x01\x02"),
|
||||
"transport codes"),
|
||||
"unscoped flood"),
|
||||
|
||||
("bad_transport_flood_policy_off",
|
||||
"Transport flood when policy off (no valid transport key)",
|
||||
("bad_transport_flood_no_keys",
|
||||
"Transport flood with no configured transport keys — always denied",
|
||||
lambda: _make_transport_flood_packet(payload=b"\x01\x02"),
|
||||
"transport"),
|
||||
|
||||
@@ -1323,9 +1321,9 @@ class TestBadPacketArray:
|
||||
BAD_PACKETS, ids=_bad_ids,
|
||||
)
|
||||
def test_process_packet_drops(self, handler, name, desc, builder, expected_reason):
|
||||
# Two entries need global_flood_allow=False
|
||||
# Two entries need unscoped_flood_allow=False
|
||||
if "policy_off" in name:
|
||||
handler.config["mesh"]["global_flood_allow"] = False
|
||||
handler.config["mesh"]["unscoped_flood_allow"] = False
|
||||
|
||||
pkt = builder()
|
||||
result = handler.process_packet(pkt, snr=5.0)
|
||||
@@ -1337,7 +1335,7 @@ class TestBadPacketArray:
|
||||
)
|
||||
def test_drop_reason_set(self, handler, name, desc, builder, expected_reason):
|
||||
if "policy_off" in name:
|
||||
handler.config["mesh"]["global_flood_allow"] = False
|
||||
handler.config["mesh"]["unscoped_flood_allow"] = False
|
||||
|
||||
pkt = builder()
|
||||
handler.process_packet(pkt, snr=5.0)
|
||||
@@ -1353,7 +1351,7 @@ class TestBadPacketArray:
|
||||
def test_bad_packet_not_marked_seen(self, handler, name, desc, builder, expected_reason):
|
||||
"""Dropped packets must NOT pollute the seen cache."""
|
||||
if "policy_off" in name:
|
||||
handler.config["mesh"]["global_flood_allow"] = False
|
||||
handler.config["mesh"]["unscoped_flood_allow"] = False
|
||||
|
||||
pkt = builder()
|
||||
handler.process_packet(pkt, snr=5.0)
|
||||
|
||||
@@ -7,7 +7,7 @@ objects to verify:
|
||||
- Loop detection modes (off, minimal, moderate, strict) with real path bytes
|
||||
- Flood re-forwarding prevention (own hash already in path)
|
||||
- Multi-byte hash mode interaction with loop/dedup
|
||||
- Global flood policy enforcement
|
||||
- Unscoped flood policy enforcement
|
||||
- mark_seen / is_duplicate cache behaviour
|
||||
- do_not_retransmit flag handling
|
||||
"""
|
||||
@@ -50,7 +50,7 @@ def _make_handler(
|
||||
loop_detect="off",
|
||||
path_hash_mode=0,
|
||||
local_hash_bytes=None,
|
||||
global_flood_allow=True,
|
||||
unscoped_flood_allow=True,
|
||||
):
|
||||
"""Create a RepeaterHandler with real engine logic, mocking only hardware."""
|
||||
lhb = local_hash_bytes or LOCAL_HASH_BYTES
|
||||
@@ -64,7 +64,7 @@ def _make_handler(
|
||||
"node_name": "test-node",
|
||||
},
|
||||
"mesh": {
|
||||
"global_flood_allow": global_flood_allow,
|
||||
"unscoped_flood_allow": unscoped_flood_allow,
|
||||
"loop_detect": loop_detect,
|
||||
"path_hash_mode": path_hash_mode,
|
||||
},
|
||||
@@ -398,29 +398,29 @@ class TestLoopDetectionMultiByte:
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# 5. Global flood policy
|
||||
# 5. Unscoped flood policy
|
||||
# ===================================================================
|
||||
|
||||
|
||||
class TestGlobalFloodPolicy:
|
||||
"""Test global_flood_allow=False blocks flood packets."""
|
||||
class TestUnscopedFloodPolicy:
|
||||
"""Test unscoped=False blocks flood packets."""
|
||||
|
||||
def test_global_flood_disabled_drops_flood(self):
|
||||
h = _make_handler(global_flood_allow=False)
|
||||
def test_unscoped_flood_disabled_drops_flood(self):
|
||||
h = _make_handler(unscoped_flood_allow=False)
|
||||
pkt = _make_flood_packet(payload=b"\x01\x02")
|
||||
result = h.flood_forward(pkt)
|
||||
assert result is None
|
||||
assert pkt.drop_reason is not None
|
||||
|
||||
def test_global_flood_enabled_allows_flood(self):
|
||||
h = _make_handler(global_flood_allow=True)
|
||||
def test_unscoped_flood_enabled_allows_flood(self):
|
||||
h = _make_handler(unscoped_flood_allow=True)
|
||||
pkt = _make_flood_packet(payload=b"\x01\x02")
|
||||
result = h.flood_forward(pkt)
|
||||
assert result is not None
|
||||
|
||||
def test_transport_flood_without_codes_drops(self):
|
||||
"""ROUTE_TYPE_TRANSPORT_FLOOD with global_flood_allow=False and no valid codes."""
|
||||
h = _make_handler(global_flood_allow=False)
|
||||
"""ROUTE_TYPE_TRANSPORT_FLOOD with unscoped_flood_allow=False and no valid codes."""
|
||||
h = _make_handler(unscoped_flood_allow=False)
|
||||
# Nullify the storage to ensure transport code check fails
|
||||
h.storage = None
|
||||
pkt = Packet()
|
||||
|
||||
@@ -74,7 +74,7 @@ def _make_handler(path_hash_mode=0, local_hash_bytes=None):
|
||||
"node_name": "test-node",
|
||||
},
|
||||
"mesh": {
|
||||
"global_flood_allow": True,
|
||||
"unscoped_flood_allow": True,
|
||||
"loop_detect": "off",
|
||||
"path_hash_mode": path_hash_mode,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user