mirror of
https://github.com/Infisical/infisical.git
synced 2026-01-10 07:58:15 -05:00
fix: secret sync form
This commit is contained in:
@@ -113,7 +113,7 @@ export const registerOctopusDeployConnectionRouter = async (server: FastifyZodPr
|
||||
name: z.string()
|
||||
})
|
||||
.array(),
|
||||
tenantTags: z
|
||||
roles: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
|
||||
@@ -72,8 +72,11 @@ export const getOctopusDeploySpaces = async (
|
||||
}));
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
const errorMessage = (error.response?.data as { error: { ErrorMessage: string } })?.error?.ErrorMessage;
|
||||
|
||||
throw new BadRequestError({
|
||||
message: `Failed to list Octopus Deploy spaces: ${error.message || "Unknown error"}`
|
||||
message: `Failed to list Octopus Deploy spaces: ${errorMessage || "Unknown error"}`,
|
||||
error: error.response?.data
|
||||
});
|
||||
}
|
||||
|
||||
@@ -107,8 +110,11 @@ export const getOctopusDeployProjects = async (
|
||||
}));
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
const errorMessage = (error.response?.data as { error: { ErrorMessage: string } })?.error?.ErrorMessage;
|
||||
|
||||
throw new BadRequestError({
|
||||
message: `Failed to list Octopus Deploy projects: ${error.message || "Unknown error"}`
|
||||
message: `Failed to list Octopus Deploy projects: ${errorMessage || "Unknown error"}`,
|
||||
error: error.response?.data
|
||||
});
|
||||
}
|
||||
|
||||
@@ -146,9 +152,9 @@ export const getOctopusDeployScopeValues = async (
|
||||
id: environment.Id,
|
||||
name: environment.Name
|
||||
})),
|
||||
tenantTags: ScopeValues.TenantTags.map((tenantTag) => ({
|
||||
id: tenantTag.Id,
|
||||
name: tenantTag.Name
|
||||
roles: ScopeValues.Roles.map((role) => ({
|
||||
id: role.Id,
|
||||
name: role.Name
|
||||
})),
|
||||
machines: ScopeValues.Machines.map((machine) => ({
|
||||
id: machine.Id,
|
||||
@@ -171,8 +177,11 @@ export const getOctopusDeployScopeValues = async (
|
||||
return scopeValues;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
const errorMessage = (error.response?.data as { error: { ErrorMessage: string } })?.error?.ErrorMessage;
|
||||
|
||||
throw new BadRequestError({
|
||||
message: `Failed to get Octopus Deploy scope values: ${error.message || "Unknown error"}`
|
||||
message: `Failed to get Octopus Deploy scope values: ${errorMessage || "Unknown error"}`,
|
||||
error: error.response?.data
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ export type TOctopusDeployProject = {
|
||||
export type TOctopusDeployScopeValuesResponse = {
|
||||
ScopeValues: {
|
||||
Environments: { Id: string; Name: string }[];
|
||||
TenantTags: { Id: string; Name: string }[];
|
||||
Roles: { Id: string; Name: string }[];
|
||||
Machines: { Id: string; Name: string }[];
|
||||
Processes: { Id: string; Name: string }[];
|
||||
Actions: { Id: string; Name: string }[];
|
||||
@@ -61,7 +61,7 @@ export type TOctopusDeployScopeValuesResponse = {
|
||||
|
||||
export type TOctopusDeployScopeValues = {
|
||||
environments: { id: string; name: string }[];
|
||||
tenantTags: { id: string; name: string }[];
|
||||
roles: { id: string; name: string }[];
|
||||
machines: { id: string; name: string }[];
|
||||
processes: { id: string; name: string }[];
|
||||
actions: { id: string; name: string }[];
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretSyncs } from "@app/lib/api-docs";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||
import {
|
||||
BaseSecretSyncSchema,
|
||||
GenericCreateSecretSyncFieldsSchema,
|
||||
GenericUpdateSecretSyncFieldsSchema
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||
|
||||
export enum OctopusDeploySyncScope {
|
||||
Project = "project"
|
||||
}
|
||||
|
||||
const OctopusDeploySyncDestinationConfigBaseSchema = z.object({
|
||||
spaceId: z
|
||||
.string()
|
||||
.min(1, "Space ID required")
|
||||
.describe(SecretSyncs.DESTINATION_CONFIG.OCTOPUS_DEPLOY.spaceId || "Octopus Deploy Space ID"),
|
||||
scope: z.nativeEnum(OctopusDeploySyncScope).default(OctopusDeploySyncScope.Project)
|
||||
});
|
||||
|
||||
export const OctopusDeploySyncDestinationConfigSchema = z.intersection(
|
||||
OctopusDeploySyncDestinationConfigBaseSchema,
|
||||
z.discriminatedUnion("scope", [
|
||||
z.object({
|
||||
scope: z.literal(OctopusDeploySyncScope.Project),
|
||||
projectId: z
|
||||
.string()
|
||||
.min(1, "Project ID required")
|
||||
.describe(SecretSyncs.DESTINATION_CONFIG.OCTOPUS_DEPLOY.projectId),
|
||||
scopeValues: z
|
||||
.object({
|
||||
environments: z.array(z.string()).optional(),
|
||||
roles: z.array(z.string()).optional(),
|
||||
machines: z.array(z.string()).optional(),
|
||||
processes: z.array(z.string()).optional(),
|
||||
actions: z.array(z.string()).optional(),
|
||||
channels: z.array(z.string()).optional()
|
||||
})
|
||||
.optional()
|
||||
.describe(SecretSyncs.DESTINATION_CONFIG.OCTOPUS_DEPLOY.scopeValues)
|
||||
})
|
||||
])
|
||||
);
|
||||
|
||||
const OctopusDeploySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||
|
||||
export const OctopusDeploySyncSchema = BaseSecretSyncSchema(SecretSync.OctopusDeploy, OctopusDeploySyncOptionsConfig)
|
||||
.extend({
|
||||
destination: z.literal(SecretSync.OctopusDeploy),
|
||||
destinationConfig: OctopusDeploySyncDestinationConfigSchema
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.OctopusDeploy] }));
|
||||
|
||||
export const CreateOctopusDeploySyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||
SecretSync.OctopusDeploy,
|
||||
OctopusDeploySyncOptionsConfig
|
||||
).extend({
|
||||
destinationConfig: OctopusDeploySyncDestinationConfigSchema
|
||||
});
|
||||
|
||||
export const UpdateOctopusDeploySyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||
SecretSync.OctopusDeploy,
|
||||
OctopusDeploySyncOptionsConfig
|
||||
).extend({
|
||||
destinationConfig: OctopusDeploySyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const OctopusDeploySyncListItemSchema = z
|
||||
.object({
|
||||
name: z.literal("Octopus Deploy"),
|
||||
connection: z.literal(AppConnection.OctopusDeploy),
|
||||
destination: z.literal(SecretSync.OctopusDeploy),
|
||||
canImportSecrets: z.literal(false)
|
||||
})
|
||||
.describe(JSON.stringify({ title: SECRET_SYNC_NAME_MAP[SecretSync.OctopusDeploy] }));
|
||||
@@ -0,0 +1,437 @@
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { MultiValue, SingleValue } from "react-select";
|
||||
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
FilterableSelect,
|
||||
FormControl,
|
||||
Select,
|
||||
SelectItem,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
useOctopusDeployConnectionGetScopeValues,
|
||||
useOctopusDeployConnectionListProjects,
|
||||
useOctopusDeployConnectionListSpaces
|
||||
} from "@app/hooks/api/appConnections/octopus-deploy/queries";
|
||||
import {
|
||||
TOctopusDeployProject,
|
||||
TOctopusDeploySpace,
|
||||
TScopeValueOption
|
||||
} from "@app/hooks/api/appConnections/octopus-deploy/types";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
import { OctopusDeploySyncScope } from "@app/hooks/api/secretSyncs/types/octopus-deploy-sync";
|
||||
|
||||
import { TSecretSyncForm } from "../schemas";
|
||||
|
||||
export const OctopusDeploySyncFields = () => {
|
||||
const { control, setValue, getValues } = useFormContext<
|
||||
TSecretSyncForm & { destination: SecretSync.OctopusDeploy }
|
||||
>();
|
||||
|
||||
const connectionId = useWatch({ name: "connection.id", control });
|
||||
const spaceId = useWatch({ name: "destinationConfig.spaceId", control });
|
||||
const scope = useWatch({ name: "destinationConfig.scope", control });
|
||||
const projectId = useWatch({ name: "destinationConfig.projectId", control });
|
||||
|
||||
const { data: spaces = [], isLoading: isSpacesLoading } = useOctopusDeployConnectionListSpaces(
|
||||
connectionId,
|
||||
{
|
||||
enabled: Boolean(connectionId)
|
||||
}
|
||||
);
|
||||
|
||||
console.log(getValues());
|
||||
|
||||
const { data: projects = [], isLoading: isProjectsLoading } =
|
||||
useOctopusDeployConnectionListProjects(connectionId, spaceId, {
|
||||
enabled: Boolean(connectionId && spaceId && scope)
|
||||
});
|
||||
|
||||
const { data: scopeValuesData, isLoading: isScopeValuesLoading } =
|
||||
useOctopusDeployConnectionGetScopeValues(connectionId, spaceId, projectId, {
|
||||
enabled: Boolean(connectionId && spaceId && projectId && scope)
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecretSyncConnectionField
|
||||
onChange={() => {
|
||||
setValue("destinationConfig.spaceId", "");
|
||||
setValue("destinationConfig.projectId", "");
|
||||
setValue("destinationConfig.scopeValues", undefined);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="destinationConfig.spaceId"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Space"
|
||||
helperText={
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content="Select the Octopus Deploy space where your project is located."
|
||||
>
|
||||
<div>
|
||||
<span>Don't see the space you're looking for?</span>{" "}
|
||||
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<FilterableSelect
|
||||
menuPlacement="top"
|
||||
isLoading={isSpacesLoading && Boolean(connectionId)}
|
||||
isDisabled={!connectionId}
|
||||
value={spaces?.find((space) => space.id === value) ?? null}
|
||||
onChange={(option) => {
|
||||
const selectedSpace = option as SingleValue<TOctopusDeploySpace>;
|
||||
onChange(selectedSpace?.id ?? null);
|
||||
setValue("destinationConfig.projectId", "");
|
||||
setValue("destinationConfig.scopeValues", undefined);
|
||||
}}
|
||||
options={spaces}
|
||||
placeholder={spaces?.length ? "Select a space..." : "No spaces found..."}
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="destinationConfig.scope"
|
||||
control={control}
|
||||
defaultValue={OctopusDeploySyncScope.Project}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Scope"
|
||||
helperText="Select the scope for this sync configuration."
|
||||
>
|
||||
<Select
|
||||
value={value || OctopusDeploySyncScope.Project}
|
||||
onValueChange={(val) => {
|
||||
onChange(val);
|
||||
setValue("destinationConfig.projectId", "");
|
||||
setValue("destinationConfig.scopeValues", undefined);
|
||||
}}
|
||||
className="w-full border border-mineshaft-500 capitalize"
|
||||
position="popper"
|
||||
placeholder="Select a scope..."
|
||||
dropdownContainerClassName="max-w-none"
|
||||
>
|
||||
{Object.values(OctopusDeploySyncScope).map((scopeValue) => (
|
||||
<SelectItem className="capitalize" value={scopeValue} key={scopeValue}>
|
||||
{scopeValue}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
{scope === OctopusDeploySyncScope.Project && (
|
||||
<Controller
|
||||
name="destinationConfig.projectId"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Project"
|
||||
helperText={
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content="Ensure the project exists in the selected space."
|
||||
>
|
||||
<div>
|
||||
<span>Don't see the project you're looking for?</span>{" "}
|
||||
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<FilterableSelect
|
||||
menuPlacement="top"
|
||||
isLoading={isProjectsLoading && Boolean(connectionId && spaceId)}
|
||||
isDisabled={Boolean(!connectionId || !spaceId)}
|
||||
value={projects?.find((project) => project.id === value) ?? null}
|
||||
onChange={(option) => {
|
||||
onChange((option as SingleValue<TOctopusDeployProject>)?.id ?? null);
|
||||
setValue("destinationConfig.scopeValues", undefined);
|
||||
}}
|
||||
options={projects}
|
||||
placeholder={
|
||||
spaceId && projects?.length ? "Select a project..." : "No projects found..."
|
||||
}
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{scope === OctopusDeploySyncScope.Project && projectId && (
|
||||
<Accordion type="single" collapsible className="w-full bg-mineshaft-700">
|
||||
<AccordionItem value="scope-values" className="overflow-visible">
|
||||
<AccordionTrigger>Scope Values (Optional)</AccordionTrigger>
|
||||
<AccordionContent className="max-h-96 overflow-y-auto">
|
||||
<div className="grid grid-cols-2 gap-x-4">
|
||||
{/* Environments */}
|
||||
<Controller
|
||||
name="destinationConfig.scopeValues.environments"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Environments"
|
||||
isOptional
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
menuPlacement="bottom"
|
||||
menuPosition="absolute"
|
||||
isLoading={isScopeValuesLoading}
|
||||
value={
|
||||
scopeValuesData?.environments?.filter((opt) =>
|
||||
(value || []).includes(opt.id)
|
||||
) || []
|
||||
}
|
||||
onChange={(options) => {
|
||||
const selectedIds = (options as MultiValue<TScopeValueOption>).map(
|
||||
(opt) => opt.id
|
||||
);
|
||||
onChange(selectedIds.length > 0 ? selectedIds : undefined);
|
||||
}}
|
||||
options={scopeValuesData?.environments || []}
|
||||
placeholder={
|
||||
scopeValuesData?.environments?.length
|
||||
? "Select environments..."
|
||||
: "No environments found..."
|
||||
}
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Target Tags */}
|
||||
<Controller
|
||||
name="destinationConfig.scopeValues.roles"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Target Tags"
|
||||
isOptional
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
menuPlacement="bottom"
|
||||
menuPosition="absolute"
|
||||
isLoading={isScopeValuesLoading}
|
||||
value={
|
||||
scopeValuesData?.roles?.filter((opt) => (value || []).includes(opt.id)) ||
|
||||
[]
|
||||
}
|
||||
onChange={(options) => {
|
||||
const selectedIds = (options as MultiValue<TScopeValueOption>).map(
|
||||
(opt) => opt.id
|
||||
);
|
||||
onChange(selectedIds.length > 0 ? selectedIds : undefined);
|
||||
}}
|
||||
options={scopeValuesData?.roles || []}
|
||||
placeholder={
|
||||
scopeValuesData?.roles?.length
|
||||
? "Select target tags..."
|
||||
: "No target tags found..."
|
||||
}
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Targets */}
|
||||
<Controller
|
||||
name="destinationConfig.scopeValues.machines"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Targets"
|
||||
isOptional
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
menuPlacement="top"
|
||||
menuPosition="absolute"
|
||||
isLoading={isScopeValuesLoading}
|
||||
value={
|
||||
scopeValuesData?.machines?.filter((opt) =>
|
||||
(value || []).includes(opt.id)
|
||||
) || []
|
||||
}
|
||||
onChange={(options) => {
|
||||
const selectedIds = (options as MultiValue<TScopeValueOption>).map(
|
||||
(opt) => opt.id
|
||||
);
|
||||
onChange(selectedIds.length > 0 ? selectedIds : undefined);
|
||||
}}
|
||||
options={scopeValuesData?.machines || []}
|
||||
placeholder={
|
||||
scopeValuesData?.machines?.length
|
||||
? "Select targets..."
|
||||
: "No targets found..."
|
||||
}
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{/* Processes */}
|
||||
<Controller
|
||||
name="destinationConfig.scopeValues.processes"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Processes"
|
||||
isOptional
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
menuPlacement="top"
|
||||
menuPosition="absolute"
|
||||
isLoading={isScopeValuesLoading}
|
||||
value={
|
||||
scopeValuesData?.processes?.filter((opt) =>
|
||||
(value || []).includes(opt.id)
|
||||
) || []
|
||||
}
|
||||
onChange={(options) => {
|
||||
const selectedIds = (options as MultiValue<TScopeValueOption>).map(
|
||||
(opt) => opt.id
|
||||
);
|
||||
onChange(selectedIds.length > 0 ? selectedIds : undefined);
|
||||
}}
|
||||
options={scopeValuesData?.processes || []}
|
||||
placeholder={
|
||||
scopeValuesData?.processes?.length
|
||||
? "Select processes..."
|
||||
: "No processes found..."
|
||||
}
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Deployment Steps */}
|
||||
<Controller
|
||||
name="destinationConfig.scopeValues.actions"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Deployment Steps"
|
||||
isOptional
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
menuPlacement="top"
|
||||
menuPosition="absolute"
|
||||
isLoading={isScopeValuesLoading}
|
||||
value={
|
||||
scopeValuesData?.actions?.filter((opt) =>
|
||||
(value || []).includes(opt.id)
|
||||
) || []
|
||||
}
|
||||
onChange={(options) => {
|
||||
const selectedIds = (options as MultiValue<TScopeValueOption>).map(
|
||||
(opt) => opt.id
|
||||
);
|
||||
onChange(selectedIds.length > 0 ? selectedIds : undefined);
|
||||
}}
|
||||
options={scopeValuesData?.actions || []}
|
||||
placeholder={
|
||||
scopeValuesData?.actions?.length
|
||||
? "Select deployment steps..."
|
||||
: "No deployment steps found..."
|
||||
}
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Channels */}
|
||||
<Controller
|
||||
name="destinationConfig.scopeValues.channels"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Channels"
|
||||
isOptional
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
menuPlacement="top"
|
||||
menuPosition="absolute"
|
||||
isLoading={isScopeValuesLoading}
|
||||
value={
|
||||
scopeValuesData?.channels?.filter((opt) =>
|
||||
(value || []).includes(opt.id)
|
||||
) || []
|
||||
}
|
||||
onChange={(options) => {
|
||||
const selectedIds = (options as MultiValue<TScopeValueOption>).map(
|
||||
(opt) => opt.id
|
||||
);
|
||||
onChange(selectedIds.length > 0 ? selectedIds : undefined);
|
||||
}}
|
||||
options={scopeValuesData?.channels || []}
|
||||
placeholder={
|
||||
scopeValuesData?.channels?.length
|
||||
? "Select channels..."
|
||||
: "No channels found..."
|
||||
}
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
import { OctopusDeploySyncScope } from "@app/hooks/api/secretSyncs/types/octopus-deploy-sync";
|
||||
|
||||
export const OctopusDeploySyncDestinationSchema = BaseSecretSyncSchema().merge(
|
||||
z.object({
|
||||
destination: z.literal(SecretSync.OctopusDeploy),
|
||||
destinationConfig: z.intersection(
|
||||
z.object({
|
||||
spaceId: z.string().trim().min(1, { message: "Space ID is required" })
|
||||
}),
|
||||
z.discriminatedUnion("scope", [
|
||||
z.object({
|
||||
scope: z.literal(OctopusDeploySyncScope.Project),
|
||||
projectId: z.string().trim().min(1, { message: "Project ID is required" }),
|
||||
scopeValues: z
|
||||
.object({
|
||||
environments: z.array(z.string()).optional(),
|
||||
roles: z.array(z.string()).optional(),
|
||||
machines: z.array(z.string()).optional(),
|
||||
processes: z.array(z.string()).optional(),
|
||||
actions: z.array(z.string()).optional(),
|
||||
channels: z.array(z.string()).optional()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
);
|
||||
@@ -0,0 +1,26 @@
|
||||
export type TOctopusDeploySpace = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
isDefault: boolean;
|
||||
};
|
||||
|
||||
export type TOctopusDeployProject = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type TOctopusDeployScopeValues = {
|
||||
environments: { id: string; name: string }[];
|
||||
roles: { id: string; name: string }[];
|
||||
machines: { id: string; name: string }[];
|
||||
processes: { id: string; name: string }[];
|
||||
actions: { id: string; name: string }[];
|
||||
channels: { id: string; name: string }[];
|
||||
};
|
||||
|
||||
export type TScopeValueOption = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
|
||||
|
||||
export enum OctopusDeploySyncScope {
|
||||
Project = "project"
|
||||
}
|
||||
|
||||
type TOctopusDeploySyncDestinationConfigProject = {
|
||||
scope: OctopusDeploySyncScope.Project;
|
||||
projectId: string;
|
||||
scopeValues?: {
|
||||
environments?: string[];
|
||||
roles?: string[];
|
||||
machines?: string[];
|
||||
processes?: string[];
|
||||
actions?: string[];
|
||||
channels?: string[];
|
||||
};
|
||||
};
|
||||
|
||||
type TOctopusDeploySyncDestinationConfig = {
|
||||
spaceId: string;
|
||||
} & TOctopusDeploySyncDestinationConfigProject;
|
||||
|
||||
export type TOctopusDeploySync = TRootSecretSync & {
|
||||
destination: SecretSync.OctopusDeploy;
|
||||
destinationConfig: TOctopusDeploySyncDestinationConfig;
|
||||
|
||||
connection: {
|
||||
app: AppConnection.OctopusDeploy;
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user