Merge branch 'main' into ENG-3041

This commit is contained in:
x032205
2025-07-03 19:55:32 -04:00
148 changed files with 3763 additions and 846 deletions
@@ -60,7 +60,7 @@ export const OnePassSyncFields = () => {
menuPlacement="top"
isLoading={isVaultsLoading && Boolean(connectionId)}
isDisabled={!connectionId}
value={vaults?.find((v) => v.id === value) ?? null}
value={vaults?.find((v) => v.id === value) || null}
onChange={(option) => onChange((option as SingleValue<TOnePassVault>)?.id ?? null)}
options={vaults}
placeholder="Select a vault..."
@@ -25,6 +25,7 @@ import { TeamCitySyncFields } from "./TeamCitySyncFields";
import { TerraformCloudSyncFields } from "./TerraformCloudSyncFields";
import { VercelSyncFields } from "./VercelSyncFields";
import { WindmillSyncFields } from "./WindmillSyncFields";
import { ZabbixSyncFields } from "./ZabbixSyncFields";
export const SecretSyncDestinationFields = () => {
const { watch } = useFormContext<TSecretSyncForm>();
@@ -76,6 +77,8 @@ export const SecretSyncDestinationFields = () => {
return <GitLabSyncFields />;
case SecretSync.CloudflarePages:
return <CloudflarePagesSyncFields />;
case SecretSync.Zabbix:
return <ZabbixSyncFields />;
default:
throw new Error(`Unhandled Destination Config Field: ${destination}`);
}
@@ -0,0 +1,147 @@
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { SingleValue } from "react-select";
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
import { FilterableSelect, FormControl, Select, SelectItem } from "@app/components/v2";
import {
TZabbixHost,
useZabbixConnectionListHosts,
ZABBIX_SYNC_SCOPES,
ZabbixMacroType,
ZabbixSyncScope
} from "@app/hooks/api/appConnections/zabbix";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TSecretSyncForm } from "../schemas";
export const ZabbixSyncFields = () => {
const { control, watch, setValue } = useFormContext<
TSecretSyncForm & { destination: SecretSync.Zabbix }
>();
const connectionId = useWatch({ name: "connection.id", control });
const currentScope = watch("destinationConfig.scope");
const { data: hosts = [], isPending: isHostsPending } = useZabbixConnectionListHosts(
connectionId,
{
enabled: Boolean(connectionId)
}
);
return (
<>
<SecretSyncConnectionField
onChange={() => {
setValue("destinationConfig.scope", ZabbixSyncScope.Global);
setValue("destinationConfig.hostId", "");
setValue("destinationConfig.hostName", "");
}}
/>
<Controller
name="destinationConfig.scope"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Scope"
tooltipClassName="max-w-lg py-3"
tooltipText={
<div className="flex flex-col gap-3">
<p>
Specify how Infisical should manage secrets from Zabbix. The following options are
available:
</p>
<ul className="flex list-disc flex-col gap-3 pl-4">
{Object.values(ZABBIX_SYNC_SCOPES).map(({ name, description }) => {
return (
<li key={name}>
<p className="text-mineshaft-300">
<span className="font-medium text-bunker-200">{name}</span>: {description}
</p>
</li>
);
})}
</ul>
</div>
}
>
<Select
value={value}
onValueChange={(val) => {
onChange(val);
setValue("destinationConfig.hostId", "");
setValue("destinationConfig.hostName", "");
}}
className="w-full border border-mineshaft-500 capitalize"
position="popper"
placeholder="Select a scope..."
dropdownContainerClassName="max-w-none"
>
{Object.values(ZabbixSyncScope).map((scope) => (
<SelectItem className="capitalize" value={scope} key={scope}>
{scope.replace("-", " ")}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
{currentScope === ZabbixSyncScope.Host && (
<Controller
name="destinationConfig.hostId"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message} label="Host">
<FilterableSelect
menuPlacement="top"
isLoading={isHostsPending && Boolean(connectionId)}
isDisabled={!connectionId}
value={hosts.find((host) => host.hostId === value) ?? null}
onChange={(option) => {
const selectedOption = option as SingleValue<TZabbixHost>;
onChange(selectedOption?.hostId ?? null);
if (selectedOption) {
setValue("destinationConfig.hostName", selectedOption.host);
} else {
setValue("destinationConfig.hostName", "");
}
}}
options={hosts}
placeholder="Select a host..."
getOptionLabel={(option) => option.host}
getOptionValue={(option) => option.hostId}
/>
</FormControl>
)}
/>
)}
<Controller
control={control}
name="destinationConfig.macroType"
defaultValue={ZabbixMacroType.Secret}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message} label="Macro Type">
<Select
value={String(value)}
onValueChange={(val) => onChange(parseInt(val, 10))}
className="w-full border border-mineshaft-500 capitalize"
position="popper"
placeholder="Select a macro type..."
dropdownContainerClassName="max-w-none"
>
<SelectItem value={String(ZabbixMacroType.Text)} key="text">
Text
</SelectItem>
<SelectItem value={String(ZabbixMacroType.Secret)} key="secret">
Secret
</SelectItem>
</Select>
</FormControl>
)}
/>
</>
);
};
@@ -23,6 +23,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
const { control, watch } = useFormContext<TSecretSyncForm>();
const destination = watch("destination");
const currentSyncOption = watch("syncOptions");
const destinationName = SECRET_SYNC_MAP[destination].name;
@@ -57,6 +58,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
case SecretSync.Flyio:
case SecretSync.GitLab:
case SecretSync.CloudflarePages:
case SecretSync.Zabbix:
AdditionalSyncOptionsFieldsComponent = null;
break;
default:
@@ -127,8 +129,9 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
{!syncOption?.canImportSecrets && (
<p className="-mt-2.5 mb-2.5 text-xs text-yellow">
<FontAwesomeIcon className="mr-1" size="xs" icon={faTriangleExclamation} />
{destinationName} only supports overwriting destination secrets. Secrets not present
in Infisical will be removed from the destination.
{destinationName} only supports overwriting destination secrets.{" "}
{!currentSyncOption.disableSecretDeletion &&
"Secrets not present in Infisical will be removed from the destination."}
</p>
)}
</>
@@ -35,6 +35,7 @@ import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields";
import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields";
import { VercelSyncReviewFields } from "./VercelSyncReviewFields";
import { WindmillSyncReviewFields } from "./WindmillSyncReviewFields";
import { ZabbixSyncReviewFields } from "./ZabbixSyncReviewFields";
export const SecretSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm>();
@@ -124,6 +125,9 @@ export const SecretSyncReviewFields = () => {
case SecretSync.CloudflarePages:
DestinationFieldsComponent = <CloudflarePagesSyncReviewFields />;
break;
case SecretSync.Zabbix:
DestinationFieldsComponent = <ZabbixSyncReviewFields />;
break;
default:
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
}
@@ -0,0 +1,31 @@
import { useFormContext } from "react-hook-form";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { GenericFieldLabel } from "@app/components/v2";
import { ZabbixSyncScope } from "@app/hooks/api/appConnections/zabbix";
import { SecretSync } from "@app/hooks/api/secretSyncs";
const isTextMacro = (macroType: number) => macroType === 0;
export const ZabbixSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Zabbix }>();
const scope = watch("destinationConfig.scope");
const hostId = watch("destinationConfig.hostId");
const hostName = watch("destinationConfig.hostName");
const macroType = watch("destinationConfig.macroType");
return (
<>
<GenericFieldLabel label="Scope">{scope}</GenericFieldLabel>
{scope === ZabbixSyncScope.Host && (
<>
<GenericFieldLabel label="Host ID">{hostId}</GenericFieldLabel>
<GenericFieldLabel label="Host Name">{hostName}</GenericFieldLabel>
</>
)}
<GenericFieldLabel label="Macro Type">
{isTextMacro(macroType) ? "Text" : "Secret"}
</GenericFieldLabel>
</>
);
};
@@ -22,6 +22,7 @@ import { TeamCitySyncDestinationSchema } from "./teamcity-sync-destination-schem
import { TerraformCloudSyncDestinationSchema } from "./terraform-cloud-destination-schema";
import { VercelSyncDestinationSchema } from "./vercel-sync-destination-schema";
import { WindmillSyncDestinationSchema } from "./windmill-sync-destination-schema";
import { ZabbixSyncDestinationSchema } from "./zabbix-sync-destination-schema";
const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
AwsParameterStoreSyncDestinationSchema,
@@ -45,7 +46,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
RenderSyncDestinationSchema,
FlyioSyncDestinationSchema,
GitlabSyncDestinationSchema,
CloudflarePagesSyncDestinationSchema
CloudflarePagesSyncDestinationSchema,
ZabbixSyncDestinationSchema
]);
export const SecretSyncFormSchema = SecretSyncUnionSchema;
@@ -0,0 +1,27 @@
import { z } from "zod";
import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema";
import { ZabbixMacroType, ZabbixSyncScope } from "@app/hooks/api/appConnections/zabbix";
import { SecretSync } from "@app/hooks/api/secretSyncs";
export const ZabbixSyncDestinationSchema = BaseSecretSyncSchema().merge(
z.object({
destination: z.literal(SecretSync.Zabbix),
destinationConfig: z.discriminatedUnion("scope", [
z.object({
scope: z.literal(ZabbixSyncScope.Host),
hostId: z.string().trim().min(1, "Host ID required"),
hostName: z.string().trim().min(1, "Host name required"),
macroType: z.nativeEnum(ZabbixMacroType, {
errorMap: () => ({ message: "Macro type must be either 'text' or 'secret'" })
})
}),
z.object({
scope: z.literal(ZabbixSyncScope.Global),
macroType: z.nativeEnum(ZabbixMacroType, {
errorMap: () => ({ message: "Macro type must be either 'text' or 'secret'" })
})
})
])
})
);