Skip to content

Commit 0e16a20

Browse files
authored
Merge pull request #1484 from devtron-labs/protection-config-dev
feat: Protection config dev
2 parents d684f9c + d880720 commit 0e16a20

File tree

7 files changed

+158
-73
lines changed

7 files changed

+158
-73
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"private": true,
55
"homepage": "/dashboard",
66
"dependencies": {
7-
"@devtron-labs/devtron-fe-common-lib": "0.0.44",
7+
"@devtron-labs/devtron-fe-common-lib": "0.0.45",
88
"@rjsf/core": "^5.13.3",
99
"@rjsf/utils": "^5.13.3",
1010
"@rjsf/validator-ajv8": "^5.13.3",
@@ -51,7 +51,8 @@
5151
"xterm-addon-search": "^0.9.0",
5252
"xterm-webfont": "^2.0.0",
5353
"yaml": "^1.7.2",
54-
"yamljs": "^0.3.0"
54+
"yamljs": "^0.3.0",
55+
"tippy.js": "^6.3.7"
5556
},
5657
"scripts": {
5758
"lint": "eslint src/**/*.tsx",

src/components/ConfigMapSecret/ConfigMapSecret.components.tsx

Lines changed: 125 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState, useEffect } from 'react'
2-
import { Progressing, ResizableTextarea, ToastBody, noop, showError } from '@devtron-labs/devtron-fe-common-lib'
2+
import { ConditionalWrap, Progressing, ResizableTextarea, TippyTheme, ToastBody, noop, showError } from '@devtron-labs/devtron-fe-common-lib'
33
import YAML from 'yaml'
44
import { DEPLOYMENT_HISTORY_CONFIGURATION_LIST_MAP, PATTERNS } from '../../config'
55
import { ReactComponent as Dropdown } from '../../assets/icons/ic-chevron-down.svg'
@@ -28,9 +28,10 @@ import { DeploymentHistoryDetail } from '../app/details/cdDetails/cd.type'
2828
import { prepareHistoryData } from '../app/details/cdDetails/service'
2929
import './ConfigMapSecret.scss'
3030
import { getCMSecret, getConfigMapList, getSecretList } from './service'
31-
import { useParams } from 'react-router-dom'
31+
import { useHistory, useParams, useRouteMatch } from 'react-router-dom'
3232
import { toast } from 'react-toastify'
3333
import Tippy from '@tippyjs/react'
34+
import { followCursor } from 'tippy.js'
3435

