Initial 1.3.x rework

This commit is contained in:
Sacha Weatherstone
2022-07-26 08:52:23 +10:00
parent ff6019098b
commit a2280ab96e
163 changed files with 6383 additions and 14397 deletions
-118
View File
@@ -1,118 +0,0 @@
import type React from 'react';
import { Button } from '@components/generic/button/Button';
import { Card } from '@components/generic/Card';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Protobuf } from '@meshtastic/meshtasticjs';
export const Debug = (): JSX.Element => {
const hardwareInfo = useAppSelector(
(state) => state.meshtastic.radio.hardware,
);
const myNodeNum = useAppSelector(
(state) => state.meshtastic.radio.hardware.myNodeNum,
);
const node = useAppSelector((state) =>
state.meshtastic.nodes.find(
(node) => node.data.num === hardwareInfo.myNodeNum,
),
);
return (
<div className="flex flex-col gap-4 p-4 md:flex-row">
<Card className="flex-grow">
<div className="grid grid-cols-4 gap-4">
<Button
onClick={(): void => {
void connection.configure();
}}
>
Configure
</Button>
<Button
onClick={(): void => {
void connection.getAllChannels();
}}
>
Get All Channels
</Button>
<Button
onClick={(): void => {
const packet = Protobuf.AdminMessage.create({
variant: {
oneofKind: 'getConfigRequest',
getConfigRequest:
Protobuf.AdminMessage_ConfigType.LORA_CONFIG,
},
});
void connection.sendPacket(
Protobuf.AdminMessage.toBinary(packet),
Protobuf.PortNum.ADMIN_APP,
myNodeNum,
true,
0,
true,
false,
async (num) => {
return await Promise.resolve();
},
);
}}
>
Get All Config
</Button>
<Button
onClick={(): void => {
void connection.getConfig(
Protobuf.AdminMessage_ConfigType.DISPLAY_CONFIG,
async (id) => {
console.log(`RESPONSE - ${id}`);
return Promise.resolve();
},
);
}}
>
Get Display Config
</Button>
<Button
onClick={(): void => {
void connection.getConfig(
Protobuf.AdminMessage_ConfigType.LORA_CONFIG,
async (id) => {
console.log(`RESPONSE - ${id}`);
return Promise.resolve();
},
);
}}
>
Get LoRa Config
</Button>
<Button
onClick={(): void => {
void connection.setConfig(
Protobuf.Config.create({
payloadVariant: {
oneofKind: 'lora',
lora: Protobuf.Config_LoRaConfig.create({
modemPreset:
Protobuf.Config_LoRaConfig_ModemPreset.MidSlow,
}),
},
}),
async (id) => {
console.log(`RESPONSE - ${id}`);
return Promise.resolve();
},
);
}}
>
Set LoRa Config
</Button>
</div>
</Card>
</div>
);
};
+17
View File
@@ -0,0 +1,17 @@
import type React from "react";
import { Pane } from "evergreen-ui";
import { useDevice } from "@app/core/stores/deviceStore.js";
export const Environment = (): JSX.Element => {
const { nodes } = useDevice();
return (
<Pane>
{nodes.map((node, index) => (
<Pane key={index}>{JSON.stringify(node.environmentMetrics)}</Pane>
))}
</Pane>
);
};
+22 -64
View File
@@ -1,13 +1,9 @@
import type React from 'react';
import type React from "react";
import { AnimatePresence, m } from 'framer-motion';
import { FiFilePlus } from 'react-icons/fi';
import useSWR from 'swr';
import { Pane } from "evergreen-ui";
import useSWR from "swr";
import { Button } from '@components/generic/button/Button';
import { Card } from '@components/generic/Card';
import { fetcher } from '@core/utils/fetcher';
import { useAppSelector } from '@hooks/useAppSelector';
import { fetcher } from "@core/utils/fetcher";
export interface File {
nameModified: string;
@@ -28,65 +24,27 @@ export interface Files {
}
export const FileBrowser = (): JSX.Element => {
const connectionParams = useAppSelector(
(state) => state.app.connectionParams,
);
const appState = useAppSelector((state) => state.app);
const meshtasticState = useAppSelector((state) => state.meshtastic);
const { data } = useSWR<Files>(
`${connectionParams.HTTP.tls ? 'https' : 'http'}://${
connectionParams.HTTP.address
}${
meshtasticState.radio.hardware.firmwareVersion.includes('1.2')
? '/json/spiffs/browse/static'
: '/json/fs/browse/static'
}`,
fetcher,
"http://meshtastic.local/json/fs/browse/static",
fetcher
);
return (
<div className="flex h-full p-4">
<Card
title="File Browser"
actions={<Button icon={<FiFilePlus />}>Upload File</Button>}
className="flex-grow flex-col"
>
<div className="h-full px-4">
<AnimatePresence>
{(!data || data?.data.files.length === 0) && (
<div className="flex h-full w-full">
<m.img
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="m-auto h-64 w-64 text-green-500"
src={appState.darkMode ? '/Files_Dark.svg' : '/Files.svg'}
/>
</div>
)}
</AnimatePresence>
{data?.data.files.map((file) => (
<div
key={file.name}
className="flex h-10 w-full border-b border-gray-400 px-4 font-medium dark:border-gray-600 dark:text-white"
>
<div className="my-auto w-1/3">
<a
target="_blank"
rel="noopener noreferrer"
href={`${connectionParams.HTTP.tls ? 'https' : 'http'}://${
connectionParams.HTTP.address
}/${file.name.replace('static/', '')}`}
>
{file.name.replace('static/', '').replace('.gz', '')}
</a>
</div>
<div className="my-auto w-1/3"></div>
</div>
))}
</div>
</Card>
</div>
<Pane>
{data?.data.files.map((file) => (
<Pane key={file.name}>
<a
target="_blank"
rel="noopener noreferrer"
href={`http://meshtastic.local/json/fs/browse/static/${file.name.replace(
"static/",
""
)}`}
>
{file.name.replace("static/", "").replace(".gz", "")}
</a>
</Pane>
))}
</Pane>
);
};
+39 -74
View File
@@ -1,81 +1,46 @@
import type React from 'react';
import { useState } from 'react';
import type React from "react";
import { useState } from "react";
import { FiFile, FiInfo } from 'react-icons/fi';
import { MdSubject } from 'react-icons/md';
import { RiPinDistanceFill } from 'react-icons/ri';
import { VscDebug, VscExtensions } from 'react-icons/vsc';
import { DocumentIcon, GanttChartIcon, RainIcon } from "evergreen-ui";
import { ExternalSection } from '@components/generic/Sidebar/ExternalSection';
import { Layout } from '@components/layout';
import { Debug } from '@pages/Extensions/Debug';
import { FileBrowser } from '@pages/Extensions/FileBrowser';
import { Info } from '@pages/Extensions/Info';
import { Logs } from '@pages/Extensions/Logs';
import { useDevice } from "@app/core/stores/deviceStore.js";
import { Tab, TabbedContent } from "@components/layout/page/TabbedContent";
import { FileBrowser } from "@pages/Extensions/FileBrowser";
export const Extensions = (): JSX.Element => {
const [selectedExtension, setSelectedExtension] = useState<
'info' | 'logs' | 'fileBrowser' | 'rangeTest' | 'debug'
>('info');
import { Environment } from "./Environment.js";
export const ExtensionsPage = (): JSX.Element => {
const [activeExtension, setActiveExtension] = useState(0);
const { hardware } = useDevice();
const tabs: Tab[] = [
{
key: 0,
name: "File Browser",
icon: DocumentIcon,
element: FileBrowser,
disabled: !hardware.hasWifi,
},
{
key: 1,
name: "Range Test",
icon: GanttChartIcon,
element: FileBrowser,
disabled: !hardware.hasWifi,
},
{
key: 2,
name: "Environment",
icon: RainIcon,
element: Environment,
},
];
return (
<Layout
title="Extensions"
icon={<VscExtensions />}
sidebarContents={
<div className="absolute flex w-full flex-col dark:bg-primaryDark">
<ExternalSection
onClick={(): void => {
setSelectedExtension('info');
}}
icon={<FiInfo />}
active={selectedExtension === 'info'}
title="Node Info"
/>
<ExternalSection
onClick={(): void => {
setSelectedExtension('logs');
}}
icon={<MdSubject />}
active={selectedExtension === 'logs'}
title="Logs"
/>
<ExternalSection
onClick={(): void => {
setSelectedExtension('fileBrowser');
}}
icon={<FiFile />}
active={selectedExtension === 'fileBrowser'}
title="File Browser"
/>
<ExternalSection
onClick={(): void => {
setSelectedExtension('rangeTest');
}}
icon={<RiPinDistanceFill />}
active={selectedExtension === 'rangeTest'}
title="Range Test"
/>
<ExternalSection
onClick={(): void => {
setSelectedExtension('debug');
}}
icon={<VscDebug />}
active={selectedExtension === 'debug'}
title="Debug"
/>
</div>
}
>
<div className="w-full">
{selectedExtension === 'info' && <Info />}
{selectedExtension === 'logs' && <Logs />}
{selectedExtension === 'fileBrowser' && <FileBrowser />}
{selectedExtension === 'debug' && <Debug />}
</div>
</Layout>
<TabbedContent
active={activeExtension}
setActive={setActiveExtension}
tabs={tabs}
/>
);
};
-50
View File
@@ -1,50 +0,0 @@
import type React from 'react';
import { FiRefreshCw } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { IconButton } from '@components/generic/button/IconButton';
import { Card } from '@components/generic/Card';
import { CopyButton } from '@components/menu/buttons/CopyButton';
import { Hashicon } from '@emeraldpay/hashicon-react';
import { useAppSelector } from '@hooks/useAppSelector';
export const Info = (): JSX.Element => {
const hardwareInfo = useAppSelector(
(state) => state.meshtastic.radio.hardware,
);
const node = useAppSelector((state) =>
state.meshtastic.nodes.find(
(node) => node.data.num === hardwareInfo.myNodeNum,
),
);
return (
<div className="flex h-full flex-col gap-4 p-4 xl:flex-row">
<Card
title="Connected Node Details"
actions={<IconButton icon={<FiRefreshCw />} />}
className="xl:w-3/5"
>
<div className="m-auto flex flex-col gap-2">
<Hashicon value={hardwareInfo.myNodeNum.toString()} size={180} />
<div className="text-center text-lg font-medium dark:text-white">
{node?.data.user?.longName || 'Unknown'}
</div>
</div>
{/* <img
src="https://docs.rakwireless.com/assets/images/wisblock/rak5005-o/overview/RAK5005-O_buy.png"
className="-rotate-90"
/> */}
</Card>
<Card
title="Debug Information"
className="flex-grow"
actions={<CopyButton data={JSON.stringify(hardwareInfo)} />}
>
<JSONPretty className="overflow-y-auto" data={hardwareInfo} />
</Card>
</div>
);
};
-167
View File
@@ -1,167 +0,0 @@
import type React from 'react';
import { AnimatePresence, m } from 'framer-motion';
import { FiArrowRight, FiPaperclip, FiTrash } from 'react-icons/fi';
import { IconButton } from '@components/generic/button/IconButton';
import { Card } from '@components/generic/Card';
import { clearLogs } from '@core/slices/meshtasticSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { Protobuf, Types } from '@meshtastic/meshtasticjs';
export const Logs = (): JSX.Element => {
const dispatch = useAppDispatch();
const meshtasticState = useAppSelector((state) => state.meshtastic);
const appState = useAppSelector((state) => state.app);
type lookupType = { [key: number]: string };
const emitterLookup: lookupType = {
[Types.Emitter.sendText]: 'text-rose-500',
[Types.Emitter.sendPacket]: 'text-pink-500',
[Types.Emitter.sendRaw]: 'text-fuchsia-500',
[Types.Emitter.setConfig]: 'text-purple-500',
[Types.Emitter.confirmSetConfig]: 'text-violet-500',
[Types.Emitter.setOwner]: 'text-indigo-500',
[Types.Emitter.setChannel]: 'text-blue-500',
[Types.Emitter.confirmSetChannel]: 'text-sky-500',
[Types.Emitter.deleteChannel]: 'text-cyan-500',
[Types.Emitter.getChannel]: 'text-teal-500',
[Types.Emitter.getAllChannels]: 'text-emerald-500',
[Types.Emitter.getConfig]: 'text-green-500',
[Types.Emitter.getOwner]: 'text-lime-500',
[Types.Emitter.configure]: 'text-yellow-500',
[Types.Emitter.handleFromRadio]: 'text-amber-500',
[Types.Emitter.handleMeshPacket]: 'text-orange-500',
[Types.Emitter.connect]: 'text-red-500',
[Types.Emitter.ping]: 'text-stone-500',
[Types.Emitter.readFromRadio]: 'text-zinc-500',
[Types.Emitter.writeToRadio]: 'text-gray-500',
[Types.Emitter.setDebugMode]: 'text-slate-500',
};
const levelLookup: lookupType = {
[Protobuf.LogRecord_Level.UNSET]: 'text-green-500',
[Protobuf.LogRecord_Level.CRITICAL]: 'text-purple-500',
[Protobuf.LogRecord_Level.ERROR]: 'text-red-500',
[Protobuf.LogRecord_Level.WARNING]: 'text-orange-500',
[Protobuf.LogRecord_Level.INFO]: 'text-blue-500',
[Protobuf.LogRecord_Level.DEBUG]: 'text-neutral-500',
[Protobuf.LogRecord_Level.TRACE]: 'text-slate-500',
};
return (
<div className="flex h-full flex-col gap-4 p-4">
<Card
title="Device Logs"
actions={
<IconButton
icon={<FiTrash />}
onClick={(): void => {
dispatch(clearLogs());
}}
/>
}
className="flex-grow overflow-y-auto"
>
<table className="table-cell flex-grow">
<tbody
className="
block h-full flex-col overflow-y-auto font-mono text-xs dark:text-gray-400"
>
<AnimatePresence>
{meshtasticState.logs.length === 0 && (
<div className="flex h-full w-full">
<m.img
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="m-auto h-64 w-64 text-green-500"
src={
appState.darkMode
? '/View_Code_Dark.svg'
: '/View_Code.svg'
}
/>
</div>
)}
{meshtasticState.logs.map((log, index) => (
<m.tr
key={index}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
className="group hover:bg-gray-300 dark:hover:bg-secondaryDark"
>
<m.td
className="w-6 cursor-pointer"
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
>
<div className="m-auto pl-2 text-white group-hover:text-black dark:text-primaryDark dark:group-hover:text-gray-400">
<FiArrowRight />
</div>
</m.td>
<Wrapper>
{log.date
.toLocaleString(undefined, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
.replaceAll('/', '-')
.replace(',', '')}
</Wrapper>
<Wrapper>
<div className={emitterLookup[log.emitter]}>
[{Types.EmitterScope[log.scope]}.
{Types.Emitter[log.emitter]}]
</div>
</Wrapper>
<Wrapper className={levelLookup[log.level]}>
[{Protobuf.LogRecord_Level[log.level]}]{/* </div> */}
</Wrapper>
<td
className={`m-auto ${
log.packet ? '' : 'dark:text-secondaryDark'
}`}
>
<FiPaperclip />
</td>
<td className="w-full truncate pl-1">{log.message}</td>
</m.tr>
// </ContextMenu>
))}
</AnimatePresence>
</tbody>
</table>
</Card>
</div>
);
};
const Wrapper = ({
className,
children,
}: {
className?: string;
children: React.ReactNode;
}): JSX.Element => (
<td className={className}>
<m.div
className="-my-0.5 flex max-w-min cursor-pointer truncate rounded-sm px-0.5 hover:bg-gray-400 dark:hover:bg-gray-700"
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
>
{children}
</m.div>
</td>
);