Add monorepo support to project (#682)
* feat: add monorepo support * fix: correct relatedProjects structure in vercel.json * update github actions with monorepo support * removed legacy dep * moved jsr config into web package
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
import { PkiRegenerateDialog } from "@components/Dialog/PkiRegenerateDialog.tsx";
|
||||
import { ManagedModeDialog } from "@components/Dialog/ManagedModeDialog.tsx";
|
||||
import {
|
||||
DynamicForm,
|
||||
type DynamicFormFormInit,
|
||||
} from "@components/Form/DynamicForm.tsx";
|
||||
import { useAppStore } from "@core/stores/appStore.ts";
|
||||
import { getX25519PrivateKey, getX25519PublicKey } from "@core/utils/x25519.ts";
|
||||
import {
|
||||
type ParsedSecurity,
|
||||
type RawSecurity,
|
||||
RawSecuritySchema,
|
||||
} from "@app/validation/config/security.ts";
|
||||
import { useEffect, useState } from "react";
|
||||
import { create } from "@bufbuild/protobuf";
|
||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||
import { Protobuf } from "@meshtastic/core";
|
||||
import { fromByteArray, toByteArray } from "base64-js";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type DefaultValues, useForm } from "react-hook-form";
|
||||
import { createZodResolver } from "@components/Form/createZodResolver.ts";
|
||||
import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts";
|
||||
|
||||
interface SecurityConfigProps {
|
||||
onFormInit: DynamicFormFormInit<RawSecurity>;
|
||||
}
|
||||
export const Security = ({ onFormInit }: SecurityConfigProps) => {
|
||||
const {
|
||||
config,
|
||||
setWorkingConfig,
|
||||
setDialogOpen,
|
||||
getEffectiveConfig,
|
||||
removeWorkingConfig,
|
||||
} = useDevice();
|
||||
|
||||
const { removeError } = useAppStore();
|
||||
const { t } = useTranslation("deviceConfig");
|
||||
|
||||
const securityConfig = getEffectiveConfig("security");
|
||||
const defaultValues = {
|
||||
...securityConfig,
|
||||
...{
|
||||
privateKey: fromByteArray(
|
||||
securityConfig?.privateKey ?? new Uint8Array(0),
|
||||
),
|
||||
publicKey: fromByteArray(
|
||||
securityConfig?.publicKey ?? new Uint8Array(0),
|
||||
),
|
||||
adminKey: [
|
||||
fromByteArray(
|
||||
securityConfig?.adminKey?.at(0) ?? new Uint8Array(0),
|
||||
),
|
||||
fromByteArray(
|
||||
securityConfig?.adminKey?.at(1) ?? new Uint8Array(0),
|
||||
),
|
||||
fromByteArray(
|
||||
securityConfig?.adminKey?.at(2) ?? new Uint8Array(0),
|
||||
),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const formMethods = useForm<RawSecurity>({
|
||||
mode: "onChange",
|
||||
defaultValues: defaultValues as DefaultValues<RawSecurity>,
|
||||
resolver: createZodResolver(RawSecuritySchema),
|
||||
shouldFocusError: false,
|
||||
resetOptions: { keepDefaultValues: true },
|
||||
});
|
||||
const { setValue, formState } = formMethods;
|
||||
|
||||
useEffect(() => {
|
||||
onFormInit?.(formMethods);
|
||||
}, [onFormInit, formMethods]);
|
||||
|
||||
const [privateKeyDialogOpen, setPrivateKeyDialogOpen] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
const [managedModeDialogOpen, setManagedModeDialogOpen] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
|
||||
const onSubmit = (data: RawSecurity) => {
|
||||
if (!formState.isReady) return;
|
||||
|
||||
const payload: ParsedSecurity = {
|
||||
...data,
|
||||
privateKey: toByteArray(data.privateKey),
|
||||
publicKey: toByteArray(data.publicKey),
|
||||
adminKey: [
|
||||
toByteArray(data.adminKey.at(0) ?? ""),
|
||||
toByteArray(data.adminKey.at(1) ?? ""),
|
||||
toByteArray(data.adminKey.at(2) ?? ""),
|
||||
],
|
||||
};
|
||||
|
||||
if (deepCompareConfig(config.security, payload, true)) {
|
||||
removeWorkingConfig("security");
|
||||
return;
|
||||
}
|
||||
|
||||
setWorkingConfig(
|
||||
create(Protobuf.Config.ConfigSchema, {
|
||||
payloadVariant: {
|
||||
case: "security",
|
||||
value: payload,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const pkiRegenerate = () => {
|
||||
const privateKey = getX25519PrivateKey();
|
||||
updatePublicKey(fromByteArray(privateKey));
|
||||
};
|
||||
|
||||
const updatePublicKey = (privateKey: string) => {
|
||||
try {
|
||||
const publicKey = fromByteArray(
|
||||
getX25519PublicKey(toByteArray(privateKey)),
|
||||
);
|
||||
setValue("privateKey", privateKey);
|
||||
setValue("publicKey", publicKey);
|
||||
|
||||
removeError("privateKey");
|
||||
removeError("publicKey");
|
||||
setPrivateKeyDialogOpen(false);
|
||||
} catch (_e) {
|
||||
setValue("privateKey", privateKey);
|
||||
}
|
||||
};
|
||||
|
||||
const bits = [
|
||||
{
|
||||
text: t("security.256bit"),
|
||||
value: "32",
|
||||
key: "bit256",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DynamicForm<RawSecurity>
|
||||
propMethods={formMethods}
|
||||
onSubmit={onSubmit}
|
||||
formId="Config_SecurityConfig"
|
||||
fieldGroups={[
|
||||
{
|
||||
label: t("security.title"),
|
||||
description: t("security.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "passwordGenerator",
|
||||
id: "pskInput",
|
||||
name: "privateKey",
|
||||
label: t("security.privateKey.label"),
|
||||
description: t("security.privateKey.description"),
|
||||
bits,
|
||||
devicePSKBitCount: 32,
|
||||
hide: true,
|
||||
inputChange: (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updatePublicKey(e.target.value);
|
||||
},
|
||||
actionButtons: [
|
||||
{
|
||||
text: t("button.generate"),
|
||||
onClick: () => setPrivateKeyDialogOpen(true),
|
||||
variant: "success",
|
||||
},
|
||||
{
|
||||
text: t("button.backupKey"),
|
||||
onClick: () => setDialogOpen("pkiBackup", true),
|
||||
variant: "subtle",
|
||||
},
|
||||
],
|
||||
properties: {
|
||||
showCopyButton: true,
|
||||
showPasswordToggle: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "publicKey",
|
||||
label: t("security.publicKey.label"),
|
||||
disabled: true,
|
||||
description: t("security.publicKey.description"),
|
||||
properties: {
|
||||
showCopyButton: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t("security.adminSettings.label"),
|
||||
description: t("security.adminSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "passwordGenerator",
|
||||
name: "adminKey.0",
|
||||
id: "adminKey0Input",
|
||||
label: t("security.primaryAdminKey.label"),
|
||||
description: t("security.primaryAdminKey.description"),
|
||||
bits,
|
||||
devicePSKBitCount: 32,
|
||||
hide: true,
|
||||
actionButtons: [],
|
||||
disabledBy: [
|
||||
{ fieldName: "adminChannelEnabled", invert: true },
|
||||
],
|
||||
properties: {
|
||||
showCopyButton: true,
|
||||
showPasswordToggle: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "passwordGenerator",
|
||||
name: "adminKey.1",
|
||||
id: "adminKey1Input",
|
||||
label: t("security.secondaryAdminKey.label"),
|
||||
description: t("security.secondaryAdminKey.description"),
|
||||
bits,
|
||||
devicePSKBitCount: 32,
|
||||
hide: true,
|
||||
actionButtons: [],
|
||||
disabledBy: [
|
||||
{ fieldName: "adminChannelEnabled", invert: true },
|
||||
],
|
||||
properties: {
|
||||
showCopyButton: true,
|
||||
showPasswordToggle: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "passwordGenerator",
|
||||
name: "adminKey.2",
|
||||
id: "adminKey2Input",
|
||||
label: t("security.tertiaryAdminKey.label"),
|
||||
description: t("security.tertiaryAdminKey.description"),
|
||||
bits,
|
||||
devicePSKBitCount: 32,
|
||||
hide: true,
|
||||
actionButtons: [],
|
||||
disabledBy: [
|
||||
{ fieldName: "adminChannelEnabled", invert: true },
|
||||
],
|
||||
properties: {
|
||||
showCopyButton: true,
|
||||
showPasswordToggle: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "isManaged",
|
||||
label: t("security.managed.label"),
|
||||
description: t("security.managed.description"),
|
||||
inputChange: (checked) => {
|
||||
if (checked) {
|
||||
setManagedModeDialogOpen(true);
|
||||
}
|
||||
|
||||
setValue("isManaged", false);
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "adminChannelEnabled",
|
||||
label: t("security.adminChannelEnabled.label"),
|
||||
description: t("security.adminChannelEnabled.description"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t("security.loggingSettings.label"),
|
||||
description: t("security.loggingSettings.description"),
|
||||
fields: [
|
||||
{
|
||||
type: "toggle",
|
||||
name: "debugLogApiEnabled",
|
||||
label: t("security.enableDebugLogApi.label"),
|
||||
description: t("security.enableDebugLogApi.description"),
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
name: "serialEnabled",
|
||||
label: t("security.serialOutputEnabled.label"),
|
||||
description: t("security.serialOutputEnabled.description"),
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<PkiRegenerateDialog
|
||||
text={{
|
||||
button: t("button.regenerate"),
|
||||
title: t("pkiRegenerate.title"),
|
||||
description: t("pkiRegenerate.description"),
|
||||
}}
|
||||
open={privateKeyDialogOpen}
|
||||
onOpenChange={() => setPrivateKeyDialogOpen((prev) => !prev)}
|
||||
onSubmit={pkiRegenerate}
|
||||
/>
|
||||
|
||||
<ManagedModeDialog
|
||||
open={managedModeDialogOpen}
|
||||
onOpenChange={() => setManagedModeDialogOpen((prev) => !prev)}
|
||||
onSubmit={() => {
|
||||
setValue("isManaged", true);
|
||||
setManagedModeDialogOpen(false);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user