implement new network visualiser

This commit is contained in:
liamcottle 2024-08-11 20:41:25 +12:00
commit 520b6e6c2a
12 changed files with 338 additions and 18 deletions

95
package-lock.json generated
View file

@ -12,6 +12,7 @@
"click-outside-vue3": "^4.0.1",
"electron-prompt": "^1.7.0",
"mitt": "^3.0.1",
"vis-network": "^9.1.9",
"vue-router": "^4.4.2"
},
"devDependencies": {
@ -88,6 +89,18 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
"peer": true,
"dependencies": {
"@types/hammerjs": "^2.0.36"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@electron/asar": {
"version": "3.2.10",
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.10.tgz",
@ -1172,6 +1185,12 @@
"@types/node": "*"
}
},
"node_modules/@types/hammerjs": {
"version": "2.0.45",
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.45.tgz",
"integrity": "sha512-qkcUlZmX6c4J8q45taBKTL3p+LbITgyx7qhlPYOdOHZB7B31K0mXbP5YA7i7SgDeEGuI9MnumiKPEMrxg8j3KQ==",
"peer": true
},
"node_modules/@types/http-cache-semantics": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
@ -2022,6 +2041,15 @@
"node": ">=0.10.0"
}
},
"node_modules/component-emitter": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
"integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/compress-commons": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
@ -3422,6 +3450,12 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/keycharm": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz",
"integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==",
"peer": true
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@ -4513,6 +4547,19 @@
"dev": true,
"peer": true
},
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"peer": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/verror": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
@ -4528,6 +4575,54 @@
"node": ">=0.6.0"
}
},
"node_modules/vis-data": {
"version": "7.1.9",
"resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.9.tgz",
"integrity": "sha512-COQsxlVrmcRIbZMMTYwD+C2bxYCFDNQ2EHESklPiInbD/Pk3JZ6qNL84Bp9wWjYjAzXfSlsNaFtRk+hO9yBPWA==",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/visjs"
},
"peerDependencies": {
"uuid": "^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
"vis-util": "^5.0.1"
}
},
"node_modules/vis-network": {
"version": "9.1.9",
"resolved": "https://registry.npmjs.org/vis-network/-/vis-network-9.1.9.tgz",
"integrity": "sha512-Ft+hLBVyiLstVYSb69Q1OIQeh3FeUxHJn0WdFcq+BFPqs+Vq1ibMi2sb//cxgq1CP7PH4yOXnHxEH/B2VzpZYA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/visjs"
},
"peerDependencies": {
"@egjs/hammerjs": "^2.0.0",
"component-emitter": "^1.3.0",
"keycharm": "^0.2.0 || ^0.3.0 || ^0.4.0",
"uuid": "^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
"vis-data": "^6.3.0 || ^7.0.0",
"vis-util": "^5.0.1"
}
},
"node_modules/vis-util": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/vis-util/-/vis-util-5.0.7.tgz",
"integrity": "sha512-E3L03G3+trvc/X4LXvBfih3YIHcKS2WrP0XTdZefr6W6Qi/2nNCqZfe4JFfJU6DcQLm6Gxqj2Pfl+02859oL5A==",
"peer": true,
"engines": {
"node": ">=8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/visjs"
},
"peerDependencies": {
"@egjs/hammerjs": "^2.0.0",
"component-emitter": "^1.3.0 || ^2.0.0"
}
},
"node_modules/vite": {
"version": "5.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz",

View file

@ -97,6 +97,7 @@
"click-outside-vue3": "^4.0.1",
"electron-prompt": "^1.7.0",
"mitt": "^3.0.1",
"vis-network": "^9.1.9",
"vue-router": "^4.4.2"
}
}

View file

@ -78,18 +78,16 @@
</SidebarLink>
</li>
<!-- network -->
<!-- network visualiser -->
<li>
<a target="_blank" href="/network.html" class="w-full text-gray-800 group flex gap-x-3 rounded-r-full p-2 mr-2 text-sm leading-6 font-semibold focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 hover:bg-gray-100">
<span class="my-auto">
<SidebarLink :to="{ name: 'network-visualiser' }">
<template v-slot:icon>
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 256 256" class="w-6 h-6">
<path d="M200,152a31.84,31.84,0,0,0-19.53,6.68l-23.11-18A31.65,31.65,0,0,0,160,128c0-.74,0-1.48-.08-2.21l13.23-4.41A32,32,0,1,0,168,104c0,.74,0,1.48.08,2.21l-13.23,4.41A32,32,0,0,0,128,96a32.59,32.59,0,0,0-5.27.44L115.89,81A32,32,0,1,0,96,88a32.59,32.59,0,0,0,5.27-.44l6.84,15.4a31.92,31.92,0,0,0-8.57,39.64L73.83,165.44a32.06,32.06,0,1,0,10.63,12l25.71-22.84a31.91,31.91,0,0,0,37.36-1.24l23.11,18A31.65,31.65,0,0,0,168,184a32,32,0,1,0,32-32Zm0-64a16,16,0,1,1-16,16A16,16,0,0,1,200,88ZM80,56A16,16,0,1,1,96,72,16,16,0,0,1,80,56ZM56,208a16,16,0,1,1,16-16A16,16,0,0,1,56,208Zm56-80a16,16,0,1,1,16,16A16,16,0,0,1,112,128Zm88,72a16,16,0,1,1,16-16A16,16,0,0,1,200,200Z"></path>
</svg>
</span>
<span class="my-auto flex w-full">
Network Visualiser
</span>
</a>
</template>
<template v-slot:text>Network Visualiser</template>
</SidebarLink>
</li>
<!-- settings -->

View file

@ -0,0 +1,221 @@
<template>
<div id="network"></div>
</template>
<script>
import { Network } from "vis-network";
import Utils from "../../js/Utils";
export default {
name: 'NetworkVisualiser',
data() {
return {
config: null,
interfaces: [],
pathTable: [],
announces: {},
};
},
mounted() {
this.update();
},
methods: {
async getInterfaceStats() {
try {
const response = await axios.get(`/api/v1/interface-stats`);
this.interfaces = response.data.interface_stats?.interfaces ?? [];
} catch(e) {
alert("failed to load interface stats");
console.log(e);
}
},
async getPathTable() {
try {
const response = await axios.get(`/api/v1/path-table`);
this.pathTable = response.data.path_table;
} catch(e) {
alert("failed to load path table");
console.log(e);
}
},
async getConfig() {
try {
const response = await axios.get("/api/v1/config");
this.config = response.data.config;
} catch(e) {
alert("failed to load config");
console.error(e);
}
},
async getAnnounces() {
try {
// fetch announces
const response = await window.axios.get(`/api/v1/announces`);
// cache announces
this.announces = {};
for(const announce of response.data.announces){
this.announces[announce.destination_hash] = announce;
}
} catch(e) {
// do nothing if failed to load announces
console.log(e);
}
},
async update() {
await this.getConfig();
await this.getInterfaceStats();
await this.getPathTable();
await this.getAnnounces();
const nodes = [];
const edges = [];
// add me
nodes.push({
id: "me",
group: "me",
label: this.config?.display_name ?? "This Device",
size: 50,
font: {
color: "#000000",
background: "#ffffff",
},
});
// add interfaces
for(const entry of this.interfaces){
const node = {
id: entry.name,
group: "interface",
label: entry.name,
size: 30,
font: {
color: "#000000",
background: '#ffffff',
},
shape: "circularImage",
image: entry.status ? "/assets/images/network-visualiser/interface_connected.png" : "/assets/images/network-visualiser/interface_disconnected.png",
};
// add interface node
nodes.push(node);
// add edge from me to interface
edges.push({
from: "me",
to: entry.name,
color: "transparent",
background: {
enabled: true,
color: entry.status ? "#22c55e" : "#ef4444",
},
label: [
`Bitrate: ${this.formatBitsPerSecond(entry.bitrate)}`,
`TX: ${this.formatBytes(entry.txb)}`,
`RX: ${this.formatBytes(entry.rxb)}`,
].join("\n"),
});
}
// add paths for announces
for(const entry of this.pathTable){
// skip this path if hops are unknown
if(entry.hops == null){
continue;
}
// find what announced this path, or skip showing it for now
const announce = this.announces[entry.hash];
if(!announce){
continue;
}
const node = {
id: entry.hash,
group: "announce",
size: 15,
};
if(announce.aspect === "lxmf.delivery"){
node.shape = "circularImage";
node.image = "/assets/images/network-visualiser/user.png";
node.label = [
announce.app_data ? atob(announce.app_data) : "Anonymous Peer",
`${entry.hops} ${entry.hops === 1 ? 'Hop' : 'Hops'}`,
].join("\n");
}
if(announce.aspect === "nomadnetwork.node"){
node.shape = "circularImage";
node.image = "/assets/images/network-visualiser/server.png";
node.label = [
announce.app_data ? atob(announce.app_data) : "Anonymous Node",
`${entry.hops} ${entry.hops === 1 ? 'Hop' : 'Hops'}`,
].join("\n");
}
// add node
nodes.push(node);
// add edge from interface to announced aspect
edges.push({
from: entry.interface,
to: entry.hash,
color: "gray",
});
}
// create network ui
const container = document.getElementById("network");
new Network(container, {
nodes: nodes,
edges: edges,
}, {
layout: {
// always layout nodes the same way across reloads if nothing changed
randomSeed: 1,
},
nodes: {
color: {
border: "#000000",
highlight: {
border: "#000000",
},
},
},
physics: {
barnesHut: {
gravitationalConstant: -5000,
},
},
groups: {
"me": {
shape: "image",
image: "/assets/images/reticulum_logo_512.png",
},
"interface": {
},
"announce": {
},
},
});
},
formatBytes: function(bytes) {
return Utils.formatBytes(bytes);
},
formatBitsPerSecond: function(bits) {
return Utils.formatBitsPerSecond(bits);
},
},
}
</script>

View file

@ -0,0 +1,14 @@
<template>
<NetworkVisualiser class="w-full h-full"/>
</template>
<script>
import NetworkVisualiser from "./NetworkVisualiser.vue";
export default {
name: 'NetworkVisualiserPage',
components: {
NetworkVisualiser,
},
}
</script>

View file

@ -1,9 +0,0 @@
<template>
<iframe class="w-full h-full" src="/network.html"/>
</template>
<script>
export default {
name: 'NetworkVisualiserPage',
}
</script>

View file

@ -5,7 +5,7 @@ import vClickOutside from "click-outside-vue3";
import App from './components/App.vue';
import AboutPage from "./components/about/AboutPage.vue";
import SettingsPage from "./components/settings/SettingsPage.vue";
import NetworkVisualiserPage from "./components/network/NetworkVisualiserPage.vue";
import NetworkVisualiserPage from "./components/network-visualiser/NetworkVisualiserPage.vue";
import InterfacesPage from "./components/interfaces/InterfacesPage.vue";
import NomadNetworkPage from "./components/nomadnetwork/NomadNetworkPage.vue";
import MessagesPage from "./components/messages/MessagesPage.vue";

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB