Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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.
3 changes: 3 additions & 0 deletions src/assets/icons/ic-slant-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
128 changes: 128 additions & 0 deletions src/components/apiTokens/APITokenList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
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)
}

const handleDeleteButton = (tokenList) => {
setSelectedToken(tokenList)
setDeleteConfirmation(true)
}
return (
<div>
<div className="cn-9 fw-6 fs-16">API tokens</div>
<p className="fs-12 fw-4">Tokens you have generated that can be used to access the Devtron API.</p>
<div className="flex content-space mb-16">
<button className="flex cta h-32" onClick={() => handleGenerateRowActionButton('create')}>
Generate new token
</button>
{renderSearchToken()}
</div>
<div className="api-token__list en-2 bw-1 bcn-0 br-8">
<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>Last used by Ip add.</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)}
>
<span className="ellipsis-right">{list.name}</span>
</div>
<div className="ellipsis-right">
{list.lastUsedAt ? moment(list.lastUsedAt).format(MomentDateFormat) : 'Never used'}
</div>
<div>{list.lastUsedByIp ? 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 right">
<button
type="button"
className="transparent mr-18"
onClick={() => handleGenerateRowActionButton('edit', list.id)}
>
<Edit className="icon-dim-20" />
</button>
<button type="button" className="transparent" onClick={() => handleDeleteButton(list)}>
<Trash className="scn-6 icon-dim-20" />
</button>
</div>
</div>
))
)}
{showDeleteConfirmation && selectedToken && (
<DeleteAPITokenModal
tokenData={selectedToken}
reload={reload}
setDeleteConfirmation={setDeleteConfirmation}
/>
)}
</div>
</div>
)
}

export default APITokenList
222 changes: 222 additions & 0 deletions src/components/apiTokens/ApiTokens.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
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() {
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 _searchTextTrimmed = _searchText.trim()
const _filteredData = tokenList.filter(
(_tokenData) =>
_tokenData.name.indexOf(_searchTextTrimmed) >= 0 || _tokenData.token.indexOf(_searchTextTrimmed) >= 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 ${searchApplied ? 'search-applied' : ''}`}
onChange={(event) => {
setSearchText(event.target.value)
}}
onKeyDown={handleFilterKeyPress}
/>
{searchApplied && (
<button className="flex 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 redirectToCreate = () => {
history.push(`${path}/create`)
}

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 are like ordinary OAuth access tokens. They can be used instead of username and
password for programmatic access to API.
</EmptyState.Subtitle>
<EmptyState.Button>
<button className="flex cta h-32" onClick={redirectToCreate}>
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