1
+ import { Box , Button } from '@mui/material' ;
2
+ import { Base64 } from 'js-base64' ;
3
+ import _ from 'lodash' ;
4
+ import React from 'react' ;
1
5
import { useTranslation } from 'react-i18next' ;
6
+ import { useDispatch } from 'react-redux' ;
2
7
import { useParams } from 'react-router-dom' ;
3
8
import Secret from '../../lib/k8s/secret' ;
9
+ import { clusterAction } from '../../redux/clusterActionSlice' ;
10
+ import { AppDispatch } from '../../redux/stores/store' ;
4
11
import { EmptyContent } from '../common' ;
5
12
import { DetailsGrid , SecretField } from '../common/Resource' ;
6
13
import { SectionBox } from '../common/SectionBox' ;
@@ -14,6 +21,7 @@ export default function SecretDetails(props: {
14
21
const params = useParams < { namespace : string ; name : string } > ( ) ;
15
22
const { name = params . name , namespace = params . namespace , cluster } = props ;
16
23
const { t } = useTranslation ( ) ;
24
+ const dispatch : AppDispatch = useDispatch ( ) ;
17
25
18
26
return (
19
27
< DetailsGrid
@@ -35,19 +43,68 @@ export default function SecretDetails(props: {
35
43
{
36
44
id : 'headlamp.secrets-data' ,
37
45
section : ( ) => {
38
- const itemData = item ?. data || { } ;
39
- const mainRows : NameValueTableRow [ ] = Object . entries ( itemData ) . map (
40
- ( item : unknown [ ] ) => ( {
41
- name : item [ 0 ] as string ,
42
- value : < SecretField value = { item [ 1 ] } /> ,
43
- } )
44
- ) ;
46
+ const initialData = _ . mapValues ( item . data , ( v : string ) => Base64 . decode ( v ) ) ;
47
+ const [ data , setData ] = React . useState ( initialData ) ;
48
+ const lastDataRef = React . useRef ( initialData ) ;
49
+
50
+ React . useEffect ( ( ) => {
51
+ const newData = _ . mapValues ( item . data , ( v : string ) => Base64 . decode ( v ) ) ;
52
+ if ( ! _ . isEqual ( newData , lastDataRef . current ) ) {
53
+ if ( _ . isEqual ( data , lastDataRef . current ) ) {
54
+ setData ( newData ) ;
55
+ lastDataRef . current = newData ;
56
+ }
57
+ }
58
+ } , [ item . data ] ) ;
59
+
60
+ const handleFieldChange = ( key : string , newValue : string ) => {
61
+ setData ( prev => ( { ...prev , [ key ] : newValue } ) ) ;
62
+ } ;
63
+
64
+ const handleSave = ( ) => {
65
+ const encodedData = _ . mapValues ( data , ( v : string ) => Base64 . encode ( v ) ) ;
66
+ const updatedSecret = { ...item . jsonData , data : encodedData } ;
67
+ dispatch (
68
+ clusterAction ( ( ) => item . update ( updatedSecret ) , {
69
+ startMessage : t ( 'translation|Applying changes to {{ itemName }}…' , {
70
+ itemName : item . metadata . name ,
71
+ } ) ,
72
+ cancelledMessage : t ( 'translation|Cancelled changes to {{ itemName }}.' , {
73
+ itemName : item . metadata . name ,
74
+ } ) ,
75
+ successMessage : t ( 'translation|Applied changes to {{ itemName }}.' , {
76
+ itemName : item . metadata . name ,
77
+ } ) ,
78
+ errorMessage : t ( 'translation|Failed to apply changes to {{ itemName }}.' , {
79
+ itemName : item . metadata . name ,
80
+ } ) ,
81
+ } )
82
+ ) ;
83
+ lastDataRef . current = _ . cloneDeep ( data ) ;
84
+ } ;
85
+
86
+ const mainRows : NameValueTableRow [ ] = Object . entries ( data ) . map ( ( item : unknown [ ] ) => ( {
87
+ name : item [ 0 ] as string ,
88
+ value : (
89
+ < SecretField
90
+ value = { item [ 1 ] }
91
+ onChange = { e => handleFieldChange ( item [ 0 ] as string , e . target . value ) }
92
+ />
93
+ ) ,
94
+ } ) ) ;
45
95
return (
46
96
< SectionBox title = { t ( 'translation|Data' ) } >
47
97
{ mainRows . length === 0 ? (
48
98
< EmptyContent > { t ( 'No data in this secret' ) } </ EmptyContent >
49
99
) : (
50
- < NameValueTable rows = { mainRows } />
100
+ < >
101
+ < NameValueTable rows = { mainRows } />
102
+ < Box mt = { 2 } display = "flex" justifyContent = "flex-end" >
103
+ < Button variant = "contained" color = "primary" onClick = { handleSave } >
104
+ { t ( 'translation|Save' ) }
105
+ </ Button >
106
+ </ Box >
107
+ </ >
51
108
) }
52
109
</ SectionBox >
53
110
) ;
0 commit comments