first working version
This commit is contained in:
+4
-2
@@ -41,11 +41,13 @@ export const App = (): JSX.Element => {
|
|||||||
<PageRouter />
|
<PageRouter />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Dashboard />
|
<>
|
||||||
|
<Dashboard />
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
|
||||||
</div>
|
</div>
|
||||||
</DeviceWrapper>
|
</DeviceWrapper>
|
||||||
</MapProvider>
|
</MapProvider>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export const ChannelChat = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3">
|
<div className="pl-3 pr-3 pt-3 pb-1">
|
||||||
<MessageInput to={to} channel={channel} />
|
<MessageInput to={to} channel={channel} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,42 +20,42 @@ export const PageLayout = ({
|
|||||||
}: PageLayoutProps): JSX.Element => {
|
}: PageLayoutProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="relative flex h-full w-full flex-col">
|
<div className="relative flex h-full w-full flex-col">
|
||||||
<div className="flex h-14 shrink-0 border-b-[0.5px] border-slate-300 dark:border-slate-700 md:h-16 md:px-4">
|
<div className="flex h-14 shrink-0 border-b-[0.5px] border-slate-300 dark:border-slate-700 md:h-16 md:px-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="pl-4 transition-all hover:text-accent md:hidden"
|
className="pl-4 transition-all hover:text-accent md:hidden"
|
||||||
>
|
>
|
||||||
<AlignLeftIcon />
|
<AlignLeftIcon />
|
||||||
</button>
|
</button>
|
||||||
<div className="flex flex-1 items-center justify-between px-4 md:px-0">
|
<div className="flex flex-1 items-center justify-between px-4 md:px-0">
|
||||||
<div className="flex w-full items-center">
|
<div className="flex w-full items-center">
|
||||||
<span className="w-full text-lg font-medium">{label}</span>
|
<span className="w-full text-lg font-medium">{label}</span>
|
||||||
<div className="flex justify-end space-x-4">
|
<div className="flex justify-end space-x-4">
|
||||||
{actions?.map((action, index) => (
|
{actions?.map((action, index) => (
|
||||||
<button
|
<button
|
||||||
key={action.icon.name}
|
key={action.icon.name}
|
||||||
type="button"
|
type="button"
|
||||||
className="transition-all hover:text-accent"
|
className="transition-all hover:text-accent"
|
||||||
onClick={action.onClick}
|
onClick={action.onClick}
|
||||||
>
|
>
|
||||||
<action.icon />
|
<action.icon />
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full flex-col overflow-y-auto",
|
||||||
|
!noPadding && "pl-3 pr-3 ",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex h-full w-full flex-col overflow-y-auto",
|
|
||||||
!noPadding && "pl-3 pr-3 ",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,38 +1,37 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export interface FooterProps
|
export interface FooterProps extends React.HTMLAttributes<HTMLElement> {}
|
||||||
extends React.HTMLAttributes<HTMLElement> {}
|
|
||||||
|
|
||||||
const Footer = React.forwardRef<HTMLElement, FooterProps>(
|
const Footer = React.forwardRef<HTMLElement, FooterProps>(
|
||||||
({className, ...props}, ref) => {
|
({ className, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<footer
|
<footer
|
||||||
className={`flex flex- justify-center pl-2 pr-2 pb-2 ${className}`}
|
className={`flex flex- justify-center p-2 ${className}`}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "var(--backgroundPrimary)",
|
backgroundColor: "var(--backgroundPrimary)",
|
||||||
color: "var(--textPrimary)",
|
color: "var(--textPrimary)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
href="https://vercel.com/?utm_source=meshtastic&utm_campaign=oss"
|
href="https://vercel.com/?utm_source=meshtastic&utm_campaign=oss"
|
||||||
className="hover:underline"
|
className="hover:underline"
|
||||||
style={{ color: "var(--link)" }}
|
style={{ color: "var(--link)" }}
|
||||||
>
|
>
|
||||||
Powered by ▲ Vercel
|
Powered by ▲ Vercel
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
| Meshtastic® is a registered trademark of Meshtastic LLC. |
|
| Meshtastic® is a registered trademark of Meshtastic LLC. |{" "}
|
||||||
{" "}
|
<a
|
||||||
<a
|
href="https://meshtastic.org/docs/legal"
|
||||||
href="https://meshtastic.org/docs/legal"
|
className="hover:underline"
|
||||||
className="hover:underline"
|
style={{ color: "var(--link)" }}
|
||||||
style={{ color: "var(--link)" }}
|
>
|
||||||
>
|
Legal Information
|
||||||
Legal Information
|
</a>
|
||||||
</a>
|
</p>
|
||||||
</p>
|
</footer>
|
||||||
</footer>
|
);
|
||||||
);
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
export default Footer;
|
export default Footer;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const TabsList = React.forwardRef<
|
|||||||
<TabsPrimitive.List
|
<TabsPrimitive.List
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex flex-wrap items-center rounded-md bg-slate-100 p-1 dark:bg-slate-800",
|
"inline-flex flex-wrap items-center rounded-md bg-slate-100 p-1 mt-2 dark:bg-slate-800",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -21,79 +21,81 @@ export const Dashboard = () => {
|
|||||||
const devices = useMemo(() => getDevices(), [getDevices]);
|
const devices = useMemo(() => getDevices(), [getDevices]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3 p-3">
|
<>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex flex-col gap-3 p-3">
|
||||||
<div className="space-y-1">
|
<div className="flex items-center justify-between">
|
||||||
<H3>Connected Devices</H3>
|
<div className="space-y-1">
|
||||||
<Subtle>Manage, connect and disconnect devices</Subtle>
|
<H3>Connected Devices</H3>
|
||||||
|
<Subtle>Manage, connect and disconnect devices</Subtle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex h-[450px] rounded-md border border-dashed border-slate-200 p-3 dark:border-slate-700">
|
||||||
|
{devices.length ? (
|
||||||
|
<ul className="grow divide-y divide-gray-200">
|
||||||
|
{devices.map((device) => {
|
||||||
|
return (
|
||||||
|
<li key={device.id}>
|
||||||
|
<div className="px-4 py-4 sm:px-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<p className="truncate text-sm font-medium text-accent">
|
||||||
|
{device.nodes.get(device.hardware.myNodeNum)?.user
|
||||||
|
?.longName ?? "UNK"}
|
||||||
|
</p>
|
||||||
|
<div className="inline-flex w-24 justify-center gap-2 rounded-full bg-slate-100 py-1 text-xs font-semibold text-slate-900 transition-colors hover:bg-slate-700 hover:text-slate-50">
|
||||||
|
{device.connection?.connType === "ble" && (
|
||||||
|
<>
|
||||||
|
<BluetoothIcon size={16} />
|
||||||
|
BLE
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{device.connection?.connType === "serial" && (
|
||||||
|
<>
|
||||||
|
<UsbIcon size={16} />
|
||||||
|
Serial
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{device.connection?.connType === "http" && (
|
||||||
|
<>
|
||||||
|
<NetworkIcon size={16} />
|
||||||
|
Network
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 sm:flex sm:justify-between">
|
||||||
|
<div className="flex gap-2 text-sm text-gray-500">
|
||||||
|
<UsersIcon
|
||||||
|
size={20}
|
||||||
|
className="text-gray-400"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{device.nodes.size === 0 ? 0 : device.nodes.size - 1}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<div className="m-auto flex flex-col gap-3 text-center">
|
||||||
|
<ListPlusIcon size={48} className="mx-auto text-textSecondary" />
|
||||||
|
<H3>No Devices</H3>
|
||||||
|
<Subtle>Connect atleast one device to get started</Subtle>
|
||||||
|
<Button
|
||||||
|
className="gap-2"
|
||||||
|
onClick={() => setConnectDialogOpen(true)}
|
||||||
|
>
|
||||||
|
<PlusIcon size={16} />
|
||||||
|
New Connection
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<div className="flex h-[450px] rounded-md border border-dashed border-slate-200 p-3 dark:border-slate-700">
|
|
||||||
{devices.length ? (
|
|
||||||
<ul className="grow divide-y divide-gray-200">
|
|
||||||
{devices.map((device) => {
|
|
||||||
return (
|
|
||||||
<li key={device.id}>
|
|
||||||
<div className="px-4 py-4 sm:px-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<p className="truncate text-sm font-medium text-accent">
|
|
||||||
{device.nodes.get(device.hardware.myNodeNum)?.user
|
|
||||||
?.longName ?? "UNK"}
|
|
||||||
</p>
|
|
||||||
<div className="inline-flex w-24 justify-center gap-2 rounded-full bg-slate-100 py-1 text-xs font-semibold text-slate-900 transition-colors hover:bg-slate-700 hover:text-slate-50">
|
|
||||||
{device.connection?.connType === "ble" && (
|
|
||||||
<>
|
|
||||||
<BluetoothIcon size={16} />
|
|
||||||
BLE
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{device.connection?.connType === "serial" && (
|
|
||||||
<>
|
|
||||||
<UsbIcon size={16} />
|
|
||||||
Serial
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{device.connection?.connType === "http" && (
|
|
||||||
<>
|
|
||||||
<NetworkIcon size={16} />
|
|
||||||
Network
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2 sm:flex sm:justify-between">
|
|
||||||
<div className="flex gap-2 text-sm text-gray-500">
|
|
||||||
<UsersIcon
|
|
||||||
size={20}
|
|
||||||
className="text-gray-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
{device.nodes.size === 0 ? 0 : device.nodes.size - 1}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
) : (
|
|
||||||
<div className="m-auto flex flex-col gap-3 text-center">
|
|
||||||
<ListPlusIcon size={48} className="mx-auto text-textSecondary" />
|
|
||||||
<H3>No Devices</H3>
|
|
||||||
<Subtle>Connect atleast one device to get started</Subtle>
|
|
||||||
<Button
|
|
||||||
className="gap-2"
|
|
||||||
onClick={() => setConnectDialogOpen(true)}
|
|
||||||
>
|
|
||||||
<PlusIcon size={16} />
|
|
||||||
New Connection
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+96
-94
@@ -87,108 +87,110 @@ export const MapPage = (): JSX.Element => {
|
|||||||
))}
|
))}
|
||||||
</SidebarSection>
|
</SidebarSection>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
<PageLayout
|
<div className="flex flex-col flex-grow">
|
||||||
label="Map"
|
<PageLayout
|
||||||
noPadding={true}
|
label="Map"
|
||||||
actions={[
|
noPadding={true}
|
||||||
{
|
actions={[
|
||||||
icon: ZoomInIcon,
|
{
|
||||||
onClick() {
|
icon: ZoomInIcon,
|
||||||
map?.zoomIn();
|
onClick() {
|
||||||
|
map?.zoomIn();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
icon: ZoomOutIcon,
|
||||||
icon: ZoomOutIcon,
|
onClick() {
|
||||||
onClick() {
|
map?.zoomOut();
|
||||||
map?.zoomOut();
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
icon: BoxSelectIcon,
|
||||||
icon: BoxSelectIcon,
|
onClick() {
|
||||||
onClick() {
|
getBBox();
|
||||||
getBBox();
|
},
|
||||||
},
|
},
|
||||||
},
|
]}
|
||||||
]}
|
|
||||||
>
|
|
||||||
<MapGl
|
|
||||||
mapStyle="https://raw.githubusercontent.com/hc-oss/maplibre-gl-styles/master/styles/osm-mapnik/v8/default.json"
|
|
||||||
// onClick={(e) => {
|
|
||||||
// const waypoint = new Protobuf.Waypoint({
|
|
||||||
// name: "test",
|
|
||||||
// description: "test description",
|
|
||||||
// latitudeI: Math.trunc(e.lngLat.lat * 1e7),
|
|
||||||
// longitudeI: Math.trunc(e.lngLat.lng * 1e7)
|
|
||||||
// });
|
|
||||||
// addWaypoint(waypoint);
|
|
||||||
// connection?.sendWaypoint(waypoint, "broadcast");
|
|
||||||
// }}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
attributionControl={false}
|
|
||||||
renderWorldCopies={false}
|
|
||||||
maxPitch={0}
|
|
||||||
style={{
|
|
||||||
filter: darkMode
|
|
||||||
? "brightness(0.6) invert(1) contrast(3) hue-rotate(200deg) saturate(0.3) brightness(0.7)"
|
|
||||||
: "",
|
|
||||||
}}
|
|
||||||
dragRotate={false}
|
|
||||||
touchZoomRotate={false}
|
|
||||||
initialViewState={{
|
|
||||||
zoom: 1.6,
|
|
||||||
latitude: 35,
|
|
||||||
longitude: 0,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{waypoints.map((wp) => (
|
<MapGl
|
||||||
<Marker
|
mapStyle="https://raw.githubusercontent.com/hc-oss/maplibre-gl-styles/master/styles/osm-mapnik/v8/default.json"
|
||||||
key={wp.id}
|
// onClick={(e) => {
|
||||||
longitude={wp.longitudeI / 1e7}
|
// const waypoint = new Protobuf.Waypoint({
|
||||||
latitude={wp.latitudeI / 1e7}
|
// name: "test",
|
||||||
anchor="bottom"
|
// description: "test description",
|
||||||
>
|
// latitudeI: Math.trunc(e.lngLat.lat * 1e7),
|
||||||
<div>
|
// longitudeI: Math.trunc(e.lngLat.lng * 1e7)
|
||||||
<MapPinIcon size={16} />
|
// });
|
||||||
</div>
|
// addWaypoint(waypoint);
|
||||||
</Marker>
|
// connection?.sendWaypoint(waypoint, "broadcast");
|
||||||
))}
|
// }}
|
||||||
{/* {rasterSources.map((source, index) => (
|
|
||||||
|
// @ts-ignore
|
||||||
|
attributionControl={false}
|
||||||
|
renderWorldCopies={false}
|
||||||
|
maxPitch={0}
|
||||||
|
style={{
|
||||||
|
filter: darkMode
|
||||||
|
? "brightness(0.6) invert(1) contrast(3) hue-rotate(200deg) saturate(0.3) brightness(0.7)"
|
||||||
|
: "",
|
||||||
|
}}
|
||||||
|
dragRotate={false}
|
||||||
|
touchZoomRotate={false}
|
||||||
|
initialViewState={{
|
||||||
|
zoom: 1.6,
|
||||||
|
latitude: 35,
|
||||||
|
longitude: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{waypoints.map((wp) => (
|
||||||
|
<Marker
|
||||||
|
key={wp.id}
|
||||||
|
longitude={wp.longitudeI / 1e7}
|
||||||
|
latitude={wp.latitudeI / 1e7}
|
||||||
|
anchor="bottom"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<MapPinIcon size={16} />
|
||||||
|
</div>
|
||||||
|
</Marker>
|
||||||
|
))}
|
||||||
|
{/* {rasterSources.map((source, index) => (
|
||||||
<Source key={index} type="raster" {...source}>
|
<Source key={index} type="raster" {...source}>
|
||||||
<Layer type="raster" />
|
<Layer type="raster" />
|
||||||
</Source>
|
</Source>
|
||||||
))} */}
|
))} */}
|
||||||
{allNodes.map((node) => {
|
{allNodes.map((node) => {
|
||||||
if (node.position?.latitudeI) {
|
if (node.position?.latitudeI) {
|
||||||
return (
|
return (
|
||||||
<Marker
|
<Marker
|
||||||
key={node.num}
|
key={node.num}
|
||||||
longitude={node.position.longitudeI / 1e7}
|
longitude={node.position.longitudeI / 1e7}
|
||||||
latitude={node.position.latitudeI / 1e7}
|
latitude={node.position.latitudeI / 1e7}
|
||||||
style={{ filter: darkMode ? "invert(1)" : "" }}
|
style={{ filter: darkMode ? "invert(1)" : "" }}
|
||||||
anchor="bottom"
|
anchor="bottom"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
map?.easeTo({
|
map?.easeTo({
|
||||||
zoom: 12,
|
zoom: 12,
|
||||||
center: [
|
center: [
|
||||||
(node.position?.longitudeI ?? 0) / 1e7,
|
(node.position?.longitudeI ?? 0) / 1e7,
|
||||||
(node.position?.latitudeI ?? 0) / 1e7,
|
(node.position?.latitudeI ?? 0) / 1e7,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex cursor-pointer gap-2 rounded-md border bg-backgroundPrimary p-1.5">
|
<div className="flex cursor-pointer gap-2 rounded-md border bg-backgroundPrimary p-1.5">
|
||||||
<Hashicon value={node.num.toString()} size={22} />
|
<Hashicon value={node.num.toString()} size={22} />
|
||||||
<Subtle className={cn(zoom < 12 && "hidden")}>
|
<Subtle className={cn(zoom < 12 && "hidden")}>
|
||||||
{node.user?.longName}
|
{node.user?.longName}
|
||||||
</Subtle>
|
</Subtle>
|
||||||
</div>
|
</div>
|
||||||
</Marker>
|
</Marker>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</MapGl>
|
</MapGl>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+54
-52
@@ -67,60 +67,62 @@ export const MessagesPage = (): JSX.Element => {
|
|||||||
))}
|
))}
|
||||||
</SidebarSection>
|
</SidebarSection>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
<PageLayout
|
<div className="flex flex-col flex-grow">
|
||||||
label={`Messages: ${
|
<PageLayout
|
||||||
chatType === "broadcast" && currentChannel
|
label={`Messages: ${
|
||||||
? getChannelName(currentChannel)
|
chatType === "broadcast" && currentChannel
|
||||||
: chatType === "direct" && nodes.get(activeChat)
|
? getChannelName(currentChannel)
|
||||||
? nodes.get(activeChat)?.user?.longName ?? "Unknown"
|
: chatType === "direct" && nodes.get(activeChat)
|
||||||
: "Loading..."
|
? nodes.get(activeChat)?.user?.longName ?? "Unknown"
|
||||||
}`}
|
: "Loading..."
|
||||||
actions={
|
}`}
|
||||||
chatType === "direct"
|
actions={
|
||||||
? [
|
chatType === "direct"
|
||||||
{
|
? [
|
||||||
icon: WaypointsIcon,
|
{
|
||||||
async onClick() {
|
icon: WaypointsIcon,
|
||||||
const targetNode = nodes.get(activeChat)?.num;
|
async onClick() {
|
||||||
if (targetNode === undefined) return;
|
const targetNode = nodes.get(activeChat)?.num;
|
||||||
toast({
|
if (targetNode === undefined) return;
|
||||||
title: "Sending Traceroute, please wait...",
|
|
||||||
});
|
|
||||||
await connection?.traceRoute(targetNode).then(() =>
|
|
||||||
toast({
|
toast({
|
||||||
title: "Traceroute sent.",
|
title: "Sending Traceroute, please wait...",
|
||||||
}),
|
});
|
||||||
);
|
await connection?.traceRoute(targetNode).then(() =>
|
||||||
|
toast({
|
||||||
|
title: "Traceroute sent.",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
]
|
||||||
]
|
: []
|
||||||
: []
|
}
|
||||||
}
|
>
|
||||||
>
|
{allChannels.map(
|
||||||
{allChannels.map(
|
(channel) =>
|
||||||
(channel) =>
|
activeChat === channel.index && (
|
||||||
activeChat === channel.index && (
|
<ChannelChat
|
||||||
<ChannelChat
|
key={channel.index}
|
||||||
key={channel.index}
|
to="broadcast"
|
||||||
to="broadcast"
|
messages={messages.broadcast.get(channel.index)}
|
||||||
messages={messages.broadcast.get(channel.index)}
|
channel={channel.index}
|
||||||
channel={channel.index}
|
/>
|
||||||
/>
|
),
|
||||||
),
|
)}
|
||||||
)}
|
{filteredNodes.map(
|
||||||
{filteredNodes.map(
|
(node) =>
|
||||||
(node) =>
|
activeChat === node.num && (
|
||||||
activeChat === node.num && (
|
<ChannelChat
|
||||||
<ChannelChat
|
key={node.num}
|
||||||
key={node.num}
|
to={activeChat}
|
||||||
to={activeChat}
|
messages={messages.direct.get(node.num)}
|
||||||
messages={messages.direct.get(node.num)}
|
channel={Types.ChannelNumber.Primary}
|
||||||
channel={Types.ChannelNumber.Primary}
|
traceroutes={traceroutes.get(node.num)}
|
||||||
traceroutes={traceroutes.get(node.num)}
|
/>
|
||||||
/>
|
),
|
||||||
),
|
)}
|
||||||
)}
|
</PageLayout>
|
||||||
</PageLayout>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+70
-66
@@ -1,3 +1,4 @@
|
|||||||
|
import Footer from "@app/components/UI/Footer";
|
||||||
import { useAppStore } from "@app/core/stores/appStore";
|
import { useAppStore } from "@app/core/stores/appStore";
|
||||||
import { Sidebar } from "@components/Sidebar.js";
|
import { Sidebar } from "@components/Sidebar.js";
|
||||||
import { Button } from "@components/UI/Button.js";
|
import { Button } from "@components/UI/Button.js";
|
||||||
@@ -27,73 +28,76 @@ export const NodesPage = (): JSX.Element => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div className="w-full overflow-y-auto">
|
<div className="flex flex-col w-full">
|
||||||
<Table
|
<div className="overflow-y-auto h-full">
|
||||||
headings={[
|
<Table
|
||||||
{ title: "", type: "blank", sortable: false },
|
headings={[
|
||||||
{ title: "Name", type: "normal", sortable: true },
|
{ title: "", type: "blank", sortable: false },
|
||||||
{ title: "Model", type: "normal", sortable: true },
|
{ title: "Name", type: "normal", sortable: true },
|
||||||
{ title: "MAC Address", type: "normal", sortable: true },
|
{ title: "Model", type: "normal", sortable: true },
|
||||||
{ title: "Last Heard", type: "normal", sortable: true },
|
{ title: "MAC Address", type: "normal", sortable: true },
|
||||||
{ title: "SNR", type: "normal", sortable: true },
|
{ title: "Last Heard", type: "normal", sortable: true },
|
||||||
{ title: "Connection", type: "normal", sortable: true },
|
{ title: "SNR", type: "normal", sortable: true },
|
||||||
{ title: "Remove", type: "normal", sortable: false },
|
{ title: "Connection", type: "normal", sortable: true },
|
||||||
]}
|
{ title: "Remove", type: "normal", sortable: false },
|
||||||
rows={filteredNodes.map((node) => [
|
]}
|
||||||
<Hashicon key="icon" size={24} value={node.num.toString()} />,
|
rows={filteredNodes.map((node) => [
|
||||||
<h1 key="header">
|
<Hashicon key="icon" size={24} value={node.num.toString()} />,
|
||||||
{node.user?.longName ??
|
<h1 key="header">
|
||||||
(node.user?.macaddr
|
{node.user?.longName ??
|
||||||
? `Meshtastic ${base16
|
(node.user?.macaddr
|
||||||
.stringify(node.user?.macaddr.subarray(4, 6) ?? [])
|
? `Meshtastic ${base16
|
||||||
.toLowerCase()}`
|
.stringify(node.user?.macaddr.subarray(4, 6) ?? [])
|
||||||
: `UNK: ${node.num}`)}
|
.toLowerCase()}`
|
||||||
</h1>,
|
: `UNK: ${node.num}`)}
|
||||||
|
</h1>,
|
||||||
|
|
||||||
<Mono key="model">
|
<Mono key="model">
|
||||||
{Protobuf.Mesh.HardwareModel[node.user?.hwModel ?? 0]}
|
{Protobuf.Mesh.HardwareModel[node.user?.hwModel ?? 0]}
|
||||||
</Mono>,
|
</Mono>,
|
||||||
<Mono key="addr">
|
<Mono key="addr">
|
||||||
{base16
|
{base16
|
||||||
.stringify(node.user?.macaddr ?? [])
|
.stringify(node.user?.macaddr ?? [])
|
||||||
.match(/.{1,2}/g)
|
.match(/.{1,2}/g)
|
||||||
?.join(":") ?? "UNK"}
|
?.join(":") ?? "UNK"}
|
||||||
</Mono>,
|
</Mono>,
|
||||||
<Fragment key="lastHeard">
|
<Fragment key="lastHeard">
|
||||||
{node.lastHeard === 0 ? (
|
{node.lastHeard === 0 ? (
|
||||||
<p>Never</p>
|
<p>Never</p>
|
||||||
) : (
|
) : (
|
||||||
<TimeAgo timestamp={node.lastHeard * 1000} />
|
<TimeAgo timestamp={node.lastHeard * 1000} />
|
||||||
)}
|
)}
|
||||||
</Fragment>,
|
</Fragment>,
|
||||||
<Mono key="snr">
|
<Mono key="snr">
|
||||||
{node.snr}db/
|
{node.snr}db/
|
||||||
{Math.min(Math.max((node.snr + 10) * 5, 0), 100)}%/
|
{Math.min(Math.max((node.snr + 10) * 5, 0), 100)}%/
|
||||||
{(node.snr + 10) * 5}raw
|
{(node.snr + 10) * 5}raw
|
||||||
</Mono>,
|
</Mono>,
|
||||||
<Mono key="hops">
|
<Mono key="hops">
|
||||||
{node.lastHeard !== 0
|
{node.lastHeard !== 0
|
||||||
? node.viaMqtt === false && node.hopsAway === 0
|
? node.viaMqtt === false && node.hopsAway === 0
|
||||||
? "Direct"
|
? "Direct"
|
||||||
: `${node.hopsAway.toString()} ${
|
: `${node.hopsAway.toString()} ${
|
||||||
node.hopsAway > 1 ? "hops" : "hop"
|
node.hopsAway > 1 ? "hops" : "hop"
|
||||||
} away`
|
} away`
|
||||||
: "-"}
|
: "-"}
|
||||||
{node.viaMqtt === true ? ", via MQTT" : ""}
|
{node.viaMqtt === true ? ", via MQTT" : ""}
|
||||||
</Mono>,
|
</Mono>,
|
||||||
<Button
|
<Button
|
||||||
key="remove"
|
key="remove"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setNodeNumToBeRemoved(node.num);
|
setNodeNumToBeRemoved(node.num);
|
||||||
setDialogOpen("nodeRemoval", true);
|
setDialogOpen("nodeRemoval", true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
Remove
|
Remove
|
||||||
</Button>,
|
</Button>,
|
||||||
])}
|
])}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user