diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 1f5f5a4ea..cc841e392 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2223,6 +2223,9 @@ export const AppConnections = { ONEPASS: { instanceUrl: "The URL of the 1Password Connect Server instance to authenticate with.", apiToken: "The API token used to access the 1Password Connect Server." + }, + FLYIO: { + accessToken: "The Access Token used to access fly.io." } } }; diff --git a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts index 53c503e4a..3a9af21c7 100644 --- a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts @@ -39,6 +39,7 @@ import { DatabricksConnectionListItemSchema, SanitizedDatabricksConnectionSchema } from "@app/services/app-connection/databricks"; +import { FlyioConnectionListItemSchema, SanitizedFlyioConnectionSchema } from "@app/services/app-connection/flyio"; import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp"; import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github"; import { @@ -100,7 +101,8 @@ const SanitizedAppConnectionSchema = z.union([ ...SanitizedTeamCityConnectionSchema.options, ...SanitizedOCIConnectionSchema.options, ...SanitizedOracleDBConnectionSchema.options, - ...SanitizedOnePassConnectionSchema.options + ...SanitizedOnePassConnectionSchema.options, + ...SanitizedFlyioConnectionSchema.options ]); const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ @@ -127,7 +129,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ TeamCityConnectionListItemSchema, OCIConnectionListItemSchema, OracleDBConnectionListItemSchema, - OnePassConnectionListItemSchema + OnePassConnectionListItemSchema, + FlyioConnectionListItemSchema ]); export const registerAppConnectionRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/server/routes/v1/app-connection-routers/flyio-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/flyio-connection-router.ts new file mode 100644 index 000000000..3a43570d9 --- /dev/null +++ b/backend/src/server/routes/v1/app-connection-routers/flyio-connection-router.ts @@ -0,0 +1,52 @@ +import z from "zod"; + +import { readLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { + CreateFlyioConnectionSchema, + SanitizedFlyioConnectionSchema, + UpdateFlyioConnectionSchema +} from "@app/services/app-connection/flyio"; +import { AuthMode } from "@app/services/auth/auth-type"; + +import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; + +export const registerFlyioConnectionRouter = async (server: FastifyZodProvider) => { + registerAppConnectionEndpoints({ + app: AppConnection.Flyio, + server, + sanitizedResponseSchema: SanitizedFlyioConnectionSchema, + createSchema: CreateFlyioConnectionSchema, + updateSchema: UpdateFlyioConnectionSchema + }); + + // The following endpoints are for internal Infisical App use only and not part of the public API + server.route({ + method: "GET", + url: `/:connectionId/apps`, + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + connectionId: z.string().uuid() + }), + response: { + 200: z + .object({ + // TODO(andrey): apps + id: z.string(), + name: z.string() + }) + .array() + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { connectionId } = req.params; + const apps = await server.services.appConnection.flyio.listApps(connectionId, req.permission); + return apps; + } + }); +}; diff --git a/backend/src/server/routes/v1/app-connection-routers/index.ts b/backend/src/server/routes/v1/app-connection-routers/index.ts index 5f07f1be7..7c8df632c 100644 --- a/backend/src/server/routes/v1/app-connection-routers/index.ts +++ b/backend/src/server/routes/v1/app-connection-routers/index.ts @@ -11,6 +11,7 @@ import { registerAzureDevOpsConnectionRouter } from "./azure-devops-connection-r import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router"; import { registerCamundaConnectionRouter } from "./camunda-connection-router"; import { registerDatabricksConnectionRouter } from "./databricks-connection-router"; +import { registerFlyioConnectionRouter } from "./flyio-connection-router"; import { registerGcpConnectionRouter } from "./gcp-connection-router"; import { registerGitHubConnectionRouter } from "./github-connection-router"; import { registerGitHubRadarConnectionRouter } from "./github-radar-connection-router"; @@ -52,5 +53,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record { getTeamCityConnectionListItem(), getOCIConnectionListItem(), getOracleDBConnectionListItem(), - getOnePassConnectionListItem() + getOnePassConnectionListItem(), + getFlyioConnectionListItem() ].sort((a, b) => a.name.localeCompare(b.name)); }; @@ -196,7 +198,8 @@ export const validateAppConnectionCredentials = async ( [AppConnection.TeamCity]: validateTeamCityConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.OCI]: validateOCIConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.OracleDB]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.OnePass]: validateOnePassConnectionCredentials as TAppConnectionCredentialsValidator + [AppConnection.OnePass]: validateOnePassConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.Flyio]: validateFlyioConnectionCredentials as TAppConnectionCredentialsValidator }; return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection); @@ -238,6 +241,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) => case HCVaultConnectionMethod.AccessToken: case TeamCityConnectionMethod.AccessToken: case AzureDevOpsConnectionMethod.AccessToken: + case FlyioConnectionMethod.AccessToken: return "Access Token"; case Auth0ConnectionMethod.ClientCredentials: return "Client Credentials"; @@ -299,7 +303,8 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record< [AppConnection.TeamCity]: platformManagedCredentialsNotSupported, [AppConnection.OCI]: platformManagedCredentialsNotSupported, [AppConnection.OracleDB]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform, - [AppConnection.OnePass]: platformManagedCredentialsNotSupported + [AppConnection.OnePass]: platformManagedCredentialsNotSupported, + [AppConnection.Flyio]: platformManagedCredentialsNotSupported }; export const enterpriseAppCheck = async ( diff --git a/backend/src/services/app-connection/app-connection-maps.ts b/backend/src/services/app-connection/app-connection-maps.ts index 19967aa3b..7080d7053 100644 --- a/backend/src/services/app-connection/app-connection-maps.ts +++ b/backend/src/services/app-connection/app-connection-maps.ts @@ -24,7 +24,8 @@ export const APP_CONNECTION_NAME_MAP: Record = { [AppConnection.TeamCity]: "TeamCity", [AppConnection.OCI]: "OCI", [AppConnection.OracleDB]: "OracleDB", - [AppConnection.OnePass]: "1Password" + [AppConnection.OnePass]: "1Password", + [AppConnection.Flyio]: "Fly.io" }; export const APP_CONNECTION_PLAN_MAP: Record = { @@ -51,5 +52,6 @@ export const APP_CONNECTION_PLAN_MAP: Record>>; @@ -192,6 +199,7 @@ export type TAppConnectionInput = { id: string } & ( | TOCIConnectionInput | TOracleDBConnectionInput | TOnePassConnectionInput + | TFlyioConnectionInput ); export type TSqlConnectionInput = @@ -230,7 +238,8 @@ export type TAppConnectionConfig = | TLdapConnectionConfig | TTeamCityConnectionConfig | TOCIConnectionConfig - | TOnePassConnectionConfig; + | TOnePassConnectionConfig + | TFlyioConnectionConfig; export type TValidateAppConnectionCredentialsSchema = | TValidateAwsConnectionCredentialsSchema @@ -256,7 +265,8 @@ export type TValidateAppConnectionCredentialsSchema = | TValidateTeamCityConnectionCredentialsSchema | TValidateOCIConnectionCredentialsSchema | TValidateOracleDBConnectionCredentialsSchema - | TValidateOnePassConnectionCredentialsSchema; + | TValidateOnePassConnectionCredentialsSchema + | TValidateFlyioConnectionCredentialsSchema; export type TListAwsConnectionKmsKeys = { connectionId: string; diff --git a/backend/src/services/app-connection/flyio/flyio-connection-enums.ts b/backend/src/services/app-connection/flyio/flyio-connection-enums.ts new file mode 100644 index 000000000..651e46658 --- /dev/null +++ b/backend/src/services/app-connection/flyio/flyio-connection-enums.ts @@ -0,0 +1,3 @@ +export enum FlyioConnectionMethod { + AccessToken = "access-token" +} diff --git a/backend/src/services/app-connection/flyio/flyio-connection-fns.ts b/backend/src/services/app-connection/flyio/flyio-connection-fns.ts new file mode 100644 index 000000000..4fdcc8e5a --- /dev/null +++ b/backend/src/services/app-connection/flyio/flyio-connection-fns.ts @@ -0,0 +1,65 @@ +import { AxiosError } from "axios"; + +import { request } from "@app/lib/config/request"; +import { BadRequestError } from "@app/lib/errors"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; + +import { FlyioConnectionMethod } from "./flyio-connection-enums"; +import { TFlyioApp, TFlyioConnection, TFlyioConnectionConfig } from "./flyio-connection-types"; + +export const getFlyioConnectionListItem = () => { + return { + name: "Fly.io" as const, + app: AppConnection.Flyio as const, + methods: Object.values(FlyioConnectionMethod) as [FlyioConnectionMethod.AccessToken] + }; +}; + +export const validateFlyioConnectionCredentials = async (config: TFlyioConnectionConfig) => { + const { accessToken } = config.credentials; + + try { + await request.post( + "https://api.fly.io/graphql", + { query: "query { viewer { id email } }" }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json" + } + } + ); + } catch (error: unknown) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to validate credentials: ${error.message || "Unknown error"}` + }); + } + throw new BadRequestError({ + message: "Unable to validate connection: verify credentials" + }); + } + + return config.credentials; +}; + +export const listFlyioApps = async (appConnection: TFlyioConnection) => { + const { accessToken } = appConnection.credentials; + + const resp = await request.post<{ data: { apps: { nodes: TFlyioApp[] } } }>( + "https://api.fly.io/graphql", + { + query: + "query GetApps { apps { nodes { id name hostname status organization { id slug } currentRelease { version status createdAt } } } }" + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + Accept: "application/json" + } + } + ); + + return resp.data.data.apps.nodes; +}; diff --git a/backend/src/services/app-connection/flyio/flyio-connection-schemas.ts b/backend/src/services/app-connection/flyio/flyio-connection-schemas.ts new file mode 100644 index 000000000..2b494b1d6 --- /dev/null +++ b/backend/src/services/app-connection/flyio/flyio-connection-schemas.ts @@ -0,0 +1,56 @@ +import z from "zod"; + +import { AppConnections } from "@app/lib/api-docs"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { + BaseAppConnectionSchema, + GenericCreateAppConnectionFieldsSchema, + GenericUpdateAppConnectionFieldsSchema +} from "@app/services/app-connection/app-connection-schemas"; + +import { FlyioConnectionMethod } from "./flyio-connection-enums"; + +export const FlyioConnectionAccessTokenCredentialsSchema = z.object({ + accessToken: z.string().trim().min(1, "Access Token required").describe(AppConnections.CREDENTIALS.FLYIO.accessToken) +}); + +const BaseFlyioConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Flyio) }); + +export const FlyioConnectionSchema = BaseFlyioConnectionSchema.extend({ + method: z.literal(FlyioConnectionMethod.AccessToken), + credentials: FlyioConnectionAccessTokenCredentialsSchema +}); + +export const SanitizedFlyioConnectionSchema = z.discriminatedUnion("method", [ + BaseFlyioConnectionSchema.extend({ + method: z.literal(FlyioConnectionMethod.AccessToken), + credentials: FlyioConnectionAccessTokenCredentialsSchema.pick({}) + }) +]); + +export const ValidateFlyioConnectionCredentialsSchema = z.discriminatedUnion("method", [ + z.object({ + method: z.literal(FlyioConnectionMethod.AccessToken).describe(AppConnections.CREATE(AppConnection.Flyio).method), + credentials: FlyioConnectionAccessTokenCredentialsSchema.describe( + AppConnections.CREATE(AppConnection.Flyio).credentials + ) + }) +]); + +export const CreateFlyioConnectionSchema = ValidateFlyioConnectionCredentialsSchema.and( + GenericCreateAppConnectionFieldsSchema(AppConnection.Flyio) +); + +export const UpdateFlyioConnectionSchema = z + .object({ + credentials: FlyioConnectionAccessTokenCredentialsSchema.optional().describe( + AppConnections.UPDATE(AppConnection.Flyio).credentials + ) + }) + .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Flyio)); + +export const FlyioConnectionListItemSchema = z.object({ + name: z.literal("Fly.io"), + app: z.literal(AppConnection.Flyio), + methods: z.nativeEnum(FlyioConnectionMethod).array() +}); diff --git a/backend/src/services/app-connection/flyio/flyio-connection-service.ts b/backend/src/services/app-connection/flyio/flyio-connection-service.ts new file mode 100644 index 000000000..dd88633a0 --- /dev/null +++ b/backend/src/services/app-connection/flyio/flyio-connection-service.ts @@ -0,0 +1,30 @@ +import { logger } from "@app/lib/logger"; +import { OrgServiceActor } from "@app/lib/types"; + +import { AppConnection } from "../app-connection-enums"; +import { listFlyioApps } from "./flyio-connection-fns"; +import { TFlyioConnection } from "./flyio-connection-types"; + +type TGetAppConnectionFunc = ( + app: AppConnection, + connectionId: string, + actor: OrgServiceActor +) => Promise; + +export const flyioConnectionService = (getAppConnection: TGetAppConnectionFunc) => { + const listApps = async (connectionId: string, actor: OrgServiceActor) => { + const appConnection = await getAppConnection(AppConnection.Flyio, connectionId, actor); + + try { + const apps = await listFlyioApps(appConnection); + return apps; + } catch (error) { + logger.error(error, "Failed to establish connection with fly.io"); + return []; + } + }; + + return { + listApps + }; +}; diff --git a/backend/src/services/app-connection/flyio/flyio-connection-types.ts b/backend/src/services/app-connection/flyio/flyio-connection-types.ts new file mode 100644 index 000000000..95e948b64 --- /dev/null +++ b/backend/src/services/app-connection/flyio/flyio-connection-types.ts @@ -0,0 +1,28 @@ +import z from "zod"; + +import { DiscriminativePick } from "@app/lib/types"; + +import { AppConnection } from "../app-connection-enums"; +import { + CreateFlyioConnectionSchema, + FlyioConnectionSchema, + ValidateFlyioConnectionCredentialsSchema +} from "./flyio-connection-schemas"; + +export type TFlyioConnection = z.infer; + +export type TFlyioConnectionInput = z.infer & { + app: AppConnection.Flyio; +}; + +export type TValidateFlyioConnectionCredentialsSchema = typeof ValidateFlyioConnectionCredentialsSchema; + +export type TFlyioConnectionConfig = DiscriminativePick & { + orgId: string; +}; + +// TODO(andrey): apps +export type TFlyioApp = { + id: string; + name: string; +}; diff --git a/backend/src/services/app-connection/flyio/index.ts b/backend/src/services/app-connection/flyio/index.ts new file mode 100644 index 000000000..f6aefd6b1 --- /dev/null +++ b/backend/src/services/app-connection/flyio/index.ts @@ -0,0 +1,4 @@ +export * from "./flyio-connection-enums"; +export * from "./flyio-connection-fns"; +export * from "./flyio-connection-schemas"; +export * from "./flyio-connection-types"; diff --git a/frontend/src/helpers/appConnections.ts b/frontend/src/helpers/appConnections.ts index 7a87de58c..1efcd23ee 100644 --- a/frontend/src/helpers/appConnections.ts +++ b/frontend/src/helpers/appConnections.ts @@ -19,6 +19,7 @@ import { AzureKeyVaultConnectionMethod, CamundaConnectionMethod, DatabricksConnectionMethod, + FlyioConnectionMethod, GcpConnectionMethod, GitHubConnectionMethod, GitHubRadarConnectionMethod, @@ -78,7 +79,8 @@ export const APP_CONNECTION_MAP: Record< [AppConnection.LDAP]: { name: "LDAP", image: "LDAP.png", size: 65 }, [AppConnection.TeamCity]: { name: "TeamCity", image: "TeamCity.png" }, [AppConnection.OCI]: { name: "OCI", image: "Oracle.png", enterprise: true }, - [AppConnection.OnePass]: { name: "1Password", image: "1Password.png" } + [AppConnection.OnePass]: { name: "1Password", image: "1Password.png" }, + [AppConnection.Flyio]: { name: "Fly.io", image: "Flyio.svg" } }; export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => { @@ -117,6 +119,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) case TeamCityConnectionMethod.AccessToken: case AzureDevOpsConnectionMethod.AccessToken: case WindmillConnectionMethod.AccessToken: + case FlyioConnectionMethod.AccessToken: return { name: "Access Token", icon: faKey }; case Auth0ConnectionMethod.ClientCredentials: return { name: "Client Credentials", icon: faServer }; diff --git a/frontend/src/hooks/api/appConnections/enums.ts b/frontend/src/hooks/api/appConnections/enums.ts index 1a3afe355..5073d16b7 100644 --- a/frontend/src/hooks/api/appConnections/enums.ts +++ b/frontend/src/hooks/api/appConnections/enums.ts @@ -22,5 +22,6 @@ export enum AppConnection { LDAP = "ldap", TeamCity = "teamcity", OCI = "oci", - OnePass = "1password" + OnePass = "1password", + Flyio = "flyio" } diff --git a/frontend/src/hooks/api/appConnections/flyio/index.ts b/frontend/src/hooks/api/appConnections/flyio/index.ts new file mode 100644 index 000000000..2c1906d36 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/flyio/index.ts @@ -0,0 +1,2 @@ +export * from "./queries"; +export * from "./types"; diff --git a/frontend/src/hooks/api/appConnections/flyio/queries.tsx b/frontend/src/hooks/api/appConnections/flyio/queries.tsx new file mode 100644 index 000000000..32c1ffbc2 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/flyio/queries.tsx @@ -0,0 +1,36 @@ +import { useQuery, UseQueryOptions } from "@tanstack/react-query"; + +import { apiRequest } from "@app/config/request"; + +import { appConnectionKeys } from "../queries"; +import { TFlyioApp } from "./types"; + +const flyioConnectionKeys = { + all: [...appConnectionKeys.all, "flyio"] as const, + listApps: (connectionId: string) => [...flyioConnectionKeys.all, "apps", connectionId] as const +}; + +export const useFlyioConnectionListApps = ( + connectionId: string, + options?: Omit< + UseQueryOptions< + TFlyioApp[], + unknown, + TFlyioApp[], + ReturnType + >, + "queryKey" | "queryFn" + > +) => { + return useQuery({ + queryKey: flyioConnectionKeys.listApps(connectionId), + queryFn: async () => { + const { data } = await apiRequest.get( + `/api/v1/app-connections/flyio/${connectionId}/apps` + ); + + return data; + }, + ...options + }); +}; diff --git a/frontend/src/hooks/api/appConnections/flyio/types.ts b/frontend/src/hooks/api/appConnections/flyio/types.ts new file mode 100644 index 000000000..b3e46f3fd --- /dev/null +++ b/frontend/src/hooks/api/appConnections/flyio/types.ts @@ -0,0 +1,5 @@ +// TODO(andrey): apps +export type TFlyioApp = { + id: string; + name: string; +}; diff --git a/frontend/src/hooks/api/appConnections/types/app-options.ts b/frontend/src/hooks/api/appConnections/types/app-options.ts index 0dcb11fa6..a1a916896 100644 --- a/frontend/src/hooks/api/appConnections/types/app-options.ts +++ b/frontend/src/hooks/api/appConnections/types/app-options.ts @@ -110,6 +110,10 @@ export type TOnePassConnectionOption = TAppConnectionOptionBase & { app: AppConnection.OnePass; }; +export type TFlyioConnectionOption = TAppConnectionOptionBase & { + app: AppConnection.Flyio; +}; + export type TAppConnectionOption = | TAwsConnectionOption | TGitHubConnectionOption @@ -132,7 +136,8 @@ export type TAppConnectionOption = | THCVaultConnectionOption | TTeamCityConnectionOption | TOCIConnectionOption - | TOnePassConnectionOption; + | TOnePassConnectionOption + | TFlyioConnectionOption; export type TAppConnectionOptionMap = { [AppConnection.AWS]: TAwsConnectionOption; @@ -159,4 +164,5 @@ export type TAppConnectionOptionMap = { [AppConnection.TeamCity]: TTeamCityConnectionOption; [AppConnection.OCI]: TOCIConnectionOption; [AppConnection.OnePass]: TOnePassConnectionOption; + [AppConnection.Flyio]: TFlyioConnectionOption; }; diff --git a/frontend/src/hooks/api/appConnections/types/flyio-connection.ts b/frontend/src/hooks/api/appConnections/types/flyio-connection.ts new file mode 100644 index 000000000..b1c9123b6 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/types/flyio-connection.ts @@ -0,0 +1,13 @@ +import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection"; + +export enum FlyioConnectionMethod { + AccessToken = "access-token" +} + +export type TFlyioConnection = TRootAppConnection & { app: AppConnection.Flyio } & { + method: FlyioConnectionMethod.AccessToken; + credentials: { + accessToken: string; + }; +}; diff --git a/frontend/src/hooks/api/appConnections/types/index.ts b/frontend/src/hooks/api/appConnections/types/index.ts index 890562b6b..8a57efa35 100644 --- a/frontend/src/hooks/api/appConnections/types/index.ts +++ b/frontend/src/hooks/api/appConnections/types/index.ts @@ -9,6 +9,7 @@ import { TAzureDevOpsConnection } from "./azure-devops-connection"; import { TAzureKeyVaultConnection } from "./azure-key-vault-connection"; import { TCamundaConnection } from "./camunda-connection"; import { TDatabricksConnection } from "./databricks-connection"; +import { TFlyioConnection } from "./flyio-connection"; import { TGcpConnection } from "./gcp-connection"; import { TGitHubConnection } from "./github-connection"; import { TGitHubRadarConnection } from "./github-radar-connection"; @@ -34,6 +35,7 @@ export * from "./azure-devops-connection"; export * from "./azure-key-vault-connection"; export * from "./camunda-connection"; export * from "./databricks-connection"; +export * from "./flyio-connection"; export * from "./gcp-connection"; export * from "./github-connection"; export * from "./github-radar-connection"; @@ -74,7 +76,8 @@ export type TAppConnection = | TLdapConnection | TTeamCityConnection | TOCIConnection - | TOnePassConnection; + | TOnePassConnection + | TFlyioConnection; export type TAvailableAppConnection = Pick; @@ -126,4 +129,5 @@ export type TAppConnectionMap = { [AppConnection.TeamCity]: TTeamCityConnection; [AppConnection.OCI]: TOCIConnection; [AppConnection.OnePass]: TOnePassConnection; + [AppConnection.Flyio]: TFlyioConnection; }; 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 052ff18a7..3882df698 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -18,6 +18,7 @@ import { AzureDevOpsConnectionForm } from "./AzureDevOpsConnectionForm"; import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm"; import { CamundaConnectionForm } from "./CamundaConnectionForm"; import { DatabricksConnectionForm } from "./DatabricksConnectionForm"; +import { FlyioConnectionForm } from "./FlyioConnectionForm"; import { GcpConnectionForm } from "./GcpConnectionForm"; import { GitHubConnectionForm } from "./GitHubConnectionForm"; import { GitHubRadarConnectionForm } from "./GitHubRadarConnectionForm"; @@ -119,6 +120,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => { return ; case AppConnection.OnePass: return ; + case AppConnection.Flyio: + return ; default: throw new Error(`Unhandled App ${app}`); } @@ -203,6 +206,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { return ; case AppConnection.OnePass: return ; + case AppConnection.Flyio: + return ; default: throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`); } diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/FlyioConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/FlyioConnectionForm.tsx new file mode 100644 index 000000000..53f4426fc --- /dev/null +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/FlyioConnectionForm.tsx @@ -0,0 +1,132 @@ +import { Controller, FormProvider, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { + Button, + FormControl, + ModalClose, + SecretInput, + Select, + SelectItem +} from "@app/components/v2"; +import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { FlyioConnectionMethod, TFlyioConnection } from "@app/hooks/api/appConnections"; +import { AppConnection } from "@app/hooks/api/appConnections/enums"; + +import { + genericAppConnectionFieldsSchema, + GenericAppConnectionsFields +} from "./GenericAppConnectionFields"; + +type Props = { + appConnection?: TFlyioConnection; + onSubmit: (formData: FormData) => void; +}; + +const rootSchema = genericAppConnectionFieldsSchema.extend({ + app: z.literal(AppConnection.Flyio) +}); + +const formSchema = z.discriminatedUnion("method", [ + rootSchema.extend({ + method: z.literal(FlyioConnectionMethod.AccessToken), + credentials: z.object({ + accessToken: z.string().trim().min(1, "Access Token required") + }) + }) +]); + +type FormData = z.infer; + +export const FlyioConnectionForm = ({ appConnection, onSubmit }: Props) => { + const isUpdate = Boolean(appConnection); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: appConnection ?? { + app: AppConnection.Flyio, + method: FlyioConnectionMethod.AccessToken + } + }); + + const { + handleSubmit, + control, + formState: { isSubmitting, isDirty } + } = form; + + return ( + +
+ {!isUpdate && } + ( + + + + )} + /> + ( + + onChange(e.target.value)} + /> + + )} + /> +
+ + + + +
+ +
+ ); +};