diff --git a/backend/src/services/integration-auth/integration-list.ts b/backend/src/services/integration-auth/integration-list.ts index 2aaf5d5f4..edc426327 100644 --- a/backend/src/services/integration-auth/integration-list.ts +++ b/backend/src/services/integration-auth/integration-list.ts @@ -30,7 +30,8 @@ export enum Integrations { DIGITAL_OCEAN_APP_PLATFORM = "digital-ocean-app-platform", CLOUD_66 = "cloud-66", NORTHFLANK = "northflank", - HASURA_CLOUD = "hasura-cloud" + HASURA_CLOUD = "hasura-cloud", + RUNDECK = "rundeck" } export enum IntegrationType { @@ -368,6 +369,15 @@ export const getIntegrationOptions = async () => { type: "pat", clientId: "", docsLink: "" + }, + { + name: "Rundeck", + slug: "rundeck", + image: "Rundeck.svg", + isAvailable: true, + type: "pat", + clientId: "", + docsLink: "" } ]; diff --git a/frontend/public/images/integrations/Rundeck.svg b/frontend/public/images/integrations/Rundeck.svg new file mode 100644 index 000000000..4ded97a8a --- /dev/null +++ b/frontend/public/images/integrations/Rundeck.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/pages/integrations/rundeck/authorize.tsx b/frontend/src/pages/integrations/rundeck/authorize.tsx new file mode 100644 index 000000000..3d7898996 --- /dev/null +++ b/frontend/src/pages/integrations/rundeck/authorize.tsx @@ -0,0 +1,129 @@ +import { useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import Head from "next/head"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import z from "zod"; + +import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; +import { useSaveIntegrationAccessToken } from "@app/hooks/api"; + +const schema = z.object({ + authToken: z.string().trim().min(1, { message: "Rundeck Auth Token is required" }), + rundeckURL: z.string().trim().min(1, { + message: "Rundeck URL is required" + }) +}); + +type FormData = z.infer; + +export default function RundeckAuthorizeIntegrationPage() { + const router = useRouter(); + const [isLoading, setIsLoading] = useState(false); + const { mutateAsync } = useSaveIntegrationAccessToken(); + + const { control, handleSubmit } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + authToken: "", + rundeckURL: "" + } + }); + + const onFormSubmit = async ({ authToken, rundeckURL }: FormData) => { + try { + setIsLoading(true); + + const integrationAuth = await mutateAsync({ + workspaceId: localStorage.getItem("projectData.id"), + integration: "rundeck", + accessToken: authToken, + url: rundeckURL.trim() + }); + + setIsLoading(false); + router.push(`/integrations/rundeck/create?integrationAuthId=${integrationAuth.id}`); + } catch (err) { + setIsLoading(false); + console.error(err); + } + }; + return ( +
+ + Authorize Rundeck Integration + + + + +
+
+ Rundeck logo +
+ Rundeck Integration + + +
+ + Docs + +
+
+ +
+
+
+ ( + + + + )} + /> + ( + + + + )} + /> + + +
+
+ ); +} + +RundeckAuthorizeIntegrationPage.requireAuth = true; diff --git a/frontend/src/pages/integrations/rundeck/create.tsx b/frontend/src/pages/integrations/rundeck/create.tsx new file mode 100644 index 000000000..79ea4eacd --- /dev/null +++ b/frontend/src/pages/integrations/rundeck/create.tsx @@ -0,0 +1,213 @@ +import { Controller, useForm } from "react-hook-form"; +import Head from "next/head"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { faArrowUpRightFromSquare, faBookOpen, faBugs } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import queryString from "query-string"; +import { z } from "zod"; + +import { + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem +} from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; +import { useCreateIntegration } from "@app/hooks/api"; +import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { useGetWorkspaceById } from "@app/hooks/api/workspace"; + +const schema = z.object({ + keyStoragePath: z.string().trim().min(1, { message: "Rundeck Key Storage path is required" }), + secretPath: z.string().trim().min(1, { message: "Secret path is required" }), + sourceEnvironment: z.string().trim().min(1, { message: "Source environment is required" }) +}); + +type TFormSchema = z.infer; + +export default function RundeckCreateIntegrationPage() { + const { + control, + handleSubmit, + watch, + formState: { isSubmitting } + } = useForm({ + resolver: zodResolver(schema) + }); + const router = useRouter(); + const { mutateAsync } = useCreateIntegration(); + const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]); + + const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? ""); + const { data: integrationAuth, isLoading: isIntegrationAuthLoading } = useGetIntegrationAuthById( + (integrationAuthId as string) ?? "" + ); + + const selectedSourceEnvironment = watch("sourceEnvironment"); + + const onFormSubmit = async ({ secretPath, sourceEnvironment, keyStoragePath }: TFormSchema) => { + try { + if (!integrationAuth?.id) return; + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + path: keyStoragePath, + sourceEnvironment, + secretPath + }); + + router.push(`/integrations/${localStorage.getItem("projectData.id")}`); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && workspace ? ( +
+ + Set Up Rundeck Integration + + + + +
+
+ Rundeck logo +
+ Rundeck Integration + + +
+ + Docs + +
+
+ +
+
+ +
+ ( + + + + )} + /> + + ( + + + + )} + /> + + ( + + + + )} + /> + + + +
+
+ ) : ( +
+ + Set Up Rundeck Integration + + + {isIntegrationAuthLoading ? ( + infisical loading indicator + ) : ( +
+ +

+ Something went wrong. Please contact{" "} + + support@infisical.com + {" "} + if the issue persists. +

+
+ )} +
+ ); +} + +RundeckCreateIntegrationPage.requireAuth = true; diff --git a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx index 10ef21e27..1aee4c656 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx @@ -128,6 +128,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) => case "hasura-cloud": link = `${window.location.origin}/integrations/hasura-cloud/authorize`; break; + case "rundeck": + link = `${window.location.origin}/integrations/rundeck/authorize`; + break; default: break; }