diff --git a/components/cloudProviderCard/cloudProviderCard.styled.ts b/components/cloudProviderCard/cloudProviderCard.styled.ts index 04ecb957..6c0fad8b 100644 --- a/components/cloudProviderCard/cloudProviderCard.styled.ts +++ b/components/cloudProviderCard/cloudProviderCard.styled.ts @@ -8,6 +8,7 @@ export const CardContainer = styled(Card)` max-width: 540px; min-width: 365px; padding: 24px; + height: 84px; `; export const DetailsContainer = styled.div` diff --git a/components/gitProviderButton/gitProviderButton.styled.ts b/components/gitProviderButton/gitProviderButton.styled.ts index e0f20ec1..7b89f62a 100644 --- a/components/gitProviderButton/gitProviderButton.styled.ts +++ b/components/gitProviderButton/gitProviderButton.styled.ts @@ -5,7 +5,7 @@ export const Button = styled.button<{ active?: boolean }>` justify-content: center; align-items: center; width: 260px; - height: 88px; + height: 84px; border: 2px solid #e2e8f0; border-radius: 8px; background-color: white; diff --git a/components/menu/index.tsx b/components/menu/index.tsx index 0fc45db5..081d8e2b 100644 --- a/components/menu/index.tsx +++ b/components/menu/index.tsx @@ -56,18 +56,18 @@ const Menu: FunctionComponent = ({ label, options, onClickMenu }) => sx={{ '& .MuiMenu-paper': { backgroundColor: 'white' } }} anchorOrigin={{ vertical: 'bottom', - horizontal: 'left', + horizontal: 'right', }} transformOrigin={{ vertical: 'top', - horizontal: 'left', + horizontal: 'right', }} > {options && options.map(({ label, icon, color }) => { return ( handleClickMenu(label)}> - {icon} + {icon && {icon}} {label} diff --git a/components/progress/progress.styled.tsx b/components/progress/progress.styled.tsx index b38b3988..ac5a879a 100644 --- a/components/progress/progress.styled.tsx +++ b/components/progress/progress.styled.tsx @@ -17,6 +17,9 @@ export const Label = muiStyled(StepLabel)(() => ({ [`& .${stepLabelClasses.disabled}`]: { color: theme.colors.saltboxBlue, }, + [`& .${stepLabelClasses.alternativeLabel}`]: { + marginTop: '4px !important', + }, })); export const ColorlibConnector = muiStyled(StepConnector)(({ theme: muiTheme }) => ({ @@ -86,6 +89,6 @@ export const Container = styled.div` background-color: ${({ theme }) => theme.colors.childOfLight}; display: flex; justify-content: center; - padding: 25px 0; + padding: 21px 0; width: 100%; `; diff --git a/components/table/index.tsx b/components/table/index.tsx index 702351f5..a28aa97e 100644 --- a/components/table/index.tsx +++ b/components/table/index.tsx @@ -12,6 +12,7 @@ import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; import Typography from '../typography'; import Menu from '../menu'; +import { CHILD_OF_LIGHT, SALTBOX_BLUE } from '../../constants/colors'; export interface Row { [key: string]: unknown | ReactNode; @@ -42,13 +43,13 @@ const Table: FunctionComponent = ({ {cols.map((col) => ( - - + + {col?.toUpperCase()} ))} - {hasActionMenu && } + {hasActionMenu && } diff --git a/constants/cluster.ts b/constants/cluster.ts index 8f2cd222..f1e4d37f 100644 --- a/constants/cluster.ts +++ b/constants/cluster.ts @@ -1,8 +1 @@ -export const CLUSTER_MANAGEMENT_COLUMNS = [ - 'Cluster Name', - 'Status', - 'Created by', - 'Git Provider', - 'Cloud', - 'Creation Date', -]; +export const CLUSTER_MANAGEMENT_COLUMNS = ['Cluster Name', 'Status', 'Created On', 'Created by']; diff --git a/containers/clusterForms/aws/index.tsx b/containers/clusterForms/aws/index.tsx index 6aac1f67..7a03c9b1 100644 --- a/containers/clusterForms/aws/index.tsx +++ b/containers/clusterForms/aws/index.tsx @@ -5,12 +5,12 @@ import ClusterRunningMessage from '../../../components/clusterReady'; import TerminalLogs from '../../terminalLogs'; import { FormStep } from '../../../constants/installation'; import { InstallValues } from '../../../types/redux'; +import AuthForm from '../shared/authForm'; -import AwsReadinessForm from './readinessForm'; import AwsSetupForm from './setupForm'; const AWS_FORM_FLOW = { - [FormStep.AUTHENTICATION]: AwsReadinessForm, + [FormStep.AUTHENTICATION]: AuthForm, [FormStep.SETUP]: AwsSetupForm, [FormStep.PROVISIONING]: TerminalLogs, [FormStep.READY]: ClusterRunningMessage, diff --git a/containers/clusterForms/aws/readinessForm/index.tsx b/containers/clusterForms/aws/readinessForm/index.tsx deleted file mode 100644 index a4603f83..00000000 --- a/containers/clusterForms/aws/readinessForm/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { FunctionComponent, useEffect } from 'react'; -import { FormFlowProps } from 'types/provision'; -import { useAppDispatch, useAppSelector } from 'redux/store'; -import { getGithubUser, getGithubUserOrganizations } from 'redux/thunks/git.thunk'; -import ControlledAutocomplete from 'components/controlledFields/AutoComplete'; - -import ControlledTextField from '../../../../components/controlledFields/TextField'; -import { InstallValues } from '../../../../types/redux'; -import ControlledPassword from '../../../../components/controlledFields/Password'; - -const ReadinessForm: FunctionComponent> = ({ control, setValue }) => { - const dispatch = useAppDispatch(); - - const { githubUser, githubUserOrganizations, gitStateLoading } = useAppSelector( - ({ installation, git }) => ({ - currentStep: installation.installationStep, - gitStateLoading: git.isLoading, - ...git, - }), - ); - - const handleGithubTokenBlur = async (token: string) => { - try { - await dispatch(getGithubUser(token)).unwrap(); - await dispatch(getGithubUserOrganizations(token)).unwrap(); - } catch (error) { - // error processed in redux state - } - }; - - useEffect(() => { - if (githubUser?.login) { - setValue('userName', githubUser?.login); - } - }, [githubUser, setValue]); - - return ( - <> - - - ({ label: login, value: login })) - } - loading={gitStateLoading} - label="GitHub organization name" - placeholder="Select" - /> - - - ); -}; - -export default ReadinessForm; diff --git a/containers/clusterForms/civo/index.tsx b/containers/clusterForms/civo/index.tsx index 0097052d..3c86fec9 100644 --- a/containers/clusterForms/civo/index.tsx +++ b/containers/clusterForms/civo/index.tsx @@ -5,12 +5,12 @@ import ClusterRunningMessage from '../../../components/clusterReady'; import TerminalLogs from '../../terminalLogs'; import { FormStep } from '../../../constants/installation'; import { InstallValues } from '../../../types/redux'; +import AuthForm from '../shared/authForm'; -import AwsReadinessForm from './readinessForm'; import AwsSetupForm from './setupForm'; const CIVO_FORM_FLOW = { - [FormStep.AUTHENTICATION]: AwsReadinessForm, + [FormStep.AUTHENTICATION]: AuthForm, [FormStep.SETUP]: AwsSetupForm, [FormStep.PROVISIONING]: TerminalLogs, [FormStep.READY]: ClusterRunningMessage, diff --git a/containers/clusterForms/civo/readinessForm/index.tsx b/containers/clusterForms/civo/readinessForm/index.tsx deleted file mode 100644 index 10ee900b..00000000 --- a/containers/clusterForms/civo/readinessForm/index.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React, { FunctionComponent, useEffect } from 'react'; - -import ControlledPassword from '../../../../components/controlledFields/Password'; -import ControlledTextField from '../../../../components/controlledFields/TextField'; -import ControlledAutocomplete from '../../../../components/controlledFields/AutoComplete'; -import { InstallValues } from '../../../../types/redux/index'; -import { FormFlowProps } from '../../../../types/provision'; -import { useAppDispatch, useAppSelector } from '../../../../redux/store'; -import { getGithubUser, getGithubUserOrganizations } from '../../../../redux/thunks/git.thunk'; - -const CivoReadinessForm: FunctionComponent> = ({ - control, - setValue, -}) => { - const dispatch = useAppDispatch(); - - const { githubUser, githubUserOrganizations, gitStateLoading } = useAppSelector( - ({ installation, git }) => ({ - currentStep: installation.installationStep, - gitStateLoading: git.isLoading, - ...git, - }), - ); - - const handleGithubTokenBlur = async (token: string) => { - try { - await dispatch(getGithubUser(token)).unwrap(); - await dispatch(getGithubUserOrganizations(token)).unwrap(); - } catch (error) { - // error processed in redux state - } - }; - - useEffect(() => { - if (githubUser?.login) { - setValue('userName', githubUser?.login); - } - }, [githubUser, setValue]); - - return ( - <> - - - ({ label: login, value: login })) - } - loading={gitStateLoading} - label="GitHub organization name" - placeholder="Select" - /> - - - ); -}; - -export default CivoReadinessForm; diff --git a/containers/clusterForms/digitalocean/index.tsx b/containers/clusterForms/digitalocean/index.tsx index 716a7693..4f8478e4 100644 --- a/containers/clusterForms/digitalocean/index.tsx +++ b/containers/clusterForms/digitalocean/index.tsx @@ -5,12 +5,12 @@ import ClusterRunningMessage from '../../../components/clusterReady'; import TerminalLogs from '../../terminalLogs'; import { FormStep } from '../../../constants/installation'; import { InstallValues } from '../../../types/redux'; +import AuthForm from '../shared/authForm'; -import DigitalOceanReadinessForm from './readinessForm'; import DigitalOceanSetupForm from './setupForm'; const DIGITAL_OCEAN_FORM_FLOW = { - [FormStep.AUTHENTICATION]: DigitalOceanReadinessForm, + [FormStep.AUTHENTICATION]: AuthForm, [FormStep.SETUP]: DigitalOceanSetupForm, [FormStep.PROVISIONING]: TerminalLogs, [FormStep.READY]: ClusterRunningMessage, diff --git a/containers/clusterForms/digitalocean/readinessForm/index.tsx b/containers/clusterForms/digitalocean/readinessForm/index.tsx deleted file mode 100644 index fbfd95c5..00000000 --- a/containers/clusterForms/digitalocean/readinessForm/index.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React, { FunctionComponent, useEffect } from 'react'; - -import ControlledPassword from '../../../../components/controlledFields/Password'; -import ControlledTextField from '../../../../components/controlledFields/TextField'; -import ControlledAutocomplete from '../../../../components/controlledFields/AutoComplete'; -import { InstallValues } from '../../../../types/redux/index'; -import { FormFlowProps } from '../../../../types/provision'; -import { useAppDispatch, useAppSelector } from '../../../../redux/store'; -import { getGithubUser, getGithubUserOrganizations } from '../../../../redux/thunks/git.thunk'; - -const DigitalOceanReadinessForm: FunctionComponent> = ({ - control, - setValue, -}) => { - const dispatch = useAppDispatch(); - - const { githubUser, githubUserOrganizations, gitStateLoading } = useAppSelector( - ({ installation, git }) => ({ - currentStep: installation.installationStep, - gitStateLoading: git.isLoading, - ...git, - }), - ); - - const handleGithubTokenBlur = async (token: string) => { - try { - await dispatch(getGithubUser(token)).unwrap(); - await dispatch(getGithubUserOrganizations(token)).unwrap(); - } catch (error) { - // error processed in redux state - } - }; - - useEffect(() => { - if (githubUser?.login) { - setValue('userName', githubUser?.login); - } - }, [githubUser, setValue]); - - return ( - <> - - - ({ label: login, value: login })) - } - loading={gitStateLoading} - label="GitHub organization name" - placeholder="Select" - /> - - - ); -}; - -export default DigitalOceanReadinessForm; diff --git a/containers/clusterForms/digitalocean/readinessForm/readinessForm.styled.ts b/containers/clusterForms/digitalocean/readinessForm/readinessForm.styled.ts deleted file mode 100644 index 1d2d8a65..00000000 --- a/containers/clusterForms/digitalocean/readinessForm/readinessForm.styled.ts +++ /dev/null @@ -1,24 +0,0 @@ -import styled from 'styled-components'; - -import Button from '../../../../components/button'; -import FormContainer from '../../../../components/formContainer'; -import { SPUN_PEARL } from '../../../../constants/colors'; - -export const Form = styled(FormContainer)` - gap: 20px; - - ${Button} { - margin-top: 20px; - } -`; - -export const Message = styled.div` - display: flex; - color: ${({ theme }) => theme.colors.volcanicSand}; - gap: 8px; - margin-top: 24px; - - & > span > svg { - color: ${SPUN_PEARL}; - } -`; diff --git a/containers/clusterForms/civo/readinessForm/readinessForm.styled.ts b/containers/clusterForms/shared/authForm/authForm.ts similarity index 100% rename from containers/clusterForms/civo/readinessForm/readinessForm.styled.ts rename to containers/clusterForms/shared/authForm/authForm.ts diff --git a/containers/clusterForms/shared/authForm/index.tsx b/containers/clusterForms/shared/authForm/index.tsx new file mode 100644 index 00000000..a339eca0 --- /dev/null +++ b/containers/clusterForms/shared/authForm/index.tsx @@ -0,0 +1,135 @@ +import { FormStep } from 'constants/installation'; + +import React, { FunctionComponent, useEffect, useMemo } from 'react'; +import { GitProvider } from 'types'; +import { useInstallation } from 'hooks/useInstallation'; + +import ControlledPassword from '../../../../components/controlledFields/Password'; +import ControlledTextField from '../../../../components/controlledFields/TextField'; +import ControlledAutocomplete from '../../../../components/controlledFields/AutoComplete'; +import { InstallValues, InstallationType } from '../../../../types/redux/index'; +import { FormFlowProps } from '../../../../types/provision'; +import { useAppDispatch, useAppSelector } from '../../../../redux/store'; +import { + getGithubUser, + getGithubUserOrganizations, + getGitlabGroups, + getGitlabUser, +} from '../../../../redux/thunks/git.thunk'; + +const AuthForm: FunctionComponent> = ({ control, setValue }) => { + const dispatch = useAppDispatch(); + + const { + gitProvider, + githubUser, + githubUserOrganizations, + gitlabUser, + gitlabGroups, + gitStateLoading, + installationType, + } = useAppSelector(({ installation, git }) => ({ + currentStep: installation.installationStep, + installationType: installation.installType, + gitProvider: installation.gitProvider, + gitStateLoading: git.isLoading, + ...git, + })); + + const { apiKeyInfo } = useInstallation( + installationType as InstallationType, + gitProvider as GitProvider, + FormStep.AUTHENTICATION, + ); + const isGitHub = useMemo(() => gitProvider === GitProvider.GITHUB, [gitProvider]); + + const handleGitTokenBlur = async (token: string) => { + try { + if (isGitHub) { + await dispatch(getGithubUser(token)).unwrap(); + await dispatch(getGithubUserOrganizations(token)).unwrap(); + } else { + await dispatch(getGitlabUser(token)).unwrap(); + await dispatch(getGitlabGroups(token)).unwrap(); + } + } catch (error) { + // error processed in redux state + } + }; + + const gitLabel = useMemo(() => (isGitHub ? 'GitHub' : 'GitLab'), [isGitHub]); + + useEffect(() => { + if (githubUser?.login || gitlabUser?.name) { + setValue('userName', githubUser?.login || gitlabUser?.name); + } + }, [githubUser, gitlabUser, setValue]); + + return ( + <> + + + {isGitHub ? ( + ({ label: login, value: login })) + } + loading={gitStateLoading} + label="GitHub organization name" + placeholder="Select" + /> + ) : ( + ({ label: name, value: path })) + } + loading={gitStateLoading} + label="GitLab group name" + placeholder="Select" + /> + )} + + + ); +}; + +export default AuthForm; diff --git a/containers/clusterForms/vultr/index.tsx b/containers/clusterForms/vultr/index.tsx index 23b9c6ee..7cfbf479 100644 --- a/containers/clusterForms/vultr/index.tsx +++ b/containers/clusterForms/vultr/index.tsx @@ -5,12 +5,12 @@ import ClusterRunningMessage from '../../../components/clusterReady'; import TerminalLogs from '../../terminalLogs'; import { FormStep } from '../../../constants/installation'; import { InstallValues } from '../../../types/redux'; +import AuthForm from '../shared/authForm'; -import AwsReadinessForm from './readinessForm'; import AwsSetupForm from './setupForm'; const VULTR_FORM_FLOW = { - [FormStep.AUTHENTICATION]: AwsReadinessForm, + [FormStep.AUTHENTICATION]: AuthForm, [FormStep.SETUP]: AwsSetupForm, [FormStep.PROVISIONING]: TerminalLogs, [FormStep.READY]: ClusterRunningMessage, diff --git a/containers/clusterForms/vultr/readinessForm/index.tsx b/containers/clusterForms/vultr/readinessForm/index.tsx deleted file mode 100644 index 8d1207ef..00000000 --- a/containers/clusterForms/vultr/readinessForm/index.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React, { FunctionComponent, useEffect } from 'react'; - -import ControlledPassword from '../../../../components/controlledFields/Password'; -import ControlledTextField from '../../../../components/controlledFields/TextField'; -import ControlledAutocomplete from '../../../../components/controlledFields/AutoComplete'; -import { InstallValues } from '../../../../types/redux/index'; -import { FormFlowProps } from '../../../../types/provision'; -import { useAppDispatch, useAppSelector } from '../../../../redux/store'; -import { getGithubUser, getGithubUserOrganizations } from '../../../../redux/thunks/git.thunk'; - -const CivoReadinessForm: FunctionComponent> = ({ - control, - setValue, -}) => { - const dispatch = useAppDispatch(); - - const { githubUser, githubUserOrganizations, gitStateLoading } = useAppSelector( - ({ installation, git }) => ({ - currentStep: installation.installationStep, - gitStateLoading: git.isLoading, - ...git, - }), - ); - - const handleGithubTokenBlur = async (token: string) => { - try { - await dispatch(getGithubUser(token)).unwrap(); - await dispatch(getGithubUserOrganizations(token)).unwrap(); - } catch (error) { - // error processed in redux state - } - }; - - useEffect(() => { - if (githubUser?.login) { - setValue('userName', githubUser?.login); - } - }, [githubUser, setValue]); - - return ( - <> - - - ({ label: login, value: login })) - } - loading={gitStateLoading} - label="GitHub organization name" - placeholder="Select" - /> - - - ); -}; - -export default CivoReadinessForm; diff --git a/containers/clusterForms/vultr/readinessForm/readinessForm.styled.ts b/containers/clusterForms/vultr/readinessForm/readinessForm.styled.ts deleted file mode 100644 index 1d2d8a65..00000000 --- a/containers/clusterForms/vultr/readinessForm/readinessForm.styled.ts +++ /dev/null @@ -1,24 +0,0 @@ -import styled from 'styled-components'; - -import Button from '../../../../components/button'; -import FormContainer from '../../../../components/formContainer'; -import { SPUN_PEARL } from '../../../../constants/colors'; - -export const Form = styled(FormContainer)` - gap: 20px; - - ${Button} { - margin-top: 20px; - } -`; - -export const Message = styled.div` - display: flex; - color: ${({ theme }) => theme.colors.volcanicSand}; - gap: 8px; - margin-top: 24px; - - & > span > svg { - color: ${SPUN_PEARL}; - } -`; diff --git a/containers/clusterManagement/index.tsx b/containers/clusterManagement/index.tsx index fde99c1c..6a2600b2 100644 --- a/containers/clusterManagement/index.tsx +++ b/containers/clusterManagement/index.tsx @@ -1,51 +1,24 @@ import React, { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'; -import { Box, Snackbar, Tabs } from '@mui/material'; -import DeleteIcon from '@mui/icons-material/Delete'; +import { Box, Snackbar } from '@mui/material'; import moment from 'moment'; import { useRouter } from 'next/router'; -import Image, { StaticImageData } from 'next/image'; -import { GitProvider } from 'types'; -import ClusterMap from '../../components/clusterMap'; import Button from '../../components/button'; import Typography from '../../components/typography'; -import TabPanel, { Tab, a11yProps } from '../../components/tab'; import Table, { Row } from '../../components/table'; -import { BISCAY, FIRE_BRICK } from '../../constants/colors'; +import { FIRE_BRICK } from '../../constants/colors'; import { CLUSTER_MANAGEMENT_COLUMNS } from '../../constants/cluster'; import { useAppDispatch, useAppSelector } from '../../redux/store'; import { deleteCluster, getCluster, getClusters } from '../../redux/thunks/cluster'; import { resetInstallState } from '../../redux/slices/installation.slice'; import { setConfigValues } from '../../redux/slices/config.slice'; -import { InstallationType } from '../../types/redux'; import { ClusterProps } from '../../types/provision'; -import GitLabLogo from '../../assets/gitlab.svg'; -import GitHubLogo from '../../assets/github.svg'; -import k3dLogo from '../../assets/k3d_logo.svg'; -import awsLogo from '../../assets/aws_logo.svg'; -import civoLogo from '../../assets/civo_logo.svg'; -import digitalOceanLogo from '../../assets/digital_ocean_logo.svg'; -import vultrLogo from '../../assets/vultr_logo.svg'; import { Container, Content, Description, Header, LearnMoreLink } from './clusterManagement.styled'; -const DELETE_OPTION = 'Delete'; -const MENU_OPTIONS = [ - { label: DELETE_OPTION, icon: , color: FIRE_BRICK }, -]; - -const GIT_PROVIDERS: { [key: string]: StaticImageData } = { - [GitProvider.GITLAB]: GitLabLogo, - [GitProvider.GITHUB]: GitHubLogo, -}; - -const CLOUDS: { [key: string]: StaticImageData } = { - [InstallationType.AWS]: awsLogo, - [InstallationType.CIVO]: civoLogo, - [InstallationType.LOCAL]: k3dLogo, - [InstallationType.DIGITAL_OCEAN]: digitalOceanLogo, - [InstallationType.VULTR]: vultrLogo, -}; +const VIEW_DETAILS_OPTION = 'View details'; +const DELETE_OPTION = 'Delete cluster'; +const MENU_OPTIONS = [{ label: VIEW_DETAILS_OPTION }, { label: DELETE_OPTION, color: FIRE_BRICK }]; export interface ClusterManagementProps { apiUrl: string; @@ -54,17 +27,12 @@ export interface ClusterManagementProps { const ClusterManagement: FunctionComponent = ({ apiUrl, useTelemetry }) => { const [selectedCluster, setSelectedCluster] = useState(''); - const [activeTab, setActiveTab] = useState(0); const interval = useRef(); const { push } = useRouter(); const dispatch = useAppDispatch(); const { isDeleted, isDeleting, isError, clusters } = useAppSelector(({ cluster }) => cluster); - const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { - setActiveTab(newValue); - }; - const handleMenuClick = (option: string, rowItem: Row) => { const { clusterName } = rowItem; if (option === DELETE_OPTION) { @@ -75,6 +43,7 @@ const ClusterManagement: FunctionComponent = ({ apiUrl, const handleDeleteCluster = (clusterName: string) => { dispatch(deleteCluster({ apiUrl, clusterName })).unwrap(); + handleGetClusters(); }; const handleCreateCluster = async () => { @@ -132,49 +101,29 @@ const ClusterManagement: FunctionComponent = ({ apiUrl, - - - - - - ({ - id: ClusterName, - clusterName: ClusterName, - Status, - GitUser, - GitProvider: , - Cloud: , - CreationTimestamp: moment(new Date(CreationTimestamp)).format( - 'YYYY MMM DD, h:mm:ss a', - ), - }), - ) - } - /> - - - - +
({ + id: ClusterName, + clusterName: ClusterName, + Status, + CreationTimestamp: moment(new Date(CreationTimestamp)).format( + 'YYYY MMM DD, h:mm:ss', + ), + GitUser, + })) + } + /> ; + reset: UseFormReset; } export const InstallationsSelection: FunctionComponent = ({ steps, + reset, }) => { const dispatch = useAppDispatch(); @@ -36,8 +40,10 @@ export const InstallationsSelection: FunctionComponent { dispatch(setInstallType(type)); + dispatch(clearGitState()); + reset(); }, - [dispatch], + [dispatch, reset], ); const handleGitProviderChange = useCallback( diff --git a/containers/provision/index.tsx b/containers/provision/index.tsx index bd916e89..a169276f 100644 --- a/containers/provision/index.tsx +++ b/containers/provision/index.tsx @@ -1,5 +1,5 @@ import React, { FunctionComponent, useCallback, useEffect, useMemo } from 'react'; -import { FieldValues, useForm } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; import { createCluster } from '../../redux/thunks/cluster'; import InstallationStepContainer from '../../components/installationStepContainer'; @@ -12,7 +12,7 @@ import { } from '../../redux/slices/installation.slice'; import { useAppDispatch, useAppSelector } from '../../redux/store'; import { useInstallation } from '../../hooks/useInstallation'; -import { InstallationType } from '../../types/redux'; +import { InstallValues, InstallationType } from '../../types/redux'; import { GitProvider } from '../../types'; import { setConfigValues } from '../../redux/slices/config.slice'; @@ -48,6 +48,7 @@ const Provision: FunctionComponent = ({ apiUrl, useTelemetry }) setValue, trigger, watch, + reset, } = useForm({ mode: 'onChange' }); const installTitle = useMemo( @@ -70,7 +71,7 @@ const Provision: FunctionComponent = ({ apiUrl, useTelemetry }) dispatch(setInstallationStep(installationStep - 1)); }, [dispatch, installationStep]); - const onSubmit = async (values: FieldValues) => { + const onSubmit = async (values: InstallValues) => { if (isValid) { dispatch(setInstallValues(values)); @@ -81,9 +82,9 @@ const Provision: FunctionComponent = ({ apiUrl, useTelemetry }) cloud_provider: installType?.toString(), cloud_region: values.cloudRegion, domain_name: values.domainName, - git_owner: values?.githubOrganization, + git_owner: values?.gitOwner, git_provider: gitProvider, - git_token: values?.githubToken, + git_token: values?.gitToken, type: 'mgmt', }; @@ -107,7 +108,7 @@ const Provision: FunctionComponent = ({ apiUrl, useTelemetry }) }, [dispatch, useTelemetry, apiUrl]); return installationStep === 0 ? ( - + ) : (
{ + const apiKeyInfo: Record = { + [InstallationType.LOCAL]: null, + [InstallationType.AWS]: { + label: 'AWS API key', + }, + [InstallationType.CIVO]: { + label: 'CIVO API key', + helperText: 'Retrieve your key at https://dashboard.civo.com/security', + }, + [InstallationType.DIGITAL_OCEAN]: { + label: 'DigitalOcean authentication token', + helperText: + 'Create your token by following the instructions at https://cloud.digitalocean.com/account/api', + }, + [InstallationType.VULTR]: { + label: 'Vultr API key', + helperText: 'Retrieve your key at https://my.vultr.com/settings/#settingsapi', + }, + }; + + return apiKeyInfo[type]; +}; + export function useInstallation(type: InstallationType, gitProvider: GitProvider, step: number) { const formByType = useMemo(() => { return FormFlowByType[type]; @@ -137,5 +161,6 @@ export function useInstallation(type: InstallationType, gitProvider: GitProvider info: getInfoByType(type, step), isProvisionStep: getIsProvisionStep(type, step), formFlow: formByType as FunctionComponent>, + apiKeyInfo: getApiKeyInfo(type), }; } diff --git a/pages/index.tsx b/pages/index.ts similarity index 95% rename from pages/index.tsx rename to pages/index.ts index a8a9bc72..a2f29fe9 100644 --- a/pages/index.tsx +++ b/pages/index.ts @@ -1,4 +1,4 @@ -import React, { FunctionComponent, useEffect, useState } from 'react'; +import { FunctionComponent, useEffect } from 'react'; import { PostHog } from 'posthog-node'; import { GetServerSideProps } from 'next'; import { useRouter } from 'next/router'; diff --git a/pages/provision.tsx b/pages/provision.tsx index 31ce1008..c4d27857 100644 --- a/pages/provision.tsx +++ b/pages/provision.tsx @@ -1,8 +1,24 @@ -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, useEffect } from 'react'; +import { useRouter } from 'next/router'; +import useFeatureFlag from '../hooks/useFeatureFlag'; import Provision, { ProvisionProps } from '../containers/provision'; const ProvisionPage: FunctionComponent = ({ apiUrl, useTelemetry }) => { + const { push } = useRouter(); + + const { flagsAreReady } = useFeatureFlag('cluster-management'); + + useEffect(() => { + if (!flagsAreReady) { + push('/'); + } + }); + + if (!flagsAreReady) { + return null; + } + return ; }; diff --git a/redux/slices/git.slice.ts b/redux/slices/git.slice.ts index a053d99c..f337bfa9 100644 --- a/redux/slices/git.slice.ts +++ b/redux/slices/git.slice.ts @@ -1,11 +1,19 @@ import { createSlice } from '@reduxjs/toolkit'; +import { GitLabGroup, GitLabUser } from 'types/gitlab'; import { GithubUser, GithubUserOrganization } from '../../types/github/index'; -import { getGithubUser, getGithubUserOrganizations } from '../thunks/git.thunk'; +import { + getGithubUser, + getGithubUserOrganizations, + getGitlabGroups, + getGitlabUser, +} from '../thunks/git.thunk'; export interface GitState { githubUser: GithubUser | null; - githubUserOrganizations: GithubUserOrganization[]; + githubUserOrganizations: Array; + gitlabUser: GitLabUser | null; + gitlabGroups: Array; isLoading: boolean; isTokenValid: boolean; error: string | null; @@ -14,6 +22,8 @@ export interface GitState { export const initialState: GitState = { githubUser: null, githubUserOrganizations: [], + gitlabUser: null, + gitlabGroups: [], isLoading: false, isTokenValid: false, error: null, @@ -26,9 +36,19 @@ const gitSlice = createSlice({ clearUserError: (state) => { state.error = null; }, + clearGitState: (state) => { + state.githubUser = null; + state.githubUserOrganizations = []; + state.gitlabUser = null; + state.gitlabGroups = []; + state.isLoading = false; + state.isTokenValid = false; + state.error = null; + }, }, extraReducers: (builder) => { builder + /* GitHub */ .addCase(getGithubUser.fulfilled, (state, action) => { state.githubUser = action.payload; state.isTokenValid = true; @@ -49,10 +69,30 @@ const gitSlice = createSlice({ ); state.isLoading = false; state.isTokenValid = true; + }) + /* GitLab */ + .addCase(getGitlabUser.fulfilled, (state, action) => { + state.gitlabUser = action.payload; + state.isTokenValid = true; + }) + .addCase(getGitlabUser.rejected, (state, action) => { + state.error = action.error.message ?? 'Failed to get user'; + }) + .addCase(getGitlabGroups.pending, (state) => { + state.isLoading = true; + }) + .addCase(getGitlabGroups.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message ?? 'Failed to get user groups'; + }) + .addCase(getGitlabGroups.fulfilled, (state, action) => { + state.gitlabGroups = action.payload.sort((a, b) => a.name.localeCompare(b.name)); + state.isLoading = false; + state.isTokenValid = true; }); }, }); -export const { clearUserError } = gitSlice.actions; +export const { clearGitState, clearUserError } = gitSlice.actions; export const gitReducer = gitSlice.reducer; diff --git a/redux/thunks/git.thunk.ts b/redux/thunks/git.thunk.ts index 890117e2..4f9b678c 100644 --- a/redux/thunks/git.thunk.ts +++ b/redux/thunks/git.thunk.ts @@ -1,6 +1,8 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; +import { GitLabGroup, GitLabUser } from 'types/gitlab'; -import { githubApi } from '../../api'; +import { githubApi } from '../../services/github'; +import { gitlabApi } from '../../services/gitlab'; import { GithubUser, GithubUserOrganization } from '../../types/github'; export const getGithubUser = createAsyncThunk( @@ -28,3 +30,27 @@ export const getGithubUserOrganizations = createAsyncThunk( + 'git/getGitlabUser', + async (token) => { + return ( + await gitlabApi.get('/user', { + headers: { Authorization: `Bearer ${token}` }, + }) + ).data; + }, +); + +export const getGitlabGroups = createAsyncThunk( + 'git/getGitlabGroups', + async (token) => { + return ( + await gitlabApi.get('/groups', { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + ).data; + }, +); diff --git a/api/index.ts b/services/github/index.ts similarity index 100% rename from api/index.ts rename to services/github/index.ts diff --git a/services/gitlab/index.ts b/services/gitlab/index.ts new file mode 100644 index 00000000..f29c0aa0 --- /dev/null +++ b/services/gitlab/index.ts @@ -0,0 +1,5 @@ +import axios from 'axios'; + +export const gitlabApi = axios.create({ + baseURL: 'https://gitlab.com/api/v4', +}); diff --git a/types/gitlab/index.ts b/types/gitlab/index.ts new file mode 100644 index 00000000..96735cf7 --- /dev/null +++ b/types/gitlab/index.ts @@ -0,0 +1,13 @@ +export type GitLabGroup = { + id: number; + name: string; + path: string; + description: string; +}; + +export type GitLabUser = { + id: number; + username: string; + name: string; + commit_email: string; +}; diff --git a/types/redux/index.ts b/types/redux/index.ts index 3fbf7b6d..e1ed1ba8 100644 --- a/types/redux/index.ts +++ b/types/redux/index.ts @@ -1,11 +1,9 @@ -import { GithubUserOrganization } from '../github'; - -export interface GithubValues { - githubToken?: string; - githubOrganization?: GithubUserOrganization['login']; +export interface GitValues { + gitToken?: string; + gitOwner?: string; } -export interface LocalInstallValues extends GithubValues { +export interface LocalInstallValues extends GitValues { gitOpsBranch?: string; templateRepoUrl?: string; } @@ -27,17 +25,15 @@ export interface AwsInstallValues { export type AwsClusterValues = AwsInstallValues & ClusterValues; -export type AwsGithubClusterValues = AwsClusterValues & GithubValues; +export type AwsGithubClusterValues = AwsClusterValues & GitValues; export interface CivoInstallValues extends ClusterValues { civoToken?: string; - githubToken?: string; userName?: string; - githubOrganization?: string; domainName?: string; } export type CivoClusterValues = CivoInstallValues & ClusterValues; -export type CivoGithubClusterValues = CivoClusterValues & GithubValues; +export type CivoGithubClusterValues = CivoClusterValues & GitValues; export type InstallValues = AwsClusterValues & LocalInstallValues & CivoInstallValues;