Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement sync with remote node #419

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
);
};