Skip to content

Commit

Permalink
Implement mesh wide map (#416)
Browse files Browse the repository at this point in the history
Implements mesh wide map. Fixes #394. It implements bunch of new features such:

- [x] It show geolocated nodes on the map, using shared stated nodes package
- [x] Show bat links, and wifi links between this nodes, using shared state bat links and wifi inks data types
- [x] Monitor the state of the nodes and the links
- [x] You can mark the actual state of the shared state data type as the reference state to monitor changes
- [x] It inform you of errors on the shared state, such non located nodes, missing shared states packages or the reference state is not set
- [x] You can switch between layers to see.
  • Loading branch information
selankon authored May 7, 2024
1 parent 6d4ab7c commit d9ece66
Show file tree
Hide file tree
Showing 78 changed files with 11,955 additions and 1,784 deletions.
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint @typescript-eslint/no-var-requires: "off" */
const preactPreset = require("jest-preset-preact");

const esModules = ["@react-leaflet", "react-leaflet"].join("|");

/** @returns {Promise<import('jest').Config>} */
module.exports = {
preset: "jest-preset-preact",
Expand All @@ -22,4 +24,5 @@ module.exports = {
"^.+\\.[tj]sx?$": "babel-jest",
},
clearMocks: true,
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
};
7,978 changes: 6,273 additions & 1,705 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,12 @@
"react-leaflet": "^4.2.1",
"react-redux": "^8.0.4",
"react-router-redux": "^4.0.8",
"react-spring": "^9.7.1",
"react-use": "^17.4.0",
"redux": "^4.2.0",
"redux-observable": "^2.0.0",
"simple-color-scale": "^1.0.1",
"tailwindcss": "^3.2.6",
"timeago.js": "^4.0.2"
},
"husky": {
Expand Down
Empty file modified plugins/lime-plugin-locate/index.ts
100755 → 100644
Empty file.
6 changes: 3 additions & 3 deletions plugins/lime-plugin-locate/src/locateMenu.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Trans } from "@lingui/macro";

import { MapIcon } from "components/icons/teenny/map";
import { PinIcon } from "components/icons/teenny/pin";

export const LocateMenu = () => (
<span>
<MapIcon />
<PinIcon />
<a href={"#/locate"}>
<Trans>Map</Trans>
<Trans>Locate</Trans>
</a>
</span>
);
10 changes: 10 additions & 0 deletions plugins/lime-plugin-mesh-wide/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { MeshWideMenu } from "./src/meshWideMenu";
import MeshWidePage from "./src/meshWidePage";
import MeshWideConfigPage from "./src/screens/configPage";

export default {
name: "MeshWide",
page: MeshWidePage,
menu: MeshWideMenu,
additionalRoutes: [["/meshwide/config", MeshWideConfigPage]],
} as LimePlugin;
58 changes: 58 additions & 0 deletions plugins/lime-plugin-mesh-wide/src/components/Components.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { VNode } from "preact";

import { Button } from "components/buttons/button";
import { BinIcon } from "components/icons/bin";
import { EditIcon } from "components/icons/edit";

import SuccessIcon from "plugins/lime-plugin-mesh-wide/src/icons/SuccessIcon";
import ErrorIcon from "plugins/lime-plugin-mesh-wide/src/icons/errorIcon";

interface IStatusMessage {
isError: boolean;
children: VNode | string;
}

export const StatusAndButton = ({
isError,
children,
btn,
onClick,
}: { btn?: VNode | string; onClick?: () => void } & IStatusMessage) => {
const containerClasses =
"flex flex-col items-center justify-center text-center bg-white py-5 gap-3";

return (
<div className={containerClasses}>
<StatusMessage isError={isError}>{children}</StatusMessage>
{btn && <Button onClick={onClick}>{btn}</Button>}
</div>
);
};

export const StatusMessage = ({
isError,
children,
classes,
}: {
classes?: string;
} & IStatusMessage) => (
<div
className={`flex flex-row gap-3 ${classes} items-center justify-center text-center`}
>
{isError ? <ErrorIcon /> : <SuccessIcon />}
{children}
</div>
);

