mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-10 14:05:18 +00:00
refactor(networks): update network form schema and enhance handling logic
- Replaced zodResolver with standardSchemaResolver for improved schema validation. - Added new toggle options for network settings, enhancing user interface clarity. - Refactored network creation and update logic to streamline payload handling. - Updated network form schema to remove default values, ensuring explicit user input. - Introduced SERVER_LOCAL sentinel for local Dokploy server identification.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { Network, Pencil, Plus } from "lucide-react";
|
import { Network, Pencil, Plus, Trash2 } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useFieldArray, useForm } from "react-hook-form";
|
import { useFieldArray, useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -47,6 +47,8 @@ const networkDriverEnum = [
|
|||||||
|
|
||||||
/** Sentinel for "no scope" */
|
/** Sentinel for "no scope" */
|
||||||
const SCOPE_EMPTY = "__scope_none__";
|
const SCOPE_EMPTY = "__scope_none__";
|
||||||
|
/** Sentinel for the local Dokploy server (no serverId) */
|
||||||
|
const SERVER_LOCAL = "__server_local__";
|
||||||
|
|
||||||
const ipamConfigEntrySchema = z.object({
|
const ipamConfigEntrySchema = z.object({
|
||||||
subnet: z.string().optional(),
|
subnet: z.string().optional(),
|
||||||
@@ -56,17 +58,17 @@ const ipamConfigEntrySchema = z.object({
|
|||||||
|
|
||||||
const networkFormSchema = z.object({
|
const networkFormSchema = z.object({
|
||||||
name: z.string().min(1, "Name is required"),
|
name: z.string().min(1, "Name is required"),
|
||||||
driver: z.enum(networkDriverEnum).default("bridge"),
|
driver: z.enum(networkDriverEnum),
|
||||||
scope: z.string().optional(),
|
scope: z.string().optional(),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
internal: z.boolean().default(false),
|
internal: z.boolean(),
|
||||||
attachable: z.boolean().default(false),
|
attachable: z.boolean(),
|
||||||
ingress: z.boolean().default(false),
|
ingress: z.boolean(),
|
||||||
configOnly: z.boolean().default(false),
|
configOnly: z.boolean(),
|
||||||
enableIPv4: z.boolean().default(true),
|
enableIPv4: z.boolean(),
|
||||||
enableIPv6: z.boolean().default(false),
|
enableIPv6: z.boolean(),
|
||||||
ipamDriver: z.string().optional(),
|
ipamDriver: z.string().optional(),
|
||||||
ipamConfig: z.array(ipamConfigEntrySchema).default([]),
|
ipamConfig: z.array(ipamConfigEntrySchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
type NetworkFormValues = z.infer<typeof networkFormSchema>;
|
type NetworkFormValues = z.infer<typeof networkFormSchema>;
|
||||||
@@ -86,6 +88,39 @@ const defaultValues: NetworkFormValues = {
|
|||||||
ipamConfig: [],
|
ipamConfig: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleOptions = [
|
||||||
|
{
|
||||||
|
name: "internal",
|
||||||
|
label: "Internal",
|
||||||
|
description: "Containers on this network cannot reach external networks.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "attachable",
|
||||||
|
label: "Attachable",
|
||||||
|
description: "Allow standalone containers to attach to this network.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "enableIPv4",
|
||||||
|
label: "Enable IPv4",
|
||||||
|
description: "Enable IPv4 addressing on the network.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "enableIPv6",
|
||||||
|
label: "Enable IPv6",
|
||||||
|
description: "Enable IPv6 addressing on the network.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ingress",
|
||||||
|
label: "Ingress",
|
||||||
|
description: "Use as the routing-mesh network in Swarm mode.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "configOnly",
|
||||||
|
label: "Config only",
|
||||||
|
description: "Placeholder network whose config is reused by others.",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
interface HandleNetworkProps {
|
interface HandleNetworkProps {
|
||||||
networkId?: string;
|
networkId?: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@@ -104,9 +139,9 @@ export const HandleNetwork = ({ networkId, children }: HandleNetworkProps) => {
|
|||||||
{ enabled: isEdit && !!networkId },
|
{ enabled: isEdit && !!networkId },
|
||||||
);
|
);
|
||||||
|
|
||||||
const { mutateAsync, isLoading: isPending } = networkId
|
const createMutation = api.network.create.useMutation();
|
||||||
? api.network.update.useMutation()
|
const updateMutation = api.network.update.useMutation();
|
||||||
: api.network.create.useMutation();
|
const isPending = isEdit ? updateMutation.isPending : createMutation.isPending;
|
||||||
|
|
||||||
const form = useForm<NetworkFormValues>({
|
const form = useForm<NetworkFormValues>({
|
||||||
resolver: zodResolver(networkFormSchema),
|
resolver: zodResolver(networkFormSchema),
|
||||||
@@ -148,25 +183,31 @@ export const HandleNetwork = ({ networkId, children }: HandleNetworkProps) => {
|
|||||||
const scope =
|
const scope =
|
||||||
data.scope && data.scope !== SCOPE_EMPTY ? data.scope : undefined;
|
data.scope && data.scope !== SCOPE_EMPTY ? data.scope : undefined;
|
||||||
|
|
||||||
try {
|
const payload = {
|
||||||
await mutateAsync({
|
name: data.name,
|
||||||
networkId: networkId ?? "",
|
driver: data.driver,
|
||||||
name: data.name,
|
scope,
|
||||||
driver: data.driver,
|
serverId: data.serverId || undefined,
|
||||||
scope,
|
internal: data.internal,
|
||||||
serverId: data.serverId || undefined,
|
attachable: data.attachable,
|
||||||
internal: data.internal,
|
ingress: data.ingress,
|
||||||
attachable: data.attachable,
|
configOnly: data.configOnly,
|
||||||
ingress: data.ingress,
|
enableIPv4: data.enableIPv4,
|
||||||
configOnly: data.configOnly,
|
enableIPv6: data.enableIPv6,
|
||||||
enableIPv4: data.enableIPv4,
|
ipam: {
|
||||||
enableIPv6: data.enableIPv6,
|
driver: data.ipamDriver,
|
||||||
ipam: {
|
config: data.ipamConfig,
|
||||||
driver: data.ipamDriver,
|
},
|
||||||
config: data.ipamConfig,
|
};
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isEdit && networkId) {
|
||||||
|
await updateMutation.mutateAsync({ networkId, ...payload });
|
||||||
|
} else {
|
||||||
|
await createMutation.mutateAsync(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(isEdit ? "Network updated" : "Network created");
|
||||||
await utils.network.all.invalidate();
|
await utils.network.all.invalidate();
|
||||||
if (networkId) await utils.network.one.invalidate({ networkId });
|
if (networkId) await utils.network.one.invalidate({ networkId });
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
@@ -193,7 +234,7 @@ export const HandleNetwork = ({ networkId, children }: HandleNetworkProps) => {
|
|||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-xl max-h-[90vh] overflow-y-auto">
|
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<Network className="size-5 text-muted-foreground" />
|
<Network className="size-5 text-muted-foreground" />
|
||||||
@@ -257,199 +298,111 @@ export const HandleNetwork = ({ networkId, children }: HandleNetworkProps) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="serverId"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Server</FormLabel>
|
|
||||||
<Select
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
value={field.value ?? undefined}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select server" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{!isCloud && (
|
|
||||||
<SelectItem value={undefined}>
|
|
||||||
Dokploy server
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
{servers?.map((server) => (
|
|
||||||
<SelectItem
|
|
||||||
key={server.serverId}
|
|
||||||
value={server.serverId}
|
|
||||||
>
|
|
||||||
{server.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormDescription className="text-muted-foreground">
|
|
||||||
{isCloud
|
|
||||||
? "Server where this network will be created."
|
|
||||||
: "Dokploy server is the default local server; or choose a specific server."}
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="scope"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Scope (optional)</FormLabel>
|
|
||||||
<Select
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
value={field.value ?? SCOPE_EMPTY}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select scope" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value={SCOPE_EMPTY}>None</SelectItem>
|
|
||||||
<SelectItem value="local">local</SelectItem>
|
|
||||||
<SelectItem value="swarm">swarm</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="internal"
|
name="serverId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
<FormItem>
|
||||||
<div className="space-y-0.5">
|
<FormLabel>Server</FormLabel>
|
||||||
<FormLabel className="text-base">Internal</FormLabel>
|
<Select
|
||||||
<FormDescription className="text-muted-foreground">
|
onValueChange={(value) =>
|
||||||
Restrict external access; containers on this network
|
field.onChange(
|
||||||
cannot reach external networks.
|
value === SERVER_LOCAL ? undefined : value,
|
||||||
</FormDescription>
|
)
|
||||||
</div>
|
}
|
||||||
<FormControl>
|
value={
|
||||||
<Switch
|
field.value ?? (isCloud ? undefined : SERVER_LOCAL)
|
||||||
checked={field.value}
|
}
|
||||||
onCheckedChange={field.onChange}
|
>
|
||||||
/>
|
<FormControl>
|
||||||
</FormControl>
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select server" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{!isCloud && (
|
||||||
|
<SelectItem value={SERVER_LOCAL}>
|
||||||
|
Dokploy server
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
{servers?.map((server) => (
|
||||||
|
<SelectItem
|
||||||
|
key={server.serverId}
|
||||||
|
value={server.serverId}
|
||||||
|
>
|
||||||
|
{server.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormDescription className="text-muted-foreground">
|
||||||
|
{isCloud
|
||||||
|
? "Server where this network will be created."
|
||||||
|
: "Dokploy server is the default local server."}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="attachable"
|
name="scope"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
<FormItem>
|
||||||
<div className="space-y-0.5">
|
<FormLabel>Scope (optional)</FormLabel>
|
||||||
<FormLabel className="text-base">Attachable</FormLabel>
|
<Select
|
||||||
<FormDescription className="text-muted-foreground">
|
onValueChange={field.onChange}
|
||||||
Allow standalone containers to attach to this network
|
value={field.value ?? SCOPE_EMPTY}
|
||||||
(e.g. in Swarm, not only services).
|
>
|
||||||
</FormDescription>
|
<FormControl>
|
||||||
</div>
|
<SelectTrigger>
|
||||||
<FormControl>
|
<SelectValue placeholder="Select scope" />
|
||||||
<Switch
|
</SelectTrigger>
|
||||||
checked={field.value}
|
</FormControl>
|
||||||
onCheckedChange={field.onChange}
|
<SelectContent>
|
||||||
/>
|
<SelectItem value={SCOPE_EMPTY}>None</SelectItem>
|
||||||
</FormControl>
|
<SelectItem value="local">local</SelectItem>
|
||||||
</FormItem>
|
<SelectItem value="swarm">swarm</SelectItem>
|
||||||
)}
|
</SelectContent>
|
||||||
/>
|
</Select>
|
||||||
<FormField
|
<FormMessage />
|
||||||
control={form.control}
|
|
||||||
name="enableIPv4"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<FormLabel className="text-base">Enable IPv4</FormLabel>
|
|
||||||
<FormDescription className="text-muted-foreground">
|
|
||||||
Enable IPv4 addressing on the network.
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="enableIPv6"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<FormLabel className="text-base">Enable IPv6</FormLabel>
|
|
||||||
<FormDescription className="text-muted-foreground">
|
|
||||||
Enable IPv6 addressing on the network.
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="ingress"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<FormLabel className="text-base">Ingress</FormLabel>
|
|
||||||
<FormDescription className="text-muted-foreground">
|
|
||||||
Use as the routing-mesh network in Swarm mode (load
|
|
||||||
balancing between nodes).
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="configOnly"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<FormLabel className="text-base">Config only</FormLabel>
|
|
||||||
<FormDescription className="text-muted-foreground">
|
|
||||||
Create a placeholder network whose config is reused by
|
|
||||||
other networks; cannot run containers on it.
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 rounded-lg border p-4">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<FormLabel>IPAM</FormLabel>
|
{toggleOptions.map((option) => (
|
||||||
|
<FormField
|
||||||
|
key={option.name}
|
||||||
|
control={form.control}
|
||||||
|
name={option.name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-start justify-between gap-3 space-y-0 rounded-lg border p-4">
|
||||||
|
<div className="space-y-1 pr-1">
|
||||||
|
<FormLabel>{option.label}</FormLabel>
|
||||||
|
<FormDescription className="text-muted-foreground">
|
||||||
|
{option.description}
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4 rounded-lg border p-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<FormLabel>IPAM</FormLabel>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
IP address management settings for this network.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="ipamDriver"
|
name="ipamDriver"
|
||||||
@@ -514,9 +467,10 @@ export const HandleNetwork = ({ networkId, children }: HandleNetworkProps) => {
|
|||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
aria-label="Remove IPAM config"
|
||||||
onClick={() => ipamConfigFieldArray.remove(index)}
|
onClick={() => ipamConfigFieldArray.remove(index)}
|
||||||
>
|
>
|
||||||
−
|
<Trash2 className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -532,6 +486,7 @@ export const HandleNetwork = ({ networkId, children }: HandleNetworkProps) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<Plus className="size-4" />
|
||||||
Add IPAM config
|
Add IPAM config
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -544,14 +499,8 @@ export const HandleNetwork = ({ networkId, children }: HandleNetworkProps) => {
|
|||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" disabled={isPending}>
|
<Button type="submit" isLoading={isPending}>
|
||||||
{isPending
|
{isEdit ? "Update network" : "Create network"}
|
||||||
? isEdit
|
|
||||||
? "Updating…"
|
|
||||||
: "Creating…"
|
|
||||||
: isEdit
|
|
||||||
? "Update network"
|
|
||||||
: "Create network"}
|
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from "@dokploy/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
import type { z } from "zod";
|
||||||
import { IS_CLOUD } from "../constants";
|
import { IS_CLOUD } from "../constants";
|
||||||
import { getRemoteDocker } from "../utils/servers/remote-docker";
|
import { getRemoteDocker } from "../utils/servers/remote-docker";
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ export const findNetworkById = async (networkId: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const createNetwork = async (
|
export const createNetwork = async (
|
||||||
input: typeof apiCreateNetwork._type,
|
input: z.infer<typeof apiCreateNetwork>,
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
) => {
|
) => {
|
||||||
if (IS_CLOUD) {
|
if (IS_CLOUD) {
|
||||||
@@ -86,7 +87,9 @@ export const createNetwork = async (
|
|||||||
return created;
|
return created;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateNetwork = async (input: typeof apiUpdateNetwork._type) => {
|
export const updateNetwork = async (
|
||||||
|
input: z.infer<typeof apiUpdateNetwork>,
|
||||||
|
) => {
|
||||||
const { networkId, ...rest } = input;
|
const { networkId, ...rest } = input;
|
||||||
const [updated] = await db
|
const [updated] = await db
|
||||||
.update(network)
|
.update(network)
|
||||||
|
|||||||
Reference in New Issue
Block a user