style: format all source files with Prettier
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -80,7 +80,9 @@ export function DecryptDialog({ sealedSecret, secretKey, onClose }: DecryptDialo
|
|||||||
</Typography>
|
</Typography>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose} aria-label="Close dialog">Close</Button>
|
<Button onClick={onClose} aria-label="Close dialog">
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
@@ -103,7 +105,9 @@ export function DecryptDialog({ sealedSecret, secretKey, onClose }: DecryptDialo
|
|||||||
</Typography>
|
</Typography>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose} aria-label="Close dialog">Close</Button>
|
<Button onClick={onClose} aria-label="Close dialog">
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
@@ -182,7 +186,9 @@ export function DecryptDialog({ sealedSecret, secretKey, onClose }: DecryptDialo
|
|||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose} aria-label="Close dialog">Close</Button>
|
<Button onClick={onClose} aria-label="Close dialog">
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -83,10 +83,12 @@ export function EncryptDialog({ open, onClose }: EncryptDialogProps) {
|
|||||||
|
|
||||||
const handleCreate = async () => {
|
const handleCreate = async () => {
|
||||||
// Filter out empty rows
|
// Filter out empty rows
|
||||||
const validKeyValues = keyValues.filter(kv => kv.key || kv.value).map(kv => ({
|
const validKeyValues = keyValues
|
||||||
key: kv.key,
|
.filter(kv => kv.key || kv.value)
|
||||||
value: kv.value,
|
.map(kv => ({
|
||||||
}));
|
key: kv.key,
|
||||||
|
value: kv.value,
|
||||||
|
}));
|
||||||
|
|
||||||
// Use the encryption hook
|
// Use the encryption hook
|
||||||
const result = await encrypt({
|
const result = await encrypt({
|
||||||
@@ -232,7 +234,11 @@ export function EncryptDialog({ open, onClose }: EncryptDialogProps) {
|
|||||||
disabled={keyValues.length === 1}
|
disabled={keyValues.length === 1}
|
||||||
color="error"
|
color="error"
|
||||||
aria-label={`Remove key-value pair ${index + 1}`}
|
aria-label={`Remove key-value pair ${index + 1}`}
|
||||||
title={keyValues.length === 1 ? 'At least one key-value pair is required' : `Remove key-value pair ${index + 1}`}
|
title={
|
||||||
|
keyValues.length === 1
|
||||||
|
? 'At least one key-value pair is required'
|
||||||
|
: `Remove key-value pair ${index + 1}`
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Icon icon="mdi:delete" />
|
<Icon icon="mdi:delete" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
@@ -192,8 +192,8 @@ export class GenericErrorBoundary extends BaseErrorBoundary {
|
|||||||
Something Went Wrong
|
Something Went Wrong
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" paragraph>
|
<Typography variant="body2" paragraph>
|
||||||
An unexpected error occurred. Please try reloading the page or contact your administrator
|
An unexpected error occurred. Please try reloading the page or contact your
|
||||||
if the problem persists.
|
administrator if the problem persists.
|
||||||
</Typography>
|
</Typography>
|
||||||
{this.state.error && (
|
{this.state.error && (
|
||||||
<Typography
|
<Typography
|
||||||
|
|||||||
@@ -41,15 +41,37 @@ export function SealedSecretDetailSkeleton() {
|
|||||||
<Skeleton variant="text" width="40%" height={40} sx={{ mb: 3 }} animation="wave" />
|
<Skeleton variant="text" width="40%" height={40} sx={{ mb: 3 }} animation="wave" />
|
||||||
|
|
||||||
{/* Metadata section */}
|
{/* Metadata section */}
|
||||||
<Skeleton variant="rectangular" height={200} sx={{ mb: 2, borderRadius: 1 }} animation="wave" />
|
<Skeleton
|
||||||
|
variant="rectangular"
|
||||||
|
height={200}
|
||||||
|
sx={{ mb: 2, borderRadius: 1 }}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Encrypted data section */}
|
{/* Encrypted data section */}
|
||||||
<Skeleton variant="rectangular" height={150} sx={{ mb: 2, borderRadius: 1 }} animation="wave" />
|
<Skeleton
|
||||||
|
variant="rectangular"
|
||||||
|
height={150}
|
||||||
|
sx={{ mb: 2, borderRadius: 1 }}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Actions section */}
|
{/* Actions section */}
|
||||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||||
<Skeleton variant="rectangular" width={120} height={36} sx={{ borderRadius: 1 }} animation="wave" />
|
<Skeleton
|
||||||
<Skeleton variant="rectangular" width={120} height={36} sx={{ borderRadius: 1 }} animation="wave" />
|
variant="rectangular"
|
||||||
|
width={120}
|
||||||
|
height={36}
|
||||||
|
sx={{ borderRadius: 1 }}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
variant="rectangular"
|
||||||
|
width={120}
|
||||||
|
height={36}
|
||||||
|
sx={{ borderRadius: 1 }}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -66,10 +88,27 @@ export function SealingKeysListSkeleton() {
|
|||||||
{[1, 2].map(i => (
|
{[1, 2].map(i => (
|
||||||
<Box key={i} sx={{ mb: 3 }}>
|
<Box key={i} sx={{ mb: 3 }}>
|
||||||
<Skeleton variant="text" width="30%" height={32} sx={{ mb: 1 }} animation="wave" />
|
<Skeleton variant="text" width="30%" height={32} sx={{ mb: 1 }} animation="wave" />
|
||||||
<Skeleton variant="rectangular" height={100} sx={{ borderRadius: 1, mb: 1 }} animation="wave" />
|
<Skeleton
|
||||||
|
variant="rectangular"
|
||||||
|
height={100}
|
||||||
|
sx={{ borderRadius: 1, mb: 1 }}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
<Skeleton variant="rectangular" width={100} height={28} sx={{ borderRadius: 1 }} animation="wave" />
|
<Skeleton
|
||||||
<Skeleton variant="rectangular" width={100} height={28} sx={{ borderRadius: 1 }} animation="wave" />
|
variant="rectangular"
|
||||||
|
width={100}
|
||||||
|
height={28}
|
||||||
|
sx={{ borderRadius: 1 }}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
variant="rectangular"
|
||||||
|
width={100}
|
||||||
|
height={28}
|
||||||
|
sx={{ borderRadius: 1 }}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -87,9 +87,7 @@ export function SealedSecretDetail() {
|
|||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
Failed to load SealedSecret
|
Failed to load SealedSecret
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">{String(error)}</Typography>
|
||||||
{String(error)}
|
|
||||||
</Typography>
|
|
||||||
</Alert>
|
</Alert>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -162,165 +160,167 @@ export function SealedSecretDetail() {
|
|||||||
<span>{sealedSecret.metadata.name}</span>
|
<span>{sealedSecret.metadata.name}</span>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
{permissions?.canUpdate && (
|
{permissions?.canUpdate && (
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={handleRotate}
|
onClick={handleRotate}
|
||||||
disabled={rotating}
|
disabled={rotating}
|
||||||
sx={{ mr: 1 }}
|
sx={{ mr: 1 }}
|
||||||
>
|
>
|
||||||
{rotating ? 'Re-encrypting...' : 'Re-encrypt'}
|
{rotating ? 'Re-encrypting...' : 'Re-encrypt'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{permissions?.canDelete && (
|
{permissions?.canDelete && (
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="error"
|
color="error"
|
||||||
onClick={() => setDeleteDialogOpen(true)}
|
onClick={() => setDeleteDialogOpen(true)}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
}
|
||||||
}
|
>
|
||||||
>
|
|
||||||
<NameValueTable
|
|
||||||
rows={[
|
|
||||||
{
|
|
||||||
name: 'Name',
|
|
||||||
value: String(sealedSecret.metadata.name || ''),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Namespace',
|
|
||||||
value: String(sealedSecret.metadata.namespace || ''),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Scope',
|
|
||||||
value: String(formatScope(sealedSecret.scope)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Sync Status',
|
|
||||||
value: (
|
|
||||||
<StatusLabel status={sealedSecret.isSynced ? 'success' : 'error'}>
|
|
||||||
{sealedSecret.isSynced ? 'Synced' : 'Not Synced'}
|
|
||||||
</StatusLabel>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Status Message',
|
|
||||||
value: String(sealedSecret.syncMessage || 'Unknown'),
|
|
||||||
hide: !sealedSecret.syncCondition,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Age',
|
|
||||||
value: String(sealedSecret.getAge() || ''),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Created',
|
|
||||||
value: sealedSecret.metadata.creationTimestamp
|
|
||||||
? new Date(sealedSecret.metadata.creationTimestamp).toLocaleString()
|
|
||||||
: 'Unknown',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</SectionBox>
|
|
||||||
|
|
||||||
<SectionBox title="Encrypted Data">
|
|
||||||
<SimpleTable
|
|
||||||
data={encryptedKeys.map(key => ({
|
|
||||||
key,
|
|
||||||
value: sealedSecret.spec.encryptedData[key],
|
|
||||||
}))}
|
|
||||||
columns={[
|
|
||||||
{
|
|
||||||
label: 'Key',
|
|
||||||
getter: (row: any) => row.key,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Encrypted Value',
|
|
||||||
getter: (row: any) => {
|
|
||||||
const val = row.value;
|
|
||||||
return val.length > 40 ? val.substring(0, 40) + '...' : val;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Actions',
|
|
||||||
getter: (row: any) =>
|
|
||||||
canDecrypt ? (
|
|
||||||
<Button size="small" onClick={() => setDecryptKey(row.key)}>
|
|
||||||
Decrypt
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button size="small" disabled title="No permission to access Secrets">
|
|
||||||
Decrypt
|
|
||||||
</Button>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</SectionBox>
|
|
||||||
|
|
||||||
{sealedSecret.spec.template && (
|
|
||||||
<SectionBox title="Template">
|
|
||||||
<NameValueTable
|
<NameValueTable
|
||||||
rows={[
|
rows={[
|
||||||
{
|
{
|
||||||
name: 'Secret Type',
|
name: 'Name',
|
||||||
value: String(sealedSecret.spec.template.type || 'Opaque'),
|
value: String(sealedSecret.metadata.name || ''),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Labels',
|
name: 'Namespace',
|
||||||
value: String(JSON.stringify(sealedSecret.spec.template.metadata?.labels || {})),
|
value: String(sealedSecret.metadata.namespace || ''),
|
||||||
hide: !sealedSecret.spec.template.metadata?.labels,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Annotations',
|
name: 'Scope',
|
||||||
value: String(
|
value: String(formatScope(sealedSecret.scope)),
|
||||||
JSON.stringify(sealedSecret.spec.template.metadata?.annotations || {})
|
},
|
||||||
|
{
|
||||||
|
name: 'Sync Status',
|
||||||
|
value: (
|
||||||
|
<StatusLabel status={sealedSecret.isSynced ? 'success' : 'error'}>
|
||||||
|
{sealedSecret.isSynced ? 'Synced' : 'Not Synced'}
|
||||||
|
</StatusLabel>
|
||||||
),
|
),
|
||||||
hide: !sealedSecret.spec.template.metadata?.annotations,
|
},
|
||||||
|
{
|
||||||
|
name: 'Status Message',
|
||||||
|
value: String(sealedSecret.syncMessage || 'Unknown'),
|
||||||
|
hide: !sealedSecret.syncCondition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Age',
|
||||||
|
value: String(sealedSecret.getAge() || ''),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Created',
|
||||||
|
value: sealedSecret.metadata.creationTimestamp
|
||||||
|
? new Date(sealedSecret.metadata.creationTimestamp).toLocaleString()
|
||||||
|
: 'Unknown',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</SectionBox>
|
</SectionBox>
|
||||||
)}
|
|
||||||
|
|
||||||
<SectionBox title="Resulting Secret">
|
<SectionBox title="Encrypted Data">
|
||||||
{secret ? (
|
<SimpleTable
|
||||||
<NameValueTable
|
data={encryptedKeys.map(key => ({
|
||||||
rows={[
|
key,
|
||||||
|
value: sealedSecret.spec.encryptedData[key],
|
||||||
|
}))}
|
||||||
|
columns={[
|
||||||
{
|
{
|
||||||
name: 'Status',
|
label: 'Key',
|
||||||
value: <StatusLabel status="success">Secret exists</StatusLabel>,
|
getter: (row: any) => row.key,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Keys',
|
label: 'Encrypted Value',
|
||||||
value: String(Object.keys(secret.data || {}).join(', ') || 'None'),
|
getter: (row: any) => {
|
||||||
|
const val = row.value;
|
||||||
|
return val.length > 40 ? val.substring(0, 40) + '...' : val;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Link',
|
label: 'Actions',
|
||||||
value: (
|
getter: (row: any) =>
|
||||||
<Link
|
canDecrypt ? (
|
||||||
routeName="secret"
|
<Button size="small" onClick={() => setDecryptKey(row.key)}>
|
||||||
params={{
|
Decrypt
|
||||||
namespace: String(secret.metadata.namespace || ''),
|
</Button>
|
||||||
name: String(secret.metadata.name || ''),
|
) : (
|
||||||
}}
|
<Button size="small" disabled title="No permission to access Secrets">
|
||||||
>
|
Decrypt
|
||||||
View Secret
|
</Button>
|
||||||
</Link>
|
),
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
) : (
|
</SectionBox>
|
||||||
<Box p={2}>
|
|
||||||
<StatusLabel status="warning">Secret not yet created</StatusLabel>
|
{sealedSecret.spec.template && (
|
||||||
<p>The controller will create the Secret once it processes this SealedSecret.</p>
|
<SectionBox title="Template">
|
||||||
</Box>
|
<NameValueTable
|
||||||
|
rows={[
|
||||||
|
{
|
||||||
|
name: 'Secret Type',
|
||||||
|
value: String(sealedSecret.spec.template.type || 'Opaque'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Labels',
|
||||||
|
value: String(
|
||||||
|
JSON.stringify(sealedSecret.spec.template.metadata?.labels || {})
|
||||||
|
),
|
||||||
|
hide: !sealedSecret.spec.template.metadata?.labels,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Annotations',
|
||||||
|
value: String(
|
||||||
|
JSON.stringify(sealedSecret.spec.template.metadata?.annotations || {})
|
||||||
|
),
|
||||||
|
hide: !sealedSecret.spec.template.metadata?.annotations,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</SectionBox>
|
||||||
)}
|
)}
|
||||||
</SectionBox>
|
|
||||||
|
<SectionBox title="Resulting Secret">
|
||||||
|
{secret ? (
|
||||||
|
<NameValueTable
|
||||||
|
rows={[
|
||||||
|
{
|
||||||
|
name: 'Status',
|
||||||
|
value: <StatusLabel status="success">Secret exists</StatusLabel>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Keys',
|
||||||
|
value: String(Object.keys(secret.data || {}).join(', ') || 'None'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Link',
|
||||||
|
value: (
|
||||||
|
<Link
|
||||||
|
routeName="secret"
|
||||||
|
params={{
|
||||||
|
namespace: String(secret.metadata.namespace || ''),
|
||||||
|
name: String(secret.metadata.name || ''),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View Secret
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Box p={2}>
|
||||||
|
<StatusLabel status="warning">Secret not yet created</StatusLabel>
|
||||||
|
<p>The controller will create the Secret once it processes this SealedSecret.</p>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</SectionBox>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{decryptKey && (
|
{decryptKey && (
|
||||||
|
|||||||
@@ -126,9 +126,7 @@ export function SealedSecretList() {
|
|||||||
// Show error if CRD is not installed
|
// Show error if CRD is not installed
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<SectionBox
|
<SectionBox title="Sealed Secrets">
|
||||||
title="Sealed Secrets"
|
|
||||||
>
|
|
||||||
<Box p={2}>
|
<Box p={2}>
|
||||||
<StatusLabel status="error">Error</StatusLabel>
|
<StatusLabel status="error">Error</StatusLabel>
|
||||||
<Box mt={2}>
|
<Box mt={2}>
|
||||||
@@ -139,7 +137,11 @@ export function SealedSecretList() {
|
|||||||
cluster.
|
cluster.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Install with: <code>kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml</code>
|
Install with:{' '}
|
||||||
|
<code>
|
||||||
|
kubectl apply -f
|
||||||
|
https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
|
||||||
|
</code>
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -153,9 +155,7 @@ export function SealedSecretList() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SectionBox
|
<SectionBox title="Sealed Secrets">
|
||||||
title="Sealed Secrets"
|
|
||||||
>
|
|
||||||
<VersionWarning autoDetect showDetails={false} />
|
<VersionWarning autoDetect showDetails={false} />
|
||||||
<SectionFilterHeader title="" noNamespaceFilter={false} actions={actions} />
|
<SectionFilterHeader title="" noNamespaceFilter={false} actions={actions} />
|
||||||
<SimpleTable data={sealedSecrets} columns={columns} />
|
<SimpleTable data={sealedSecrets} columns={columns} />
|
||||||
|
|||||||
@@ -5,7 +5,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { K8s } from '@kinvolk/headlamp-plugin/lib';
|
import { K8s } from '@kinvolk/headlamp-plugin/lib';
|
||||||
import { SectionBox, SimpleTable, StatusLabel } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
import {
|
||||||
|
SectionBox,
|
||||||
|
SimpleTable,
|
||||||
|
StatusLabel,
|
||||||
|
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||||
import { Box, Button, Chip } from '@mui/material';
|
import { Box, Button, Chip } from '@mui/material';
|
||||||
import { useSnackbar } from 'notistack';
|
import { useSnackbar } from 'notistack';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@@ -27,7 +31,9 @@ interface SealingKey {
|
|||||||
*/
|
*/
|
||||||
export function SealingKeysView() {
|
export function SealingKeysView() {
|
||||||
const config = getPluginConfig();
|
const config = getPluginConfig();
|
||||||
const [secrets, , loading] = K8s.ResourceClasses.Secret.useList({ namespace: config.controllerNamespace });
|
const [secrets, , loading] = K8s.ResourceClasses.Secret.useList({
|
||||||
|
namespace: config.controllerNamespace,
|
||||||
|
});
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
|
|
||||||
// Filter for sealing key secrets
|
// Filter for sealing key secrets
|
||||||
|
|||||||
@@ -6,7 +6,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Link } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
import { Link } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||||
import { NameValueTable, SectionBox, StatusLabel } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
import {
|
||||||
|
NameValueTable,
|
||||||
|
SectionBox,
|
||||||
|
StatusLabel,
|
||||||
|
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SealedSecret } from '../lib/SealedSecretCRD';
|
import { SealedSecret } from '../lib/SealedSecretCRD';
|
||||||
|
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ export function VersionWarning({ autoDetect = true, showDetails = false }: Versi
|
|||||||
} else if (result.ok === false) {
|
} else if (result.ok === false) {
|
||||||
setDetectedVersion(null);
|
setDetectedVersion(null);
|
||||||
// Ensure error is always a string
|
// Ensure error is always a string
|
||||||
const errorMessage = typeof result.error === 'string'
|
const errorMessage = typeof result.error === 'string' ? result.error : String(result.error);
|
||||||
? result.error
|
|
||||||
: String(result.error);
|
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -70,11 +68,14 @@ export function VersionWarning({ autoDetect = true, showDetails = false }: Versi
|
|||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<Box mb={2}>
|
<Box mb={2}>
|
||||||
<Alert severity="error" action={
|
<Alert
|
||||||
<Button color="inherit" size="small" onClick={detectVersion}>
|
severity="error"
|
||||||
Retry
|
action={
|
||||||
</Button>
|
<Button color="inherit" size="small" onClick={detectVersion}>
|
||||||
}>
|
Retry
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
<strong>API Version Detection Failed</strong>
|
<strong>API Version Detection Failed</strong>
|
||||||
<br />
|
<br />
|
||||||
{String(error)}
|
{String(error)}
|
||||||
@@ -84,7 +85,8 @@ export function VersionWarning({ autoDetect = true, showDetails = false }: Versi
|
|||||||
<br />
|
<br />
|
||||||
Install Sealed Secrets with:{' '}
|
Install Sealed Secrets with:{' '}
|
||||||
<code style={{ fontSize: '0.875em' }}>
|
<code style={{ fontSize: '0.875em' }}>
|
||||||
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
|
kubectl apply -f
|
||||||
|
https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
|
||||||
</code>
|
</code>
|
||||||
<br />
|
<br />
|
||||||
Or visit:{' '}
|
Or visit:{' '}
|
||||||
|
|||||||
@@ -15,14 +15,7 @@ import {
|
|||||||
parsePublicKeyFromCert,
|
parsePublicKeyFromCert,
|
||||||
} from '../lib/crypto';
|
} from '../lib/crypto';
|
||||||
import { validateSecretKey, validateSecretName, validateSecretValue } from '../lib/validators';
|
import { validateSecretKey, validateSecretName, validateSecretValue } from '../lib/validators';
|
||||||
import {
|
import { AsyncResult, CertificateInfo, Err, Ok, PlaintextValue, SealedSecretScope } from '../types';
|
||||||
AsyncResult,
|
|
||||||
CertificateInfo,
|
|
||||||
Err,
|
|
||||||
Ok,
|
|
||||||
PlaintextValue,
|
|
||||||
SealedSecretScope,
|
|
||||||
} from '../types';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request parameters for encryption
|
* Request parameters for encryption
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* SealedSecret Custom Resource Definition
|
* SealedSecret Custom Resource Definition
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ApiProxy,K8s } from '@kinvolk/headlamp-plugin/lib';
|
import { ApiProxy, K8s } from '@kinvolk/headlamp-plugin/lib';
|
||||||
|
|
||||||
const { apiFactoryWithNamespace } = ApiProxy;
|
const { apiFactoryWithNamespace } = ApiProxy;
|
||||||
const { KubeObject } = K8s.cluster;
|
const { KubeObject } = K8s.cluster;
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ describe('retry logic', () => {
|
|||||||
.mockResolvedValueOnce({ ok: false, error: 'error2' })
|
.mockResolvedValueOnce({ ok: false, error: 'error2' })
|
||||||
.mockResolvedValueOnce({ ok: true, value: 'success' });
|
.mockResolvedValueOnce({ ok: true, value: 'success' });
|
||||||
|
|
||||||
const promise = retryWithBackoff(failTwiceThenSucceed, { maxAttempts: 3, initialDelayMs: 100 });
|
const promise = retryWithBackoff(failTwiceThenSucceed, {
|
||||||
|
maxAttempts: 3,
|
||||||
|
initialDelayMs: 100,
|
||||||
|
});
|
||||||
await vi.runAllTimersAsync();
|
await vi.runAllTimersAsync();
|
||||||
const result = await promise;
|
const result = await promise;
|
||||||
|
|
||||||
@@ -72,7 +75,10 @@ describe('retry logic', () => {
|
|||||||
.mockResolvedValueOnce({ ok: false, error: 'error2' })
|
.mockResolvedValueOnce({ ok: false, error: 'error2' })
|
||||||
.mockResolvedValueOnce({ ok: true, value: 'success' });
|
.mockResolvedValueOnce({ ok: true, value: 'success' });
|
||||||
|
|
||||||
const promise = retryWithBackoff(failTwiceThenSucceed, { maxAttempts: 3, initialDelayMs: 1000 });
|
const promise = retryWithBackoff(failTwiceThenSucceed, {
|
||||||
|
maxAttempts: 3,
|
||||||
|
initialDelayMs: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
// Fast-forward through retries
|
// Fast-forward through retries
|
||||||
await vi.runAllTimersAsync();
|
await vi.runAllTimersAsync();
|
||||||
|
|||||||
+6
-8
@@ -109,9 +109,7 @@ export async function retryWithBackoff<T, E>(
|
|||||||
const isLastAttempt = attempt === opts.maxAttempts - 1;
|
const isLastAttempt = attempt === opts.maxAttempts - 1;
|
||||||
if (isLastAttempt) {
|
if (isLastAttempt) {
|
||||||
// No more retries, return final error
|
// No more retries, return final error
|
||||||
return Err(
|
return Err(`Operation failed after ${opts.maxAttempts} attempts:\n${errors.join('\n')}`);
|
||||||
`Operation failed after ${opts.maxAttempts} attempts:\n${errors.join('\n')}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait before retrying
|
// Wait before retrying
|
||||||
@@ -124,9 +122,7 @@ export async function retryWithBackoff<T, E>(
|
|||||||
|
|
||||||
const isLastAttempt = attempt === opts.maxAttempts - 1;
|
const isLastAttempt = attempt === opts.maxAttempts - 1;
|
||||||
if (isLastAttempt) {
|
if (isLastAttempt) {
|
||||||
return Err(
|
return Err(`Operation failed after ${opts.maxAttempts} attempts:\n${errors.join('\n')}`);
|
||||||
`Operation failed after ${opts.maxAttempts} attempts:\n${errors.join('\n')}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const delay = calculateDelay(attempt, opts);
|
const delay = calculateDelay(attempt, opts);
|
||||||
@@ -171,10 +167,12 @@ export function isRetryableHttpError(error: Error): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for specific retryable status codes
|
// Check for specific retryable status codes
|
||||||
return message.includes('429') || // Too Many Requests
|
return (
|
||||||
|
message.includes('429') || // Too Many Requests
|
||||||
message.includes('408') || // Request Timeout
|
message.includes('408') || // Request Timeout
|
||||||
message.includes('503') || // Service Unavailable
|
message.includes('503') || // Service Unavailable
|
||||||
message.includes('504'); // Gateway Timeout
|
message.includes('504')
|
||||||
|
); // Gateway Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-3
@@ -16,9 +16,7 @@ type KubeObjectInterface = K8s.cluster.KubeObjectInterface;
|
|||||||
* return Ok(a / b);
|
* return Ok(a / b);
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export type Result<T, E = Error> =
|
export type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
|
||||||
| { ok: true; value: T }
|
|
||||||
| { ok: false; error: E };
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Async result type for promises that can fail
|
* Async result type for promises that can fail
|
||||||
|
|||||||
Reference in New Issue
Block a user