mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-06-15 02:34:51 +02:00
Add public key to identity details and enhance companion configuration handling
- Updated IdentityManager to include public key in identity details when retrieving identities. - Introduced a new method in RepeaterDaemon for adding companions from configuration, supporting hot-reload functionality. - Enhanced error handling for companion registration, ensuring proper validation of identity keys and settings. - Updated API endpoints to include configured companions in the response, improving visibility of companion status and configuration.
This commit is contained in:
@@ -51,6 +51,7 @@ class IdentityManager:
|
||||
"name": name,
|
||||
"type": id_type,
|
||||
"address": identity.get_address_bytes().hex() if identity else "N/A",
|
||||
"public_key": identity.get_public_key().hex() if identity else None,
|
||||
}
|
||||
)
|
||||
return identities
|
||||
|
||||
@@ -511,6 +511,153 @@ class RepeaterDaemon:
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load companion '{name}': {e}", exc_info=True)
|
||||
|
||||
async def add_companion_from_config(self, comp_config: dict) -> None:
|
||||
"""
|
||||
Load a single companion from config and register it (hot-reload).
|
||||
Creates RepeaterCompanionBridge, CompanionFrameServer, starts the server,
|
||||
and registers with identity_manager. Raises on error.
|
||||
"""
|
||||
from pymc_core import LocalIdentity
|
||||
from pymc_core.companion.models import Channel
|
||||
|
||||
from repeater.companion import CompanionFrameServer, RepeaterCompanionBridge
|
||||
from repeater.companion.constants import DEFAULT_PUBLIC_CHANNEL_SECRET
|
||||
|
||||
name = comp_config.get("name")
|
||||
identity_key = comp_config.get("identity_key")
|
||||
settings = comp_config.get("settings") or {}
|
||||
|
||||
if not name or not identity_key:
|
||||
raise ValueError("Companion config missing name or identity_key")
|
||||
|
||||
if isinstance(identity_key, str):
|
||||
try:
|
||||
identity_key_bytes = bytes.fromhex(identity_key)
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Companion '{name}' identity_key invalid hex: {e}") from e
|
||||
elif isinstance(identity_key, bytes):
|
||||
identity_key_bytes = identity_key
|
||||
else:
|
||||
raise ValueError(f"Companion '{name}' identity_key has unknown type")
|
||||
|
||||
if len(identity_key_bytes) not in (32, 64):
|
||||
raise ValueError(
|
||||
f"Companion '{name}' identity_key must be 32 bytes (hex) or 64 bytes (MeshCore firmware key)"
|
||||
)
|
||||
|
||||
# Already registered?
|
||||
if name in self.identity_manager.named_identities:
|
||||
raise ValueError(f"Companion '{name}' is already registered")
|
||||
|
||||
identity = LocalIdentity(seed=identity_key_bytes)
|
||||
pubkey = identity.get_public_key()
|
||||
companion_hash = pubkey[0]
|
||||
companion_hash_str = f"0x{companion_hash:02x}"
|
||||
|
||||
if companion_hash in self.companion_bridges:
|
||||
raise ValueError(f"Companion with hash 0x{companion_hash:02x} already loaded")
|
||||
|
||||
sqlite_handler = None
|
||||
if self.repeater_handler and self.repeater_handler.storage:
|
||||
sqlite_handler = self.repeater_handler.storage.sqlite_handler
|
||||
|
||||
radio_config = (
|
||||
self.repeater_handler.radio_config
|
||||
if self.repeater_handler
|
||||
else self.config.get("radio", {})
|
||||
)
|
||||
|
||||
node_name = settings.get("node_name", name)
|
||||
tcp_port = settings.get("tcp_port", 5000)
|
||||
bind_address = settings.get("bind_address", "0.0.0.0")
|
||||
|
||||
bridge = RepeaterCompanionBridge(
|
||||
identity=identity,
|
||||
packet_injector=self.router.inject_packet,
|
||||
node_name=node_name,
|
||||
radio_config=radio_config,
|
||||
sqlite_handler=sqlite_handler,
|
||||
companion_hash=companion_hash_str,
|
||||
)
|
||||
|
||||
if sqlite_handler:
|
||||
contact_rows = sqlite_handler.companion_load_contacts(companion_hash_str)
|
||||
if contact_rows:
|
||||
records = []
|
||||
for row in contact_rows:
|
||||
d = dict(row)
|
||||
d["public_key"] = d.pop("pubkey", d.get("public_key", b""))
|
||||
records.append(d)
|
||||
bridge.contacts.load_from_dicts(records)
|
||||
|
||||
channel_rows = sqlite_handler.companion_load_channels(companion_hash_str)
|
||||
for row in channel_rows:
|
||||
s = row.get("secret", b"")
|
||||
if isinstance(s, bytes):
|
||||
raw = s
|
||||
elif isinstance(s, (bytearray, memoryview)):
|
||||
raw = bytes(s)
|
||||
elif s:
|
||||
raw = bytes.fromhex(s if isinstance(s, str) else str(s))
|
||||
else:
|
||||
raw = b""
|
||||
if len(raw) < 32:
|
||||
raw = raw + b"\x00" * (32 - len(raw))
|
||||
elif len(raw) > 32:
|
||||
raw = raw[:32]
|
||||
ch = Channel(name=row.get("name", ""), secret=raw)
|
||||
bridge.channels.set(row.get("channel_idx", 0), ch)
|
||||
|
||||
for msg_dict in sqlite_handler.companion_load_messages(companion_hash_str):
|
||||
from pymc_core.companion.models import QueuedMessage
|
||||
|
||||
sk = msg_dict.get("sender_key", b"")
|
||||
if isinstance(sk, str):
|
||||
sk = bytes.fromhex(sk)
|
||||
bridge.message_queue.push(
|
||||
QueuedMessage(
|
||||
sender_key=sk,
|
||||
txt_type=msg_dict.get("txt_type", 0),
|
||||
timestamp=msg_dict.get("timestamp", 0),
|
||||
text=msg_dict.get("text", ""),
|
||||
is_channel=bool(msg_dict.get("is_channel", False)),
|
||||
channel_idx=msg_dict.get("channel_idx", 0),
|
||||
path_len=msg_dict.get("path_len", 0),
|
||||
)
|
||||
)
|
||||
|
||||
if bridge.get_channel(0) is None:
|
||||
bridge.set_channel(0, "Public", DEFAULT_PUBLIC_CHANNEL_SECRET)
|
||||
|
||||
self.companion_bridges[companion_hash] = bridge
|
||||
|
||||
frame_server = CompanionFrameServer(
|
||||
bridge=bridge,
|
||||
companion_hash=companion_hash_str,
|
||||
port=tcp_port,
|
||||
bind_address=bind_address,
|
||||
sqlite_handler=sqlite_handler,
|
||||
local_hash=self.local_hash,
|
||||
stats_getter=self._get_companion_stats,
|
||||
control_handler=(
|
||||
self.discovery_helper.control_handler if self.discovery_helper else None
|
||||
),
|
||||
)
|
||||
await frame_server.start()
|
||||
self.companion_frame_servers.append(frame_server)
|
||||
|
||||
self.identity_manager.register_identity(
|
||||
name=name,
|
||||
identity=identity,
|
||||
config=comp_config,
|
||||
identity_type="companion",
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Hot-reload: Loaded companion '{name}': hash=0x{companion_hash:02x}, "
|
||||
f"port={tcp_port}, bind={bind_address}"
|
||||
)
|
||||
|
||||
async def _on_raw_rx_for_companions(self, data: bytes, rssi: int, snr: float) -> None:
|
||||
"""Raw RX subscriber: push PUSH_CODE_LOG_RX_DATA (0x88) to connected companion clients."""
|
||||
servers = getattr(self, "companion_frame_servers", [])
|
||||
|
||||
+228
-54
@@ -1234,9 +1234,9 @@ class APIEndpoints:
|
||||
self.config["radio"]["cad"]["min_threshold"] = min_val
|
||||
|
||||
config_path = getattr(self, "_config_path", "/etc/pymc_repeater/config.yaml")
|
||||
saved, err = self.config_manager.save_to_file()
|
||||
saved = self.config_manager.save_to_file()
|
||||
if not saved:
|
||||
return self._error(err or "Failed to save configuration to file")
|
||||
return self._error("Failed to save configuration to file")
|
||||
|
||||
logger.info(
|
||||
f"Saved CAD settings to config: peak={peak}, min={min_val}, rate={detection_rate:.1f}%"
|
||||
@@ -1766,14 +1766,14 @@ class APIEndpoints:
|
||||
|
||||
# Update the configuration file using ConfigManager
|
||||
try:
|
||||
saved, err = self.config_manager.save_to_file()
|
||||
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'}"
|
||||
)
|
||||
else:
|
||||
logger.error(f"Failed to save global flood policy to file: {err}")
|
||||
return self._error(err or "Failed to save configuration to file")
|
||||
logger.error("Failed to save global 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}")
|
||||
return self._error(f"Failed to save configuration to file: {e}")
|
||||
@@ -1961,7 +1961,7 @@ class APIEndpoints:
|
||||
identities_config = self.config.get("identities", {})
|
||||
room_servers = identities_config.get("room_servers") or []
|
||||
|
||||
# Enhance with config data
|
||||
# Enhance with config data (room servers)
|
||||
configured = []
|
||||
for room_config in room_servers:
|
||||
name = room_config.get("name")
|
||||
@@ -1988,12 +1988,46 @@ class APIEndpoints:
|
||||
}
|
||||
)
|
||||
|
||||
# Configured companions (same pattern as room servers)
|
||||
companions = identities_config.get("companions") or []
|
||||
configured_companions = []
|
||||
for comp_config in companions:
|
||||
name = comp_config.get("name")
|
||||
identity_key = comp_config.get("identity_key", "")
|
||||
settings = comp_config.get("settings", {})
|
||||
|
||||
matching = next(
|
||||
(
|
||||
r
|
||||
for r in registered_identities
|
||||
if r["name"] == f"companion:{name}"
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
configured_companions.append(
|
||||
{
|
||||
"name": name,
|
||||
"type": "companion",
|
||||
"identity_key": (
|
||||
identity_key[:16] + "..." if len(identity_key) > 16 else identity_key
|
||||
),
|
||||
"identity_key_length": len(identity_key),
|
||||
"settings": settings,
|
||||
"hash": matching["hash"] if matching else None,
|
||||
"public_key": matching.get("public_key") if matching else None,
|
||||
"registered": matching is not None,
|
||||
}
|
||||
)
|
||||
|
||||
return self._success(
|
||||
{
|
||||
"registered": registered_identities,
|
||||
"configured": configured,
|
||||
"configured_companions": configured_companions,
|
||||
"total_registered": len(registered_identities),
|
||||
"total_configured": len(configured),
|
||||
"total_configured_companions": len(configured_companions),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2019,14 +2053,17 @@ class APIEndpoints:
|
||||
|
||||
identities_config = self.config.get("identities", {})
|
||||
room_servers = identities_config.get("room_servers") or []
|
||||
companions = identities_config.get("companions") or []
|
||||
|
||||
# Find the identity in config
|
||||
# Find the identity in config (room servers first, then companions)
|
||||
identity_config = next((r for r in room_servers if r.get("name") == name), None)
|
||||
if identity_config is None:
|
||||
identity_config = next((c for c in companions if c.get("name") == name), None)
|
||||
|
||||
if not identity_config:
|
||||
return self._error(f"Identity '{name}' not found")
|
||||
|
||||
# Get runtime info if available
|
||||
# Get runtime info if available (identity_manager uses name for both types)
|
||||
if self.daemon_instance and hasattr(self.daemon_instance, "identity_manager"):
|
||||
identity_manager = self.daemon_instance.identity_manager
|
||||
runtime_info = identity_manager.get_identity_by_name(name)
|
||||
@@ -2087,11 +2124,18 @@ class APIEndpoints:
|
||||
if not name:
|
||||
return self._error("Missing required field: name")
|
||||
|
||||
# Validate passwords are different if both provided
|
||||
admin_pw = settings.get("admin_password")
|
||||
guest_pw = settings.get("guest_password")
|
||||
if admin_pw and guest_pw and admin_pw == guest_pw:
|
||||
return self._error("admin_password and guest_password must be different")
|
||||
# Validate identity type
|
||||
if identity_type not in ["room_server", "companion"]:
|
||||
return self._error(
|
||||
f"Invalid identity type: {identity_type}. Only 'room_server' and 'companion' are supported."
|
||||
)
|
||||
|
||||
# Room server: validate passwords are different if both provided
|
||||
if identity_type == "room_server":
|
||||
admin_pw = settings.get("admin_password")
|
||||
guest_pw = settings.get("guest_password")
|
||||
if admin_pw and guest_pw and admin_pw == guest_pw:
|
||||
return self._error("admin_password and guest_password must be different")
|
||||
|
||||
# Auto-generate identity key if not provided
|
||||
key_was_generated = False
|
||||
@@ -2106,38 +2150,58 @@ class APIEndpoints:
|
||||
logger.error(f"Failed to auto-generate identity key: {gen_error}")
|
||||
return self._error(f"Failed to auto-generate identity key: {gen_error}")
|
||||
|
||||
# Validate identity type
|
||||
if identity_type not in ["room_server"]:
|
||||
return self._error(
|
||||
f"Invalid identity type: {identity_type}. Only 'room_server' is supported."
|
||||
)
|
||||
|
||||
# Check if identity already exists
|
||||
identities_config = self.config.get("identities", {})
|
||||
room_servers = identities_config.get("room_servers") or []
|
||||
|
||||
if any(r.get("name") == name for r in room_servers):
|
||||
return self._error(f"Identity with name '{name}' already exists")
|
||||
|
||||
# Create new identity config
|
||||
new_identity = {
|
||||
"name": name,
|
||||
"identity_key": identity_key,
|
||||
"type": identity_type,
|
||||
"settings": settings,
|
||||
}
|
||||
|
||||
# Add to config
|
||||
room_servers.append(new_identity)
|
||||
|
||||
if "identities" not in self.config:
|
||||
self.config["identities"] = {}
|
||||
self.config["identities"]["room_servers"] = room_servers
|
||||
|
||||
if identity_type == "companion":
|
||||
# Companion: validate key length (32 or 64 bytes hex), normalize settings
|
||||
if identity_key:
|
||||
try:
|
||||
key_bytes = bytes.fromhex(identity_key)
|
||||
if len(key_bytes) not in (32, 64):
|
||||
return self._error(
|
||||
"Companion identity_key must be 32 or 64 bytes (64 or 128 hex chars)"
|
||||
)
|
||||
except ValueError:
|
||||
return self._error("Companion identity_key must be a valid hex string")
|
||||
|
||||
companions = identities_config.get("companions") or []
|
||||
if any(c.get("name") == name for c in companions):
|
||||
return self._error(f"Companion with name '{name}' already exists")
|
||||
|
||||
comp_settings = {
|
||||
"node_name": settings.get("node_name") or name,
|
||||
"tcp_port": settings.get("tcp_port", 5000),
|
||||
"bind_address": settings.get("bind_address", "0.0.0.0"),
|
||||
}
|
||||
new_identity = {
|
||||
"name": name,
|
||||
"identity_key": identity_key,
|
||||
"type": identity_type,
|
||||
"settings": comp_settings,
|
||||
}
|
||||
companions.append(new_identity)
|
||||
self.config["identities"]["companions"] = companions
|
||||
else:
|
||||
# Room server
|
||||
room_servers = identities_config.get("room_servers") or []
|
||||
if any(r.get("name") == name for r in room_servers):
|
||||
return self._error(f"Identity with name '{name}' already exists")
|
||||
|
||||
new_identity = {
|
||||
"name": name,
|
||||
"identity_key": identity_key,
|
||||
"type": identity_type,
|
||||
"settings": settings,
|
||||
}
|
||||
room_servers.append(new_identity)
|
||||
self.config["identities"]["room_servers"] = room_servers
|
||||
|
||||
# Save to file
|
||||
saved, err = self.config_manager.save_to_file()
|
||||
saved = self.config_manager.save_to_file()
|
||||
if not saved:
|
||||
return self._error(err or "Failed to save configuration to file")
|
||||
return self._error("Failed to save configuration to file")
|
||||
|
||||
logger.info(
|
||||
f"Created new identity: {name} (type: {identity_type}){' with auto-generated key' if key_was_generated else ''}"
|
||||
@@ -2145,7 +2209,7 @@ class APIEndpoints:
|
||||
|
||||
# Hot reload - register identity immediately
|
||||
registration_success = False
|
||||
if self.daemon_instance:
|
||||
if identity_type == "room_server" and self.daemon_instance:
|
||||
try:
|
||||
from pymc_core import LocalIdentity
|
||||
|
||||
@@ -2188,11 +2252,35 @@ class APIEndpoints:
|
||||
f"Failed to hot reload identity {name}: {reg_error}", exc_info=True
|
||||
)
|
||||
|
||||
message = (
|
||||
f"Identity '{name}' created successfully and activated immediately!"
|
||||
if registration_success
|
||||
else f"Identity '{name}' created successfully. Restart required to activate."
|
||||
)
|
||||
elif identity_type == "companion" and self.daemon_instance and self.event_loop:
|
||||
try:
|
||||
import asyncio
|
||||
|
||||
future = asyncio.run_coroutine_threadsafe(
|
||||
self.daemon_instance.add_companion_from_config(new_identity),
|
||||
self.event_loop,
|
||||
)
|
||||
future.result(timeout=15)
|
||||
registration_success = True
|
||||
logger.info(f"Hot reload: Companion '{name}' activated immediately")
|
||||
except Exception as comp_error:
|
||||
logger.warning(
|
||||
f"Hot reload companion '{name}' failed: {comp_error}. Restart required to activate.",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
if identity_type == "companion":
|
||||
message = (
|
||||
f"Companion '{name}' created successfully and activated immediately!"
|
||||
if registration_success
|
||||
else f"Companion '{name}' created successfully. Restart required to activate."
|
||||
)
|
||||
else:
|
||||
message = (
|
||||
f"Identity '{name}' created successfully and activated immediately!"
|
||||
if registration_success
|
||||
else f"Identity '{name}' created successfully. Restart required to activate."
|
||||
)
|
||||
if key_was_generated:
|
||||
message += " Identity key was auto-generated."
|
||||
|
||||
@@ -2242,10 +2330,63 @@ class APIEndpoints:
|
||||
if not name:
|
||||
return self._error("Missing required field: name")
|
||||
|
||||
identities_config = self.config.get("identities", {})
|
||||
room_servers = identities_config.get("room_servers") or []
|
||||
identity_type = data.get("type", "room_server")
|
||||
if identity_type not in ["room_server", "companion"]:
|
||||
return self._error(
|
||||
f"Invalid identity type: {identity_type}. Only 'room_server' and 'companion' are supported."
|
||||
)
|
||||
|
||||
# Find the identity
|
||||
identities_config = self.config.get("identities", {})
|
||||
|
||||
if identity_type == "companion":
|
||||
companions = identities_config.get("companions") or []
|
||||
identity_index = next(
|
||||
(i for i, c in enumerate(companions) if c.get("name") == name), None
|
||||
)
|
||||
if identity_index is None:
|
||||
return self._error(f"Companion '{name}' not found")
|
||||
identity = companions[identity_index]
|
||||
|
||||
if "new_name" in data:
|
||||
new_name = data["new_name"]
|
||||
if any(
|
||||
c.get("name") == new_name
|
||||
for i, c in enumerate(companions)
|
||||
if i != identity_index
|
||||
):
|
||||
return self._error(f"Companion with name '{new_name}' already exists")
|
||||
identity["name"] = new_name
|
||||
|
||||
if "identity_key" in data and data["identity_key"]:
|
||||
new_key = data["identity_key"]
|
||||
if "..." not in new_key:
|
||||
try:
|
||||
key_bytes = bytes.fromhex(new_key)
|
||||
if len(key_bytes) in (32, 64):
|
||||
identity["identity_key"] = new_key
|
||||
logger.info(f"Updated identity_key for companion '{name}'")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if "settings" in data:
|
||||
if "settings" not in identity:
|
||||
identity["settings"] = {}
|
||||
# Only allow companion settings
|
||||
for k, v in data["settings"].items():
|
||||
if k in ("node_name", "tcp_port", "bind_address"):
|
||||
identity["settings"][k] = v
|
||||
|
||||
companions[identity_index] = identity
|
||||
self.config["identities"]["companions"] = companions
|
||||
saved = self.config_manager.save_to_file()
|
||||
if not saved:
|
||||
return self._error("Failed to save configuration to file")
|
||||
logger.info(f"Updated companion: {name}")
|
||||
message = f"Companion '{name}' updated successfully. Restart required to apply changes."
|
||||
return self._success(identity, message=message)
|
||||
|
||||
# Room server path
|
||||
room_servers = identities_config.get("room_servers") or []
|
||||
identity_index = next(
|
||||
(i for i, r in enumerate(room_servers) if r.get("name") == name), None
|
||||
)
|
||||
@@ -2298,9 +2439,9 @@ class APIEndpoints:
|
||||
room_servers[identity_index] = identity
|
||||
self.config["identities"]["room_servers"] = room_servers
|
||||
|
||||
saved, err = self.config_manager.save_to_file()
|
||||
saved = self.config_manager.save_to_file()
|
||||
if not saved:
|
||||
return self._error(err or "Failed to save configuration to file")
|
||||
return self._error("Failed to save configuration to file")
|
||||
|
||||
logger.info(f"Updated identity: {name}")
|
||||
|
||||
@@ -2380,9 +2521,9 @@ class APIEndpoints:
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
def delete_identity(self, name=None):
|
||||
def delete_identity(self, name=None, type=None):
|
||||
"""
|
||||
DELETE /api/delete_identity?name=<name> - Delete an identity
|
||||
DELETE /api/delete_identity?name=<name>&type=<room_server|companion> - Delete an identity
|
||||
"""
|
||||
# Enable CORS for this endpoint only if configured
|
||||
self._set_cors_headers()
|
||||
@@ -2399,7 +2540,40 @@ class APIEndpoints:
|
||||
if not name:
|
||||
return self._error("Missing name parameter")
|
||||
|
||||
identity_type = (type or "room_server").lower()
|
||||
if identity_type not in ["room_server", "companion"]:
|
||||
return self._error(
|
||||
f"Invalid type: {type}. Use 'room_server' or 'companion'."
|
||||
)
|
||||
|
||||
identities_config = self.config.get("identities", {})
|
||||
|
||||
if identity_type == "companion":
|
||||
companions = identities_config.get("companions") or []
|
||||
initial_count = len(companions)
|
||||
companions = [c for c in companions if c.get("name") != name]
|
||||
if len(companions) == initial_count:
|
||||
return self._error(f"Companion '{name}' not found")
|
||||
self.config["identities"]["companions"] = companions
|
||||
saved = self.config_manager.save_to_file()
|
||||
if not saved:
|
||||
return self._error("Failed to save configuration to file")
|
||||
logger.info(f"Deleted companion: {name}")
|
||||
unregister_success = False
|
||||
if self.daemon_instance and hasattr(self.daemon_instance, "identity_manager"):
|
||||
identity_manager = self.daemon_instance.identity_manager
|
||||
if name in identity_manager.named_identities:
|
||||
del identity_manager.named_identities[name]
|
||||
logger.info(f"Removed companion {name} from named_identities")
|
||||
unregister_success = True
|
||||
message = (
|
||||
f"Companion '{name}' deleted successfully and deactivated immediately!"
|
||||
if unregister_success
|
||||
else f"Companion '{name}' deleted successfully. Restart required to fully remove."
|
||||
)
|
||||
return self._success({"name": name}, message=message)
|
||||
|
||||
# Room server path
|
||||
room_servers = identities_config.get("room_servers") or []
|
||||
|
||||
# Find and remove the identity
|
||||
@@ -2412,9 +2586,9 @@ class APIEndpoints:
|
||||
# Update config
|
||||
self.config["identities"]["room_servers"] = room_servers
|
||||
|
||||
saved, err = self.config_manager.save_to_file()
|
||||
saved = self.config_manager.save_to_file()
|
||||
if not saved:
|
||||
return self._error(err or "Failed to save configuration to file")
|
||||
return self._error("Failed to save configuration to file")
|
||||
|
||||
logger.info(f"Deleted identity: {name}")
|
||||
|
||||
|
||||
+1
-1
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
@@ -1 +1 @@
|
||||
import{a as p,b as n,g as m,e as t,s as g,t as s,j as d,p as l}from"./index-sHch0610.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"},j={class:"flex gap-3"},_=p({__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,u=i=>{i.target===i.currentTarget&&r("close")},k={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:u,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",k[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",j,[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)])])])):m("",!0)}});export{_};
|
||||
import{a as p,b as n,g as m,e as t,s as g,t as s,j as d,p as l}from"./index-DyUIpN7m.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"},j={class:"flex gap-3"},_=p({__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,u=i=>{i.target===i.currentTarget&&r("close")},k={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:u,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",k[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",j,[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)])])])):m("",!0)}});export{_};
|
||||
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
@@ -1 +1 @@
|
||||
import{a as e,b as r,i as o,p as n}from"./index-sHch0610.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};
|
||||
import{a as e,b as r,i as o,p as n}from"./index-DyUIpN7m.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};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{a as k,b as o,g,e as r,j as a,t as p,s as x,p as s}from"./index-DyUIpN7m.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")},b={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"},u={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",b[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",u[t.variant]])}," OK ",2)])])])):g("",!0)}});export{B as _};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+6
-6
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
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 @@
|
||||
.plotly-chart[data-v-8daccd7e]{background:transparent!important}
|
||||
@@ -1 +0,0 @@
|
||||
.plotly-chart[data-v-c51a7a30]{background:transparent!important}
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{L as J,a as zl,r as ut,o as Hl,$ as Ul,P as Rn,D as ql,b as tt,e as Z,g as Yt,t as Is,w as Kl,v as Vl,X as Ji,j as Tn,s as jl,p as it,x as Gl}from"./index-sHch0610.js";/**
|
||||
import{L as J,a as zl,r as ut,o as Hl,$ as Ul,P as Rn,D as ql,b as tt,e as Z,g as Yt,t as Is,w as Kl,v as Vl,X as Ji,j as Tn,s as jl,p as it,x as Gl}from"./index-DyUIpN7m.js";/**
|
||||
* Copyright (c) 2014-2024 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
+1
-1
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
@@ -233,7 +233,7 @@ float cookTorranceSpecular(
|
||||
float G1 = (2.0 * NdotH * VdotN) / VdotH;
|
||||
float G2 = (2.0 * NdotH * LdotN) / LdotH;
|
||||
float G = min(1.0, min(G1, G2));
|
||||
|
||||
|
||||
//Distribution term
|
||||
float D = beckmannDistribution(NdotH, roughness);
|
||||
|
||||
@@ -245,7 +245,7 @@ float cookTorranceSpecular(
|
||||
}
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -393,7 +393,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -511,7 +511,7 @@ float cookTorranceSpecular(
|
||||
float G1 = (2.0 * NdotH * VdotN) / VdotH;
|
||||
float G2 = (2.0 * NdotH * LdotN) / LdotH;
|
||||
float G = min(1.0, min(G1, G2));
|
||||
|
||||
|
||||
//Distribution term
|
||||
float D = beckmannDistribution(NdotH, roughness);
|
||||
|
||||
@@ -525,7 +525,7 @@ float cookTorranceSpecular(
|
||||
//#pragma glslify: beckmann = require(glsl-specular-beckmann) // used in gl-surface3d
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -604,7 +604,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -639,7 +639,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -713,7 +713,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -746,7 +746,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -861,7 +861,7 @@ float beckmannSpecular(
|
||||
}
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -963,7 +963,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -1011,7 +1011,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -1068,7 +1068,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -1126,7 +1126,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -1186,7 +1186,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -1222,7 +1222,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -1526,7 +1526,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -1673,7 +1673,7 @@ float cookTorranceSpecular(
|
||||
float G1 = (2.0 * NdotH * VdotN) / VdotH;
|
||||
float G2 = (2.0 * NdotH * LdotN) / LdotH;
|
||||
float G = min(1.0, min(G1, G2));
|
||||
|
||||
|
||||
//Distribution term
|
||||
float D = beckmannDistribution(NdotH, roughness);
|
||||
|
||||
@@ -1685,7 +1685,7 @@ float cookTorranceSpecular(
|
||||
}
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -1796,7 +1796,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -1871,7 +1871,7 @@ void main() {
|
||||
#define GLSLIFY 1
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
@@ -1957,7 +1957,7 @@ vec4 packFloat(float v) {
|
||||
}
|
||||
|
||||
bool outOfRange(float a, float b, float p) {
|
||||
return ((p > max(a, b)) ||
|
||||
return ((p > max(a, b)) ||
|
||||
(p < min(a, b)));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{M as x,c as s}from"./index-sHch0610.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};
|
||||
import{M as x,c as s}from"./index-DyUIpN7m.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};
|
||||
@@ -8,8 +8,8 @@
|
||||
<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-sHch0610.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Dk6Oh8NN.css">
|
||||
<script type="module" crossorigin src="/assets/index-DyUIpN7m.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D-3p9FIW.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
Reference in New Issue
Block a user