diff --git a/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/NodeDetail.tsx b/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/NodeDetail.tsx index 0d81296f..06596cf9 100644 --- a/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/NodeDetail.tsx +++ b/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/NodeDetail.tsx @@ -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, @@ -40,7 +41,10 @@ const NodeDetails = ({ actual, reference, name }: NodeMapFeature) => {
{name}
- +
+ + +
{!isDown ? ( diff --git a/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/UpdateNodeInfoBtn.tsx b/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/UpdateNodeInfoBtn.tsx new file mode 100644 index 00000000..b8a8b9db --- /dev/null +++ b/plugins/lime-plugin-mesh-wide/src/components/FeatureDetail/UpdateNodeInfoBtn.tsx @@ -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: ( + + Error connecting with {node.hostname}, is node up? + + ), + 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 ( + + ); +}; + +export default UpdateNodeInfoBtn; diff --git a/plugins/lime-plugin-mesh-wide/src/components/Map/FloatingAlert.tsx b/plugins/lime-plugin-mesh-wide/src/components/Map/FloatingAlert.tsx index 30600437..33ddb97c 100644 --- a/plugins/lime-plugin-mesh-wide/src/components/Map/FloatingAlert.tsx +++ b/plugins/lime-plugin-mesh-wide/src/components/Map/FloatingAlert.tsx @@ -48,13 +48,15 @@ export const FloatingAlert = () => { ]); return hasErrors ? ( -
-
- +
+
+
+ +
) : null; diff --git a/plugins/lime-plugin-mesh-wide/src/containers/Map.tsx b/plugins/lime-plugin-mesh-wide/src/containers/Map.tsx index d261ed68..b4d9f190 100644 --- a/plugins/lime-plugin-mesh-wide/src/containers/Map.tsx +++ b/plugins/lime-plugin-mesh-wide/src/containers/Map.tsx @@ -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, @@ -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 = @@ -77,6 +77,15 @@ export const MeshWideMap = ({ } }, [loading, nodeLocation]); + const mapSupportedLayers: Record< + DataTypes, + { name: string; layer: ComponentChildren } + > = { + node_info: { name: "Nodes", layer: }, + wifi_links_info: { name: "Wifi Links", layer: }, + bat_links_info: { name: "Batman", layer: }, + }; + return ( - - - - - - - - - - - - - - - - + {Object.values(mapSupportedLayers).map(({ name, layer }, k) => ( + + {layer} + + ))} ); diff --git a/plugins/lime-plugin-mesh-wide/src/meshWideApi.ts b/plugins/lime-plugin-mesh-wide/src/meshWideApi.ts index c8470df9..682de569 100644 --- a/plugins/lime-plugin-mesh-wide/src/meshWideApi.ts +++ b/plugins/lime-plugin-mesh-wide/src/meshWideApi.ts @@ -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"; @@ -19,3 +25,41 @@ export const doSharedStateApiCall = async ( } 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(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); +} diff --git a/plugins/lime-plugin-mesh-wide/src/meshWideQueries.tsx b/plugins/lime-plugin-mesh-wide/src/meshWideQueries.tsx index 50570c3b..f1d57c9a 100644 --- a/plugins/lime-plugin-mesh-wide/src/meshWideQueries.tsx +++ b/plugins/lime-plugin-mesh-wide/src/meshWideQueries.tsx @@ -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, @@ -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 */ diff --git a/plugins/lime-plugin-mesh-wide/src/meshWideQueriesKeys.tsx b/plugins/lime-plugin-mesh-wide/src/meshWideQueriesKeys.tsx index 237afe23..dc8138fc 100644 --- a/plugins/lime-plugin-mesh-wide/src/meshWideQueriesKeys.tsx +++ b/plugins/lime-plugin-mesh-wide/src/meshWideQueriesKeys.tsx @@ -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", @@ -19,6 +24,10 @@ export const getFromSharedStateKeys = { ...getFromSharedStateAsyncKey, { data_type: dataType }, ], + syncFromSharedStateAsync: ( + dataType: T, + peers_ip: string[] + ) => [...syncFromSharedStateAsyncKey, { data_type: dataType, peers_ip }], getFromSharedStateMultiWriter: (dataType: DataTypes) => [ ...getFromSharedStateMultiWriterKey, { data_type: dataType }, diff --git a/plugins/lime-plugin-mesh-wide/src/meshWideTypes.tsx b/plugins/lime-plugin-mesh-wide/src/meshWideTypes.tsx index a73fcd17..014bb795 100644 --- a/plugins/lime-plugin-mesh-wide/src/meshWideTypes.tsx +++ b/plugins/lime-plugin-mesh-wide/src/meshWideTypes.tsx @@ -198,3 +198,10 @@ export type SharedStateReturnType = { 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, +}; diff --git a/src/components/icons/teenny/refresh.jsx b/src/components/icons/teenny/refresh.jsx new file mode 100644 index 00000000..2b443694 --- /dev/null +++ b/src/components/icons/teenny/refresh.jsx @@ -0,0 +1,16 @@ +export const RefreshIcon = () => { + return ( + + + + ); +};