diff --git a/ui/src/app/settings/components/project-role-edit-panel/project-role-edit-panel.tsx b/ui/src/app/settings/components/project-role-edit-panel/project-role-edit-panel.tsx index b2591b061a587..09393942398e5 100644 --- a/ui/src/app/settings/components/project-role-edit-panel/project-role-edit-panel.tsx +++ b/ui/src/app/settings/components/project-role-edit-panel/project-role-edit-panel.tsx @@ -19,8 +19,8 @@ interface ProjectRoleDefaultParams { interface ProjectRoleEditPanelProps { nameReadonly?: boolean; submit: (params: ProjectRoleParams) => any; - createJWTToken: (params: CreateJWTTokenParams) => void; - deleteJWTToken: (params: DeleteJWTTokenParams) => void; + createJWTToken: (params: CreateJWTTokenParams) => Promise; + deleteJWTToken: (params: DeleteJWTTokenParams) => Promise; hideJWTToken: () => void; token: string; getApi?: (formApi: FormApi) => void; diff --git a/ui/src/app/settings/components/project-role-jwt-tokens/project-role-jwt-tokens.scss b/ui/src/app/settings/components/project-role-jwt-tokens/project-role-jwt-tokens.scss index 110eef85457e5..c72688ba72788 100644 --- a/ui/src/app/settings/components/project-role-jwt-tokens/project-role-jwt-tokens.scss +++ b/ui/src/app/settings/components/project-role-jwt-tokens/project-role-jwt-tokens.scss @@ -3,4 +3,29 @@ right: 1em; top: 1em; cursor: pointer; -} \ No newline at end of file +} + +.project-role-jwt-tokens__id { + display: flex; + align-items: center; +} + +.project-role-jwt-tokens__id-tooltip { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.project-role-jwt-tokens__expired-token { + color: white; + font-size: 1.2em; + padding-left: 0.5em; + padding-right: 0.5em; + line-height: 1.4em; + background-color: #8FA4B1; + margin-left: 0.2em; + border-radius: 0.2em; + transform: scale(0.8); + flex: 0; + min-width: fit-content; +} diff --git a/ui/src/app/settings/components/project-role-jwt-tokens/project-role-jwt-tokens.tsx b/ui/src/app/settings/components/project-role-jwt-tokens/project-role-jwt-tokens.tsx index 51461bf92b341..c7aa6897633f8 100644 --- a/ui/src/app/settings/components/project-role-jwt-tokens/project-role-jwt-tokens.tsx +++ b/ui/src/app/settings/components/project-role-jwt-tokens/project-role-jwt-tokens.tsx @@ -1,4 +1,4 @@ -import {FormField, Tooltip} from 'argo-ui'; +import {FormField, NotificationType, Tooltip} from 'argo-ui'; import * as React from 'react'; import {Form, FormApi, Text} from 'react-form'; @@ -6,14 +6,16 @@ import {Consumer, ContextApis} from '../../../shared/context'; import {JwtToken} from '../../../shared/models'; import {CreateJWTTokenParams, DeleteJWTTokenParams} from '../../../shared/services'; import {convertExpiresInToSeconds, validExpiresIn} from '../utils'; +import {Button} from '../../../shared/components/button'; +import {ProgressPopup} from '../../../shared/components'; interface ProjectRoleJWTTokensProps { projName: string; roleName: string; tokens: JwtToken[]; token: string; - createJWTToken: (params: CreateJWTTokenParams) => void; - deleteJWTToken: (params: DeleteJWTTokenParams) => void; + createJWTToken: (params: CreateJWTTokenParams) => Promise; + deleteJWTToken: (params: DeleteJWTTokenParams) => Promise; hideJWTToken: () => void; getApi?: (formApi: FormApi) => void; } @@ -21,11 +23,32 @@ interface ProjectRoleJWTTokensProps { require('./project-role-jwt-tokens.scss'); export const ProjectRoleJWTTokens = (props: ProjectRoleJWTTokensProps) => { + const [progress, setProgress] = React.useState<{percentage: number; title: string} | null>(null); + return ( {ctx => ( -

JWT Tokens

+

+ JWT Tokens + {progress !== null && setProgress(null)} />} + +

Generate JWT tokens to bind to this role
{props.tokens && props.tokens.length > 0 && (
@@ -121,17 +144,45 @@ async function deleteJWTToken(props: ProjectRoleJWTTokensProps, iat: number, ctx } } +async function deleteJWTTokens(props: ProjectRoleJWTTokensProps, ctx: any, tokens: JwtToken[], setProgress: (progress: {percentage: number; title: string}) => void) { + const confirmed = await ctx.popup.confirm( + 'Delete Expired JWT Tokens', + `Are you sure you want to delete all ${tokens.length} expired tokens for role '${props.roleName}' in project '${props.projName}'?` + ); + if (confirmed) { + setProgress({percentage: 0, title: 'Deleting Expired Tokens'}); + let i = 0; + const promises = tokens.map(token => + props.deleteJWTToken({project: props.projName, role: props.roleName, iat: token.iat} as DeleteJWTTokenParams).then(() => { + setProgress({percentage: Number(((i / tokens.length) * 100).toFixed(2)), title: `Deleting token ${i + 1} of ${tokens.length}(${token.id})`}); + i++; + }) + ); + await Promise.all(promises); + + setProgress({percentage: 100, title: 'Delete Expired Tokens Complete'}); + } +} + function renderJWTRow(props: ProjectRoleJWTTokensProps, ctx: ContextApis, jwToken: JwtToken): React.ReactFragment { const issuedAt = new Date(jwToken.iat * 1000).toISOString(); const expiresAt = jwToken.exp == null ? 'Never' : new Date(jwToken.exp * 1000).toISOString(); + const isExpired = jwToken.exp && jwToken.exp < Date.now() / 1000; return (
- -
{jwToken.id}
-
+
+ + {jwToken.id} + + {isExpired && ( + + Expired + + )} +
{issuedAt}
diff --git a/ui/src/app/shared/components/progress/__snapshots__/progress-popup.test.tsx.snap b/ui/src/app/shared/components/progress/__snapshots__/progress-popup.test.tsx.snap index eb7de1552ea3e..65dab86932bf5 100644 --- a/ui/src/app/shared/components/progress/__snapshots__/progress-popup.test.tsx.snap +++ b/ui/src/app/shared/components/progress/__snapshots__/progress-popup.test.tsx.snap @@ -10,7 +10,11 @@ exports[`ProgressPopup.0% 1`] = `
- + + +
+ > +
+
@@ -47,11 +55,7 @@ exports[`ProgressPopup.0% 1`] = ` className="row popup-container__footer" >
@@ -68,7 +72,11 @@ exports[`ProgressPopup.50% 1`] = `
- My Title + + My Title +
+ > +
+
@@ -105,11 +117,7 @@ exports[`ProgressPopup.50% 1`] = ` className="row popup-container__footer" >
@@ -126,7 +134,11 @@ exports[`ProgressPopup.100% 1`] = `
- + + +
+ > +
+
@@ -163,11 +179,7 @@ exports[`ProgressPopup.100% 1`] = ` className="row popup-container__footer" >