liamcottle___reticulum-mesh.../src/frontend/components/interfaces/AddInterfacePage.vue
2025-02-01 23:47:40 +13:00

1562 lines
74 KiB
Vue

<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">
<!-- community interfaces -->
<div v-if="!isEditingInterface && config != null && config.show_suggested_community_interfaces"
class="bg-white rounded shadow divide-y divide-gray-200 dark:bg-zinc-900">
<div class="flex p-2">
<div class="my-auto mr-auto">
<div class="font-bold dark:text-white">Community Interfaces</div>
<div class="text-sm dark:text-gray-100">These TCP interfaces serve as a quick way to test Reticulum. We
suggest running your own as these may not always be available.
</div>
</div>
<div class="my-auto ml-2">
<button @click="updateConfig({'show_suggested_community_interfaces': false})" type="button"
class="text-gray-700 bg-gray-100 hover:bg-gray-200 p-2 rounded-full dark:bg-zinc-600 dark:text-white dark:hover:bg-zinc-700 dark:focus-visible:outline-zinc-500">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
<path
d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z"/>
</svg>
</button>
</div>
</div>
<div class="divide-y divide-gray-200 dark:text-white">
<div class="flex px-2 py-1">
<div class="my-auto mr-auto">
<div>RNS Testnet Amsterdam</div>
<div class="text-xs">amsterdam.connect.reticulum.network:4965</div>
</div>
<div class="ml-2 my-auto">
<button
@click="newInterfaceName='RNS Testnet Amsterdam';newInterfaceType='TCPClientInterface';newInterfaceTargetHost='amsterdam.connect.reticulum.network';newInterfaceTargetPort='4965'"
type="button"
class="inline-flex items-center gap-x-1 rounded-md bg-gray-500 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-gray-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500">
<span>Use Interface</span>
</button>
</div>
</div>
<div class="flex px-2 py-1">
<div class="my-auto mr-auto">
<div>RNS Testnet BetweenTheBorders</div>
<div class="text-xs">reticulum.betweentheborders.com:4242</div>
</div>
<div class="ml-2 my-auto">
<button
@click="newInterfaceName='RNS Testnet BetweenTheBorders';newInterfaceType='TCPClientInterface';newInterfaceTargetHost='reticulum.betweentheborders.com';newInterfaceTargetPort='4242'"
type="button"
class="inline-flex items-center gap-x-1 rounded-md bg-gray-500 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-gray-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500">
<span>Use Interface</span>
</button>
</div>
</div>
</div>
</div>
<!-- add interface form -->
<div class="bg-white rounded shadow divide-y divide-gray-200 dark:bg-zinc-900">
<div class="p-2 font-bold dark:text-white">
<span v-if="isEditingInterface">Edit Interface</span>
<span v-else>Add Interface</span>
</div>
<div class="p-2 space-y-3">
<!-- iGeneric interface settings -->
<!-- interface name -->
<div>
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Name</label>
<input type="text" :disabled="isEditingInterface" placeholder="New Interface Name"
v-model="newInterfaceName"
class="border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
:class="[ isEditingInterface ? 'cursor-not-allowed bg-gray-200' : 'bg-gray-50' ]">
<div class="text-xs text-gray-600 dark:text-zinc-300">Interface names must be unique.</div>
</div>
<!-- interface type -->
<div class="mb-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Type</label>
<select v-model="newInterfaceType"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white">
<option disabled selected>--</option>
<option value="AutoInterface">Auto Interface</option>
<option disabled selected>RNodes</option>
<option value="RNodeInterface">RNode Interface</option>
<option value="RNodeMultiInterface">RNode Multi Interface</option>
<option disabled selected>IP Networks</option>
<option value="TCPClientInterface">TCP Client Interface</option>
<option value="TCPServerInterface">TCP Server Interface</option>
<option value="UDPInterface">UDP Interface</option>
<option value="I2PInterface">I2P Interface</option>
<option disabled selected>Hardware</option>
<option value="SerialInterface">Serial Interface</option>
<option value="KISSInterface">KISS Interface</option>
<option hidden value="AX25KISSInterface">AX.25 KISS Interface</option>
<option disabled selected>Other</option>
<option value="PipeInterface">Pipe Interface</option>
</select>
<div class="text-xs text-gray-600 dark:text-zinc-300">
Need help? <a class="text-blue-500 underline" href="https://reticulum.network/manual/interfaces.html"
target="_blank">Reticulum Docs: Configuring Interfaces</a>
</div>
</div>
<div v-if="newInterfaceType === 'AutoInterface'" class="mb-4">
<div
@click="toggleAllSettings"
class="flex items-center justify-between cursor-pointer p-2 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-zinc-800 dark:hover:bg-zinc-700"
>
<span
class="font-semibold text-gray-900 dark:text-gray-100">Additional AutoInterface Settings</span>
<span class="w-5 h-5 text-gray-600 dark:text-gray-300 transform transition-transform duration-200 "
:class="{ 'rotate-180': showAllSettings }"
>
</span>
</div>
<div
v-show="showAllSettings"
class="mt-3 space-y-4 p-4 border border-gray-200 rounded-lg bg-white dark:bg-zinc-900 dark:border-zinc-700"
>
<p class="text-sm text-gray-600 dark:text-gray-400"> These settings are optional</p>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Group ID</label>
<input
type="text"
v-model="newInterfaceGroupID"
placeholder="reticulum"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
<div class="flex-1">
<label class="text-sm dark:text-zinc-100">Multicast Address Type</label>
<select
v-model="newInterfaceMulticastAddressType"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600"
>
<option value="permanent">
Permanent
</option>
<option value="temporary">
Temporary
</option>
</select>
</div>
<div class="flex items-center space-x-4 mt-4">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Network devices</label>
<input
type="text"
v-model="newInterfaceDevices"
placeholder="wlan0,eth1"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Ignored Devices</label>
<input
type="text"
v-model="newInterfaceIgnoredDevices"
placeholder="tun0,eth0"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
</div>
<div class="flex items-center space-x-4 mt-4">
<div class="flex-1">
<label class="text-sm dark:text-zinc-100">Discovery Scope</label>
<select
v-model="newInterfaceDiscoveryScope"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600"
>
<option value="global">
Global
</option>
<option value="admin">
Admin
</option>
<option value="organisation">
Organisation
</option>
<option value="site">
Site
</option>
<option value="link">
Link
</option>
</select>
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Discovery Port</label>
<input
type="number"
v-model="newInterfaceDiscoveryPort"
placeholder="48555"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Data Port</label>
<input
type="number"
v-model="newInterfaceDataPort"
placeholder="49555"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
</div>
</div>
</div>
<!-- TCPClientInterface -->
<!-- interface target host -->
<div v-if="newInterfaceType === 'TCPClientInterface'" class="mb-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Target Host</label>
<input type="text" placeholder="example.com" v-model="newInterfaceTargetHost"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
</div>
<!-- interface target port -->
<div v-if="newInterfaceType === 'TCPClientInterface'" class="mb-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Target Port</label>
<input type="text" placeholder="1234" v-model="newInterfaceTargetPort"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
</div>
<div v-if="newInterfaceType === 'TCPClientInterface'" class="mb-4">
<div
@click="toggleAllSettings"
class="flex items-center justify-between cursor-pointer p-2 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-zinc-800 dark:hover:bg-zinc-700"
>
<span
class="font-semibold text-gray-900 dark:text-gray-100">Additional TCP Client Interface settings</span>
<span class="w-5 h-5 text-gray-600 dark:text-gray-300 transform transition-transform duration-200 "
:class="{ 'rotate-180': showAllSettings }"
>
</span>
</div>
<div
v-show="showAllSettings"
class="mt-3 space-y-4 p-4 border border-gray-200 rounded-lg bg-white dark:bg-zinc-900 dark:border-zinc-700"
>
<div class="flex items-start">
<div class="flex flex-col">
<label for="kiss-framing" class="text-sm font-medium text-gray-900 dark:text-zinc-100">
Enable KISS Framing
</label>
<span class="text-sm text-gray-500 dark:text-zinc-300">
Enabled when connecting to software that uses KISS framing such as packet radio sound modems.
For KISS connections through serial hardware select "KISS Interface" as the interface type.
</span>
</div>
<input
id="kiss-framing"
type="checkbox"
v-model="newInterfaceKISSFramingEnabled"
class="ml-auto h-5 w-5 rounded border-gray-300 text-blue-600 focus:ring focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-800 dark:focus:ring-blue-600"
/>
</div>
<div class="flex items-start">
<div class="flex flex-col">
<label for="i2p-tunneled" class="text-sm font-medium text-gray-900 dark:text-zinc-100">
Enable I2P tunneling
</label>
<span class="text-sm text-gray-500 dark:text-zinc-300">
Enables tunnelling through an I2P Connection using the TCPClientInterface
</span>
</div>
<input
id="i2p-tunneled"
type="checkbox"
v-model="newInterfaceI2PTunnelingEnabled"
class="ml-auto h-5 w-5 rounded border-gray-300 text-blue-600 focus:ring focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-800 dark:focus:ring-blue-600"
/>
</div>
</div>
</div>
<!-- TCPServerInterface -->
<!-- interface listen ip -->
<div v-if="newInterfaceType === 'TCPServerInterface' || newInterfaceType === 'UDPInterface'" class="mb-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Listen IP</label>
<input type="text" placeholder="0.0.0.0" v-model="newInterfaceListenIp"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
</div>
<!-- interface listen port -->
<div v-if="newInterfaceType === 'TCPServerInterface' || newInterfaceType === 'UDPInterface'" class="mb-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Listen Port</label>
<input type="text" placeholder="1234" v-model="newInterfaceListenPort"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
</div>
<div v-if="newInterfaceType === 'TCPServerInterface' || newInterfaceType === 'UDPInterface'" class="mb-4">
<div
@click="toggleAllSettings"
class="flex items-center justify-between cursor-pointer p-2 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-zinc-800 dark:hover:bg-zinc-700"
>
<span
class="font-semibold text-gray-900 dark:text-gray-100">Additional Server Interface settings</span>
<span class="w-5 h-5 text-gray-600 dark:text-gray-300 transform transition-transform duration-200 "
:class="{ 'rotate-180': showAllSettings }"
>
</span>
</div>
<div
v-show="showAllSettings"
class="mt-3 space-y-4 p-4 border border-gray-200 rounded-lg bg-white dark:bg-zinc-900 dark:border-zinc-700">
<div class="mb-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Network device</label>
<span class="text-sm text-gray-500 dark:text-zinc-300">
Binds the interface to a specific network interface
</span>
<input type="text" placeholder="eth0" v-model="newInterfaceNetworkDevice"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
</div>
<div class="flex items-start">
<div class="flex flex-col">
<label for="prefer-ipv6" class="text-sm font-medium text-gray-900 dark:text-zinc-100">
Prefer IPv6
</label>
<span class="text-sm text-gray-500 dark:text-zinc-300">
Binds the TCP Server Interface to an IPv6 address
</span>
</div>
<input
id="prefer-ipv6"
type="checkbox"
value="1"
v-model="newInterfacePreferIPV6"
class="ml-auto h-5 w-5 rounded border-gray-300 text-blue-600 focus:ring focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-800 dark:focus:ring-blue-600"
/>
</div>
</div>
</div>
<!-- UDPInterface -->
<!-- interface forward ip -->
<div v-if="newInterfaceType === 'UDPInterface'" class="mb-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Forward IP</label>
<input type="text" placeholder="255.255.255.255" v-model="newInterfaceForwardIp"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
</div>
<!-- interface listen port -->
<div v-if="newInterfaceType === 'UDPInterface'" class="mb-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Forward Port</label>
<input type="text" placeholder="1234" v-model="newInterfaceForwardPort"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
</div>
<!-- I2PInterface -->
<div v-if="newInterfaceType === 'I2PInterface'" class="mb-4">
<span class="text-sm text-gray-500 dark:text-zinc-300">
To use the I2P interface, you must have an I2P router running on your system. When the I2P Interface is added for the first time Reticulum will generate a new I2P address for the interface and begin listening for inbound traffic.
</span>
<div class="mt-4">
<div
@click="toggleI2PPeerAccordion"
class="flex items-center justify-between cursor-pointer p-2 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-zinc-800 dark:hover:bg-zinc-700"
>
<span class="font-semibold text-gray-900 dark:text-gray-100">Manage Peers</span>
<span
class="w-5 h-5 text-gray-600 dark:text-gray-300 transform transition-transform duration-200"
:class="{ 'rotate-180': I2PSettings.showPeerAccordion }"
>
</span>
</div>
<div
v-show="I2PSettings.showPeerAccordion"
class="mt-3 space-y-4 p-4 border border-gray-200 rounded-lg bg-white dark:bg-zinc-900 dark:border-zinc-700"
>
<div v-for="(peer, index) in I2PSettings.newInterfacePeers" :key="index"
class="flex items-center space-x-2">
<input
type="text"
v-model="I2PSettings.newInterfacePeers[index]"
placeholder="Enter peer address (ex: 5urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq.b32.i2p)"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600"
/>
<button
@click="removeI2PPeer(index)"
type="button"
class="bg-red-500 hover:bg-red-400 text-white text-sm p-2 rounded-lg"
>
Remove
</button>
</div>
<button
@click="addI2PPeer('')"
type="button"
class="bg-green-500 hover:bg-green-400 text-white text-sm px-4 py-2 rounded-lg"
>
Add Peer
</button>
</div>
</div>
</div>
<!-- RNode interface -->
<!-- interface port -->
<div v-if="newInterfaceType === 'RNodeInterface'" class="mb-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Port</label>
<select v-model="newInterfacePort"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
<option v-for="comport of comports" :value="comport.device">{{ comport.device }} (Product:
{{ comport.product ?? '?' }}, Serial: {{ comport.serial ?? '?' }})
</option>
</select>
</div>
<div v-if="newInterfaceType === 'RNodeInterface'" class="mb-2 flex flex-wrap items-start gap-4">
<!-- interface Frequency -->
<div class="flex-1">
<div class="mb-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Frequency</label>
<div class="flex items-center gap-2">
<div class="flex flex-col">
<input
type="number"
v-model.number="RNodeGHzValue"
min="0"
placeholder="GHz"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600"
/>
<label class="text-xs text-gray-500 dark:text-zinc-400 mt-1 text-center">GHz</label>
</div>
<div class="flex flex-col">
<input
type="number"
v-model.number="RNodeMHzValue"
min="0"
placeholder="MHz"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600"
/>
<label class="text-xs text-gray-500 dark:text-zinc-400 mt-1 text-center">MHz</label>
</div>
<div class="flex flex-col">
<input
type="number"
v-model.number="RNodekHzValue"
min="0"
placeholder="kHz"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600"
/>
<label class="text-xs text-gray-500 dark:text-zinc-400 mt-1 text-center">kHz</label>
</div>
</div>
<div class="text-sm text-gray-600 dark:text-zinc-100 mt-2 font-bold">
Selected Frequency: {{ formattedFrequency }}
</div>
</div>
</div>
<!-- interface Bandwidth -->
<div class="flex-1">
<div class="mb-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Bandwidth</label>
<select
v-model="newInterfaceBandwidth"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600"
>
<option v-for="bandwidth in RNodeInterfaceDefaults.bandwidths" :value="bandwidth">
{{ bandwidth / 1000 }} KHz
</option>
</select>
</div>
</div>
</div>
<!-- interface txpower -->
<div v-if="newInterfaceType === 'RNodeInterface'" class="mb-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Transmit Power (dBm)</label>
<input v-model="newInterfaceTxpower" type="number"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white">
</div>
<div v-if="newInterfaceType === 'RNodeInterface'" class="mb-2 flex flex-wrap items-start gap-4">
<!-- interface spreading factor -->
<div class="flex-1">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Spreading Factor</label>
<select v-model="newInterfaceSpreadingFactor"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
<option v-for="spreadingfactor in RNodeInterfaceDefaults.spreadingfactors" :value="spreadingfactor">
{{ spreadingfactor }}
</option>
</select>
</div>
<!-- interface coding rate -->
<div class="flex-1">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Coding Rate</label>
<select v-model="newInterfaceCodingRate"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
<option v-for="codingrate in RNodeInterfaceDefaults.codingrates" :value="codingrate">{{
codingrate
}}
</option>
</select>
</div>
</div>
<!-- RNode LoRa parameters-->
<div v-if="newInterfaceType === 'RNodeInterface'" class="mb-4">
<div
@click="toggleRNodeLoRaParameters"
class="flex items-center justify-between cursor-pointer p-2 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-zinc-800 dark:hover:bg-zinc-700"
>
<span
class="font-semibold text-gray-900 dark:text-gray-100">Show on-air RNode bitrate & link budget</span>
<span class="w-5 h-5 text-gray-600 dark:text-gray-300 transform transition-transform duration-200 "
:class="{ 'rotate-180': showRNodeLoRaParameters }"
>
</span>
</div>
<div
v-show="showRNodeLoRaParameters"
class="mt-3 space-y-6 p-6 border border-gray-300 rounded-lg bg-gray-50 dark:bg-zinc-900 dark:border-zinc-700"
>
<div class="space-y-2">
<div class="flex items-start space-x-2">
<div class="w-1/4">
<label class="block text-sm font-medium text-gray-800 dark:text-white">
Antenna Gain (dBi)
</label>
<input
type="number"
v-model.number="RNodeInterfaceLoRaParameters.antennaGain"
placeholder="Enter gain"
class="w-1/2 bg-gray-100 border border-gray-300 rounded-lg py-1 px-2 text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-zinc-800 dark:border-zinc-600 dark:text-white"
/>
<p class="text-xs text-gray-600 dark:text-gray-400 mt-1"> A stub or PCB antenna might have around 1
dBi of
gain, where a directional Yagi might have 5 dBi of gain.</p>
</div>
</div>
</div>
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white text-center">
On-Air Calculations
</h3>
<div class="grid grid-cols-3 gap-4 text-center mt-2">
<div class="bg-gray-100 p-4 rounded-lg shadow dark:bg-zinc-800">
<div class="text-sm font-medium text-gray-700 dark:text-gray-300">Sensitivity</div>
<div class="text-xl font-bold text-gray-900 dark:text-white">
{{ RNodeInterfaceLoRaParameters.sensitivity }}
</div>
</div>
<div class="bg-gray-100 p-4 rounded-lg shadow dark:bg-zinc-800">
<div class="text-sm font-medium text-gray-700 dark:text-gray-300">Data Rate</div>
<div class="text-xl font-bold text-gray-900 dark:text-white">
{{ RNodeInterfaceLoRaParameters.dataRate }}
</div>
</div>
<div class="bg-gray-100 p-4 rounded-lg shadow dark:bg-zinc-800">
<div class="text-sm font-medium text-gray-700 dark:text-gray-300">Link Budget</div>
<div class="text-xl font-bold text-gray-900 dark:text-white">
{{ RNodeInterfaceLoRaParameters.linkBudget }}
</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="newInterfaceType === 'RNodeInterface'" class="mb-4">
<div
@click="toggleAllSettings"
class="flex items-center justify-between cursor-pointer p-2 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-zinc-800 dark:hover:bg-zinc-700"
>
<span
class="font-semibold text-gray-900 dark:text-gray-100">Additional RNode Interface Settings</span>
<span class="w-5 h-5 text-gray-600 dark:text-gray-300 transform transition-transform duration-200 "
:class="{ 'rotate-180': showAllSettings }"
>
</span>
</div>
<div
v-show="showAllSettings"
class="mt-3 space-y-4 p-4 border border-gray-200 rounded-lg bg-white dark:bg-zinc-900 dark:border-zinc-700">
<div class="flex items-center space-x-4">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Callsign</label>
<input
type="text"
v-model="newInterfaceCallsign"
placeholder="Enter callsign"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Callsign ID Interval</label>
<input
type="number"
v-model="newInterfaceIDInterval"
placeholder="Enter interval (seconds)"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
</div>
<div class="flex items-center space-x-4">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Airtime Limit
(Short)</label>
<input
type="number"
v-model="newInterfaceAirtimeLimitShort"
placeholder="Enter short airtime limit (seconds)"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Airtime Limit (Long)</label>
<input
type="number"
v-model="newInterfaceAirtimeLimitLong"
placeholder="Enter long airtime limit (seconds)"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
</div>
</div>
</div>
<!-- RNodeMultiInterface -->
<div v-if="newInterfaceType === 'RNodeMultiInterface'" class="mb-2">
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3"> The RNode Multi Interface is used for custom
devices
with multiple LoRa transceivers such as the openCom XL. </p>
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Port</label>
<select v-model="newInterfacePort"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
<option v-for="comport of comports" :value="comport.device">{{ comport.device }} (Product:
{{ comport.product ?? '?' }}, Serial: {{ comport.serial ?? '?' }})
</option>
</select>
</div>
<div v-if="newInterfaceType === 'RNodeMultiInterface'" class="mb-4">
<div
@click="toggleRNodeSubInterfaces"
class="flex items-center justify-between cursor-pointer p-2 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-zinc-800 dark:hover:bg-zinc-700">
<span class="font-semibold text-gray-900 dark:text-gray-100">Manage RNode Subinterfaces</span>
<span
class="w-5 h-5 text-gray-600 dark:text-gray-300 transform transition-transform duration-200"
:class="{ 'rotate-180': showRNodeSubInterfaces }">
</span>
</div>
<div
v-show="showRNodeSubInterfaces"
class="mt-3 space-y-4 p-4 border border-gray-200 rounded-lg bg-white dark:bg-zinc-900 dark:border-zinc-700">
<div
v-for="(sub, idx) in RNodeMultiInterface.subInterfaces"
:key="idx"
class="space-y-2 border-b border-gray-200 dark:border-zinc-700 pb-4 mb-4">
<input
v-model="sub.name"
type="text"
placeholder="Subinterface Name"
class="w-full bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white">
<div class="flex gap-2">
<div class="flex-1">
<label class="text-sm dark:text-zinc-100">Frequency (Hz)</label>
<input
v-model.number="sub.frequency"
type="number"
class="w-full bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white">
</div>
<div class="flex-1">
<label class="text-sm dark:text-zinc-100">Bandwidth</label>
<select
v-model="sub.bandwidth"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600"
>
<option v-for="bandwidth in RNodeInterfaceDefaults.bandwidths" :value="bandwidth">
{{ bandwidth / 1000 }} KHz
</option>
</select>
</div>
</div>
<div class="flex gap-2">
<div class="flex-1">
<label class="text-sm dark:text-zinc-100">Spreading Factor</label>
<select
v-model.number="sub.spreadingfactor"
class="w-full bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white">
<option
v-for="sf in RNodeInterfaceDefaults.spreadingfactors"
:key="sf"
:value="sf">
{{ sf }}
</option>
</select>
</div>
<div class="flex-1">
<label class="text-sm dark:text-zinc-100">Coding Rate</label>
<select
v-model.number="sub.codingrate"
class="w-full bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white">
<option
v-for="cr in RNodeInterfaceDefaults.codingrates"
:key="cr"
:value="cr">
{{ cr }}
</option>
</select>
</div>
</div>
<div class="flex gap-2 items-center">
<div class="flex-1">
<label class="text-sm dark:text-zinc-100">TX Power (dBm)</label>
<input
v-model.number="sub.txpower"
type="number"
class="w-full bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white">
</div>
<div class="flex-1">
<label class="text-sm dark:text-zinc-100">Virtual Port</label>
<input
v-model.number="sub.vport"
type="number"
class="w-full bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white">
</div>
</div>
<button
@click="removeSubInterface(idx)"
type="button"
class="bg-red-500 hover:bg-red-400 text-white text-sm p-2 rounded-lg">
Remove Subinterface
</button>
</div>
<button
@click="addSubInterface"
type="button"
class="bg-green-500 hover:bg-green-400 text-white text-sm px-4 py-2 rounded-lg">
Add Subinterface
</button>
</div>
</div>
<!-- Serial, KISS, and AX25Kiss -->
<div v-if="['SerialInterface', 'KISSInterface', 'AX25KISSInterface'].includes(newInterfaceType)" class="mb-4">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Port</label>
<select v-model="newInterfacePort"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
<option v-for="comport of comports" :value="comport.device">{{ comport.device }} (Product:
{{ comport.product ?? '?' }}, Serial: {{ comport.serial ?? '?' }})
</option>
</select>
<div class="mb-2">
<label class="block mt-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Serial connection baud
rate (bps)</label>
<input v-model="newInterfaceSpeed" placeholder="115200" type="number"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white">
</div>
<div class="mt-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Databits</label>
<input v-model="newInterfaceDatabits" type="number" placeholder="8"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white">
</div>
<div class="mt-2">
<label class="block mt-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Parity </label>
<input v-model="newInterfaceParity" type="number" placeholder=""
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white">
</div>
<div class="mt-2">
<label class="block mt-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Stopbits</label>
<input v-model="newInterfaceStopbits" type="number" placeholder="1"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white">
</div>
</div>
<!-- KISS and AX.25 KISS-->
<div v-if="['KISSInterface', 'AX25KISSInterface'].includes(newInterfaceType)" class="mb-4">
<div class="flex items-center">
<input
id="use-ax25"
type="checkbox"
:checked="newInterfaceType === 'AX25KISSInterface'"
@click="useKISSAX25"
class="h-5 w-5 rounded border-gray-300 text-blue-600 focus:ring focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-800 dark:focus:ring-blue-600"
/>
<label for="use-ax25" class="ml-2 text-sm font-medium text-gray-900 dark:text-zinc-100">
Use AX.25
</label>
</div>
<span class="text-sm text-gray-500 dark:text-zinc-300 mb-3">
Enables AX.25 Framing
</span>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">
Preamble (milliseconds)
</label>
<input
v-model="this.newInterfacePreamble"
type="number"
placeholder="150"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">
TX Tail (milliseconds)
</label>
<input
v-model="this.newInterfaceTXTail"
type="number"
placeholder="10"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">
CDMA Persistence (milliseconds)
</label>
<input
v-model="this.newInterfacePersistence"
type="number"
placeholder="200"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">
CDMA Slot Time (milliseconds)
</label>
<input
v-model="this.newInterfaceSlotTime"
type="number"
placeholder="20"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
</div>
<div class="flex items-center space-x-4 mt-4">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">SSID</label>
<input
type="text"
value="0"
v-model="newInterfaceSSID"
placeholder="Enter SSID"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Callsign</label>
<input
type="text"
v-model="newInterfaceCallsign"
placeholder="Enter callsign"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Callsign ID Interval</label>
<input
type="number"
v-model="newInterfaceIDInterval"
placeholder="Enter interval (seconds)"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
</div>
</div>
<!-- Pipe Interface-->
<div v-if="newInterfaceType === 'PipeInterface'" class="mb-2">
<span class="text-sm text-gray-500 dark:text-zinc-300 mb-3">
Using this interface, Reticulum can use any program as an interface via stdin and stdout. This can be used
to easily create virtual interfaces, or to interface with custom hardware or other systems.
</span>
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Command</label>
<input type="text" placeholder="netcat -l 5757" v-model="newInterfaceCommand"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
<label class="block mt-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Respawn Delay
(seconds)</label>
<input type="number" placeholder="5" v-model="newInterfaceRespawnDelay"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
</div>
<!-- Shared interface setings -->
<div v-if="newInterfaceType"
@click="toggleSharedInterfaceSettings"
class="flex items-center justify-between cursor-pointer p-2 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-zinc-800 dark:hover:bg-zinc-700"
>
<span class="font-semibold text-gray-900 dark:text-gray-100">Shared Interface Settings</span>
<span class="w-5 h-5 text-gray-600 dark:text-gray-300 transform transition-transform duration-200 "
:class="{ 'rotate-180': showSharedInterfaceSettings }"
>
</span>
</div>
<div
v-show="showSharedInterfaceSettings"
class="mt-3 space-y-4 p-4 border border-gray-200 rounded-lg bg-white dark:bg-zinc-900 dark:border-zinc-700"
>
<p class="text-sm text-gray-600 dark:text-gray-400"> These settings are optional.</p>
<div v-show="transportEnabled" class="mb-4">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Interface Mode</label>
<select
v-model="sharedInterfaceSettings.mode"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
>
<option value="full">Full</option>
<option value="gateway">Gateway</option>
<option value="access_point">Access Point</option>
<option value="roaming">Roaming</option>
<option value="boundary">Boundary</option>
</select>
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Inferred Interface
Bitrate</label>
<input
v-model="sharedInterfaceSettings.bitrate"
type="number"
placeholder="Enter inferred bitrate"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
<div class="mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-zinc-100 mb-2">IFAC Settings</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3"> IFAC (Interface Access Code) settings are used
for creating
private
networks and can be configured on the interface level. </p>
<div class="grid grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Network Name</label>
<input
v-model="sharedInterfaceSettings.network_name"
type="text"
placeholder="Enter network name"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">Passphrase</label>
<input
v-model="sharedInterfaceSettings.passphrase"
type="text"
placeholder="Enter passphrase"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-900 dark:text-zinc-100">IFAC Size</label>
<input
v-model="sharedInterfaceSettings.ifac_size"
type="number"
min="8"
max="512"
placeholder="Enter size (8-512)"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white"
/>
</div>
</div>
</div>
</div>
</div>
<div class="p-2">
<!-- add button -->
<button @click="addInterface" type="button"
class="bg-green-500 hover:bg-green-400 focus-visible:outline-green-500 my-auto inline-flex items-center gap-x-1 rounded-md p-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
<span v-if="isEditingInterface">Save Interface</span>
<span v-else>Add Interface</span>
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import Utils from "../../js/Utils";
import DialogUtils from "../../js/DialogUtils";
export default {
name: 'AddInterfacePage',
data() {
return {
isEditingInterface: false,
showAllSettings: false, // more interface settings, used for TCPInterface and RNodeInterface
showSharedInterfaceSettings: false, // shared interface settings like IFAC, bitrate
showRNodeLoRaParameters: false, // accordion for displaying RNode physical LoRa parameters
showRNodeSubInterfaces: false, // accordion for adding multiple RNode interfaces
appInfo: null,
transportEnabled: false,
config: null,
comports: [],
newInterfaceName: null,
newInterfaceType: null,
newInterfaceGroupID: null,
newInterfaceMulticastAddressType: null,
newInterfaceDevices: null,
newInterfaceIgnoredDevices: null,
newInterfaceDiscoveryScope: null,
newInterfaceDiscoveryPort: null,
newInterfaceDataPort: null,
newInterfaceTargetHost: null,
newInterfaceTargetPort: null,
newInterfaceListenIp: null,
newInterfaceListenPort: null,
newInterfaceNetworkDevice: null,
newInterfacePreferIPV6: null,
newInterfaceKISSFramingEnabled: null,
newInterfaceI2PTunnelingEnabled: null,
sharedInterfaceSettings: {
"mode": null,
"network_name": null,
"passphrase": null,
"ifac_size": null,
},
newInterfaceForwardIp: null,
newInterfaceForwardPort: null,
I2PSettings: {
showPeerAccordion: false,
newInterfacePeers: [],
},
RNodeMultiInterface: {
port: null,
subInterfaces: [],
},
newInterfacePort: null,
RNodeGHzValue: 0,
RNodeMHzValue: 0,
RNodekHzValue: 0,
newInterfaceFrequency: null,
newInterfaceBandwidth: null,
newInterfaceTxpower: null,
newInterfaceSpreadingFactor: null,
newInterfaceCodingRate: null,
// Serial, KISS, and AX25KISS options
newInterfaceSpeed: null,
newInterfaceDatabits: null,
newInterfaceParity: null,
newInterfaceStopbits: null,
// KISS and AX25KISS
newInterfacePreamble: null,
newInterfaceTXTail: null,
newInterfacePersistence: null,
newInterfaceSlotTime: null,
// RNode and KISS
newInterfaceCallsign: null,
newInterfaceIDInterval: null,
newInterfaceFlowControl: null,
newInterfaceAirtimeLimitLong: null,
newInterfaceAirtimeLimitShort: null,
// Pipe interface
newInterfaceCommand: null,
newInterfaceRespawnDelay: null,
RNodeInterfaceDefaults: {
// bandwidth in hz
bandwidths: [
7800, // 7.8 kHz
10400, // 10.4 kHz
15600, // 15.6 kHz
20800, // 20.8 kHz
31250, // 31.25 kHz
41700, // 41.7 kHz
62500, // 62.5 kHz
125000, // 125 kHz
250000, // 250 kHz
500000, // 500 kHz
1625000, // 1625 kHz (for 2.4 GHz SX1280)
],
codingrates: [
5, // 4:5
6, // 4:6
7, // 4:7
8, // 4:8
],
spreadingfactors: [
5,
6,
7,
8,
9,
10,
11,
12,
],
},
RNodeInterfaceLoRaParameters: {
antennaGain: 0,
noiseFloor: 5,
sensitivity: null,
dataRate: null,
linkBudget: null,
},
};
},
computed: {
formattedFrequency() {
const totalHz = this.calculateFrequencyInHz();
if (totalHz >= 1e9) {
return `${(totalHz / 1e9).toFixed(3)} GHz`;
} else if (totalHz >= 1e6) {
return `${(totalHz / 1e6).toFixed(3)} MHz`;
} else if (totalHz >= 1e3) {
return `${(totalHz / 1e3).toFixed(3)} kHz`;
}
return `${totalHz} Hz`;
},
},
watch: {
newInterfaceBandwidth: "updateRNodeCalculations",
newInterfaceSpreadingFactor: "updateRNodeCalculations",
newInterfaceCodingRate: "updateRNodeCalculations",
newInterfaceTxpower: "updateRNodeCalculations",
'RNodeInterfaceLoRaParameters.antennaGain': "updateRNodeCalculations",
},
mounted() {
this.getAppInfo();
this.getConfig();
this.loadComports();
// check if we are editing an interface
const interfaceName = this.$route.query.interface_name;
if (interfaceName != null) {
this.isEditingInterface = true;
this.loadInterfaceToEdit(interfaceName);
}
},
methods: {
async getConfig() {
try {
const response = await window.axios.get(`/api/v1/config`);
this.config = response.data.config;
} catch (e) {
// do nothing if failed to load config
console.log(e);
}
},
async updateConfig(config) {
try {
const response = await window.axios.patch("/api/v1/config", config);
this.config = response.data.config;
} catch (e) {
alert("Failed to save config!");
console.log(e);
}
},
async loadComports() {
try {
const response = await window.axios.get(`/api/v1/comports`);
this.comports = response.data.comports;
} catch (e) {
// do nothing if failed to load interfaces
}
},
async loadInterfaceToEdit(interfaceName) {
try {
// fetch interfaces
const response = await window.axios.get(`/api/v1/reticulum/interfaces`);
const interfaces = response.data.interfaces;
// find interface, else show error and redirect to interfaces
const iface = interfaces[interfaceName];
if (!iface) {
DialogUtils.alert("The selected interface for editing could not be found.");
this.$router.push({
"name": "interfaces",
});
return;
}
// set form values
this.newInterfaceName = interfaceName;
this.newInterfaceType = iface.type;
// AutoInterface additional settings
this.newInterfaceGroupID = iface.group_id;
this.newInterfaceMulticastAddressType = iface.multicast_address_type;
this.newInterfaceDevices = iface.devices;
this.newInterfaceIgnoredDevices = iface.ignored_devices;
this.newInterfaceDiscoveryScope = iface.discovery_scope;
this.newInterfaceDiscoveryPort = iface.discovery_port;
this.newInterfaceDataPort = iface.data_port;
// tcp client interface
this.newInterfaceTargetHost = iface.target_host;
this.newInterfaceTargetPort = iface.target_port;
if (iface.kiss_framing) {
this.newInterfaceKISSFramingEnabled = true;
}
if (iface.i2p_tunneled) {
this.newInterfaceI2PTunnelingEnabled = true;
}
if (iface.prefer_ipv6) {
this.newInterfacePreferIPV6 = true;
}
// tcp server interface & udp interface
this.newInterfaceNetworkDevice = iface.device;
this.newInterfaceListenIp = iface.listen_ip;
this.newInterfaceListenPort = iface.listen_port;
// I2P Interface
if (iface.peers) {
const peerstoAdd = iface.peers.split(',');
for (const address of peerstoAdd) {
this.addI2PPeer(address);
}
}
// udp interface
this.newInterfaceForwardIp = iface.forward_ip;
this.newInterfaceForwardPort = iface.forward_port;
// Port (For RNode, Serial, and KISS)
this.newInterfacePort = iface.port;
// RNode Interface
this.newInterfaceFrequency = iface.frequency;
this.RNodeGHzValue = Math.floor(iface.frequency / 1e9);
this.RNodeMHzValue = Math.floor((iface.frequency % 1e9) / 1e6);
this.RNodekHzValue = Math.floor((iface.frequency % 1e6) / 1e3);
this.newInterfaceBandwidth = iface.bandwidth;
this.newInterfaceTxpower = iface.txpower;
this.newInterfaceSpreadingFactor = iface.spreadingfactor;
this.newInterfaceCodingRate = iface.codingrate;
// RNode Multi Interface
this.RNodeMultiInterface.subInterfaces = iface.sub_interfaces;
// Serial, KISS, and AX25KISS
this.newInterfaceSpeed = iface.speed;
this.newInterfaceDatabits = iface.databits;
this.newInterfaceParity = iface.parity;
this.newInterfaceStopbits = iface.stopbits;
this.newInterfacePreamble = iface.preamble;
this.newInterfaceTXTail = iface.txtail;
this.newInterfacePersistence = iface.persistence;
this.newInterfaceSlotTime = iface.slottime;
this.newInterfaceCallsign = iface.callsign;
this.newInterfaceIDInterval = iface.id_interval;
this.newInterfaceSSID = iface.ssid;
// Airtime limit
this.newInterfaceAirtimeLimitLong = iface.airtime_limit_long;
this.newInterfaceAirtimeLimitShort = iface.airtime_limit_short;
// Pipe Interface
this.newInterfaceCommand = iface.command;
this.newInterfaceRespawnDelay = iface.respawn_delay;
// Shared interface settings
this.sharedInterfaceSettings.mode = iface.mode;
this.sharedInterfaceSettings.bitrate = iface.bitrate;
this.sharedInterfaceSettings.network_name = iface.network_name;
this.sharedInterfaceSettings.passphrase = iface.passphrase;
this.sharedInterfaceSettings.ifac_size = iface.ifac_size;
} catch (e) {
// do nothing if failed to load interfaces
}
},
async addInterface() {
try {
let subInterfacesData = null;
if (this.newInterfaceType === 'RNodeMultiInterface') {
subInterfacesData = this.RNodeMultiInterface.subInterfaces.map(s => ({
name: s.name,
frequency: s.frequency,
bandwidth: s.bandwidth,
txpower: s.txpower,
spreadingfactor: s.spreadingfactor,
codingrate: s.codingrate,
vport: s.vport
}));
}
// add interface
const response = await window.axios.post(`/api/v1/reticulum/interfaces/add`, {
allow_overwriting_interface: this.isEditingInterface,
// required values
name: this.newInterfaceName,
type: this.newInterfaceType,
// AutoInterface
group_id: this.newInterfaceGroupID,
multicast_address_type: this.newInterfaceMulticastAddressType,
devices: this.newInterfaceDevices,
ignored_devices: this.newInterfaceIgnoredDevices,
discovery_scope: this.newInterfaceDiscoveryScope,
discovery_port: this.newInterfaceDiscoveryPort,
data_port: this.newInterfaceDataPort,
// tcp client interface
target_host: this.newInterfaceTargetHost,
target_port: this.newInterfaceTargetPort,
// TCP Client & Server interface
kiss_framing: this.newInterfaceKISSFramingEnabled,
i2p_tunneled: this.newInterfaceI2PTunnelingEnabled,
// tcp server interface & udp interface
listen_ip: this.newInterfaceListenIp,
listen_port: this.newInterfaceListenPort,
device: this.newInterfaceNetworkDevice,
prefer_ipv6: this.newInterfacePreferIPV6,
// udp interface
forward_ip: this.newInterfaceForwardIp,
forward_port: this.newInterfaceForwardPort,
// I2P Interface
peers: this.I2PSettings.newInterfacePeers.join(','),
// rnode interface
port: this.newInterfacePort,
frequency: this.calculateFrequencyInHz(),
bandwidth: this.newInterfaceBandwidth,
txpower: this.newInterfaceTxpower,
spreadingfactor: this.newInterfaceSpreadingFactor,
codingrate: this.newInterfaceCodingRate,
// RNode Multi Interface
sub_interfaces: subInterfacesData,
// Seiral, KISS, and AX25KISS
speed: this.newInterfaceSpeed,
databits: this.newInterfaceDatabits,
parity: this.newInterfaceParity,
stopbits: this.newInterfaceStopbits,
// KISS and AX25KISS
preamble: this.newInterfacePreamble,
txtail: this.newInterfaceTXTail,
persistence: this.newInterfacePersistence,
slottime: this.newInterfaceSlotTime,
callsign: this.newInterfaceCallsign,
id_interval: this.newInterfaceIDInterval,
ssid: this.newInterfaceSSID,
// Pipe interface
command: this.newInterfaceCommand,
respawn_delay: this.newInterfaceRespawnDelay,
// Airtime limit
airtime_limit_long: this.newInterfaceAirtimeLimitLong,
airtime_limit_short: this.newInterfaceAirtimeLimitShort,
// Shared interface settings
mode: this.sharedInterfaceSettings.mode,
bitrate: this.sharedInterfaceSettings.bitrate,
network_name: this.sharedInterfaceSettings.network_name,
passphrase: this.sharedInterfaceSettings.passphrase,
ifac_size: this.sharedInterfaceSettings.ifac_size,
});
// show success message
if (response.data.message) {
DialogUtils.alert(response.data.message);
}
// go to interfaces page
this.$router.push({
name: "interfaces",
});
} catch (e) {
const message = e.response?.data?.message ?? "failed to add interface";
DialogUtils.alert(message);
console.log(e);
}
},
formatFrequency(hz) {
return Utils.formatFrequency(hz);
},
calculateFrequencyInHz() {
const ghzToHz = this.RNodeGHzValue * 1e9;
const mhzToHz = this.RNodeMHzValue * 1e6;
const khzToHz = this.RNodekHzValue * 1e3;
return ghzToHz + mhzToHz + khzToHz;
},
updateRNodeCalculations() {
this.calculateRNodeParameters(
this.newInterfaceBandwidth,
this.newInterfaceSpreadingFactor,
this.newInterfaceCodingRate,
this.RNodeInterfaceLoRaParameters.noiseFloor,
this.RNodeInterfaceLoRaParameters.antennaGain,
this.newInterfaceTxpower
);
},
calculateRNodeParameters(bandwidth, spreadingFactor, codingRate, noiseFloor, antennaGain, transmitPower) {
// https://unsigned.io/understanding-lora-parameters/
// "SX1272/3/6/7/8 LoRa Modem Design Guide" https://www.openhacks.com/uploadsproductos/loradesignguide_std.pdf
// 4:5 - 4:8
const crn = {
5: 1,
6: 2,
7: 3,
8: 4,
};
codingRate = crn[codingRate];
const sfn = {
5: -2.5,
6: -5,
7: -7.5,
8: -10,
9: -12.5,
10: -15,
11: -17.5,
12: -20
};
let dataRate = spreadingFactor * ((4 / (4 + codingRate)) / (Math.pow(2, spreadingFactor) / (bandwidth / 1000))) * 1000
let sensitivity = -174 + 10 * Math.log10(bandwidth) + noiseFloor + (sfn[spreadingFactor] || 0);
if (bandwidth === 203125 || bandwidth === 406250 || bandwidth > 500000) {
sensitivity = -165.6 + 10 * Math.log10(bandwidth) + noiseFloor + (sfn[spreadingFactor] || 0);
}
let linkBudget = (transmitPower - sensitivity) + antennaGain;
this.RNodeInterfaceLoRaParameters.dataRate = dataRate < 1000
? `${dataRate.toFixed(0)} bps`
: `${(dataRate / 1000).toFixed(2)} kbps`;
this.RNodeInterfaceLoRaParameters.linkBudget = `${linkBudget.toFixed(1)} dB`;
this.RNodeInterfaceLoRaParameters.sensitivity = `${sensitivity.toFixed(1)} dBm`;
},
toggleI2PPeerAccordion() {
this.I2PSettings.showPeerAccordion = !this.I2PSettings.showPeerAccordion;
},
addI2PPeer(address = "") {
this.I2PSettings.newInterfacePeers.push(address);
},
removeI2PPeer(index) {
this.I2PSettings.newInterfacePeers.splice(index, 1);
},
toggleAllSettings() {
this.showAllSettings = !this.showAllSettings;
},
toggleSharedInterfaceSettings() {
this.showSharedInterfaceSettings = !this.showSharedInterfaceSettings;
},
toggleRNodeLoRaParameters() {
this.showRNodeLoRaParameters = !this.showRNodeLoRaParameters;
},
toggleRNodeSubInterfaces() {
this.showRNodeSubInterfaces = !this.showRNodeSubInterfaces;
},
addSubInterface() {
this.RNodeMultiInterface.subInterfaces.push({
name: '',
frequency: null,
bandwidth: null,
txpower: null,
spreadingfactor: null,
codingrate: null,
vport: null
});
},
useKISSAX25() {
if (this.newInterfaceType === 'AX25KISSInterface') {
this.newInterfaceType = "KISSInterface";
} else {
this.newInterfaceType = 'AX25KISSInterface';
}
},
removeSubInterface(idx) {
this.RNodeMultiInterface.subInterfaces.splice(idx, 1);
},
async getAppInfo() {
try {
const response = await window.axios.get("/api/v1/app/info");
this.appInfo = response.data.app_info;
this.transportEnabled = this.appInfo.is_transport_enabled;
} catch (e) {
console.log(e);
}
},
},
}
</script>