From 789d64843eb980ff5d98c44c21c389754abe748b Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Mon, 13 Oct 2025 17:07:04 +0530 Subject: [PATCH] feat: adds secret sync handlers --- backend/src/lib/api-docs/constants.ts | 5 +- .../secret-sync/laravel-forge/index.ts | 1 + .../laravel-forge/laravel-forge-sync-fns.ts | 129 ++++++++++++++++++ .../laravel-forge-sync-schemas.ts | 8 +- .../laravel-forge/laravel-forge-sync-types.ts | 29 ++-- .../services/secret-sync/secret-sync-fns.ts | 8 ++ .../LaravelForgeSyncFields.tsx | 6 + .../SecretSyncOptionsFields.tsx | 11 +- .../LaravelForgeSyncReviewFields.tsx | 23 ++++ .../SecretSyncReviewFields.tsx | 24 ++-- .../laravel-forge-sync-destination-schema.ts | 5 +- .../secretSyncs/types/laravel-forge-sync.ts | 3 + .../CloudIntegrationSection.tsx | 14 +- .../LaravelForgeSyncDestinationCol.tsx | 15 ++ .../SecretSyncDestinationCol.tsx | 3 + .../SecretSyncTable/helpers/index.ts | 4 + .../SecretSyncDetailsByIDPage.tsx | 6 +- .../LaravelForgeSyncDestinationSection.tsx | 24 ++++ .../SecretSyncDestinatonSection.tsx | 10 +- .../SecretSyncOptionsSection.tsx | 7 +- 20 files changed, 289 insertions(+), 46 deletions(-) create mode 100644 backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-fns.ts create mode 100644 frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/LaravelForgeSyncReviewFields.tsx create mode 100644 frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/LaravelForgeSyncDestinationCol.tsx create mode 100644 frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/LaravelForgeSyncDestinationSection.tsx diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 141776b2b..1799555fb 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2512,8 +2512,11 @@ export const SecretSyncs = { }, LARAVEL_FORGE: { orgSlug: "The slug of the Laravel Forge org to sync secrets to.", + orgName: "The name of the Laravel Forge org to sync secrets to.", serverId: "The ID of the Laravel Forge server to sync secrets to.", - siteId: "The ID of the Laravel Forge site to sync secrets to." + serverName: "The name of the Laravel Forge server to sync secrets to.", + siteId: "The ID of the Laravel Forge site to sync secrets to.", + siteName: "The name of the Laravel Forge site to sync secrets to." }, WINDMILL: { workspace: "The Windmill workspace to sync secrets to.", diff --git a/backend/src/services/secret-sync/laravel-forge/index.ts b/backend/src/services/secret-sync/laravel-forge/index.ts index b9e2e1fe3..f38e2a06b 100644 --- a/backend/src/services/secret-sync/laravel-forge/index.ts +++ b/backend/src/services/secret-sync/laravel-forge/index.ts @@ -1,3 +1,4 @@ export * from "./laravel-forge-sync-constants"; +export * from "./laravel-forge-sync-fns"; export * from "./laravel-forge-sync-schemas"; export * from "./laravel-forge-sync-types"; diff --git a/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-fns.ts b/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-fns.ts new file mode 100644 index 000000000..c08a11a89 --- /dev/null +++ b/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-fns.ts @@ -0,0 +1,129 @@ +import { request } from "@app/lib/config/request"; +import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; +import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; + +import { + LaravelForgeSecret, + TGetLaravelForgeSecrets, + TLaravelForgeSecrets, + TLaravelForgeSyncWithCredentials +} from "./laravel-forge-sync-types"; + +const getLaravelForgeSecretsRaw = async ({ apiToken, orgSlug, serverId, siteId }: TGetLaravelForgeSecrets) => { + const { data } = await request.get( + `${IntegrationUrls.LARAVELFORGE_API_URL}/api/orgs/${orgSlug}/servers/${serverId}/sites/${siteId}/environment`, + { + headers: { + Authorization: `Bearer ${apiToken}`, + Accept: "application/json", + "Content-Type": "application/json" + } + } + ); + + return data.data.attributes.content; +}; + +const parseEnv = (str: string) => { + const lines = str.split("\n"); + const parsed: { key: string; value: string }[] = []; + + lines.forEach((line) => { + const trimmed = line.trim(); + + const isInvalidLine = trimmed === "" || trimmed.startsWith("#"); + + if (!isInvalidLine && trimmed.includes("=")) { + const equalIndex = trimmed.indexOf("="); + const key = trimmed.substring(0, equalIndex).trim(); + const valueRaw = trimmed.substring(equalIndex + 1).trim(); + let value = valueRaw; + + if ((value.startsWith(`"`) && value.endsWith(`"`)) || (value.startsWith(`'`) && value.endsWith(`'`))) { + value = value.slice(1, -1); + } + + parsed.push({ + key, + value + }); + } + }); + + return parsed; +}; + +const getLaravelForgeSecrets = async (secretSync: TLaravelForgeSyncWithCredentials): Promise => { + const { + connection, + destinationConfig: { orgSlug, serverId, siteId } + } = secretSync; + + const { apiToken } = connection.credentials; + + const secrets = await getLaravelForgeSecretsRaw({ apiToken, orgSlug, serverId, siteId }); + + const parsedSecrets = parseEnv(secrets); + + return parsedSecrets; +}; + +const buildEnvString = (secrets: LaravelForgeSecret[]) => { + return secrets.map((secret) => `${secret.key}=${secret.value}`).join("\n"); +}; + +const updateLaravelForgeSecrets = async (secretSync: TLaravelForgeSyncWithCredentials, envString: string) => { + const { + connection, + destinationConfig: { orgSlug, serverId, siteId } + } = secretSync; + + const { apiToken } = connection.credentials; + + await request.put( + `${IntegrationUrls.LARAVELFORGE_API_URL}/api/orgs/${orgSlug}/servers/${serverId}/sites/${siteId}/environment`, + { + environment: envString + }, + + { + headers: { + Authorization: `Bearer ${apiToken}`, + Accept: "application/json", + "Content-Type": "application/json" + } + } + ); +}; + +export const LaravelForgeSyncFns = { + async syncSecrets(secretSync: TLaravelForgeSyncWithCredentials, secretMap: TSecretMap) { + const secrets = await getLaravelForgeSecrets(secretSync); + const secretsMap = Object.fromEntries(secrets.map((secret) => [secret.key, { value: secret.value }])); + + for (const [key, { value }] of Object.entries(secretMap)) { + secretsMap[key] = { value }; + } + + await updateLaravelForgeSecrets(secretSync, buildEnvString(secrets)); + }, + + async getSecrets(secretSync: TLaravelForgeSyncWithCredentials): Promise { + const secrets = await getLaravelForgeSecrets(secretSync); + return Object.fromEntries(secrets.map((secret) => [secret.key, { value: secret.value }])); + }, + + async removeSecrets(secretSync: TLaravelForgeSyncWithCredentials, secretMap: TSecretMap) { + const existingSecrets = await getLaravelForgeSecrets(secretSync); + + const newSecrets = existingSecrets.filter((secret) => !Object.hasOwn(secretMap, secret.key)); + + if (newSecrets.length === existingSecrets.length) { + return; + } + + const envString = buildEnvString(newSecrets); + + await updateLaravelForgeSecrets(secretSync, envString); + } +}; diff --git a/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-schemas.ts b/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-schemas.ts index b5b4474ae..66632d2dd 100644 --- a/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-schemas.ts +++ b/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-schemas.ts @@ -12,8 +12,14 @@ import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types" const LaravelForgeSyncDestinationConfigSchema = z.object({ orgSlug: z.string().min(1, "Org Slug is required").describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.orgSlug), + orgName: z.string().min(1, "Org Name is required").describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.orgName), serverId: z.string().min(1, "Server ID is required").describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.serverId), - siteId: z.string().min(1, "Site ID is required").describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.siteId) + serverName: z + .string() + .min(1, "Server Name is required") + .describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.serverName), + siteId: z.string().min(1, "Site ID is required").describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.siteId), + siteName: z.string().min(1, "Site Name is required").describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.siteName) }); const LaravelForgeSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; diff --git a/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-types.ts b/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-types.ts index 1de4f6070..e631bc86a 100644 --- a/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-types.ts +++ b/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-types.ts @@ -18,24 +18,29 @@ export type TLaravelForgeSyncWithCredentials = TLaravelForgeSync & { connection: TLaravelForgeConnection; }; +export type TGetLaravelForgeSecrets = { + apiToken: string; + orgSlug: string; + serverId: string; + siteId: string; +}; + +export type TLaravelForgeSecrets = { + data: { + id: string; + type: string; + attributes: { + content: string; + }; + }; +}; + export type LaravelForgeSecret = { - description: string; - is_secret: boolean; key: string; - source: "app" | "env"; value: string; }; export interface LaravelForgeApiSecret { - id: string; key: string; value: string; - type: string; - target: string[]; - customEnvironmentIds?: string[]; - gitBranch?: string; - createdAt?: number; - updatedAt?: number; - configurationId?: string; - system?: boolean; } diff --git a/backend/src/services/secret-sync/secret-sync-fns.ts b/backend/src/services/secret-sync/secret-sync-fns.ts index 80ffeb7ec..85fc27250 100644 --- a/backend/src/services/secret-sync/secret-sync-fns.ts +++ b/backend/src/services/secret-sync/secret-sync-fns.ts @@ -50,6 +50,7 @@ import { HEROKU_SYNC_LIST_OPTION, HerokuSyncFns } from "./heroku"; import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec"; import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns"; import { LARAVEL_FORGE_SYNC_LIST_OPTION } from "./laravel-forge"; +import { LaravelForgeSyncFns } from "./laravel-forge/laravel-forge-sync-fns"; import { NETLIFY_SYNC_LIST_OPTION, NetlifySyncFns } from "./netlify"; import { RAILWAY_SYNC_LIST_OPTION } from "./railway/railway-sync-constants"; import { RailwaySyncFns } from "./railway/railway-sync-fns"; @@ -279,6 +280,8 @@ export const SecretSyncFns = { return NetlifySyncFns.syncSecrets(secretSync, schemaSecretMap); case SecretSync.Bitbucket: return BitbucketSyncFns.syncSecrets(secretSync, schemaSecretMap); + case SecretSync.LaravelForge: + return LaravelForgeSyncFns.syncSecrets(secretSync, schemaSecretMap); default: throw new Error( `Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` @@ -395,6 +398,9 @@ export const SecretSyncFns = { case SecretSync.Bitbucket: secretMap = await BitbucketSyncFns.getSecrets(secretSync); break; + case SecretSync.LaravelForge: + secretMap = await LaravelForgeSyncFns.getSecrets(secretSync); + break; default: throw new Error( `Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` @@ -488,6 +494,8 @@ export const SecretSyncFns = { return NetlifySyncFns.removeSecrets(secretSync, schemaSecretMap); case SecretSync.Bitbucket: return BitbucketSyncFns.removeSecrets(secretSync, schemaSecretMap); + case SecretSync.LaravelForge: + return LaravelForgeSyncFns.removeSecrets(secretSync, schemaSecretMap); default: throw new Error( `Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/LaravelForgeSyncFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/LaravelForgeSyncFields.tsx index 33e7b80e8..90409da4f 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/LaravelForgeSyncFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/LaravelForgeSyncFields.tsx @@ -50,6 +50,9 @@ export const LaravelForgeSyncFields = () => { setValue("destinationConfig.orgSlug", ""); setValue("destinationConfig.serverId", ""); setValue("destinationConfig.siteId", ""); + setValue("destinationConfig.orgName", ""); + setValue("destinationConfig.serverName", ""); + setValue("destinationConfig.siteName", ""); }; return ( @@ -69,6 +72,7 @@ export const LaravelForgeSyncFields = () => { onChange={(option) => { const selectedOrg = option as SingleValue; onChange(selectedOrg?.slug ?? ""); + setValue("destinationConfig.orgName", selectedOrg?.name ?? ""); setValue("destinationConfig.serverId", ""); setValue("destinationConfig.siteId", ""); }} @@ -94,6 +98,7 @@ export const LaravelForgeSyncFields = () => { onChange={(option) => { const selectedServer = option as SingleValue; onChange(selectedServer?.id ?? ""); + setValue("destinationConfig.serverName", selectedServer?.name ?? ""); setValue("destinationConfig.siteId", ""); }} options={servers} @@ -118,6 +123,7 @@ export const LaravelForgeSyncFields = () => { onChange={(option) => { const selectedSite = option as SingleValue; onChange(selectedSite?.id ?? ""); + setValue("destinationConfig.siteName", selectedSite?.name ?? ""); }} options={sites} placeholder="Select a site..." diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx index 4fe457179..de5b85e02 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx @@ -69,6 +69,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { case SecretSync.DigitalOceanAppPlatform: case SecretSync.Netlify: case SecretSync.Bitbucket: + case SecretSync.LaravelForge: AdditionalSyncOptionsFieldsComponent = null; break; default: @@ -77,7 +78,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { return ( <> -

Configure how secrets should be synced.

+

Configure how secrets should be synced.

{!hideInitialSync && ( <> { return (
  • - {name}:{" "} + {name}:{" "} {description}

  • @@ -118,7 +119,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { isDisabled={!syncOption?.canImportSecrets} value={value} onValueChange={(val) => onChange(val)} - className="w-full border border-mineshaft-500" + className="border-mineshaft-500 w-full border" position="popper" placeholder="Select an option..." dropdownContainerClassName="max-w-none" @@ -137,7 +138,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { )} /> {!syncOption?.canImportSecrets && ( -

    +

    {destinationName} only supports overwriting destination secrets.{" "} {!currentSyncOption.disableSecretDeletion && @@ -218,7 +219,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { return ( { + const { watch } = useFormContext(); + const orgName = watch("destinationConfig.orgName"); + const orgSlug = watch("destinationConfig.orgSlug"); + const serverName = watch("destinationConfig.serverName"); + const serverId = watch("destinationConfig.serverId"); + const siteName = watch("destinationConfig.siteName"); + const siteId = watch("destinationConfig.siteId"); + + return ( + <> + {orgName || orgSlug} + {serverName || serverId || "None"} + {siteName || siteId || "None"} + + ); +}; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx index d6c34fb87..6475ad7b3 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx @@ -35,6 +35,7 @@ import { GitLabSyncReviewFields } from "./GitLabSyncReviewFields"; import { HCVaultSyncReviewFields } from "./HCVaultSyncReviewFields"; import { HerokuSyncReviewFields } from "./HerokuSyncReviewFields"; import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields"; +import { LaravelForgeSyncReviewFields } from "./LaravelForgeSyncReviewFields"; import { NetlifySyncReviewFields } from "./NetlifySyncReviewFields"; import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields"; import { OnePassSyncReviewFields } from "./OnePassSyncReviewFields"; @@ -168,6 +169,9 @@ export const SecretSyncReviewFields = () => { case SecretSync.Bitbucket: DestinationFieldsComponent = ; break; + case SecretSync.LaravelForge: + DestinationFieldsComponent = ; + break; default: throw new Error(`Unhandled Destination Review Fields: ${destination}`); } @@ -175,8 +179,8 @@ export const SecretSyncReviewFields = () => { return (

    -
    - Source +
    + Source
    {environment.name} @@ -184,14 +188,14 @@ export const SecretSyncReviewFields = () => {
    -
    - Destination - {isChecking && Checking...} +
    + Destination + {isChecking && Checking...}
    {hasDuplicate && (
    - +

    Another secret sync in your organization is already configured with the same @@ -215,8 +219,8 @@ export const SecretSyncReviewFields = () => {

    -
    - Sync Options +
    + Sync Options
    @@ -237,8 +241,8 @@ export const SecretSyncReviewFields = () => {
    -
    - Details +
    + Details
    {name} diff --git a/frontend/src/components/secret-syncs/forms/schemas/laravel-forge-sync-destination-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/laravel-forge-sync-destination-schema.ts index 41f8ab51f..011f835b7 100644 --- a/frontend/src/components/secret-syncs/forms/schemas/laravel-forge-sync-destination-schema.ts +++ b/frontend/src/components/secret-syncs/forms/schemas/laravel-forge-sync-destination-schema.ts @@ -8,8 +8,11 @@ export const LaravelForgeSyncDestinationSchema = BaseSecretSyncSchema().merge( destination: z.literal(SecretSync.LaravelForge), destinationConfig: z.object({ orgSlug: z.string().trim().min(1, "Org Slug required"), + orgName: z.string().trim().min(1, "Org Name required"), serverId: z.string().trim().min(1, "Server ID required"), - siteId: z.string().trim().min(1, "Site ID required") + serverName: z.string().trim().min(1, "Server Name required"), + siteId: z.string().trim().min(1, "Site ID required"), + siteName: z.string().trim().min(1, "Site Name required") }) }) ); diff --git a/frontend/src/hooks/api/secretSyncs/types/laravel-forge-sync.ts b/frontend/src/hooks/api/secretSyncs/types/laravel-forge-sync.ts index 68a2deb7b..cce693a48 100644 --- a/frontend/src/hooks/api/secretSyncs/types/laravel-forge-sync.ts +++ b/frontend/src/hooks/api/secretSyncs/types/laravel-forge-sync.ts @@ -6,8 +6,11 @@ export type TLaravelForgeSync = TRootSecretSync & { destination: SecretSync.LaravelForge; destinationConfig: { orgSlug: string; + orgName: string; serverId: string; + serverName: string; siteId: string; + siteName: string; }; connection: { app: AppConnection.LaravelForge; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx index 9a6db9def..3b46d4b64 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx @@ -130,9 +130,9 @@ export const CloudIntegrationSection = ({ tabIndex={0} className={`group relative ${ cloudIntegration.isAvailable - ? "cursor-pointer duration-200 hover:bg-mineshaft-700" + ? "hover:bg-mineshaft-700 cursor-pointer duration-200" : "opacity-50" - } flex h-36 flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-3`} + } border-mineshaft-600 bg-mineshaft-800 flex h-36 flex-col items-center justify-center rounded-md border p-3`} onClick={() => { if (isSyncAvailable) { navigate({ @@ -180,9 +180,9 @@ export const CloudIntegrationSection = ({
    {cloudIntegration.isAvailable && Boolean(integrationAuths?.[cloudIntegration.slug]) && ( -
    +
    -
    +
    Authorized
    @@ -197,7 +197,7 @@ export const CloudIntegrationSection = ({ provider: cloudIntegration.slug }); }} - className="absolute top-0 right-0 flex h-0 w-12 cursor-pointer items-center justify-center overflow-hidden rounded-r-md bg-red text-xs opacity-50 transition-all duration-300 group-hover:h-full hover:opacity-100" + className="bg-red absolute right-0 top-0 flex h-0 w-12 cursor-pointer items-center justify-center overflow-hidden rounded-r-md text-xs opacity-50 transition-all duration-300 hover:opacity-100 group-hover:h-full" >
    @@ -208,7 +208,7 @@ export const CloudIntegrationSection = ({ {isSyncAvailable && (
    -
    +
    Secret Sync Available
    @@ -230,7 +230,7 @@ export const CloudIntegrationSection = ({ {Array.from({ length: 16 }).map((_, index) => (
    ))}
    diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/LaravelForgeSyncDestinationCol.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/LaravelForgeSyncDestinationCol.tsx new file mode 100644 index 000000000..dbb4f0ebd --- /dev/null +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/LaravelForgeSyncDestinationCol.tsx @@ -0,0 +1,15 @@ +import { TLaravelForgeSync } from "@app/hooks/api/secretSyncs/types/laravel-forge-sync"; + +import { getSecretSyncDestinationColValues } from "../helpers"; +import { SecretSyncTableCell } from "../SecretSyncTableCell"; + +type Props = { + secretSync: TLaravelForgeSync; +}; + +export const LaravelForgeSyncDestinationCol = ({ secretSync }: Props) => { + const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync); + + console.log({ secretSync }); + return ; +}; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx index 009af9930..1e4df7351 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx @@ -20,6 +20,7 @@ import { GitLabSyncDestinationCol } from "./GitLabSyncDestinationCol"; import { HCVaultSyncDestinationCol } from "./HCVaultSyncDestinationCol"; import { HerokuSyncDestinationCol } from "./HerokuSyncDestinationCol"; import { HumanitecSyncDestinationCol } from "./HumanitecSyncDestinationCol"; +import { LaravelForgeSyncDestinationCol } from "./LaravelForgeSyncDestinationCol"; import { NetlifySyncDestinationCol } from "./NetlifySyncDestinationCol"; import { OCIVaultSyncDestinationCol } from "./OCIVaultSyncDestinationCol"; import { RailwaySyncDestinationCol } from "./RailwaySyncDestinationCol"; @@ -97,6 +98,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => { return ; case SecretSync.Bitbucket: return ; + case SecretSync.LaravelForge: + return ; default: throw new Error( `Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}` diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts index bb0246236..c4a3f2a37 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts @@ -194,6 +194,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => { primaryText = destinationConfig.workspaceSlug; secondaryText = destinationConfig.repositorySlug; break; + case SecretSync.LaravelForge: + primaryText = destinationConfig.siteName || destinationConfig.siteId; + secondaryText = destinationConfig.orgName || destinationConfig.orgSlug; + break; default: throw new Error(`Unhandled Destination Col Values ${destination}`); } diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/SecretSyncDetailsByIDPage.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/SecretSyncDetailsByIDPage.tsx index 548c1c847..fd87bbfb5 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/SecretSyncDetailsByIDPage.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/SecretSyncDetailsByIDPage.tsx @@ -72,7 +72,7 @@ const PageContent = () => { return ( <> -
    +
    diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/LaravelForgeSyncDestinationSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/LaravelForgeSyncDestinationSection.tsx new file mode 100644 index 000000000..7d78fd770 --- /dev/null +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/LaravelForgeSyncDestinationSection.tsx @@ -0,0 +1,24 @@ +import { GenericFieldLabel } from "@app/components/secret-syncs"; +import { TLaravelForgeSync } from "@app/hooks/api/secretSyncs/types/laravel-forge-sync"; + +type Props = { + secretSync: TLaravelForgeSync; +}; + +export const LaravelForgeSyncDestinationSection = ({ secretSync }: Props) => { + const { destinationConfig } = secretSync; + + return ( + <> + + {destinationConfig.orgName || destinationConfig.orgSlug} + + + {destinationConfig.serverName || destinationConfig.serverId} + + + {destinationConfig.siteName || destinationConfig.siteId} + + + ); +}; diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx index 7ab07c8d1..41c346aba 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx @@ -31,6 +31,7 @@ import { GitLabSyncDestinationSection } from "./GitLabSyncDestinationSection"; import { HCVaultSyncDestinationSection } from "./HCVaultSyncDestinationSection"; import { HerokuSyncDestinationSection } from "./HerokuSyncDestinationSection"; import { HumanitecSyncDestinationSection } from "./HumanitecSyncDestinationSection"; +import { LaravelForgeSyncDestinationSection } from "./LaravelForgeSyncDestinationSection"; import { NetlifySyncDestinationSection } from "./NetlifySyncDestinationSection"; import { OCIVaultSyncDestinationSection } from "./OCIVaultSyncDestinationSection"; import { RailwaySyncDestinationSection } from "./RailwaySyncDestinationSection"; @@ -148,6 +149,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }: case SecretSync.Bitbucket: DestinationComponents = ; break; + case SecretSync.LaravelForge: + DestinationComponents = ; + break; default: throw new Error(`Unhandled Destination Section components: ${destination}`); } @@ -161,9 +165,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }: : ProjectPermissionSub.SecretSyncs; return ( -
    -
    -

    Destination Configuration

    +
    +
    +

    Destination Configuration

    {(isAllowed) => ( -
    -
    -

    Sync Options

    +
    +
    +

    Sync Options

    {(isAllowed) => (