Merge branch 'pki' into feature/security-tab
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on: push
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -21,42 +24,3 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Package
|
- name: Build Package
|
||||||
run: pnpm build
|
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 }}"
|
|
||||||
|
|||||||
@@ -19,3 +19,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Package
|
- name: Build Package
|
||||||
run: pnpm build
|
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
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
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: 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,6 +5,7 @@ import { DeviceSelector } from "@components/DeviceSelector.js";
|
|||||||
import { DialogManager } from "@components/Dialog/DialogManager.js";
|
import { DialogManager } from "@components/Dialog/DialogManager.js";
|
||||||
import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.js";
|
import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.js";
|
||||||
import { Toaster } from "@components/Toaster.js";
|
import { Toaster } from "@components/Toaster.js";
|
||||||
|
import Footer from "@components/UI/Footer.js";
|
||||||
import { ThemeController } from "@components/generic/ThemeController.js";
|
import { ThemeController } from "@components/generic/ThemeController.js";
|
||||||
import { useAppStore } from "@core/stores/appStore.js";
|
import { useAppStore } from "@core/stores/appStore.js";
|
||||||
import { useDeviceStore } from "@core/stores/deviceStore.js";
|
import { useDeviceStore } from "@core/stores/deviceStore.js";
|
||||||
@@ -40,7 +41,11 @@ export const App = (): JSX.Element => {
|
|||||||
<PageRouter />
|
<PageRouter />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
<Dashboard />
|
<Dashboard />
|
||||||
|
<div className="flex flex-grow" />
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ import {
|
|||||||
} from "react-hook-form";
|
} from "react-hook-form";
|
||||||
|
|
||||||
interface DisabledBy<T> {
|
interface DisabledBy<T> {
|
||||||
fieldName: Path<T> | "always";
|
fieldName: Path<T>;
|
||||||
selector?: number;
|
selector?: number;
|
||||||
invert?: boolean;
|
invert?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseFormBuilderProps<T> {
|
export interface BaseFormBuilderProps<T> {
|
||||||
name: Path<T>;
|
name: Path<T>;
|
||||||
|
disabled?: boolean;
|
||||||
disabledBy?: DisabledBy<T>[];
|
disabledBy?: DisabledBy<T>[];
|
||||||
label: string;
|
label: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@@ -62,11 +63,14 @@ export function DynamicForm<T extends FieldValues>({
|
|||||||
defaultValues: defaultValues,
|
defaultValues: defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isDisabled = (disabledBy?: DisabledBy<T>[]): boolean => {
|
const isDisabled = (
|
||||||
|
disabledBy?: DisabledBy<T>[],
|
||||||
|
disabled?: boolean,
|
||||||
|
): boolean => {
|
||||||
|
if (disabled) return true;
|
||||||
if (!disabledBy) return false;
|
if (!disabledBy) return false;
|
||||||
|
|
||||||
return disabledBy.some((field) => {
|
return disabledBy.some((field) => {
|
||||||
if (field.fieldName === "always") return true;
|
|
||||||
const value = getValues(field.fieldName);
|
const value = getValues(field.fieldName);
|
||||||
if (value === "always") return true;
|
if (value === "always") return true;
|
||||||
if (typeof value === "boolean") return field.invert ? value : !value;
|
if (typeof value === "boolean") return field.invert ? value : !value;
|
||||||
@@ -111,7 +115,7 @@ export function DynamicForm<T extends FieldValues>({
|
|||||||
<DynamicFormField
|
<DynamicFormField
|
||||||
field={field}
|
field={field}
|
||||||
control={control}
|
control={control}
|
||||||
disabled={isDisabled(field.disabledBy)}
|
disabled={isDisabled(field.disabledBy, field.disabled)}
|
||||||
/>
|
/>
|
||||||
</FieldWrapper>
|
</FieldWrapper>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -164,12 +164,9 @@ export const Security = (): JSX.Element => {
|
|||||||
type: "text",
|
type: "text",
|
||||||
name: "publicKey",
|
name: "publicKey",
|
||||||
label: "Public Key",
|
label: "Public Key",
|
||||||
|
disabled: true,
|
||||||
description:
|
description:
|
||||||
"Sent out to other nodes on the mesh to allow them to compute a shared secret key",
|
"Sent out to other nodes on the mesh to allow them to compute a shared secret key",
|
||||||
disabledBy: [{ fieldName: "always" }],
|
|
||||||
properties: {
|
|
||||||
value: publicKey,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -192,16 +189,28 @@ export const Security = (): JSX.Element => {
|
|||||||
'If true, device is considered to be "managed" by a mesh administrator via admin messages',
|
'If true, device is considered to be "managed" by a mesh administrator via admin messages',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "passwordGenerator",
|
||||||
name: "adminKey",
|
name: "adminKey",
|
||||||
label: "Admin Key",
|
label: "Admin Key",
|
||||||
description:
|
description:
|
||||||
"The public key authorized to send admin messages to this node",
|
"The public key authorized to send admin messages to this node",
|
||||||
validationText: adminKeyValidationText,
|
validationText: adminKeyValidationText,
|
||||||
|
devicePSKBitCount: adminKeyBitCount,
|
||||||
inputChange: adminKeyInputChangeEvent,
|
inputChange: adminKeyInputChangeEvent,
|
||||||
|
selectChange: adminKeySelectChangeEvent,
|
||||||
|
hide: !adminKeyVisible,
|
||||||
|
buttonClick: () =>
|
||||||
|
clickEvent(
|
||||||
|
setAdminKey,
|
||||||
|
adminKeyBitCount,
|
||||||
|
setAdminKeyValidationText,
|
||||||
|
),
|
||||||
disabledBy: [{ fieldName: "adminChannelEnabled" }],
|
disabledBy: [{ fieldName: "adminChannelEnabled" }],
|
||||||
properties: {
|
properties: {
|
||||||
value: adminKey,
|
value: adminKey,
|
||||||
|
action: {
|
||||||
|
icon: adminKeyVisible ? EyeOff : Eye,
|
||||||
|
onClick: () => setAdminKeyVisible(!adminKeyVisible),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const Telemetry = (): JSX.Element => {
|
|||||||
description: "How often to send Power data over the mesh",
|
description: "How often to send Power data over the mesh",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "toggle",
|
||||||
name: "powerScreenEnabled",
|
name: "powerScreenEnabled",
|
||||||
label: "Power Screen Enabled",
|
label: "Power Screen Enabled",
|
||||||
description: "Enable the Power Telemetry Screen",
|
description: "Enable the Power Telemetry Screen",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { cn } from "@app/core/utils/cn.js";
|
import { cn } from "@app/core/utils/cn.js";
|
||||||
import { AlignLeftIcon, type LucideIcon } from "lucide-react";
|
import { AlignLeftIcon, type LucideIcon } from "lucide-react";
|
||||||
|
import Footer from "./UI/Footer";
|
||||||
|
|
||||||
export interface PageLayoutProps {
|
export interface PageLayoutProps {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -18,6 +19,7 @@ export const PageLayout = ({
|
|||||||
children,
|
children,
|
||||||
}: 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
|
||||||
@@ -47,11 +49,13 @@ export const PageLayout = ({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-full w-full flex-col overflow-y-auto",
|
"flex h-full w-full flex-col overflow-y-auto",
|
||||||
!noPadding && "p-3",
|
!noPadding && "pl-3 pr-3 ",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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,6 +21,7 @@ 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 flex-col gap-3 p-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -95,5 +96,6 @@ export const Dashboard = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export const MessagesPage = (): JSX.Element => {
|
|||||||
))}
|
))}
|
||||||
</SidebarSection>
|
</SidebarSection>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
<div className="flex flex-col flex-grow">
|
||||||
<PageLayout
|
<PageLayout
|
||||||
label={`Messages: ${
|
label={`Messages: ${
|
||||||
chatType === "broadcast" && currentChannel
|
chatType === "broadcast" && currentChannel
|
||||||
@@ -121,6 +122,7 @@ export const MessagesPage = (): JSX.Element => {
|
|||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+5
-1
@@ -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,7 +28,8 @@ export const NodesPage = (): JSX.Element => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div className="w-full overflow-y-auto">
|
<div className="flex flex-col w-full">
|
||||||
|
<div className="overflow-y-auto h-full">
|
||||||
<Table
|
<Table
|
||||||
headings={[
|
headings={[
|
||||||
{ title: "", type: "blank", sortable: false },
|
{ title: "", type: "blank", sortable: false },
|
||||||
@@ -95,6 +97,8 @@ export const NodesPage = (): JSX.Element => {
|
|||||||
])}
|
])}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user