Browse Source
Merge branch 'pki' into feature/security-tab
pull/277/head
Hunter Thornsberry
2 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with
401 additions and
306 deletions
-
.github/workflows/ci.yml
-
.github/workflows/pr.yml
-
.github/workflows/release.yml
-
src/App.tsx
-
src/components/Form/DynamicForm.tsx
-
src/components/PageComponents/Config/Security.tsx
-
src/components/PageComponents/Messages/ChannelChat.tsx
-
src/components/PageComponents/ModuleConfig/Telemetry.tsx
-
src/components/PageLayout.tsx
-
src/components/UI/Footer.tsx
-
src/components/UI/Tabs.tsx
-
src/pages/Dashboard/index.tsx
-
src/pages/Messages.tsx
-
src/pages/Nodes.tsx
|
|
|
@ -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 }}" |
|
|
|
|
|
|
|
@ -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 |
|
|
|
|
|
|
|
@ -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 { 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> |
|
|
|
|
|
|
|
@ -16,13 +16,14 @@ import { |
|
|
|
} from "react-hook-form"; |
|
|
|
|
|
|
|
interface DisabledBy<T> { |
|
|
|
fieldName: Path<T> | "always"; |
|
|
|
fieldName: Path<T>; |
|
|
|
selector?: number; |
|
|
|
invert?: boolean; |
|
|
|
} |
|
|
|
|
|
|
|
export interface BaseFormBuilderProps<T> { |
|
|
|
name: Path<T>; |
|
|
|
disabled?: boolean; |
|
|
|
disabledBy?: DisabledBy<T>[]; |
|
|
|
label: string; |
|
|
|
description?: string; |
|
|
|
@ -62,11 +63,14 @@ export function DynamicForm<T extends FieldValues>({ |
|
|
|
defaultValues: defaultValues, |
|
|
|
}); |
|
|
|
|
|
|
|
const isDisabled = (disabledBy?: DisabledBy<T>[]): boolean => { |
|
|
|
const isDisabled = ( |
|
|
|
disabledBy?: DisabledBy<T>[], |
|
|
|
disabled?: boolean, |
|
|
|
): boolean => { |
|
|
|
if (disabled) return true; |
|
|
|
if (!disabledBy) return false; |
|
|
|
|
|
|
|
return disabledBy.some((field) => { |
|
|
|
if (field.fieldName === "always") return true; |
|
|
|
const value = getValues(field.fieldName); |
|
|
|
if (value === "always") return true; |
|
|
|
if (typeof value === "boolean") return field.invert ? value : !value; |
|
|
|
@ -111,7 +115,7 @@ export function DynamicForm<T extends FieldValues>({ |
|
|
|
<DynamicFormField |
|
|
|
field={field} |
|
|
|
control={control} |
|
|
|
disabled={isDisabled(field.disabledBy)} |
|
|
|
disabled={isDisabled(field.disabledBy, field.disabled)} |
|
|
|
/> |
|
|
|
</FieldWrapper> |
|
|
|
))} |
|
|
|
|
|
|
|
@ -164,12 +164,9 @@ export const Security = (): JSX.Element => { |
|
|
|
type: "text", |
|
|
|
name: "publicKey", |
|
|
|
label: "Public Key", |
|
|
|
disabled: true, |
|
|
|
description: |
|
|
|
"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', |
|
|
|
}, |
|
|
|
{ |
|
|
|
type: "text", |
|
|
|
type: "passwordGenerator", |
|
|
|
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" }], |
|
|
|
properties: { |
|
|
|
value: adminKey, |
|
|
|
action: { |
|
|
|
icon: adminKeyVisible ? EyeOff : Eye, |
|
|
|
onClick: () => setAdminKeyVisible(!adminKeyVisible), |
|
|
|
}, |
|
|
|
}, |
|
|
|
], |
|
|
|
|
|
|
|
@ -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", |
|
|
|
|
|
|
|
@ -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; |
|
|
|
@ -18,6 +19,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 |
|
|
|
@ -47,11 +49,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> |
|
|
|
</> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
@ -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 |
|
|
|
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} |
|
|
|
|
|
|
|
@ -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> |
|
|
|
</> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
@ -67,6 +67,7 @@ export const MessagesPage = (): JSX.Element => { |
|
|
|
))} |
|
|
|
</SidebarSection> |
|
|
|
</Sidebar> |
|
|
|
<div className="flex flex-col flex-grow"> |
|
|
|
<PageLayout |
|
|
|
label={`Messages: ${ |
|
|
|
chatType === "broadcast" && currentChannel |
|
|
|
@ -121,6 +122,7 @@ export const MessagesPage = (): JSX.Element => { |
|
|
|
), |
|
|
|
)} |
|
|
|
</PageLayout> |
|
|
|
</div> |
|
|
|
</> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
@ -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"; |
|
|
|
@ -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 }, |
|
|
|
@ -95,6 +97,8 @@ export const NodesPage = (): JSX.Element => { |
|
|
|
])} |
|
|
|
/> |
|
|
|
</div> |
|
|
|
<Footer /> |
|
|
|
</div> |
|
|
|
</> |
|
|
|
); |
|
|
|
}; |
|
|
|
|