Initial 1.3.x rework
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
Reference in New Issue
Block a user