export const EditOrDelete = ({
onEdit,
onDelete,
}: {
onEdit: (e) => void;
onDelete: (e) => void;
}) => (
<div className={"flex flex-row gap-3"}>
<EditIcon className={"cursor-pointer"} onClick={onEdit} />
<BinIcon className={"cursor-pointer"} onClick={onDelete} />
</div>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
import { Trans } from "@lingui/macro";
import { useState } from "preact/hooks";

import Tabs from "components/tabs";

import { StatusAndButton } from "plugins/lime-plugin-mesh-wide/src/components/Components";
import { useSetReferenceState } from "plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/SetReferenceStateBtn";
import {
getQueryByLinkType,
usePointToPointErrors,
} from "plugins/lime-plugin-mesh-wide/src/hooks/useLocatedLinks";
import ErrorIcon from "plugins/lime-plugin-mesh-wide/src/icons/errorIcon";
import { MacToMacLink } from "plugins/lime-plugin-mesh-wide/src/lib/links/PointToPointLink";
import { readableBytes } from "plugins/lime-plugin-mesh-wide/src/lib/utils";
import {
BaseMacToMacLink,
BatmanLinkErrorCodes,
IBatManLinkData,
ILinkMtoMErrors,
IWifiLinkData,
LinkMapFeature,
WifiLinkErrorCodes,
} from "plugins/lime-plugin-mesh-wide/src/meshWideTypes";

import { isEmpty } from "utils/utils";

import { Row, TitleAndText } from "./index";

const BatmanDetail = ({
name,
errorsArray,
node,
}: {
name: string;
errorsArray: BatmanLinkErrorCodes[] | undefined;
node: IBatManLinkData;
}) => {
return (
<>
<Row>
<div className={"flex"}>
<strong>{name}</strong>{" "}
{errorsArray?.length > 0 && <ErrorIcon />}
</div>
</Row>
<Row>
<TitleAndText title={<Trans>Iface</Trans>}>
{node?.iface}
</TitleAndText>
<TitleAndText title={<Trans>Last seen</Trans>}>
<>{node?.last_seen_msecs} ms</>
</TitleAndText>
</Row>
</>
);
};

const WifiDetail = ({
name,
errorsArray,
node,
}: {
name: string;
errorsArray: WifiLinkErrorCodes[];
node: IWifiLinkData;
}) => {
return (
<div>
<Row>
<div className={"flex"}>
<strong>{name}</strong>{" "}
{errorsArray?.length > 0 && <ErrorIcon />}
</div>
</Row>
<Row>
<TitleAndText
title={<Trans>Signal</Trans>}
error={
errorsArray?.includes(
WifiLinkErrorCodes.SIGNAL_LOSS
) ? (
<Trans>
The signal is X below the reference state
</Trans>
) : null
}
>
{node?.signal?.toString() ?? "0"}
</TitleAndText>
<TitleAndText
title={<Trans>Chains</Trans>}
error={
errorsArray?.includes(WifiLinkErrorCodes.CHAIN_LOSS) ? (
<Trans>
The difference between chains is too big
</Trans>
) : null
}
>
{node?.chains?.toString() ?? "0/0"}
</TitleAndText>
</Row>
<Row>
<TitleAndText title={<Trans>TxRate</Trans>}>
{`${readableBytes(node.tx_rate)}`}
</TitleAndText>
<TitleAndText title={<Trans>RxRate</Trans>}>
{`${readableBytes(node.rx_rate)}`}
</TitleAndText>
</Row>
</div>
);
};

const SelectedLink = ({
linkDetail,
errors,
}: {
linkDetail: BaseMacToMacLink;
errors: ILinkMtoMErrors | undefined;
}) => {
if (linkDetail === undefined || (errors && !errors?.linkUp))
return (
<div>
<Trans>This link seems down</Trans>
</div>
);

const names = linkDetail?.names;
const linkType = linkDetail.type;

return (
<>
<Row>
{names && (
<div className={"text-3xl"}>
<Trans>
Link from <strong>{names[0]}</strong> to{" "}
<strong>{names[1]}</strong>
</Trans>
</div>
)}
</Row>
{names.map((name, i) => {
const node = linkDetail.linkByName(name);
const errorsArray = errors?.linkErrors[name] ?? [];
return linkType === "wifi_links_info" ? (
<WifiDetail
key={i}
name={name}
errorsArray={errorsArray as WifiLinkErrorCodes[]}
node={node as IWifiLinkData}
/>
) : (
<BatmanDetail
key={i}
name={name}
errorsArray={errorsArray as BatmanLinkErrorCodes[]}
node={node as IBatManLinkData}
/>
);
})}
</>
);
};

const LinkFeatureDetail = ({ actual, reference }: LinkMapFeature) => {
const linkToShow = reference ?? actual;
const [selectedLink, setSelectedLink] = useState(0);
const { errors } = usePointToPointErrors({
id: linkToShow.id,
type: linkToShow.type,
});
const linkType = linkToShow.type;

const tabs = linkToShow.links.map(
(link: MacToMacLink<typeof linkType>, i) => {
return {
key: i,
repr: (
<div className={"flex"}>
<Trans>
Link {i + 1}{" "}
{errors &&
errors?.macToMacErrors[link.id]?.hasErrors ? (
<ErrorIcon />
) : null}
</Trans>
</div>
),
};
}
);

return (
<div className="d-flex flex-column flex-grow-1 overflow-auto gap-6">
{tabs?.length > 1 && (
<Tabs
tabs={tabs}
current={selectedLink}
onChange={setSelectedLink}
/>
)}
{selectedLink !== null && (
<SelectedLink
linkDetail={actual?.links[selectedLink]}
errors={
errors?.macToMacErrors[actual?.links[selectedLink]?.id]
}
/>
)}
</div>
);
};

export const LinkReferenceStatus = ({ reference }: LinkMapFeature) => {
const isNewNode = !reference;

const { errors } = usePointToPointErrors({
id: reference.id,
type: reference.type,
});

// Check if there are errors of global reference state to shown
const { reference: fetchDataReference } = getQueryByLinkType(
reference.type
);
const { data: referenceData, isError: isReferenceError } =
fetchDataReference({});
let referenceError = false;
if (!referenceData || isEmpty(referenceData) || isReferenceError) {
referenceError = true;
}

// Mutation to update the reference state
const { mutate, btnText } = useSetReferenceState(reference.type);

let errorMessage = <Trans>Same status as in the reference state</Trans>;
if (referenceError) {
errorMessage = <Trans>Reference is not set or has errors</Trans>;
} else if (errors?.hasErrors) {
errorMessage = <Trans>This link has errors</Trans>;
} else if (isNewNode) {
errorMessage = (
<Trans>This Link is not registered on the reference state</Trans>
);
}

const hasError = errors?.hasErrors || referenceError || isNewNode;

return (
<StatusAndButton
isError={hasError}
btn={hasError && btnText}
onClick={mutate}
>
{errorMessage}
</StatusAndButton>
);
};

export default LinkFeatureDetail;
Loading

0 comments on commit d9ece66

Please sign in to comment.