Skip to content

Commit e859b58

Browse files
authored
feat(Images): Added post image consolidation restrictions (#78)
1 parent 9a5bc45 commit e859b58

File tree

7 files changed

+303
-187
lines changed

7 files changed

+303
-187
lines changed

ui/src/components/Feedback/ConfirmableButton.tsx

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import React, { useState, ReactElement, ReactNode, FunctionComponent, MouseEvent } from 'react';
1+
import React, {
2+
useState,
3+
ReactElement,
4+
ReactNode,
5+
FunctionComponent,
6+
MouseEvent,
7+
} from 'react';
28

39
import {
410
Button,
@@ -27,14 +33,19 @@ export type BaseProps = {
2733
cancelColor?: ButtonProps['color'];
2834
cancelText?: string;
2935
children?: ReactNode;
30-
}
36+
};
37+
type CleanButtonProps = Omit<ButtonProps, 'component'>;
3138

32-
type ComponentButton = (ButtonProps & { component: 'Button' }) & BaseProps
33-
type ComponentIconButton = (ButtonProps & { component: 'IconButton' }) & BaseProps
34-
type ComponentMenuItem = (ButtonProps & { component: 'MenuItem' }) & BaseProps
39+
type ComponentButton = (CleanButtonProps & { component: 'Button' }) & BaseProps;
40+
type ComponentIconButton = (CleanButtonProps & { component: 'IconButton' }) &
41+
BaseProps;
42+
type ComponentMenuItem = (CleanButtonProps & { component: 'MenuItem' }) &
43+
BaseProps;
3544

3645
export type ConfirmableButtonProps =
37-
ComponentButton | ComponentIconButton | ComponentMenuItem;
46+
| ComponentButton
47+
| ComponentIconButton
48+
| ComponentMenuItem;
3849

3950
export const ConfirmableButton = ({
4051
title,
@@ -50,7 +61,8 @@ export const ConfirmableButton = ({
5061
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
5162

5263
// eslint-disable-next-line
53-
const Component: FunctionComponent<any> = COMPONENTS_MAP[component] || COMPONENTS_MAP.fallback;
64+
const Component: FunctionComponent<any> =
65+
COMPONENTS_MAP[component] || COMPONENTS_MAP.fallback;
5466

5567
return (
5668
<>
@@ -70,16 +82,16 @@ export const ConfirmableButton = ({
7082
)}
7183
<DialogActions>
7284
<Button
73-
color= {cancelColor || 'primary'}
74-
variant='outlined'
85+
color={cancelColor || 'primary'}
86+
variant="outlined"
7587
onClick={() => setShowConfirmDialog(false)}
7688
>
7789
{cancelText || 'Cancel'}
7890
</Button>
7991
<Button
8092
autoFocus
81-
color= {okColor || 'error'}
82-
variant='outlined'
93+
color={okColor || 'error'}
94+
variant="outlined"
8395
onClick={(event: MouseEvent<HTMLButtonElement>) => {
8496
if (rest.onClick) rest.onClick(event as any); // eslint-disable-line
8597
setShowConfirmDialog(false);

ui/src/components/Header/Controller.tsx

Lines changed: 106 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import React, { ReactElement, useEffect, useState } from 'react';
2-
import { Chip, ButtonGroup, Select, MenuItem, FormControl, Box, Badge, Tooltip } from '@mui/material';
2+
import {
3+
Chip,
4+
ButtonGroup,
5+
Select,
6+
MenuItem,
7+
FormControl,
8+
Box,
9+
Badge,
10+
Tooltip,
11+
} from '@mui/material';
312
import { PlayArrow, Stop } from '@mui/icons-material';
413
import {
514
isALocalStackContainer,
@@ -11,9 +20,9 @@ import {
1120
} from '../../services';
1221
import {
1322
DEFAULT_CONFIGURATION_ID,
14-
PRO_IMAGE,
15-
COMMUNITY_IMAGE,
1623
FLAGS_AS_STRING,
24+
AUTH_TOKEN_VAR,
25+
LOCALSTACK_IMAGE,
1726
} from '../../constants';
1827
import { LongMenu } from './Menu';
1928
import { DockerContainer, DockerImage } from '../../types';
@@ -23,11 +32,21 @@ import { ProgressButton } from '../Feedback';
2332
const EXCLUDED_ERROR_TOAST = ['INFO', 'WARN', 'DEBUG'];
2433

2534
export const Controller = (): ReactElement => {
26-
const { configData, isLoading, setRunningConfig: setBackendRunningConfig, createConfig } = useRunConfigs();
35+
const {
36+
configData,
37+
isLoading,
38+
setRunningConfig: setBackendRunningConfig,
39+
createConfig,
40+
} = useRunConfigs();
2741
const { data, mutate } = useLocalStack();
2842
const { user, os, hasSkippedConfiguration } = useMountPoint();
29-
const [runningConfig, setRunningConfig] = useState<string>(configData.runningConfig ?? DEFAULT_CONFIGURATION_ID);
30-
const [downloadProps, setDownloadProps] = useState({ open: false, image: COMMUNITY_IMAGE });
43+
const [runningConfig, setRunningConfig] = useState<string>(
44+
configData.runningConfig ?? DEFAULT_CONFIGURATION_ID,
45+
);
46+
const [downloadProps, setDownloadProps] = useState({
47+
open: false,
48+
image: LOCALSTACK_IMAGE,
49+
});
3150
const [isStarting, setIsStarting] = useState<boolean>(false);
3251
const [isStopping, setIsStopping] = useState<boolean>(false);
3352
const { client: ddClient, getBinary } = useDDClient();
@@ -36,10 +55,17 @@ export const Controller = (): ReactElement => {
3655
const tooltipLabel = isUnhealthy ? 'Unhealthy' : 'Healthy';
3756

3857
useEffect(() => {
39-
if (!isLoading &&
40-
(!configData?.configs || !configData.configs?.find(item => item.id === DEFAULT_CONFIGURATION_ID))) {
58+
if (
59+
!isLoading &&
60+
(!configData?.configs ||
61+
!configData.configs?.find(
62+
(item) => item.id === DEFAULT_CONFIGURATION_ID,
63+
))
64+
) {
4165
createConfig({
42-
name: 'Default', id: DEFAULT_CONFIGURATION_ID, vars: [],
66+
name: 'Default',
67+
id: DEFAULT_CONFIGURATION_ID,
68+
vars: [],
4369
});
4470
}
4571
if (!isLoading) {
@@ -66,8 +92,9 @@ export const Controller = (): ReactElement => {
6692
};
6793

6894
const normalizeArguments = (): NodeJS.ProcessEnv => {
69-
const addedArgs = configData.configs.find(config => config.id === runningConfig)
70-
.vars.map(item => {
95+
const addedArgs = configData.configs
96+
.find((config) => config.id === runningConfig)
97+
.vars.map((item) => {
7198
if (item.variable === 'DOCKER_FLAGS') {
7299
return { [item.variable]: `${FLAGS_AS_STRING} ${item.value}` };
73100
}
@@ -76,7 +103,7 @@ export const Controller = (): ReactElement => {
76103
});
77104

78105
return [...addedArgs, buildHostArgs()].reduce((acc, obj) => {
79-
const [key, value] = Object.entries(obj)[0];
106+
const [key, value] = Object.entries(obj)[0];
80107
acc[key] = value;
81108
return acc;
82109
}, {} as NodeJS.ProcessEnv);
@@ -85,26 +112,31 @@ export const Controller = (): ReactElement => {
85112
const start = async () => {
86113
setIsStarting(true);
87114

88-
const images = await ddClient.docker.listImages() as [DockerImage];
115+
const images = (await ddClient.docker.listImages()) as [DockerImage];
116+
117+
const hasTokenSet = configData.configs
118+
.find((config) => config.id === runningConfig)
119+
.vars.some((item) => item.variable === AUTH_TOKEN_VAR && item.value);
89120

90-
const isPro = configData.configs.find(config => config.id === runningConfig)
91-
.vars.some(item => (item.variable === 'LOCALSTACK_API_KEY' ||
92-
item.variable === 'LOCALSTACK_AUTH_TOKEN') && item.value);
93121

94-
const havePro = images.some(image => removeTagFromImage(image) === PRO_IMAGE);
95-
if (!havePro && isPro) {
96-
setDownloadProps({ open: true, image: PRO_IMAGE });
122+
if(!hasTokenSet){
123+
ddClient.desktopUI.toast.error('Missing auth token in selected config');
124+
setIsStarting(false);
97125
return;
98126
}
127+
128+
const haveImage = images.some(
129+
(image) => removeTagFromImage(image) === LOCALSTACK_IMAGE,
130+
);
99131

100-
const haveCommunity = images.some(image => removeTagFromImage(image) === COMMUNITY_IMAGE);
101-
if (!haveCommunity) {
102-
setDownloadProps({ open: true, image: COMMUNITY_IMAGE });
132+
133+
if (!haveImage) {
134+
setDownloadProps({ open: true, image: LOCALSTACK_IMAGE });
103135
return;
104136
}
105137

106138
const args = normalizeArguments();
107-
139+
108140
const binary = getBinary();
109141
if (!binary) {
110142
setIsStarting(false);
@@ -115,7 +147,9 @@ export const Controller = (): ReactElement => {
115147
env: args,
116148
stream: {
117149
onOutput(data): void {
118-
const shouldDisplayError = !EXCLUDED_ERROR_TOAST.some(item => data.stderr?.includes(item)) && data.stderr;
150+
const shouldDisplayError =
151+
!EXCLUDED_ERROR_TOAST.some((item) => data.stderr?.includes(item)) &&
152+
data.stderr;
119153
if (shouldDisplayError) {
120154
ddClient.desktopUI.toast.error(data.stderr);
121155
setIsStarting(false);
@@ -133,23 +167,31 @@ export const Controller = (): ReactElement => {
133167

134168
const stop = async () => {
135169
setIsStopping(true);
136-
const containers = await ddClient.docker.listContainers({ 'all': true }) as [DockerContainer];
170+
const containers = (await ddClient.docker.listContainers({
171+
all: true,
172+
})) as [DockerContainer];
137173

138-
const stoppedContainer = containers.find(container =>
139-
isALocalStackContainer(container)
140-
&& !Object.keys(containers[0].Labels).some(key => key === 'cloud.localstack.spawner')
141-
&& container.Command === 'docker-entrypoint.sh');
174+
const stoppedContainer = containers.find(
175+
(container) =>
176+
isALocalStackContainer(container) &&
177+
!Object.keys(containers[0].Labels).some(
178+
(key) => key === 'cloud.localstack.spawner',
179+
) &&
180+
container.Command === 'docker-entrypoint.sh',
181+
);
142182

143-
const spawnerContainer = containers.find(container =>
144-
Object.keys(container.Labels).includes('cloud.localstack.spawner'));
183+
const spawnerContainer = containers.find((container) =>
184+
Object.keys(container.Labels).includes('cloud.localstack.spawner'),
185+
);
145186

146187
if (spawnerContainer) {
147188
await ddClient.docker.cli.exec('stop', [spawnerContainer.Id]); // stop the spawner
148189
}
149190

150191
if (stoppedContainer) {
151-
if (stoppedContainer.State === 'created') { // not started
152-
await ddClient.docker.cli.exec('rm', [stoppedContainer.Id]); // remove it
192+
if (stoppedContainer.State === 'created') {
193+
// not started
194+
await ddClient.docker.cli.exec('rm', [stoppedContainer.Id]); // remove it
153195
} else {
154196
await ddClient.docker.cli.exec('stop', [stoppedContainer.Id]);
155197
}
@@ -165,53 +207,63 @@ export const Controller = (): ReactElement => {
165207
};
166208

167209
return (
168-
<Box display='flex' gap={1} alignItems='center'>
210+
<Box display="flex" gap={1} alignItems="center">
169211
<DownloadProgressDialog
170212
imageName={downloadProps.image}
171213
open={downloadProps.open}
172214
onClose={onClose}
173215
/>
174-
<ButtonGroup variant='outlined'>
175-
{(isRunning && !isStarting) ?
216+
<ButtonGroup variant="outlined">
217+
{isRunning && !isStarting ? (
176218
<ProgressButton
177-
variant='outlined'
219+
variant="outlined"
178220
loading={isStopping}
179221
onClick={stop}
180-
startIcon={<Stop />}>
222+
startIcon={<Stop />}
223+
>
181224
Stop
182225
</ProgressButton>
183-
:
184-
<Box display='flex' alignItems='center'>
185-
<FormControl sx={{ m: 1, minWidth: 120, border: 'none' }} size='small'>
226+
) : (
227+
<Box display="flex" alignItems="center">
228+
<FormControl
229+
sx={{ m: 1, minWidth: 120, border: 'none' }}
230+
size="small"
231+
>
186232
<Select
187233
value={runningConfig}
188234
onChange={({ target }) => setBackendRunningConfig(target.value)}
189235
>
190-
{
191-
configData?.configs?.map(config => (
192-
<MenuItem key={config.id} value={config.id}>{config.name}</MenuItem>
193-
))
194-
}
236+
{configData?.configs?.map((config) => (
237+
<MenuItem key={config.id} value={config.id}>
238+
{config.name}
239+
</MenuItem>
240+
))}
195241
</Select>
196242
</FormControl>
197243
<Box>
198244
<ProgressButton
199-
variant='outlined'
245+
variant="outlined"
200246
loading={isStarting}
201247
onClick={start}
202-
startIcon={<PlayArrow />}>
248+
startIcon={<PlayArrow />}
249+
>
203250
Start
204251
</ProgressButton>
205-
206252
</Box>
207253
</Box>
208-
}
254+
)}
209255
</ButtonGroup>
210-
<Tooltip title={data ? tooltipLabel : ''} >
211-
<Badge color='error' overlap='circular' badgeContent=' ' variant='dot' invisible={!isUnhealthy}>
256+
<Tooltip title={data ? tooltipLabel : ''}>
257+
<Badge
258+
color="error"
259+
overlap="circular"
260+
badgeContent=" "
261+
variant="dot"
262+
invisible={!isUnhealthy}
263+
>
212264
<Chip
213-
label={(isRunning && !isStarting) ? 'Running' : 'Stopped'}
214-
color={(isRunning && !isStarting) ? 'success' : 'warning'}
265+
label={isRunning && !isStarting ? 'Running' : 'Stopped'}
266+
color={isRunning && !isStarting ? 'success' : 'warning'}
215267
sx={{ p: 2, borderRadius: 4 }}
216268
/>
217269
</Badge>

ui/src/components/Views/Configs/ConfigPage.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,11 @@ export const ConfigPage = (): ReactElement => {
5858
headerName: 'Name',
5959
flex: 2,
6060
},
61-
{
62-
field: 'id',
63-
headerName: 'ID',
64-
flex: 2,
65-
},
6661
{
6762
field: 'Configurations',
6863
headerName: 'Configurations',
6964
sortable: false,
70-
flex: 5,
65+
flex: 7,
7166
renderCell: (params: GridRenderCellParams) =>
7267
(params.row as RunConfig).vars.map(setting => `${setting.variable}=${setting.value}`).join(', '),
7368
},

0 commit comments

Comments
 (0)