Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06d2c393ce | ||
|
|
cecdf9758b | ||
|
|
02cb4f2584 | ||
|
|
8cfcd7b1af | ||
|
|
c0cb059f52 | ||
|
|
a2a9b37238 | ||
|
|
57d0d27bbb | ||
|
|
0e92dd9bea | ||
|
|
c16ebf3917 | ||
|
|
3d3a08a23f | ||
|
|
4d1227a942 | ||
|
|
a8ee273b24 | ||
|
|
3ee7a57480 | ||
|
|
2f2c777c56 | ||
|
|
2f36118e9d | ||
|
|
a6d161581f | ||
|
|
d05ea5a2cc | ||
|
|
471db94242 | ||
|
|
2654e4fbc9 | ||
|
|
f2aa5bfbee | ||
|
|
3b018b0c70 | ||
|
|
921db10d91 | ||
|
|
bf4f593e3a | ||
|
|
1e061a1e19 | ||
|
|
9b9f537e2c | ||
|
|
985cce0b0d | ||
|
|
8ed3ce8203 | ||
|
|
ebd5a3d3a6 | ||
|
|
1cdf18747d |
+2
-1
@@ -23,7 +23,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bufbuild/protobuf": "^1.10.0",
|
"@bufbuild/protobuf": "^1.10.0",
|
||||||
"@emeraldpay/hashicon-react": "^0.5.2",
|
"@emeraldpay/hashicon-react": "^0.5.2",
|
||||||
"@meshtastic/js": "2.3.7-1",
|
"@meshtastic/js": "2.3.7-5",
|
||||||
"@noble/curves": "^1.5.0",
|
"@noble/curves": "^1.5.0",
|
||||||
"@radix-ui/react-accordion": "^1.2.0",
|
"@radix-ui/react-accordion": "^1.2.0",
|
||||||
"@radix-ui/react-checkbox": "^1.1.0",
|
"@radix-ui/react-checkbox": "^1.1.0",
|
||||||
@@ -60,6 +60,7 @@
|
|||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"timeago-react": "^3.0.6",
|
"timeago-react": "^3.0.6",
|
||||||
|
"vite-plugin-node-polyfills": "^0.22.0",
|
||||||
"zustand": "4.5.2"
|
"zustand": "4.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
Generated
+673
-5
File diff suppressed because it is too large
Load Diff
@@ -111,10 +111,14 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
|||||||
type: "select",
|
type: "select",
|
||||||
name: "role",
|
name: "role",
|
||||||
label: "Role",
|
label: "Role",
|
||||||
|
disabled: channel.index === 0,
|
||||||
description:
|
description:
|
||||||
"Device telemetry is sent over PRIMARY. Only one PRIMARY allowed",
|
"Device telemetry is sent over PRIMARY. Only one PRIMARY allowed",
|
||||||
properties: {
|
properties: {
|
||||||
enumValue: Protobuf.Channel.Channel_Role,
|
enumValue:
|
||||||
|
channel.index === 0
|
||||||
|
? { PRIMARY: 1 }
|
||||||
|
: { DISABLED: 0, SECONDARY: 2 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,7 +32,22 @@ export const Device = (): JSX.Element => {
|
|||||||
label: "Role",
|
label: "Role",
|
||||||
description: "What role the device performs on the mesh",
|
description: "What role the device performs on the mesh",
|
||||||
properties: {
|
properties: {
|
||||||
enumValue: Protobuf.Config.Config_DeviceConfig_Role,
|
enumValue: {
|
||||||
|
Client: Protobuf.Config.Config_DeviceConfig_Role.CLIENT,
|
||||||
|
"Client Mute":
|
||||||
|
Protobuf.Config.Config_DeviceConfig_Role.CLIENT_MUTE,
|
||||||
|
Router: Protobuf.Config.Config_DeviceConfig_Role.ROUTER,
|
||||||
|
Repeater: Protobuf.Config.Config_DeviceConfig_Role.REPEATER,
|
||||||
|
Tracker: Protobuf.Config.Config_DeviceConfig_Role.TRACKER,
|
||||||
|
Sensor: Protobuf.Config.Config_DeviceConfig_Role.SENSOR,
|
||||||
|
TAK: Protobuf.Config.Config_DeviceConfig_Role.TAK,
|
||||||
|
"Client Hidden":
|
||||||
|
Protobuf.Config.Config_DeviceConfig_Role.CLIENT_HIDDEN,
|
||||||
|
"Lost and Found":
|
||||||
|
Protobuf.Config.Config_DeviceConfig_Role.LOST_AND_FOUND,
|
||||||
|
"TAK Tracker":
|
||||||
|
Protobuf.Config.Config_DeviceConfig_Role.SENSOR,
|
||||||
|
},
|
||||||
formatEnumName: true,
|
formatEnumName: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import type { NetworkValidation } from "@app/validation/config/network.js";
|
import type { NetworkValidation } from "@app/validation/config/network.js";
|
||||||
import { DynamicForm } from "@components/Form/DynamicForm.js";
|
import { DynamicForm } from "@components/Form/DynamicForm.js";
|
||||||
import { useDevice } from "@core/stores/deviceStore.js";
|
import { useDevice } from "@core/stores/deviceStore.js";
|
||||||
|
import {
|
||||||
|
convertIntToIpAddress,
|
||||||
|
convertIpAddressToInt,
|
||||||
|
} from "@core/utils/ip.js";
|
||||||
import { Protobuf } from "@meshtastic/js";
|
import { Protobuf } from "@meshtastic/js";
|
||||||
|
|
||||||
export const Network = (): JSX.Element => {
|
export const Network = (): JSX.Element => {
|
||||||
@@ -13,9 +17,12 @@ export const Network = (): JSX.Element => {
|
|||||||
case: "network",
|
case: "network",
|
||||||
value: {
|
value: {
|
||||||
...data,
|
...data,
|
||||||
ipv4Config: new Protobuf.Config.Config_NetworkConfig_IpV4Config(
|
ipv4Config: new Protobuf.Config.Config_NetworkConfig_IpV4Config({
|
||||||
data.ipv4Config,
|
ip: convertIpAddressToInt(data.ipv4Config.ip) ?? 0,
|
||||||
),
|
gateway: convertIpAddressToInt(data.ipv4Config.gateway) ?? 0,
|
||||||
|
subnet: convertIpAddressToInt(data.ipv4Config.subnet) ?? 0,
|
||||||
|
dns: convertIpAddressToInt(data.ipv4Config.dns) ?? 0,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -25,7 +32,19 @@ export const Network = (): JSX.Element => {
|
|||||||
return (
|
return (
|
||||||
<DynamicForm<NetworkValidation>
|
<DynamicForm<NetworkValidation>
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
defaultValues={config.network}
|
defaultValues={{
|
||||||
|
...config.network,
|
||||||
|
ipv4Config: {
|
||||||
|
ip: convertIntToIpAddress(config.network?.ipv4Config?.ip ?? 0),
|
||||||
|
gateway: convertIntToIpAddress(
|
||||||
|
config.network?.ipv4Config?.gateway ?? 0,
|
||||||
|
),
|
||||||
|
subnet: convertIntToIpAddress(
|
||||||
|
config.network?.ipv4Config?.subnet ?? 0,
|
||||||
|
),
|
||||||
|
dns: convertIntToIpAddress(config.network?.ipv4Config?.dns ?? 0),
|
||||||
|
},
|
||||||
|
}}
|
||||||
fieldGroups={[
|
fieldGroups={[
|
||||||
{
|
{
|
||||||
label: "WiFi Config",
|
label: "WiFi Config",
|
||||||
|
|||||||
@@ -77,12 +77,6 @@ export const Position = (): JSX.Element => {
|
|||||||
label: "Enable Pin",
|
label: "Enable Pin",
|
||||||
description: "GPS module enable pin override",
|
description: "GPS module enable pin override",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: "number",
|
|
||||||
name: "channelPrecision",
|
|
||||||
label: "Channel Precision",
|
|
||||||
description: "GPS channel precision",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useDevice } from "@app/core/stores/deviceStore.js";
|
import { useDevice } from "@app/core/stores/deviceStore.js";
|
||||||
import type { Protobuf } from "@meshtastic/js";
|
import type { Protobuf } from "@meshtastic/js";
|
||||||
|
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
||||||
|
|
||||||
export interface TraceRouteProps {
|
export interface TraceRouteProps {
|
||||||
from?: Protobuf.Mesh.NodeInfo;
|
from?: Protobuf.Mesh.NodeInfo;
|
||||||
@@ -24,7 +25,10 @@ export const TraceRoute = ({
|
|||||||
<div className="ml-5 flex">
|
<div className="ml-5 flex">
|
||||||
<span className="ml-4 border-l-2 border-l-backgroundPrimary pl-2 text-textPrimary">
|
<span className="ml-4 border-l-2 border-l-backgroundPrimary pl-2 text-textPrimary">
|
||||||
{to?.user?.longName}↔
|
{to?.user?.longName}↔
|
||||||
{route.map((hop) => `${nodes.get(hop)?.user?.longName ?? "Unknown"}↔`)}
|
{route.map((hop) => {
|
||||||
|
const node = nodes.get(hop);
|
||||||
|
return `${node?.user?.longName ?? (node?.num ? numberToHexUnpadded(node.num) : "Unknown")}↔`;
|
||||||
|
})}
|
||||||
{from?.user?.longName}
|
{from?.user?.longName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export function convertIntToIpAddress(int: number): string {
|
||||||
|
return `${int & 0xff}.${(int >> 8) & 0xff}.${(int >> 16) & 0xff}.${(int >> 24) & 0xff}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertIpAddressToInt(ip: string): number | null {
|
||||||
|
return (
|
||||||
|
ip
|
||||||
|
.split(".")
|
||||||
|
.reverse()
|
||||||
|
.reduce((ipnum, octet) => {
|
||||||
|
return (ipnum << 8) + Number.parseInt(octet);
|
||||||
|
}, 0) >>> 0
|
||||||
|
);
|
||||||
|
}
|
||||||
+3
-1
@@ -7,6 +7,7 @@ import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js";
|
|||||||
import { useAppStore } from "@core/stores/appStore.js";
|
import { useAppStore } from "@core/stores/appStore.js";
|
||||||
import { useDevice } from "@core/stores/deviceStore.js";
|
import { useDevice } from "@core/stores/deviceStore.js";
|
||||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
import { Hashicon } from "@emeraldpay/hashicon-react";
|
||||||
|
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
||||||
import { bbox, lineString } from "@turf/turf";
|
import { bbox, lineString } from "@turf/turf";
|
||||||
import {
|
import {
|
||||||
BoxSelectIcon,
|
BoxSelectIcon,
|
||||||
@@ -180,7 +181,8 @@ export const MapPage = (): JSX.Element => {
|
|||||||
<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 ||
|
||||||
|
`!${numberToHexUnpadded(node.num)}`}
|
||||||
</Subtle>
|
</Subtle>
|
||||||
</div>
|
</div>
|
||||||
</Marker>
|
</Marker>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useToast } from "@core/hooks/useToast.js";
|
|||||||
import { useDevice } from "@core/stores/deviceStore.js";
|
import { useDevice } from "@core/stores/deviceStore.js";
|
||||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
import { Hashicon } from "@emeraldpay/hashicon-react";
|
||||||
import { Protobuf, Types } from "@meshtastic/js";
|
import { Protobuf, Types } from "@meshtastic/js";
|
||||||
|
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
||||||
import { getChannelName } from "@pages/Channels.js";
|
import { getChannelName } from "@pages/Channels.js";
|
||||||
import { HashIcon, LockIcon, LockOpenIcon, WaypointsIcon } from "lucide-react";
|
import { HashIcon, LockIcon, LockOpenIcon, WaypointsIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -28,6 +29,8 @@ export const MessagesPage = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
const currentChannel = channels.get(activeChat);
|
const currentChannel = channels.get(activeChat);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const node = nodes.get(activeChat);
|
||||||
|
const nodeHex = node?.num ? numberToHexUnpadded(node.num) : "Unknown";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -56,7 +59,7 @@ export const MessagesPage = (): JSX.Element => {
|
|||||||
{filteredNodes.map((node) => (
|
{filteredNodes.map((node) => (
|
||||||
<SidebarButton
|
<SidebarButton
|
||||||
key={node.num}
|
key={node.num}
|
||||||
label={node.user?.longName ?? "Unknown"}
|
label={node.user?.longName ?? `!${numberToHexUnpadded(node.num)}`}
|
||||||
active={activeChat === node.num}
|
active={activeChat === node.num}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setChatType("direct");
|
setChatType("direct");
|
||||||
@@ -73,7 +76,7 @@ export const MessagesPage = (): JSX.Element => {
|
|||||||
chatType === "broadcast" && currentChannel
|
chatType === "broadcast" && currentChannel
|
||||||
? getChannelName(currentChannel)
|
? getChannelName(currentChannel)
|
||||||
: chatType === "direct" && nodes.get(activeChat)
|
: chatType === "direct" && nodes.get(activeChat)
|
||||||
? nodes.get(activeChat)?.user?.longName ?? "Unknown"
|
? nodes.get(activeChat)?.user?.longName ?? nodeHex
|
||||||
: "Loading..."
|
: "Loading..."
|
||||||
}`}
|
}`}
|
||||||
actions={
|
actions={
|
||||||
|
|||||||
+2
-1
@@ -8,6 +8,7 @@ import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.js";
|
|||||||
import { useDevice } from "@core/stores/deviceStore.js";
|
import { useDevice } from "@core/stores/deviceStore.js";
|
||||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
import { Hashicon } from "@emeraldpay/hashicon-react";
|
||||||
import { Protobuf } from "@meshtastic/js";
|
import { Protobuf } from "@meshtastic/js";
|
||||||
|
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
||||||
import { LockIcon, LockOpenIcon, TrashIcon } from "lucide-react";
|
import { LockIcon, LockOpenIcon, TrashIcon } from "lucide-react";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { base16 } from "rfc4648";
|
import { base16 } from "rfc4648";
|
||||||
@@ -50,7 +51,7 @@ export const NodesPage = (): JSX.Element => {
|
|||||||
? `Meshtastic ${base16
|
? `Meshtastic ${base16
|
||||||
.stringify(node.user?.macaddr.subarray(4, 6) ?? [])
|
.stringify(node.user?.macaddr.subarray(4, 6) ?? [])
|
||||||
.toLowerCase()}`
|
.toLowerCase()}`
|
||||||
: `UNK: ${node.num}`)}
|
: `!${numberToHexUnpadded(node.num)}`)}
|
||||||
</h1>,
|
</h1>,
|
||||||
|
|
||||||
<Mono key="model">
|
<Mono key="model">
|
||||||
|
|||||||
@@ -41,21 +41,24 @@ export class NetworkValidation
|
|||||||
|
|
||||||
export class NetworkValidationIpV4Config
|
export class NetworkValidationIpV4Config
|
||||||
implements
|
implements
|
||||||
Omit<Protobuf.Config.Config_NetworkConfig_IpV4Config, keyof Message>
|
Omit<
|
||||||
|
Protobuf.Config.Config_NetworkConfig_IpV4Config,
|
||||||
|
keyof Message | "ip" | "gateway" | "subnet" | "dns"
|
||||||
|
>
|
||||||
{
|
{
|
||||||
@IsIP()
|
@IsIP()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
ip: number;
|
ip: string;
|
||||||
|
|
||||||
@IsIP()
|
@IsIP()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
gateway: number;
|
gateway: string;
|
||||||
|
|
||||||
@IsIP()
|
@IsIP()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
subnet: number;
|
subnet: string;
|
||||||
|
|
||||||
@IsIP()
|
@IsIP()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
dns: number;
|
dns: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,4 @@ export class PositionValidation
|
|||||||
|
|
||||||
@IsEnum(Protobuf.Config.Config_PositionConfig_GpsMode)
|
@IsEnum(Protobuf.Config.Config_PositionConfig_GpsMode)
|
||||||
gpsMode: Protobuf.Config.Config_PositionConfig_GpsMode;
|
gpsMode: Protobuf.Config.Config_PositionConfig_GpsMode;
|
||||||
|
|
||||||
@IsArray()
|
|
||||||
channelPrecision: number[];
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -1,11 +1,10 @@
|
|||||||
import { execSync } from "node:child_process";
|
import { execSync } from "node:child_process";
|
||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
import { visualizer } from "rollup-plugin-visualizer";
|
import { visualizer } from "rollup-plugin-visualizer";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import EnvironmentPlugin from "vite-plugin-environment";
|
import EnvironmentPlugin from "vite-plugin-environment";
|
||||||
|
|
||||||
import react from "@vitejs/plugin-react";
|
|
||||||
|
|
||||||
let hash = "";
|
let hash = "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user