diff --git a/src/components/InformationBanner.tsx b/src/components/InformationBanner.tsx index ad01db56..f47086e8 100644 --- a/src/components/InformationBanner.tsx +++ b/src/components/InformationBanner.tsx @@ -24,7 +24,7 @@ export default function InformationBanner({ message, children, small, sx }: Prop return ( - {message} + {message} {children} ) diff --git a/src/components/Setting.tsx b/src/components/Setting.tsx index c5dc5c01..b2efc367 100644 --- a/src/components/Setting.tsx +++ b/src/components/Setting.tsx @@ -58,9 +58,7 @@ export const getSettingUiSchema = (settings: GetSettingsInfoApiResponse, setting adminPassword: { 'ui:widget': 'hidden' }, useORCS: { 'ui:widget': 'hidden' }, aiEnabled: { 'ui:widget': 'hidden' }, - git: { - password: { 'ui:widget': 'password' }, - }, + git: { 'ui:widget': 'hidden' }, }, kms: { sops: { diff --git a/src/components/modals/ConfigureGitModal.tsx b/src/components/modals/ConfigureGitModal.tsx index 0ce86d7d..785a68ed 100644 --- a/src/components/modals/ConfigureGitModal.tsx +++ b/src/components/modals/ConfigureGitModal.tsx @@ -1,7 +1,8 @@ /* eslint-disable no-nested-ternary */ +import ContentCopyIcon from '@mui/icons-material/ContentCopy' import { yupResolver } from '@hookform/resolvers/yup' import { LoadingButton } from '@mui/lab' -import { Box, Button, Modal, Typography, styled } from '@mui/material' +import { Box, Button, IconButton, Modal, Tooltip, Typography, styled } from '@mui/material' import { FetchBaseQueryError } from '@reduxjs/toolkit/query' import InformationBanner from 'components/InformationBanner' import { TextField } from 'components/forms/TextField' @@ -9,7 +10,7 @@ import { useEffect, useMemo, useState } from 'react' import { Controller, useForm } from 'react-hook-form' import { useLocalStorage } from 'react-use' import { useSession } from 'providers/Session' -import { useMigrateGitMutation } from 'redux/otomiApi' +import { useGetGitSettingsQuery, useMigrateGitMutation } from 'redux/otomiApi' import { GitSettingsFormValues, gitSettingsSchema } from './gitSettingsValidator' const MODAL_TITLE = 'Configure Git Repository' @@ -88,6 +89,25 @@ const IntroParagraph = styled(BodyText)({ marginBottom: '24px', }) +const DefaultGitUrlBlock = styled(Box)(({ theme }) => ({ + marginTop: '24px', + padding: '14px 16px', + borderRadius: 8, + border: '1px solid rgba(145, 158, 171, 0.24)', + backgroundColor: theme.palette.cm.rowAlter, + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + gap: '16px', +})) + +const DefaultGitUrlText = styled(Typography)({ + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + fontFamily: 'monospace', +}) + const SectionTitle = styled(Typography)({ marginBottom: '4px', fontWeight: 550, @@ -216,15 +236,45 @@ function getErrorMessage(error: unknown): string { return 'Something went wrong while migrating Git settings.' } +const emptyGitFormValues: GitSettingsFormValues = { + repoUrl: '', + branch: '', + username: '', + password: '', + email: '', +} + export default function ConfigureGitModal({ open, onClose }: ConfigureGitModalProps) { const { user: { isPlatformAdmin }, settings: { + cluster: { domainSuffix }, otomi: { isPreInstalled }, }, } = useSession() const [showGitWizard, setShowGitWizard] = useLocalStorage('showGitConfigureWizard', true) + + const isControlled = typeof open === 'boolean' + const actualOpen = useMemo(() => (isControlled ? !!open : !!showGitWizard), [isControlled, open, showGitWizard]) + + const { data: gitSettings, isFetching: isFetchingGitSettings } = useGetGitSettingsQuery(undefined, { + skip: !isPlatformAdmin || !isPreInstalled || !actualOpen, + }) + + const defaultGitUrl = gitSettings?.repoUrl || '' + const isDefaultGitConfiguration = gitSettings?.repoUrl?.includes('git-server.git-server.svc.cluster.local') ?? false + const hasGitConfiguration = !!gitSettings?.repoUrl && !isDefaultGitConfiguration + const displayedRepoUrl = isDefaultGitConfiguration && domainSuffix ? `https://git.${domainSuffix}/otomi/values` : '' + + const getGitFormValues = (): GitSettingsFormValues => ({ + repoUrl: hasGitConfiguration ? gitSettings?.repoUrl || '' : '', + branch: hasGitConfiguration ? gitSettings?.branch || '' : '', + username: hasGitConfiguration ? gitSettings?.username || '' : '', + password: hasGitConfiguration ? gitSettings?.password || '' : '', + email: hasGitConfiguration ? gitSettings?.email || '' : '', + }) + const [showFormStep, setShowFormStep] = useState(false) const [isTransitioning, setIsTransitioning] = useState(false) const [submitError, setSubmitError] = useState('') @@ -235,29 +285,21 @@ export default function ConfigureGitModal({ open, onClose }: ConfigureGitModalPr const { control, handleSubmit, + getValues, formState: { errors }, reset, } = useForm({ resolver: yupResolver(gitSettingsSchema), - defaultValues: { - repoUrl: '', - branch: '', - username: '', - password: '', - email: '', - }, + defaultValues: emptyGitFormValues, mode: 'onBlur', }) - const isControlled = typeof open === 'boolean' - const actualOpen = useMemo(() => (isControlled ? !!open : !!showGitWizard), [isControlled, open, showGitWizard]) - const resetModalState = () => { - setShowFormStep(false) + setShowFormStep(hasGitConfiguration) setSubmitError('') setMigrationSucceeded(false) setIsTransitioning(false) - reset() + reset(getGitFormValues()) } useEffect(() => { @@ -269,9 +311,18 @@ export default function ConfigureGitModal({ open, onClose }: ConfigureGitModalPr }, [isPreInstalled, isControlled, setShowGitWizard]) useEffect(() => { - if (!actualOpen) resetModalState() + if (!actualOpen) { + resetModalState() + return + } + + if (!gitSettings) return + + reset(getGitFormValues()) + setShowFormStep(hasGitConfiguration) + // eslint-disable-next-line react-hooks/exhaustive-deps - }, [actualOpen]) + }, [actualOpen, gitSettings, hasGitConfiguration]) const handleClose = () => { resetModalState() @@ -284,6 +335,19 @@ export default function ConfigureGitModal({ open, onClose }: ConfigureGitModalPr setShowGitWizard(false) } + const handleCopyDefaultGitUrl = async () => { + if (!displayedRepoUrl) return + await navigator.clipboard.writeText(displayedRepoUrl) + } + + const handleCopyRepoUrl = async () => { + const repoUrl = getValues('repoUrl') + + if (!repoUrl) return + + await navigator.clipboard.writeText(repoUrl) + } + const goToFormStep = () => { setIsTransitioning(true) @@ -356,7 +420,12 @@ export default function ConfigureGitModal({ open, onClose }: ConfigureGitModalPr > - {!showFormStep ? ( + {isFetchingGitSettings ? ( + + {MODAL_TITLE} + Loading Git settings... + + ) : !showFormStep ? ( <> {MODAL_TITLE} @@ -370,6 +439,25 @@ export default function ConfigureGitModal({ open, onClose }: ConfigureGitModalPr Configuring an external Git Repo is recommended for installing App Platform. + + {!!defaultGitUrl && ( + + + Current internal Git repository + {displayedRepoUrl} + + + + + + + + + )} @@ -413,6 +501,10 @@ export default function ConfigureGitModal({ open, onClose }: ConfigureGitModalPr {MODAL_TITLE} + {hasGitConfiguration && ( + + )} + {!!submitError && } @@ -427,6 +519,24 @@ export default function ConfigureGitModal({ open, onClose }: ConfigureGitModalPr fullWidth error={!!errors.repoUrl} helperText={errors.repoUrl?.message} + InputProps={ + hasGitConfiguration + ? { + endAdornment: ( + + + + + + ), + } + : undefined + } /> )} /> @@ -513,7 +623,7 @@ export default function ConfigureGitModal({ open, onClose }: ConfigureGitModalPr diff --git a/src/pages/SettingsOverview.tsx b/src/pages/SettingsOverview.tsx index ef5d2ff8..70533db7 100644 --- a/src/pages/SettingsOverview.tsx +++ b/src/pages/SettingsOverview.tsx @@ -35,7 +35,7 @@ export default function SettingsOverview() { { title: 'Backup', path: '/settings/platformBackups', icon: getIcon('backup_icon.svg'), id: 'backup' }, { title: 'Object Storage', path: '/settings/obj', icon: getIcon('cloud_upload.svg'), id: 'objectStorage' }, { - title: 'Git', + title: 'GitOps', icon: getIcon('git_icon.svg'), id: 'git', onClick: () => setOpenGitModal(true), diff --git a/src/redux/otomiApi.ts b/src/redux/otomiApi.ts index 4e567b4a..d525d1be 100644 --- a/src/redux/otomiApi.ts +++ b/src/redux/otomiApi.ts @@ -382,6 +382,9 @@ const injectedRtkApi = api.injectEndpoints({ body: queryArg.body, }), }), + getGitSettings: build.query({ + query: () => ({ url: `/v2/git` }), + }), migrateGit: build.mutation({ query: (queryArg) => ({ url: `/v2/git`, method: 'PUT', body: queryArg.body }), }), @@ -4251,6 +4254,9 @@ export type GetSettingsInfoApiResponse = /** status 200 The request is successfu git?: { repoUrl?: string branch?: string + username?: string + password?: string + email?: string } } ingressClassNames?: string[] @@ -4854,6 +4860,14 @@ export type EditAppApiArg = { } } } +export type GetGitSettingsApiResponse = /** status 200 Current Git settings */ { + repoUrl: string + username?: string + password: string + email: string + branch: string +} +export type GetGitSettingsApiArg = void export type MigrateGitApiResponse = /** status 200 Migration successful. API is now locked. */ undefined export type MigrateGitApiArg = { /** New git configuration to migrate to. */ @@ -4968,6 +4982,7 @@ export const { useToggleAppsMutation, useGetTeamAppQuery, useEditAppMutation, + useGetGitSettingsQuery, useMigrateGitMutation, useGetApiStatusQuery, } = injectedRtkApi