diff --git a/CHANGELOG.md b/CHANGELOG.md index fa465448..36c37812 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed "At least one project, user, or group must be specified" for GitLab configs with `all` in web configurator. [#512](https://github.com/sourcebot-dev/sourcebot/pull/512) - Fixed zoekt indexing failing with pipe in branch/tag names [#506](https://github.com/sourcebot-dev/sourcebot/pull/506) +- Removed deprecated connection creation/edit UI [#515](https://github.com/sourcebot-dev/sourcebot/pull/515) ## [4.6.8] - 2025-09-15 diff --git a/packages/web/src/app/[domain]/components/connectionCreationForms/bitbucketCloudConnectionCreationForm.tsx b/packages/web/src/app/[domain]/components/connectionCreationForms/bitbucketCloudConnectionCreationForm.tsx deleted file mode 100644 index 52c762fc..00000000 --- a/packages/web/src/app/[domain]/components/connectionCreationForms/bitbucketCloudConnectionCreationForm.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client'; - -import SharedConnectionCreationForm from "./sharedConnectionCreationForm"; -import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/connection.type"; -import { bitbucketSchema } from "@sourcebot/schemas/v3/bitbucket.schema"; -import { bitbucketCloudQuickActions } from "../../connections/quickActions"; - -interface BitbucketCloudConnectionCreationFormProps { - onCreated?: (id: number) => void; -} - -const additionalConfigValidation = (config: BitbucketConnectionConfig): { message: string, isValid: boolean } => { - const hasProjects = config.projects && config.projects.length > 0 && config.projects.some(p => p.trim().length > 0); - const hasRepos = config.repos && config.repos.length > 0 && config.repos.some(r => r.trim().length > 0); - const hasWorkspaces = config.workspaces && config.workspaces.length > 0 && config.workspaces.some(w => w.trim().length > 0); - - if (!hasProjects && !hasRepos && !hasWorkspaces) { - return { - message: "At least one project, repository, or workspace must be specified", - isValid: false, - } - } - - return { - message: "Valid", - isValid: true, - } -}; - -export const BitbucketCloudConnectionCreationForm = ({ onCreated }: BitbucketCloudConnectionCreationFormProps) => { - const defaultConfig: BitbucketConnectionConfig = { - type: 'bitbucket', - deploymentType: 'cloud', - } - - return ( - - type="bitbucket-cloud" - title="Create a Bitbucket Cloud connection" - defaultValues={{ - config: JSON.stringify(defaultConfig, null, 2), - }} - schema={bitbucketSchema} - additionalConfigValidation={additionalConfigValidation} - quickActions={bitbucketCloudQuickActions} - onCreated={onCreated} - /> - ) -} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/connectionCreationForms/bitbucketDataCenterConnectionCreationForm.tsx b/packages/web/src/app/[domain]/components/connectionCreationForms/bitbucketDataCenterConnectionCreationForm.tsx deleted file mode 100644 index 5065de00..00000000 --- a/packages/web/src/app/[domain]/components/connectionCreationForms/bitbucketDataCenterConnectionCreationForm.tsx +++ /dev/null @@ -1,48 +0,0 @@ -'use client'; - -import SharedConnectionCreationForm from "./sharedConnectionCreationForm"; -import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/connection.type"; -import { bitbucketSchema } from "@sourcebot/schemas/v3/bitbucket.schema"; -import { bitbucketDataCenterQuickActions } from "../../connections/quickActions"; - -interface BitbucketDataCenterConnectionCreationFormProps { - onCreated?: (id: number) => void; -} - -const additionalConfigValidation = (config: BitbucketConnectionConfig): { message: string, isValid: boolean } => { - const hasProjects = config.projects && config.projects.length > 0 && config.projects.some(p => p.trim().length > 0); - const hasRepos = config.repos && config.repos.length > 0 && config.repos.some(r => r.trim().length > 0); - - if (!hasProjects && !hasRepos) { - return { - message: "At least one project or repository must be specified", - isValid: false, - } - } - - return { - message: "Valid", - isValid: true, - } -}; - -export const BitbucketDataCenterConnectionCreationForm = ({ onCreated }: BitbucketDataCenterConnectionCreationFormProps) => { - const defaultConfig: BitbucketConnectionConfig = { - type: 'bitbucket', - deploymentType: 'server', - } - - return ( - - type="bitbucket-server" - title="Create a Bitbucket Data Center connection" - defaultValues={{ - config: JSON.stringify(defaultConfig, null, 2), - }} - schema={bitbucketSchema} - additionalConfigValidation={additionalConfigValidation} - quickActions={bitbucketDataCenterQuickActions} - onCreated={onCreated} - /> - ) -} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/connectionCreationForms/gerritConnectionCreationForm.tsx b/packages/web/src/app/[domain]/components/connectionCreationForms/gerritConnectionCreationForm.tsx deleted file mode 100644 index 64b6b10b..00000000 --- a/packages/web/src/app/[domain]/components/connectionCreationForms/gerritConnectionCreationForm.tsx +++ /dev/null @@ -1,47 +0,0 @@ -'use client'; - -import { GerritConnectionConfig } from "@sourcebot/schemas/v3/gerrit.type"; -import { gerritSchema } from "@sourcebot/schemas/v3/gerrit.schema"; -import { gerritQuickActions } from "../../connections/quickActions"; -import SharedConnectionCreationForm from "./sharedConnectionCreationForm"; - -interface GerritConnectionCreationFormProps { - onCreated?: (id: number) => void; -} - -const additionalConfigValidation = (config: GerritConnectionConfig): { message: string, isValid: boolean } => { - const hasProjects = config.projects && config.projects.length > 0; - - if (!hasProjects) { - return { - message: "At least one project must be specified", - isValid: false, - } - } - - return { - message: "Valid", - isValid: true, - } -} - -export const GerritConnectionCreationForm = ({ onCreated }: GerritConnectionCreationFormProps) => { - const defaultConfig: GerritConnectionConfig = { - type: 'gerrit', - url: "https://gerrit.example.com" - } - - return ( - - type="gerrit" - title="Create a Gerrit connection" - defaultValues={{ - config: JSON.stringify(defaultConfig, null, 2), - }} - schema={gerritSchema} - quickActions={gerritQuickActions} - additionalConfigValidation={additionalConfigValidation} - onCreated={onCreated} - /> - ) -} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/connectionCreationForms/giteaConnectionCreationForm.tsx b/packages/web/src/app/[domain]/components/connectionCreationForms/giteaConnectionCreationForm.tsx deleted file mode 100644 index f19c441c..00000000 --- a/packages/web/src/app/[domain]/components/connectionCreationForms/giteaConnectionCreationForm.tsx +++ /dev/null @@ -1,48 +0,0 @@ -'use client'; - -import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type"; -import { giteaSchema } from "@sourcebot/schemas/v3/gitea.schema"; -import { giteaQuickActions } from "../../connections/quickActions"; -import SharedConnectionCreationForm from "./sharedConnectionCreationForm"; - -interface GiteaConnectionCreationFormProps { - onCreated?: (id: number) => void; -} - -const additionalConfigValidation = (config: GiteaConnectionConfig): { message: string, isValid: boolean } => { - const hasOrgs = config.orgs && config.orgs.length > 0 && config.orgs.some(o => o.trim().length > 0); - const hasUsers = config.users && config.users.length > 0 && config.users.some(u => u.trim().length > 0); - const hasRepos = config.repos && config.repos.length > 0 && config.repos.some(r => r.trim().length > 0); - - if (!hasOrgs && !hasUsers && !hasRepos) { - return { - message: "At least one organization, user, or repository must be specified", - isValid: false, - } - } - - return { - message: "Valid", - isValid: true, - } -} - -export const GiteaConnectionCreationForm = ({ onCreated }: GiteaConnectionCreationFormProps) => { - const defaultConfig: GiteaConnectionConfig = { - type: 'gitea', - } - - return ( - - type="gitea" - title="Create a Gitea connection" - defaultValues={{ - config: JSON.stringify(defaultConfig, null, 2), - }} - schema={giteaSchema} - quickActions={giteaQuickActions} - additionalConfigValidation={additionalConfigValidation} - onCreated={onCreated} - /> - ) -} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/connectionCreationForms/githubConnectionCreationForm.tsx b/packages/web/src/app/[domain]/components/connectionCreationForms/githubConnectionCreationForm.tsx deleted file mode 100644 index 80446ab4..00000000 --- a/packages/web/src/app/[domain]/components/connectionCreationForms/githubConnectionCreationForm.tsx +++ /dev/null @@ -1,48 +0,0 @@ -'use client'; - -import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type"; -import { githubSchema } from "@sourcebot/schemas/v3/github.schema"; -import { githubQuickActions } from "../../connections/quickActions"; -import SharedConnectionCreationForm from "./sharedConnectionCreationForm"; - -interface GitHubConnectionCreationFormProps { - onCreated?: (id: number) => void; -} - -const additionalConfigValidation = (config: GithubConnectionConfig): { message: string, isValid: boolean } => { - const hasRepos = config.repos && config.repos.length > 0 && config.repos.some(r => r.trim().length > 0); - const hasOrgs = config.orgs && config.orgs.length > 0 && config.orgs.some(o => o.trim().length > 0); - const hasUsers = config.users && config.users.length > 0 && config.users.some(u => u.trim().length > 0); - - if (!hasRepos && !hasOrgs && !hasUsers) { - return { - message: "At least one repository, organization, or user must be specified", - isValid: false, - } - } - - return { - message: "Valid", - isValid: true, - } -}; - -export const GitHubConnectionCreationForm = ({ onCreated }: GitHubConnectionCreationFormProps) => { - const defaultConfig: GithubConnectionConfig = { - type: 'github', - } - - return ( - - type="github" - title="Create a GitHub connection" - defaultValues={{ - config: JSON.stringify(defaultConfig, null, 2), - }} - schema={githubSchema} - additionalConfigValidation={additionalConfigValidation} - quickActions={githubQuickActions} - onCreated={onCreated} - /> - ) -} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/connectionCreationForms/gitlabConnectionCreationForm.tsx b/packages/web/src/app/[domain]/components/connectionCreationForms/gitlabConnectionCreationForm.tsx deleted file mode 100644 index b408fcee..00000000 --- a/packages/web/src/app/[domain]/components/connectionCreationForms/gitlabConnectionCreationForm.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client'; - -import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type"; -import { gitlabSchema } from "@sourcebot/schemas/v3/gitlab.schema"; -import { gitlabQuickActions } from "../../connections/quickActions"; -import SharedConnectionCreationForm from "./sharedConnectionCreationForm"; - -interface GitLabConnectionCreationFormProps { - onCreated?: (id: number) => void; -} - -const additionalConfigValidation = (config: GitlabConnectionConfig): { message: string, isValid: boolean } => { - const hasProjects = config.projects && config.projects.length > 0 && config.projects.some(p => p.trim().length > 0); - const hasUsers = config.users && config.users.length > 0 && config.users.some(u => u.trim().length > 0); - const hasGroups = config.groups && config.groups.length > 0 && config.groups.some(g => g.trim().length > 0); - const hasAll = config.all; - - if (!hasProjects && !hasUsers && !hasGroups && !hasAll) { - return { - message: "At least one project, user, or group must be specified", - isValid: false, - } - } - - return { - message: "Valid", - isValid: true, - } -} - -export const GitLabConnectionCreationForm = ({ onCreated }: GitLabConnectionCreationFormProps) => { - const defaultConfig: GitlabConnectionConfig = { - type: 'gitlab', - } - - return ( - - type="gitlab" - title="Create a GitLab connection" - defaultValues={{ - config: JSON.stringify(defaultConfig, null, 2), - }} - schema={gitlabSchema} - quickActions={gitlabQuickActions} - additionalConfigValidation={additionalConfigValidation} - onCreated={onCreated} - /> - ) -} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/connectionCreationForms/index.ts b/packages/web/src/app/[domain]/components/connectionCreationForms/index.ts deleted file mode 100644 index db4bcd0f..00000000 --- a/packages/web/src/app/[domain]/components/connectionCreationForms/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { GitHubConnectionCreationForm } from "./githubConnectionCreationForm"; -export { GitLabConnectionCreationForm } from "./gitlabConnectionCreationForm"; -export { GiteaConnectionCreationForm } from "./giteaConnectionCreationForm"; -export { GerritConnectionCreationForm } from "./gerritConnectionCreationForm"; -export { BitbucketCloudConnectionCreationForm } from "./bitbucketCloudConnectionCreationForm"; -export { BitbucketDataCenterConnectionCreationForm } from "./bitbucketDataCenterConnectionCreationForm"; \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/connectionCreationForms/secretCombobox.tsx b/packages/web/src/app/[domain]/components/connectionCreationForms/secretCombobox.tsx deleted file mode 100644 index 6bfe4baf..00000000 --- a/packages/web/src/app/[domain]/components/connectionCreationForms/secretCombobox.tsx +++ /dev/null @@ -1,163 +0,0 @@ -'use client'; - -import { getSecrets } from "@/actions"; -import { Button } from "@/components/ui/button"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import { Separator } from "@/components/ui/separator"; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; -import useCaptureEvent from "@/hooks/useCaptureEvent"; -import { useDomain } from "@/hooks/useDomain"; -import { cn, CodeHostType, isDefined, isServiceError, unwrapServiceError } from "@/lib/utils"; -import { useQuery } from "@tanstack/react-query"; -import { Check, ChevronsUpDown, Loader2, PlusCircleIcon, TriangleAlert } from "lucide-react"; -import { useCallback, useState } from "react"; -import { ImportSecretDialog } from "../importSecretDialog"; - -interface SecretComboBoxProps { - isDisabled: boolean; - codeHostType: CodeHostType; - secretKey?: string; - onSecretChange: (secretKey: string) => void; -} - -export const SecretCombobox = ({ - isDisabled, - codeHostType, - secretKey, - onSecretChange, -}: SecretComboBoxProps) => { - const [searchFilter, setSearchFilter] = useState(""); - const domain = useDomain(); - const [isCreateSecretDialogOpen, setIsCreateSecretDialogOpen] = useState(false); - const captureEvent = useCaptureEvent(); - - const { data: secrets, isPending, isError, refetch } = useQuery({ - queryKey: ["secrets", domain], - queryFn: () => unwrapServiceError(getSecrets(domain)), - }); - - const onSecretCreated = useCallback((key: string) => { - onSecretChange(key); - refetch(); - }, [onSecretChange, refetch]); - - return ( - <> - - - - - - - {isPending ? ( -
- -
- ) : isError ? ( -

Failed to load secrets

- ) : secrets.length > 0 && ( - <> - - setSearchFilter(value)} - /> - - -

