implement tab to display all known nomadnetwork nodes

This commit is contained in:
liamcottle
2024-05-06 00:10:48 +12:00
parent 424ccf2fa4
commit 585fc4c789

View File

@@ -64,7 +64,7 @@
<!-- sidebar -->
<div class="flex flex-col w-80 h-full py-2 space-y-2">
<!-- peers -->
<!-- peers/nodes -->
<div class="flex-1 flex flex-col border rounded-xl bg-white shadow overflow-hidden">
<!-- tabs -->
@@ -72,9 +72,11 @@
<div class="-mb-px flex">
<!-- Current: "border-blue-500 text-blue-600", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" -->
<div @click="tab = 'peers'" class="w-full border-b-2 py-3 px-1 text-center text-sm font-medium cursor-pointer" :class="[ tab === 'peers' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700']">Peers</div>
<div @click="tab = 'nodes'" class="w-full border-b-2 py-3 px-1 text-center text-sm font-medium cursor-pointer" :class="[ tab === 'nodes' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700']">Nodes</div>
</div>
</div>
<!-- peers -->
<template v-if="tab === 'peers'">
<div v-if="peersCount > 0" class="p-1 border-b border-gray-300">
<input v-model="peersSearchTerm" type="text" :placeholder="`Search ${peersCount} peers...`" 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">
@@ -122,6 +124,54 @@
</div>
</template>
<!-- nodes -->
<template v-if="tab === 'nodes'">
<div v-if="nodesCount > 0" class="p-1 border-b border-gray-300">
<input v-model="nodesSearchTerm" type="text" :placeholder="`Search ${nodesCount} nodes...`" 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">
</div>
<div v-if="searchedNodes.length > 0" class="overflow-y-scroll">
<div @click="onNodeClick(node)" v-for="node of searchedNodes" class="flex cursor-pointer p-2 border-l-2" :class="[ node.destination_hash === selectedNode?.destination_hash ? 'bg-gray-100 border-blue-500' : 'bg-white border-transparent hover:bg-gray-50 hover:border-gray-200' ]">
<div class="my-auto mr-2">
<div class="bg-gray-200 text-gray-500 p-2 rounded">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 0 1-3-3m3 3a3 3 0 1 0 0 6h13.5a3 3 0 1 0 0-6m-16.5-3a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3m-19.5 0a4.5 4.5 0 0 1 .9-2.7L5.737 5.1a3.375 3.375 0 0 1 2.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 0 1 .9 2.7m0 0a3 3 0 0 1-3 3m0 3h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Zm-3 6h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Z" />
</svg>
</div>
</div>
<div>
<div class="text-gray-900">{{ node.name }}</div>
<div class="text-gray-500 text-sm">{{ formatTimeAgo(node.updated_at) }}</div>
</div>
</div>
</div>
<div v-else class="mx-auto my-auto text-center leading-5">
<!-- no nodes at all -->
<div v-if="nodesCount === 0" class="flex flex-col">
<div class="mx-auto mb-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z" />
</svg>
</div>
<div class="font-semibold">No Nodes Discovered</div>
<div>Waiting for a node to announce!</div>
</div>
<!-- is searching, but no results -->
<div v-if="nodesSearchTerm !== '' && nodesCount > 0" class="flex flex-col">
<div class="mx-auto mb-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
</svg>
</div>
<div class="font-semibold">No Search Results</div>
<div>Your search didn't match any Nodes!</div>
</div>
</div>
</template>
</div>
<!-- my identity -->
@@ -421,6 +471,10 @@
peersSearchTerm: "",
selectedPeer: null,
nodes: {},
nodesSearchTerm: "",
selectedNode: null,
lxmfMessagesRequestSequence: 0,
chatItems: [],
@@ -432,6 +486,7 @@
mounted: function() {
this.connectWebsocket();
this.getLxmfDeliveryAnnounces();
this.getNomadnetworkNodeAnnounces();
},
methods: {
connectWebsocket: function() {
@@ -465,6 +520,8 @@
const aspect = json.announce.aspect;
if(aspect === "lxmf.delivery"){
this.updatePeerFromAnnounce(json.announce);
} else if(aspect === "nomadnetwork.node"){
this.updateNodeFromAnnounce(json.announce);
}
break;
}
@@ -803,6 +860,28 @@
} catch(e) {
// do nothing if failed to load announces
console.log(e);
}
},
async getNomadnetworkNodeAnnounces() {
try {
// fetch announces for "nomadnetwork.node" aspect
const response = await window.axios.get(`/api/v1/announces`, {
params: {
aspect: "nomadnetwork.node",
},
});
// update ui
const nodeAnnounces = response.data.announces;
for(const nodeAnnounce of nodeAnnounces){
this.updateNodeFromAnnounce(nodeAnnounce);
}
} catch(e) {
// do nothing if failed to load announces
console.log(e);
}
},
getPeerNameFromAppData: function(appData) {
@@ -813,6 +892,14 @@
return "Anonymous Peer";
}
},
getNodeNameFromAppData: function(appData) {
try {
// app data should be node name, and our server provides it base64 encoded
return atob(appData);
} catch(e){
return "Anonymous Node";
}
},
updatePeerFromAnnounce: function(announce) {
this.peers[announce.destination_hash] = {
...announce,
@@ -820,6 +907,13 @@
name: this.getPeerNameFromAppData(announce.app_data),
};
},
updateNodeFromAnnounce: function(announce) {
this.nodes[announce.destination_hash] = {
...announce,
// helper property for easily grabbing node name from app data
name: this.getNodeNameFromAppData(announce.app_data),
};
},
async loadLxmfMessages(destinationHash) {
const seq = ++this.lxmfMessagesRequestSequence;
try {
@@ -910,10 +1004,15 @@
},
onPeerClick: function(peer) {
this.selectedNode = null;
this.selectedPeer = peer;
this.chatItems = [];
this.loadLxmfMessages(peer.destination_hash);
},
onNodeClick: function(node) {
this.selectedPeer = null;
this.selectedNode = node;
},
parseSeconds: function(secondsToFormat) {
secondsToFormat = Number(secondsToFormat);
var days = Math.floor(secondsToFormat / (3600 * 24));
@@ -1102,6 +1201,26 @@
return [];
},
nodesCount() {
return Object.keys(this.nodes).length;
},
nodesOrderedByLatestAnnounce() {
const nodes = Object.values(this.nodes);
return nodes.sort(function(nodeA, nodeB) {
// order by updated_at desc
const nodeAUpdatedAt = new Date(nodeA.updated_at).getTime();
const nodeBUpdatedAt = new Date(nodeB.updated_at).getTime();
return nodeBUpdatedAt - nodeAUpdatedAt;
});
},
searchedNodes() {
return this.nodesOrderedByLatestAnnounce.filter((node) => {
const search = this.nodesSearchTerm.toLowerCase();
const matchesAppData = node.name.toLowerCase().includes(search);
const matchesDestinationHash = node.destination_hash.toLowerCase().includes(search);
return matchesAppData || matchesDestinationHash;
});
},
canSendMessage() {
// can't send if empty message