diff --git a/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx b/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx new file mode 100644 index 00000000..1d383f0a --- /dev/null +++ b/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx @@ -0,0 +1,472 @@ +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { api } from "@/utils/api"; +import { AlertBlock } from "@/components/shared/alert-block"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; +import { Textarea } from "@/components/ui/textarea"; +import { Settings } from "lucide-react"; + +const HealthCheckSwarmSchema = z + .object({ + Test: z.array(z.string()).optional(), + Interval: z.number().optional(), + Timeout: z.number().optional(), + StartPeriod: z.number().optional(), + Retries: z.number().optional(), + }) + .strict(); + +const RestartPolicySwarmSchema = z + .object({ + Condition: z.string().optional(), + Delay: z.number().optional(), + MaxAttempts: z.number().optional(), + Window: z.number().optional(), + }) + .strict(); + +const PreferenceSchema = z + .object({ + Spread: z.object({ + SpreadDescriptor: z.string(), + }), + }) + .strict(); + +const PlatformSchema = z + .object({ + Architecture: z.string(), + OS: z.string(), + }) + .strict(); + +const PlacementSwarmSchema = z + .object({ + Constraints: z.array(z.string()).optional(), + Preferences: z.array(PreferenceSchema).optional(), + MaxReplicas: z.number().optional(), + Platforms: z.array(PlatformSchema).optional(), + }) + .strict(); + +const UpdateConfigSwarmSchema = z.object({ + Parallelism: z.number(), + Delay: z.number().optional(), + FailureAction: z.string().optional(), + Monitor: z.number().optional(), + MaxFailureRatio: z.number().optional(), + Order: z.string(), +}); + +const ReplicatedSchema = z + .object({ + Replicas: z.number().optional(), + }) + .strict(); + +const ReplicatedJobSchema = z + .object({ + MaxConcurrent: z.number().optional(), + TotalCompletions: z.number().optional(), + }) + .strict(); + +const ServiceModeSwarmSchema = z.object({ + Replicated: ReplicatedSchema.optional(), + Global: z.object({}).optional(), + ReplicatedJob: ReplicatedJobSchema.optional(), + GlobalJob: z.object({}).optional(), +}); + +const LabelsSwarmSchema = z.record(z.string()); + +// const stringToJSONSchema = z +// .string() +// .transform((str, ctx) => { +// try { +// return JSON.parse(str); +// } catch (e) { +// ctx.addIssue({ code: "custom", message: "Invalid JSON format" }); +// return z.NEVER; +// } +// }) +// .superRefine((data, ctx) => { +// const parseResult = HealthCheckSwarmSchema.safeParse(data); +// if (!parseResult.success) { +// for (const error of parseResult.error.issues) { +// const path = error.path.join("."); +// ctx.addIssue({ +// code: z.ZodIssueCode.custom, +// message: `${path} ${error.message}`, +// }); +// } +// // parseResult.error.errors.forEach((error) => { +// // const path = error.path.join("."); +// // ctx.addIssue({ +// // code: z.ZodIssueCode.custom, +// // message: `${path} ${error.message}`, +// // }); +// // }); +// } +// }); +const createStringToJSONSchema = (schema: z.ZodTypeAny) => { + return z + .string() + .transform((str, ctx) => { + if (str === null || str === "") { + return null; + } + try { + return JSON.parse(str); + } catch (e) { + ctx.addIssue({ code: "custom", message: "Invalid JSON format" }); + return z.NEVER; + } + }) + .superRefine((data, ctx) => { + if (data === null) { + return; + } + + if (Object.keys(data).length === 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Object cannot be empty", + }); + return; + } + + const parseResult = schema.safeParse(data); + if (!parseResult.success) { + for (const error of parseResult.error.issues) { + const path = error.path.join("."); + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `${path} ${error.message}`, + }); + } + } + }); +}; + +const addSwarmSettings = z.object({ + healthCheckSwarm: createStringToJSONSchema(HealthCheckSwarmSchema).nullable(), + restartPolicySwarm: createStringToJSONSchema( + RestartPolicySwarmSchema, + ).nullable(), + placementSwarm: createStringToJSONSchema(PlacementSwarmSchema).nullable(), + updateConfigSwarm: createStringToJSONSchema( + UpdateConfigSwarmSchema, + ).nullable(), + rollbackConfigSwarm: createStringToJSONSchema( + UpdateConfigSwarmSchema, + ).nullable(), + modeSwarm: createStringToJSONSchema(ServiceModeSwarmSchema).nullable(), + labelsSwarm: createStringToJSONSchema(LabelsSwarmSchema).nullable(), +}); + +type AddSwarmSettings = z.infer; + +interface Props { + applicationId: string; +} + +export const AddSwarmSettings = ({ applicationId }: Props) => { + const { data, refetch } = api.application.one.useQuery( + { + applicationId, + }, + { + enabled: !!applicationId, + }, + ); + + const { mutateAsync, isError, error, isLoading } = + api.application.update.useMutation(); + + const form = useForm({ + defaultValues: { + healthCheckSwarm: null, + restartPolicySwarm: null, + placementSwarm: null, + updateConfigSwarm: null, + rollbackConfigSwarm: null, + modeSwarm: null, + labelsSwarm: null, + }, + resolver: zodResolver(addSwarmSettings), + }); + + useEffect(() => { + if (data) { + console.log(data.healthCheckSwarm, null); + form.reset({ + healthCheckSwarm: data.healthCheckSwarm || null, + restartPolicySwarm: data.restartPolicySwarm || null, + placementSwarm: data.placementSwarm || null, + updateConfigSwarm: data.updateConfigSwarm || null, + rollbackConfigSwarm: data.rollbackConfigSwarm || null, + modeSwarm: data.modeSwarm || null, + labelsSwarm: data.labelsSwarm || null, + }); + } + }, [form, form.formState.isSubmitSuccessful, form.reset, data]); + + const onSubmit = async (data: AddSwarmSettings) => { + console.log(data.restartPolicySwarm); + await mutateAsync({ + applicationId, + healthCheckSwarm: data.healthCheckSwarm + ? JSON.stringify(data.healthCheckSwarm) + : null, + restartPolicySwarm: data.restartPolicySwarm + ? JSON.stringify(data.restartPolicySwarm) + : null, + placementSwarm: data.placementSwarm + ? JSON.stringify(data.placementSwarm) + : null, + updateConfigSwarm: data.updateConfigSwarm + ? JSON.stringify(data.updateConfigSwarm) + : null, + rollbackConfigSwarm: data.rollbackConfigSwarm + ? JSON.stringify(data.rollbackConfigSwarm) + : null, + modeSwarm: data.modeSwarm ? JSON.stringify(data.modeSwarm) : null, + labelsSwarm: data.labelsSwarm ? JSON.stringify(data.labelsSwarm) : null, + }) + .then(async () => { + toast.success("Swarm settings updated"); + refetch(); + }) + .catch(() => { + toast.error("Error to update the swarm settings"); + }); + }; + return ( + + + + + + + Swarm Settings + + Update certain settings using a json object. + + + {isError && {error?.message}} + +
+ + ( + + Health Check + + Check the interface + + +