Skip to content

Commit c171dd8

Browse files
committed
frontend: add node shell
Fixes #996 Signed-off-by: farodin91 <github@jan-jansen.net>
1 parent aeb60f0 commit c171dd8

File tree

18 files changed

+448
-143
lines changed

18 files changed

+448
-143
lines changed

frontend/src/components/App/Settings/SettingsCluster.tsx

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import React from 'react';
66
import { useTranslation } from 'react-i18next';
77
import { useDispatch } from 'react-redux';
88
import { useHistory } from 'react-router-dom';
9-
import helpers, { ClusterSettings } from '../../../helpers';
9+
import helpers, { ClusterSettings, DEFAULT_DROP_SHELL_IMAGE } from '../../../helpers';
1010
import { useCluster, useClustersConf } from '../../../lib/k8s';
1111
import { deleteCluster } from '../../../lib/k8s/apiProxy';
1212
import { setConfig } from '../../../redux/configSlice';
@@ -47,12 +47,25 @@ function isValidNamespaceFormat(namespace: string) {
4747
return regex.test(namespace);
4848
}
4949

50+
function isValidImageFormat(image: string) {
51+
// We allow empty strings just because that's the default value in our case.
52+
if (!image) {
53+
return true;
54+
}
55+
56+
// Validates that the namespace is a valid DNS-1123 label and returns a boolean.
57+
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names
58+
//const regex = new RegExp('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$');
59+
return true;
60+
}
61+
5062
export default function SettingsCluster() {
5163
const cluster = useCluster();
5264
const clusterConf = useClustersConf();
5365
const { t } = useTranslation(['translation']);
5466
const [defaultNamespace, setDefaultNamespace] = React.useState('default');
5567
const [userDefaultNamespace, setUserDefaultNamespace] = React.useState('');
68+
const [dropShellImage, setDropShellImage] = React.useState('');
5669
const [newAllowedNamespace, setNewAllowedNamespace] = React.useState('');
5770
const [clusterSettings, setClusterSettings] = React.useState<ClusterSettings | null>(null);
5871
const classes = useStyles();
@@ -127,10 +140,34 @@ export default function SettingsCluster() {
127140
};
128141
}, [userDefaultNamespace]);
129142

143+
React.useEffect(() => {
144+
let timeoutHandle: NodeJS.Timeout | null = null;
145+
146+
if (isEditingDropShellImage()) {
147+
// We store the namespace after a timeout.
148+
timeoutHandle = setTimeout(() => {
149+
if (isValidImageFormat(dropShellImage)) {
150+
storeNewDropShellImage(dropShellImage);
151+
}
152+
}, 1000);
153+
}
154+
155+
return () => {
156+
if (timeoutHandle) {
157+
clearTimeout(timeoutHandle);
158+
timeoutHandle = null;
159+
}
160+
};
161+
}, [dropShellImage]);
162+
130163
function isEditingDefaultNamespace() {
131164
return clusterSettings?.defaultNamespace !== userDefaultNamespace;
132165
}
133166

167+
function isEditingDropShellImage() {
168+
return clusterSettings?.dropShellImage !== dropShellImage;
169+
}
170+
134171
if (!cluster) {
135172
return null;
136173
}
@@ -163,7 +200,18 @@ export default function SettingsCluster() {
163200
});
164201
}
165202

203+
function storeNewDropShellImage(image: string) {
204+
setClusterSettings((settings: ClusterSettings | null) => {
205+
const newSettings = { ...(settings || {}) };
206+
if (isValidImageFormat(image)) {
207+
newSettings.dropShellImage = image;
208+
}
209+
return newSettings;
210+
});
211+
}
212+
166213
const isValidDefaultNamespace = isValidNamespaceFormat(userDefaultNamespace);
214+
const isValidDropShellImage = isValidImageFormat(dropShellImage);
167215
const isValidNewAllowedNamespace = isValidNamespaceFormat(newAllowedNamespace);
168216
const invalidNamespaceMessage = t(
169217
"translation|Namespaces must contain only lowercase alphanumeric characters or '-', and must start and end with an alphanumeric character."
@@ -295,6 +343,40 @@ export default function SettingsCluster() {
295343
</>
296344
),
297345
},
346+
{
347+
name: t('translation|Drop Node Shell Image'),
348+
value: (
349+
<TextField
350+
onChange={event => {
351+
let value = event.target.value;
352+
value = value.replace(' ', '');
353+
setDropShellImage(value);
354+
}}
355+
value={dropShellImage}
356+
placeholder={DEFAULT_DROP_SHELL_IMAGE}
357+
error={!isValidImageFormat}
358+
helperText={
359+
isValidDropShellImage
360+
? t(
361+
'translation|The default image is used for dropping a shell into a node (when not specified directly).'
362+
)
363+
: invalidNamespaceMessage
364+
}
365+
InputProps={{
366+
endAdornment: isEditingDropShellImage() ? (
367+
<Icon
368+
width={24}
369+
color={theme.palette.text.secondary}
370+
icon="mdi:progress-check"
371+
/>
372+
) : (
373+
<Icon width={24} icon="mdi:check-bold" />
374+
),
375+
className: classes.input,
376+
}}
377+
/>
378+
),
379+
},
298380
]}
299381
/>
300382
</SectionBox>

0 commit comments

Comments
 (0)