implement ping page to allow for continuous pinging

This commit is contained in:
liamcottle 2024-12-24 16:56:21 +13:00
commit 76aa9168ca
3 changed files with 220 additions and 0 deletions

View 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>

View file

@ -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>

View file

@ -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',