No secrets found

-

{`Your search term "${searchFilter}" did not match any secrets.`}

-
- - {secrets.map(({ key }) => ( - { - onSecretChange(key); - }} - > - {key} - - - ))} - -
-
- - - )} - -
-
- - - ) -} diff --git a/packages/web/src/app/[domain]/components/connectionCreationForms/sharedConnectionCreationForm.tsx b/packages/web/src/app/[domain]/components/connectionCreationForms/sharedConnectionCreationForm.tsx deleted file mode 100644 index 859d87da..00000000 --- a/packages/web/src/app/[domain]/components/connectionCreationForms/sharedConnectionCreationForm.tsx +++ /dev/null @@ -1,239 +0,0 @@ - -'use client'; - -import { checkIfSecretExists, createConnection } from "@/actions"; -import { ConnectionIcon } from "@/app/[domain]/connections/components/connectionIcon"; -import { createZodConnectionConfigValidator } from "@/app/[domain]/connections/utils"; -import { useToast } from "@/components/hooks/use-toast"; -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { CodeHostType, isServiceError, isAuthSupportedForCodeHost } from "@/lib/utils"; -import { cn } from "@/lib/utils"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Schema } from "ajv"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; -import ConfigEditor, { isConfigValidJson, onQuickAction, QuickActionFn } from "../configEditor"; -import { useDomain } from "@/hooks/useDomain"; -import { InfoIcon, Loader2 } from "lucide-react"; -import { ReactCodeMirrorRef } from "@uiw/react-codemirror"; -import { SecretCombobox } from "./secretCombobox"; -import strings from "@/lib/strings"; -import useCaptureEvent from "@/hooks/useCaptureEvent"; - -interface SharedConnectionCreationFormProps { - type: CodeHostType; - defaultValues: { - name?: string; - config: string; - }; - title: string; - schema: Schema; - quickActions?: { - name: string; - fn: QuickActionFn; - }[], - className?: string; - onCreated?: (id: number) => void; - additionalConfigValidation?: (config: T) => { message: string, isValid: boolean }; -} - - -export default function SharedConnectionCreationForm({ - type, - defaultValues, - title, - schema, - quickActions, - className, - onCreated, - additionalConfigValidation -}: SharedConnectionCreationFormProps) { - const { toast } = useToast(); - const domain = useDomain(); - const editorRef = useRef(null); - const captureEvent = useCaptureEvent(); - const formSchema = useMemo(() => { - return z.object({ - name: z.string().min(1), - config: createZodConnectionConfigValidator(schema, additionalConfigValidation), - secretKey: z.string().optional().refine(async (secretKey) => { - if (!secretKey) { - return true; - } - - return checkIfSecretExists(secretKey, domain); - }, { message: "Secret not found" }), - }); - }, [schema, domain, additionalConfigValidation]); - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: defaultValues, - }); - const { isSubmitting } = form.formState; - - const onSubmit = useCallback(async (data: z.infer) => { - const response = await createConnection(data.name, type, data.config, domain); - if (isServiceError(response)) { - toast({ - description: `❌ Failed to create connection. Reason: ${response.message}` - }); - captureEvent('wa_create_connection_fail', { - type: type, - error: response.message, - }); - } else { - toast({ - description: `✅ Connection created successfully.` - }); - captureEvent('wa_create_connection_success', { - type: type, - }); - onCreated?.(response.id); - } - }, [domain, toast, type, onCreated, captureEvent]); - - const onConfigChange = useCallback((value: string) => { - form.setValue("config", value); - const isValid = isConfigValidJson(value); - setIsSecretsDisabled(!isValid); - if (isValid) { - const configJson = JSON.parse(value); - if (configJson.token?.secret !== undefined) { - form.setValue("secretKey", configJson.token.secret); - } else { - form.setValue("secretKey", undefined); - } - } - }, [form]); - - // Run onConfigChange on mount to set the initial secret key - useEffect(() => { - onConfigChange(defaultValues.config); - }, [defaultValues, onConfigChange]); - - const [isSecretsDisabled, setIsSecretsDisabled] = useState(false); - - return ( -
-
-
- -

