diff --git a/src/frontend/components/App.vue b/src/frontend/components/App.vue index d8374d6..b4bb566 100644 --- a/src/frontend/components/App.vue +++ b/src/frontend/components/App.vue @@ -370,343 +370,9 @@ -
- - -
-
- - - -
-
Reticulum MeshChat must be restarted for any interface changes to take effect.
- -
- - - - -
- - -
-
-
- - - -
- - {{ iface._stats.ifac_size * 8 }}-bit IFAC with sig <{{ iface._stats.ifac_signature.slice(0, 6) }}...{{ iface._stats.ifac_signature.slice(-6) }}> - -
-
- -
- - -
- - - - - - - - - - - - - - - - - - - - - -
- - -
-
{{ iface._name }}
-
- - - - {{ iface.type }} • Ethernet and WiFi - - - - - {{ iface.type }} • {{ iface.target_host }}:{{ iface.target_port }} - - - - - {{ iface.type }} • {{ iface.listen_ip }}:{{ iface.listen_port }} - - - - - {{ iface.type }} • {{ iface.listen_ip }}:{{ iface.listen_port }} • {{ iface.forward_ip }}:{{ iface.forward_port }} - - - - - {{ iface.type }} • {{ iface.port }} • freq={{ iface.frequency }} • bw={{ iface.bandwidth }} • power={{ iface.txpower }}dBm • sf={{ iface.spreadingfactor }} • cr={{ iface.codingrate }} - - - - - {{ iface.type ?? 'Unknown Interface Type' }} - - -
-
- - -
- Enabled - Disabled -
- - -
- - -
- - -
- -
- - -
- -
- -
- -
- - -
Connected
-
Disconnected
- - -
• Bitrate: {{ formatBitsPerSecond(iface._stats?.bitrate ?? 0) }}
-
• TX: {{ formatBytes(iface._stats?.txb ?? 0) }}
-
• RX: {{ formatBytes(iface._stats?.rxb ?? 0) }}
- -
- -
-
- - -
- - -
-
-
-
Community Interfaces
-
These TCP interfaces serve as a quick way to test Reticulum. We suggest running your own as these may not always be available.
-
-
- -
-
-
- -
-
-
RNS Testnet Amsterdam
-
amsterdam.connect.reticulum.network:4965
-
-
- -
-
- -
-
-
RNS Testnet BetweenTheBorders
-
betweentheborders.com:4242
-
-
- -
-
- -
-
- - -
-
- Edit Interface - Add Interface -
-
- - -
- - -
Interface names must be unique.
-
- - -
- - - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
{{ formatFrequency(newInterfaceFrequency) }}
-
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - - - -
-
- -
+ @@ -721,10 +387,12 @@ import MessagesSidebar from "./messages/MessagesSidebar.vue"; import NomadNetworkSidebar from "./nomadnetwork/NomadNetworkSidebar.vue"; import ConversationViewer from "./messages/ConversationViewer.vue"; import DialogUtils from "../js/DialogUtils"; +import InterfacesPage from "./interfaces/InterfacesPage.vue"; export default { name: 'App', components: { + InterfacesPage, ConversationViewer, NomadNetworkSidebar, MessagesSidebar, @@ -774,60 +442,6 @@ export default { nomadnetPageDownloadCallbacks: {}, nomadnetFileDownloadCallbacks: {}, - interfaces: {}, - interfaceStats: {}, - - newInterfaceName: null, - newInterfaceType: null, - - newInterfaceTargetHost: null, - newInterfaceTargetPort: null, - - newInterfaceListenIp: null, - newInterfaceListenPort: null, - - newInterfaceForwardIp: null, - newInterfaceForwardPort: null, - - newInterfacePort: null, - newInterfaceFrequency: null, - newInterfaceBandwidth: null, - newInterfaceTxpower: null, - newInterfaceSpreadingFactor: null, - newInterfaceCodingRate: 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 - ], - codingrates: [ - 5, // 4:5 - 6, // 4:6 - 7, // 4:7 - 8, // 4:8 - ], - spreadingfactors: [ - 7, - 8, - 9, - 10, - 11, - 12, - ], - }, - - comports: [], - }; }, mounted() { @@ -837,10 +451,6 @@ export default { this.getLxmfDeliveryAnnounces(); this.getNomadnetworkNodeAnnounces(); this.getConversations(); - this.loadInterfaces(); - this.updateInterfaceStats(); - - this.loadComports(); // fixme: this is called by the micron-parser.js window.onNodePageUrlClick = (url) => { @@ -853,7 +463,6 @@ export default { // update info every few seconds setInterval(() => { this.updateCallsList(); - this.updateInterfaceStats(); this.getConversations(); }, 3000); @@ -1294,87 +903,6 @@ export default { name: this.getNodeNameFromAppData(announce.app_data), }; }, - async loadInterfaces() { - try { - - // fetch interfaces - const response = await window.axios.get(`/api/v1/reticulum/interfaces`); - - // update ui - this.interfaces = response.data.interfaces; - - } catch(e) { - // do nothing if failed to load interfaces - } - }, - 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 updateInterfaceStats() { - try { - - // fetch interface stats - const response = await window.axios.get(`/api/v1/interface-stats`); - - // update data - const interfaces = response.data.interface_stats?.interfaces ?? []; - for(const iface of interfaces){ - this.interfaceStats[iface.name] = iface; - } - - } catch(e) { - // do nothing if failed to load interfaces - } - }, - getInterfaceDescription(interfaceName) { - - // the interface-stats api returns interface names like the following; - // - // "AutoInterface[Default Interface]" - // "RNodeInterface[RNode LoRa Interface Fast]" - // "TCPInterface[RNS Testnet Amsterdam/amsterdam.connect.reticulum.network:4965]" - // - // however, the interfaces api just returns; - // "Default Interface" - // "RNode LoRa Interface Fast" - // "RNS Testnet Amsterdam" - // - // so we need to map the basic interface name to the former, so we can lookup stats for the interface - const iface = this.interfaces[interfaceName]; - if(iface){ - switch(iface.type){ - case "TCPClientInterface": { - // yes, this is meant to be passed as TCPInterface, even though the interface type includes client... - // example: "TCPInterface[RNS Testnet Amsterdam/amsterdam.connect.reticulum.network:4965]"; - return `TCPInterface[${interfaceName}/${iface.target_host}:${iface.target_port}]`; - } - case "TCPServerInterface": { - // example: "TCPServerInterface[TCP Server Interface/0.0.0.0:4242]"; - return `TCPServerInterface[${interfaceName}/${iface.listen_ip}:${iface.listen_port}]`; - } - case "UDPInterface": { - // example: "UDPInterface[UDP Interface/0.0.0.0:1234]"; - return `UDPInterface[${interfaceName}/${iface.listen_ip}:${iface.listen_port}]`; - } - default: { - // example: "RNodeInterface[RNode LoRa Interface Fast]", - return `${iface.type}[${interfaceName}]`; - } - } - } - - return null; - - }, - findInterfaceStats(interfaceName) { - const interfaceDescription = this.getInterfaceDescription(interfaceName); - return this.interfaceStats[interfaceDescription]; - }, async loadNodePage(destinationHash, pagePath, addToHistory = true, loadFromCache = true) { // get new sequence for this page load @@ -1731,208 +1259,6 @@ export default { return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i]; - }, - formatBitsPerSecond: function(bits) { - if (bits === 0) { - return '0 bps'; - } - - const k = 1000; // Use 1000 instead of 1024 for network speeds - const decimals = 0; - const sizes = ['bps', 'kbps', 'Mbps', 'Gbps', 'Tbps', 'Pbps', 'Ebps', 'Zbps', 'Ybps']; - - const i = Math.floor(Math.log(bits) / Math.log(k)); - - return parseFloat((bits / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i]; - }, - formatFrequency: function(hz) { - - if(hz === 0 || hz == null){ - return '0 Hz'; - } - - const k = 1000; - const sizes = ['Hz', 'kHz', 'MHz', 'GHz', 'THz', 'PHz', 'EHz', 'ZHz', 'YHz']; - const i = Math.floor(Math.log(hz) / Math.log(k)); - return parseFloat((hz / Math.pow(k, i))) + ' ' + sizes[i]; - - }, - async enableInterface(interfaceName) { - - // enable interface - try { - await window.axios.post(`/api/v1/reticulum/interfaces/enable`, { - name: interfaceName, - }); - } catch(e) { - DialogUtils.alert("failed to enable interface"); - console.log(e); - } - - // reload interfaces - await this.loadInterfaces(); - - }, - async disableInterface(interfaceName) { - - // disable interface - try { - await window.axios.post(`/api/v1/reticulum/interfaces/disable`, { - name: interfaceName, - }); - } catch(e) { - DialogUtils.alert("failed to disable interface"); - console.log(e); - } - - // reload interfaces - await this.loadInterfaces(); - - }, - showAddInterfaceForm() { - this.resetAddInterfaceForm(); - this.loadComports(); - this.isEditingInterface = false; - this.tab = "interfaces.add"; - }, - showEditInterfaceForm() { - this.resetAddInterfaceForm(); - this.loadComports(); - this.isEditingInterface = true; - this.tab = "interfaces.add"; - }, - resetAddInterfaceForm() { - - // clear add interface form - this.newInterfaceName = null; - this.newInterfaceType = null; - - // tcp client interface - this.newInterfaceTargetHost = null; - this.newInterfaceTargetPort = null; - - // tcp server interface & udp interface - this.newInterfaceListenIp = null; - this.newInterfaceListenPort = null; - - // udp interface - this.newInterfaceForwardIp = null; - this.newInterfaceForwardPort = null; - - // rnode interface - this.newInterfacePort = null; - this.newInterfaceFrequency = null; - this.newInterfaceBandwidth = null; - this.newInterfaceTxpower = null; - this.newInterfaceSpreadingFactor = null; - this.newInterfaceCodingRate = null; - - }, - async editInterface(interfaceName) { - - // find interface - const iface = this.interfaces[interfaceName]; - if(!iface){ - return; - } - - // go to edit interface tab - this.showEditInterfaceForm(); - - // set form values - this.newInterfaceName = interfaceName; - this.newInterfaceType = iface.type; - - // tcp client interface - this.newInterfaceTargetHost = iface.target_host; - this.newInterfaceTargetPort = iface.target_port; - - // tcp server interface & udp interface - this.newInterfaceListenIp = iface.listen_ip; - this.newInterfaceListenPort = iface.listen_port; - - // udp interface - this.newInterfaceForwardIp = iface.forward_ip; - this.newInterfaceForwardPort = iface.forward_port; - - // rnode interface - this.newInterfacePort = iface.port; - this.newInterfaceFrequency = iface.frequency; - this.newInterfaceBandwidth = iface.bandwidth; - this.newInterfaceTxpower = iface.txpower; - this.newInterfaceSpreadingFactor = iface.spreadingfactor; - this.newInterfaceCodingRate = iface.codingrate; - - }, - async deleteInterface(interfaceName) { - - // ask user to confirm deleting conversation history - if(!confirm("Are you sure you want to delete this interface? This can not be undone!")){ - return; - } - - // delete interface - try { - await window.axios.post(`/api/v1/reticulum/interfaces/delete`, { - name: interfaceName, - }); - } catch(e) { - DialogUtils.alert("failed to delete interface"); - console.log(e); - } - - // reload interfaces - await this.loadInterfaces(); - - }, - async addInterface() { - - try { - - // 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, - // tcp client interface - target_host: this.newInterfaceTargetHost, - target_port: this.newInterfaceTargetPort, - // tcp server interface & udp interface - listen_ip: this.newInterfaceListenIp, - listen_port: this.newInterfaceListenPort, - // udp interface - forward_ip: this.newInterfaceForwardIp, - forward_port: this.newInterfaceForwardPort, - // rnode interface - port: this.newInterfacePort, - frequency: this.newInterfaceFrequency, - bandwidth: this.newInterfaceBandwidth, - txpower: this.newInterfaceTxpower, - spreadingfactor: this.newInterfaceSpreadingFactor, - codingrate: this.newInterfaceCodingRate, - }); - - // reset add interface form - this.resetAddInterfaceForm(); - - // go to interfaces tab - this.tab = "interfaces"; - - // show success message - if(response.data.message){ - DialogUtils.alert(response.data.message); - } - - } catch(e) { - const message = e.response?.data?.message ?? "failed to add interface"; - DialogUtils.alert(message); - console.log(e); - } - - // reload interfaces - await this.loadInterfaces(); - }, onDestinationPathClick: function(path) { DialogUtils.alert(`${path.hops} ${ path.hops === 1 ? 'hop' : 'hops' } away via ${path.next_hop_interface}`); @@ -1979,19 +1305,6 @@ export default { return window.prompt(message); } }, - relaunch() { - if(window.electron){ - window.electron.relaunch(); - } - }, - isInterfaceEnabled: function(iface) { - const rawValue = iface.enabled ?? iface.interface_enabled; - const value = rawValue?.toLowerCase(); - return value === "on" || value === "yes" || value === "true"; - }, - onIFACSignatureClick: function(ifacSignature) { - DialogUtils.alert(ifacSignature); - }, }, computed: { isElectron() { @@ -2020,15 +1333,6 @@ export default { return audioCall.is_outbound; }); }, - interfacesWithStats() { - const results = []; - for(const [interfaceName, iface] of Object.entries(this.interfaces)){ - iface._name = interfaceName; - iface._stats = this.findInterfaceStats(interfaceName); - results.push(iface); - } - return results; - }, }, } diff --git a/src/frontend/components/interfaces/InterfacesPage.vue b/src/frontend/components/interfaces/InterfacesPage.vue new file mode 100644 index 0000000..c6f72cf --- /dev/null +++ b/src/frontend/components/interfaces/InterfacesPage.vue @@ -0,0 +1,732 @@ + + + diff --git a/src/frontend/js/Utils.js b/src/frontend/js/Utils.js index 1e7c131..71c9889 100644 --- a/src/frontend/js/Utils.js +++ b/src/frontend/js/Utils.js @@ -94,6 +94,36 @@ class Utils { return window.btoa(binary); } + static formatBitsPerSecond(bits) { + + if(bits === 0){ + return '0 bps'; + } + + const k = 1000; // Use 1000 instead of 1024 for network speeds + const decimals = 0; + const sizes = ['bps', 'kbps', 'Mbps', 'Gbps', 'Tbps', 'Pbps', 'Ebps', 'Zbps', 'Ybps']; + + const i = Math.floor(Math.log(bits) / Math.log(k)); + + return parseFloat((bits / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i]; + + } + + static formatFrequency(hz) { + + if(hz === 0 || hz == null){ + return '0 Hz'; + } + + const k = 1000; + const sizes = ['Hz', 'kHz', 'MHz', 'GHz', 'THz', 'PHz', 'EHz', 'ZHz', 'YHz']; + const i = Math.floor(Math.log(hz) / Math.log(k)); + + return parseFloat((hz / Math.pow(k, i))) + ' ' + sizes[i]; + + } + } export default Utils;