3536
const ConfigToolbar = importComponentFromFELibrary('ConfigToolbar')
3637
const ApproveRequestTippy = importComponentFromFELibrary('ApproveRequestTippy')
@@ -114,12 +115,13 @@ export function ConfigMapSecretContainer({
114115
parentName,
115116
reloadEnvironments,
116117
}: ConfigMapSecretProps) {
117-
const { appId, envId } = useParams<{ appId; envId }>()
118-
const [collapsed, toggleCollapse] = useState(true)
118+
const { appId, envId, name } = useParams<{ appId; envId; name }>()
119+
const history = useHistory()
120+
const match = useRouteMatch()
119121
const [isLoader, setLoader] = useState<boolean>(false)
120122
const [draftData, setDraftData] = useState(null)
121123
const [selectedTab, setSelectedTab] = useState(data?.draftState === 4 ? 2 : 3)
122-
const [abortController, setAbortController] = useState(new AbortController());
124+
const [abortController, setAbortController] = useState(new AbortController())
123125

124126
let cmSecretStateLabel = !data?.isNew ? CM_SECRET_STATE.BASE : CM_SECRET_STATE.UNPUBLISHED
125127
if (isOverrideView) {
@@ -129,18 +131,32 @@ export function ConfigMapSecretContainer({
129131
cmSecretStateLabel = !data?.isNew ? CM_SECRET_STATE.ENV : CM_SECRET_STATE.UNPUBLISHED
130132
}
131133
}
134+
135+
useEffect(() => {
136+
if (title !== '' && title === name) {
137+
getData()
138+
}
139+
}, [])
132140

133141
const getData = async () => {
134142
try {
135143
abortController.abort()
136-
const newAbortController = new AbortController();
137-
setAbortController(newAbortController);
144+
const newAbortController = new AbortController()
138145
setLoader(true)
146+
setAbortController(newAbortController)
139147
const [_draftData, _cmSecretData] = await Promise.allSettled([
140148
isProtected && getDraftByResourceName
141-
? getDraftByResourceName(appId, envId ?? -1, componentType === 'secret' ? 2 : 1, data.name, newAbortController.signal )
149+
? getDraftByResourceName(
150+
appId,
151+
envId ?? -1,
152+
componentType === 'secret' ? 2 : 1,
153+
title,
154+
newAbortController.signal,
155+
)
156+
: null,
157+
!data?.isNew
158+
? getCMSecret(componentType, id, appId, title, envId, newAbortController.signal)
142159
: null,
143-
!data?.isNew ? getCMSecret(componentType, id, appId, data?.name, envId, newAbortController.signal ) : null,
144160
])
145161
let draftId, draftState
146162
if (
@@ -181,6 +197,7 @@ export function ConfigMapSecretContainer({
181197
} else {
182198
toast.error(`The ${componentType} '${data?.name}' has been deleted`)
183199
update(index, null)
200+
redirectURLToInitial()
184201
}
185202
} else if (
186203
cmSecretStateLabel === CM_SECRET_STATE.UNPUBLISHED &&
@@ -193,10 +210,14 @@ export function ConfigMapSecretContainer({
193210
} else if (_draftData.value.result.draftState === 2) {
194211
toast.error(`The ${componentType} '${data?.name}' has been deleted`)
195212
update(index, null)
213+
redirectURLToInitial()
196214
}
197215
}
198-
if((_cmSecretData?.status === 'fulfilled' && _cmSecretData?.value !== null) || (_draftData?.status === 'fulfilled' && _draftData?.value !== null)) {
199-
toggleCollapse(false)
216+
if (
217+
(_cmSecretData?.status === 'fulfilled' && _cmSecretData?.value !== null) ||
218+
(_draftData?.status === 'fulfilled' && _draftData?.value !== null)
219+
) {
220+
setLoader(true)
200221
}
201222
if (
202223
(_cmSecretData?.status === 'rejected' && _cmSecretData?.reason?.code === 403) ||
@@ -213,20 +234,32 @@ export function ConfigMapSecretContainer({
213234
}
214235
}
215236

237+
const redirectURLToInitial = (urlTo: string = '') => {
238+
const componentTypeName = componentType === 'secret' ? 'secrets' : 'configmap'
239+
const urlPrefix = match.url.split(componentTypeName)[0]
240+
history.push(`${urlPrefix}${componentTypeName}/${urlTo}`)
241+
}
242+
216243
const updateCollapsed = (_collapsed?: boolean): void => {
217-
if (_collapsed !== undefined) {
218-
toggleCollapse(_collapsed)
244+
if (!title) {
245+
//Redirect and Add config map & secret
246+
if (name === 'create') {
247+
toggleDraftComments(null)
248+
setDraftData(null)
249+
return redirectURLToInitial()
250+
}
251+
return redirectURLToInitial('create')
219252
} else {
220-
if (collapsed && data?.name) {
221-
getData()
253+
//Redirect and Open existing config map & secret
254+
if (name === title) {
255+
toggleDraftComments(null)
256+
setDraftData(null)
257+
return redirectURLToInitial()
222258
} else {
223-
toggleCollapse(!collapsed)
224-
if (!collapsed) {
225-
toggleDraftComments(null)
226-
setDraftData(null)
227-
}
259+
getData()
260+
return redirectURLToInitial(title)
228261
}
229-
}
262+
}
230263
}
231264

232265
const handleTabSelection = (index: number): void => {
@@ -255,6 +288,7 @@ export function ConfigMapSecretContainer({
255288
}
256289

257290
const renderDetails = (): JSX.Element => {
291+
if ((name && ((!title && name !== 'create') || (title && name !== title))) || !name) return null
258292
if (title && isProtected && draftData?.draftId) {
259293
return (
260294
<>
@@ -323,7 +357,7 @@ export function ConfigMapSecretContainer({
323357
}
324358

325359
const renderDraftState = (): JSX.Element => {
326-
if (collapsed) {
360+
if (title !== name ) {
327361
if (data.draftState === 1) {
328362
return <i className="mr-10 cr-5">In draft</i>
329363
} else if (data.draftState === 4) {
@@ -334,53 +368,77 @@ export function ConfigMapSecretContainer({
334368
return null
335369
}
336370

337-
const handleCMSecretClick = () => {
371+
const handleCMSecretClick = (event) => {
338372
if (title && isProtected && draftData?.draftId) {
339373
setSelectedTab(draftData.draftState === 4 ? 2 : 3)
340374
}
341-
updateCollapsed()
375+
updateCollapsed()
342376
}
343377

378+
const showBlurEffect = name && ((!title && name !== 'create') || (title && name !== title))
379+
344380
return (
345381
<>
346-
<section
347-
className={`pt-16 dc__border bcn-0 br-8 ${title ? 'mb-16' : 'en-3 bw-1 dashed mb-20'} ${
348-
reduceOpacity ? 'dc__disable-click dc__blur-1_5' : ''
349-
}`}
382+
<ConditionalWrap
383+
condition={showBlurEffect}
384+
wrap={(children) => (
385+
<Tippy
386+
theme={TippyTheme.black}
387+
followCursor={true}
388+
plugins= {[followCursor]}
389+
arrow={true}
390+
animation="shift-toward-subtle"
391+
placement='top'
392+
content={`Collapse opened ${componentType === 'secret' ? ' Secret' : ' ConfigMap'} first`}
393+
>
394+
<div>{children}</div>
395+
</Tippy>
396+
)}
350397
>
351-
<article
352-
className="dc__configuration-list pointer pr-16 pl-16 mb-16"
353-
onClick={handleCMSecretClick}
354-
data-testid="click-to-add-configmaps-secret"
398+
<div className={`${showBlurEffect ? 'cursor-not-allowed' : 'cursor'}`}>
399+
<section
400+
className={`pt-16 dc__border bcn-0 br-8 ${title ? 'mb-16' : 'en-3 bw-1 dashed mb-20'} ${
401+
reduceOpacity || showBlurEffect ? 'dc__disable-click dc__blur-1_5' : ''
402+
}`}
355403
>
356-
{renderIcon()}
357-
<div
358-
data-testid={`add-${componentType}-button`}
359-
className={`flex left lh-20 ${!title ? 'fw-5 fs-14 cb-5' : 'fw-5 fs-14 cn-9'}`}
404+
<article
405+
className="dc__configuration-list pr-16 pl-16 mb-16"
406+
onClick={handleCMSecretClick}
407+
data-testid="click-to-add-configmaps-secret"
360408
>
361-
{title || `Add ${componentType === 'secret' ? 'Secret' : 'ConfigMap'}`}
362-
{cmSecretStateLabel && <div className="flex tag ml-12">{cmSecretStateLabel}</div>}
363-
</div>
364-
{title && (
365-
<div className="flex right">
366-
{isProtected && (
367-
<>
368-
{renderDraftState()}
369-
<ProtectedIcon className="icon-dim-20 mr-10 fcv-5" />
370-
</>
371-
)}
372-
{isLoader ? (
373-
<span style={{ width: '20px' }}>
374-
<Progressing />
375-
</span>
376-
) : (
377-
<Dropdown className={`icon-dim-20 rotate ${collapsed ? '' : 'dc__flip-180'}`} />
378-
)}
409+
{renderIcon()}
410+
<div
411+
data-testid={`add-${componentType}-button`}
412+
className={`flex left lh-20 ${!title ? 'fw-5 fs-14 cb-5' : 'fw-5 fs-14 cn-9'}`}
413+
>
414+
{title || `Add ${componentType === 'secret' ? 'Secret' : 'ConfigMap'}`}
415+
{cmSecretStateLabel && <div className="flex tag ml-12">{cmSecretStateLabel}</div>}
379416
</div>
380-
)}
381-
</article>
382-
{!collapsed && renderDetails()}
383-
</section>
417+
{title && (
418+
<div className="flex right">
419+
{isProtected && (
420+
<>
421+
{renderDraftState()}
422+
<ProtectedIcon className="icon-dim-20 mr-10 fcv-5" />
423+
</>
424+
)}
425+
{isLoader ? (
426+
<span style={{ width: '20px' }}>
427+
<Progressing />
428+
</span>
429+
) : (
430+
<Dropdown
431+
className={`icon-dim-20 rotate ${name === title ? 'dc__flip-180' : ''}`}
432+
/>
433+
)}
434+
</div>
435+
)}
436+
</article>
437+
438+
{!isLoader ? renderDetails() : null}
439+
</section>
440+
</div>
441+
</ConditionalWrap>
384442
</>
385443
)
386444
}
@@ -400,27 +458,30 @@ export function ProtectedConfigMapSecretDetails({
400458
parentName,
401459
reloadEnvironments,
402460
}: ProtectedConfigMapSecretDetailsProps) {
403-
const { appId, envId } = useParams<{ appId; envId }>()
461+
const { appId, name } = useParams<{ appId; name }>()
404462
const [isLoader, setLoader] = useState<boolean>(false)
405463
const [baseData, setBaseData] = useState(null)
406-
const [abortController, setAbortController] = useState(new AbortController());
407-
464+
const [abortController, setAbortController] = useState(new AbortController())
408465

409466
const getBaseData = async () => {
410467
try {
411468
abortController.abort()
412469
let newAbortController = new AbortController()
413470
setAbortController(newAbortController)
414471
setLoader(true)
415-
const { result } = await(componentType === 'secret' ? getSecretList(appId, {signal: newAbortController.signal}) : getConfigMapList(appId, {signal: newAbortController.signal}))
472+
const { result } = await (componentType === 'secret'
473+
? getSecretList(appId, { signal: newAbortController.signal })
474+
: getConfigMapList(appId, { signal: newAbortController.signal }))
416475
let _baseData
417476
if (result?.configData?.length) {
418477
_baseData = result.configData.find((config) => config.name === data.name)
419478
if (_baseData) {
420479
_baseData.unAuthorized = data.unAuthorized
421480
}
422481
if (componentType === 'secret' && !data.unAuthorized) {
423-
const { result: secretResult } = await getCMSecret(componentType, result.id, appId, data?.name, {signal: newAbortController.signal})
482+
const { result: secretResult } = await getCMSecret(componentType, result.id, appId, data?.name, {
483+
signal: newAbortController.signal,
484+
})
424485
if (secretResult?.configData?.length) {
425486
_baseData = { ...secretResult.configData[0], unAuthorized: false }
426487
}
@@ -435,7 +496,7 @@ export function ProtectedConfigMapSecretDetails({
435496

436497
useEffect(() => {
437498
if (draftData.action === 3 && cmSecretStateLabel === CM_SECRET_STATE.OVERRIDDEN) {
438-
getBaseData()
499+
getBaseData()
439500
}
440501
}, [])
441502

src/components/EnvironmentOverride/EnvironmentOverride.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export default function EnvironmentOverride({
112112
reloadEnvironments={reloadEnvironments}
113113
/>
114114
</Route>
115-
<Route path={`${path}/${URLS.APP_CM_CONFIG}`}>
115+
<Route path={`${path}/${URLS.APP_CM_CONFIG}/:name?`}>
116116
<ConfigMapList
117117
key={`config-map-${params.appId}-${params.envId}`}
118118
isOverrideView={true}
@@ -125,7 +125,7 @@ export default function EnvironmentOverride({
125125
clusterId={environmentsMap.get(+params.envId)?.clusterId?.toString()}
126126
/>
127127
</Route>
128-
<Route path={`${path}/${URLS.APP_CS_CONFIG}`}>
128+
<Route path={`${path}/${URLS.APP_CS_CONFIG}/:name?`}>
129129
<SecretList
130130
key={`secret-${params.appId}-${params.envId}`}
131131
isOverrideView={true}

src/components/app/details/appConfig/AppComposeRouter.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,12 @@ export default function AppComposeRouter({
9999
/>,
100100
<Route
101101
key={`${path}/${URLS.APP_CM_CONFIG}`}
102-
path={`${path}/${URLS.APP_CM_CONFIG}`}
102+
path={`${path}/${URLS.APP_CM_CONFIG}/:name?`}
103103
render={(props) => <ConfigMapList isJobView={isJobView} isProtected={false} />}
104104
/>,
105105
<Route
106106
key={`${path}/${URLS.APP_CS_CONFIG}`}
107-
path={`${path}/${URLS.APP_CS_CONFIG}`}
107+
path={`${path}/${URLS.APP_CS_CONFIG}/:name?`}
108108
render={(props) => <SecretList isJobView={isJobView} isProtected={false} />}
109109
/>,
110110
<Route
@@ -195,15 +195,15 @@ export default function AppComposeRouter({
195195
/>
196196
)}
197197
/>,
198-
<Route key={`${path}/${URLS.APP_CM_CONFIG}`} path={`${path}/${URLS.APP_CM_CONFIG}`}>
198+
<Route key={`${path}/${URLS.APP_CM_CONFIG}`} path={`${path}/${URLS.APP_CM_CONFIG}/:name?`}>
199199
<ConfigMapList isProtected={isBaseConfigProtected} reloadEnvironments={reloadEnvironments} />
200200
</Route>,
201-
<Route key={`${path}/${URLS.APP_CS_CONFIG}`} path={`${path}/${URLS.APP_CS_CONFIG}`}>
201+
<Route key={`${path}/${URLS.APP_CS_CONFIG}`} path={`${path}/${URLS.APP_CS_CONFIG}/:name?`}>
202202
<SecretList isProtected={isBaseConfigProtected} reloadEnvironments={reloadEnvironments} />
203203
</Route>,
204204
<Route
205205
key={`${path}/${URLS.APP_ENV_OVERRIDE_CONFIG}`}
206-
path={`${path}/${URLS.APP_ENV_OVERRIDE_CONFIG}/:envId(\\d+)?`}
206+
path={`${path}/${URLS.APP_ENV_OVERRIDE_CONFIG}/:envId(\\d+)?/:name?`}
207207
render={(props) => (
208208
<EnvironmentOverride environments={environments} reloadEnvironments={reloadEnvironments} />
209209
)}

0 commit comments

Comments
 (0)