Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ const ApiTokens = () => {

const handleFilterChanges = (_searchText: string): void => {
const _searchTextTrimmed = _searchText.trim()
const _filteredData = tokenList.filter((_tokenData) => _tokenData.name.indexOf(_searchTextTrimmed) >= 0)
const _filteredData = tokenList.filter(
(_tokenData) =>
_tokenData.name.indexOf(_searchTextTrimmed) >= 0 || _tokenData?.token?.indexOf(_searchTextTrimmed) >= 0,
)
setFilteredTokenList(_filteredData)
setNoResults(_filteredData.length === 0)
}
Expand All @@ -95,6 +98,7 @@ const ApiTokens = () => {
token: '',
userId: 0,
userIdentifier: 'API-TOKEN:test',
hideApiToken: false,
})

const renderSearchToken = () => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ const CreateAPIToken = ({
const userPermissionPayload = createUserPermissionPayload({
id: result.userId,
userIdentifier: result.userIdentifier,
hideApiToken: result.hideApiToken,
userRoleGroups,
serverMode,
directPermission,
Expand Down Expand Up @@ -315,6 +316,7 @@ const CreateAPIToken = ({
reload={reload}
redirectToTokenList={redirectToTokenList}
open={showGenerateModal}
hideApiToken={tokenResponse?.hideApiToken}
/>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Button,
ButtonStyleType,
ButtonVariantType,
ClipboardButton,
CustomInput,
Icon,
InfoBlock,
Expand Down Expand Up @@ -124,6 +125,7 @@ const EditAPIToken = ({
const userPermissionPayload = createUserPermissionPayload({
id: editData.userId,
userIdentifier: editData.userIdentifier,
hideApiToken: result.hideApiToken,
userRoleGroups,
serverMode,
directPermission,
Expand Down Expand Up @@ -243,6 +245,19 @@ const EditAPIToken = ({
placeholder="Enter a description to remember where you have used this token"
error={invalidDescription ? 'Max 350 characters allowed.' : null}
/>
{!!editData?.token?.length && (
<label className="form__row">
<span className="form__label">Token</span>
<div className="flex dc__content-space top cn-9">
<span data-testid="api-token-string" className="mono fs-14 dc__word-break">
{editData.token}
</span>
<div className="icon-dim-16 ml-8">
<ClipboardButton content={editData.token} />
</div>
</div>
</label>
)}
<div className="dc__border-top" />
<PermissionConfigurationForm showUserPermissionGroupSelector isAddMode={false} />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ import {
} from '@devtron-labs/devtron-fe-common-lib'

import { GenerateTokenModalType } from './apiToken.type'
import { getApiTokenHeader } from './apiToken.utils'

const GenerateModal = ({
close,
token,
token = '',
reload,
redirectToTokenList,
isRegenerationModal,
open,
hideApiToken = false,
}: GenerateTokenModalType) => {
const [copyToClipboardPromise, setCopyToClipboardPromise] = useState<ReturnType<typeof copyToClipboard>>(null)
const modelType = isRegenerationModal ? 'Regenerated' : 'Generated'
Expand All @@ -60,9 +62,7 @@ const GenerateModal = ({
<GenericModal.Body>
<div className="flexbox-col dc__gap-20 p-20">
<div className="flexbox-col dc__gap-4">
<h5 className="m-0 cn-9 lh-1-5 fw-6">
Copy and store this token safely, you won’t be able to view it again.
</h5>
<h5 className="m-0 cn-9 lh-1-5 fw-6">{getApiTokenHeader(hideApiToken)}</h5>
<p className="cn-7 fs-12 lh-1-5 m-0">
You can regenerate a token anytime. If you do, remember to update any scripts or
applications using the old token.
Expand All @@ -71,7 +71,11 @@ const GenerateModal = ({

<InfoBlock
heading="API Token"
description={token}
description={
<div className="fs-13 font-roboto flexbox dc__word-break" data-testid="generated-token">
{token}
</div>
}
variant="success"
customIcon={<Icon name="ic-key" color="G500" />}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ const RegeneratedModal = ({
redirectToTokenList={redirectToTokenList}
isRegenerationModal
open={showGenerateModal}
hideApiToken={tokenResponse.hideApiToken}
/>
) : (
<VisibleModal className="regenerate-token-modal">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ export interface FormType {
}
export interface TokenResponseType {
success: boolean
token: string
userId: number
userIdentifier: string
hideApiToken: boolean
token?: string
}

export interface GenerateTokenType {
Expand All @@ -42,20 +43,21 @@ export interface GenerateTokenType {
reload: () => void
}

export interface TokenListType {
export interface TokenListType extends Pick<TokenResponseType, 'token' | 'userIdentifier' | 'userId' | 'hideApiToken'> {
expireAtInMs: number
id: number
name: string
userId: number
userIdentifier: string
description: string
lastUsedByIp?: string
lastUsedAt?: string
updatedAt?: string
}

export interface EditDataType
extends Pick<TokenListType, 'name' | 'description' | 'expireAtInMs' | 'id' | 'userId' | 'userIdentifier'> {}
extends Pick<
TokenListType,
'name' | 'description' | 'expireAtInMs' | 'token' | 'id' | 'userId' | 'userIdentifier' | 'hideApiToken'
> {}
export interface EditTokenType {
setShowRegeneratedModal: React.Dispatch<React.SetStateAction<boolean>>
showRegeneratedModal: boolean
Expand All @@ -77,11 +79,12 @@ export interface GenerateActionButtonType {

export interface GenerateTokenModalType {
close: () => void
token: string
token: TokenListType['token']
reload: () => void
redirectToTokenList: () => void
isRegenerationModal?: boolean
open: GenericModalProps['open']
hideApiToken: TokenListType['hideApiToken']
}

export interface APITokenListType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* limitations under the License.
*/

import { TokenListType } from './apiToken.type'

export function getOptions(customDate) {
return [
{ value: 7, label: '7 days' },
Expand All @@ -36,3 +38,6 @@ export const isTokenExpired = (expiredDate: number): boolean => {

return getDateInMilliseconds(new Date().valueOf()) > getDateInMilliseconds(expiredDate)
}

export const getApiTokenHeader = (hideApiToken: TokenListType['hideApiToken']) =>
`Copy and store this token safely ${hideApiToken ? ', you won’t be able to view it again.' : ''}`
4 changes: 3 additions & 1 deletion src/Pages/GlobalConfigurations/Authorization/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
} from '@devtron-labs/devtron-fe-common-lib'

import { SERVER_MODE } from '../../../config'
import { TokenResponseType } from './APITokens/apiToken.type'
import { PermissionType, UserRoleType } from './constants'

export interface UserAndGroupPermissionsWrapProps {
Expand Down Expand Up @@ -311,7 +312,8 @@ export interface CreateUserPermissionPayloadParams
extends Pick<User, 'userStatus' | 'timeToLive' | 'userRoleGroups' | 'canManageAllAccess'> {
id: number
userGroups: Pick<UserGroupType, 'name' | 'userGroupId'>[]
userIdentifier: string
hideApiToken?: TokenResponseType['hideApiToken']
userIdentifier: TokenResponseType['userIdentifier']
serverMode: SERVER_MODE
directPermission: DirectPermissionsRoleFilter[]
chartPermission: ChartGroupPermissionsFilter
Expand Down
88 changes: 80 additions & 8 deletions src/components/ciPipeline/Webhook/WebhookDetailsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
stopPropagation,
ButtonStyleType,
Icon,
SelectPicker,
} from '@devtron-labs/devtron-fe-common-lib'
import { useParams } from 'react-router-dom'
import Tippy from '@tippyjs/react'
Expand All @@ -53,12 +54,15 @@ import { createGeneratedAPIToken } from '@Pages/GlobalConfigurations/Authorizati
import {
CURL_PREFIX,
GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS,
getWebhookTokenListOptions,
PLAYGROUND_TAB_LIST,
REQUEST_BODY_TAB_LIST,
RESPONSE_TAB_LIST,
SELECT_AUTO_GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS,
TOKEN_TAB_LIST,
} from './webhook.utils'
import { SchemaType, TabDetailsType, WebhookDetailsType, WebhookDetailType } from './types'
import { executeWebhookAPI, getExternalCIConfig } from './webhook.service'
import { SchemaType, TabDetailsType, TokenListOptionsType, WebhookDetailsType, WebhookDetailType } from './types'
import { executeWebhookAPI, getExternalCIConfig, getWebhookAPITokenList } from './webhook.service'
import { GENERATE_TOKEN_NAME_VALIDATION } from '../../../config/constantMessaging'
import { createUserPermissionPayload } from '@Pages/GlobalConfigurations/Authorization/utils'
import { ChartGroupPermissionsFilter } from '@Pages/GlobalConfigurations/Authorization/types'
Expand All @@ -67,6 +71,8 @@ import {
getDefaultStatusAndTimeout,
getDefaultUserStatusAndTimeout,
} from '@Pages/GlobalConfigurations/Authorization/libUtils'
import { getApiTokenHeader } from '@Pages/GlobalConfigurations/Authorization/APITokens/apiToken.utils'
import { TokenListType } from '@Pages/GlobalConfigurations/Authorization/APITokens/apiToken.type'

export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType) => {
const { appId, webhookId } = useParams<{
Expand All @@ -84,6 +90,7 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
const [selectedRequestBodyTab, setRequestBodyPlaygroundTab] = useState<string>(REQUEST_BODY_TAB_LIST[0].key)
const [webhookResponse, setWebhookResponse] = useState<Object>(null)
const [generatedAPIToken, setGeneratedAPIToken] = useState<string>(null)
const [selectedTokenTab, setSelectedTokenTab] = useState<string>(TOKEN_TAB_LIST[0].key)
const [showTokenSection, setShowTokenSection] = useState(false)
const [isSuperAdmin, setIsSuperAdmin] = useState(false)
const [samplePayload, setSamplePayload] = useState<any>(null)
Expand All @@ -94,6 +101,8 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
const [tryoutAPIToken, setTryoutAPIToken] = useState<string>(null)
const [showTryoutAPITokenError, setTryoutAPITokenError] = useState(false)
const [webhookDetails, setWebhookDetails] = useState<WebhookDetailsType>(null)
const [selectedToken, setSelectedToken] = useState<TokenListOptionsType>(null)
const [tokenList, setTokenList] = useState<TokenListType[]>(null)
const [selectedSchema, setSelectedSchema] = useState<string>('')
const [errorInGetData, setErrorInGetData] = useState(false)
const [copyToClipboardPromise, setCopyToClipboardPromise] = useState<ReturnType<typeof copyToClipboard>>(null)
Expand Down Expand Up @@ -184,13 +193,33 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
setSampleCURL(
CURL_PREFIX.replace('{webhookURL}', _webhookDetails.webhookUrl).replace('{data}', modifiedJSONString),
)
if (_isSuperAdmin) {
const { result } = await getWebhookAPITokenList(
_webhookDetails.projectName,
_webhookDetails.environmentIdentifier,
_webhookDetails.appName,
)
const sortedResult =
result
?.sort((a, b) => a['name'].localeCompare(b['name']))
.map((tokenData) => ({
...tokenData,
label: tokenData.name,
value: tokenData.id,
})) || []
setTokenList(sortedResult)
}
setLoader(false)
} catch (error) {
setIsSuperAdmin(false)
setLoader(false)
setErrorInGetData(true)
}
}

const hideApiToken = !tokenList?.[0]?.token
console.log(hideApiToken)

const generateToken = async (): Promise<void> => {
if (!tokenName) {
setTokenNameError(true)
Expand All @@ -208,6 +237,7 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
const userPermissionPayload = createUserPermissionPayload({
id: result.userId,
userIdentifier: result.userIdentifier,
hideApiToken: result.hideApiToken,
userRoleGroups: [],
serverMode: SERVER_MODE.FULL,
directPermission: [],
Expand Down Expand Up @@ -367,7 +397,7 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
return (
<div className="mt-16">
<InfoBlock
heading="Copy and store this token safely, you won’t be able to view it again."
heading={getApiTokenHeader(hideApiToken)}
description={
<div className="fs-13 font-roboto flexbox dc__word-break" data-testid="generated-api-token">
{token}
Expand Down Expand Up @@ -424,6 +454,47 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
)
}

const handleSelectedTokenChange = (selectedToken): void => {
setSelectedToken(selectedToken)
}

const renderSelectTokenSection = (): JSX.Element => (
<>
<div className="w-400 h-32 mt-16">
<SelectPicker
inputId="select-token"
name="select-token"
classNamePrefix="select-token"
placeholder="Select API token"
isClearable={false}
options={getWebhookTokenListOptions(tokenList)}
value={selectedToken}
onChange={handleSelectedTokenChange}
isSearchable={false}
/>
</div>
{selectedToken?.name && renderSelectedToken(selectedToken.token)}
</>
)

const renderGeneratedTokenDetails = () => {
if (hideApiToken) {
return (
<div className="cn-9 fs-13 mb-8">
<span className="fs-13 lh-1-5 fw-6">{GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS}</span>
{renderGenerateTokenSection()}
</div>
)
}
return (
<div>
{generateTabHeader(TOKEN_TAB_LIST, selectedTokenTab, setSelectedTokenTab)}
{selectedTokenTab === TOKEN_TAB_LIST[0].key && renderSelectTokenSection()}
{selectedTokenTab === TOKEN_TAB_LIST[1].key && renderGenerateTokenSection()}
</div>
)
}

const renderTokenSection = (): JSX.Element | null => {
if (!isSuperAdmin) {
return (
Expand All @@ -440,13 +511,14 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
dataTestId="select-or-generate-token"
variant={ButtonVariantType.text}
onClick={toggleTokenSection}
text={GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS}
text={
hideApiToken
? GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS
: SELECT_AUTO_GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS
}
/>
) : (
<div className="cn-9 fs-13 mb-8">
<span className="fs-13 lh-1-5 fw-6">{GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS}</span>
{renderGenerateTokenSection()}
</div>
renderGeneratedTokenDetails()
)
}

Expand Down
Loading