From 2900550e9604cda6bd6fa9e6baf5081ea6c7aff6 Mon Sep 17 00:00:00 2001 From: Luca Pivetta <36865043+Pive01@users.noreply.github.com> Date: Fri, 13 Jun 2025 12:35:21 +0200 Subject: [PATCH 1/3] fix(OS response): Fixed issue with http response format on different OS (#57) --- Makefile | 2 +- ui/src/services/hooks/api.ts | 128 ++++++++++++++++++++++++----------- 2 files changed, 88 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index 5648165..3ea60f5 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ IMAGE?=localstack/localstack-docker-desktop -TAG?=0.5.4 +TAG?=0.5.5 BUILDER=buildx-multi-arch diff --git a/ui/src/services/hooks/api.ts b/ui/src/services/hooks/api.ts index 5dc623c..b72fce6 100644 --- a/ui/src/services/hooks/api.ts +++ b/ui/src/services/hooks/api.ts @@ -1,29 +1,53 @@ import useSWR from 'swr'; -import { STORAGE_KEY_ENVVARS, STORAGE_KEY_LOCALSTACK, STORAGE_KEY_MOUNT } from '../../constants'; -import { ConfigData, DockerContainer, mountPointData, RunConfig } from '../../types'; +import { + STORAGE_KEY_ENVVARS, + STORAGE_KEY_LOCALSTACK, + STORAGE_KEY_MOUNT, +} from '../../constants'; +import { + ConfigData, + DockerContainer, + mountPointData, + RunConfig, +} from '../../types'; import { isALocalStackContainer, isJson } from '../util'; import { useDDClient } from './utils'; interface useRunConfigsReturn { - configData: ConfigData, - isLoading: boolean, + configData: ConfigData; + isLoading: boolean; setRunningConfig: (data: string) => unknown; createConfig: (data: RunConfig) => unknown; updateConfig: (data: RunConfig) => unknown; deleteConfig: (data: string) => unknown; } -interface HTTPMessage { - data: { - Message: string, - }, -} +// This is what backend calls send in MacOS +type BaseMessage = { + Message: string; +}; + +// This is what backend calls send on Linux +type LinuxMessage = { + data: BaseMessage; +}; + +type HTTPMessage = LinuxMessage | BaseMessage; + +const isBaseMessage = (msg: HTTPMessage): msg is BaseMessage => (msg as BaseMessage).Message !== undefined; + +const resolveOSMessage = (message: HTTPMessage | undefined): BaseMessage | undefined => { + if (!message) return undefined; + if (isBaseMessage(message)) return message; + return message.data; +}; const adaptVersionData = (message: HTTPMessage, error: Error) => { - const newData = (!message || !message.data?.Message || error) ? - { configs: [], runningConfig: null } - : - JSON.parse(message.data?.Message); + const data = resolveOSMessage(message); + const newData = + !data || !data?.Message || error + ? { configs: [], runningConfig: null } + : JSON.parse(data?.Message); if (Array.isArray(newData)) { return { configs: newData, runningConfig: newData.at(0).id ?? null }; } @@ -35,21 +59,27 @@ export const useRunConfigs = (): useRunConfigsReturn => { const { client: ddClient } = useDDClient(); const { data, mutate, isValidating, error } = useSWR( cacheKey, - () => (ddClient.extension.vm.service.get('/configs') as Promise), + () => ddClient.extension.vm.service.get('/configs') as Promise, ); const updateConfig = async (newData: RunConfig) => { - await ddClient.extension.vm.service.put('/configs', { Data: JSON.stringify(newData) }); + await ddClient.extension.vm.service.put('/configs', { + Data: JSON.stringify(newData), + }); mutate(); }; const setRunningConfig = async (configId: string) => { - await ddClient.extension.vm.service.put('/configs/running', { Data: JSON.stringify(configId) }); + await ddClient.extension.vm.service.put('/configs/running', { + Data: JSON.stringify(configId), + }); mutate(); }; const createConfig = async (newData: RunConfig) => { - await ddClient.extension.vm.service.post('/configs', { Data: JSON.stringify(newData) }); + await ddClient.extension.vm.service.post('/configs', { + Data: JSON.stringify(newData), + }); mutate(); }; @@ -58,7 +88,6 @@ export const useRunConfigs = (): useRunConfigsReturn => { mutate(); }; - return { configData: adaptVersionData(data, error), isLoading: isValidating || (!error && !data), @@ -70,12 +99,12 @@ export const useRunConfigs = (): useRunConfigsReturn => { }; interface useMountPointReturn { - user: string | null, - os: string | null, - showForm: boolean, - showSetupWarning: boolean, - hasSkippedConfiguration: boolean, - isLoading: boolean, + user: string | null; + os: string | null; + showForm: boolean; + showSetupWarning: boolean; + hasSkippedConfiguration: boolean; + isLoading: boolean; setMountPointData: (data: mountPointData) => void; } @@ -85,30 +114,41 @@ export const useMountPoint = (): useMountPointReturn => { const { data, mutate, isValidating, error } = useSWR( cacheKey, - async () => (ddClient.extension.vm.service.get('/mount') as Promise), + async () => + ddClient.extension.vm.service.get('/mount') as Promise, ); const setMountPointData = async (data: mountPointData) => { - await ddClient.extension.vm.service.post('/mount', { Data: JSON.stringify(data) }); + await ddClient.extension.vm.service.post('/mount', { + Data: JSON.stringify(data), + }); mutate(); }; - const fileContent = (!error && data) ? data.data.Message : null; - const mountPointData = isJson(fileContent) ? JSON.parse(fileContent) as mountPointData : null; + const adaptedData = resolveOSMessage(data); + + const fileContent = !error && adaptedData ? adaptedData.Message : null; + const mountPointData = isJson(fileContent) + ? (JSON.parse(fileContent) as mountPointData) + : null; return { user: mountPointData?.user, os: mountPointData?.os, - showForm: mountPointData?.showForm == null ? true : mountPointData?.showForm, - showSetupWarning: mountPointData?.showSetupWarning == null ? true : mountPointData?.showSetupWarning, + showForm: + mountPointData?.showForm == null ? true : mountPointData?.showForm, + showSetupWarning: + mountPointData?.showSetupWarning == null + ? true + : mountPointData?.showSetupWarning, hasSkippedConfiguration: mountPointData?.hasSkippedConfiguration || false, - isLoading: isValidating || (!error && !data), + isLoading: isValidating || (!error && !adaptedData), setMountPointData, }; }; interface useLocalStackReturn { - data: DockerContainer | null, + data: DockerContainer | null; mutate: () => void; } @@ -118,15 +158,21 @@ export const useLocalStack = (): useLocalStackReturn => { const { data, mutate } = useSWR( cacheKey, - async () => (await ddClient.docker.listContainers() as [DockerContainer]) - .find(container => - isALocalStackContainer(container) && container.Command !== 'bin/localstack update docker-images', - ), { - refreshInterval: 2000, compare: - /* - * compares whether the old (b) status aligns with that of new (a) status - */ - (a, b) => a?.Id === b?.Id && a?.Status.includes('unhealthy') === b?.Status.includes('unhealthy'), + async () => + ((await ddClient.docker.listContainers()) as [DockerContainer]).find( + (container) => + isALocalStackContainer(container) && + container.Command !== 'bin/localstack update docker-images', + ), + { + refreshInterval: 2000, + compare: + /* + * compares whether the old (b) status aligns with that of new (a) status + */ + (a, b) => + a?.Id === b?.Id && + a?.Status.includes('unhealthy') === b?.Status.includes('unhealthy'), }, ); From c9c91f300a643eafc02f24fc46d63d7d6be6ad2a Mon Sep 17 00:00:00 2001 From: Luca Pivetta <36865043+Pive01@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:45:19 +0200 Subject: [PATCH 2/3] Change button to outlined and update version / changelog (#58) --- CHANGELOG.md | 6 + Makefile | 2 +- .../components/Feedback/ConfirmableButton.tsx | 2 +- ui/src/components/Header/Controller.tsx | 4 +- .../components/Views/Configs/ConfigPage.tsx | 4 +- .../components/Views/Configs/SettingsForm.tsx | 231 +++++++++++------- .../components/Views/Configs/UpsertConfig.tsx | 2 +- 7 files changed, 156 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28c3318..1a10d4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.5.6] — 2025-06-24 + +### Changed + +- Fixed white theme button disappearing issue + ## [0.5.4] — 2025-06-12 ### Changed diff --git a/Makefile b/Makefile index 3ea60f5..0494de2 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ IMAGE?=localstack/localstack-docker-desktop -TAG?=0.5.5 +TAG?=0.5.6 BUILDER=buildx-multi-arch diff --git a/ui/src/components/Feedback/ConfirmableButton.tsx b/ui/src/components/Feedback/ConfirmableButton.tsx index 1233fb4..890231c 100644 --- a/ui/src/components/Feedback/ConfirmableButton.tsx +++ b/ui/src/components/Feedback/ConfirmableButton.tsx @@ -79,7 +79,7 @@ export const ConfirmableButton = ({ - diff --git a/ui/src/components/Views/Configs/SettingsForm.tsx b/ui/src/components/Views/Configs/SettingsForm.tsx index 47f756b..3833305 100644 --- a/ui/src/components/Views/Configs/SettingsForm.tsx +++ b/ui/src/components/Views/Configs/SettingsForm.tsx @@ -25,23 +25,38 @@ import { } from '../../../services'; import { ConfirmableButton } from '../../Feedback'; -const ShrinkedCircularProgress = (): ReactElement => ; +const ShrinkedCircularProgress = (): ReactElement => ( + +); interface MountPointFormProps { initialState: number; } -export const SettingsForm = ({ initialState }: MountPointFormProps): ReactElement => { - - const [userState, setUserState] = useState({ loading: false, selectedUser: '', users: [] }); - const [osState, setOsState] = useState({ loading: false, selectedOS: '', OSs: [] }); +export const SettingsForm = ({ + initialState, +}: MountPointFormProps): ReactElement => { + const [userState, setUserState] = useState({ + loading: false, + selectedUser: '', + users: [], + }); + const [osState, setOsState] = useState({ + loading: false, + selectedOS: '', + OSs: [], + }); const [triggerUserCheck, setTriggerUserCheck] = useState(false); const [activeStep, setActiveStep] = useState(initialState); const { setMountPointData, user, os } = useMountPoint(); const { client: ddClient } = useDDClient(); - const steps = ['Enable Docker Desktop option', 'Launching pro container', 'Set mount point']; + const steps = [ + 'Enable Docker Desktop option', + 'Launching pro container', + 'Set mount point', + ]; const handleNext = () => { if (activeStep !== steps.length - 1) { @@ -55,9 +70,13 @@ export const SettingsForm = ({ initialState }: MountPointFormProps): ReactElemen const getMountPointPath = (): string => { if (ddClient.host.platform === 'darwin') { - return `/Users/${userState.selectedUser || 'loading...'}/Library/Caches/localstack/volume`; + return `/Users/${ + userState.selectedUser || 'loading...' + }/Library/Caches/localstack/volume`; } - return `/home/${userState.selectedUser || 'loading...'}/.cache/localstack/volume`; + return `/home/${ + userState.selectedUser || 'loading...' + }/.cache/localstack/volume`; }; const checkWindowsDistro = async () => { @@ -67,7 +86,11 @@ export const SettingsForm = ({ initialState }: MountPointFormProps): ReactElemen const foundOSs = getOSsFromBinary(res.stdout); - setOsState({ loading: false, selectedOS: os || foundOSs[0], OSs: foundOSs }); + setOsState({ + loading: false, + selectedOS: os || foundOSs[0], + OSs: foundOSs, + }); setTriggerUserCheck(true); }; @@ -77,7 +100,9 @@ export const SettingsForm = ({ initialState }: MountPointFormProps): ReactElemen let res: ExecResult; let foundUsers = []; if (ddClient.host.platform === 'win32') { - res = await ddClient.extension.host?.cli.exec('checkUser.cmd', [osState.selectedOS]); + res = await ddClient.extension.host?.cli.exec('checkUser.cmd', [ + osState.selectedOS, + ]); foundUsers = getUsersFromBinaryWindows(res.stdout); } else { res = await ddClient.extension.host?.cli.exec('checkUser.sh', []); @@ -85,11 +110,17 @@ export const SettingsForm = ({ initialState }: MountPointFormProps): ReactElemen } if (res.stderr || !res.stdout) { - ddClient.desktopUI.toast.error(`Error while locating users: ${res.stderr} using /tmp as mount point`); + ddClient.desktopUI.toast.error( + `Error while locating users: ${res.stderr} using /tmp as mount point`, + ); closeWithoutSetting(); } - setUserState({ loading: false, selectedUser: user || foundUsers[0], users: foundUsers }); + setUserState({ + loading: false, + selectedUser: user || foundUsers[0], + users: foundUsers, + }); }; const locateMountPoint = async () => { @@ -102,8 +133,10 @@ export const SettingsForm = ({ initialState }: MountPointFormProps): ReactElemen useEffect(() => { const execChecks = async () => { - if (userState.users.length === 0 - || (ddClient.host.platform === 'win32' && osState.OSs.length === 0)) { + if ( + userState.users.length === 0 || + (ddClient.host.platform === 'win32' && osState.OSs.length === 0) + ) { locateMountPoint(); } }; @@ -154,94 +187,117 @@ export const SettingsForm = ({ initialState }: MountPointFormProps): ReactElemen {label} - ), - )} + ))} - {activeStep === 0 && + {activeStep === 0 && ( <> - Make sure to have the option "Show Docker Extensions system containers" enabled. - To enable it visit your settings: + Make sure to have the option "Show Docker Extensions system + containers" enabled. To enable it visit your settings:
  • Navigate to Settings
  • Select the Extensions tab
  • -
  • Next to Show Docker Extensions system containers, select the checkbox
  • +
  • + Next to Show Docker Extensions system containers, select the + checkbox +
  • In the bottom-right corner, select Apply & Restart
- } - { - activeStep === 1 && + )} + {activeStep === 1 && ( - In order to start the Pro container, add a configuration with the variable LOCALSTACK_AUTH_TOKEN - set to your auth token and select that configuration in the top right corner. - API Keys are also supported, but will be deprecated in the future. + In order to start the Pro container, add a configuration with the + variable LOCALSTACK_AUTH_TOKEN set to your auth token and select + that configuration in the top right corner. API Keys are also + supported, but will be deprecated in the future. - } - {activeStep === 2 && + )} + {activeStep === 2 && ( <> - - Default mount point settings -
- {ddClient.host.platform === 'win32' && + Default mount point settings +
+ + {ddClient.host.platform === 'win32' && ( <> - - WSL distro - - + WSL distro + Select in which WSL distro you want to mount the container - {osState.loading ? + {osState.loading ? ( - : - + ) : ( + - } + + )} - } + + )} <> - - User - - + User + Select under which user you want to mount the container - {userState.loading || osState.loading ? + {userState.loading || osState.loading ? ( - : - + ) : ( + - } + + )} -
+
+
+ {`The LocalStack container will be mounted under ${getMountPointPath()}`} - - *You can still change this by overriding the LOCALSTACK_VOLUME_DIR environment variable - - } + + + *You can still change this by overriding the + LOCALSTACK_VOLUME_DIR environment variable + + + )}
- - : - - } + + ) : ( + + )} - + ); }; diff --git a/ui/src/components/Views/Configs/UpsertConfig.tsx b/ui/src/components/Views/Configs/UpsertConfig.tsx index dbb5715..440c8dc 100644 --- a/ui/src/components/Views/Configs/UpsertConfig.tsx +++ b/ui/src/components/Views/Configs/UpsertConfig.tsx @@ -191,7 +191,7 @@ export const UpsertConfig = ({ config, open, onClose }: Props): ReactElement => Cancel