/** * SealedSecret Detail View * * Shows detailed information about a specific SealedSecret including * encrypted data, template, resulting Secret, and actions */ import { Link, Loader } from '@kinvolk/headlamp-plugin/lib/CommonComponents'; import { ActionButton, NameValueTable, SectionBox, SimpleTable, StatusLabel, } from '@kinvolk/headlamp-plugin/lib/CommonComponents'; import { K8s } from '@kinvolk/headlamp-plugin/lib'; import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; import { useSnackbar } from 'notistack'; import React from 'react'; import { useParams } from 'react-router-dom'; import { SealedSecret } from '../lib/SealedSecretCRD'; import { getPluginConfig, rotateSealedSecret } from '../lib/controller'; import { SealedSecretScope } from '../types'; import { DecryptDialog } from './DecryptDialog'; /** * Format scope for display */ function formatScope(scope: SealedSecretScope): string { switch (scope) { case 'strict': return 'Strict'; case 'namespace-wide': return 'Namespace-wide'; case 'cluster-wide': return 'Cluster-wide'; default: return scope; } } /** * SealedSecret detail view component */ export function SealedSecretDetail() { const { namespace, name } = useParams<{ namespace: string; name: string }>(); const [sealedSecret] = SealedSecret.useGet(name, namespace); const [secret] = K8s.ResourceClasses.Secret.useGet(name, namespace); const [decryptKey, setDecryptKey] = React.useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false); const [rotating, setRotating] = React.useState(false); const { enqueueSnackbar } = useSnackbar(); if (!sealedSecret) { return ; } const handleDelete = async () => { try { await sealedSecret.delete(); enqueueSnackbar('SealedSecret deleted successfully', { variant: 'success' }); window.history.back(); } catch (error: any) { enqueueSnackbar(`Failed to delete SealedSecret: ${error.message}`, { variant: 'error' }); } setDeleteDialogOpen(false); }; const handleRotate = async () => { setRotating(true); try { const config = getPluginConfig(); const yaml = JSON.stringify(sealedSecret.jsonData); await rotateSealedSecret(config, yaml); enqueueSnackbar('SealedSecret re-encrypted successfully', { variant: 'success' }); // The resource will auto-refresh via the watch } catch (error: any) { enqueueSnackbar(`Failed to re-encrypt: ${error.message}`, { variant: 'error' }); } finally { setRotating(false); } }; const encryptedKeys = Object.keys(sealedSecret.spec.encryptedData || {}); return ( <> {sealedSecret.metadata.name} } > {sealedSecret.isSynced ? 'Synced' : 'Not Synced'} ), }, { name: 'Status Message', value: sealedSecret.syncMessage, hide: !sealedSecret.syncCondition, }, { name: 'Age', value: sealedSecret.getAge(), }, { name: 'Created', value: new Date(sealedSecret.metadata.creationTimestamp!).toLocaleString(), }, ]} /> ({ 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) => ( ), }, ]} /> {sealedSecret.spec.template && ( )} {secret ? ( Secret exists, }, { name: 'Keys', value: Object.keys(secret.data || {}).join(', '), }, { name: 'Link', value: ( View Secret ), }, ]} /> ) : ( Secret not yet created

The controller will create the Secret once it processes this SealedSecret.

)}
{decryptKey && ( setDecryptKey(null)} /> )} setDeleteDialogOpen(false)}> Delete SealedSecret? Are you sure you want to delete the SealedSecret {name}? This will also delete the resulting Kubernetes Secret. ); }