Skip to content

Commit 2c0c6b2

Browse files
authored
Merge pull request #1592 from devtron-labs/feat/inactive-user
feat: user status support, user permissions, permission groups refactoring
2 parents dbaaf48 + 0155a8c commit 2c0c6b2

File tree

145 files changed

+5813
-4761
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

145 files changed

+5813
-4761
lines changed

.eslintignore

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -146,21 +146,6 @@ src/components/ResourceBrowser/Types.ts
146146
src/components/ResourceBrowser/Utils.ts
147147
src/components/__mocks__/monaco-editor.js
148148
src/components/__mocks__/xterm-webfont.js
149-
src/components/apiTokens/APITokenList.tsx
150-
src/components/apiTokens/ApiTokens.component.tsx
151-
src/components/apiTokens/CreateAPIToken.tsx
152-
src/components/apiTokens/DeleteAPITokenModal.tsx
153-
src/components/apiTokens/EditAPIToken.tsx
154-
src/components/apiTokens/ExpirationDate.tsx
155-
src/components/apiTokens/GenerateActionButton.tsx
156-
src/components/apiTokens/GenerateModal.tsx
157-
src/components/apiTokens/GroupPermission.tsx
158-
src/components/apiTokens/RegenerateModal.tsx
159-
src/components/apiTokens/__mocks__/ApiTokens.mock.ts
160-
src/components/apiTokens/authorization.type.ts
161-
src/components/apiTokens/authorization.utils.ts
162-
src/components/apiTokens/service.ts
163-
src/components/apiTokens/validationRules.ts
164149
src/components/app/EventsLogs.tsx
165150
src/components/app/LogFilter.ts
166151
src/components/app/Overview/EnvironmentList.tsx
@@ -541,10 +526,8 @@ src/components/hostURL/hosturl.service.ts
541526
src/components/hostURL/hosturl.type.ts
542527
src/components/hyperion/EnvironmentSelect.tsx
543528
src/components/login/Login.tsx
544-
src/components/login/SSOLogin.tsx
545529
src/components/login/login.service.ts
546530
src/components/login/login.types.ts
547-
src/components/login/ssoConfig.types.tsx
548531
src/components/material/CreateMaterial.tsx
549532
src/components/material/MaterialList.tsx
550533
src/components/material/MaterialView.tsx
@@ -601,18 +584,15 @@ src/components/security/security.service.ts
601584
src/components/security/security.types.ts
602585
src/components/security/security.util.tsx
603586
src/components/terminal/TerminalWrapper.tsx
604-
src/components/terminal/index.ts
605-
src/components/userGroups/AppPermissions.tsx
606-
src/components/userGroups/Group.tsx
607-
src/components/userGroups/K8sObjectPermissions/K8sListItemCard.tsx
608-
src/components/userGroups/K8sObjectPermissions/K8sPermission.component.tsx
609-
src/components/userGroups/K8sObjectPermissions/K8sPermissionModal.tsx
610-
src/components/userGroups/K8sObjectPermissions/K8sPermissions.utils.ts
611-
src/components/userGroups/K8sObjectPermissions/K8sPermissons.tsx
612-
src/components/userGroups/User.tsx
613-
src/components/userGroups/UserGroup.tsx
614-
src/components/userGroups/userGroup.service.ts
615-
src/components/userGroups/userGroups.types.ts
587+
src/Pages/GlobalConfigurations/Authorization/shared/components/AppPermissions.tsx
588+
src/Pages/GlobalConfigurations/Authorization/shared/components/K8sObjectPermissions/K8sListItemCard.tsx
589+
src/Pages/GlobalConfigurations/Authorization/shared/components/K8sObjectPermissions/K8sPermission.component.tsx
590+
src/Pages/GlobalConfigurations/Authorization/shared/components/K8sObjectPermissions/K8sPermissionModal.tsx
591+
src/Pages/GlobalConfigurations/Authorization/shared/components/K8sObjectPermissions/K8sPermissions.utils.ts
592+
src/Pages/GlobalConfigurations/Authorization/shared/components/K8sObjectPermissions/K8sPermissons.tsx
593+
src/Pages/GlobalConfigurations/Authorization/shared/components/userGroups/UserGroup.tsx
594+
src/Pages/GlobalConfigurations/Authorization/shared/components/userGroups/userGroups.types.ts
595+
src/Pages/GlobalConfigurations/Authorization/SSOLoginServices/SSOLogin.component.tsx
616596
src/components/util/KeyValueFileInput.tsx
617597
src/components/util/URLUtil.ts
618598
src/components/v2/HelmAppOverview/HelpAppOverview.tsx

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ module.exports = {
4040
// Since we are using typescript, we can disable the no-unused-vars rule for enum,etc
4141
'no-unused-vars': 'off',
4242
'@typescript-eslint/no-unused-vars': 'error',
43+
'@typescript-eslint/explicit-module-boundary-types': 'off',
4344
'react/jsx-filename-extension': [
4445
'error',
4546
{

package.json

Lines changed: 1 addition & 1 deletion
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.61",
7+
"@devtron-labs/devtron-fe-common-lib": "0.0.62",
88
"@rjsf/core": "^5.13.3",
99
"@rjsf/utils": "^5.13.3",
1010
"@rjsf/validator-ajv8": "^5.13.3",

src/GlobalConfigurations/.gitkeep

Whitespace-only changes.
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import moment from 'moment'
2+
import React, { useState } from 'react'
3+
import { useHistory } from 'react-router-dom'
4+
import { TippyCustomized, TippyTheme, GenericEmptyState } from '@devtron-labs/devtron-fe-common-lib'
5+
import { DOCUMENTATION, MomentDateFormat } from '../../../../config'
6+
import { ReactComponent as Key } from '../../../../assets/icons/ic-key-bulb.svg'
7+
import { ReactComponent as Edit } from '../../../../assets/icons/ic-pencil.svg'
8+
import { ReactComponent as Trash } from '../../../../assets/icons/ic-delete-interactive.svg'
9+
import { APITokenListType, TokenListType } from './authorization.type'
10+
import { isTokenExpired } from './authorization.utils'
11+
import DeleteAPITokenModal from './DeleteAPITokenModal'
12+
import NoResults from '../../../../assets/img/[email protected]'
13+
import './apiToken.scss'
14+
import { ReactComponent as Question } from '../../../../assets/icons/ic-help-outline.svg'
15+
import { ReactComponent as QuestionFilled } from '../../../../assets/icons/ic-help.svg'
16+
import { EMPTY_STATE_STATUS } from '../../../../config/constantMessaging'
17+
18+
const APITokenList = ({ tokenList, renderSearchToken, reload }: APITokenListType) => {
19+
const history = useHistory()
20+
const [showDeleteConfirmation, setDeleteConfirmation] = useState(false)
21+
const [selectedToken, setSelectedToken] = useState<TokenListType>()
22+
23+
const handleGenerateRowActionButton = (key: 'create' | 'edit', id?) => {
24+
history.push(id ? `${key}/${id}` : key)
25+
}
26+
27+
// eslint-disable-next-line @typescript-eslint/no-shadow
28+
const handleDeleteButton = (tokenList) => {
29+
setSelectedToken(tokenList)
30+
setDeleteConfirmation(true)
31+
}
32+
33+
const handleQuestion = () => {
34+
return (
35+
<TippyCustomized
36+
theme={TippyTheme.white}
37+
className="w-300 h-100 fcv-5"
38+
placement="right"
39+
Icon={QuestionFilled}
40+
heading="API tokens"
41+
infoText="Tokens you have generated that can be used to access the Devtron API."
42+
showCloseButton
43+
trigger="click"
44+
interactive
45+
documentationLink={DOCUMENTATION.WEBHOOK_API_TOKEN}
46+
documentationLinkText="View Documentation"
47+
>
48+
<div className="icon-dim-16 fcn-9 ml-8 cursor">
49+
<Question />
50+
</div>
51+
</TippyCustomized>
52+
)
53+
}
54+
55+
const handleGenerateRowAction = () => {
56+
handleGenerateRowActionButton('create')
57+
}
58+
59+
const handleEditRowAction = (e) => {
60+
const { id } = tokenList[e.currentTarget.dataset.index]
61+
handleGenerateRowActionButton('edit', id)
62+
}
63+
64+
const handleDelete = (e) => {
65+
const list = tokenList[e.currentTarget.dataset.index]
66+
handleDeleteButton(list)
67+
}
68+
69+
const noMatchingResults = () => {
70+
return (
71+
<GenericEmptyState
72+
image={NoResults}
73+
title={EMPTY_STATE_STATUS.API_TOKEN.TITLE}
74+
subTitle={EMPTY_STATE_STATUS.API_TOKEN.SUBTITLE}
75+
/>
76+
)
77+
}
78+
79+
return (
80+
<div className="bcn-0">
81+
<div data-testid="api-token-page-header" className="flex dc__content-space pl-20 pr-20 pb-16">
82+
<div className="flex row ml-0">
83+
<div className="cn-9 fw-6 fs-16">API tokens</div>
84+
{handleQuestion()}
85+
</div>
86+
<div className="flex dc__align-end dc__content-end">
87+
{renderSearchToken()}
88+
<button
89+
type="button"
90+
data-testid="api-token-generate-button"
91+
className="flex cta h-32 ml-10"
92+
onClick={handleGenerateRowAction}
93+
>
94+
Generate new token
95+
</button>
96+
</div>
97+
</div>
98+
<div className="api-token__list">
99+
<div className="api-list__row fw-6 cn-7 fs-12 dc__border-bottom pt-8 pb-8 pl-20 pr-20 dc__uppercase">
100+
<div />
101+
<div>Name</div>
102+
<div>Last Used On</div>
103+
<div>Last used by Ip add.</div>
104+
<div>Expires on</div>
105+
<div />
106+
</div>
107+
<div className="dc__overflow-scroll api__list__height dc__position-rel">
108+
{!tokenList || tokenList.length === 0
109+
? noMatchingResults()
110+
: tokenList.map((list, index) => (
111+
<div
112+
// eslint-disable-next-line react/no-array-index-key
113+
key={`api_${index}`}
114+
data-testid="api-list-row"
115+
className="api-list__row api-list-row flex-align-center fw-4 cn-9 fs-13 pr-20 pl-20"
116+
style={{ height: '45px' }}
117+
>
118+
<button
119+
type="button"
120+
className="dc__transparent cursor flex"
121+
data-index={index}
122+
onClick={handleEditRowAction}
123+
aria-label="Edit api token"
124+
>
125+
<Key
126+
className={`api-key-icon icon-dim-20 ${
127+
isTokenExpired(list.expireAtInMs) ? 'api-key-expired-icon' : ''
128+
}`}
129+
/>
130+
</button>
131+
<div className="flexbox cb-5 cursor" data-index={index} onClick={handleEditRowAction}>
132+
<span className="dc__ellipsis-right">{list.name}</span>
133+
</div>
134+
<div className="dc__ellipsis-right">
135+
{list.lastUsedAt
136+
? moment(list.lastUsedAt).format(MomentDateFormat)
137+
: 'Never used'}
138+
</div>
139+
<div>{list.lastUsedByIp ? list.lastUsedByIp : '-'}</div>
140+
<div className={`${isTokenExpired(list.expireAtInMs) ? 'cr-5' : ''}`}>
141+
{list.expireAtInMs === 0 ? (
142+
'No expiration date'
143+
) : (
144+
<>
145+
{isTokenExpired(list.expireAtInMs) ? 'Expired on ' : ''}
146+
{moment(list.expireAtInMs).format(MomentDateFormat)}
147+
</>
148+
)}
149+
</div>
150+
<div className="api__row-actions flex right">
151+
<button
152+
type="button"
153+
className="dc__transparent mr-12"
154+
data-index={index}
155+
data-testid="api-token-edit-button"
156+
onClick={handleEditRowAction}
157+
aria-label="Edit api token"
158+
>
159+
<Edit className="icon-dim-20" />
160+
</button>
161+
<button
162+
type="button"
163+
className="dc__transparent"
164+
data-index={index}
165+
data-testid="api-token-delete-button"
166+
onClick={handleDelete}
167+
aria-label="Delete api token"
168+
>
169+
<Trash className="scn-6 icon-dim-20" />
170+
</button>
171+
</div>
172+
</div>
173+
))}
174+
</div>
175+
{showDeleteConfirmation && selectedToken && (
176+
<DeleteAPITokenModal
177+
tokenData={selectedToken}
178+
reload={reload}
179+
setDeleteConfirmation={setDeleteConfirmation}
180+
/>
181+
)}
182+
</div>
183+
</div>
184+
)
185+
}
186+
187+
export default APITokenList

src/components/apiTokens/ApiTokens.component.tsx renamed to src/Pages/GlobalConfigurations/Authorization/APITokens/ApiTokens.component.tsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import React, { Fragment, useEffect, useState } from 'react'
22
import './apiToken.scss'
3-
import { ReactComponent as Search } from '../../assets/icons/ic-search.svg'
4-
import { ReactComponent as Clear } from '../../assets/icons/ic-error.svg'
5-
import { getGeneratedAPITokenList } from './service'
63
import { showError, Progressing, ErrorScreenManager, GenericEmptyState } from '@devtron-labs/devtron-fe-common-lib'
7-
import emptyGeneratToken from '../../assets/img/ic-empty-generate-token.png'
84
import { Redirect, Route, Switch, useHistory, useLocation, useRouteMatch } from 'react-router-dom'
5+
import { ReactComponent as Search } from '../../../../assets/icons/ic-search.svg'
6+
import { ReactComponent as Clear } from '../../../../assets/icons/ic-error.svg'
7+
import { getGeneratedAPITokenList } from './service'
8+
import emptyGeneratToken from '../../../../assets/img/ic-empty-generate-token.png'
99
import APITokenList from './APITokenList'
1010
import CreateAPIToken from './CreateAPIToken'
1111
import EditAPIToken from './EditAPIToken'
1212
import { TokenListType, TokenResponseType } from './authorization.type'
13-
import { EMPTY_STATE_STATUS } from '../../config/constantMessaging'
13+
import { EMPTY_STATE_STATUS } from '../../../../config/constantMessaging'
1414

15-
function ApiTokens() {
15+
const ApiTokens = () => {
1616
const { path } = useRouteMatch()
1717
const history = useHistory()
1818
const { pathname } = useLocation()
@@ -21,7 +21,7 @@ function ApiTokens() {
2121
const [loader, setLoader] = useState(false)
2222
const [tokenList, setTokenlist] = useState<TokenListType[]>(undefined)
2323
const [filteredTokenList, setFilteredTokenList] = useState<TokenListType[]>(undefined)
24-
const [noResults, setNoResults] = useState(false)
24+
const [, setNoResults] = useState(false)
2525
const [errorStatusCode, setErrorStatusCode] = useState(0)
2626
const [showGenerateModal, setShowGenerateModal] = useState(false)
2727
const [showRegenerateTokenModal, setShowRegenerateTokenModal] = useState(false)
@@ -46,7 +46,7 @@ function ApiTokens() {
4646
setLoader(false)
4747
})
4848
.catch((error) => {
49-
showError(error,true,true)
49+
showError(error, true, true)
5050
setErrorStatusCode(error.code)
5151
setLoader(false)
5252
})
@@ -117,7 +117,12 @@ function ApiTokens() {
117117
onKeyDown={handleFilterKeyPress}
118118
/>
119119
{searchApplied && (
120-
<button className="flex search__clear-button" type="button" onClick={clearSearch}>
120+
<button
121+
className="flex search__clear-button"
122+
type="button"
123+
onClick={clearSearch}
124+
aria-label="Clear Search"
125+
>
121126
<Clear className="icon-dim-18 icon-n4 dc__vertical-align-middle" />
122127
</button>
123128
)}
@@ -133,8 +138,8 @@ function ApiTokens() {
133138

134139
const renderAPITokenRoutes = (): JSX.Element => {
135140
return (
136-
<Fragment>
137-
<div data-testid="api-token-page" className="api-token-container bcn-0">
141+
<>
142+
<div data-testid="api-token-page" className="api-token-container flexbox-col flex-grow-1">
138143
<Switch>
139144
<Route path={`${path}/list`}>
140145
<APITokenList
@@ -171,7 +176,7 @@ function ApiTokens() {
171176
<Redirect to={`${path}/list`} />
172177
</Switch>
173178
</div>
174-
</Fragment>
179+
</>
175180
)
176181
}
177182

@@ -181,7 +186,7 @@ function ApiTokens() {
181186

182187
const renderGenerateButton = () => {
183188
return (
184-
<button className="flex cta h-32" onClick={redirectToCreate}>
189+
<button className="flex cta h-32" onClick={redirectToCreate} type="button">
185190
Generate new token
186191
</button>
187192
)
@@ -193,15 +198,17 @@ function ApiTokens() {
193198
image={emptyGeneratToken}
194199
title={EMPTY_STATE_STATUS.GENERATE_API_TOKEN.TITLE}
195200
subTitle={EMPTY_STATE_STATUS.GENERATE_API_TOKEN.SUBTITLE}
196-
isButtonAvailable={true}
201+
isButtonAvailable
197202
renderButton={renderGenerateButton}
203+
classname="flex-grow-1"
198204
/>
199205
)
200206
}
201207

202208
if (loader) {
203209
return <Progressing data-testid="api-token-page-loading" pageLoader />
204-
} else if (errorStatusCode > 0) {
210+
}
211+
if (errorStatusCode > 0) {
205212
return (
206213
<div className="error-screen-wrapper flex column h-100">
207214
<ErrorScreenManager
@@ -210,7 +217,8 @@ function ApiTokens() {
210217
/>
211218
</div>
212219
)
213-
} else if (!pathname.includes('/create') && (!tokenList || tokenList.length === 0)) {
220+
}
221+
if (!pathname.includes('/create') && (!tokenList || tokenList.length === 0)) {
214222
return renderEmptyState()
215223
}
216224
return renderAPITokenRoutes()

0 commit comments

Comments
 (0)