Skip to content

Commit

Permalink
chore(meshwide): implement sync with remote node (#419)
Browse files Browse the repository at this point in the history
It implements the flow to perform publish all of a node, and get the syncronization from the node you are visiting.

On this way, when a user browses the map, it always have the updated version of the node

It depends on libremesh/lime-packages#1108
  • Loading branch information
selankon authored May 11, 2024
1 parent 30f821d commit f431351
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Trans } from "@lingui/macro";
import { StatusAndButton } from "plugins/lime-plugin-mesh-wide/src/components/Components";
import RemoteRebootBtn from "plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/RebootNodeBtn";
import { useSetReferenceState } from "plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/SetReferenceStateBtn";
import UpdateNodeInfoBtn from "plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/UpdateNodeInfoBtn";
import {
Row,
TitleAndText,
Expand Down Expand Up @@ -40,7 +41,10 @@ const NodeDetails = ({ actual, reference, name }: NodeMapFeature) => {
<div>
<Row>
<div className={"text-3xl"}>{name}</div>
<RemoteRebootBtn node={nodeToShow} />
<div className={"flex flex-row gap-4"}>
<UpdateNodeInfoBtn node={nodeToShow} />
<RemoteRebootBtn node={nodeToShow} />
</div>
</Row>
<Row>
{!isDown ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Trans } from "@lingui/macro";
import { useEffect, useState } from "preact/hooks";
import { useCallback } from "react";

import { Button } from "components/buttons/button";
import { RefreshIcon } from "components/icons/teenny/refresh";
import { useToast } from "components/toast/toastProvider";

import {
usePublishOnRemoteNode,
useSyncDataTypes,
} from "plugins/lime-plugin-mesh-wide/src/meshWideQueries";
import { getFromSharedStateKeys } from "plugins/lime-plugin-mesh-wide/src/meshWideQueriesKeys";
import {
DataTypes,
INodeInfo,
completeDataTypeKeys,
} from "plugins/lime-plugin-mesh-wide/src/meshWideTypes";

import queryCache from "utils/queryCache";

const UpdateNodeInfoBtn = ({ node }: { node: INodeInfo }) => {
const ip = node.ipv4;

const [isLoading, setIsLoading] = useState(false);
const { showToast, hideToast } = useToast();

const { mutateAsync: localNodeSync } = useSyncDataTypes({
ip,
});
const { mutateAsync: publishOnRemoteNode } = usePublishOnRemoteNode({
ip,
});

const invalidateQueries = useCallback(() => {
for (const dataType of Object.keys(
completeDataTypeKeys
) as DataTypes[]) {
queryCache.invalidateQueries({
queryKey:
getFromSharedStateKeys.getFromSharedStateAsync(dataType),
});
}
}, []);

// useCallback to sync the node data
const syncNode = useCallback(async () => {
if (isLoading) return;
setIsLoading(true);
publishOnRemoteNode({ ip })
.catch((e) => {
showToast({
text: (
<Trans>
Error connecting with {node.hostname}, is node up?
</Trans>
),
duration: 5000,
});
throw e;
})
.then(async () => {
await localNodeSync({ ip });
await invalidateQueries();
})
.finally(() => {
setIsLoading(false);
});
}, [
invalidateQueries,
ip,
isLoading,
localNodeSync,
node.hostname,
publishOnRemoteNode,
showToast,
]);

// Use effect to sync the node data on mount
useEffect(() => {
(async () => {
await syncNode();
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [node.ipv4]);

return (
<Button
color={"primary"}
outline={!isLoading}
size={"sm"}
onClick={syncNode}
>
<RefreshIcon />
</Button>
);
};

export default UpdateNodeInfoBtn;
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ export const FloatingAlert = () => {
]);

return hasErrors ? (
<div
data-testid={"has-invalid-nodes"}
onClick={callback}
className="cursor-pointer z-50 fixed top-24 right-24 my-2 mx-4 w-24 h-24 bg-gray-500 opacity-80 rounded flex justify-center items-center text-white"
>
<div className={"text-info"}>
<WarningIcon />
<div className={"absolute w-full h-full"}>
<div
data-testid={"has-invalid-nodes"}
onClick={callback}
className="cursor-pointer z-50 absolute float-right right-32 top-2 my-2 mx-4 w-24 h-24 bg-gray-500 opacity-80 rounded flex justify-center items-center text-white"
>
<div className={"text-info"}>
<WarningIcon />
</div>
</div>
</div>
) : null;
Expand Down
34 changes: 16 additions & 18 deletions plugins/lime-plugin-mesh-wide/src/containers/Map.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { t } from "@lingui/macro";
import L from "leaflet";
import { ComponentChildren } from "preact";
import { useEffect, useRef } from "preact/hooks";
import {
LayerGroup,
Expand All @@ -12,13 +12,13 @@ import {
useLoadLeaflet,
useLocation,
} from "plugins/lime-plugin-locate/src/locateQueries";
import { FloatingAlert } from "plugins/lime-plugin-mesh-wide/src/components/Map/FloatingAlert";
import {
BatmanLinksLayer,
WifiLinksLayer,
} from "plugins/lime-plugin-mesh-wide/src/containers/MapLayers/LinksLayers";
import NodesLayer from "plugins/lime-plugin-mesh-wide/src/containers/MapLayers/NodesLayer";
import { useSelectedMapFeature } from "plugins/lime-plugin-mesh-wide/src/meshWideQueries";
import { DataTypes } from "plugins/lime-plugin-mesh-wide/src/meshWideTypes";

const openStreetMapTileString = "https://{s}.tile.osm.org/{z}/{x}/{y}.png";
const openStreetMapAttribution =
Expand Down Expand Up @@ -77,6 +77,15 @@ export const MeshWideMap = ({
}
}, [loading, nodeLocation]);

const mapSupportedLayers: Record<
DataTypes,
{ name: string; layer: ComponentChildren }
> = {
node_info: { name: "Nodes", layer: <NodesLayer /> },
wifi_links_info: { name: "Wifi Links", layer: <WifiLinksLayer /> },
bat_links_info: { name: "Batman", layer: <BatmanLinksLayer /> },
};

return (
<MapContainer
center={[-30, -60]}
Expand All @@ -89,23 +98,12 @@ export const MeshWideMap = ({
attribution={openStreetMapAttribution}
url={openStreetMapTileString}
/>
<FloatingAlert />
<LayersControl position="topright">
<LayersControl.Overlay checked={nodes} name={t`Nodes`}>
<LayerGroup>
<NodesLayer />
</LayerGroup>
</LayersControl.Overlay>
<LayersControl.Overlay checked={wifiLinks} name={t`Wifi Links`}>
<LayerGroup>
<WifiLinksLayer />
</LayerGroup>
</LayersControl.Overlay>
<LayersControl.Overlay checked={batmanLinks} name={t`Batman`}>
<LayerGroup>
<BatmanLinksLayer />
</LayerGroup>
</LayersControl.Overlay>
{Object.values(mapSupportedLayers).map(({ name, layer }, k) => (
<LayersControl.Overlay key={k} checked={true} name={name}>
<LayerGroup>{layer}</LayerGroup>
</LayersControl.Overlay>
))}
</LayersControl>
</MapContainer>
);
Expand Down
44 changes: 44 additions & 0 deletions plugins/lime-plugin-mesh-wide/src/meshWideApi.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { QueryKey } from "@tanstack/react-query";

import { callToRemoteNode } from "plugins/lime-plugin-mesh-wide-upgrade/src/utils/api";
import {
getFromSharedStateKeys,
publishAllFromSharedStateAsyncKey,
} from "plugins/lime-plugin-mesh-wide/src/meshWideQueriesKeys";
import {
DataTypeMap,
DataTypes,
SharedStateReturnType,
completeDataTypeKeys,
} from "plugins/lime-plugin-mesh-wide/src/meshWideTypes";

import api from "utils/uhttpd.service";
Expand All @@ -19,3 +25,41 @@ export const doSharedStateApiCall = async <T extends DataTypes>(
}
return res.data;
};

/**
* Sync all data types from shared state from remote node
*/

interface ISyncWithIpProps {
ip: string;
}

export async function publishOnRemoteNode({ ip }: ISyncWithIpProps) {
return await callToRemoteNode({
ip,
apiCall: (customApi) =>
customApi
.call(...publishAllFromSharedStateAsyncKey, {})
.then(() => true),
});
}

export async function syncDataType({
dataType,
ip,
}: {
dataType: DataTypes;
ip: string;
}) {
const queryKey = getFromSharedStateKeys.syncFromSharedStateAsync(dataType, [
ip,
]);
return doSharedStateApiCall<typeof dataType>(queryKey);
}

export async function syncAllDataTypes({ ip }: { ip: string }) {
const promises = (Object.keys(completeDataTypeKeys) as DataTypes[]).map(
(dataType) => syncDataType({ dataType, ip })
);
return await Promise.all(promises);
}
42 changes: 40 additions & 2 deletions plugins/lime-plugin-mesh-wide/src/meshWideQueries.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { useMutation, useQuery } from "@tanstack/react-query";

import { doSharedStateApiCall } from "plugins/lime-plugin-mesh-wide/src/meshWideApi";
import {
doSharedStateApiCall,
publishOnRemoteNode,
syncAllDataTypes,
} from "plugins/lime-plugin-mesh-wide/src/meshWideApi";
import { getMeshWideConfig } from "plugins/lime-plugin-mesh-wide/src/meshWideMocks";
import { getFromSharedStateKeys } from "plugins/lime-plugin-mesh-wide/src/meshWideQueriesKeys";
import {
getFromSharedStateKeys,
publishAllFromSharedStateAsyncKey,
syncFromSharedStateAsyncKey,
} from "plugins/lime-plugin-mesh-wide/src/meshWideQueriesKeys";
import {
DataTypes,
IBatmanLinks,
Expand Down Expand Up @@ -152,6 +160,36 @@ export const useSetBatmanLinksInfoReferenceState = (params) => {
);
};

/**
* Sync all data types from shared state from remote node
* */

export const usePublishOnRemoteNode = ({
ip,
...opts
}: {
ip: string;
opts?: any;
}) => {
return useMutation(publishOnRemoteNode, {
mutationKey: [publishAllFromSharedStateAsyncKey, ip],
...opts,
});
};

export const useSyncDataTypes = ({
ip,
...opts
}: {
ip: string;
opts?: any;
}) => {
return useMutation(syncAllDataTypes, {
mutationKey: [syncFromSharedStateAsyncKey, ip],
...opts,
});
};

/**
* Set mesh wide config
*/
Expand Down
9 changes: 9 additions & 0 deletions plugins/lime-plugin-mesh-wide/src/meshWideQueriesKeys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export const getFromSharedStateMultiWriterKey = [
"getFromSharedStateMultiWriter",
];
export const getFromSharedStateAsyncKey = ["shared-state-async", "get"];
export const syncFromSharedStateAsyncKey = ["shared-state-async", "sync"];
export const publishAllFromSharedStateAsyncKey = [
"shared-state-async",
"publish_all",
];

export const insertIntoSharedStateKey = [
"shared-state",
Expand All @@ -19,6 +24,10 @@ export const getFromSharedStateKeys = {
...getFromSharedStateAsyncKey,
{ data_type: dataType },
],
syncFromSharedStateAsync: <T extends DataTypes>(
dataType: T,
peers_ip: string[]
) => [...syncFromSharedStateAsyncKey, { data_type: dataType, peers_ip }],
getFromSharedStateMultiWriter: (dataType: DataTypes) => [
...getFromSharedStateMultiWriterKey,
{ data_type: dataType },
Expand Down
7 changes: 7 additions & 0 deletions plugins/lime-plugin-mesh-wide/src/meshWideTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,10 @@ export type SharedStateReturnType<T extends SharedStateTypes> = {
data: T;
error: number;
};
// Util in order to iterate over the keys of the DataTypeMap
export type CompleteDataTypeKeys = { [K in DataTypes]: true };
export const completeDataTypeKeys: CompleteDataTypeKeys = {
node_info: true,
wifi_links_info: true,
bat_links_info: true,
};
16 changes: 16 additions & 0 deletions src/components/icons/teenny/refresh.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const RefreshIcon = () => {
return (
<svg
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
>
<path
d="M7.5 14.5A7 7 0 013.17 2M7.5.5A7 7 0 0111.83 13m-.33-3v3.5H15M0 1.5h3.5V5"
stroke="currentColor"
/>
</svg>
);
};

0 comments on commit f431351

Please sign in to comment.