"use client"; import { useEffect, useState } from "react"; import { Button } from "@app/components/ui/button"; import { Input } from "@app/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@app/components/ui/select"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@app/components/ui/form"; import { Credenza, CredenzaBody, CredenzaClose, CredenzaContent, CredenzaDescription, CredenzaFooter, CredenzaHeader, CredenzaTitle } from "@app/components/Credenza"; import { toast } from "@app/hooks/useToast"; import { useTranslations } from "next-intl"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { Separator } from "@app/components/ui/separator"; import { ListRolesResponse } from "@server/routers/role"; import { ListUsersResponse } from "@server/routers/user"; import { ListSiteResourceRolesResponse } from "@server/routers/siteResource/listSiteResourceRoles"; import { ListSiteResourceUsersResponse } from "@server/routers/siteResource/listSiteResourceUsers"; import { Tag, TagInput } from "@app/components/tags/tag-input"; import { AxiosResponse } from "axios"; import { UserType } from "@server/types/UserTypes"; type InternalResourceData = { id: number; name: string; orgId: string; siteName: string; protocol: string; proxyPort: number | null; siteId: number; destinationIp?: string; destinationPort?: number; }; type EditInternalResourceDialogProps = { open: boolean; setOpen: (val: boolean) => void; resource: InternalResourceData; orgId: string; onSuccess?: () => void; }; export default function EditInternalResourceDialog({ open, setOpen, resource, orgId, onSuccess }: EditInternalResourceDialogProps) { const t = useTranslations(); const api = createApiClient(useEnvContext()); const [isSubmitting, setIsSubmitting] = useState(false); const formSchema = z.object({ name: z.string().min(1, t("editInternalResourceDialogNameRequired")).max(255, t("editInternalResourceDialogNameMaxLength")), protocol: z.enum(["tcp", "udp"]), proxyPort: z.number().int().positive().min(1, t("editInternalResourceDialogProxyPortMin")).max(65535, t("editInternalResourceDialogProxyPortMax")), destinationIp: z.string(), destinationPort: z.number().int().positive().min(1, t("editInternalResourceDialogDestinationPortMin")).max(65535, t("editInternalResourceDialogDestinationPortMax")), roles: z.array( z.object({ id: z.string(), text: z.string() }) ).optional(), users: z.array( z.object({ id: z.string(), text: z.string() }) ).optional() }); type FormData = z.infer; const [allRoles, setAllRoles] = useState<{ id: string; text: string }[]>([]); const [allUsers, setAllUsers] = useState<{ id: string; text: string }[]>([]); const [activeRolesTagIndex, setActiveRolesTagIndex] = useState(null); const [activeUsersTagIndex, setActiveUsersTagIndex] = useState(null); const [loadingRolesUsers, setLoadingRolesUsers] = useState(false); const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { name: resource.name, protocol: resource.protocol as "tcp" | "udp", proxyPort: resource.proxyPort || undefined, destinationIp: resource.destinationIp || "", destinationPort: resource.destinationPort || undefined, roles: [], users: [] } }); const fetchRolesAndUsers = async () => { setLoadingRolesUsers(true); try { const [ rolesResponse, resourceRolesResponse, usersResponse, resourceUsersResponse ] = await Promise.all([ api.get>(`/org/${orgId}/roles`), api.get>( `/site-resource/${resource.id}/roles` ), api.get>(`/org/${orgId}/users`), api.get>( `/site-resource/${resource.id}/users` ) ]); setAllRoles( rolesResponse.data.data.roles .map((role) => ({ id: role.roleId.toString(), text: role.name })) .filter((role) => role.text !== "Admin") ); form.setValue( "roles", resourceRolesResponse.data.data.roles .map((i) => ({ id: i.roleId.toString(), text: i.name })) .filter((role) => role.text !== "Admin") ); setAllUsers( usersResponse.data.data.users.map((user) => ({ id: user.id.toString(), text: `${user.email || user.username}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}` })) ); form.setValue( "users", resourceUsersResponse.data.data.users.map((i) => ({ id: i.userId.toString(), text: `${i.email || i.username}${i.type !== UserType.Internal ? ` (${i.idpName})` : ""}` })) ); } catch (error) { console.error("Error fetching roles and users:", error); } finally { setLoadingRolesUsers(false); } }; useEffect(() => { if (open) { form.reset({ name: resource.name, protocol: resource.protocol as "tcp" | "udp", proxyPort: resource.proxyPort || undefined, destinationIp: resource.destinationIp || "", destinationPort: resource.destinationPort || undefined, roles: [], users: [] }); fetchRolesAndUsers(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, resource]); const handleSubmit = async (data: FormData) => { setIsSubmitting(true); try { // Update the site resource await api.post(`/org/${orgId}/site/${resource.siteId}/resource/${resource.id}`, { name: data.name, protocol: data.protocol, proxyPort: data.proxyPort, destinationIp: data.destinationIp, destinationPort: data.destinationPort }); // Update roles and users await Promise.all([ api.post(`/site-resource/${resource.id}/roles`, { roleIds: (data.roles || []).map((r) => parseInt(r.id)) }), api.post(`/site-resource/${resource.id}/users`, { userIds: (data.users || []).map((u) => u.id) }) ]); toast({ title: t("editInternalResourceDialogSuccess"), description: t("editInternalResourceDialogInternalResourceUpdatedSuccessfully"), variant: "default" }); onSuccess?.(); setOpen(false); } catch (error) { console.error("Error updating internal resource:", error); toast({ title: t("editInternalResourceDialogError"), description: formatAxiosError(error, t("editInternalResourceDialogFailedToUpdateInternalResource")), variant: "destructive" }); } finally { setIsSubmitting(false); } }; return ( {t("editInternalResourceDialogEditClientResource")} {t("editInternalResourceDialogUpdateResourceProperties", { resourceName: resource.name })}
{/* Resource Properties Form */}

{t("editInternalResourceDialogResourceProperties")}

( {t("editInternalResourceDialogName")} )} />
( {t("editInternalResourceDialogProtocol")} )} /> ( {t("editInternalResourceDialogSitePort")} field.onChange(parseInt(e.target.value) || 0)} /> )} />
{/* Target Configuration Form */}

{t("editInternalResourceDialogTargetConfiguration")}

( {t("targetAddr")} )} /> ( {t("targetPort")} field.onChange(parseInt(e.target.value) || 0)} /> )} />
{/* Access Control Section */}

{t("resourceUsersRoles")}

{loadingRolesUsers ? (
{t("loading")}
) : (
( {t("roles")} { form.setValue( "roles", newRoles as [Tag, ...Tag[]] ); }} enableAutocomplete={true} autocompleteOptions={allRoles} allowDuplicates={false} restrictTagsToAutocompleteOptions={true} sortTags={true} /> {t("resourceRoleDescription")} )} /> ( {t("users")} { form.setValue( "users", newUsers as [Tag, ...Tag[]] ); }} enableAutocomplete={true} autocompleteOptions={allUsers} allowDuplicates={false} restrictTagsToAutocompleteOptions={true} sortTags={true} /> )} />
)}
); }