Add additional interfaces to AddInterfacePage
This commit is contained in:
204
meshchat.py
204
meshchat.py
@@ -329,8 +329,30 @@ class ReticulumMeshChat:
|
||||
if "interfaces" in self.reticulum.config:
|
||||
interfaces = self.reticulum.config["interfaces"]
|
||||
|
||||
processed_interfaces = {}
|
||||
for interface_name, interface in interfaces.items():
|
||||
interface_data = interface.copy()
|
||||
|
||||
# handle sub-interfaces for RNodeMultiInterface
|
||||
if interface_data.get("type") == "RNodeMultiInterface":
|
||||
sub_interfaces = []
|
||||
for sub_name, sub_config in interface_data.items():
|
||||
if sub_name not in {"type", "port", "interface_enabled", "selected_interface_mode",
|
||||
"configured_bitrate"}:
|
||||
if isinstance(sub_config, dict):
|
||||
sub_config["name"] = sub_name
|
||||
sub_interfaces.append(sub_config)
|
||||
|
||||
# add sub-interfaces to the main interface data
|
||||
interface_data["sub_interfaces"] = sub_interfaces
|
||||
|
||||
for sub in sub_interfaces:
|
||||
del interface_data[sub["name"]]
|
||||
|
||||
processed_interfaces[interface_name] = interface_data
|
||||
|
||||
return web.json_response({
|
||||
"interfaces": interfaces,
|
||||
"interfaces": processed_interfaces,
|
||||
})
|
||||
|
||||
# enable reticulum interface
|
||||
@@ -443,11 +465,32 @@ class ReticulumMeshChat:
|
||||
if "enabled" not in interface_details and "interface_enabled" not in interface_details:
|
||||
interface_details["interface_enabled"] = "true"
|
||||
|
||||
# additional AutoInterface options
|
||||
if interface_type == "AutoInterface":
|
||||
if data.get("group_id"):
|
||||
interface_details["group_id"] = data.get("group_id")
|
||||
if data.get("multicast_address_type"):
|
||||
interface_details["multicast_address_type"] = data.get("multicast_address_type")
|
||||
if data.get("devices"):
|
||||
interface_details["devices"] = data.get("devices")
|
||||
if data.get("ignored_devices"):
|
||||
interface_details["ignored_devices"] = data.get("ignored_devices")
|
||||
if data.get("discovery_scope"):
|
||||
interface_details["discovery_scope"] = data.get("discovery_scope")
|
||||
if data.get("discovery_port"):
|
||||
interface_details["discovery_port"] = data.get("discovery_port")
|
||||
if data.get("data_port"):
|
||||
interface_details["data_port"] = data.get("data_port")
|
||||
|
||||
|
||||
# handle tcp client interface
|
||||
if interface_type == "TCPClientInterface":
|
||||
|
||||
interface_target_host = data.get('target_host')
|
||||
interface_target_port = data.get('target_port')
|
||||
# optional parameters for kiss_framing and i2p tunnelling
|
||||
interface_kiss_framing = data.get('kiss_framing')
|
||||
interface_i2p_tunneled = data.get('i2p_tunneled')
|
||||
|
||||
# ensure target host provided
|
||||
if interface_target_host is None or interface_target_host == "":
|
||||
@@ -463,6 +506,13 @@ class ReticulumMeshChat:
|
||||
|
||||
interface_details["target_host"] = data.get('target_host')
|
||||
interface_details["target_port"] = data.get('target_port')
|
||||
interface_details["kiss_framing"] = interface_kiss_framing
|
||||
interface_details["i2p_tunneled"] = interface_i2p_tunneled
|
||||
|
||||
# handle I2P interface
|
||||
if interface_type == "I2PInterface":
|
||||
interface_details['connectable'] = "True"
|
||||
interface_details["peers"] = data.get('peers')
|
||||
|
||||
# handle tcp server interface
|
||||
if interface_type == "TCPServerInterface":
|
||||
@@ -470,6 +520,9 @@ class ReticulumMeshChat:
|
||||
interface_listen_ip = data.get('listen_ip')
|
||||
interface_listen_port = data.get('listen_port')
|
||||
|
||||
interface_network_device = data.get('device')
|
||||
interface_prefer_ipv6 = data.get('prefer_ipv6')
|
||||
|
||||
# ensure listen ip provided
|
||||
if interface_listen_ip is None or interface_listen_ip == "":
|
||||
return web.json_response({
|
||||
@@ -485,6 +538,11 @@ class ReticulumMeshChat:
|
||||
interface_details["listen_ip"] = data.get('listen_ip')
|
||||
interface_details["listen_port"] = data.get('listen_port')
|
||||
|
||||
if interface_network_device is not None and interface_network_device != "":
|
||||
interface_details["device"] = interface_network_device
|
||||
if interface_prefer_ipv6 is not None and interface_prefer_ipv6 != "" and interface_prefer_ipv6 != False:
|
||||
interface_details["prefer_ipv6"] = True
|
||||
|
||||
# handle udp interface
|
||||
if interface_type == "UDPInterface":
|
||||
|
||||
@@ -492,6 +550,7 @@ class ReticulumMeshChat:
|
||||
interface_listen_port = data.get('listen_port')
|
||||
interface_forward_ip = data.get('forward_ip')
|
||||
interface_forward_port = data.get('forward_port')
|
||||
interface_network_device = data.get('device')
|
||||
|
||||
# ensure listen ip provided
|
||||
if interface_listen_ip is None or interface_listen_ip == "":
|
||||
@@ -522,6 +581,9 @@ class ReticulumMeshChat:
|
||||
interface_details["forward_ip"] = data.get('forward_ip')
|
||||
interface_details["forward_port"] = data.get('forward_port')
|
||||
|
||||
if interface_network_device is not None and interface_network_device != "":
|
||||
interface_details["network_device"] = interface_network_device
|
||||
|
||||
# handle rnode interface
|
||||
if interface_type == "RNodeInterface":
|
||||
|
||||
@@ -575,6 +637,132 @@ class ReticulumMeshChat:
|
||||
interface_details["spreadingfactor"] = interface_spreadingfactor
|
||||
interface_details["codingrate"] = interface_codingrate
|
||||
|
||||
# Handle RNode Multi Interface
|
||||
if interface_type == "RNodeMultiInterface":
|
||||
interface_port = data.get("port")
|
||||
sub_interfaces = data.get("sub_interfaces", [])
|
||||
|
||||
if not interface_port:
|
||||
return web.json_response({"message": "Port is required"}, status=422)
|
||||
|
||||
if not isinstance(sub_interfaces, list) or not sub_interfaces:
|
||||
return web.json_response({"message": "At least one sub-interface is required"}, status=422)
|
||||
|
||||
interface_details["type"] = interface_type
|
||||
interface_details["interface_enabled"] = True
|
||||
interface_details["port"] = interface_port
|
||||
|
||||
for idx, sub in enumerate(sub_interfaces):
|
||||
# validate required fields for each sub-interface
|
||||
required_subinterface_fields = ["name", "frequency", "bandwidth", "txpower", "spreadingfactor",
|
||||
"codingrate",
|
||||
"vport"]
|
||||
missing_fields = [field for field in required_subinterface_fields if not sub.get(field)]
|
||||
if missing_fields:
|
||||
return web.json_response({
|
||||
"message": f"Sub-interface {idx + 1} is missing required field(s): {', '.join(missing_fields)}"
|
||||
}, status=422)
|
||||
|
||||
sub_interface_name = sub.get("name")
|
||||
interface_details[sub_interface_name] = {
|
||||
"interface_enabled": "true",
|
||||
"frequency": int(sub["frequency"]),
|
||||
"bandwidth": int(sub["bandwidth"]),
|
||||
"txpower": int(sub["txpower"]),
|
||||
"spreadingfactor": int(sub["spreadingfactor"]),
|
||||
"codingrate": int(sub["codingrate"]),
|
||||
"vport": int(sub["vport"]),
|
||||
}
|
||||
|
||||
interfaces[interface_name] = interface_details
|
||||
|
||||
# Handle Serial, KISS, and AX25KISS
|
||||
if interface_type == "SerialInterface" or interface_type == "KISSInterface" or interface_type == "AX25KISSInterface":
|
||||
interface_port = data.get('port')
|
||||
interface_speed = data.get('speed')
|
||||
|
||||
required_fields = {
|
||||
interface_port: "Port is required",
|
||||
interface_speed: "Serial speed is required",
|
||||
}
|
||||
|
||||
for field, error_message in required_fields.items():
|
||||
if field is None or field == "":
|
||||
return web.json_response({
|
||||
"message": error_message,
|
||||
}, status=422)
|
||||
|
||||
interface_details["port"] = interface_port
|
||||
interface_details['connectable'] = "True"
|
||||
interface_details["type"] = interface_type
|
||||
interface_details["interface_enabled"] = True
|
||||
|
||||
interface_details["speed"] = interface_speed
|
||||
interface_details["databits"] = data.get('databits')
|
||||
interface_details['parity'] = data.get('parity')
|
||||
interface_details['stopbits'] = data.get('stopbits')
|
||||
|
||||
# Handle KISS and AX25KISS specific options
|
||||
if (interface_type == "KISSInterface" or interface_type == "AX25KISSInterface"):
|
||||
interface_details["preamble"] = data.get('preamble')
|
||||
interface_details["txtail"] = data.get('txtail')
|
||||
interface_details['persistence'] = data.get('persistence')
|
||||
interface_details['slottime'] = data.get('slottime')
|
||||
interface_details['callsign'] = data.get('callsign')
|
||||
interface_details['ssid'] = data.get('ssid')
|
||||
|
||||
# RNode Airtime limits and station ID
|
||||
callsign = data.get('callsign')
|
||||
id_interval = data.get('id_interval')
|
||||
airtime_limit_long = data.get('airtime_limit_long')
|
||||
airtime_limit_short = data.get('airtime_limit_short')
|
||||
|
||||
if callsign is not None and callsign != "":
|
||||
interface_details["callsign"] = callsign
|
||||
if id_interval is not None and id_interval != "":
|
||||
interface_details["id_interval"] = id_interval
|
||||
if airtime_limit_long is not None and airtime_limit_long != "":
|
||||
interface_details["airtime_limit_long"] = airtime_limit_long
|
||||
if airtime_limit_short is not None and airtime_limit_short != "":
|
||||
interface_details["airtime_limit_short"] = airtime_limit_short
|
||||
|
||||
# Handle Pipe Interface
|
||||
if interface_type == "PipeInterface":
|
||||
interface_command = data.get('command')
|
||||
interface_respawn_delay = data.get('respawn_delay')
|
||||
|
||||
required_fields = {
|
||||
interface_command: "Command is required",
|
||||
interface_respawn_delay: "Respawn delay is required",
|
||||
}
|
||||
|
||||
for field, error_message in required_fields.items():
|
||||
if field is None or field == "":
|
||||
return web.json_response({
|
||||
"message": error_message,
|
||||
}, status=422)
|
||||
|
||||
interface_details["command"] = interface_command
|
||||
interface_details["respawn_delay"] = interface_respawn_delay
|
||||
|
||||
# Common interface options
|
||||
inferred_bitrate = data.get('bitrate')
|
||||
transport_mode = data.get('mode')
|
||||
network_name = data.get('network_name')
|
||||
ifac_passphrase = data.get('passphrase')
|
||||
ifac_size = data.get('ifac_size')
|
||||
|
||||
if inferred_bitrate is not None and inferred_bitrate != "":
|
||||
interface_details["bitrate"] = inferred_bitrate
|
||||
if transport_mode is not None and transport_mode != "":
|
||||
interface_details["mode"] = transport_mode
|
||||
if network_name is not None and network_name != "":
|
||||
interface_details["network_name"] = network_name
|
||||
if ifac_passphrase is not None and ifac_passphrase != "":
|
||||
interface_details["passphrase"] = ifac_passphrase
|
||||
if ifac_size is not None and ifac_size != "":
|
||||
interface_details["ifac_size"] = ifac_size
|
||||
|
||||
# merge new interface into existing interfaces
|
||||
interfaces[interface_name] = interface_details
|
||||
self.reticulum.config["interfaces"] = interfaces
|
||||
@@ -619,7 +807,19 @@ class ReticulumMeshChat:
|
||||
for key, value in interface.items():
|
||||
output.append(f" {key} = {value}")
|
||||
output.append("")
|
||||
|
||||
|
||||
# Handle sub-interfaces for RNodeMultiInterface
|
||||
if interface.get("type") == "RNodeMultiInterface":
|
||||
for sub_name, sub_config in interface.items():
|
||||
if sub_name in {"type", "port", "interface_enabled"}:
|
||||
continue
|
||||
if isinstance(sub_config, dict):
|
||||
output.append(f" [[[ {sub_name} ]]]")
|
||||
for sub_key, sub_value in sub_config.items():
|
||||
output.append(f" {sub_key} = {sub_value}")
|
||||
output.append("")
|
||||
|
||||
|
||||
return web.Response(
|
||||
text="\n".join(output),
|
||||
content_type="text/plain",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,14 @@
|
||||
<path d="M252.44,121.34l-48-32A8,8,0,0,0,192,96v24H72V72h33a32,32,0,1,0,0-16H72A16,16,0,0,0,56,72v48H8a8,8,0,0,0,0,16H56v48a16,16,0,0,0,16,16h32v8a16,16,0,0,0,16,16h32a16,16,0,0,0,16-16V176a16,16,0,0,0-16-16H120a16,16,0,0,0-16,16v8H72V136H192v24a8,8,0,0,0,12.44,6.66l48-32a8,8,0,0,0,0-13.32ZM136,48a16,16,0,1,1-16,16A16,16,0,0,1,136,48ZM120,176h32v32H120Zm88-30.95V111l25.58,17Z"></path>
|
||||
</svg>
|
||||
|
||||
<svg v-else-if="iface.type === 'RNodeMultiInterface'" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 256 256" class="size-6 dark:text-white"><path d="M128,88a40,40,0,1,0,40,40A40,40,0,0,0,128,88Zm0,64a24,24,0,1,1,24-24A24,24,0,0,1,128,152Zm73.71,7.14a80,80,0,0,1-14.08,22.2,8,8,0,0,1-11.92-10.67,63.95,63.95,0,0,0,0-85.33,8,8,0,1,1,11.92-10.67,80.08,80.08,0,0,1,14.08,84.47ZM69,103.09a64,64,0,0,0,11.26,67.58,8,8,0,0,1-11.92,10.67,79.93,79.93,0,0,1,0-106.67A8,8,0,1,1,80.29,85.34,63.77,63.77,0,0,0,69,103.09ZM248,128a119.58,119.58,0,0,1-34.29,84,8,8,0,1,1-11.42-11.2,103.9,103.9,0,0,0,0-145.56A8,8,0,1,1,213.71,44,119.58,119.58,0,0,1,248,128ZM53.71,200.78A8,8,0,1,1,42.29,212a119.87,119.87,0,0,1,0-168,8,8,0,1,1,11.42,11.2,103.9,103.9,0,0,0,0,145.56Z"></path></svg>
|
||||
|
||||
<svg v-else-if="iface.type === 'I2PInterface'" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 256 256" class="size-6 dark:text-white"><path d="M72,92A12,12,0,1,1,60,80,12,12,0,0,1,72,92Zm56-12a12,12,0,1,0,12,12A12,12,0,0,0,128,80Zm68,24a12,12,0,1,0-12-12A12,12,0,0,0,196,104ZM60,152a12,12,0,1,0,12,12A12,12,0,0,0,60,152Zm68,0a12,12,0,1,0,12,12A12,12,0,0,0,128,152Zm68,0a12,12,0,1,0,12,12A12,12,0,0,0,196,152Z"></path></svg>
|
||||
|
||||
<svg v-else-if="iface.type === 'KISSInterface' || iface.type === 'AX25KISSInterface'" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 256 256" class="size-6 dark:text-white"><path d="M104,168a8,8,0,0,1-8,8H64a8,8,0,0,1,0-16H96A8,8,0,0,1,104,168Zm-8-40H64a8,8,0,0,0,0,16H96a8,8,0,0,0,0-16Zm0-32H64a8,8,0,0,0,0,16H96a8,8,0,0,0,0-16ZM232,80V192a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V72a8,8,0,0,1,5.7-7.66l160-48a8,8,0,0,1,4.6,15.33L86.51,64H216A16,16,0,0,1,232,80ZM216,192V80H40V192H216Zm-16-56a40,40,0,1,1-40-40A40,40,0,0,1,200,136Zm-16,0a24,24,0,1,0-24,24A24,24,0,0,0,184,136Z"></path></svg>
|
||||
|
||||
<svg v-else-if="iface.type === 'PipeInterface'" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 256 256" class="size-6 dark:text-white"><path d="M128,128a8,8,0,0,1-3,6.25l-40,32a8,8,0,1,1-10-12.5L107.19,128,75,102.25a8,8,0,1,1,10-12.5l40,32A8,8,0,0,1,128,128Zm48,24H136a8,8,0,0,0,0,16h40a8,8,0,0,0,0-16Zm56-96V200a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V56A16,16,0,0,1,40,40H216A16,16,0,0,1,232,56ZM216,200V56H40V200H216Z"></path></svg>
|
||||
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 256 256" class="size-6 dark:text-white">
|
||||
<path d="M140,180a12,12,0,1,1-12-12A12,12,0,0,1,140,180ZM128,72c-22.06,0-40,16.15-40,36v4a8,8,0,0,0,16,0v-4c0-11,10.77-20,24-20s24,9,24,20-10.77,20-24,20a8,8,0,0,0-8,8v8a8,8,0,0,0,16,0v-.72c18.24-3.35,32-17.9,32-35.28C168,88.15,150.06,72,128,72Zm104,56A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"></path>
|
||||
</svg>
|
||||
@@ -186,6 +194,10 @@
|
||||
<div>• Bitrate: {{ formatBitsPerSecond(iface._stats?.bitrate ?? 0) }}</div>
|
||||
<div>• TX: {{ formatBytes(iface._stats?.txb ?? 0) }}</div>
|
||||
<div>• RX: {{ formatBytes(iface._stats?.rxb ?? 0) }}</div>
|
||||
<div v-if="iface.type === 'RNodeInterface'">• Noise Floor: {{
|
||||
iface._stats?.noise_floor
|
||||
}} dBm
|
||||
</div>
|
||||
<div v-if="iface._stats?.clients">• Clients: {{ iface._stats?.clients }}</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1 overflow-hidden min-w-full sm:min-w-[500px] dark:bg-zinc-950">
|
||||
<div class="overflow-y-auto p-2 space-y-2">
|
||||
<div class="h-full p-2 space-y-2">
|
||||
|
||||
<!-- warning - keeping orange-500 for warning visibility in both modes -->
|
||||
<div class="flex bg-orange-500 p-2 text-sm font-semibold leading-6 text-white rounded shadow">
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
</div>
|
||||
<div class="my-auto">Loading {{ nodePageProgress }}%</div>
|
||||
</div>
|
||||
<pre v-else v-html="nodePageContent" class="h-full text-wrap"></pre>
|
||||
<pre v-else v-html="nodePageContent" class="h-full break-words whitespace-pre-wrap"></pre>
|
||||
</div>
|
||||
|
||||
<!-- file download bottom bar -->
|
||||
|
||||
@@ -143,6 +143,10 @@ class Utils {
|
||||
|
||||
static isInterfaceEnabled(iface) {
|
||||
const rawValue = iface.enabled ?? iface.interface_enabled;
|
||||
if (rawValue === true) {
|
||||
// when enabled or interface_enabled is True it needs to return true.
|
||||
return true;
|
||||
}
|
||||
const value = rawValue?.toLowerCase();
|
||||
return value === "on" || value === "yes" || value === "true";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user