{title}

-
- - Connections are used to specify what repositories you want Sourcebot to sync. - -
-
- -
- ( - - Display Name - This is the {`connection's`} display name within Sourcebot. - - - - - - )} - /> - {isAuthSupportedForCodeHost(type) && ( - ( - - Secret (optional) - {strings.createSecretDescription} - - { - const view = editorRef.current?.view; - if (!view) { - return; - } - - onQuickAction( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (previous: any) => { - return { - ...previous, - token: { - secret: secretKey, - } - } - }, - form.getValues("config"), - view, - { - focusEditor: false - } - ); - }} - /> - - - - )} - /> - )} - { - return ( - - Configuration - {strings.connectionConfigDescription} - - - ref={editorRef} - type={type} - value={value} - onChange={onConfigChange} - actions={quickActions ?? []} - schema={schema} - /> - - - - ) - }} - /> -
-
- -
-
- -
- ) -} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/connections/[id]/components/configSetting.tsx b/packages/web/src/app/[domain]/connections/[id]/components/configSetting.tsx deleted file mode 100644 index 0da3eb98..00000000 --- a/packages/web/src/app/[domain]/connections/[id]/components/configSetting.tsx +++ /dev/null @@ -1,273 +0,0 @@ -'use client'; - -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { githubSchema } from "@sourcebot/schemas/v3/github.schema"; -import { Loader2 } from "lucide-react"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; -import ConfigEditor, { isConfigValidJson, onQuickAction, QuickAction } from "../../../components/configEditor"; -import { createZodConnectionConfigValidator } from "../../utils"; -import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type"; -import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type"; -import { GerritConnectionConfig } from "@sourcebot/schemas/v3/gerrit.type"; -import { githubQuickActions, gitlabQuickActions, giteaQuickActions, gerritQuickActions, bitbucketCloudQuickActions, bitbucketDataCenterQuickActions } from "../../quickActions"; -import { Schema } from "ajv"; -import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type"; -import { gitlabSchema } from "@sourcebot/schemas/v3/gitlab.schema"; -import { checkIfSecretExists, updateConnectionConfigAndScheduleSync } from "@/actions"; -import { useToast } from "@/components/hooks/use-toast"; -import { isServiceError, CodeHostType, isAuthSupportedForCodeHost } from "@/lib/utils"; -import { useRouter } from "next/navigation"; -import { giteaSchema } from "@sourcebot/schemas/v3/gitea.schema"; -import { gerritSchema } from "@sourcebot/schemas/v3/gerrit.schema"; -import { useDomain } from "@/hooks/useDomain"; -import { SecretCombobox } from "@/app/[domain]/components/connectionCreationForms/secretCombobox"; -import { ReactCodeMirrorRef } from "@uiw/react-codemirror"; -import strings from "@/lib/strings"; -import { bitbucketSchema } from "@sourcebot/schemas/v3/bitbucket.schema"; -import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/bitbucket.type"; - -interface ConfigSettingProps { - connectionId: number; - config: string; - type: CodeHostType; - disabled?: boolean; -} - -export const ConfigSetting = (props: ConfigSettingProps) => { - const { type } = props; - - if (type === 'github') { - return - {...props} - type="github" - quickActions={githubQuickActions} - schema={githubSchema} - />; - } - - if (type === 'gitlab') { - return - {...props} - type="gitlab" - quickActions={gitlabQuickActions} - schema={gitlabSchema} - />; - } - - if (type === 'bitbucket-cloud') { - return - {...props} - type="bitbucket-cloud" - quickActions={bitbucketCloudQuickActions} - schema={bitbucketSchema} - />; - } - - if (type === 'bitbucket-server') { - return - {...props} - type="bitbucket-server" - quickActions={bitbucketDataCenterQuickActions} - schema={bitbucketSchema} - />; - } - - if (type === 'gitea') { - return - {...props} - type="gitea" - quickActions={giteaQuickActions} - schema={giteaSchema} - />; - } - - if (type === 'gerrit') { - return - {...props} - type="gerrit" - quickActions={gerritQuickActions} - schema={gerritSchema} - />; - } - - return null; -} - - -function ConfigSettingInternal({ - connectionId, - config, - quickActions, - schema, - type, - disabled, -}: ConfigSettingProps & { - quickActions?: QuickAction[], - schema: Schema, - type: CodeHostType, - disabled?: boolean, -}) { - const { toast } = useToast(); - const router = useRouter(); - const domain = useDomain(); - const editorRef = useRef(null); - const [isSecretsDisabled, setIsSecretsDisabled] = useState(false); - - const formSchema = useMemo(() => { - return z.object({ - config: createZodConnectionConfigValidator(schema), - secretKey: z.string().optional().refine(async (secretKey) => { - if (!secretKey) { - return true; - } - - return checkIfSecretExists(secretKey, domain); - }, { message: "Secret not found" }) - }); - }, [schema, domain]); - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - config, - }, - }); - - const [isLoading, setIsLoading] = useState(false); - const onSubmit = useCallback((data: z.infer) => { - setIsLoading(true); - updateConnectionConfigAndScheduleSync(connectionId, data.config, domain) - .then((response) => { - if (isServiceError(response)) { - toast({ - description: `❌ Failed to update connection. Reason: ${response.message}` - }); - } else { - toast({ - description: `✅ Connection config updated successfully.` - }); - router.push(`?tab=overview`); - router.refresh(); - } - }) - .finally(() => { - setIsLoading(false); - }) - }, [connectionId, domain, router, toast]); - - const onConfigChange = useCallback((value: string) => { - form.setValue("config", value); - const isValid = isConfigValidJson(value); - setIsSecretsDisabled(!isValid); - if (isValid) { - const configJson = JSON.parse(value); - if (configJson.token?.secret !== undefined) { - form.setValue("secretKey", configJson.token.secret); - } else { - form.setValue("secretKey", undefined); - } - } - }, [form]); - - useEffect(() => { - onConfigChange(config); - }, [config, onConfigChange]); - - return ( -
-

Configuration

-
- - {isAuthSupportedForCodeHost(type) && ( - ( - - Secret (optional) - {strings.createSecretDescription} - - { - const view = editorRef.current?.view; - if (!view) { - return; - } - - onQuickAction( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (previous: any) => { - return { - ...previous, - token: { - secret: secretKey, - } - } - }, - form.getValues("config"), - view, - { - focusEditor: false - } - ); - }} - /> - - - - )} - /> - )} - ( - - - {isAuthSupportedForCodeHost(type) && ( - Configuration - )} - {strings.connectionConfigDescription} - - - ref={editorRef} - type={type} - value={value} - onChange={onConfigChange} - schema={schema} - actions={quickActions ?? []} - /> - - - - - - )} - /> -
- -
- - -
- ); -} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/connections/[id]/page.tsx b/packages/web/src/app/[domain]/connections/[id]/page.tsx index 72962f7e..0e0a91d2 100644 --- a/packages/web/src/app/[domain]/connections/[id]/page.tsx +++ b/packages/web/src/app/[domain]/connections/[id]/page.tsx @@ -7,53 +7,29 @@ import { BreadcrumbPage, BreadcrumbSeparator, } from "@/components/ui/breadcrumb" -import { TabSwitcher } from "@/components/ui/tab-switcher" -import { Tabs, TabsContent } from "@/components/ui/tabs" import { ConnectionIcon } from "../components/connectionIcon" import { Header } from "../../components/header" -import { ConfigSetting } from "./components/configSetting" -import { DeleteConnectionSetting } from "./components/deleteConnectionSetting" -import { DisplayNameSetting } from "./components/displayNameSetting" import { RepoList } from "./components/repoList" import { getConnectionByDomain } from "@/data/connection" import { Overview } from "./components/overview" -import { getOrgMembership } from "@/actions" -import { isServiceError } from "@/lib/utils" -import { notFound } from "next/navigation" -import { OrgRole } from "@sourcebot/db" -import { CodeHostType } from "@/lib/utils" -import { env } from "@/env.mjs" interface ConnectionManagementPageProps { params: Promise<{ domain: string id: string }>, - searchParams: Promise<{ - tab: string - }> } export default async function ConnectionManagementPage(props: ConnectionManagementPageProps) { - const searchParams = await props.searchParams; const params = await props.params; const connection = await getConnectionByDomain(Number(params.id), params.domain); if (!connection) { return } - const membership = await getOrgMembership(params.domain); - if (isServiceError(membership)) { - return notFound(); - } - - const isOwner = membership.role === OrgRole.OWNER; - const isDisabled = !isOwner || env.CONFIG_PATH !== undefined; - const currentTab = searchParams.tab || "overview"; - return ( - -
+
+
@@ -65,46 +41,23 @@ export default async function ConnectionManagementPage(props: ConnectionManageme -
+
-

{connection.name}

+

{connection.name}

-
- + +
-

Overview

+

Overview

-

Linked Repositories

+

Linked Repositories

- - - - - - - +
+
) } diff --git a/packages/web/src/app/[domain]/connections/components/newConnectionCard.tsx b/packages/web/src/app/[domain]/connections/components/newConnectionCard.tsx deleted file mode 100644 index f6a97fa0..00000000 --- a/packages/web/src/app/[domain]/connections/components/newConnectionCard.tsx +++ /dev/null @@ -1,148 +0,0 @@ -"use client" - -import { cn, type CodeHostType, getCodeHostIcon } from "@/lib/utils" -import placeholderLogo from "@/public/placeholder_avatar.png" -import { BlocksIcon, LockIcon } from "lucide-react" -import Image from "next/image" -import Link from "next/link" -import { useMemo } from "react" -import { OrgRole } from "@sourcebot/db" - -interface NewConnectionCardProps { - className?: string - role: OrgRole - configPathProvided: boolean -} - -export const NewConnectionCard = ({ className, role, configPathProvided }: NewConnectionCardProps) => { - const isOwner = role === OrgRole.OWNER - const isDisabled = !isOwner || configPathProvided - - return ( -
- {isDisabled && ( -
- -
- )} - -

- Connect to a Code Host -

-

- Create a connection to import repos from a code host. -

-
- - - - - - -
- {isDisabled && ( -

- {configPathProvided - ? "Connections are managed through the configuration file." - : "Only organization owners can manage connections."} -

- )} -
- ) -} - -interface CardProps { - type: string - title: string - subtitle: string - disabled?: boolean -} - -const Card = ({ type, title, subtitle, disabled = false }: CardProps) => { - const Icon = useMemo(() => { - const iconInfo = getCodeHostIcon(type as CodeHostType) - if (iconInfo) { - const { src, className } = iconInfo - return ( - {`${type} - ) - } - - return ( - {`${type} - ) - }, [type, disabled]) - - const CardContent = ( -
-
- {Icon} -
-

{title}

-

{subtitle}

-
-
-
- ) - - if (disabled) { - return CardContent - } - - return ( - - {CardContent} - - ) -} - diff --git a/packages/web/src/app/[domain]/connections/new/[type]/page.tsx b/packages/web/src/app/[domain]/connections/new/[type]/page.tsx deleted file mode 100644 index 8c63d4ef..00000000 --- a/packages/web/src/app/[domain]/connections/new/[type]/page.tsx +++ /dev/null @@ -1,51 +0,0 @@ -'use client'; - -import { useRouter } from "next/navigation"; -import { - GitHubConnectionCreationForm, - GitLabConnectionCreationForm, - GiteaConnectionCreationForm, - GerritConnectionCreationForm, - BitbucketCloudConnectionCreationForm, - BitbucketDataCenterConnectionCreationForm -} from "@/app/[domain]/components/connectionCreationForms"; -import { useCallback, use } from "react"; -import { useDomain } from "@/hooks/useDomain"; - -export default function NewConnectionPage(props: { params: Promise<{ type: string }> }) { - const params = use(props.params); - const { type } = params; - const router = useRouter(); - const domain = useDomain(); - - const onCreated = useCallback(() => { - router.push(`/${domain}/connections`); - }, [domain, router]); - - if (type === 'github') { - return ; - } - - if (type === 'gitlab') { - return ; - } - - if (type === 'gitea') { - return ; - } - - if (type === 'gerrit') { - return ; - } - - if (type === 'bitbucket-cloud') { - return ; - } - - if (type === 'bitbucket-server') { - return ; - } - - - router.push(`/${domain}/connections`); -} diff --git a/packages/web/src/app/[domain]/connections/page.tsx b/packages/web/src/app/[domain]/connections/page.tsx index 4102927f..fdf3f32c 100644 --- a/packages/web/src/app/[domain]/connections/page.tsx +++ b/packages/web/src/app/[domain]/connections/page.tsx @@ -1,11 +1,9 @@ import { ConnectionList } from "./components/connectionList"; import { Header } from "../components/header"; -import { NewConnectionCard } from "./components/newConnectionCard"; import { getConnections, getOrgMembership } from "@/actions"; import { isServiceError } from "@/lib/utils"; import { notFound, ServiceErrorException } from "@/lib/serviceError"; import { OrgRole } from "@sourcebot/db"; -import { env } from "@/env.mjs"; export default async function ConnectionsPage(props: { params: Promise<{ domain: string }> }) { const params = await props.params; @@ -29,17 +27,9 @@ export default async function ConnectionsPage(props: { params: Promise<{ domain:

Connections

-
- - -
+ ); }