diff --git a/components/button/button.styled.ts b/components/button/button.styled.ts index e112391b..9baab175 100644 --- a/components/button/button.styled.ts +++ b/components/button/button.styled.ts @@ -62,3 +62,14 @@ export const InfoButton = styled(Button)` box-shadow: none !important; color: ${({ theme }) => theme.colors.saltboxBlue}; `; + +export const TextButton = styled(Button)` + background-color: inherit; + box-shadow: none !important; + color: ${({ theme }) => theme.colors.saltboxBlue} !important; + + &:hover { + color: ${({ theme }) => theme.colors.primary} !important; + background-color: ${({ theme }) => theme.colors.magnolia}; + } +`; diff --git a/components/button/index.tsx b/components/button/index.tsx index 9a86f91b..f3695dc4 100644 --- a/components/button/index.tsx +++ b/components/button/index.tsx @@ -7,6 +7,7 @@ import { SecondaryButton, ErrorButton, InfoButton, + TextButton, } from './button.styled'; const BUTTONS_MAP = { @@ -14,10 +15,11 @@ const BUTTONS_MAP = { ['secondary']: SecondaryButton, ['error']: ErrorButton, ['info']: InfoButton, + ['text']: TextButton, }; export interface IButtonProps extends ButtonProps { - color: 'primary' | 'secondary' | 'error' | 'info'; + color: 'primary' | 'secondary' | 'error' | 'info' | 'text'; } const Button: FunctionComponent = ({ variant, color, disabled, ...rest }) => { diff --git a/components/clusterReady/clusterReady.stories.tsx b/components/clusterReady/clusterReady.stories.tsx index 73bdae36..04e0adf7 100644 --- a/components/clusterReady/clusterReady.stories.tsx +++ b/components/clusterReady/clusterReady.stories.tsx @@ -14,4 +14,5 @@ const DefaultTemplate: ComponentStory = (args) => void; } const ClusterReady: FunctionComponent = ({ clusterName, domainName, + kbotPassword, onOpenConsole, -}) => ( - - box - - <Typography variant="body1"> - Cluster <strong>{clusterName || '<cluster identifier>'}</strong> is now up and running. +}) => { + const [copyLabel, setCopyLabel] = useState<string>('Copy'); + + const handleOnCopy = () => { + setCopyLabel('Copied!'); + + setTimeout(() => setCopyLabel('Copy'), 3000); + }; + + return ( + <Container> + <Image alt="box" src="/static/box.svg" width={170} height={160} /> + <Title> + <Typography variant="body1"> + Cluster <strong>{clusterName || '<cluster identifier>'}</strong> is now up and running. + </Typography> + + + Copy this k-bot password and log into the kubefirst console UI. - - - -); + + + + + + + + + ); +}; export default ClusterReady; diff --git a/components/password/index.tsx b/components/password/index.tsx index f15f4979..4cd51ee4 100644 --- a/components/password/index.tsx +++ b/components/password/index.tsx @@ -8,7 +8,7 @@ import TextField from '../textField'; import { InputAdornmentContainer } from './password.styled'; export interface PasswordProps extends InputProps { - label: string; + label?: string; helperText?: string; } diff --git a/components/textField/index.tsx b/components/textField/index.tsx index 4ab32053..c592e10d 100644 --- a/components/textField/index.tsx +++ b/components/textField/index.tsx @@ -7,7 +7,7 @@ import Typography from '../typography'; import { Container, FormHelperText, InputAdornmentError, Required } from './textField.styled'; export interface TextFieldProps extends InputProps { - label: string; + label?: string; helperText?: string; } @@ -48,11 +48,13 @@ const TextField: FunctionComponent = ({ return ( - - - {label} {required && *} - - + {label && ( + + + {label} {required && *} + + + )} = ({ currentStep }) => { - const ActiveFormStep = AWS_FORM_FLOW[currentStep]; - - if (!ActiveFormStep) { - return null; - } - - return ; -}; diff --git a/containers/clusterForms/civo/index.tsx b/containers/clusterForms/civo/index.tsx deleted file mode 100644 index 16754864..00000000 --- a/containers/clusterForms/civo/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React, { FunctionComponent } from 'react'; - -import TerminalLogs from '../../terminalLogs'; -import { FormStep } from '../../../constants/installation'; -import AuthForm from '../shared/authForm'; -import ClusterRunning from '../shared/clusterRunning'; -import SetupForm from '../shared/setupForm'; - -const CIVO_FORM_FLOW = { - [FormStep.AUTHENTICATION]: AuthForm, - [FormStep.SETUP]: SetupForm, - [FormStep.PROVISIONING]: TerminalLogs, - [FormStep.READY]: ClusterRunning, -}; - -export const CivoFormFlow: FunctionComponent<{ currentStep: FormStep }> = ({ currentStep }) => { - const ActiveFormStep = CIVO_FORM_FLOW[currentStep]; - - if (!ActiveFormStep) { - return null; - } - - return ; -}; diff --git a/containers/clusterForms/digitalocean/index.tsx b/containers/clusterForms/digitalocean/index.tsx deleted file mode 100644 index 88b93252..00000000 --- a/containers/clusterForms/digitalocean/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { FunctionComponent } from 'react'; - -import TerminalLogs from '../../terminalLogs'; -import { FormStep } from '../../../constants/installation'; -import AuthForm from '../shared/authForm'; -import ClusterRunning from '../shared/clusterRunning'; -import SetupForm from '../shared/setupForm'; - -const DIGITAL_OCEAN_FORM_FLOW = { - [FormStep.AUTHENTICATION]: AuthForm, - [FormStep.SETUP]: SetupForm, - [FormStep.PROVISIONING]: TerminalLogs, - [FormStep.READY]: ClusterRunning, -}; - -export const DigitalOceanFormFlow: FunctionComponent<{ currentStep: FormStep }> = ({ - currentStep, -}) => { - const ActiveFormStep = DIGITAL_OCEAN_FORM_FLOW[currentStep]; - - if (!ActiveFormStep) { - return null; - } - - return ; -}; diff --git a/containers/clusterForms/index.tsx b/containers/clusterForms/index.tsx new file mode 100644 index 00000000..150a0910 --- /dev/null +++ b/containers/clusterForms/index.tsx @@ -0,0 +1,25 @@ +import React, { FunctionComponent } from 'react'; + +import TerminalLogs from '../terminalLogs'; +import { FormStep } from '../../constants/installation'; + +import AuthForm from './shared/authForm'; +import ClusterRunning from './shared/clusterRunning'; +import SetupForm from './shared/setupForm'; + +const FORM_FLOW_MAP = { + [FormStep.AUTHENTICATION]: AuthForm, + [FormStep.SETUP]: SetupForm, + [FormStep.PROVISIONING]: TerminalLogs, + [FormStep.READY]: ClusterRunning, +}; + +export const FormFlow: FunctionComponent<{ currentStep: FormStep }> = ({ currentStep }) => { + const ActiveFormStep = FORM_FLOW_MAP[currentStep]; + + if (!ActiveFormStep) { + return null; + } + + return ; +}; diff --git a/containers/clusterForms/shared/clusterRunning/index.tsx b/containers/clusterForms/shared/clusterRunning/index.tsx index bf9cb93b..867cab59 100644 --- a/containers/clusterForms/shared/clusterRunning/index.tsx +++ b/containers/clusterForms/shared/clusterRunning/index.tsx @@ -1,9 +1,9 @@ import React, { FunctionComponent } from 'react'; import { useRouter } from 'next/router'; +import ClusterReady from '../../../../components/clusterReady'; import { getClusters } from '../../../../redux/thunks/api.thunk'; import { useAppDispatch, useAppSelector } from '../../../../redux/store'; -import ClusterReady from '../../../../components/clusterReady'; export interface ClusterRunningProps { clusterName?: string; @@ -12,7 +12,10 @@ export interface ClusterRunningProps { const ClusterRunning: FunctionComponent = (props) => { const dispatch = useAppDispatch(); - const installValues = useAppSelector(({ installation }) => installation.values); + const { installValues, selectedCluster } = useAppSelector(({ installation, cluster }) => ({ + installValues: installation.values, + selectedCluster: cluster.selectedCluster, + })); const { push } = useRouter(); const onOpenConsole = () => { @@ -25,6 +28,7 @@ const ClusterRunning: FunctionComponent = (props) => { onOpenConsole={onOpenConsole} clusterName={installValues?.clusterName} domainName={installValues?.domainName} + kbotPassword={selectedCluster?.vaultAuth?.kbotPassword} {...props} /> ); diff --git a/containers/clusterForms/vultr/index.tsx b/containers/clusterForms/vultr/index.tsx deleted file mode 100644 index bc91a9ab..00000000 --- a/containers/clusterForms/vultr/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { FunctionComponent } from 'react'; - -import TerminalLogs from '../../terminalLogs'; -import { FormStep } from '../../../constants/installation'; -import AuthForm from '../shared/authForm'; -import ClusterRunning from '../shared/clusterRunning'; -import SetupForm from '../shared/setupForm'; - -const VULTR_FORM_FLOW = { - [FormStep.AUTHENTICATION]: AuthForm, - [FormStep.SETUP]: SetupForm, - [FormStep.PROVISIONING]: TerminalLogs, - [FormStep.READY]: ClusterRunning, -}; - -export const VultrFormFlow: FunctionComponent<{ currentStep: FormStep }> = ({ - currentStep, - ...rest -}) => { - const ActiveFormStep = VULTR_FORM_FLOW[currentStep]; - - if (!ActiveFormStep) { - return null; - } - - return ; -}; diff --git a/containers/provision/index.tsx b/containers/provision/index.tsx index 6331206b..744af7c2 100644 --- a/containers/provision/index.tsx +++ b/containers/provision/index.tsx @@ -2,11 +2,13 @@ import React, { FunctionComponent, useCallback, useEffect, useMemo } from 'react import { FormProvider, useForm } from 'react-hook-form'; import { useRouter } from 'next/router'; -import { AUTHENTICATION_ERROR_MSG } from '../../constants'; -import { createCluster, getCloudRegions, resetClusterProgress } from '../../redux/thunks/api.thunk'; import InstallationStepContainer from '../../components/installationStepContainer'; import InstallationInfoCard from '../../components/installationInfoCard'; +import ErrorBanner from '../../components/errorBanner'; +import Button from '../../components/button'; import { InstallationsSelection } from '../installationsSelection'; +import { FormFlow } from '../clusterForms'; +import AdvancedOptions from '../clusterForms/shared/advancedOptions'; import { clearError, setError, @@ -14,14 +16,13 @@ import { setInstallValues, setInstallationStep, } from '../../redux/slices/installation.slice'; +import { clearClusterState, clearValidation } from '../../redux/slices/api.slice'; import { useAppDispatch, useAppSelector } from '../../redux/store'; +import { createCluster, getCloudRegions, resetClusterProgress } from '../../redux/thunks/api.thunk'; import { useInstallation } from '../../hooks/useInstallation'; import { InstallValues, InstallationType } from '../../types/redux'; import { GitProvider } from '../../types'; -import { clearClusterState, clearValidation } from '../../redux/slices/api.slice'; -import AdvancedOptions from '../clusterForms/shared/advancedOptions'; -import ErrorBanner from '../../components/errorBanner'; -import Button from '../../components/button'; +import { AUTHENTICATION_ERROR_MSG } from '../../constants'; import { AdvancedOptionsContainer, ErrorContainer, Form, FormContent } from './provision.styled'; @@ -53,20 +54,13 @@ const Provision: FunctionComponent = () => { const { isProvisioned } = useAppSelector(({ api }) => api); const isMarketplace = useMemo(() => installMethod?.includes('marketplace'), [installMethod]); - const { - stepTitles, - formFlow: FormFlow, - installTitles, - info, - isAuthStep, - isProvisionStep, - isSetupStep, - } = useInstallation( - installType as InstallationType, - gitProvider as GitProvider, - installationStep, - !!isMarketplace, - ); + const { stepTitles, installTitles, info, isAuthStep, isProvisionStep, isSetupStep } = + useInstallation( + installType as InstallationType, + gitProvider as GitProvider, + installationStep, + !!isMarketplace, + ); const methods = useForm({ mode: 'onChange' }); const { @@ -203,7 +197,6 @@ const Provision: FunctionComponent = () => { error, authErrors, provisionCluster, - FormFlow, isSetupStep, installType, ]); diff --git a/declaration.d.ts b/declaration.d.ts index 8e11efec..6bb934b0 100644 --- a/declaration.d.ts +++ b/declaration.d.ts @@ -123,4 +123,14 @@ declare module '@mui/material/styles' { body3?: React.CSSProperties; tooltip?: React.CSSProperties; } + + interface ButtonPropsColorOverrides { + text: true; + } +} + +declare module '@mui/material/Button' { + interface ButtonPropsColorOverrides { + text: true; + } } diff --git a/hooks/useInstallation.ts b/hooks/useInstallation.ts index 5ffaa425..a695abc8 100644 --- a/hooks/useInstallation.ts +++ b/hooks/useInstallation.ts @@ -1,5 +1,3 @@ -import { FunctionComponent, useMemo } from 'react'; - import { DEFAULT_STEPS, FormStep, @@ -13,22 +11,6 @@ import { } from '../constants/installation'; import { GitProvider } from '../types'; import { InstallationType } from '../types/redux'; -import { CivoFormFlow } from '../containers/clusterForms/civo'; -import { AwsFormFlow } from '../containers/clusterForms/aws'; -import { LocalFormFlow } from '../containers/clusterForms/k3d'; -import { DigitalOceanFormFlow } from '../containers/clusterForms/digitalocean'; -import { VultrFormFlow } from '../containers/clusterForms/vultr'; - -export const FormFlowByType: Record< - InstallationType, - FunctionComponent<{ currentStep: FormStep }> -> = { - [InstallationType.LOCAL]: LocalFormFlow, - [InstallationType.AWS]: AwsFormFlow, - [InstallationType.CIVO]: CivoFormFlow, - [InstallationType.DIGITAL_OCEAN]: DigitalOceanFormFlow, - [InstallationType.VULTR]: VultrFormFlow, -}; const getInstallationTitles = ( installType: InstallationType, @@ -116,24 +98,17 @@ const getIsProvisionStep = ( return isLocalProvisionStep || isProvisionStep; }; -export function useInstallation( +export const useInstallation = ( type: InstallationType, gitProvider: GitProvider, step: number, isMarketplace = false, -) { - const formByType = useMemo(() => { - return FormFlowByType[type] || FormFlowByType.aws; - }, [type]); - - return { - stepTitles: getStepTitles(type, isMarketplace), - installTitles: getInstallationTitles(type, gitProvider, isMarketplace), - info: getInfoByType(type, step, isMarketplace), - isAuthStep: getIsAuthStep(type, step, isMarketplace), - isSetupStep: getIsSetupStep(type, step, isMarketplace), - isProvisionStep: getIsProvisionStep(type, step, isMarketplace), - formFlow: formByType, - apiKeyInfo: INSTALLATION_TYPE_API_KEYS[type], - }; -} +) => ({ + stepTitles: getStepTitles(type, isMarketplace), + installTitles: getInstallationTitles(type, gitProvider, isMarketplace), + info: getInfoByType(type, step, isMarketplace), + isAuthStep: getIsAuthStep(type, step, isMarketplace), + isSetupStep: getIsSetupStep(type, step, isMarketplace), + isProvisionStep: getIsProvisionStep(type, step, isMarketplace), + apiKeyInfo: INSTALLATION_TYPE_API_KEYS[type], +}); diff --git a/redux/thunks/api.thunk.ts b/redux/thunks/api.thunk.ts index 9beee619..b08e6430 100644 --- a/redux/thunks/api.thunk.ts +++ b/redux/thunks/api.thunk.ts @@ -29,6 +29,9 @@ const mapClusterFromRaw = (cluster: ClusterResponse): Cluster => ({ creationDate: cluster.creation_timestamp, lastErrorCondition: cluster.last_condition, status: cluster.status, + vaultAuth: { + kbotPassword: cluster.vault_auth?.kbot_password, + }, checks: { install_tools_check: cluster.install_tools_check, domain_liveness_check: cluster.domain_liveness_check, diff --git a/types/provision/index.ts b/types/provision/index.ts index d0573849..e43f38c8 100644 --- a/types/provision/index.ts +++ b/types/provision/index.ts @@ -51,6 +51,9 @@ export interface ClusterResponse { gitOwner: string; gitToken?: string; }; + vault_auth: { + kbot_password: string; + }; last_condition: string; install_tools_check: boolean; domain_liveness_check: boolean; @@ -89,6 +92,9 @@ export interface Cluster extends Row { gitOwner: string; gitToken?: string; }; + vaultAuth: { + kbotPassword: string; + }; checks: { install_tools_check: boolean; domain_liveness_check: boolean; diff --git a/utils/mock/index.ts b/utils/mock/index.ts index 8a6294fe..acbfe77f 100644 --- a/utils/mock/index.ts +++ b/utils/mock/index.ts @@ -8,12 +8,12 @@ export function mockSuccessResponse() { const mock = new MockAdapter(githubApi); mock.onGet('/user').reply(200, mockGithubUser); - mock.onGet('/user/orgs').reply(200, mockGithubUserOrganizations); + mock.onGet('/user/orgs?per_page=100').reply(200, mockGithubUserOrganizations); } export function mockFailedResponse() { const mock = new MockAdapter(githubApi); mock.onGet('/user').reply(403); - mock.onGet('/user/orgs').reply(403); + mock.onGet('/user/orgs?per_page=100').reply(403); }