From d839600db298b944215756dd0a23e4db12ef73c1 Mon Sep 17 00:00:00 2001 From: jgresham Date: Mon, 6 Nov 2023 13:31:29 -0800 Subject: [PATCH] feat: required podman update in add node flow --- assets/locales/en/translation.json | 1 + src/main/ipc.ts | 4 ++ src/main/podman/details.ts | 43 +++++++++++++++++ src/main/podman/install/install.ts | 2 +- src/main/podman/update.ts | 1 + src/main/preload.ts | 2 + .../AddNodeStepper/AddNodeStepperModal.tsx | 6 +-- .../AddNodeStepper/podmanRequirements.ts | 11 +++++ .../ModalManager/AddNodeModal.tsx | 24 ++++++---- .../PodmanInstallation/PodmanInstallation.tsx | 46 +++++++++++++++++-- src/renderer/preload.d.ts | 3 ++ src/renderer/state/settingsService.ts | 15 ++++++ 12 files changed, 142 insertions(+), 16 deletions(-) create mode 100644 src/main/podman/details.ts create mode 100644 src/renderer/Presentational/AddNodeStepper/podmanRequirements.ts diff --git a/assets/locales/en/translation.json b/assets/locales/en/translation.json index 268cc17ce..dcae4b5ed 100644 --- a/assets/locales/en/translation.json +++ b/assets/locales/en/translation.json @@ -53,6 +53,7 @@ "ChooseYourNetwork": "Choose your network", "PodmanInstallation": "Podman installation", "LearnMorePodman": "Learn more about Podman", + "PodmanUpdateRequiredDescription": "An update to Podman is required. Please download and install the update to continue. Note that any running nodes will be temporarily stopped.", "DownloadAndInstall": "Download and Install", "StartPodman": "Start Podman", "StartNode": "Start node", diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 24a43be8c..ceb63b847 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -72,6 +72,8 @@ import { import { checkPorts } from './ports'; import { getAppClientId } from './state/eventReporting'; import { onUserChangedLanguage } from './i18nMain'; +import { getPodmanDetails } from './podman/details'; +import { updatePodman } from './podman/update'; // eslint-disable-next-line import/prefer-default-export export const initialize = () => { @@ -198,7 +200,9 @@ export const initialize = () => { ipcMain.handle('getIsPodmanInstalled', isPodmanInstalled); ipcMain.handle('installPodman', installPodman); ipcMain.handle('getIsPodmanRunning', isPodmanRunning); + ipcMain.handle('getPodmanDetails', getPodmanDetails); ipcMain.handle('startPodman', startPodman); + ipcMain.handle('updatePodman', updatePodman); // Settings ipcMain.handle('getSetHasSeenSplashscreen', (_event, hasSeen?: boolean) => { diff --git a/src/main/podman/details.ts b/src/main/podman/details.ts new file mode 100644 index 000000000..b50a4ee0b --- /dev/null +++ b/src/main/podman/details.ts @@ -0,0 +1,43 @@ +import { + PODMAN_LATEST_VERSION, + PODMAN_MIN_VERSION, + getInstalledPodmanVersion, +} from './install/install'; +import { isPodmanInstalled, isPodmanRunning } from './podman'; + +export type PodmanDetails = { + isInstalled: boolean; + isRunning?: boolean; + installedVersion?: string; + minimumVersionRequired?: string; + latestVersionAvailable?: string; + isOutdated?: boolean; // user must update podman + isOptionalUpdateAvailable?: boolean; +}; + +export const getPodmanDetails = async (): Promise => { + const isInstalled = await isPodmanInstalled(); + const isRunning = await isPodmanRunning(); + const installedVersion = await getInstalledPodmanVersion(); + let isOutdated; + if (installedVersion) { + isOutdated = installedVersion < PODMAN_MIN_VERSION; + } + let isOptionalUpdateAvailable; + // if outdated, leave as undefined because a required update takes precedent + if (!isOutdated && installedVersion) { + isOptionalUpdateAvailable = installedVersion < PODMAN_LATEST_VERSION; + } + + const details: PodmanDetails = { + isInstalled, + isRunning, + installedVersion, + minimumVersionRequired: PODMAN_MIN_VERSION, + latestVersionAvailable: PODMAN_LATEST_VERSION, + isOutdated, + isOptionalUpdateAvailable, + }; + + return details; +}; diff --git a/src/main/podman/install/install.ts b/src/main/podman/install/install.ts index 3ea9bab42..7a17dbc0a 100644 --- a/src/main/podman/install/install.ts +++ b/src/main/podman/install/install.ts @@ -6,7 +6,7 @@ import installOnLinux from './installOnLinux'; import { runCommand } from '../podman'; export const PODMAN_LATEST_VERSION = '4.7.2'; -export const PODMAN_MIN_VERSION = '4.6.0'; +export const PODMAN_MIN_VERSION = '4.5.0'; // eslint-disable-next-line const installPodman = async (): Promise => { diff --git a/src/main/podman/update.ts b/src/main/podman/update.ts index 8e8805ad0..96186d800 100644 --- a/src/main/podman/update.ts +++ b/src/main/podman/update.ts @@ -14,6 +14,7 @@ import { removeNiceNodeMachine } from './machine'; /** * Stops all nodes, downloads and installs new podman version, * restarts all the nodes that were previously running prior to the update. + * todo: send messages to the front-end updating the status of the update */ export const updatePodman = async () => { logger.info('updatePodman...'); diff --git a/src/main/preload.ts b/src/main/preload.ts index fdec13966..ee9b9784f 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -119,7 +119,9 @@ contextBridge.exposeInMainWorld('electron', { getIsPodmanInstalled: () => ipcRenderer.invoke('getIsPodmanInstalled'), installPodman: () => ipcRenderer.invoke('installPodman'), getIsPodmanRunning: () => ipcRenderer.invoke('getIsPodmanRunning'), + getPodmanDetails: () => ipcRenderer.invoke('getPodmanDetails'), startPodman: () => ipcRenderer.invoke('startPodman'), + updatePodman: () => ipcRenderer.invoke('updatePodman'), // Settings getSetHasSeenSplashscreen: (hasSeen?: boolean) => diff --git a/src/renderer/Presentational/AddNodeStepper/AddNodeStepperModal.tsx b/src/renderer/Presentational/AddNodeStepper/AddNodeStepperModal.tsx index 26938f65d..87f2ee009 100644 --- a/src/renderer/Presentational/AddNodeStepper/AddNodeStepperModal.tsx +++ b/src/renderer/Presentational/AddNodeStepper/AddNodeStepperModal.tsx @@ -26,7 +26,7 @@ export interface AddNodeStepperModalProps { nodePackageLibrary?: NodePackageLibrary; step: number; disableSaveButton: (value: boolean) => void; - setIsPodmanRunning: (value: boolean) => void; + setPodmanInstallDone: (value: boolean) => void; } const AddNodeStepperModal = ({ @@ -37,7 +37,7 @@ const AddNodeStepperModal = ({ nodePackageLibrary, step, disableSaveButton, - setIsPodmanRunning, + setPodmanInstallDone, }: AddNodeStepperModalProps) => { const [sNodeConfig, setNodeConfig] = useState(); const [sEthereumNodeConfig, setEthereumNodeConfig] = @@ -119,7 +119,7 @@ const AddNodeStepperModal = ({ console.log('onChangeDockerInstall newValue ', newValue); disableSaveButton(false); if (newValue === 'done') { - setIsPodmanRunning(true); + setPodmanInstallDone(true); disableSaveButton(false); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/renderer/Presentational/AddNodeStepper/podmanRequirements.ts b/src/renderer/Presentational/AddNodeStepper/podmanRequirements.ts new file mode 100644 index 000000000..6cda8d835 --- /dev/null +++ b/src/renderer/Presentational/AddNodeStepper/podmanRequirements.ts @@ -0,0 +1,11 @@ +import { PodmanDetails } from '../../../main/podman/details'; + +export const arePodmanRequirementsMet = ( + podmanDetails: PodmanDetails, +): boolean => { + let isPodmanRequirementsMet = false; + if (podmanDetails?.isRunning && !podmanDetails.isOutdated) { + isPodmanRequirementsMet = true; + } + return isPodmanRequirementsMet; +}; diff --git a/src/renderer/Presentational/ModalManager/AddNodeModal.tsx b/src/renderer/Presentational/ModalManager/AddNodeModal.tsx index 585f6597d..d4efd1cee 100644 --- a/src/renderer/Presentational/ModalManager/AddNodeModal.tsx +++ b/src/renderer/Presentational/ModalManager/AddNodeModal.tsx @@ -6,7 +6,7 @@ import { updateSelectedNodePackageId } from '../../state/node'; import AddNodeStepperModal from '../AddNodeStepper/AddNodeStepperModal'; import { Modal } from '../../Generics/redesign/Modal/Modal'; import { modalOnChangeConfig, ModalConfig } from './modalUtils'; -import { useGetIsPodmanRunningQuery } from '../../state/settingsService'; +import { useGetPodmanDetailsQuery } from '../../state/settingsService'; import { reportEvent } from '../../events/reportEvent'; import { NodePackageSpecification } from '../../../common/nodeSpec'; import { AddNodePackageNodeService } from '../../../main/nodePackageManager'; @@ -15,6 +15,7 @@ import { NodePackageLibrary, } from '../../../main/state/nodeLibrary'; import { mergePackageAndClientConfigValues } from '../AddNodeConfiguration/mergePackageAndClientConfigValues'; +import { arePodmanRequirementsMet } from '../AddNodeStepper/podmanRequirements'; type Props = { modalOnClose: () => void; @@ -26,7 +27,6 @@ export const AddNodeModal = ({ modalOnClose }: Props) => { useState(); const [isSaveButtonDisabled, setIsSaveButtonDisabled] = useState(false); - const [sIsPodmanRunning, setIsPodmanRunning] = useState(false); const [step, setStep] = useState(0); const [sNodeLibrary, setNodeLibrary] = useState(); const [sNodePackageLibrary, setNodePackageLibrary] = @@ -47,10 +47,10 @@ export const AddNodeModal = ({ modalOnClose }: Props) => { fetchNodeLibrarys(); }, []); - const qIsPodmanRunning = useGetIsPodmanRunningQuery(null, { + const qPodmanDetails = useGetPodmanDetailsQuery(null, { pollingInterval: 15000, }); - const isPodmanRunning = qIsPodmanRunning?.data; + const podmanDetails = qPodmanDetails?.data; const dispatch = useAppDispatch(); @@ -88,8 +88,8 @@ export const AddNodeModal = ({ modalOnClose }: Props) => { default: } - const startNode = - (step === 2 || step === 3) && (isPodmanRunning || sIsPodmanRunning); + const isPodmanRequirementsMet = arePodmanRequirementsMet(podmanDetails); + const startNode = (step === 2 || step === 3) && isPodmanRequirementsMet; const buttonSaveLabel = startNode ? t('StartNode') : t('Continue'); const buttonCancelLabel = step === 0 ? t('Cancel') : t('Back'); const buttonSaveVariant = startNode ? 'icon-left' : 'text'; @@ -203,7 +203,8 @@ export const AddNodeModal = ({ modalOnClose }: Props) => { setStep(2); // optionally advance to Podman screen (install or start) } else if (step === 2) { - if (isPodmanRunning || sIsPodmanRunning) { + const isPodmanRequirementsMet = arePodmanRequirementsMet(podmanDetails); + if (isPodmanRequirementsMet) { modalOnSaveConfig(undefined); modalOnClose(); } else { @@ -216,6 +217,13 @@ export const AddNodeModal = ({ modalOnClose }: Props) => { } }; + const onSetPodmanInstallDone = () => { + // refetch podman details to verify podman is installed + // which will allow the user to continue. + // This is faster than waiting for the qPodmanDetails polling + qPodmanDetails?.refetch(); + }; + return ( { step={step} nodeLibrary={sNodeLibrary} nodePackageLibrary={sNodePackageLibrary} - setIsPodmanRunning={setIsPodmanRunning} + setPodmanInstallDone={onSetPodmanInstallDone} modal modalConfig={modalConfig} modalOnChangeConfig={(config, save) => { diff --git a/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx b/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx index 1d0e03e68..d067af44d 100644 --- a/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx +++ b/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx @@ -24,10 +24,12 @@ import TimedProgressBar from '../../Generics/redesign/ProgressBar/TimedProgressB import { useGetIsPodmanInstalledQuery, useGetIsPodmanRunningQuery, + useGetPodmanDetailsQuery, } from '../../state/settingsService'; // import { reportEvent } from '../../events/reportEvent'; import { CHANNELS } from '../../../main/messenger'; import { IpcMessage } from '../../../main/podman/messageFrontEnd'; +import { Message } from '../../Generics/redesign/Message/Message'; // 6.5(docker), ? min on 2022 MacbookPro 16inch, baseline const TOTAL_INSTALL_TIME_SEC = 5 * 60; @@ -52,6 +54,10 @@ const PodmanInstallation = ({ pollingInterval: 15000, }); const isPodmanRunning = qIsPodmanRunning?.data; + const qPodmanDetails = useGetPodmanDetailsQuery(null, { + pollingInterval: 15000, + }); + const podmanDetails = qPodmanDetails?.data; const [sHasStartedDownload, setHasStartedDownload] = useState(); const [sDownloadComplete, setDownloadComplete] = useState(); const [sDownloadProgress, setDownloadProgress] = useState(0); @@ -73,10 +79,10 @@ const PodmanInstallation = ({ console.log('isPodmanRunning: ', isPodmanRunning); useEffect(() => { - if (isPodmanRunning) { + if (isPodmanRunning && !podmanDetails?.isOutdated) { onChange('done'); } - }, [isPodmanRunning, onChange]); + }, [isPodmanRunning, podmanDetails, onChange]); const onClickDownloadAndInstall = async () => { setHasStartedDownload(true); @@ -102,6 +108,22 @@ const PodmanInstallation = ({ console.log('startPodman finished. Start result: ', startResult); }; + const onClickUpdatePodman = async () => { + setHasStartedDownload(true); + if (navigator.userAgent.indexOf('Linux') !== -1) { + setDownloadComplete(true); + } + const updateResult = await electron.updatePodman(); + // todo: consolodate these to just use qPodmanDetails + qIsPodmanInstalled.refetch(); + qIsPodmanRunning.refetch(); + qPodmanDetails.refetch(); + if (updateResult && !updateResult.error) { + setInstallComplete(true); + } + console.log('updatePodman finished. Start result: ', updateResult); + }; + const podmanMessageListener = (message: FileDownloadProgress[]) => { // set totalSize & progress if (message[0]) { @@ -167,6 +189,7 @@ const PodmanInstallation = ({ // }, []); console.log('isPodmanInstalled', isPodmanInstalled); + console.log('podmanDetails', podmanDetails); // listen to podman install messages return ( @@ -182,7 +205,16 @@ const PodmanInstallation = ({ {/* Podman is not installed */}
- {!isPodmanInstalled && ( + {podmanDetails?.isOutdated && ( +
+ +
+ )} + {(!isPodmanInstalled || podmanDetails?.isOutdated) && ( <> {!sDownloadComplete && !sInstallComplete && ( <> @@ -191,7 +223,13 @@ const PodmanInstallation = ({
diff --git a/src/renderer/preload.d.ts b/src/renderer/preload.d.ts index 2500e9306..b2ebd1c3b 100644 --- a/src/renderer/preload.d.ts +++ b/src/renderer/preload.d.ts @@ -16,6 +16,7 @@ import { CheckStorageDetails } from '../main/files'; import { FailSystemRequirementsData } from '../main/minSystemRequirement'; import { SystemData } from '../main/systemInfo'; import { ConfigValuesMap } from '../common/nodeConfig'; +import { PodmanDetails } from '../main/podman/details'; // Since we are using Chrome only in Electron and this is not a web standard yet, // we extend window.performance to include Chrome's memory stats @@ -101,7 +102,9 @@ declare global { getIsPodmanInstalled(): boolean; installPodman(): any; getIsPodmanRunning(): true; + getPodmanDetails(): PodmanDetails; startPodman(): any; + updatePodman(): any; // Settings getSetHasSeenSplashscreen(hasSeen?: boolean): boolean; diff --git a/src/renderer/state/settingsService.ts b/src/renderer/state/settingsService.ts index 0eaa7eda9..9c42ec061 100644 --- a/src/renderer/state/settingsService.ts +++ b/src/renderer/state/settingsService.ts @@ -2,6 +2,7 @@ import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react'; import { Settings } from '../../main/state/settings'; import electron from '../electronGlobal'; +import { PodmanDetails } from '../../main/podman/details'; type CustomerErrorType = { message: string; @@ -55,6 +56,19 @@ export const RtkqSettingsService: any = createApi({ return { data }; }, }), + getPodmanDetails: builder.query({ + queryFn: async () => { + let data; + try { + data = await electron.getPodmanDetails(); + } catch (e) { + const error = { message: 'Unable to getPodmanDetails' }; + console.log(e); + return { error }; + } + return { data }; + }, + }), }), }); @@ -62,4 +76,5 @@ export const { useGetSettingsQuery, useGetIsPodmanInstalledQuery, useGetIsPodmanRunningQuery, + useGetPodmanDetailsQuery, } = RtkqSettingsService;