60 Commits
Author SHA1 Message Date
Hunter ThornsberryandGitHub 921db10d91 Merge pull request #297 from Hunter275/js-version-bump
js version bump
2024-09-12 17:20:09 -04:00
Hunter Thornsberry bf4f593e3a use new js and remove polyfills 2024-09-12 14:20:32 -04:00
Hunter Thornsberry 1e061a1e19 polyfill 2024-09-11 23:07:52 -04:00
Hunter Thornsberry 9b9f537e2c js version bump 2024-09-11 22:13:46 -04:00
Hunter ThornsberryandGitHub 985cce0b0d Merge pull request #294 from meshtastic/pki
PKI
2024-09-11 17:51:31 -04:00
Hunter ThornsberryandGitHub 3fe38eb506 Merge pull request #295 from meshtastic/master
Update release.yml
2024-09-10 20:53:31 -04:00
Hunter ThornsberryandGitHub 51081d3052 Update release.yml
Create a build artifact on release
2024-09-10 20:51:50 -04:00
Hunter Thornsberry c08f6d16bb Merge branch 'master' into pki 2024-09-09 18:55:02 -04:00
Hunter ThornsberryandGitHub 62ad4c49f8 Merge pull request #293 from Hunter275/pki-nodelist
Node List & DMs
2024-09-09 18:50:29 -04:00
Hunter Thornsberry 3b0a1e6108 biome 2024-09-09 18:37:22 -04:00
Hunter Thornsberry c2f2205626 cleanup 2024-09-09 18:25:58 -04:00
Hunter ThornsberryandGitHub 87c729d694 Merge branch 'pki' into pki-nodelist 2024-09-09 18:01:17 -04:00
Hunter Thornsberry 8e4f60edf3 biome 2024-09-09 16:36:56 -04:00
Hunter Thornsberry 8811eee9f5 Remove bluetooth debugging and reword generic debug 2024-09-09 16:36:17 -04:00
Hunter Thornsberry 2af93f1acd Fix protobufs, add configOkToMqtt, add PKI icons 2024-09-09 16:22:48 -04:00
Hunter Thornsberry 78a35544c7 Node list and DMs now show icons 2024-09-08 19:48:42 -04:00
Hunter Thornsberry 3ad2d650b0 update protobufs 2024-09-08 18:59:57 -04:00
Hunter ThornsberryandGitHub 2e12b27566 Merge pull request #277 from KomelT/feature/security-tab
Update key generation
2024-09-08 18:13:42 -04:00
Hunter Thornsberry 989fad7e17 biome 2024-09-07 20:24:45 -04:00
Hunter Thornsberry f7a2e5f76b Invert adminKey disabledBy 2024-09-07 20:24:18 -04:00
Hunter Thornsberry 2b81fc47e2 disable the public key input 2024-09-07 20:19:51 -04:00
Hunter Thornsberry b522113cd7 bad merge on Security.tsx 2024-09-07 20:16:29 -04:00
Hunter ThornsberryandGitHub fce642c24e Merge branch 'pki' into feature/security-tab 2024-09-07 19:54:57 -04:00
Hunter ThornsberryandGitHub bf425a8ec7 Merge pull request #291 from Kongduino/patch-1 2024-09-07 12:21:47 -04:00
KongduinoandGitHub a7d0d36086 Update index.tsx
"at least", two words. Thanks...
2024-09-07 13:32:16 +08:00
Tilen Komel 5e72510bef Removed submit button 2024-09-06 09:33:13 +02:00
Hunter Thornsberry 88efdc4758 Merge branch 'master' into pki 2024-09-05 19:26:32 -04:00
Hunter ThornsberryandGitHub 41acc4d25d Merge pull request #289 from Hunter275/gh-actions
GitHub Actions: Releases
2024-09-04 16:15:41 -04:00
Tilen Komel d10c010b9a Dont disable PKI on Allow Legacy Admin 2024-09-04 18:50:49 +02:00
Hunter Thornsberry 80ab44c8db don't release on release 2024-09-04 00:39:12 -04:00
Hunter Thornsberry 7895df2d9f Stop release on ci 2024-09-04 00:35:22 -04:00
Hunter ThornsberryandGitHub c780437355 Merge pull request #286 from TheCyberRonin/feature/provide-compressed-build-during-pr
feat: Provide compressed build during PR GitHub Action
2024-09-04 00:12:26 -04:00
Tilen Komel 9d4aa05316 Updated pki regenerate dialog 2024-09-02 15:45:58 +02:00
Tilen Komel 354d04592b Fix build 2024-09-02 15:05:04 +02:00
Tilen Komel 9bea6870bb Add submit button to work 2024-09-02 13:11:42 +02:00
Tilen Komel d0bd02980d Update private key generation 2024-09-02 13:10:59 +02:00
Hunter ThornsberryandGitHub 7d1135b9dc Merge pull request #288 from mrfyda/master
Change 'powerScreenEnabled' config type to toggle
2024-09-01 19:00:25 -04:00
Tilen Komel f66332b3e3 Handle undefined booleans 2024-08-31 15:58:12 +02:00
Rafael Cortês 05a6b6293e Change 'powerScreenEnabled' config type to toggle 2024-08-31 10:51:53 +01:00
Hunter ThornsberryandGitHub b9a8a2ba6c Merge pull request #287 from TheCyberRonin/feature/scope-ci-to-push-on-master
feat: Only do `CI` GHA on push to `master`
2024-08-30 12:31:09 -04:00
Ronin c16070f02b feat: Only do CI GHA on push to master 2024-08-29 21:54:58 -04:00
Ronin 3e6653a98f chore: Fix typo in package name for uploading 2024-08-29 21:31:04 -04:00
Ronin 59126ca939 feat: Add a build step for build.tar 2024-08-29 21:27:37 -04:00
Tilen Komel 4bde402a53 Add key generation confirmation 2024-08-29 23:19:12 +02:00
Tilen Komel 4cf91272de Removed admin key generation from security 2024-08-29 23:19:12 +02:00
Tilen Komel c711c39aa9 Add inputChange to FormInpit 2024-08-29 23:19:12 +02:00
Tilen Komel c4342f1a2b Update passwordGenerator disabled 2024-08-29 23:19:12 +02:00
Tilen Komel afc45588fa Fixed security key generation 2024-08-29 23:19:12 +02:00
Tilen Komel 22cd5aa88d Add key generation 2024-08-29 23:19:12 +02:00
Tilen Komel 8c4c8a760e Add @noble/curves 2024-08-29 23:19:11 +02:00
Tilen Komel 0911df6b0d Add dynamic bit to password generator 2024-08-29 23:16:02 +02:00
Tilen Komel 06c20fa950 Add dynamic value to input 2024-08-29 23:16:02 +02:00
Hunter ThornsberryandGitHub 22d900a831 Merge pull request #278 from Hunter275/dashboard-footer
flex-grow to make footer stick to bottom
2024-08-26 15:33:40 -04:00
Hunter Thornsberry c6cc5e5e6f biome 2024-08-26 14:32:18 -04:00
Hunter Thornsberry f0d8db1c87 flex-grow to make footer stick to bottom 2024-08-26 14:10:43 -04:00
Hunter ThornsberryandGitHub 8c37be4af3 Merge pull request #274 from rcarteraz/add-footer 2024-08-24 11:50:51 -04:00
Hunter Thornsberry 076dae80b7 first working version 2024-08-23 18:58:22 -04:00
Hunter Thornsberry 7d4001ea9d Update footer and initial groundword 2024-08-23 18:40:17 -04:00
rcarteraz 6c1f140ad1 oops -- helps if you use the full link 2024-08-19 13:31:22 -07:00
rcarteraz 4c4be2e18f temp solution 2024-08-19 13:20:56 -07:00
24 changed files with 1440 additions and 469 deletions
+4 -40
View File
@@ -1,6 +1,9 @@
name: CI
on: push
on:
push:
branches:
- master
permissions:
contents: write
@@ -21,42 +24,3 @@ jobs:
- name: Build Package
run: pnpm build
- name: Package Output
run: pnpm package
- name: Upload Artifact
uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
automatic_release_tag: "latest"
prerelease: false
files: |
./dist/build.tar
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Buildah Build
id: build-container
uses: redhat-actions/buildah-build@v2
with:
containerfiles: |
./Containerfile
image: ${{github.event.repository.full_name}}
tags: latest ${{ github.sha }}
oci: true
platforms: linux/amd64, linux/arm64
- name: Push To Registry
id: push-to-registry
uses: redhat-actions/push-to-registry@v2
with:
image: ${{ steps.build-container.outputs.image }}
tags: ${{ steps.build-container.outputs.tags }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Print image url
run: echo "Image pushed to ${{ steps.push-to-registry.outputs.registry-paths }}"
+9
View File
@@ -19,3 +19,12 @@ jobs:
- name: Build Package
run: pnpm build
- name: Compress build
run: pnpm package
- name: Archive compressed build
uses: actions/upload-artifact@v4
with:
name: build
path: dist/build.tar
+61
View File
@@ -0,0 +1,61 @@
name: 'Release'
on:
release:
types: [released]
permissions:
contents: write
packages: write
jobs:
build-and-package:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: latest
- name: Install Dependencies
run: pnpm install
- name: Build Package
run: pnpm build
- name: Package Output
run: pnpm package
- name: Archive compressed build
uses: actions/upload-artifact@v4
with:
name: build
path: dist/build.tar
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Buildah Build
id: build-container
uses: redhat-actions/buildah-build@v2
with:
containerfiles: |
./Containerfile
image: ${{github.event.repository.full_name}}
tags: latest ${{ github.sha }}
oci: true
platforms: linux/amd64, linux/arm64
- name: Push To Registry
id: push-to-registry
uses: redhat-actions/push-to-registry@v2
with:
image: ${{ steps.build-container.outputs.image }}
tags: ${{ steps.build-container.outputs.tags }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Print image url
run: echo "Image pushed to ${{ steps.push-to-registry.outputs.registry-paths }}"
+5 -3
View File
@@ -23,7 +23,8 @@
"dependencies": {
"@bufbuild/protobuf": "^1.10.0",
"@emeraldpay/hashicon-react": "^0.5.2",
"@meshtastic/js": "2.3.7-1",
"@meshtastic/js": "2.3.7-5",
"@noble/curves": "^1.5.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
@@ -48,7 +49,7 @@
"crypto-random-string": "^5.0.0",
"immer": "^10.1.1",
"lucide-react": "^0.363.0",
"mapbox-gl": "npm:empty-npm-package@^1.0.0",
"mapbox-gl": "^3.6.0",
"maplibre-gl": "4.1.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -59,11 +60,12 @@
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"timeago-react": "^3.0.6",
"vite-plugin-node-polyfills": "^0.22.0",
"zustand": "4.5.2"
},
"devDependencies": {
"@biomejs/biome": "^1.8.2",
"@buf/meshtastic_protobufs.bufbuild_es": "1.10.0-20240820152623-fac6975bbc78.1",
"@buf/meshtastic_protobufs.bufbuild_es": "1.10.0-20240906232734-3da561588c55.1",
"@types/chrome": "^0.0.263",
"@types/node": "^20.14.9",
"@types/react": "^18.3.3",
+788 -20
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -5,6 +5,7 @@ import { DeviceSelector } from "@components/DeviceSelector.js";
import { DialogManager } from "@components/Dialog/DialogManager.js";
import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.js";
import { Toaster } from "@components/Toaster.js";
import Footer from "@components/UI/Footer.js";
import { ThemeController } from "@components/generic/ThemeController.js";
import { useAppStore } from "@core/stores/appStore.js";
import { useDeviceStore } from "@core/stores/deviceStore.js";
@@ -40,7 +41,11 @@ export const App = (): JSX.Element => {
<PageRouter />
</div>
) : (
<>
<Dashboard />
<div className="flex flex-grow" />
<Footer />
</>
)}
</div>
</div>
@@ -0,0 +1,39 @@
import { Button } from "@components/UI/Button.js";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog.js";
export interface PkiRegenerateDialogProps {
open: boolean;
onOpenChange: () => void;
onSubmit: () => void;
}
export const PkiRegenerateDialog = ({
open,
onOpenChange,
onSubmit,
}: PkiRegenerateDialogProps): JSX.Element => {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Regenerate Key pair?</DialogTitle>
<DialogDescription>
Are you sure you want to regenerate key pair?
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="destructive" onClick={() => onSubmit()}>
Regenerate
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
+7 -3
View File
@@ -4,11 +4,14 @@ import type {
} from "@components/Form/DynamicForm.js";
import { Input } from "@components/UI/Input.js";
import type { LucideIcon } from "lucide-react";
import type { ChangeEventHandler } from "react";
import { Controller, type FieldValues } from "react-hook-form";
export interface InputFieldProps<T> extends BaseFormBuilderProps<T> {
type: "text" | "number" | "password";
inputChange?: ChangeEventHandler;
properties?: {
value?: string;
prefix?: string;
suffix?: string;
step?: number;
@@ -33,13 +36,14 @@ export function GenericInput<T extends FieldValues>({
type={field.type}
step={field.properties?.step}
value={field.type === "number" ? Number.parseFloat(value) : value}
onChange={(e) =>
onChange={(e) => {
if (field.inputChange) field.inputChange(e);
onChange(
field.type === "number"
? Number.parseFloat(e.target.value)
: e.target.value,
)
}
);
}}
{...field.properties}
{...rest}
disabled={disabled}
@@ -9,6 +9,7 @@ import { Controller, type FieldValues } from "react-hook-form";
export interface PasswordGeneratorProps<T> extends BaseFormBuilderProps<T> {
type: "passwordGenerator";
hide?: boolean;
bits?: { text: string; value: string; key: string }[];
devicePSKBitCount: number;
inputChange: ChangeEventHandler;
selectChange: (event: string) => void;
@@ -28,6 +29,7 @@ export function PasswordGenerator<T extends FieldValues>({
<Generator
hide={field.hide}
devicePSKBitCount={field.devicePSKBitCount}
bits={field.bits}
inputChange={field.inputChange}
selectChange={field.selectChange}
buttonClick={field.buttonClick}
@@ -56,6 +56,13 @@ export const LoRa = (): JSX.Element => {
label: "Ignore MQTT",
description: "Don't forward MQTT messages over the mesh",
},
{
type: "toggle",
name: "configOkToMqtt",
label: "OK to MQTT",
description:
"When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT",
},
],
},
{
@@ -1,9 +1,13 @@
import { PkiRegenerateDialog } from "@app/components/Dialog/PkiRegenerateDialog";
import { DynamicForm } from "@app/components/Form/DynamicForm.js";
import {
getX25519PrivateKey,
getX25519PublicKey,
} from "@app/core/utils/x25519";
import type { SecurityValidation } from "@app/validation/config/security.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Protobuf } from "@meshtastic/js";
import { fromByteArray, toByteArray } from "base64-js";
import cryptoRandomString from "crypto-random-string";
import { Eye, EyeOff } from "lucide-react";
import { useState } from "react";
@@ -15,7 +19,7 @@ export const Security = (): JSX.Element => {
);
const [privateKeyVisible, setPrivateKeyVisible] = useState<boolean>(false);
const [privateKeyBitCount, setPrivateKeyBitCount] = useState<number>(
config.security?.privateKey.length ?? 16,
config.security?.privateKey.length ?? 32,
);
const [privateKeyValidationText, setPrivateKeyValidationText] =
useState<string>();
@@ -23,14 +27,11 @@ export const Security = (): JSX.Element => {
fromByteArray(config.security?.publicKey ?? new Uint8Array(0)),
);
const [adminKey, setAdminKey] = useState<string>(
fromByteArray(config.security?.adminKey ?? new Uint8Array(0)),
);
const [adminKeyVisible, setAdminKeyVisible] = useState<boolean>(false);
const [adminKeyBitCount, setAdminKeyBitCount] = useState<number>(
config.security?.adminKey.length ?? 16,
fromByteArray(config.security?.adminKey[0] ?? new Uint8Array(0)),
);
const [adminKeyValidationText, setAdminKeyValidationText] =
useState<string>();
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
const onSubmit = (data: SecurityValidation) => {
if (privateKeyValidationText || adminKeyValidationText) return;
@@ -41,7 +42,7 @@ export const Security = (): JSX.Element => {
case: "security",
value: {
...data,
adminKey: toByteArray(adminKey),
adminKey: [toByteArray(adminKey)],
privateKey: toByteArray(privateKey),
publicKey: toByteArray(publicKey),
},
@@ -50,73 +51,86 @@ export const Security = (): JSX.Element => {
);
};
const clickEvent = (
setKey: (value: React.SetStateAction<string>) => void,
bitCount: number,
setValidationText: (
value: React.SetStateAction<string | undefined>,
) => void,
) => {
setKey(
btoa(
cryptoRandomString({
length: bitCount ?? 0,
type: "alphanumeric",
}),
),
);
setValidationText(undefined);
};
const validatePass = (
const validateKey = (
input: string,
count: number,
setValidationText: (
value: React.SetStateAction<string | undefined>,
) => void,
) => {
try {
if (input.length % 4 !== 0 || toByteArray(input).length !== count) {
setValidationText(`Please enter a valid ${count * 8} bit PSK.`);
} else {
setValidationText(undefined);
}
} catch (e) {
console.error(e);
setValidationText(`Please enter a valid ${count * 8} bit PSK.`);
}
};
const privateKeyClickEvent = () => {
setDialogOpen(true);
};
const pkiRegenerate = () => {
const privateKey = getX25519PrivateKey();
const publicKey = getX25519PublicKey(privateKey);
setPrivateKey(fromByteArray(privateKey));
setPublicKey(fromByteArray(publicKey));
validateKey(
fromByteArray(privateKey),
privateKeyBitCount,
setPrivateKeyValidationText,
);
setDialogOpen(false);
};
const privateKeyInputChangeEvent = (
e: React.ChangeEvent<HTMLInputElement>,
) => {
const psk = e.currentTarget?.value;
setPrivateKey(psk);
validatePass(psk, privateKeyBitCount, setPrivateKeyValidationText);
const privateKeyB64String = e.target.value;
setPrivateKey(privateKeyB64String);
validateKey(
privateKeyB64String,
privateKeyBitCount,
setPrivateKeyValidationText,
);
const publicKey = getX25519PublicKey(toByteArray(privateKeyB64String));
setPublicKey(fromByteArray(publicKey));
};
const adminKeyInputChangeEvent = (e: React.ChangeEvent<HTMLInputElement>) => {
const psk = e.currentTarget?.value;
setAdminKey(psk);
validatePass(psk, privateKeyBitCount, setAdminKeyValidationText);
validateKey(psk, privateKeyBitCount, setAdminKeyValidationText);
};
const privateKeySelectChangeEvent = (e: string) => {
const count = Number.parseInt(e);
setPrivateKeyBitCount(count);
validatePass(privateKey, count, setPrivateKeyValidationText);
};
const adminKeySelectChangeEvent = (e: string) => {
const count = Number.parseInt(e);
setAdminKeyBitCount(count);
validatePass(privateKey, count, setAdminKeyValidationText);
validateKey(privateKey, count, setPrivateKeyValidationText);
};
return (
<>
<DynamicForm<SecurityValidation>
onSubmit={onSubmit}
submitType="onChange"
defaultValues={{
...config.security,
...{
adminKey: adminKey,
privateKey: privateKey,
publicKey: publicKey,
adminChannelEnabled: config.security?.adminChannelEnabled ?? false,
isManaged: config.security?.isManaged ?? false,
debugLogApiEnabled: config.security?.debugLogApiEnabled ?? false,
serialEnabled: config.security?.serialEnabled ?? false,
},
}}
fieldGroups={[
@@ -129,23 +143,13 @@ export const Security = (): JSX.Element => {
name: "privateKey",
label: "Private Key",
description: "Used to create a shared key with a remote device",
bits: [{ text: "256 bit", value: "32", key: "bit256" }],
validationText: privateKeyValidationText,
devicePSKBitCount: privateKeyBitCount,
inputChange: privateKeyInputChangeEvent,
selectChange: privateKeySelectChangeEvent,
hide: !privateKeyVisible,
buttonClick: () =>
clickEvent(
setPrivateKey,
privateKeyBitCount,
setPrivateKeyValidationText,
),
disabledBy: [
{
fieldName: "adminChannelEnabled",
invert: true,
},
],
buttonClick: privateKeyClickEvent,
properties: {
value: privateKey,
action: {
@@ -161,6 +165,9 @@ export const Security = (): JSX.Element => {
disabled: true,
description:
"Sent out to other nodes on the mesh to allow them to compute a shared secret key",
properties: {
value: publicKey,
},
},
],
},
@@ -183,29 +190,18 @@ export const Security = (): JSX.Element => {
'If true, device is considered to be "managed" by a mesh administrator via admin messages',
},
{
type: "passwordGenerator",
type: "text",
name: "adminKey",
label: "Admin Key",
description:
"The public key authorized to send admin messages to this node",
validationText: adminKeyValidationText,
devicePSKBitCount: adminKeyBitCount,
inputChange: adminKeyInputChangeEvent,
selectChange: adminKeySelectChangeEvent,
hide: !adminKeyVisible,
buttonClick: () =>
clickEvent(
setAdminKey,
adminKeyBitCount,
setAdminKeyValidationText,
),
disabledBy: [{ fieldName: "adminChannelEnabled" }],
disabledBy: [
{ fieldName: "adminChannelEnabled", invert: true },
],
properties: {
value: adminKey,
action: {
icon: adminKeyVisible ? EyeOff : Eye,
onClick: () => setAdminKeyVisible(!adminKeyVisible),
},
},
},
],
@@ -214,17 +210,12 @@ export const Security = (): JSX.Element => {
label: "Logging Settings",
description: "Settings for Logging",
fields: [
{
type: "toggle",
name: "bluetoothLoggingEnabled",
label: "Allow Bluetooth Logging",
description: "Enables device (serial style logs) over Bluetooth",
},
{
type: "toggle",
name: "debugLogApiEnabled",
label: "Enable Debug Log API",
description: "Output live debug logging over serial",
description:
"Output live debug logging over serial, view and export position-redacted device logs over Bluetooth",
},
{
type: "toggle",
@@ -236,5 +227,11 @@ export const Security = (): JSX.Element => {
},
]}
/>
<PkiRegenerateDialog
open={dialogOpen}
onOpenChange={() => setDialogOpen(false)}
onSubmit={() => pkiRegenerate()}
/>
</>
);
};
@@ -68,7 +68,7 @@ export const ChannelChat = ({
)}
</div>
</div>
<div className="p-3">
<div className="pl-3 pr-3 pt-3 pb-1">
<MessageInput to={to} channel={channel} />
</div>
</div>
@@ -87,7 +87,7 @@ export const Telemetry = (): JSX.Element => {
description: "How often to send Power data over the mesh",
},
{
type: "text",
type: "toggle",
name: "powerScreenEnabled",
label: "Power Screen Enabled",
description: "Enable the Power Telemetry Screen",
+7 -2
View File
@@ -1,5 +1,6 @@
import { cn } from "@app/core/utils/cn.js";
import { AlignLeftIcon, type LucideIcon } from "lucide-react";
import Footer from "./UI/Footer";
export interface PageLayoutProps {
label: string;
@@ -7,6 +8,7 @@ export interface PageLayoutProps {
children: React.ReactNode;
actions?: {
icon: LucideIcon;
iconClasses?: string;
onClick: () => void;
}[];
}
@@ -18,6 +20,7 @@ export const PageLayout = ({
children,
}: PageLayoutProps): JSX.Element => {
return (
<>
<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">
<button
@@ -37,7 +40,7 @@ export const PageLayout = ({
className="transition-all hover:text-accent"
onClick={action.onClick}
>
<action.icon />
<action.icon className={action.iconClasses} />
</button>
))}
</div>
@@ -47,11 +50,13 @@ export const PageLayout = ({
<div
className={cn(
"flex h-full w-full flex-col overflow-y-auto",
!noPadding && "p-3",
!noPadding && "pl-3 pr-3 ",
)}
>
{children}
<Footer />
</div>
</div>
</>
);
};
+37
View File
@@ -0,0 +1,37 @@
import React from "react";
export interface FooterProps extends React.HTMLAttributes<HTMLElement> {}
const Footer = React.forwardRef<HTMLElement, FooterProps>(
({ className, ...props }, ref) => {
return (
<footer
className={`flex flex- justify-center p-2 ${className}`}
style={{
backgroundColor: "var(--backgroundPrimary)",
color: "var(--textPrimary)",
}}
>
<p>
<a
href="https://vercel.com/?utm_source=meshtastic&utm_campaign=oss"
className="hover:underline"
style={{ color: "var(--link)" }}
>
Powered by ▲ Vercel
</a>{" "}
| Meshtastic® is a registered trademark of Meshtastic LLC. |{" "}
<a
href="https://meshtastic.org/docs/legal"
className="hover:underline"
style={{ color: "var(--link)" }}
>
Legal Information
</a>
</p>
</footer>
);
},
);
export default Footer;
+28 -8
View File
@@ -17,6 +17,7 @@ export interface GeneratorProps extends React.BaseHTMLAttributes<HTMLElement> {
value: string;
variant: "default" | "invalid";
buttonText?: string;
bits?: { text: string; value: string; key: string }[];
selectChange: (event: string) => void;
inputChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
buttonClick: React.MouseEventHandler<HTMLButtonElement>;
@@ -35,6 +36,11 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
variant,
value,
buttonText,
bits = [
{ text: "256 bit", value: "32", key: "bit256" },
{ text: "128 bit", value: "16", key: "bit128" },
{ text: "8 bit", value: "1", key: "bit8" },
],
selectChange,
inputChange,
buttonClick,
@@ -44,6 +50,21 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
},
ref,
) => {
const inputRef = React.useRef<HTMLInputElement>(null);
// Invokes onChange event on the input element when the value changes from the parent component
React.useEffect(() => {
if (!inputRef.current) return;
const setValue = Object.getOwnPropertyDescriptor(
HTMLInputElement.prototype,
"value",
)?.set;
if (!setValue) return;
inputRef.current.value = "";
setValue.call(inputRef.current, value);
inputRef.current.dispatchEvent(new Event("input", { bubbles: true }));
}, [value]);
return (
<>
<Input
@@ -54,30 +75,29 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
onChange={inputChange}
action={action}
disabled={disabled}
ref={inputRef}
/>
<Select
value={devicePSKBitCount?.toString()}
onValueChange={(e) => selectChange(e)}
disabled={disabled}
>
<SelectTrigger className="!max-w-max">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem key="bit256" value="32">
256 bit
</SelectItem>
<SelectItem key="bit128" value="16">
128 bit
</SelectItem>
<SelectItem key="bit8" value="1">
8 bit
{bits.map(({ text, value, key }) => (
<SelectItem key={key} value={value}>
{text}
</SelectItem>
))}
</SelectContent>
</Select>
<Button
type="button"
variant="success"
onClick={buttonClick}
disabled={disabled}
{...props}
>
{buttonText}
+2 -1
View File
@@ -31,7 +31,7 @@ export interface InputProps
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, variant, prefix, suffix, action, ...props }, ref) => {
({ className, value, variant, prefix, suffix, action, ...props }, ref) => {
return (
<div className="relative w-full">
{prefix && (
@@ -45,6 +45,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
className,
inputVariants({ variant }),
)}
value={value}
ref={ref}
{...props}
/>
+1 -1
View File
@@ -12,7 +12,7 @@ const TabsList = React.forwardRef<
<TabsPrimitive.List
ref={ref}
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,
)}
{...props}
+15
View File
@@ -0,0 +1,15 @@
import { x25519 } from "@noble/curves/ed25519";
export function getX25519PrivateKey(): Uint8Array {
const key = x25519.utils.randomPrivateKey();
key[0] &= 248;
key[31] &= 127;
key[31] |= 64;
return key;
}
export function getX25519PublicKey(privateKey: Uint8Array): Uint8Array {
return x25519.getPublicKey(privateKey);
}
+2
View File
@@ -21,6 +21,7 @@ export const Dashboard = () => {
const devices = useMemo(() => getDevices(), [getDevices]);
return (
<>
<div className="flex flex-col gap-3 p-3">
<div className="flex items-center justify-between">
<div className="space-y-1">
@@ -95,5 +96,6 @@ export const Dashboard = () => {
)}
</div>
</div>
</>
);
};
+20 -1
View File
@@ -8,7 +8,7 @@ import { useDevice } from "@core/stores/deviceStore.js";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { Protobuf, Types } from "@meshtastic/js";
import { getChannelName } from "@pages/Channels.js";
import { HashIcon, WaypointsIcon } from "lucide-react";
import { HashIcon, LockIcon, LockOpenIcon, WaypointsIcon } from "lucide-react";
import { useState } from "react";
export const MessagesPage = (): JSX.Element => {
@@ -67,6 +67,7 @@ export const MessagesPage = (): JSX.Element => {
))}
</SidebarSection>
</Sidebar>
<div className="flex flex-col flex-grow">
<PageLayout
label={`Messages: ${
chatType === "broadcast" && currentChannel
@@ -78,6 +79,23 @@ export const MessagesPage = (): JSX.Element => {
actions={
chatType === "direct"
? [
{
icon: nodes.get(activeChat)?.user?.publicKey.length
? LockIcon
: LockOpenIcon,
iconClasses: nodes.get(activeChat)?.user?.publicKey.length
? "text-green-600"
: "text-yellow-300",
async onClick() {
const targetNode = nodes.get(activeChat)?.num;
if (targetNode === undefined) return;
toast({
title: nodes.get(activeChat)?.user?.publicKey.length
? "Chat is using PKI encryption."
: "Chat is using PSK encryption.",
});
},
},
{
icon: WaypointsIcon,
async onClick() {
@@ -121,6 +139,7 @@ export const MessagesPage = (): JSX.Element => {
),
)}
</PageLayout>
</div>
</>
);
};
+14 -2
View File
@@ -1,3 +1,4 @@
import Footer from "@app/components/UI/Footer";
import { useAppStore } from "@app/core/stores/appStore";
import { Sidebar } from "@components/Sidebar.js";
import { Button } from "@components/UI/Button.js";
@@ -7,7 +8,7 @@ import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { Protobuf } from "@meshtastic/js";
import { TrashIcon } from "lucide-react";
import { LockIcon, LockOpenIcon, TrashIcon } from "lucide-react";
import { Fragment } from "react";
import { base16 } from "rfc4648";
@@ -27,7 +28,8 @@ export const NodesPage = (): JSX.Element => {
return (
<>
<Sidebar />
<div className="w-full overflow-y-auto">
<div className="flex flex-col w-full">
<div className="overflow-y-auto h-full">
<Table
headings={[
{ title: "", type: "blank", sortable: false },
@@ -36,6 +38,7 @@ export const NodesPage = (): JSX.Element => {
{ title: "MAC Address", type: "normal", sortable: true },
{ title: "Last Heard", type: "normal", sortable: true },
{ title: "SNR", type: "normal", sortable: true },
{ title: "Encryption", type: "normal", sortable: false },
{ title: "Connection", type: "normal", sortable: true },
{ title: "Remove", type: "normal", sortable: false },
]}
@@ -71,6 +74,13 @@ export const NodesPage = (): JSX.Element => {
{Math.min(Math.max((node.snr + 10) * 5, 0), 100)}%/
{(node.snr + 10) * 5}raw
</Mono>,
<Mono key="pki">
{node.user?.publicKey && node.user?.publicKey.length > 0 ? (
<LockIcon className="text-green-600" />
) : (
<LockOpenIcon className="text-yellow-300" />
)}
</Mono>,
<Mono key="hops">
{node.lastHeard !== 0
? node.viaMqtt === false && node.hopsAway === 0
@@ -95,6 +105,8 @@ export const NodesPage = (): JSX.Element => {
])}
/>
</div>
<Footer />
</div>
</>
);
};
+3
View File
@@ -60,4 +60,7 @@ export class LoRaValidation
@IsBoolean()
ignoreMqtt: boolean;
@IsBoolean()
configOkToMqtt: boolean;
}
+1 -2
View File
@@ -1,11 +1,10 @@
import { execSync } from "node:child_process";
import { resolve } from "node:path";
import react from "@vitejs/plugin-react";
import { visualizer } from "rollup-plugin-visualizer";
import { defineConfig } from "vite";
import EnvironmentPlugin from "vite-plugin-environment";
import react from "@vitejs/plugin-react";
let hash = "";
try {