diff --git a/monkey/monkey_island/cc/ui/src/components/contexts/plugins/PluginsContext.tsx b/monkey/monkey_island/cc/ui/src/components/contexts/plugins/PluginsContext.tsx index aafb6e19fe3..4d5c368ca21 100644 --- a/monkey/monkey_island/cc/ui/src/components/contexts/plugins/PluginsContext.tsx +++ b/monkey/monkey_island/cc/ui/src/components/contexts/plugins/PluginsContext.tsx @@ -1,6 +1,8 @@ import React, {createContext, useEffect, useState} from 'react'; import semver from 'semver'; import islandHttpClient, {APIEndpoint} from '../../IslandHttpClient'; +import {useInterval} from '../../ui-components/utils/useInterval'; +import {Hour} from '../../../utils/timeConsts'; // Types returned from the API @@ -85,15 +87,22 @@ export const PluginState = () :PluginsContextType => { const [installedPlugins, setInstalledPlugins] = useState([]); const [numberOfPluginsThatRequiresUpdate, setNumberOfPluginsThatRequiresUpdate] = useState(0); + useInterval(() => { + refreshAvailablePluginsAndNumberOfUpgradablePlugins(true); + }, Hour) useEffect(() => { refreshInstalledPlugins(); }, []); useEffect(() => { - refreshAvailablePlugins().then(() => refreshNumberOfUpgradablePlugins()); + refreshAvailablePluginsAndNumberOfUpgradablePlugins(); }, [installedPlugins]); + const refreshAvailablePluginsAndNumberOfUpgradablePlugins = (forceRefresh = false) => { + refreshAvailablePlugins(forceRefresh).then(() => refreshNumberOfUpgradablePlugins()); + } + const parsePluginMetadataResponse = (response: PluginMetadataResponse) :AvailablePlugin[] => { let plugins :AvailablePlugin[] = []; for (const pluginType in response) { diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/plugins-marketplace/AvailablePlugins.tsx b/monkey/monkey_island/cc/ui/src/components/ui-components/plugins-marketplace/AvailablePlugins.tsx index 4a7d9728953..dc54de0419d 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/plugins-marketplace/AvailablePlugins.tsx +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/plugins-marketplace/AvailablePlugins.tsx @@ -84,6 +84,9 @@ const AvailablePlugins = (props) => { const filterInstalledPlugins = (row: PluginRow) => { let availablePlugin = availablePlugins.find(availablePlugin => row.id === availablePlugin.id); + if(availablePlugin === undefined) { + return true; + } return !isPluginInstalled(availablePlugin, installedPlugins) || successfullyInstalledPluginsIds.includes(row.id); } diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/utils/useInterval.tsx b/monkey/monkey_island/cc/ui/src/components/ui-components/utils/useInterval.tsx new file mode 100644 index 00000000000..f0ed9456b65 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/utils/useInterval.tsx @@ -0,0 +1,23 @@ +import {useEffect, useRef} from 'react' + +export const useInterval = (callback: () => void, delay: number | null) => { + const savedCallback = useRef(callback); + + // Remember the latest callback if it changes + useEffect(() => { + savedCallback.current = callback; + }, [callback]) + + // Set up the interval + useEffect(() => { + // Don't schedule if no delay is specified + // Note: 0 is a valid value for delay + if (!delay && delay !== 0) { + return; + } + + const id = setInterval(() => savedCallback.current(), delay); + + return () => clearInterval(id); + }, [delay]) +} diff --git a/monkey/monkey_island/cc/ui/src/utils/timeConsts.js b/monkey/monkey_island/cc/ui/src/utils/timeConsts.js new file mode 100644 index 00000000000..81e2a962166 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/utils/timeConsts.js @@ -0,0 +1,3 @@ +export const Second = 1000; +export const Minute = 60 * Second; +export const Hour = 60 * Minute;