mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-28 13:48:13 +02:00
use strict zod objects and hide proto on targets
This commit is contained in:
parent
44b932937f
commit
ba3505a385
14 changed files with 154 additions and 162 deletions
|
@ -10,6 +10,7 @@ import {
|
||||||
import { and, eq, or } from "drizzle-orm";
|
import { and, eq, or } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export async function verifySiteAccess(
|
export async function verifySiteAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -28,6 +29,7 @@ export async function verifySiteAccess(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNaN(siteId)) {
|
if (isNaN(siteId)) {
|
||||||
|
logger.debug(JSON.stringify(req.body));
|
||||||
return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid site ID"));
|
return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid site ID"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,6 @@ authenticated.get(
|
||||||
authenticated.post(
|
authenticated.post(
|
||||||
"/resource/:resourceId",
|
"/resource/:resourceId",
|
||||||
verifyResourceAccess,
|
verifyResourceAccess,
|
||||||
verifySiteAccess,
|
|
||||||
verifyUserHasAction(ActionsEnum.updateResource),
|
verifyUserHasAction(ActionsEnum.updateResource),
|
||||||
resource.updateResource
|
resource.updateResource
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,11 +12,13 @@ import config from "@server/config";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { defaultRoleAllowedActions } from "../role";
|
import { defaultRoleAllowedActions } from "../role";
|
||||||
|
|
||||||
const createOrgSchema = z.object({
|
const createOrgSchema = z
|
||||||
orgId: z.string(),
|
.object({
|
||||||
name: z.string().min(1).max(255),
|
orgId: z.string(),
|
||||||
// domain: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255),
|
||||||
});
|
// domain: z.string().min(1).max(255).optional(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const MAX_ORGS = 5;
|
const MAX_ORGS = 5;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ const updateOrgBodySchema = z
|
||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).optional(),
|
||||||
domain: z.string().min(1).max(255).optional(),
|
domain: z.string().min(1).max(255).optional(),
|
||||||
})
|
})
|
||||||
|
.strict()
|
||||||
.refine((data) => Object.keys(data).length > 0, {
|
.refine((data) => Object.keys(data).length > 0, {
|
||||||
message: "At least one field must be provided for update",
|
message: "At least one field must be provided for update",
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,10 +25,12 @@ const createResourceParamsSchema = z.object({
|
||||||
orgId: z.string(),
|
orgId: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const createResourceSchema = z.object({
|
const createResourceSchema = z
|
||||||
name: z.string().min(1).max(255),
|
.object({
|
||||||
subdomain: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255),
|
||||||
});
|
subdomain: z.string().min(1).max(255).optional(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type CreateResourceResponse = Resource;
|
export type CreateResourceResponse = Resource;
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,9 @@ const updateResourceBodySchema = z
|
||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).optional(),
|
||||||
subdomain: z.string().min(1).max(255).optional(),
|
subdomain: z.string().min(1).max(255).optional(),
|
||||||
ssl: z.boolean().optional(),
|
ssl: z.boolean().optional(),
|
||||||
siteId: z.number(),
|
// siteId: z.number(),
|
||||||
})
|
})
|
||||||
|
.strict()
|
||||||
.refine((data) => Object.keys(data).length > 0, {
|
.refine((data) => Object.keys(data).length > 0, {
|
||||||
message: "At least one field must be provided for update",
|
message: "At least one field must be provided for update",
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,10 +14,12 @@ const createRoleParamsSchema = z.object({
|
||||||
orgId: z.string(),
|
orgId: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const createRoleSchema = z.object({
|
const createRoleSchema = z
|
||||||
name: z.string().min(1).max(255),
|
.object({
|
||||||
description: z.string().optional(),
|
name: z.string().min(1).max(255),
|
||||||
});
|
description: z.string().optional(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export const defaultRoleAllowedActions: ActionsEnum[] = [
|
export const defaultRoleAllowedActions: ActionsEnum[] = [
|
||||||
ActionsEnum.getOrg,
|
ActionsEnum.getOrg,
|
||||||
|
|
|
@ -18,6 +18,7 @@ const updateRoleBodySchema = z
|
||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).optional(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
.strict()
|
||||||
.refine((data) => Object.keys(data).length > 0, {
|
.refine((data) => Object.keys(data).length > 0, {
|
||||||
message: "At least one field must be provided for update",
|
message: "At least one field must be provided for update",
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,13 +15,15 @@ const createSiteParamsSchema = z.object({
|
||||||
orgId: z.string(),
|
orgId: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const createSiteSchema = z.object({
|
const createSiteSchema = z
|
||||||
name: z.string().min(1).max(255),
|
.object({
|
||||||
exitNodeId: z.number().int().positive(),
|
name: z.string().min(1).max(255),
|
||||||
subdomain: z.string().min(1).max(255).optional(),
|
exitNodeId: z.number().int().positive(),
|
||||||
pubKey: z.string(),
|
subdomain: z.string().min(1).max(255).optional(),
|
||||||
subnet: z.string(),
|
pubKey: z.string(),
|
||||||
});
|
subnet: z.string(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type CreateSiteResponse = {
|
export type CreateSiteResponse = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -23,6 +23,7 @@ const updateSiteBodySchema = z
|
||||||
megabytesIn: z.number().int().nonnegative().optional(),
|
megabytesIn: z.number().int().nonnegative().optional(),
|
||||||
megabytesOut: z.number().int().nonnegative().optional(),
|
megabytesOut: z.number().int().nonnegative().optional(),
|
||||||
})
|
})
|
||||||
|
.strict()
|
||||||
.refine((data) => Object.keys(data).length > 0, {
|
.refine((data) => Object.keys(data).length > 0, {
|
||||||
message: "At least one field must be provided for update",
|
message: "At least one field must be provided for update",
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,13 +15,15 @@ const createTargetParamsSchema = z.object({
|
||||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
||||||
});
|
});
|
||||||
|
|
||||||
const createTargetSchema = z.object({
|
const createTargetSchema = z
|
||||||
ip: z.string().ip(),
|
.object({
|
||||||
method: z.string().min(1).max(10),
|
ip: z.string().ip(),
|
||||||
port: z.number().int().min(1).max(65535),
|
method: z.string().min(1).max(10),
|
||||||
protocol: z.string().optional(),
|
port: z.number().int().min(1).max(65535),
|
||||||
enabled: z.boolean().default(true),
|
protocol: z.string().optional(),
|
||||||
});
|
enabled: z.boolean().default(true),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export type CreateTargetResponse = Target;
|
export type CreateTargetResponse = Target;
|
||||||
|
|
||||||
|
@ -104,6 +106,7 @@ export async function createTarget(
|
||||||
.insert(targets)
|
.insert(targets)
|
||||||
.values({
|
.values({
|
||||||
resourceId,
|
resourceId,
|
||||||
|
protocol: "tcp", // hard code for now
|
||||||
...targetData,
|
...targetData,
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
|
@ -15,12 +15,12 @@ const updateTargetParamsSchema = z.object({
|
||||||
|
|
||||||
const updateTargetBodySchema = z
|
const updateTargetBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
// ip: z.string().ip().optional(), // for now we cant update the ip; you will have to delete
|
ip: z.string().ip().optional(), // for now we cant update the ip; you will have to delete
|
||||||
method: z.string().min(1).max(10).optional(),
|
method: z.string().min(1).max(10).optional(),
|
||||||
port: z.number().int().min(1).max(65535).optional(),
|
port: z.number().int().min(1).max(65535).optional(),
|
||||||
protocol: z.string().optional(),
|
|
||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
|
.strict()
|
||||||
.refine((data) => Object.keys(data).length > 0, {
|
.refine((data) => Object.keys(data).length > 0, {
|
||||||
message: "At least one field must be provided for update",
|
message: "At least one field must be provided for update",
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { AxiosResponse } from "axios";
|
||||||
import { ListTargetsResponse } from "@server/routers/target/listTargets";
|
import { ListTargetsResponse } from "@server/routers/target/listTargets";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { set, z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
@ -27,7 +27,7 @@ import {
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@app/components/ui/form";
|
} from "@app/components/ui/form";
|
||||||
import { CreateTargetResponse, updateTarget } from "@server/routers/target";
|
import { CreateTargetResponse } from "@server/routers/target";
|
||||||
import {
|
import {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
getFilteredRowModel,
|
getFilteredRowModel,
|
||||||
|
@ -51,7 +51,6 @@ import { useResourceContext } from "@app/hooks/useResourceContext";
|
||||||
import { ArrayElement } from "@server/types/ArrayElement";
|
import { ArrayElement } from "@server/types/ArrayElement";
|
||||||
import { Dot } from "lucide-react";
|
import { Dot } from "lucide-react";
|
||||||
import { formatAxiosError } from "@app/lib/utils";
|
import { formatAxiosError } from "@app/lib/utils";
|
||||||
import { escape } from "querystring";
|
|
||||||
|
|
||||||
const addTargetSchema = z.object({
|
const addTargetSchema = z.object({
|
||||||
ip: z.string().ip(),
|
ip: z.string().ip(),
|
||||||
|
@ -62,15 +61,18 @@ const addTargetSchema = z.object({
|
||||||
message: "Port must be a number",
|
message: "Port must be a number",
|
||||||
})
|
})
|
||||||
.transform((val) => Number(val)),
|
.transform((val) => Number(val)),
|
||||||
protocol: z.string(),
|
// protocol: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type AddTargetFormValues = z.infer<typeof addTargetSchema>;
|
type AddTargetFormValues = z.infer<typeof addTargetSchema>;
|
||||||
|
|
||||||
type LocalTarget = ArrayElement<ListTargetsResponse["targets"]> & {
|
type LocalTarget = Omit<
|
||||||
new?: boolean;
|
ArrayElement<ListTargetsResponse["targets"]> & {
|
||||||
updated?: boolean;
|
new?: boolean;
|
||||||
};
|
updated?: boolean;
|
||||||
|
},
|
||||||
|
"protocol"
|
||||||
|
>;
|
||||||
|
|
||||||
export default function ReverseProxyTargets(props: {
|
export default function ReverseProxyTargets(props: {
|
||||||
params: Promise<{ resourceId: number }>;
|
params: Promise<{ resourceId: number }>;
|
||||||
|
@ -84,13 +86,15 @@ export default function ReverseProxyTargets(props: {
|
||||||
const [targetsToRemove, setTargetsToRemove] = useState<number[]>([]);
|
const [targetsToRemove, setTargetsToRemove] = useState<number[]>([]);
|
||||||
const [sslEnabled, setSslEnabled] = useState(resource.ssl);
|
const [sslEnabled, setSslEnabled] = useState(resource.ssl);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const addTargetForm = useForm({
|
const addTargetForm = useForm({
|
||||||
resolver: zodResolver(addTargetSchema),
|
resolver: zodResolver(addTargetSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
ip: "",
|
ip: "",
|
||||||
method: "http",
|
method: "http",
|
||||||
port: "80",
|
port: "80",
|
||||||
protocol: "TCP",
|
// protocol: "TCP",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -153,109 +157,72 @@ export default function ReverseProxyTargets(props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveAll() {
|
async function saveAll() {
|
||||||
const res = await api
|
try {
|
||||||
.post(`/resource/${params.resourceId}`, { ssl: sslEnabled })
|
setLoading(true);
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
const res = await api.post(`/resource/${params.resourceId}`, {
|
||||||
toast({
|
ssl: sslEnabled,
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to update resource",
|
|
||||||
description: formatAxiosError(
|
|
||||||
err,
|
|
||||||
"Failed to update resource"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
updateResource({ ssl: sslEnabled });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const target of targets) {
|
updateResource({ ssl: sslEnabled });
|
||||||
const data = {
|
|
||||||
ip: target.ip,
|
|
||||||
port: target.port,
|
|
||||||
method: target.method,
|
|
||||||
protocol: target.protocol,
|
|
||||||
enabled: target.enabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (target.new) {
|
for (const target of targets) {
|
||||||
await api
|
const data = {
|
||||||
.put<AxiosResponse<CreateTargetResponse>>(
|
ip: target.ip,
|
||||||
`/resource/${params.resourceId}/target`,
|
port: target.port,
|
||||||
|
// protocol: target.protocol,
|
||||||
|
method: target.method,
|
||||||
|
enabled: target.enabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (target.new) {
|
||||||
|
const res = await api.put<
|
||||||
|
AxiosResponse<CreateTargetResponse>
|
||||||
|
>(`/resource/${params.resourceId}/target`, data);
|
||||||
|
} else if (target.updated) {
|
||||||
|
const res = await api.post(
|
||||||
|
`/target/${target.targetId}`,
|
||||||
data
|
data
|
||||||
)
|
|
||||||
.then((res) => {
|
|
||||||
setTargets(
|
|
||||||
targets.map((t) => {
|
|
||||||
if (
|
|
||||||
t.new &&
|
|
||||||
t.targetId === res.data.data.targetId
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
...t,
|
|
||||||
new: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to add target",
|
|
||||||
description: formatAxiosError(
|
|
||||||
err,
|
|
||||||
"Failed to add target"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else if (target.updated) {
|
|
||||||
const res = await api
|
|
||||||
.post(`/target/${target.targetId}`, data)
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to update target",
|
|
||||||
description: formatAxiosError(
|
|
||||||
err,
|
|
||||||
"Failed to update target"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const targetId of targetsToRemove) {
|
|
||||||
await api
|
|
||||||
.delete(`/target/${targetId}`)
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to remove target",
|
|
||||||
description: formatAxiosError(
|
|
||||||
err,
|
|
||||||
"Failed to remove target"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
setTargets(
|
|
||||||
targets.filter((target) => target.targetId !== targetId)
|
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
setTargets([
|
||||||
|
...targets.map((t) => {
|
||||||
|
return {
|
||||||
|
...t,
|
||||||
|
new: false,
|
||||||
|
updated: false,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const targetId of targetsToRemove) {
|
||||||
|
await api.delete(`/target/${targetId}`);
|
||||||
|
setTargets(
|
||||||
|
targets.filter((target) => target.targetId !== targetId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: "Resource updated",
|
||||||
|
description: "Resource and targets updated successfully",
|
||||||
|
});
|
||||||
|
|
||||||
|
setTargetsToRemove([]);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Operation failed",
|
||||||
|
description: formatAxiosError(
|
||||||
|
err,
|
||||||
|
"An error occurred during the save operation"
|
||||||
|
),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
setLoading(false);
|
||||||
title: "Resource updated",
|
|
||||||
description: "Resource and targets updated successfully",
|
|
||||||
});
|
|
||||||
|
|
||||||
setTargetsToRemove([]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns: ColumnDef<LocalTarget>[] = [
|
const columns: ColumnDef<LocalTarget>[] = [
|
||||||
|
@ -306,24 +273,24 @@ export default function ReverseProxyTargets(props: {
|
||||||
</Select>
|
</Select>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
accessorKey: "protocol",
|
// accessorKey: "protocol",
|
||||||
header: "Protocol",
|
// header: "Protocol",
|
||||||
cell: ({ row }) => (
|
// cell: ({ row }) => (
|
||||||
<Select
|
// <Select
|
||||||
defaultValue={row.original.protocol!}
|
// defaultValue={row.original.protocol!}
|
||||||
onValueChange={(value) =>
|
// onValueChange={(value) =>
|
||||||
updateTarget(row.original.targetId, { protocol: value })
|
// updateTarget(row.original.targetId, { protocol: value })
|
||||||
}
|
// }
|
||||||
>
|
// >
|
||||||
<SelectTrigger>{row.original.protocol}</SelectTrigger>
|
// <SelectTrigger>{row.original.protocol}</SelectTrigger>
|
||||||
<SelectContent>
|
// <SelectContent>
|
||||||
<SelectItem value="TCP">TCP</SelectItem>
|
// <SelectItem value="TCP">TCP</SelectItem>
|
||||||
<SelectItem value="UDP">UDP</SelectItem>
|
// <SelectItem value="UDP">UDP</SelectItem>
|
||||||
</SelectContent>
|
// </SelectContent>
|
||||||
</Select>
|
// </Select>
|
||||||
),
|
// ),
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
accessorKey: "enabled",
|
accessorKey: "enabled",
|
||||||
header: "Enabled",
|
header: "Enabled",
|
||||||
|
@ -341,7 +308,14 @@ export default function ReverseProxyTargets(props: {
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-end space-x-2">
|
<div className="flex items-center justify-end space-x-2">
|
||||||
{row.original.new && <Dot />}
|
<Dot
|
||||||
|
className={
|
||||||
|
row.original.new || row.original.updated
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => removeTarget(row.original.targetId)}
|
onClick={() => removeTarget(row.original.targetId)}
|
||||||
|
@ -475,7 +449,7 @@ export default function ReverseProxyTargets(props: {
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
{/* <FormField
|
||||||
control={addTargetForm.control}
|
control={addTargetForm.control}
|
||||||
name="protocol"
|
name="protocol"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
|
@ -511,7 +485,7 @@ export default function ReverseProxyTargets(props: {
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
<Button type="submit" variant="gray">
|
<Button type="submit" variant="gray">
|
||||||
Add Target
|
Add Target
|
||||||
|
@ -569,7 +543,9 @@ export default function ReverseProxyTargets(props: {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<Button onClick={saveAll}>Save Changes</Button>
|
<Button onClick={saveAll} loading={loading} disabled={loading}>
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -61,7 +61,7 @@ export default function GeneralForm() {
|
||||||
resolver: zodResolver(GeneralFormSchema),
|
resolver: zodResolver(GeneralFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: resource.name,
|
name: resource.name,
|
||||||
siteId: resource.siteId!,
|
// siteId: resource.siteId!,
|
||||||
},
|
},
|
||||||
mode: "onChange",
|
mode: "onChange",
|
||||||
});
|
});
|
||||||
|
@ -84,7 +84,7 @@ export default function GeneralForm() {
|
||||||
`resource/${resource?.resourceId}`,
|
`resource/${resource?.resourceId}`,
|
||||||
{
|
{
|
||||||
name: data.name,
|
name: data.name,
|
||||||
siteId: data.siteId,
|
// siteId: data.siteId,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
@ -137,7 +137,7 @@ export default function GeneralForm() {
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
{/* <FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="siteId"
|
name="siteId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
|
@ -213,7 +213,7 @@ export default function GeneralForm() {
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/> */}
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={saveLoading}
|
loading={saveLoading}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue