diff --git a/frontend/index.html b/frontend/index.html index 796f71b..67a2fff 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,5 +1,5 @@ - + diff --git a/frontend/public/images/icons/close.svg b/frontend/public/images/icons/close.svg new file mode 100644 index 0000000..3446b59 --- /dev/null +++ b/frontend/public/images/icons/close.svg @@ -0,0 +1,14 @@ + diff --git a/frontend/public/images/icons/dark-mode.svg b/frontend/public/images/icons/dark-mode.svg new file mode 100644 index 0000000..6de1aee --- /dev/null +++ b/frontend/public/images/icons/dark-mode.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/images/icons/light-mode.svg b/frontend/public/images/icons/light-mode.svg new file mode 100644 index 0000000..93058fd --- /dev/null +++ b/frontend/public/images/icons/light-mode.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/images/icons/menu.svg b/frontend/public/images/icons/menu.svg new file mode 100644 index 0000000..56cbb07 --- /dev/null +++ b/frontend/public/images/icons/menu.svg @@ -0,0 +1,3 @@ + diff --git a/frontend/src/components/HardwareImg.tsx b/frontend/src/components/HardwareImg.tsx index dfa1300..a97f933 100644 --- a/frontend/src/components/HardwareImg.tsx +++ b/frontend/src/components/HardwareImg.tsx @@ -18,7 +18,7 @@ export const HardwareImg = ({ model }: { model: number }) => { src={`${import.meta.env.BASE_URL}images/hardware/${image}`} alt={modelName} title={modelName} - className="w-8 h-8 object-cover" + className="w-8 h-8 object-cover dark:brightness-5" /> ); }; diff --git a/frontend/src/components/HeardBy.tsx b/frontend/src/components/HeardBy.tsx index c1582b6..c8d44f4 100644 --- a/frontend/src/components/HeardBy.tsx +++ b/frontend/src/components/HeardBy.tsx @@ -9,7 +9,10 @@ export const HeardBy = () => { <> {" "} that have been heard by the mesh by{" "} - + {config?.server?.node_id} {" "} ({config?.server?.node_id}) diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 26d15b0..f4b056d 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -1,226 +1,57 @@ -import { Link, useLocation } from "react-router-dom"; +import { useEffect, useState } from "react"; +import { useLocation } from "react-router-dom"; -import { useGetConfigQuery } from "../slices/apiSlice"; - -const defaultTools = [ - { name: "Armooo's MeshView", url: "https://meshview.armooo.net" }, - { name: "Liam's Meshtastic Map", url: "https://meshtastic.liamcottle.net" }, - { name: "MeshMap", url: "https://meshmap.net" }, - { name: "Bay Mesh Explorer", url: "https://app.bayme.sh" }, - { name: "HWT Path Profiler", url: "https://heywhatsthat.com/profiler.html" }, -]; +import { Menu } from "./Menu"; export const Layout = ({ children }: { children: React.ReactNode }) => { const { pathname } = useLocation(); - const { data: config } = useGetConfigQuery(); - - return ( - <> -
-
-
-
- {config?.mesh?.name?.split(" ").map((word, index) => ( - // eslint-disable-next-line react/no-array-index-key -
- {word[0]} - {word.slice(1)} -
- ))} -
-
- -
{config?.mesh?.description}
- -
- - Website - -
- - - - + const [isDark, setIsDark] = useState(false); - + useEffect(() => { + // set the initial state of the theme based on the user's preference + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches && + localStorage.getItem("theme") !== "light" + ) { + setIsDark(true); + } - + const handleColorSchemeChange = (event: MediaQueryListEvent) => { + // if there is a theme set in local storage, don't change the theme + if ( + localStorage.getItem("theme") === "light" || + localStorage.getItem("theme") === "dark" + ) + return; -
+ // else set the theme based on the event change + if (event.matches) { + setIsDark(true); + } else { + setIsDark(false); + } + }; - {/* - TODO
-
Data Updated
-
-
*/} + window + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener("change", handleColorSchemeChange); -
+ // Clean up the event listener + return () => { + window + .matchMedia("(prefers-color-scheme: dark)") + .removeEventListener("change", handleColorSchemeChange); + }; + }, []); -
-
- Powered by MeshInfo{" "} - - {config?.server?.version_info?.refName} - -
- - GitHub Stars - -
-
-
+ return ( +
+ setIsDark(dark)} /> -
+
{
- +
); }; diff --git a/frontend/src/components/Menu.tsx b/frontend/src/components/Menu.tsx new file mode 100644 index 0000000..be5c111 --- /dev/null +++ b/frontend/src/components/Menu.tsx @@ -0,0 +1,354 @@ +import { useState } from "react"; +import { Link } from "react-router-dom"; + +import { useGetConfigQuery } from "../slices/apiSlice"; + +const defaultTools = [ + { name: "Armooo's MeshView", url: "https://meshview.armooo.net" }, + { name: "Liam's Meshtastic Map", url: "https://meshtastic.liamcottle.net" }, + { name: "MeshMap", url: "https://meshmap.net" }, + { name: "Bay Mesh Explorer", url: "https://app.bayme.sh" }, + { name: "HWT Path Profiler", url: "https://heywhatsthat.com/profiler.html" }, +]; + +export const Menu = ({ + isDark, + onDarkChange, +}: { + isDark: boolean; + onDarkChange: (dark: boolean) => void; +}) => { + const { data: config } = useGetConfigQuery(); + + const [showMenu, setShowMenu] = useState(false); + + const handleDarkChange = (dark: boolean) => { + onDarkChange(dark); + localStorage.setItem("theme", dark ? "dark" : "light"); + }; + + return ( + <> + + +
+
+
+
+ {config?.mesh?.name?.split(" ").map((word, index) => ( +
+ {word[0]} + + {word.slice(1)} + +
+ ))} +
+
+ +
{config?.mesh?.description}
+ + + + + + + + + + + + + +
+ + {/* + TODO
+
Data Updated
+
+
*/} + +
+ +
+
+ Powered by MeshInfo{" "} + + {config?.server?.version_info?.refName} + +
+
+ + GitHub Stars + + {isDark ? ( +
handleDarkChange(false)} + onKeyDown={() => handleDarkChange(false)} + tabIndex={0} + > + light mode icon +
+ ) : ( +
handleDarkChange(true)} + onKeyDown={() => handleDarkChange(true)} + tabIndex={0} + > + light mode icon +
+ )} +
+
+
+
+ + ); +}; diff --git a/frontend/src/pages/Chat.tsx b/frontend/src/pages/Chat.tsx index 2dfede9..11ab51f 100644 --- a/frontend/src/pages/Chat.tsx +++ b/frontend/src/pages/Chat.tsx @@ -35,8 +35,8 @@ export const Chat = () => { }, [channels]); return ( -
-
Chat
+
+
Chat

Chat

{channels.map(([id, channel]) => ( @@ -60,7 +60,7 @@ export const Chat = () => { className="block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm" > {channels.map(([id]) => ( - + ))}
@@ -78,6 +78,7 @@ export const Chat = () => { onClick={() => setSelectedChannel(id)} role="button" tabIndex={0} + key={`channel-selector-${id}`} > Channel {id} @@ -91,28 +92,28 @@ export const Chat = () => {

Channel {selectedChannel}

- +
- - - - - - - @@ -121,16 +122,23 @@ export const Chat = () => { {selectedChannel ? chat?.channels[selectedChannel].messages.map((message, i) => { const nodeFrom = nodes[message.from] || null; - const nodeSender = nodes[message.sender] || null; + const senders = message.sender + .map((s) => nodes[s]) + .filter(Boolean); const nodeTo = nodes[message.to] || null; const distanceFromSender = - nodeFrom && nodeSender - ? calculateDistanceBetweenNodes(nodeFrom, nodeSender) + nodeFrom && senders.length + ? senders + .filter((s) => s.position) + .map((s) => calculateDistanceBetweenNodes(nodeFrom, s)) + .filter(Boolean) : null; return ( - // eslint-disable-next-line react/no-array-index-key - +
+ Time + From + Via + To + Hops + DX + Message
{format( new Date(message.timestamp * 1000), @@ -141,6 +149,7 @@ export const Chat = () => { { - {nodeSender && ( - ( + + {s.shortname ?? "UNK"} + {idx < senders.length - 1 ? ", " : ""} + + )) + ) : ( + - {nodes[message.sender] - ? nodes[message.sender].shortname - : "UNK"} - + UNK + )} @@ -177,6 +191,7 @@ export const Chat = () => { ? `${message.to} / ${nodes[message.to].longname}` : `${message.to} / Unknown` } + className="dark:text-indigo-400 dark:visited:text-indigo-400 dark:hover:text-indigo-500" > {nodes[message.to] ? nodes[message.to].shortname @@ -193,7 +208,9 @@ export const Chat = () => { className="p-1 text-nowrap border border-gray-400" align="right" > - {distanceFromSender && `${distanceFromSender} km`} + {distanceFromSender?.length + ? distanceFromSender.map((d) => `${d} km`).join(", ") + : ""} {message.text} diff --git a/frontend/src/pages/Map.tsx b/frontend/src/pages/Map.tsx index e1e4e53..47a9f20 100644 --- a/frontend/src/pages/Map.tsx +++ b/frontend/src/pages/Map.tsx @@ -145,12 +145,31 @@ export function Map() { ]); const initialZoom = JSON.parse(localStorage.getItem("savedZoom") ?? "9.5"); + const tileLayer = new TileLayer({ + source: new OSM(), + }); + + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + tileLayer.on("prerender", (evt) => { + if (evt.context) { + const context = evt.context as CanvasRenderingContext2D; + context.filter = "grayscale(80%) invert(100%) "; + context.globalCompositeOperation = "source-over"; + } + }); + tileLayer.on("postrender", (evt) => { + if (evt.context) { + const context = evt.context as CanvasRenderingContext2D; + context.filter = "none"; + } + }); + } + const map = new OlMap({ - layers: [ - new TileLayer({ - source: new OSM(), - }), - ], + layers: [tileLayer], target: mapRef.current as HTMLElement, view: new View({ center: initialCenter, @@ -431,16 +450,16 @@ export function Map() { panel += "Elsewhere
"; const nodeId = parseInt(node.id, 16); - panel += `Armooo's MeshView
`; - panel += `Bay Mesh Explorer
`; - panel += `Liam's Map
`; - panel += `MeshMap
`; @@ -477,7 +496,7 @@ export function Map() { return (
-
+
NODE NAME @@ -491,7 +510,7 @@ export function Map() {
-
+
LEGEND
Heard A diff --git a/frontend/src/pages/MeshLog.tsx b/frontend/src/pages/MeshLog.tsx index ef7533d..63b68e2 100644 --- a/frontend/src/pages/MeshLog.tsx +++ b/frontend/src/pages/MeshLog.tsx @@ -17,11 +17,15 @@ export const MeshLog = () => { since this server was last restarted are shown.

- +
- - + + diff --git a/frontend/src/pages/MqttLog.tsx b/frontend/src/pages/MqttLog.tsx index e9990ba..f19d977 100644 --- a/frontend/src/pages/MqttLog.tsx +++ b/frontend/src/pages/MqttLog.tsx @@ -19,12 +19,18 @@ export const MqttLog = () => { are shown.

-
TimestampMessage + Timestamp + + Message +
+
- - - + + + diff --git a/frontend/src/pages/Neighbors.tsx b/frontend/src/pages/Neighbors.tsx index 81ed8e9..956414e 100644 --- a/frontend/src/pages/Neighbors.tsx +++ b/frontend/src/pages/Neighbors.tsx @@ -1,7 +1,9 @@ -import { formatDuration, formatISO, intervalToDuration } from "date-fns"; -import { useMemo } from "react"; +import { formatISO } from "date-fns"; +import { useEffect, useMemo, useState } from "react"; +import { Link } from "react-router-dom"; import { Avatar } from "../components/Avatar"; +import { DateToSince } from "../components/DateSince"; import { HeardBy } from "../components/HeardBy"; import { useGetNodesQuery } from "../slices/apiSlice"; import { convertNodeIdFromIntToHex } from "../utils/convertNodeId"; @@ -17,6 +19,15 @@ export const Neighbors = () => { [nodes] ); + const [currentDate, setCurrentDate] = useState(new Date()); + + useEffect(() => { + const interval = setInterval(() => { + setCurrentDate(new Date()); + }, 3000); + return () => clearInterval(interval); + }, []); + if (!nodes) { return
Loading...
; } @@ -33,63 +44,84 @@ export const Neighbors = () => {
TimestampTopicMessage + Timestamp + + Topic + + Message +
- - - - - - - - - + + + + - - {Object.entries(activeNodesWithNeighbors).map(([id, node]) => { - const sanitizedId = id.replace("!", ""); + {activeNodesWithNeighbors.map((node) => { + const id = node.id.replace("!", ""); return ( - + @@ -98,17 +130,20 @@ export const Neighbors = () => { @@ -137,9 +172,12 @@ export const Neighbors = () => { // eslint-disable-next-line react/no-array-index-key ); @@ -195,7 +227,12 @@ export const Neighbors = () => {


- Download JSON + + Download JSON + ); }; diff --git a/frontend/src/pages/Node.tsx b/frontend/src/pages/Node.tsx index cae099c..e958f32 100644 --- a/frontend/src/pages/Node.tsx +++ b/frontend/src/pages/Node.tsx @@ -25,7 +25,13 @@ export const Node = () => { return ( <>
- Nodes > {node.shortname} + + Nodes + {" "} + > {node.shortname}
@@ -122,6 +128,7 @@ export const Node = () => { href={`https://meshview.armooo.net/packet_list/${convertNodeIdFromHexToInt(node.id)}`} target="_blank" rel="noreferrer" + className="dark:text-indigo-400 dark:visited:text-indigo-400 dark:hover:text-indigo-500" > Armooo's MeshView @@ -130,6 +137,7 @@ export const Node = () => { href={`https://app.bayme.sh/node/${node.id}`} target="_blank" rel="noreferrer" + className="dark:text-indigo-400 dark:visited:text-indigo-400 dark:hover:text-indigo-500" > Bay Mesh Explorer @@ -138,6 +146,7 @@ export const Node = () => { href={`https://meshtastic.liamcottle.net/?node_id=${convertNodeIdFromHexToInt(node.id)}`} target="_blank" rel="noreferrer" + className="dark:text-indigo-400 dark:visited:text-indigo-400 dark:hover:text-indigo-500" > Liam's Map @@ -146,6 +155,7 @@ export const Node = () => { href={`https://meshmap.net/#${convertNodeIdFromHexToInt(node.id)}`} target="_blank" rel="noreferrer" + className="dark:text-indigo-400 dark:visited:text-indigo-400 dark:hover:text-indigo-500" > MeshMap @@ -156,7 +166,7 @@ export const Node = () => {

Details

-
+ ID + Name + Neighbors Seen
- + + Short LongHeardHeard ByInterval + + Long + + Heard + + Heard By + + Interval + Last + Since
- - - + + + - {node.shortname} + + {node.shortname} + {node.longname} - {node.neighborinfo?.neighbors?.map( - (neighbor, index) => ( - // eslint-disable-next-line react/no-array-index-key - + {node.neighborinfo?.neighbors?.map((neighbor) => { + const neighborIdHex = convertNodeIdFromIntToHex( + neighbor.node_id + ); + return ( + - ) - )} + ); + })}
- {nodes[neighbor.node_id] ? ( - - {nodes[neighbor.node_id].shortname} - + {nodes[neighborIdHex].shortname} + ) : ( UNK )} @@ -121,8 +156,8 @@ export const Neighbors = () => { `${neighbor.distance} km`}
- + {nnode.shortname} - + SNR: {neighbor.snr} @@ -175,16 +213,10 @@ export const Neighbors = () => { className="hidden xl:table-cell p-1 text-nowrap border border-gray-400" align="right" > - {formatDuration( - intervalToDuration({ - start: new Date(node.last_seen), - end: new Date(), - }), - { - format: ["seconds"], - } - )} - secs +
+
@@ -238,9 +252,11 @@ export const Node = () => { Location @@ -248,9 +264,11 @@ export const Node = () => { Altitude @@ -265,9 +283,11 @@ export const Node = () => { {calculateDistanceBetweenNodes( nodes[config?.server?.node_id ?? ""], node - ) !== null - ? `${calculateDistanceBetweenNodes(nodes[config?.server?.node_id ?? ""], node)} km` - : "Unknown"} + ) !== null ? ( + `${calculateDistanceBetweenNodes(nodes[config?.server?.node_id ?? ""], node)} km` + ) : ( + Unknown + )} @@ -290,7 +310,9 @@ export const Node = () => { className="p-1 text-nowrap" title={node.last_seen || "Unknown"} > - {node.last_seen || "Unknown"} + {node.last_seen || ( + Unknown + )} @@ -299,36 +321,44 @@ export const Node = () => {

Heard (zero hop)

-
@@ -215,7 +225,9 @@ export const Node = () => { case 10: return "ATAK Tracker"; default: - return "Unknown"; + return ( + Unknown + ); } })() : "Client"} @@ -228,9 +240,11 @@ export const Node = () => { {node.position && node.position.latitude_i && - node.position.longitude_i - ? `${node.position.longitude_i / 1e7}, ${node.position.latitude_i / 1e7}` - : "Unknown"} + node.position.longitude_i ? ( + `${node.position.longitude_i / 1e7}, ${node.position.latitude_i / 1e7}` + ) : ( + Unknown + )}
- {node.position && node.position.geocoded - ? node.position.geocoded.display_name - : "Unknown"} + {node.position && node.position.geocoded ? ( + node.position.geocoded.display_name + ) : ( + Unknown + )}
- {node.position && node.position.altitude - ? `${node.position.altitude} m` - : "Unknown"} + {node.position && node.position.altitude ? ( + `${node.position.altitude} m` + ) : ( + Unknown + )}
+
{node.neighborinfo ? ( node.neighborinfo?.neighbors?.map((neighbor, index) => { - const nid = convertNodeIdFromIntToHex(neighbor.node_id); - const nnode = nodes[nid] || null; - return ( - // eslint-disable-next-line react/no-array-index-key - - - - - - ); - }) - ) : ( - - - - )} + const nid = convertNodeIdFromIntToHex(neighbor.node_id); + const nnode = nodes[nid] || null; + return ( + // eslint-disable-next-line react/no-array-index-key + + + + + + ); + }) + ) : ( + + + + )}
- {nnode ? ( - {nnode.shortname} - ) : ( - UNK - )} - SNR: {neighbor.snr} - {nnode && calculateDistanceBetweenNodes(nnode, node) - ? `${calculateDistanceBetweenNodes(nnode, node)} km` - : ""} -
No neighbors detected. Does this node publish Neighbor Info?
+ {nnode ? ( + + {nnode.shortname} + + ) : ( + UNK + )} + SNR: {neighbor.snr} + {nnode && calculateDistanceBetweenNodes(nnode, node) + ? `${calculateDistanceBetweenNodes(nnode, node)} km` + : ""} +
+ No neighbors detected. Does this node publish Neighbor + Info? +
@@ -337,7 +367,7 @@ export const Node = () => {

Heard By (zero hop)

- +
{Object.entries(nodes).map( ([iid, nnode], index) => @@ -351,7 +381,12 @@ export const Node = () => {
{iid in nodes ? ( - {nodes[iid].shortname} + + {nodes[iid].shortname} + ) : ( UNK )} diff --git a/frontend/src/pages/NodeMap.tsx b/frontend/src/pages/NodeMap.tsx index 2bddf6a..0f8a924 100644 --- a/frontend/src/pages/NodeMap.tsx +++ b/frontend/src/pages/NodeMap.tsx @@ -56,14 +56,32 @@ export const NodeMap = ({ node }: { node: INode }) => { useEffect(() => { if (olMap) return; if (!node.position || !mapRef) return; - console.log("mount"); + + const tileLayer = new TileLayer({ + source: new OSM(), + }); + + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + tileLayer.on("prerender", (evt) => { + if (evt.context) { + const context = evt.context as CanvasRenderingContext2D; + context.filter = "grayscale(80%) invert(100%) "; + context.globalCompositeOperation = "source-over"; + } + }); + tileLayer.on("postrender", (evt) => { + if (evt.context) { + const context = evt.context as CanvasRenderingContext2D; + context.filter = "none"; + } + }); + } const map = new OlMap({ - layers: [ - new TileLayer({ - source: new OSM(), - }), - ], + layers: [tileLayer], target: mapRef.current as HTMLElement, view: new View({ center: fromLonLat([node.position.longitude, node.position.latitude]), diff --git a/frontend/src/pages/Nodes.tsx b/frontend/src/pages/Nodes.tsx index 4c3fba7..3ad4840 100644 --- a/frontend/src/pages/Nodes.tsx +++ b/frontend/src/pages/Nodes.tsx @@ -94,7 +94,6 @@ export const Nodes = () => { } else { setSort({ by: column, dir: "asc" }); } - console.log("Sorting by %s %s", sort.by, sort.dir); } return ( @@ -106,100 +105,113 @@ export const Nodes = () => { of {Object.entries(nodes).length} seen nodes - +
- - - - + - - - - + - - - - - - - - - - @@ -258,20 +279,30 @@ export const Nodes = () => { {node.position.altitude || ""}
+ ID + Name + HW Role + Role + Last Position + Neighbors Telemetry + Seen
- Hex + + + Hex + + - - + + + + Coordinates + DX + Count + Battery + Voltage + Air Util TX + Channel Util + @@ -214,11 +226,16 @@ export const Nodes = () => { align="center" valign="middle" > - + {id ? ( - {id.replace("!", "")} + + {id.replace("!", "")} + ) : ( id )} @@ -228,14 +245,18 @@ export const Nodes = () => { style={{ color: node.shortname === "UNK" ? "#777" : "#000" }} > {id ? ( - {node.shortname} + + {node.shortname} + ) : ( node.shortname )} {node.longname} - {node.position && node.position.latitude && node.position.longitude ? ( - Yes + {node.position && + node.position.latitude && + node.position.longitude ? ( + + Yes + ) : ( - <> - + <> )} - {serverNode?.position && serverNode.position.latitude && serverNode.position.longitude && - node.position && node.position.latitude && node.position.latitude !== 0 && - node.position.longitude && node.position.longitude !== 0 && + {serverNode?.position && + serverNode.position.latitude && + serverNode.position.longitude && + node.position && + node.position.latitude && + node.position.latitude !== 0 && + node.position.longitude && + node.position.longitude !== 0 && getDistanceBetweenTwoPoints( [node.position.longitude, node.position.latitude], [ @@ -351,7 +382,12 @@ export const Nodes = () => {


- Download JSON + + Download JSON + ); }; diff --git a/frontend/src/pages/Telemetry.tsx b/frontend/src/pages/Telemetry.tsx index 3f26c3b..c28ae89 100644 --- a/frontend/src/pages/Telemetry.tsx +++ b/frontend/src/pages/Telemetry.tsx @@ -1,3 +1,5 @@ +import { Link } from "react-router-dom"; + import { HeardBy } from "../components/HeardBy"; import { useGetNodesQuery, useGetTelemetryQuery } from "../slices/apiSlice"; @@ -24,113 +26,119 @@ export const Telemetry = () => { - - +
+ Timestamp + Node Air Util TX Channel Util Battery Uptime Voltage Current Barometric Pressure Relative Humidity Temperature Gas Resistance @@ -142,7 +150,7 @@ export const Telemetry = () => { const inode = nodes[item.from]; return ( // eslint-disable-next-line react/no-array-index-key -
{"timestamp" in item ? ( new Date(item.timestamp * 1000).toISOString() @@ -152,7 +160,12 @@ export const Telemetry = () => { {inode ? ( - {inode.shortname} + + {inode.shortname} + ) : ( UNK )} diff --git a/frontend/src/pages/Traceroutes.tsx b/frontend/src/pages/Traceroutes.tsx index 07ff0d8..55c6052 100644 --- a/frontend/src/pages/Traceroutes.tsx +++ b/frontend/src/pages/Traceroutes.tsx @@ -1,3 +1,5 @@ +import { Link } from "react-router-dom"; + import { HeardBy } from "../components/HeardBy"; import { useGetNodesQuery, useGetTraceroutesQuery } from "../slices/apiSlice"; @@ -21,26 +23,41 @@ export const Traceroutes = () => { Traceroutes as

- +
- - - - -
+ Timestamp + From + To + Hops + Route Route Hops @@ -60,14 +77,24 @@ export const Traceroutes = () => { {fnode ? ( - {fnode.shortname} + + {fnode.shortname} + ) : ( UNK )} {tnode ? ( - {tnode.shortname} + + {tnode.shortname} + ) : ( UNK )} @@ -82,7 +109,12 @@ export const Traceroutes = () => { // eslint-disable-next-line react/no-array-index-key {hnode ? ( - {hnode.shortname} + + {hnode.shortname} + ) : ( UNK )} diff --git a/frontend/src/slices/appSlice.ts b/frontend/src/slices/appSlice.ts index 7a290b9..76cdaa7 100644 --- a/frontend/src/slices/appSlice.ts +++ b/frontend/src/slices/appSlice.ts @@ -1,25 +1,14 @@ +/* eslint-disable no-param-reassign */ import { createSlice } from "@reduxjs/toolkit"; +interface InitialState {} + +const initialState: InitialState = {}; + export const appSlice = createSlice({ name: "app", - initialState: { - value: 7, - }, - reducers: { - increment: (state) => { - // Redux Toolkit allows us to write "mutating" logic in reducers. It - // doesn't actually mutate the state because it uses the immer library, - // which detects changes to a "draft state" and produces a brand new - // immutable state based off those changes - state.value += 1; - }, - decrement: (state) => { - state.value -= 1; - }, - incrementByAmount: (state, action) => { - state.value += action.payload; - }, - }, + initialState, + reducers: {}, }); -export const { increment, decrement, incrementByAmount } = appSlice.actions; +export const {} = appSlice.actions; diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 978d984..1c2e6ce 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -110,7 +110,7 @@ export interface IMessage { id: number; to: string; from: string; - sender: string; + sender: string[]; hops_away: number; timestamp: number; message: string; diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts index 5fef7c2..32dd6c4 100644 --- a/frontend/tailwind.config.ts +++ b/frontend/tailwind.config.ts @@ -9,4 +9,5 @@ export default { corePlugins: { preflight: true, }, + darkMode: "class", } satisfies Config; diff --git a/mqtt.py b/mqtt.py index cb6fcd7..12148e9 100644 --- a/mqtt.py +++ b/mqtt.py @@ -383,6 +383,8 @@ async def handle_nodeinfo(self, msg): if 'role' in msg['payload']: node['role'] = msg['payload']['role'] + else: + node['role'] = 0 self.data.update_node(id, node) print(f"Node {id} updated") diff --git a/templates/static/layout.html.j2 b/templates/static/layout.html.j2 index 3efb10e..da1a6d0 100644 --- a/templates/static/layout.html.j2 +++ b/templates/static/layout.html.j2 @@ -37,6 +37,10 @@ {% endif %} + +