From b187cb9494ce9502d61dd43f106ad07c88b0b9bb Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Wed, 19 Nov 2025 21:36:17 -0800 Subject: [PATCH] Add dns made easy UI --- .../dns-made-easy-connection-fns.ts | 10 +- .../dns-made-easy-connection-schema.ts | 2 +- frontend/src/helpers/appConnections.ts | 2 +- .../types/dns-made-easy-connection.ts | 4 +- .../hooks/api/appConnections/types/index.ts | 1 + .../AppConnectionForm/AppConnectionForm.tsx | 5 + .../DNSMadeEasyConnectionForm.tsx | 157 ++++++++++++++++++ 7 files changed, 172 insertions(+), 9 deletions(-) create mode 100644 frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/DNSMadeEasyConnectionForm.tsx diff --git a/backend/src/services/app-connection/dns-made-easy/dns-made-easy-connection-fns.ts b/backend/src/services/app-connection/dns-made-easy/dns-made-easy-connection-fns.ts index b50e47a03..fc34940b8 100644 --- a/backend/src/services/app-connection/dns-made-easy/dns-made-easy-connection-fns.ts +++ b/backend/src/services/app-connection/dns-made-easy/dns-made-easy-connection-fns.ts @@ -26,14 +26,14 @@ interface DNSMadeEasyApiResponse { export const makeDNSMadeEasyAuthHeaders = ( apiKey: string, - apiSecret: string, + secretKey: string, currentDate: Date = new Date() ): Record => { // Format date as "Day, DD Mon YYYY HH:MM:SS GMT" (e.g., "Mon, 01 Jan 2024 12:00:00 GMT") const requestDate = currentDate.toUTCString(); // Generate HMAC-SHA1 signature - const hmac = crypto.nativeCrypto.createHmac("sha1", apiSecret); + const hmac = crypto.nativeCrypto.createHmac("sha1", secretKey); hmac.update(requestDate); const hmacSignature = hmac.digest("hex"); @@ -64,7 +64,7 @@ export const listDNSMadeEasyZones = async (appConnection: TDNSMadeEasyConnection } const { - credentials: { apiKey, apiSecret } + credentials: { apiKey, secretKey } } = appConnection; try { @@ -89,12 +89,12 @@ export const validateDNSMadeEasyConnectionCredentials = async (config: TDNSMadeE throw new Error("Unsupported DNS Made Easy connection method"); } - const { apiKey, apiSecret } = config.credentials; + const { apiKey, secretKey } = config.credentials; try { const resp = await request.get(`${IntegrationUrls.DNS_MADE_EASY_API_URL}/V2.0/dns/managed/`, { headers: { - ...makeDNSMadeEasyAuthHeaders(apiKey, apiSecret), + ...makeDNSMadeEasyAuthHeaders(apiKey, secretKey), Accept: "application/json" } }); diff --git a/backend/src/services/app-connection/dns-made-easy/dns-made-easy-connection-schema.ts b/backend/src/services/app-connection/dns-made-easy/dns-made-easy-connection-schema.ts index d5ed6d3cb..d968ba768 100644 --- a/backend/src/services/app-connection/dns-made-easy/dns-made-easy-connection-schema.ts +++ b/backend/src/services/app-connection/dns-made-easy/dns-made-easy-connection-schema.ts @@ -13,7 +13,7 @@ import { DNSMadeEasyConnectionMethod } from "./dns-made-easy-connection-enum"; export const DNSMadeEasyConnectionApiKeyCredentialsSchema = z.object({ apiKey: z.string().trim().min(1, "API key required").max(256, "API key cannot exceed 256 characters"), - apiSecret: z.string().trim().min(1, "API secret required").max(256, "API secret cannot exceed 256 characters") + secretKey: z.string().trim().min(1, "Secret key required").max(256, "Secret key cannot exceed 256 characters") }); const BaseDNSMadeEasyConnectionSchema = BaseAppConnectionSchema.extend({ diff --git a/frontend/src/helpers/appConnections.ts b/frontend/src/helpers/appConnections.ts index d32f8cc7f..20e959c42 100644 --- a/frontend/src/helpers/appConnections.ts +++ b/frontend/src/helpers/appConnections.ts @@ -112,7 +112,7 @@ export const APP_CONNECTION_MAP: Record< [AppConnection.Flyio]: { name: "Fly.io", image: "Flyio.svg" }, [AppConnection.GitLab]: { name: "GitLab", image: "GitLab.png" }, [AppConnection.Cloudflare]: { name: "Cloudflare", image: "Cloudflare.png" }, - [AppConnection.DNSMadeEasy]: { name: "DNS Made Easy", image: "DNSMadeEasy.svg" }, + [AppConnection.DNSMadeEasy]: { name: "DNS Made Easy", image: "DNSMadeEasy.svg", size: 120 }, [AppConnection.Zabbix]: { name: "Zabbix", image: "Zabbix.png" }, [AppConnection.Railway]: { name: "Railway", image: "Railway.png" }, [AppConnection.Bitbucket]: { name: "Bitbucket", image: "Bitbucket.png" }, diff --git a/frontend/src/hooks/api/appConnections/types/dns-made-easy-connection.ts b/frontend/src/hooks/api/appConnections/types/dns-made-easy-connection.ts index 2d1b93c86..fd4dc098b 100644 --- a/frontend/src/hooks/api/appConnections/types/dns-made-easy-connection.ts +++ b/frontend/src/hooks/api/appConnections/types/dns-made-easy-connection.ts @@ -8,7 +8,7 @@ export enum DNSMadeEasyConnectionMethod { export type TDNSMadeEasyConnection = TRootAppConnection & { app: AppConnection.DNSMadeEasy } & { method: DNSMadeEasyConnectionMethod.APIKeySecret; credentials: { - apiToken: string; - accountId: string; + apiKey: string; + secretKey: string; }; }; diff --git a/frontend/src/hooks/api/appConnections/types/index.ts b/frontend/src/hooks/api/appConnections/types/index.ts index f52c902d2..dcefd549c 100644 --- a/frontend/src/hooks/api/appConnections/types/index.ts +++ b/frontend/src/hooks/api/appConnections/types/index.ts @@ -57,6 +57,7 @@ export * from "./camunda-connection"; export * from "./checkly-connection"; export * from "./chef-connection"; export * from "./cloudflare-connection"; +export * from "./dns-made-easy-connection"; export * from "./databricks-connection"; export * from "./flyio-connection"; export * from "./gcp-connection"; diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx index aca33ffa2..af26a940d 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -24,6 +24,7 @@ import { ChefConnectionForm } from "./ChefConnectionForm"; import { CloudflareConnectionForm } from "./CloudflareConnectionForm"; import { DatabricksConnectionForm } from "./DatabricksConnectionForm"; import { DigitalOceanConnectionForm } from "./DigitalOceanConnectionForm"; +import { DNSMadeEasyConnectionForm } from "./DNSMadeEasyConnectionForm"; import { FlyioConnectionForm } from "./FlyioConnectionForm"; import { GcpConnectionForm } from "./GcpConnectionForm"; import { GitHubConnectionForm } from "./GitHubConnectionForm"; @@ -148,6 +149,8 @@ const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => { return ; case AppConnection.Cloudflare: return ; + case AppConnection.DNSMadeEasy: + return ; case AppConnection.Bitbucket: return ; case AppConnection.Zabbix: @@ -306,6 +309,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { ); case AppConnection.Cloudflare: return ; + case AppConnection.DNSMadeEasy: + return ; case AppConnection.Bitbucket: return ; case AppConnection.Zabbix: diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/DNSMadeEasyConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/DNSMadeEasyConnectionForm.tsx new file mode 100644 index 000000000..0d7d7403a --- /dev/null +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/DNSMadeEasyConnectionForm.tsx @@ -0,0 +1,157 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { Controller, FormProvider, useForm } from "react-hook-form"; +import { z } from "zod"; + +import { + Button, + FormControl, + Input, + ModalClose, + SecretInput, + Select, + SelectItem +} from "@app/components/v2"; +import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { TDNSMadeEasyConnection } from "@app/hooks/api/appConnections"; +import { AppConnection } from "@app/hooks/api/appConnections/enums"; + +import { DNSMadeEasyConnectionMethod } from "@app/hooks/api/appConnections/types/dns-made-easy-connection"; +import { + genericAppConnectionFieldsSchema, + GenericAppConnectionsFields +} from "./GenericAppConnectionFields"; + +type Props = { + appConnection?: TDNSMadeEasyConnection; + onSubmit: (formData: FormData) => Promise; +}; + +const rootSchema = genericAppConnectionFieldsSchema.extend({ + app: z.literal(AppConnection.DNSMadeEasy) +}); + +const formSchema = z.discriminatedUnion("method", [ + rootSchema.extend({ + method: z.literal(DNSMadeEasyConnectionMethod.APIKeySecret), + credentials: z.object({ + apiKey: z.string().trim().min(1, "API Key required"), + secretKey: z.string().trim().min(1, "Secret Key required") + }) + }) +]); + +type FormData = z.infer; + +export const DNSMadeEasyConnectionForm = ({ appConnection, onSubmit }: Props) => { + const isUpdate = Boolean(appConnection); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: appConnection ?? { + app: AppConnection.DNSMadeEasy, + method: DNSMadeEasyConnectionMethod.APIKeySecret, + credentials: { + apiKey: "", + secretKey: "" + } + } + }); + + const { + handleSubmit, + control, + formState: { isSubmitting, isDirty } + } = form; + + return ( + +
+ {!isUpdate && } + ( + + + + )} + /> + ( + + onChange(e.target.value)} + placeholder="af1b628f-3272-46aa-9cde-837d0c59155d" + /> + + )} + /> + ( + + onChange(e.target.value)} + /> + + )} + /> +
+ + + + +
+ +
+ ); +};