mirror of
https://github.com/liamcottle/reticulum-meshchat.git
synced 2026-04-27 16:10:32 +00:00
implement ping page to allow for continuous pinging
This commit is contained in:
parent
3392e24f0d
commit
76aa9168ca
3 changed files with 220 additions and 0 deletions
195
src/frontend/components/ping/PingPage.vue
Normal file
195
src/frontend/components/ping/PingPage.vue
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
<template>
|
||||
<div class="flex flex-col flex-1 overflow-hidden min-w-full sm:min-w-[500px] dark:bg-zinc-950">
|
||||
<div class="flex flex-col h-full space-y-2 p-2 overflow-y-auto">
|
||||
|
||||
<!-- appearance -->
|
||||
<div class="bg-white dark:bg-zinc-800 rounded shadow">
|
||||
<div class="flex border-b border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-200 p-2 font-semibold">Ping</div>
|
||||
<div class="dark:divide-zinc-700 text-gray-900 dark:text-gray-100 p-2">
|
||||
Only lxmf.delivery destinations can be pinged.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- inputs -->
|
||||
<div class="bg-white dark:bg-zinc-800 rounded shadow">
|
||||
<div class="divide-y divide-gray-300 dark:divide-zinc-700 text-gray-900 dark:text-gray-100">
|
||||
|
||||
<div class="p-2">
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">Destination Hash</div>
|
||||
<div class="flex">
|
||||
<input v-model="destinationHash" type="text" placeholder="e.g: 7b746057a7294469799cd8d7d429676a" class="bg-gray-50 dark:bg-zinc-700 border border-gray-300 dark:border-zinc-600 text-gray-900 dark:text-gray-100 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-600 dark:focus:border-blue-600 block w-full p-2.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-2">
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">Ping Timeout (seconds)</div>
|
||||
<div class="flex">
|
||||
<input v-model="timeout" type="number" placeholder="Timeout" class="bg-gray-50 dark:bg-zinc-700 border border-gray-300 dark:border-zinc-600 text-gray-900 dark:text-gray-100 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-600 dark:focus:border-blue-600 block w-full p-2.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-2 space-x-1">
|
||||
<button v-if="!isRunning" @click.stop="start" type="button" class="my-auto 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 dark:bg-zinc-800 dark:text-white dark:hover:bg-zinc-700 dark:focus-visible:outline-zinc-500">
|
||||
Start
|
||||
</button>
|
||||
<button v-if="isRunning" @click.stop="stop" type="button" class="my-auto 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 dark:bg-zinc-800 dark:text-white dark:hover:bg-zinc-700 dark:focus-visible:outline-zinc-500">
|
||||
Stop
|
||||
</button>
|
||||
<button @click.stop="clear" type="button" class="my-auto 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 dark:bg-zinc-800 dark:text-white dark:hover:bg-zinc-700 dark:focus-visible:outline-zinc-500">
|
||||
Clear Results
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- results -->
|
||||
<div class="flex flex-col h-full bg-white dark:bg-zinc-800 rounded shadow overflow-hidden">
|
||||
<div class="flex border-b border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-200 p-2 font-semibold">Results</div>
|
||||
<div id="results" class="flex flex-col h-full bg-black text-white p-2 overflow-y-scroll">
|
||||
<div v-for="pingResult of pingResults">{{ pingResult }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {CanceledError} from "axios";
|
||||
import DialogUtils from "../../js/DialogUtils";
|
||||
|
||||
export default {
|
||||
name: 'PingPage',
|
||||
data() {
|
||||
return {
|
||||
isRunning: false,
|
||||
destinationHash: null,
|
||||
timeout: 10,
|
||||
pingResults: [],
|
||||
abortController: null,
|
||||
};
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.stop();
|
||||
},
|
||||
methods: {
|
||||
async start() {
|
||||
|
||||
// do nothing if already running
|
||||
if(this.isRunning){
|
||||
return;
|
||||
}
|
||||
|
||||
// simple check to ensure destination hash is valid
|
||||
if(this.destinationHash == null || this.destinationHash.length !== 32){
|
||||
DialogUtils.alert("Invalid Destination Hash!");
|
||||
return;
|
||||
}
|
||||
|
||||
// simple check to ensure destination hash is valid
|
||||
if(this.timeout == null || this.timeout < 0){
|
||||
DialogUtils.alert("Timeout must be a number!");
|
||||
return;
|
||||
}
|
||||
|
||||
// we are now running ping
|
||||
this.isRunning = true;
|
||||
this.abortController = new AbortController();
|
||||
|
||||
// run ping until stopped
|
||||
while(this.isRunning){
|
||||
|
||||
// run ping
|
||||
await this.ping();
|
||||
|
||||
// wait a bit before running next ping
|
||||
await this.sleep(500);
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
async stop() {
|
||||
this.isRunning = false;
|
||||
this.abortController.abort();
|
||||
},
|
||||
async clear() {
|
||||
this.pingResults = [];
|
||||
},
|
||||
async sleep(millis) {
|
||||
return new Promise((resolve, reject) => setTimeout(resolve, millis));
|
||||
},
|
||||
async ping() {
|
||||
try {
|
||||
|
||||
// ping destination
|
||||
const response = await window.axios.get(`/api/v1/ping/${this.destinationHash}/lxmf.delivery`, {
|
||||
signal: this.abortController.signal,
|
||||
params: {
|
||||
timeout: this.timeout,
|
||||
},
|
||||
});
|
||||
|
||||
const pingResult = response.data.ping_result;
|
||||
const rttMilliseconds = (pingResult.rtt * 1000).toFixed(3);
|
||||
const rttDurationString = `${rttMilliseconds} ms`;
|
||||
|
||||
const info = [
|
||||
`Valid reply:`,
|
||||
`duration=${rttDurationString}`,
|
||||
`hops_there=${pingResult.hops_there}`,
|
||||
`hops_back=${pingResult.hops_back}`,
|
||||
];
|
||||
|
||||
// add rssi if available
|
||||
if(pingResult.rssi != null){
|
||||
info.push(`rssi=${pingResult.rssi}dBm`);
|
||||
}
|
||||
|
||||
// add snr if available
|
||||
if(pingResult.snr != null){
|
||||
info.push(`snr=${pingResult.snr}dB`);
|
||||
}
|
||||
|
||||
// add signal quality if available
|
||||
if(pingResult.quality != null){
|
||||
info.push(`quality=${pingResult.quality}%`);
|
||||
}
|
||||
|
||||
// update ui
|
||||
this.addPingResult(info.join(" "));
|
||||
|
||||
} catch(e) {
|
||||
|
||||
// ignore cancelled error
|
||||
if(e instanceof CanceledError){
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(e);
|
||||
|
||||
// add ping error to results
|
||||
const message = e.response?.data?.message ?? `Ping failed: ${e}`;
|
||||
this.addPingResult(message);
|
||||
|
||||
}
|
||||
},
|
||||
addPingResult(result) {
|
||||
this.pingResults.push(result);
|
||||
this.scrollPingResultsToBottom();
|
||||
},
|
||||
scrollPingResultsToBottom: function() {
|
||||
// next tick waits for the ui to have the new elements added
|
||||
this.$nextTick(() => {
|
||||
// set timeout with zero millis seems to fix issue where it doesn't scroll all the way to the bottom...
|
||||
setTimeout(() => {
|
||||
const container = document.getElementById("results");
|
||||
if(container){
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
@ -28,6 +28,26 @@
|
|||
</div>
|
||||
</a>
|
||||
|
||||
<!-- ping -->
|
||||
<RouterLink :to="{ name: 'ping' }" class="group flex bg-white dark:bg-zinc-800 p-2 rounded shadow hover:bg-gray-50 dark:hover:bg-zinc-700">
|
||||
<div class="mr-2">
|
||||
<div class="flex bg-gray-300 text-gray-500 rounded shadow p-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-10">
|
||||
<path fill-rule="evenodd" d="M14.615 1.595a.75.75 0 0 1 .359.852L12.982 9.75h7.268a.75.75 0 0 1 .548 1.262l-10.5 11.25a.75.75 0 0 1-1.272-.71l1.992-7.302H3.75a.75.75 0 0 1-.548-1.262l10.5-11.25a.75.75 0 0 1 .913-.143Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-auto mr-auto dark:text-gray-200">
|
||||
<div class="font-bold">Ping</div>
|
||||
<div class="text-sm">Allows you to ping an lxmf.delivery destination hash</div>
|
||||
</div>
|
||||
<div class="my-auto text-gray-400 group-hover:text-gray-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</RouterLink>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -60,6 +60,11 @@ const router = createRouter({
|
|||
path: '/propagation-nodes',
|
||||
component: defineAsyncComponent(() => import("./components/propagation-nodes/PropagationNodesPage.vue")),
|
||||
},
|
||||
{
|
||||
name: "ping",
|
||||
path: '/ping',
|
||||
component: defineAsyncComponent(() => import("./components/ping/PingPage.vue")),
|
||||
},
|
||||
{
|
||||
name: "settings",
|
||||
path: '/settings',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue