From e88d11132a0726c3bbfc72389adcbdcf9791d8c1 Mon Sep 17 00:00:00 2001 From: myarmolinsky Date: Wed, 5 Mar 2025 09:12:11 -0500 Subject: [PATCH] confirmation dialog Change-type: patch --- .../DownloadImageDialog/ImageForm.tsx | 778 ++++++++++-------- 1 file changed, 431 insertions(+), 347 deletions(-) diff --git a/src/components/DownloadImageDialog/ImageForm.tsx b/src/components/DownloadImageDialog/ImageForm.tsx index 582dc3e0..f155ec5a 100644 --- a/src/components/DownloadImageDialog/ImageForm.tsx +++ b/src/components/DownloadImageDialog/ImageForm.tsx @@ -21,6 +21,11 @@ import { Accordion, AccordionSummary, AccordionDetails, + Switch, + Dialog, + DialogTitle, + DialogContent, + DialogActions, } from '@mui/material'; import HelpIcon from '@mui/icons-material/Help'; import { memo, useCallback, useEffect, useMemo, useState } from 'react'; @@ -43,6 +48,8 @@ import { } from '@fortawesome/free-solid-svg-icons'; import * as semver from 'balena-semver'; import { Callout } from '../Callout'; +import { ButtonWithTracking } from '../ButtonWithTracking'; +import { getFromLocalStorage, setToLocalStorage } from '../../utils/storage'; const POLL_INTERVAL_DOCS = 'https://www.balena.io/docs/reference/supervisor/bandwidth-reduction/#side-effects--warnings'; @@ -114,6 +121,12 @@ export const ImageForm = memo(function ImageForm({ const theme = useTheme(); const [showAdvancedSettings, setShowAdvancedSettings] = useState(false); + const [ + showSecureBootConfirmationDialog, + setShowSecureBootConfirmationDialog, + ] = useState(false); + const [dontShowSecureBootWarningAgain, setDontShowSecureBootWarningAgain] = + useState(false); const [showPassword, setShowPassword] = useState(false); const [version, setVersion] = useState(); const [variant, setVariant] = useState('prod'); @@ -141,6 +154,11 @@ export const ImageForm = memo(function ImageForm({ ); }, [model.deviceType.slug, model.version]); + const secureBootDontShowAgainKey = useMemo( + () => `${model.deviceType.slug}_secureboot_warning_do_not_show_again`, + [model.deviceType.slug], + ); + const handleVariantChange = useCallback( (v: typeof variant) => { setVariant(v); @@ -232,272 +250,276 @@ export const ImageForm = memo(function ImageForm({ ); return ( - - - - - - - - {compatibleDeviceTypes && compatibleDeviceTypes.length > 1 && ( - - - Device type{' '} - - - - - option.name} - renderOption={(props, option) => ( - - + + + + + + + + {compatibleDeviceTypes && compatibleDeviceTypes.length > 1 && ( + + + Device type{' '} + + - {option.name} - - )} - renderInput={({ InputProps, ...params }) => ( - - ), - }} - /> - )} - onChange={(_event, value) => { - if (!value) { - return; + + + option.name} + renderOption={(props, option) => ( + + + {option.name} + + )} + renderInput={({ InputProps, ...params }) => ( + + ), + }} + /> + )} + onChange={(_event, value) => { + if (!value) { + return; + } + handleSelectedDeviceTypeChange(value); + }} + disableClearable + // TODO: consider whether there is a better solution than letting the width vary as you search + componentsProps={{ + popper: { sx: { width: 'fit-content' } }, + }} + /> + + )} + {(!isInitialDefault || osType) && + hasEsrVersions && + model.deviceType && ( + + )} + + {!isInitialDefault && version && ( + + + + Select version + + option.value} + isOptionEqualToValue={(option, value) => + option.value === value.value } - handleSelectedDeviceTypeChange(value); - }} - disableClearable - // TODO: consider whether there is a better solution than letting the width vary as you search - componentsProps={{ - popper: { sx: { width: 'fit-content' } }, + options={versionSelectionOpts} + onChange={(_event, ver) => { + handleVersionChange(ver); + }} + placeholder="Choose a version..." + renderOption={(props, option) => ( + + + + )} + renderInput={({ InputProps, ...params }) => ( + + {version.value === recommendedVersion && ( + + )} + {!!version?.knownIssueList && ( + + + + )} + {InputProps.endAdornment} + + ), + }} + /> + )} + disableClearable + /> + + {showAllVersionsToggle && ( + + + } + label="Show outdated versions" + /> + + )} + + )} + + {(!isInitialDefault || !variant) && ( + + { + handleVariantChange(v ? 'dev' : 'prod'); }} /> - + )} - {(!isInitialDefault || osType) && - hasEsrVersions && - model.deviceType && ( - - )} - - {!isInitialDefault && version && ( - - - - Select version - - option.value} - isOptionEqualToValue={(option, value) => - option.value === value.value - } - options={versionSelectionOpts} - onChange={(_event, ver) => { - handleVersionChange(ver); + + + + + Network + + { + onChange('network', event.target.value); }} - placeholder="Choose a version..." - renderOption={(props, option) => ( - - - - )} - renderInput={({ InputProps, ...params }) => ( - - {version.value === recommendedVersion && ( - - )} - {!!version?.knownIssueList && ( - - - - )} - {InputProps.endAdornment} - - ), - }} - /> - )} - disableClearable - /> - - {showAllVersionsToggle && ( - - } - label="Show outdated versions" + value="ethernet" + control={} + label="Ethernet only" + /> + } + label="Wifi + Ethernet" /> - + + + {model.network === 'wifi' && ( + <> + + WiFi SSID + + { + onChange('wifiSsid', event.target.value); + }} + /> + + Wifi Passphrase + + + { + setShowPassword((show) => !show); + }} + onMouseDown={( + event: React.MouseEvent, + ) => { + event.preventDefault(); + }} + edge="end" + > + {showPassword ? : } + + + ), + }} + onChange={(event) => { + onChange('wifiKey', event.target.value); + }} + /> + )} - - )} - - {(!isInitialDefault || !variant) && ( - - { - handleVariantChange(v ? 'dev' : 'prod'); - }} - /> - - )} - - - - - Network - - { - onChange('network', event.target.value); - }} - > - } - label="Ethernet only" - /> - } - label="Wifi + Ethernet" - /> - - - {model.network === 'wifi' && ( + + {supportsSecureBoot && ( <> - - WiFi SSID - - { - onChange('wifiSsid', event.target.value); - }} + - - Wifi Passphrase - - - { - setShowPassword((show) => !show); - }} - onMouseDown={( - event: React.MouseEvent, - ) => { - event.preventDefault(); - }} - edge="end" - > - {showPassword ? : } - - - ), - }} - onChange={(event) => { - onChange('wifiKey', event.target.value); - }} - /> - - )} - - {supportsSecureBoot && ( - <> - - @@ -506,10 +528,20 @@ export const ImageForm = memo(function ImageForm({ { + if ( + !model.secureboot && + !getFromLocalStorage(secureBootDontShowAgainKey) + ) { + event.preventDefault(); + setShowSecureBootConfirmationDialog(true); + } + }} onChange={(event) => { onChange('secureboot', event.target.checked); }} + checked={model.secureboot} /> } label={ @@ -532,108 +564,160 @@ export const ImageForm = memo(function ImageForm({ } /> - {model.secureboot && ( - - Enabling Secure Boot and Full Disk Encryption will have an - impact on data, kernel modules, debugging, and more. Make sure - you understand the implications and thoroughly test it on your - hardware.{' '} - {/* TODO: replace with the secure boot learn more link */} - - Learn more. - - - )} - - - )} - - { - setShowAdvancedSettings(!showAdvancedSettings); - }} - sx={{ - border: 'none', - '&:not(:last-child)': { - borderBottom: 0, - }, - '&::before': { - display: 'none', - }, - }} - > - } - sx={{ flexDirection: 'row-reverse', gap: 1 }} + + )} + + { + setShowAdvancedSettings(!showAdvancedSettings); + }} + sx={{ + border: 'none', + '&:not(:last-child)': { + borderBottom: 0, + }, + '&::before': { + display: 'none', + }, + }} > - Advanced settings - - - - - - Check for updates every X minutes{' '} - } + sx={{ flexDirection: 'row-reverse', gap: 1 }} + > + Advanced settings + + + + + - - - + Check for updates every X minutes{' '} + + + + + { + onChange('appUpdatePollInterval', event.target.value); + }} + /> + + + Provisioning Key name + { - onChange('appUpdatePollInterval', event.target.value); + onChange('provisioningKeyName', event.target.value); }} /> - - - Provisioning Key name - - { - onChange('provisioningKeyName', event.target.value); - }} - /> - - Provisioning Key expiring on - - { - onChange('provisioningKeyExpiryDate', event.target.value); - }} + + Provisioning Key expiring on + + { + onChange('provisioningKeyExpiryDate', event.target.value); + }} + /> + + + + + { + setShowSecureBootConfirmationDialog(false); + setDontShowSecureBootWarningAgain(false); + }} + > + Enabling Secure Boot and Full Disk Encryption + + + + Enabling Secure Boot and Full Disk Encryption will have an impact + on data, kernel modules, debugging, and more. Make sure you + understand the implications and thoroughly test it on your + hardware.{' '} + {/* TODO: replace with the secure boot learn more link */} + + Learn more. + + + { + setDontShowSecureBootWarningAgain(event.target.checked); + }} + /> + } + label="Don't show me this warning again for this device type" /> - - - + + + { + setShowSecureBootConfirmationDialog(false); + setDontShowSecureBootWarningAgain(false); + }} + variant="outlined" + color="secondary" + > + Cancel + + { + onChange('secureboot', true); + setShowSecureBootConfirmationDialog(false); + if (dontShowSecureBootWarningAgain) { + setToLocalStorage(secureBootDontShowAgainKey, 'true'); + } + setDontShowSecureBootWarningAgain(false); + }} + > + I understand and acknowledge + + + + ); });