Skip to content

Commit 899e747

Browse files
committed
fix: sealed secrets styling and type issues & update components
1 parent 43d2d6d commit 899e747

7 files changed

Lines changed: 118 additions & 58 deletions

File tree

src/components/forms/KeyValue.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,10 @@ export default function KeyValue(props: KeyValueProps) {
265265
</Box>
266266
</FormRow>
267267
{addLabel && !disabled && (
268-
<IconButton sx={{ alignSelf: 'flex-end' }} onClick={() => remove(index)}>
268+
<IconButton
269+
sx={{ alignSelf: 'flex-start', mt: localIndex === 0 ? '48px' : '20px' }}
270+
onClick={() => remove(index)}
271+
>
269272
<Clear />
270273
</IconButton>
271274
)}

src/components/forms/TextArea.tsx

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,43 @@ import { Theme } from '@mui/material/styles'
44
import { Box } from '@mui/material'
55
import { InputLabel } from 'components/InputLabel'
66

7-
const useStyles = makeStyles()((theme: Theme) => ({
8-
inputLabel: {
9-
color: theme.palette.cl.text.title,
10-
marginBottom: theme.spacing(2),
11-
},
12-
textarea: {
13-
backgroundColor: theme.palette.background.default,
14-
color: theme.palette.cl.text.title,
15-
padding: theme.spacing(1),
16-
border: `1px solid ${theme.palette.cm.inputBorder}`,
17-
boxSizing: 'border-box',
18-
fontSize: '1rem',
19-
fontFamily: theme.typography.fontFamily,
20-
width: '100%',
21-
resize: 'both',
22-
display: 'block',
23-
whiteSpace: 'pre-wrap',
24-
overflowWrap: 'anywhere',
25-
wordBreak: 'break-word',
26-
},
27-
}))
7+
const useStyles = makeStyles<{ disabled?: boolean }>()((theme: Theme, { disabled }) => {
8+
const disabledStyles = disabled
9+
? {
10+
backgroundColor: theme.palette.cm.disabledBackground,
11+
borderColor: theme.palette.cm.disabledBorder,
12+
color: theme.palette.cm.disabledText,
13+
}
14+
: {}
15+
return {
16+
inputLabel: {
17+
color: theme.palette.cl.text.title,
18+
marginBottom: theme.spacing(2),
19+
},
20+
textarea: {
21+
backgroundColor: theme.palette.background.default,
22+
color: theme.palette.cl.text.title,
23+
padding: theme.spacing(1),
24+
border: `1px solid ${theme.palette.cm.inputBorder}`,
25+
boxSizing: 'border-box',
26+
overflow: 'hidden',
27+
fontFamily: 'monospace',
28+
fontSize: '14px',
29+
resize: 'both',
30+
display: 'inline-block',
31+
whiteSpace: 'pre-wrap',
32+
overflowWrap: 'anywhere',
33+
wordBreak: 'break-word',
34+
minWidth: '200px',
35+
minHeight: '34px',
36+
maxWidth: '850px',
37+
maxHeight: '800px',
38+
width: 'auto',
39+
height: 'auto',
40+
...disabledStyles,
41+
},
42+
}
43+
})
2844

2945
export interface AutoResizableTextareaProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'style'> {
3046
label?: string
@@ -43,15 +59,15 @@ export function AutoResizableTextarea({
4359
maxRows = 40,
4460
minWidth = 200,
4561
maxWidth = 850,
46-
minHeight,
62+
minHeight = 34,
4763
maxHeight = 800,
4864
style,
4965
onInput,
5066
onPaste,
5167
onChange,
5268
...rest
5369
}: AutoResizableTextareaProps) {
54-
const { classes, cx } = useStyles()
70+
const { classes, cx } = useStyles(rest.disabled ? { disabled: true } : {})
5571
const textareaRef = useRef<HTMLTextAreaElement>(null)
5672
const rulerRef = useRef<HTMLSpanElement | null>(null)
5773

@@ -152,7 +168,6 @@ export function AutoResizableTextarea({
152168
{...rest}
153169
ref={textareaRef}
154170
className={cx(classes.textarea)}
155-
style={style}
156171
rows={minRows}
157172
onInput={(e) => {
158173
onInput?.(e)
@@ -166,6 +181,7 @@ export function AutoResizableTextarea({
166181
onChange?.(e)
167182
adjustSize()
168183
}}
184+
disabled={rest.disabled}
169185
/>
170186
</Box>
171187
)

src/pages/code-repositories/create-edit/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,15 +323,15 @@ export default function CreateEditCodeRepositories({
323323
Select a secret
324324
</MenuItem>
325325
{teamSecrets?.map((secret) => (
326-
<MenuItem key={secret?.id} id={secret?.id} value={secret?.name}>
326+
<MenuItem key={secret?.name} id={secret?.name} value={secret?.name}>
327327
{secret?.name}
328328
</MenuItem>
329329
))}
330330
</TextField>
331331
<Button
332332
className={classes.link}
333333
onClick={() =>
334-
history.push(`/teams/${teamId}/create-sealedsecret`, {
334+
history.push(`/teams/${teamId}/secrets/create`, {
335335
coderepository: true,
336336
prefilled: watch(),
337337
})

src/pages/secrets/create-edit/SecretCreateEditPage.tsx

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Grid, MenuItem } from '@mui/material'
22
import PaperLayout from 'layouts/Paper'
33
import { LandingHeader } from 'components/LandingHeader'
4-
import { Redirect, RouteComponentProps } from 'react-router-dom'
4+
import { Redirect, RouteComponentProps, useHistory, useLocation } from 'react-router-dom'
55
import {
66
CreateSealedSecretApiResponse,
77
useCreateSealedSecretMutation,
@@ -26,6 +26,7 @@ import { useSession } from 'providers/Session'
2626
import { useTranslation } from 'react-i18next'
2727
import { mapObjectToKeyValueArray, valueArrayToObject } from 'utils/helpers'
2828
import { useAppSelector } from 'redux/hooks'
29+
import InformationBanner from 'components/InformationBanner'
2930
import { useStyles } from './create-edit-secrets.styles'
3031
import { createSealedSecretApiResponseSchema, secretTypes } from './create-edit-secrets.validator'
3132
import { SecretTypeFields } from './SecretTypeFields'
@@ -85,14 +86,21 @@ export default function SecretCreateEditPage({
8586
const { t } = useTranslation()
8687
const { classes } = useStyles()
8788
const { sealedSecretsPEM } = useSession()
89+
const history = useHistory()
90+
const location = useLocation()
91+
const locationState = location?.state as any
92+
const isCoderepository = locationState?.coderepository
93+
const prefilled = locationState?.prefilled || {}
8894

89-
const [create, { isLoading: isLoadingCreate, isSuccess: isSuccessCreate }] = useCreateSealedSecretMutation()
95+
const [create, { isLoading: isLoadingCreate, isSuccess: isSuccessCreate, data: dataCreate }] =
96+
useCreateSealedSecretMutation()
9097
const [update, { isLoading: isLoadingUpdate, isSuccess: isSuccessUpdate }] = useEditSealedSecretMutation()
9198
const [del, { isLoading: isLoadingDelete, isSuccess: isSuccessDelete }] = useDeleteSealedSecretMutation()
9299
const { data, isLoading, isFetching, isError, refetch } = useGetSealedSecretQuery(
93100
{ teamId, sealedSecretName },
94101
{ skip: !sealedSecretName },
95102
)
103+
const isImmutable = data?.immutable || false
96104

97105
const isDirty = useAppSelector(({ global: { isDirty } }) => isDirty)
98106
useEffect(() => {
@@ -109,10 +117,8 @@ export default function SecretCreateEditPage({
109117
}
110118

111119
const mergedDefaultValues = createSealedSecretApiResponseSchema.cast(formData)
112-
113120
const methods = useForm<SealedSecretFormData>({
114-
// @ts-ignore
115-
resolver: yupResolver(createSealedSecretApiResponseSchema) as Resolver<CreateSealedSecretApiResponse>,
121+
resolver: yupResolver(createSealedSecretApiResponseSchema) as Resolver<SealedSecretFormData>,
116122
defaultValues: mergedDefaultValues,
117123
})
118124

@@ -146,8 +152,14 @@ export default function SecretCreateEditPage({
146152
}, [watch('type'), sealedSecretName])
147153

148154
const mutating = isLoadingCreate || isLoadingUpdate || isLoadingDelete
149-
if (!mutating && (isSuccessUpdate || isSuccessDelete)) return <Redirect to={`/teams/${teamId}/secrets`} />
150-
if (!mutating && isSuccessCreate) return <Redirect to={`/teams/${teamId}/secrets`} />
155+
if (!mutating && (isSuccessCreate || isSuccessUpdate || isSuccessDelete)) {
156+
if (isCoderepository) {
157+
history.push(`/teams/${teamId}/code-repositories/create`, {
158+
coderepository: false,
159+
prefilled: { ...prefilled, secret: dataCreate.name },
160+
})
161+
} else return <Redirect to={`/teams/${teamId}/secrets`} />
162+
}
151163

152164
const onSubmit = async () => {
153165
const body = cloneDeep(watch())
@@ -227,6 +239,12 @@ export default function SecretCreateEditPage({
227239
// hides the first two crumbs (e.g. /teams/teamName)
228240
hideCrumbX={[0, 1]}
229241
/>
242+
{sealedSecretName && isImmutable && (
243+
<InformationBanner
244+
sx={{ my: '1rem' }}
245+
message='This secret is marked as immutable and therefore values cannot be changed, only deleted.'
246+
/>
247+
)}
230248
<FormProvider {...methods}>
231249
<form onSubmit={handleSubmit(onSubmit)}>
232250
<Section>
@@ -250,22 +268,27 @@ export default function SecretCreateEditPage({
250268
value={watch('type') || 'kubernetes.io/opaque'}
251269
disabled={!!sealedSecretName}
252270
>
253-
{/** default will be basic auth */}
254-
{/* <MenuItem key='select-a-secret'>Select a secret type</MenuItem> */}
255271
{secretTypes.map((t) => (
256272
<MenuItem key={t} value={t}>
257273
{t}
258274
</MenuItem>
259275
))}
260276
</TextField>
261-
<SecretTypeFields />
262-
<Divider />
277+
{sealedSecretName && !isImmutable && (
278+
<InformationBanner
279+
sx={{ mt: '2rem' }}
280+
message='You can add new values to override existing values, but be aware that applications using this token might need to be adapted.'
281+
/>
282+
)}
283+
<SecretTypeFields immutable={sealedSecretName && watch('immutable')} />
284+
<Divider sx={{ mb: 1 }} />
263285
<ControlledCheckbox
264286
sx={{ my: 2 }}
265287
name='immutable'
266288
control={control}
267289
label='Immutable'
268290
explainertext='If set to true, ensures that data stored in the Secret cannot be updated (only object metadata can be modified).'
291+
disabled={sealedSecretName && isImmutable}
269292
/>
270293
</Section>
271294
<AdvancedSettings>

src/pages/secrets/create-edit/SecretTypeFields.tsx

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,57 @@ import { secretTypes } from './create-edit-secrets.validator'
55
* Renders the secret-specific form fields based on selected secret type.
66
* Expects encryptedData to be part of form schema.
77
*/
8-
export function SecretTypeFields({ namePrefix = '' }: { namePrefix?: string }) {
8+
export function SecretTypeFields({ namePrefix = '', immutable = false }: { namePrefix?: string; immutable?: boolean }) {
99
const { control } = useFormContext()
1010
const typePath = namePrefix ? `${namePrefix}.type` : 'type'
1111
const dataPath = namePrefix ? `${namePrefix}.encryptedData` : 'encryptedData'
1212
const selectedType = useWatch({ control, name: typePath }) as typeof secretTypes[number]
13+
const title = 'Encrypted Data'
14+
const disabled = immutable && { keyDisabled: true, valueDisabled: true, disabled: true }
1315

1416
switch (selectedType) {
1517
case 'kubernetes.io/opaque':
16-
return <KeyValue title='' name={dataPath} keyLabel='Key' valueLabel='Value' addLabel='Add another' isTextArea />
18+
return (
19+
<KeyValue
20+
title={title}
21+
subTitle='A Kubernetes Opaque Secret is simply the "generic" secret type—perfect any time you need to stash arbitrary key-value data that doesn’t fit one of the built‐in types'
22+
name={dataPath}
23+
keyLabel='Key'
24+
valueLabel='Value'
25+
addLabel='Add another'
26+
isTextArea
27+
{...disabled}
28+
/>
29+
)
1730
case 'kubernetes.io/service-account-token':
1831
case 'kubernetes.io/dockercfg':
1932
case 'kubernetes.io/dockerconfigjson':
2033
case 'kubernetes.io/ssh-auth':
2134
case 'kubernetes.io/tls':
22-
return <KeyValue title='' name={dataPath} keyDisabled keyLabel='Key' valueLabel='Value' isTextArea />
35+
return (
36+
<KeyValue
37+
title={title}
38+
subTitle={`Enter key-value pairs for the "${selectedType}" secret type`}
39+
name={dataPath}
40+
keyDisabled
41+
keyLabel='Key'
42+
valueLabel='Value'
43+
isTextArea
44+
{...disabled}
45+
/>
46+
)
2347
case 'kubernetes.io/basic-auth':
24-
return <KeyValue title='' keyDisabled name={dataPath} keyLabel='Key' valueLabel='Value' />
48+
return (
49+
<KeyValue
50+
title={title}
51+
subTitle='Basic-auth Secret exists out of a username and password'
52+
keyDisabled
53+
name={dataPath}
54+
keyLabel='Key'
55+
valueLabel='Value'
56+
{...disabled}
57+
/>
58+
)
2559
default:
2660
return null
2761
}

src/pages/secrets/create-edit/create-edit-secrets.validator.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ const metadataSchema = yup
5252

5353
// 5) Main CreateSealedSecretApiResponse schema
5454
export const createSealedSecretApiResponseSchema = yup.object({
55-
id: yup.string().optional().default(undefined),
5655
name: yup.string().required('Secret name is required'),
5756
namespace: yup.string().optional().default(undefined),
5857
immutable: yup.boolean().optional().default(undefined),
@@ -68,5 +67,4 @@ export const createSealedSecretApiResponseSchema = yup.object({
6867
.min(1, 'At least one encrypted data entry is required')
6968
.default(() => [{ key: '', value: '' }]), // default to one empty entry
7069
metadata: metadataSchema,
71-
isDisabled: yup.boolean().optional().default(undefined),
7270
})

0 commit comments

Comments
 (0)