Skip to content

Enhance GitHub Token Configuration Modal #667

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
Binary file added web-server/public/assets/FST_permissions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions web-server/src/constants/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const ClassicStyles = [
{ height: '42px', top: '230px' },
{ height: '120px', top: '378px' },
{ height: '120px', top: '806px' }
];

export const FineGrainedStyles = [
{ height: '123px', top: '185px' },
{ height: '125px', top: '750px' },
{ height: '52px', top: '1106px' },
{ height: '125px', top: '1675px' },
{ height: '52px', top: '2030px' },
{ height: '52px', top: '2516px' },
{ height: '125px', top: '3225px' }
];
155 changes: 111 additions & 44 deletions web-server/src/content/Dashboards/ConfigureGithubModalBody.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import { LoadingButton } from '@mui/lab';
import { Divider, Link, TextField, alpha } from '@mui/material';
import {
Divider,
Link,
TextField,
alpha,
ToggleButton,
ToggleButtonGroup
} from '@mui/material';
import Image from 'next/image';
import { useSnackbar } from 'notistack';
import { FC, useCallback, useMemo } from 'react';

import { FlexBox } from '@/components/FlexBox';
import { Line } from '@/components/Text';
import { Integration } from '@/constants/integrations';
import { ClassicStyles, FineGrainedStyles } from '@/constants/style';
import { useAuth } from '@/hooks/useAuth';
import { useBoolState, useEasyState } from '@/hooks/useEasyState';
import { fetchCurrentOrg } from '@/slices/auth';
import { fetchTeams } from '@/slices/team';
import { useDispatch } from '@/store';
import { GithubTokenType } from '@/types/resources';
import {
checkGitHubValidity,
linkProvider,
getMissingPATScopes
getMissingPATScopes,
getMissingFineGrainedScopes,
getTokenType
} from '@/utils/auth';
import { checkDomainWithRegex } from '@/utils/domainCheck';
import { depFn } from '@/utils/fn';
Expand All @@ -29,6 +40,8 @@ export const ConfigureGithubModalBody: FC<{
const customDomain = useEasyState('');
const dispatch = useDispatch();
const isLoading = useBoolState();
const tokenType = useEasyState<GithubTokenType>(GithubTokenType.CLASSIC);
const isTokenValid = useBoolState(false);

const showError = useEasyState<string>('');
const showDomainError = useEasyState<string>('');
Expand All @@ -49,13 +62,34 @@ export const ConfigureGithubModalBody: FC<{

const handleChange = (e: string) => {
token.set(e);
showError.set('');
const detectedType = getTokenType(e);

if (detectedType === 'unknown') {
setError('Invalid token format');
isTokenValid.false();
} else if (detectedType !== tokenType.value) {
setError(
`Token format doesn't match selected type. Expected ${tokenType.value} token.`
);
isTokenValid.false();
} else {
showError.set('');
isTokenValid.true();
}
};

const handleDomainChange = (e: string) => {
customDomain.set(e);
showDomainError.set('');
};

const handleTokenTypeChange = (value: GithubTokenType) => {
tokenType.set(value);
token.set(''); // Reset token when switching token types
showError.set('');
isTokenValid.false();
};

const handleSubmission = useCallback(async () => {
if (!token.value) {
setError('Please enter a valid token');
Expand All @@ -80,17 +114,25 @@ export const ConfigureGithubModalBody: FC<{
return;
}

const missingScopes = await getMissingPATScopes(
token.value,
customDomain.valueRef.current
);
const missingScopes =
tokenType.value === GithubTokenType.CLASSIC
? await getMissingPATScopes(
token.value,
customDomain.valueRef.current
)
: await getMissingFineGrainedScopes(
token.value,
customDomain.valueRef.current
);

if (missingScopes.length > 0) {
setError(`Token is missing scopes: ${missingScopes.join(', ')}`);
return;
}

await linkProvider(token.value, orgId, Integration.GITHUB, {
custom_domain: customDomain.valueRef.current
custom_domain: customDomain.valueRef.current,
token_type: tokenType.value
});

dispatch(fetchCurrentOrg());
Expand All @@ -109,6 +151,7 @@ export const ConfigureGithubModalBody: FC<{
}, [
token.value,
customDomain.value,
tokenType.value,
dispatch,
enqueueSnackbar,
isLoading.false,
Expand All @@ -123,15 +166,27 @@ export const ConfigureGithubModalBody: FC<{

const focusDomainInput = useCallback(() => {
if (!customDomain.value)
document.getElementById('gitlab-custom-domain')?.focus();
else handleSubmission();
}, [customDomain.value, handleSubmission]);
document.getElementById('github-custom-domain')?.focus();
}, [customDomain.value]);

return (
<FlexBox gap2>
<FlexBox gap={2} minWidth={'400px'} col>
<FlexBox>Enter you Github token below.</FlexBox>
<FlexBox>Enter your Github token below.</FlexBox>
<FlexBox fullWidth minHeight={'80px'} col>
<ToggleButtonGroup
value={tokenType.value}
exclusive
onChange={(_, value) => value && handleTokenTypeChange(value)}
sx={{ mb: 2 }}
>
<ToggleButton value={GithubTokenType.CLASSIC}>
Classic Token
</ToggleButton>
<ToggleButton value={GithubTokenType.FINE_GRAINED}>
Fine Grained Token
</ToggleButton>
</ToggleButtonGroup>
<TextField
onKeyDown={(e) => {
if (e.key === 'Enter') {
Expand All @@ -149,7 +204,11 @@ export const ConfigureGithubModalBody: FC<{
onChange={(e) => {
handleChange(e.currentTarget.value);
}}
label="Github Personal Access Token"
label={`Github ${
tokenType.value === GithubTokenType.CLASSIC
? 'Personal Access Token'
: 'Fine Grained Token'
}`}
type="password"
/>
<Line error tiny mt={1}>
Expand All @@ -158,7 +217,11 @@ export const ConfigureGithubModalBody: FC<{
<FlexBox>
<Line tiny mt={1} primary sx={{ cursor: 'pointer' }}>
<Link
href="https://github.com/settings/tokens"
href={
tokenType.value === GithubTokenType.CLASSIC
? 'https://github.com/settings/tokens'
: 'https://github.com/settings/tokens?type=beta'
}
target="_blank"
rel="noopener noreferrer"
>
Expand All @@ -168,7 +231,11 @@ export const ConfigureGithubModalBody: FC<{
textUnderlineOffset: '2px'
}}
>
Generate new classic token
Generate new{' '}
{tokenType.value === GithubTokenType.CLASSIC
? 'classic'
: 'fine-grained'}{' '}
token
</Line>
</Link>
<Line ml={'5px'}>{' ->'}</Line>
Expand Down Expand Up @@ -217,10 +284,16 @@ export const ConfigureGithubModalBody: FC<{
<FlexBox col sx={{ opacity: 0.8 }}>
<Line>Learn more about Github</Line>
<Line>
Personal Access Token (PAT)
{tokenType.value === GithubTokenType.CLASSIC
? 'Personal Access Token (PAT)'
: 'Fine Grained Token (FGT)'}
<Link
ml={1 / 2}
href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens"
href={
tokenType.value === GithubTokenType.CLASSIC
? 'https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens'
: 'https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#fine-grained-personal-access-tokens'
}
target="_blank"
rel="noopener noreferrer"
>
Expand All @@ -231,6 +304,7 @@ export const ConfigureGithubModalBody: FC<{
<FlexBox gap={2} justifyEnd>
<LoadingButton
loading={isLoading.value}
disabled={!isTokenValid.value}
variant="contained"
onClick={handleSubmission}
>
Expand All @@ -240,12 +314,14 @@ export const ConfigureGithubModalBody: FC<{
</FlexBox>
</FlexBox>
<Divider orientation="vertical" flexItem />
<TokenPermissions />
<TokenPermissions tokenType={tokenType.value} />
</FlexBox>
);
};

const TokenPermissions = () => {
const TokenPermissions: FC<{ tokenType: GithubTokenType }> = ({
tokenType
}) => {
const imageLoaded = useBoolState(false);

const expandedStyles = useMemo(() => {
Expand All @@ -254,33 +330,16 @@ const TokenPermissions = () => {
transition: 'all 0.8s ease',
borderRadius: '12px',
opacity: 1,
width: '240px',
width: tokenType === GithubTokenType.CLASSIC ? '250px' : '790px',
position: 'absolute',
maxWidth: 'calc(100% - 48px)',
left: '24px'
};

return [
{
height: '170px',
top: '58px'
},
{
height: '42px',
top: '230px'
},
{
height: '120px',

top: '378px'
},
{
height: '120px',

top: '806px'
}
].map((item) => ({ ...item, ...baseStyles }));
}, []);
const styles =
tokenType === GithubTokenType.CLASSIC ? ClassicStyles : FineGrainedStyles;
return styles.map((style) => ({ ...style, ...baseStyles }));
}, [tokenType]);

return (
<FlexBox col gap1 maxWidth={'100%'} overflow={'auto'}>
Expand All @@ -304,10 +363,18 @@ const TokenPermissions = () => {
transition: 'all 0.8s ease',
opacity: !imageLoaded.value ? 0 : 1
}}
src="/assets/PAT_permissions.png"
src={
tokenType === GithubTokenType.CLASSIC
? '/assets/PAT_permissions.png'
: '/assets/FST_permissions.png'
}
width={816}
height={1257}
alt="PAT_permissions"
height={tokenType === GithubTokenType.CLASSIC ? 1257 : 3583}
alt={
tokenType === GithubTokenType.CLASSIC
? 'PAT_permissions'
: 'FST_permissions'
}
/>

{imageLoaded.value &&
Expand Down
5 changes: 5 additions & 0 deletions web-server/src/types/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,11 @@ export enum DeploymentSources {
WORKFLOW = 'WORKFLOW'
}

export enum GithubTokenType {
CLASSIC = 'classic',
FINE_GRAINED = 'fine-grained'
}

export type DeploymentSourceResponse = {
team_id: ID;
is_active: boolean;
Expand Down
Loading