Skip to content
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
4477394
APT TOEKEN: list ui added
shivani170 Jun 10, 2022
53e4850
APP LIST: icon added on the row
shivani170 Jun 10, 2022
fece8bf
API LIST: interaction added in listing page
shivani170 Jun 10, 2022
cc1abc6
GENMERATE TOKEN: ui | info bar added
shivani170 Jun 10, 2022
a71baba
REGENERATE MODAL: created success modal
shivani170 Jun 11, 2022
bb0f20c
COODE REFACTORING: code refactoring and renaming
shivani170 Jun 13, 2022
76a8279
REGENERATE MODAL: added regenerate modal for edit page
shivani170 Jun 13, 2022
c10d9f6
EDIT API TOKEN: ui added
shivani170 Jun 13, 2022
f695e23
CREATE API TOKEN: integration with api
shivani170 Jun 13, 2022
3b39341
API TOKEN LIST: debugging while rendering list
shivani170 Jun 13, 2022
9ad0610
EDIT TOKEN: debugging of userId and selector options passed correctly
shivani170 Jun 15, 2022
77adcd2
EDIT | LIST: delete confirmation dialog box added
shivani170 Jun 15, 2022
a13dd2c
API TOKEN: search filtered added
shivani170 Jun 15, 2022
ff91ba7
EDIT TOKEN: state managed in edit token component
shivani170 Jun 15, 2022
8a9ebc9
API TOKEN: reload after delete fixed
shivani170 Jun 15, 2022
75e3014
API TOKEN: code cleaning | type defined
shivani170 Jun 15, 2022
b2a338d
REFGERATE DATA: api added in regenerated modal
shivani170 Jun 16, 2022
e113d84
renamed auth folder tp api tokens
shivani170 Jun 17, 2022
8503ae0
Quick fix for Authorisation nav item grouping
sohel-rp Jun 17, 2022
00145dd
validation added
shivani170 Jun 20, 2022
b22a7a3
HEADERS: added header and its navigation to listing
shivani170 Jun 20, 2022
8703ace
Added specific & super admin permissions section
sohel-rp Jun 20, 2022
60fd8d4
Fix - nav item selection states
sohel-rp Jun 20, 2022
a5324ef
Text area fix
sohel-rp Jun 20, 2022
0456bb7
Fix - token description text & styling
sohel-rp Jun 20, 2022
14db0c6
CREATE APP: single date picker added
shivani170 Jun 21, 2022
93a73ce
API TOKEN: conflict
shivani170 Jun 21, 2022
dd48e19
API LIST: expired css interaction added
shivani170 Jun 21, 2022
c1ab686
Merge remote-tracking branch 'origin/main' into api_token_1834
sohel-rp Jun 21, 2022
a633d16
CREATE: common expiration
shivani170 Jun 21, 2022
e9a1d23
Merge branch 'api_token_1834' of https://github.com/devtron-labs/dash…
sohel-rp Jun 21, 2022
58cb185
Multiple fixes around expiration date
sohel-rp Jun 21, 2022
8c47cf7
Fixed calendar date selection and other changes
sohel-rp Jun 21, 2022
dd86f0b
Added validations & some fixes around RegeneratedModal flow
sohel-rp Jun 21, 2022
1f804b5
minor collapsed state fix
sohel-rp Jun 21, 2022
aa2ff39
Deleted Token Modal handling
sohel-rp Jun 22, 2022
a2da3b8
Added expiration states & related handling
sohel-rp Jun 22, 2022
6cd4bbc
Showing selected group list
sohel-rp Jun 22, 2022
b5c9054
minor fixes
sohel-rp Jun 22, 2022
9022051
Merge remote-tracking branch 'origin/main' into api_token_1834
sohel-rp Jun 22, 2022
b0abf21
CSS fixes
shivani170 Jun 22, 2022
2198f27
Fixed issues around empty states
sohel-rp Jun 22, 2022
b29429f
Minor validation fixes
sohel-rp Jun 22, 2022
95f0f40
GROUP PERMISSION : css fixes
shivani170 Jun 22, 2022
8fb651b
merge
shivani170 Jun 22, 2022
3f0fef9
merge main
shivani170 Jun 22, 2022
7db9f7a
REMOVED: repetitive icon
shivani170 Jun 22, 2022
a3f90ff
minor fixes around generate token modal
sohel-rp Jun 22, 2022
0bb6853
minor comment fix
sohel-rp Jun 22, 2022
3828efc
Merge remote-tracking branch 'origin/main' into api_token_1834
sohel-rp Jun 23, 2022
f77e80f
UX review changes
sohel-rp Jun 23, 2022
58e29d3
Multiple UX review fixes
sohel-rp Jun 23, 2022
70c4793
CSS fixes
shivani170 Jun 27, 2022
851a457
APP TOKEN LIST: fixed token filter on search
shivani170 Jun 27, 2022
aa9f77b
FIXES UI
shivani170 Jun 27, 2022
5f7050e
UI FIXES
shivani170 Jun 27, 2022
60f6977
GC: SCROLL added on left panel
shivani170 Jun 27, 2022
e4e0b1e
API TOKEN CREATE: app permission dropdown disabled background css fix
shivani170 Jun 27, 2022
16dae00
TOAST: reload info background bug fix
shivani170 Jun 27, 2022
f9a7e8b
FEEDBACK: code deedback incorporated
shivani170 Jun 27, 2022
d5ebbb9
EDIT: description validation added
shivani170 Jun 27, 2022
3c4d5b6
code feedbck
shivani170 Jun 27, 2022
a89c7b6
SingleDatePicker - Fixed disabled color codes for today
sohel-rp Jun 27, 2022
a5b2142
APP LIST: last used by statte fixed
shivani170 Jun 28, 2022
2ee8226
merge
shivani170 Jun 28, 2022
f8f9778
APP LIST & UTILS: code refactoring | css fixes
shivani170 Jun 28, 2022
e6ff7e2
API TOKEN: CODE REFACTORING
shivani170 Jun 28, 2022
0a05dae
VALIDATION: name calidation fixed
shivani170 Jun 28, 2022
f5b37c2
VALIDATION: messaging fixed
shivani170 Jun 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/assets/icons/ic-key-bulb.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icons/ic-success-outline.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/ic-empty-generate-token.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/CustomChart/CustomChartList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export default function CustomChartList() {
)
}
if (loader) {
return <Progressing />
return <Progressing pageLoader />
}
if (errorStatusCode > 0) {
return (
Expand Down
132 changes: 132 additions & 0 deletions src/components/apiTokens/APITokenList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import moment from 'moment'
import React, { useState } from 'react'
import { MomentDateFormat } from '../../config'
import { ReactComponent as Key } from '../../assets/icons/ic-key-bulb.svg'
import { ReactComponent as Edit } from '../../assets/icons/ic-pencil.svg'
import { ReactComponent as Trash } from '../../assets/icons/ic-delete-interactive.svg'
import { useHistory } from 'react-router-dom'
import { APITokenListType, TokenListType } from './authorization.type'
import { isTokenExpired } from './authorization.utils'
import DeleteAPITokenModal from './DeleteAPITokenModal'
import NoResults from '../../assets/img/[email protected]'
import './apiToken.scss'
import EmptyState from '../EmptyState/EmptyState'

function NoMatchingResults() {
return (
<EmptyState>
<EmptyState.Image>
<img src={NoResults} width="250" height="200" alt="No matching results" />
</EmptyState.Image>
<EmptyState.Title>
<h2 className="fs-16 fw-4 c-9">No matching results</h2>
</EmptyState.Title>
<EmptyState.Subtitle>We couldn't find any matching token</EmptyState.Subtitle>
</EmptyState>
)
}

function APITokenList({ tokenList, renderSearchToken, reload }: APITokenListType) {
const history = useHistory()
const [showDeleteConfirmation, setDeleteConfirmation] = useState(false)
const [selectedToken, setSelectedToken] = useState<TokenListType>()

const handleGenerateRowActionButton = (key: 'create' | 'edit', id?) => {
history.push(id ? `${key}/${id}` : key)
}

return (
<div>
<div className="cn-9 fw-6 fs-16">API tokens</div>
<p className="fs-13 fw-4">Tokens you have generated that can be used to access the Devtron API.</p>
<div className="flex content-space">
<button className="flex cta h-32" onClick={() => handleGenerateRowActionButton('create')}>
Generate new token
</button>
{renderSearchToken()}
</div>
<div
className="mt-16 en-2 bw-1 bcn-0 br-8"
style={{ minHeight: 'calc(100vh - 235px)', overflow: 'hidden' }}
>
<div className="api-list-row fw-6 cn-7 fs-12 border-bottom pt-10 pb-10 pr-20 pl-20 text-uppercase">
<div></div>
<div>Name</div>
<div>Last Used On</div>
<div>Ip address</div>
<div>Expires on</div>
<div></div>
</div>
{!tokenList || tokenList.length === 0 ? (
<NoMatchingResults />
) : (
tokenList.map((list, index) => (
<div
key={`api_${index}`}
className="api-list-row flex-align-center fw-4 cn-9 fs-13 pr-20 pl-20"
style={{ height: '45px' }}
>
<button
type="button"
className="transparent cursor flex"
onClick={() => handleGenerateRowActionButton('edit', list.id)}
>
<Key
className={`api-key-icon icon-dim-20 ${
isTokenExpired(list.expireAtInMs) ? 'api-key-expired-icon' : ''
}`}
/>
</button>
<div
className={`flexbox cb-5 cursor`}
onClick={() => handleGenerateRowActionButton('edit', list.id)}
>
{list.name}
</div>
<div className="ellipsis-right">{moment(list.lastUsedAt).format(MomentDateFormat)}</div>
<div>{list.lastUsedByIp}</div>
<div className={`${isTokenExpired(list.expireAtInMs) ? 'cr-5' : ''}`}>
{list.expireAtInMs === 0 ? (
'No expiration date'
) : (
<>
{isTokenExpired(list.expireAtInMs) ? 'Expired on ' : ''}
{moment(list.expireAtInMs).format(MomentDateFormat)}
</>
)}
</div>
<div className="api__row-actions flex">
<button
type="button"
className="transparent mr-8 ml-8"
onClick={() => handleGenerateRowActionButton('edit', list.id)}
>
<Edit className="icon-dim-20" />
</button>
<button
type="button"
className="transparent"
onClick={() => {
setSelectedToken(list)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should create methods instead of doing it inline

setDeleteConfirmation(true)
}}
>
<Trash className="scn-6 icon-dim-20" />
</button>
</div>
</div>
))
)}
{showDeleteConfirmation && selectedToken && (
<DeleteAPITokenModal
tokenData={selectedToken}
reload={reload}
setDeleteConfirmation={setDeleteConfirmation}
/>
)}
</div>
</div>
)
}

export default APITokenList
215 changes: 215 additions & 0 deletions src/components/apiTokens/ApiTokens.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import React, { Fragment, useEffect, useState } from 'react'
import './apiToken.scss'
import { ReactComponent as Search } from '../../assets/icons/ic-search.svg'
import { ReactComponent as Clear } from '../../assets/icons/ic-error.svg'
import { getGeneratedAPITokenList } from './service'
import { showError, Progressing, ErrorScreenManager, useAsync } from '../common'
import EmptyState from '../EmptyState/EmptyState'
import emptyGeneratToken from '../../assets/img/ic-empty-generate-token.png'
import { Redirect, Route, Switch, useHistory, useLocation, useRouteMatch } from 'react-router-dom'
import APITokenList from './APITokenList'
import CreateAPIToken from './CreateAPIToken'
import EditAPIToken from './EditAPIToken'
import { TokenListType, TokenResponseType } from './authorization.type'

function ApiTokens({ reloadLists }) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we using this reloadLists?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No! removed

const { path } = useRouteMatch()
const history = useHistory()
const { pathname } = useLocation()
const [searchText, setSearchText] = useState('')
const [searchApplied, setSearchApplied] = useState(false)
const [loader, setLoader] = useState(false)
const [tokenList, setTokenlist] = useState<TokenListType[]>(undefined)
const [filteredTokenList, setFilteredTokenList] = useState<TokenListType[]>(undefined)
const [noResults, setNoResults] = useState(false)
const [errorStatusCode, setErrorStatusCode] = useState(0)
const [showGenerateModal, setShowGenerateModal] = useState(false)
const [showRegenerateTokenModal, setShowRegenerateTokenModal] = useState(false)
const [copied, setCopied] = useState(false)
const [selectedExpirationDate, setSelectedExpirationDate] = useState<{ label: string; value: number }>({
label: '30 days',
value: 30,
})

const getData = (): void => {
setLoader(true)
getGeneratedAPITokenList()
.then((response) => {
if (response.result) {
const sortedResult = response.result.sort((a, b) => a['name'].localeCompare(b['name']))
setTokenlist(sortedResult)
setFilteredTokenList(sortedResult)
} else {
setTokenlist([])
setFilteredTokenList([])
}
setLoader(false)
})
.catch((error) => {
showError(error)
setErrorStatusCode(error.code)
setLoader(false)
})
}

useEffect(() => {
// TODO: Revisit. Temp check
if (
pathname.includes('/devtron-apps') ||
pathname.includes('/helm-apps') ||
pathname.includes('/chart-groups')
) {
history.replace(pathname.split('/').slice(0, -1).join('/'))
}

getData()
}, [])

const handleFilterChanges = (_searchText: string): void => {
const _filteredData = tokenList.filter((token) => token.name.indexOf(_searchText) >= 0)
setFilteredTokenList(_filteredData)
setNoResults(_filteredData.length === 0)
}

const clearSearch = (): void => {
if (searchApplied) {
handleFilterChanges('')
setSearchApplied(false)
}
setSearchText('')
}

const handleFilterKeyPress = (event): void => {
const theKeyCode = event.key
if (theKeyCode === 'Enter') {
handleFilterChanges(event.target.value)
setSearchApplied(!!event.target.value)
} else if (theKeyCode === 'Backspace' && searchText.length === 1) {
clearSearch()
}
}

const [tokenResponse, setTokenResponse] = useState<TokenResponseType>({
success: false,
token: '',
userId: 0,
userIdentifier: 'API-TOKEN:test',
})

const renderSearchToken = () => {
return (
<div className="flexbox content-space">
<div className="search position-rel en-2 bw-1 br-4 h-32">
<Search className="search__icon icon-dim-18" />
<input
type="text"
placeholder="Search Token"
value={searchText}
className="search__input bcn-0"
onChange={(event) => {
setSearchText(event.target.value)
}}
onKeyDown={handleFilterKeyPress}
/>
{searchApplied && (
<button className="search__clear-button" type="button" onClick={clearSearch}>
<Clear className="icon-dim-18 icon-n4 vertical-align-middle" />
</button>
)}
</div>
</div>
)
}

const handleActionButton = () => {
setShowGenerateModal(false)
setShowRegenerateTokenModal(false)
}

const renderAPITokenRoutes = (): JSX.Element => {
return (
<Fragment>
<div className="api-token-container">
<Switch>
<Route path={`${path}/list`}>
<APITokenList
tokenList={filteredTokenList}
renderSearchToken={renderSearchToken}
reload={getData}
/>
</Route>
<Route path={`${path}/create`}>
<CreateAPIToken
setShowGenerateModal={setShowGenerateModal}
showGenerateModal={showGenerateModal}
handleGenerateTokenActionButton={handleActionButton}
setSelectedExpirationDate={setSelectedExpirationDate}
selectedExpirationDate={selectedExpirationDate}
tokenResponse={tokenResponse}
setTokenResponse={setTokenResponse}
reload={getData}
/>
</Route>
<Route path={`${path}/edit/:id`}>
<EditAPIToken
handleRegenerateActionButton={handleActionButton}
setShowRegeneratedModal={setShowRegenerateTokenModal}
showRegeneratedModal={showRegenerateTokenModal}
setSelectedExpirationDate={setSelectedExpirationDate}
selectedExpirationDate={selectedExpirationDate}
tokenList={tokenList}
setCopied={setCopied}
copied={copied}
reload={getData}
/>
</Route>
<Redirect to={`${path}/list`} />
</Switch>
</div>
</Fragment>
)
}

const renderEmptyState = (): JSX.Element => {
return (
<div className="flex column h-100">
<EmptyState>
<EmptyState.Image>
<img src={emptyGeneratToken} alt="Empty api token links" />
</EmptyState.Image>
<EmptyState.Title>
<h4 className="title">Generate a token to access the Devtron API</h4>
</EmptyState.Title>
<EmptyState.Subtitle>
API tokens function like ordinary OAuth access tokens. They can be used instead of a password
for Git over HTTPS, or can be used to authenticate to the API over Basic Authentication.
</EmptyState.Subtitle>
<EmptyState.Button>
<button className="flex cta h-32" onClick={() => history.push(`${path}/create`)}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid inline functions as much as possible

Generate new token
</button>
</EmptyState.Button>
</EmptyState>
</div>
)
}

if (loader) {
return <Progressing pageLoader />
} else if (errorStatusCode > 0) {
return (
<div className="error-screen-wrapper flex column h-100">
<ErrorScreenManager
code={errorStatusCode}
subtitle="Information on this page is available only to superadmin users."
/>
</div>
)
} else if (!pathname.includes('/create') && (!tokenList || tokenList.length === 0)) {
return renderEmptyState()
}

return renderAPITokenRoutes()
}

export default ApiTokens
Loading