Cleanup & start transition of moduleConfig
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
import { Button } from "@app/components/form/Button.js";
|
||||||
|
import { ChevronRightIcon, HomeIcon } from "@primer/octicons-react";
|
||||||
|
|
||||||
|
export interface NavBarProps {
|
||||||
|
breadcrumb: string[];
|
||||||
|
actions?: {
|
||||||
|
label: string;
|
||||||
|
onClick: () => void;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NavBar = ({ breadcrumb, actions }: NavBarProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div className="flex rounded-md bg-backgroundSecondary p-2">
|
||||||
|
<ol className="my-auto ml-2 flex gap-4 text-textSecondary">
|
||||||
|
<li className="cursor-pointer hover:brightness-disabled">
|
||||||
|
<HomeIcon className="h-5 w-5 flex-shrink-0" />
|
||||||
|
</li>
|
||||||
|
{breadcrumb.map((breadcrumb, index) => (
|
||||||
|
<li key={index} className="my-auto flex gap-4">
|
||||||
|
<ChevronRightIcon className="h-5 w-5 flex-shrink-0 brightness-disabled" />
|
||||||
|
<span className="cursor-pointer text-sm font-medium hover:brightness-disabled">
|
||||||
|
{breadcrumb}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
<div className="ml-auto">
|
||||||
|
{actions?.map((Action, index) => (
|
||||||
|
<Button key={index} onClick={Action.onClick}>
|
||||||
|
{Action.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -30,11 +30,6 @@ export const PageNav = (): JSX.Element => {
|
|||||||
icon: MapIcon,
|
icon: MapIcon,
|
||||||
page: "map"
|
page: "map"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Extensions",
|
|
||||||
icon: BeakerIcon,
|
|
||||||
page: "extensions"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "Config",
|
name: "Config",
|
||||||
icon: Cog8ToothIcon,
|
icon: Cog8ToothIcon,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useDevice } from "@core/providers/useDevice.js";
|
import { useDevice } from "@core/providers/useDevice.js";
|
||||||
import { ChannelsPage } from "@pages/Channels.js";
|
import { ChannelsPage } from "@pages/Channels.js";
|
||||||
import { ConfigPage } from "@pages/Config/index.js";
|
import { ConfigPage } from "@pages/Config/index.js";
|
||||||
import { ExtensionsPage } from "@pages/Extensions/Index.js";
|
|
||||||
import { MapPage } from "@pages/Map.js";
|
import { MapPage } from "@pages/Map.js";
|
||||||
import { MessagesPage } from "@pages/Messages.js";
|
import { MessagesPage } from "@pages/Messages.js";
|
||||||
import { PeersPage } from "@pages/Peers.js";
|
import { PeersPage } from "@pages/Peers.js";
|
||||||
@@ -12,7 +11,6 @@ export const PageRouter = (): JSX.Element => {
|
|||||||
<div className="flex-grow overflow-y-auto bg-backgroundPrimary">
|
<div className="flex-grow overflow-y-auto bg-backgroundPrimary">
|
||||||
{activePage === "messages" && <MessagesPage />}
|
{activePage === "messages" && <MessagesPage />}
|
||||||
{activePage === "map" && <MapPage />}
|
{activePage === "map" && <MapPage />}
|
||||||
{activePage === "extensions" && <ExtensionsPage />}
|
|
||||||
{activePage === "config" && <ConfigPage />}
|
{activePage === "config" && <ConfigPage />}
|
||||||
{activePage === "channels" && <ChannelsPage />}
|
{activePage === "channels" && <ChannelsPage />}
|
||||||
{activePage === "peers" && <PeersPage />}
|
{activePage === "peers" && <PeersPage />}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface GroupViewProps {
|
|||||||
export const GroupView = ({ group }: GroupViewProps): JSX.Element => {
|
export const GroupView = ({ group }: GroupViewProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Combobox.Option
|
<Combobox.Option
|
||||||
value={group.name}
|
value={group.label}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
`flex cursor-default select-none items-center rounded-md px-3 py-2 ${
|
`flex cursor-default select-none items-center rounded-md px-3 py-2 ${
|
||||||
active ? "bg-backgroundPrimary text-textPrimary" : ""
|
active ? "bg-backgroundPrimary text-textPrimary" : ""
|
||||||
@@ -19,7 +19,7 @@ export const GroupView = ({ group }: GroupViewProps): JSX.Element => {
|
|||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<>
|
<>
|
||||||
<group.icon className="h-6 w-6" />
|
<group.icon className="h-6 w-6" />
|
||||||
<span className="ml-3 flex-auto truncate">{group.name}</span>
|
<span className="ml-3 flex-auto truncate">{group.label}</span>
|
||||||
{active && <ChevronRightIcon className="h-5 text-textSecondary" />}
|
{active && <ChevronRightIcon className="h-5 text-textSecondary" />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -40,12 +40,12 @@ import { Blur } from "@components/generic/Blur.js";
|
|||||||
import { ThemeController } from "@components/generic/ThemeController.js";
|
import { ThemeController } from "@components/generic/ThemeController.js";
|
||||||
|
|
||||||
export interface Group {
|
export interface Group {
|
||||||
name: string;
|
label: string;
|
||||||
icon: ComponentType<SVGProps<SVGSVGElement>>;
|
icon: ComponentType<SVGProps<SVGSVGElement>>;
|
||||||
commands: Command[];
|
commands: Command[];
|
||||||
}
|
}
|
||||||
export interface Command {
|
export interface Command {
|
||||||
name: string;
|
label: string;
|
||||||
icon: ComponentType<SVGProps<SVGSVGElement>>;
|
icon: ComponentType<SVGProps<SVGSVGElement>>;
|
||||||
action?: () => void;
|
action?: () => void;
|
||||||
subItems?: SubItem[];
|
subItems?: SubItem[];
|
||||||
@@ -53,7 +53,7 @@ export interface Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SubItem {
|
export interface SubItem {
|
||||||
name: string;
|
label: string;
|
||||||
icon: JSX.Element;
|
icon: JSX.Element;
|
||||||
action: () => void;
|
action: () => void;
|
||||||
}
|
}
|
||||||
@@ -77,32 +77,25 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
|
|
||||||
const groups: Group[] = [
|
const groups: Group[] = [
|
||||||
{
|
{
|
||||||
name: "Goto",
|
label: "Goto",
|
||||||
icon: LinkIcon,
|
icon: LinkIcon,
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
name: "Messages",
|
label: "Messages",
|
||||||
icon: InboxIcon,
|
icon: InboxIcon,
|
||||||
action() {
|
action() {
|
||||||
setActivePage("messages");
|
setActivePage("messages");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Map",
|
label: "Map",
|
||||||
icon: MapIcon,
|
icon: MapIcon,
|
||||||
action() {
|
action() {
|
||||||
setActivePage("map");
|
setActivePage("map");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Extensions",
|
label: "Config",
|
||||||
icon: BeakerIcon,
|
|
||||||
action() {
|
|
||||||
setActivePage("extensions");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Config",
|
|
||||||
icon: Cog8ToothIcon,
|
icon: Cog8ToothIcon,
|
||||||
action() {
|
action() {
|
||||||
setActivePage("config");
|
setActivePage("config");
|
||||||
@@ -110,14 +103,14 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
tags: ["settings"]
|
tags: ["settings"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Channels",
|
label: "Channels",
|
||||||
icon: Square3Stack3DIcon,
|
icon: Square3Stack3DIcon,
|
||||||
action() {
|
action() {
|
||||||
setActivePage("channels");
|
setActivePage("channels");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Peers",
|
label: "Peers",
|
||||||
icon: UsersIcon,
|
icon: UsersIcon,
|
||||||
action() {
|
action() {
|
||||||
setActivePage("peers");
|
setActivePage("peers");
|
||||||
@@ -126,15 +119,15 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Manage",
|
label: "Manage",
|
||||||
icon: DevicePhoneMobileIcon,
|
icon: DevicePhoneMobileIcon,
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
name: "Switch Node",
|
label: "Switch Node",
|
||||||
icon: ArrowsRightLeftIcon,
|
icon: ArrowsRightLeftIcon,
|
||||||
subItems: getDevices().map((device) => {
|
subItems: getDevices().map((device) => {
|
||||||
return {
|
return {
|
||||||
name:
|
label:
|
||||||
device.nodes.find(
|
device.nodes.find(
|
||||||
(n) => n.data.num === device.hardware.myNodeNum
|
(n) => n.data.num === device.hardware.myNodeNum
|
||||||
)?.data.user?.longName ?? device.hardware.myNodeNum.toString(),
|
)?.data.user?.longName ?? device.hardware.myNodeNum.toString(),
|
||||||
@@ -151,7 +144,7 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Connect New Node",
|
label: "Connect New Node",
|
||||||
icon: PlusIcon,
|
icon: PlusIcon,
|
||||||
action() {
|
action() {
|
||||||
setSelectedDevice(0);
|
setSelectedDevice(0);
|
||||||
@@ -160,22 +153,22 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Contextual",
|
label: "Contextual",
|
||||||
icon: CubeTransparentIcon,
|
icon: CubeTransparentIcon,
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
name: "QR Code",
|
label: "QR Code",
|
||||||
icon: QrCodeIcon,
|
icon: QrCodeIcon,
|
||||||
subItems: [
|
subItems: [
|
||||||
{
|
{
|
||||||
name: "Generator",
|
label: "Generator",
|
||||||
icon: <QueueListIcon className="w-4" />,
|
icon: <QueueListIcon className="w-4" />,
|
||||||
action() {
|
action() {
|
||||||
setDialogOpen("QR", true);
|
setDialogOpen("QR", true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Import",
|
label: "Import",
|
||||||
icon: <ArrowDownOnSquareStackIcon className="w-4" />,
|
icon: <ArrowDownOnSquareStackIcon className="w-4" />,
|
||||||
action() {
|
action() {
|
||||||
setDialogOpen("import", true);
|
setDialogOpen("import", true);
|
||||||
@@ -184,7 +177,7 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Disconnect",
|
label: "Disconnect",
|
||||||
icon: XCircleIcon,
|
icon: XCircleIcon,
|
||||||
action() {
|
action() {
|
||||||
void connection?.disconnect();
|
void connection?.disconnect();
|
||||||
@@ -193,21 +186,21 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Schedule Shutdown",
|
label: "Schedule Shutdown",
|
||||||
icon: PowerIcon,
|
icon: PowerIcon,
|
||||||
action() {
|
action() {
|
||||||
setDialogOpen("shutdown", true);
|
setDialogOpen("shutdown", true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Schedule Reboot",
|
label: "Schedule Reboot",
|
||||||
icon: ArrowPathIcon,
|
icon: ArrowPathIcon,
|
||||||
action() {
|
action() {
|
||||||
setDialogOpen("reboot", true);
|
setDialogOpen("reboot", true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Reset Peers",
|
label: "Reset Peers",
|
||||||
icon: TrashIcon,
|
icon: TrashIcon,
|
||||||
action() {
|
action() {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
@@ -220,7 +213,7 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Factory Reset",
|
label: "Factory Reset",
|
||||||
icon: ArrowPathRoundedSquareIcon,
|
icon: ArrowPathRoundedSquareIcon,
|
||||||
action() {
|
action() {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
@@ -235,18 +228,18 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Debug",
|
label: "Debug",
|
||||||
icon: BugAntIcon,
|
icon: BugAntIcon,
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
name: "Reconfigure",
|
label: "Reconfigure",
|
||||||
icon: ArrowPathIcon,
|
icon: ArrowPathIcon,
|
||||||
action() {
|
action() {
|
||||||
void connection?.configure();
|
void connection?.configure();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "[WIP] Clear Messages",
|
label: "[WIP] Clear Messages",
|
||||||
icon: ArchiveBoxXMarkIcon,
|
icon: ArchiveBoxXMarkIcon,
|
||||||
action() {
|
action() {
|
||||||
alert("This feature is not implemented");
|
alert("This feature is not implemented");
|
||||||
@@ -255,22 +248,22 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Application",
|
label: "Application",
|
||||||
icon: WindowIcon,
|
icon: WindowIcon,
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
name: "Toggle Dark Mode",
|
label: "Toggle Dark Mode",
|
||||||
icon: MoonIcon,
|
icon: MoonIcon,
|
||||||
action() {
|
action() {
|
||||||
setDarkMode(!darkMode);
|
setDarkMode(!darkMode);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Accent Color",
|
label: "Accent Color",
|
||||||
icon: SwatchIcon,
|
icon: SwatchIcon,
|
||||||
subItems: [
|
subItems: [
|
||||||
{
|
{
|
||||||
name: "Red",
|
label: "Red",
|
||||||
icon: (
|
icon: (
|
||||||
<span
|
<span
|
||||||
className={`h-3 w-3 rounded-full ${
|
className={`h-3 w-3 rounded-full ${
|
||||||
@@ -283,7 +276,7 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Orange",
|
label: "Orange",
|
||||||
icon: (
|
icon: (
|
||||||
<span
|
<span
|
||||||
className={`h-3 w-3 rounded-full ${
|
className={`h-3 w-3 rounded-full ${
|
||||||
@@ -296,7 +289,7 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Yellow",
|
label: "Yellow",
|
||||||
icon: (
|
icon: (
|
||||||
<span
|
<span
|
||||||
className={`h-3 w-3 rounded-full ${
|
className={`h-3 w-3 rounded-full ${
|
||||||
@@ -309,7 +302,7 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Green",
|
label: "Green",
|
||||||
icon: (
|
icon: (
|
||||||
<span
|
<span
|
||||||
className={`h-3 w-3 rounded-full ${
|
className={`h-3 w-3 rounded-full ${
|
||||||
@@ -322,7 +315,7 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Blue",
|
label: "Blue",
|
||||||
icon: (
|
icon: (
|
||||||
<span
|
<span
|
||||||
className={`h-3 w-3 rounded-full ${
|
className={`h-3 w-3 rounded-full ${
|
||||||
@@ -335,7 +328,7 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Purple",
|
label: "Purple",
|
||||||
icon: (
|
icon: (
|
||||||
<span
|
<span
|
||||||
className={`h-3 w-3 rounded-full ${
|
className={`h-3 w-3 rounded-full ${
|
||||||
@@ -348,7 +341,7 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Pink",
|
label: "Pink",
|
||||||
icon: (
|
icon: (
|
||||||
<span
|
<span
|
||||||
className={`h-3 w-3 rounded-full ${
|
className={`h-3 w-3 rounded-full ${
|
||||||
@@ -386,7 +379,7 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
return {
|
return {
|
||||||
...group,
|
...group,
|
||||||
commands: group.commands.filter((command) => {
|
commands: group.commands.filter((command) => {
|
||||||
const nameIncludes = `${group.name} ${command.name}`
|
const nameIncludes = `${group.label} ${command.label}`
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(query.toLowerCase());
|
.includes(query.toLowerCase());
|
||||||
|
|
||||||
@@ -399,7 +392,7 @@ export const CommandPalette = (): JSX.Element => {
|
|||||||
const subItemsInclude = (
|
const subItemsInclude = (
|
||||||
command.subItems
|
command.subItems
|
||||||
?.map((s) =>
|
?.map((s) =>
|
||||||
s.name.toLowerCase().includes(query.toLowerCase())
|
s.label.toLowerCase().includes(query.toLowerCase())
|
||||||
)
|
)
|
||||||
.filter(Boolean) ?? []
|
.filter(Boolean) ?? []
|
||||||
).length;
|
).length;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const SearchResult = ({ group }: SearchResultProps): JSX.Element => {
|
|||||||
<div className="rounded-md border-2 border-backgroundPrimary py-2">
|
<div className="rounded-md border-2 border-backgroundPrimary py-2">
|
||||||
<div className="flex items-center px-3 py-2">
|
<div className="flex items-center px-3 py-2">
|
||||||
<group.icon className="text-gray-900 h-6 w-6 flex-none text-opacity-40" />
|
<group.icon className="text-gray-900 h-6 w-6 flex-none text-opacity-40" />
|
||||||
<span className="ml-3 flex-auto truncate">{group.name}</span>
|
<span className="ml-3 flex-auto truncate">{group.label}</span>
|
||||||
</div>
|
</div>
|
||||||
{group.commands.map((command, index) => (
|
{group.commands.map((command, index) => (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
@@ -30,7 +30,7 @@ export const SearchResult = ({ group }: SearchResultProps): JSX.Element => {
|
|||||||
active ? "text-opacity-100" : ""
|
active ? "text-opacity-100" : ""
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<span className="ml-3">{command.name}</span>
|
<span className="ml-3">{command.label}</span>
|
||||||
{active && (
|
{active && (
|
||||||
<ChevronRightIcon className="text-gray-400 ml-auto h-4" />
|
<ChevronRightIcon className="text-gray-400 ml-auto h-4" />
|
||||||
)}
|
)}
|
||||||
@@ -54,7 +54,7 @@ export const SearchResult = ({ group }: SearchResultProps): JSX.Element => {
|
|||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<>
|
<>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
<span className="ml-3">{item.name}</span>
|
<span className="ml-3">{item.label}</span>
|
||||||
{active && (
|
{active && (
|
||||||
<ChevronRightIcon className="text-gray-400 ml-auto h-4" />
|
<ChevronRightIcon className="text-gray-400 ml-auto h-4" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -41,11 +41,13 @@ export const RebootDialog = ({
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className="w-24"
|
className="w-24"
|
||||||
iconBefore={<ArrowPathIcon className="w-4" />}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
connection?.reboot(2).then(() => setDialogOpen("reboot", false));
|
connection?.reboot(2).then(() => setDialogOpen("reboot", false));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
|
<ArrowPathIcon className="w-4" />
|
||||||
|
</span>
|
||||||
Now
|
Now
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ export const ShutdownDialog = ({
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className="w-24"
|
className="w-24"
|
||||||
iconBefore={<PowerIcon className="w-4" />}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
connection
|
connection
|
||||||
?.shutdown(2)
|
?.shutdown(2)
|
||||||
.then(() => setDialogOpen("shutdown", false));
|
.then(() => setDialogOpen("shutdown", false));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Now
|
<PowerIcon className="w-4" />
|
||||||
|
<span>Now</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ export const Drawer = (): JSX.Element => {
|
|||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
|
|
||||||
const tabs: TabType[] = [
|
const tabs: TabType[] = [
|
||||||
{ name: "Notifications", element: Notifications },
|
{ label: "Notifications", element: Notifications },
|
||||||
{ name: "Metrics", element: Metrics },
|
{ label: "Metrics", element: Metrics },
|
||||||
{ name: "Sensor", element: Sensor }
|
{ label: "Sensor", element: Sensor }
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<Tab.Group as="div">
|
<Tab.Group as="div">
|
||||||
@@ -30,7 +30,7 @@ export const Drawer = (): JSX.Element => {
|
|||||||
: "border-backgroundPrimary text-textSecondary"
|
: "border-backgroundPrimary text-textSecondary"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className="m-auto select-none">{tab.name}</span>
|
<span className="m-auto select-none">{tab.label}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const NewDevice = () => {
|
|||||||
|
|
||||||
const tabs: TabType[] = [
|
const tabs: TabType[] = [
|
||||||
{
|
{
|
||||||
name: "Bluetooth",
|
label: "Bluetooth",
|
||||||
element: BLE,
|
element: BLE,
|
||||||
disabled: !navigator.bluetooth,
|
disabled: !navigator.bluetooth,
|
||||||
disabledMessage:
|
disabledMessage:
|
||||||
@@ -21,13 +21,13 @@ export const NewDevice = () => {
|
|||||||
"https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility"
|
"https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "HTTP",
|
label: "HTTP",
|
||||||
element: HTTP,
|
element: HTTP,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
disabledMessage: "Unsuported connection method"
|
disabledMessage: "Unsuported connection method"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Serial",
|
label: "Serial",
|
||||||
element: Serial,
|
element: Serial,
|
||||||
disabled: !navigator.serial,
|
disabled: !navigator.serial,
|
||||||
disabledMessage:
|
disabledMessage:
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||||
|
import { NavBar } from "@app/Nav/NavBar.js";
|
||||||
|
|
||||||
export interface SettingsPanelProps {
|
export interface SettingsPanelProps {
|
||||||
channel: Protobuf.Channel;
|
channel: Protobuf.Channel;
|
||||||
@@ -102,92 +103,106 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={onSubmit}>
|
<div className="flex flex-grow flex-col gap-2">
|
||||||
{channel?.index !== 0 && (
|
<NavBar
|
||||||
<>
|
breadcrumb={["Channels", channel?.index.toString()]}
|
||||||
<Controller
|
actions={[
|
||||||
name="enabled"
|
{
|
||||||
control={control}
|
label: "Apply",
|
||||||
render={({ field: { value, ...rest } }) => (
|
async onClick() {
|
||||||
<Toggle
|
await onSubmit();
|
||||||
label="Enabled"
|
}
|
||||||
description="Description"
|
|
||||||
checked={value}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
label="Name"
|
|
||||||
description="Max transmit power in dBm"
|
|
||||||
error={errors.name?.message}
|
|
||||||
{...register("name")}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Select
|
|
||||||
label="Key Size"
|
|
||||||
description="Desired size of generated key."
|
|
||||||
value={keySize}
|
|
||||||
onChange={(e): void => {
|
|
||||||
setKeySize(parseInt(e.target.value) as 128 | 256);
|
|
||||||
}}
|
|
||||||
action={{
|
|
||||||
icon: <ArrowPathIcon className="h-4" />,
|
|
||||||
action: () => {
|
|
||||||
const key = new Uint8Array(keySize / 8);
|
|
||||||
crypto.getRandomValues(key);
|
|
||||||
setValue("psk", fromByteArray(key), {
|
|
||||||
shouldDirty: true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}}
|
]}
|
||||||
>
|
|
||||||
<option value={128}>128 Bit</option>
|
|
||||||
<option value={256}>256 Bit</option>
|
|
||||||
</Select>
|
|
||||||
<Input
|
|
||||||
width="100%"
|
|
||||||
label="Pre-Shared Key"
|
|
||||||
description="Channel key to encrypt data"
|
|
||||||
type={pskHidden ? "password" : "text"}
|
|
||||||
action={{
|
|
||||||
icon: pskHidden ? (
|
|
||||||
<EyeIcon className="w-4" />
|
|
||||||
) : (
|
|
||||||
<EyeSlashIcon className="w-4" />
|
|
||||||
),
|
|
||||||
action: () => {
|
|
||||||
setPskHidden(!pskHidden);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
error={errors.psk?.message}
|
|
||||||
{...register("psk")}
|
|
||||||
/>
|
/>
|
||||||
<Controller
|
|
||||||
name="uplinkEnabled"
|
<Form onSubmit={onSubmit}>
|
||||||
control={control}
|
{channel?.index !== 0 && (
|
||||||
render={({ field: { value, ...rest } }) => (
|
<>
|
||||||
<Toggle
|
<Controller
|
||||||
label="Uplink Enabled"
|
name="enabled"
|
||||||
description="Send packets to designated MQTT server"
|
control={control}
|
||||||
checked={value}
|
render={({ field: { value, ...rest } }) => (
|
||||||
{...rest}
|
<Toggle
|
||||||
/>
|
label="Enabled"
|
||||||
|
description="Description"
|
||||||
|
checked={value}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Name"
|
||||||
|
description="Max transmit power in dBm"
|
||||||
|
error={errors.name?.message}
|
||||||
|
{...register("name")}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
<Select
|
||||||
<Controller
|
label="Key Size"
|
||||||
name="downlinkEnabled"
|
description="Desired size of generated key."
|
||||||
control={control}
|
value={keySize}
|
||||||
render={({ field: { value, ...rest } }) => (
|
onChange={(e): void => {
|
||||||
<Toggle
|
setKeySize(parseInt(e.target.value) as 128 | 256);
|
||||||
label="Downlink Enabled"
|
}}
|
||||||
description="Recieve packets to designated MQTT server"
|
action={{
|
||||||
checked={value}
|
icon: <ArrowPathIcon className="h-4" />,
|
||||||
{...rest}
|
action: () => {
|
||||||
/>
|
const key = new Uint8Array(keySize / 8);
|
||||||
)}
|
crypto.getRandomValues(key);
|
||||||
/>
|
setValue("psk", fromByteArray(key), {
|
||||||
</Form>
|
shouldDirty: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value={128}>128 Bit</option>
|
||||||
|
<option value={256}>256 Bit</option>
|
||||||
|
</Select>
|
||||||
|
<Input
|
||||||
|
width="100%"
|
||||||
|
label="Pre-Shared Key"
|
||||||
|
description="Channel key to encrypt data"
|
||||||
|
type={pskHidden ? "password" : "text"}
|
||||||
|
action={{
|
||||||
|
icon: pskHidden ? (
|
||||||
|
<EyeIcon className="w-4" />
|
||||||
|
) : (
|
||||||
|
<EyeSlashIcon className="w-4" />
|
||||||
|
),
|
||||||
|
action: () => {
|
||||||
|
setPskHidden(!pskHidden);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
error={errors.psk?.message}
|
||||||
|
{...register("psk")}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="uplinkEnabled"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, ...rest } }) => (
|
||||||
|
<Toggle
|
||||||
|
label="Uplink Enabled"
|
||||||
|
description="Send packets to designated MQTT server"
|
||||||
|
checked={value}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="downlinkEnabled"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, ...rest } }) => (
|
||||||
|
<Toggle
|
||||||
|
label="Downlink Enabled"
|
||||||
|
description="Recieve packets to designated MQTT server"
|
||||||
|
checked={value}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ export const BLE = (): JSX.Element => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
iconBefore={<PlusCircleIcon className="w-4" />}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void navigator.bluetooth
|
void navigator.bluetooth
|
||||||
.requestDevice({
|
.requestDevice({
|
||||||
@@ -65,7 +64,8 @@ export const BLE = (): JSX.Element => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
New device
|
<PlusCircleIcon className="w-4" />
|
||||||
|
<span>New device</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -71,8 +71,9 @@ export const HTTP = (): JSX.Element => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button iconBefore={<PlusCircleIcon className="w-4" />} type="submit">
|
<Button type="submit">
|
||||||
Connect
|
<PlusCircleIcon className="w-4" />
|
||||||
|
<span>Connect</span>
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -64,14 +64,14 @@ export const Serial = (): JSX.Element => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
iconBefore={<PlusCircleIcon className="w-4" />}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void navigator.serial.requestPort().then((port) => {
|
void navigator.serial.requestPort().then((port) => {
|
||||||
setSerialPorts(serialPorts.concat(port));
|
setSerialPorts(serialPorts.concat(port));
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
New device
|
<PlusCircleIcon className="w-4" />
|
||||||
|
<span>New device</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,12 +28,8 @@ export const DeviceWidget = ({
|
|||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
<div className="my-auto ml-auto">
|
<div className="my-auto ml-auto">
|
||||||
<Button
|
<Button onClick={disconnected ? reconnect : disconnect} size="sm">
|
||||||
onClick={disconnected ? reconnect : disconnect}
|
<span>{disconnected ? "Reconnect" : "Disconnect"}</span>
|
||||||
size="sm"
|
|
||||||
iconBefore={<XCircleIcon className="h-4" />}
|
|
||||||
>
|
|
||||||
{disconnected ? "Reconnect" : "Disconnect"}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import type { ButtonHTMLAttributes } from "react";
|
import type { ButtonHTMLAttributes, ComponentType, SVGProps } from "react";
|
||||||
|
|
||||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
size?: "sm" | "md" | "lg";
|
size?: "sm" | "md" | "lg";
|
||||||
iconBefore?: JSX.Element;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Button = ({
|
export const Button = ({
|
||||||
size = "md",
|
size = "md",
|
||||||
iconBefore,
|
|
||||||
children,
|
children,
|
||||||
disabled,
|
disabled,
|
||||||
className,
|
className,
|
||||||
@@ -30,7 +28,6 @@ export const Button = ({
|
|||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<div className="m-auto flex shrink-0 items-center gap-2 font-medium">
|
<div className="m-auto flex shrink-0 items-center gap-2 font-medium">
|
||||||
{iconBefore}
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const Form = ({
|
|||||||
}: FormProps): JSX.Element => {
|
}: FormProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="mr-2 w-full rounded-md bg-backgroundSecondary px-2"
|
className="w-full rounded-md bg-backgroundSecondary px-2"
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
onChange={onSubmit}
|
onChange={onSubmit}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Mono } from "@components/generic/Mono";
|
|||||||
import { Tab } from "@headlessui/react";
|
import { Tab } from "@headlessui/react";
|
||||||
|
|
||||||
export interface TabType {
|
export interface TabType {
|
||||||
name: string;
|
label: string;
|
||||||
icon?: JSX.Element;
|
icon?: JSX.Element;
|
||||||
element: () => JSX.Element;
|
element: () => JSX.Element;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@@ -46,7 +46,7 @@ export const TabbedContent = ({
|
|||||||
{entry.icon && (
|
{entry.icon && (
|
||||||
<div className="text-slate-500 m-auto">{entry.icon}</div>
|
<div className="text-slate-500 m-auto">{entry.icon}</div>
|
||||||
)}
|
)}
|
||||||
<span className="m-auto">{entry.name}</span>
|
<span className="m-auto">{entry.label}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Tab>
|
</Tab>
|
||||||
@@ -65,7 +65,7 @@ export const TabbedContent = ({
|
|||||||
</Tab.List>
|
</Tab.List>
|
||||||
<Tab.Panels as={Fragment}>
|
<Tab.Panels as={Fragment}>
|
||||||
{tabs.map((entry, index) => (
|
{tabs.map((entry, index) => (
|
||||||
<Tab.Panel key={index} className="flex flex-grow">
|
<Tab.Panel key={index} className="m-2 flex flex-grow">
|
||||||
{!entry.disabled ? (
|
{!entry.disabled ? (
|
||||||
<entry.element />
|
<entry.element />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { Fragment } from "react";
|
||||||
|
import { Mono } from "@components/generic/Mono";
|
||||||
|
import { Tab } from "@headlessui/react";
|
||||||
|
|
||||||
|
export interface TabType {
|
||||||
|
label: string;
|
||||||
|
element: () => JSX.Element;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabbedContentProps {
|
||||||
|
tabs: TabType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VerticalTabbedContent = ({
|
||||||
|
tabs
|
||||||
|
}: TabbedContentProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Tab.Group as="div" className="flex w-full gap-3">
|
||||||
|
<Tab.List className="flex w-44 flex-col">
|
||||||
|
{tabs.map((tab, index) => (
|
||||||
|
<Tab key={index} as={Fragment}>
|
||||||
|
{({ selected }) => (
|
||||||
|
<div
|
||||||
|
className={`flex cursor-pointer items-center border-l-4 p-4 text-sm font-medium ${
|
||||||
|
selected
|
||||||
|
? "border-accent bg-accentMuted bg-opacity-10 text-textPrimary"
|
||||||
|
: "border-backgroundPrimary text-textSecondary"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
<span className="ml-auto rounded-full bg-accent px-3 text-textPrimary">
|
||||||
|
3
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Tab>
|
||||||
|
))}
|
||||||
|
</Tab.List>
|
||||||
|
<Tab.Panels as={Fragment}>
|
||||||
|
{tabs.map((tab, index) => (
|
||||||
|
<Tab.Panel key={index} as={Fragment}>
|
||||||
|
<tab.element />
|
||||||
|
</Tab.Panel>
|
||||||
|
))}
|
||||||
|
</Tab.Panels>
|
||||||
|
</Tab.Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -8,7 +8,6 @@ import { Protobuf, Types } from "@meshtastic/meshtasticjs";
|
|||||||
export type Page =
|
export type Page =
|
||||||
| "messages"
|
| "messages"
|
||||||
| "map"
|
| "map"
|
||||||
| "extensions"
|
|
||||||
| "config"
|
| "config"
|
||||||
| "channels"
|
| "channels"
|
||||||
| "peers";
|
| "peers";
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const ChannelsPage = (): JSX.Element => {
|
|||||||
|
|
||||||
const tabs: TabType[] = channels.map((channel) => {
|
const tabs: TabType[] = channels.map((channel) => {
|
||||||
return {
|
return {
|
||||||
name: channel.config.settings?.name.length
|
label: channel.config.settings?.name.length
|
||||||
? channel.config.settings.name
|
? channel.config.settings.name
|
||||||
: channel.config.role === Protobuf.Channel_Role.PRIMARY
|
: channel.config.role === Protobuf.Channel_Role.PRIMARY
|
||||||
? "Primary"
|
? "Primary"
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ import { Tab } from "@headlessui/react";
|
|||||||
import { ChevronRightIcon, HomeIcon } from "@heroicons/react/24/outline";
|
import { ChevronRightIcon, HomeIcon } from "@heroicons/react/24/outline";
|
||||||
import { Button } from "@components/form/Button.js";
|
import { Button } from "@components/form/Button.js";
|
||||||
import { CheckIcon } from "@primer/octicons-react";
|
import { CheckIcon } from "@primer/octicons-react";
|
||||||
|
import { NavBar } from "@app/Nav/NavBar.js";
|
||||||
|
import { VerticalTabbedContent } from "@app/components/generic/VerticalTabbedContent.js";
|
||||||
|
|
||||||
export const DeviceConfig = (): JSX.Element => {
|
export const DeviceConfig = (): JSX.Element => {
|
||||||
const { hardware, workingConfig, connection } = useDevice();
|
const { hardware, workingConfig, connection } = useDevice();
|
||||||
|
|
||||||
const configSections = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: "User",
|
label: "User",
|
||||||
element: User
|
element: User
|
||||||
@@ -53,65 +55,23 @@ export const DeviceConfig = (): JSX.Element => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="flex flex-grow flex-col gap-2">
|
||||||
<div className="m-2 flex rounded-md bg-backgroundSecondary p-2">
|
<NavBar
|
||||||
<ol className="my-auto ml-2 flex gap-4 text-textSecondary">
|
breadcrumb={["Config"]}
|
||||||
<li className="cursor-pointer hover:brightness-disabled">
|
actions={[
|
||||||
<HomeIcon className="h-5 w-5 flex-shrink-0" />
|
{
|
||||||
</li>
|
label: "Apply & Reboot",
|
||||||
{["Config", "User"].map((breadcrumb, index) => (
|
async onClick() {
|
||||||
<li key={index} className="flex gap-4">
|
|
||||||
<ChevronRightIcon className="h-5 w-5 flex-shrink-0 brightness-disabled" />
|
|
||||||
<span className="cursor-pointer text-sm font-medium hover:brightness-disabled">
|
|
||||||
{breadcrumb}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ol>
|
|
||||||
<div className="ml-auto">
|
|
||||||
<Button
|
|
||||||
onClick={async () => {
|
|
||||||
workingConfig.map(async (config) => {
|
workingConfig.map(async (config) => {
|
||||||
await connection?.setConfig(config);
|
await connection?.setConfig(config);
|
||||||
});
|
});
|
||||||
await connection?.commitEditSettings();
|
await connection?.commitEditSettings();
|
||||||
}}
|
}
|
||||||
iconBefore={<CheckIcon className="w-4" />}
|
}
|
||||||
>
|
]}
|
||||||
Apply & Reboot
|
/>
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Tab.Group as="div" className="flex w-full gap-3">
|
<VerticalTabbedContent tabs={tabs} />
|
||||||
<Tab.List className="flex w-44 flex-col">
|
|
||||||
{configSections.map((Config, index) => (
|
|
||||||
<Tab key={index} as={Fragment}>
|
|
||||||
{({ selected }) => (
|
|
||||||
<div
|
|
||||||
className={`flex cursor-pointer items-center border-l-4 p-4 text-sm font-medium ${
|
|
||||||
selected
|
|
||||||
? "border-accent bg-accentMuted bg-opacity-10 text-textPrimary"
|
|
||||||
: "border-backgroundPrimary text-textSecondary"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{Config.label}
|
|
||||||
<span className="ml-auto rounded-full bg-accent px-3 text-textPrimary">
|
|
||||||
3
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tab>
|
|
||||||
))}
|
|
||||||
</Tab.List>
|
|
||||||
<Tab.Panels as={Fragment}>
|
|
||||||
{configSections.map((Config, index) => (
|
|
||||||
<Tab.Panel key={index} as={Fragment}>
|
|
||||||
<Config.element />
|
|
||||||
</Tab.Panel>
|
|
||||||
))}
|
|
||||||
</Tab.Panels>
|
|
||||||
</Tab.Group>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,10 +7,14 @@ import { RangeTest } from "@components/PageComponents/ModuleConfig/RangeTest.js"
|
|||||||
import { Serial } from "@components/PageComponents/ModuleConfig/Serial.js";
|
import { Serial } from "@components/PageComponents/ModuleConfig/Serial.js";
|
||||||
import { StoreForward } from "@components/PageComponents/ModuleConfig/StoreForward.js";
|
import { StoreForward } from "@components/PageComponents/ModuleConfig/StoreForward.js";
|
||||||
import { Telemetry } from "@components/PageComponents/ModuleConfig/Telemetry.js";
|
import { Telemetry } from "@components/PageComponents/ModuleConfig/Telemetry.js";
|
||||||
import { Tab } from "@headlessui/react";
|
import { useDevice } from "@app/core/providers/useDevice.js";
|
||||||
|
import { NavBar } from "@app/Nav/NavBar.js";
|
||||||
|
import { VerticalTabbedContent } from "@app/components/generic/VerticalTabbedContent.js";
|
||||||
|
|
||||||
export const ModuleConfig = (): JSX.Element => {
|
export const ModuleConfig = (): JSX.Element => {
|
||||||
const configSections = [
|
const { workingModuleConfig, connection } = useDevice();
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: "MQTT",
|
label: "MQTT",
|
||||||
element: MQTT
|
element: MQTT
|
||||||
@@ -46,31 +50,23 @@ export const ModuleConfig = (): JSX.Element => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab.Group as="div" className="flex w-full gap-3">
|
<div className="flex flex-grow flex-col gap-2">
|
||||||
<Tab.List className="flex w-44 flex-col gap-1">
|
<NavBar
|
||||||
{configSections.map((Config, index) => (
|
breadcrumb={["Module Config"]}
|
||||||
<Tab key={index} as={Fragment}>
|
actions={[
|
||||||
{({ selected }) => (
|
{
|
||||||
<div
|
label: "Apply & Reboot",
|
||||||
className={`flex cursor-pointer items-center rounded-md px-3 py-2 text-sm font-medium ${
|
async onClick() {
|
||||||
selected
|
workingModuleConfig.map(async (moduleConfig) => {
|
||||||
? "bg-gray-100 text-gray-900"
|
await connection?.setModuleConfig(moduleConfig);
|
||||||
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900"
|
});
|
||||||
}`}
|
await connection?.commitEditSettings();
|
||||||
>
|
}
|
||||||
{Config.label}
|
}
|
||||||
</div>
|
]}
|
||||||
)}
|
/>
|
||||||
</Tab>
|
|
||||||
))}
|
<VerticalTabbedContent tabs={tabs} />
|
||||||
</Tab.List>
|
</div>
|
||||||
<Tab.Panels as={Fragment}>
|
|
||||||
{configSections.map((Config, index) => (
|
|
||||||
<Tab.Panel key={index} as={Fragment}>
|
|
||||||
<Config.element />
|
|
||||||
</Tab.Panel>
|
|
||||||
))}
|
|
||||||
</Tab.Panels>
|
|
||||||
</Tab.Group>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,17 +14,17 @@ export const ConfigPage = (): JSX.Element => {
|
|||||||
|
|
||||||
const tabs: TabType[] = [
|
const tabs: TabType[] = [
|
||||||
{
|
{
|
||||||
name: "Device Config",
|
label: "Device Config",
|
||||||
icon: <Cog8ToothIcon className="h-4" />,
|
icon: <Cog8ToothIcon className="h-4" />,
|
||||||
element: DeviceConfig
|
element: DeviceConfig
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Module Config",
|
label: "Module Config",
|
||||||
icon: <CubeTransparentIcon className="h-4" />,
|
icon: <CubeTransparentIcon className="h-4" />,
|
||||||
element: ModuleConfig
|
element: ModuleConfig
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "App Config",
|
label: "App Config",
|
||||||
icon: <WindowIcon className="h-4" />,
|
icon: <WindowIcon className="h-4" />,
|
||||||
element: AppConfig
|
element: AppConfig
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { useDevice } from "@core/providers/useDevice.js";
|
|
||||||
|
|
||||||
export const Environment = (): JSX.Element => {
|
|
||||||
const { nodes } = useDevice();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{nodes.map((node, index) => (
|
|
||||||
<div key={index}>{JSON.stringify(node.environmentMetrics)}</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
export interface File {
|
|
||||||
nameModified: string;
|
|
||||||
name: string;
|
|
||||||
size: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Files {
|
|
||||||
data: {
|
|
||||||
files: File[];
|
|
||||||
fileSystem: {
|
|
||||||
total: number;
|
|
||||||
used: number;
|
|
||||||
free: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
status: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FileBrowser = (): JSX.Element => {
|
|
||||||
const [data, setData] = useState<Files>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void fetch("http://meshtastic.local/json/fs/browse/static").then(
|
|
||||||
async (res) => {
|
|
||||||
setData((await res.json()) as Files);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{data?.data.files.map((file) => (
|
|
||||||
<div key={file.name}>
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
href={`http://meshtastic.local/${file.name.replace("static/", "")}`}
|
|
||||||
>
|
|
||||||
{file.name.replace("static/", "").replace(".gz", "")}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { TabbedContent, TabType } from "@components/generic/TabbedContent";
|
|
||||||
import { useDevice } from "@core/providers/useDevice.js";
|
|
||||||
import {
|
|
||||||
CloudIcon,
|
|
||||||
DocumentIcon,
|
|
||||||
SignalIcon
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import { Environment } from "@pages/Extensions/Environment.js";
|
|
||||||
import { FileBrowser } from "@pages/Extensions/FileBrowser";
|
|
||||||
|
|
||||||
export const ExtensionsPage = (): JSX.Element => {
|
|
||||||
const { hardware } = useDevice();
|
|
||||||
|
|
||||||
const tabs: TabType[] = [
|
|
||||||
{
|
|
||||||
name: "File Browser",
|
|
||||||
icon: <DocumentIcon className="h-4" />,
|
|
||||||
element: FileBrowser,
|
|
||||||
disabled: !hardware.hasWifi
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Range Test",
|
|
||||||
icon: <SignalIcon className="h-4" />,
|
|
||||||
element: FileBrowser,
|
|
||||||
disabled: !hardware.hasWifi
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Environment",
|
|
||||||
icon: <CloudIcon className="h-4" />,
|
|
||||||
element: Environment
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return <TabbedContent tabs={tabs} />;
|
|
||||||
};
|
|
||||||
@@ -9,7 +9,7 @@ export const MessagesPage = (): JSX.Element => {
|
|||||||
|
|
||||||
const tabs: TabType[] = channels.map((channel) => {
|
const tabs: TabType[] = channels.map((channel) => {
|
||||||
return {
|
return {
|
||||||
name: channel.config.settings?.name.length
|
label: channel.config.settings?.name.length
|
||||||
? channel.config.settings?.name
|
? channel.config.settings?.name
|
||||||
: channel.config.index === 0
|
: channel.config.index === 0
|
||||||
? "Primary"
|
? "Primary"
|
||||||
|
|||||||
Reference in New Issue
Block a user