diff --git a/server/lib/config.ts b/server/lib/config.ts index 7d8d9c8b..61ca3d66 100644 --- a/server/lib/config.ts +++ b/server/lib/config.ts @@ -184,7 +184,8 @@ const configSchema = z.object({ disable_signup_without_invite: z.boolean().optional(), disable_user_create_org: z.boolean().optional(), allow_raw_resources: z.boolean().optional(), - allow_base_domain_resources: z.boolean().optional() + allow_base_domain_resources: z.boolean().optional(), + allow_local_sites: z.boolean().optional() }) .optional() }); diff --git a/src/app/[orgId]/settings/access/roles/CreateRoleForm.tsx b/src/app/[orgId]/settings/access/roles/CreateRoleForm.tsx index d95f3e20..2312d67a 100644 --- a/src/app/[orgId]/settings/access/roles/CreateRoleForm.tsx +++ b/src/app/[orgId]/settings/access/roles/CreateRoleForm.tsx @@ -7,7 +7,7 @@ import { FormField, FormItem, FormLabel, - FormMessage, + FormMessage } from "@app/components/ui/form"; import { Input } from "@app/components/ui/input"; import { toast } from "@app/hooks/useToast"; @@ -24,11 +24,11 @@ import { CredenzaDescription, CredenzaFooter, CredenzaHeader, - CredenzaTitle, + CredenzaTitle } from "@app/components/Credenza"; import { useOrgContext } from "@app/hooks/useOrgContext"; import { CreateRoleBody, CreateRoleResponse } from "@server/routers/role"; -import { formatAxiosError } from "@app/lib/api";; +import { formatAxiosError } from "@app/lib/api"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; @@ -40,13 +40,13 @@ type CreateRoleFormProps = { const formSchema = z.object({ name: z.string({ message: "Name is required" }).max(32), - description: z.string().max(255).optional(), + description: z.string().max(255).optional() }); export default function CreateRoleForm({ open, setOpen, - afterCreate, + afterCreate }: CreateRoleFormProps) { const { org } = useOrgContext(); @@ -58,8 +58,8 @@ export default function CreateRoleForm({ resolver: zodResolver(formSchema), defaultValues: { name: "", - description: "", - }, + description: "" + } }); async function onSubmit(values: z.infer) { @@ -70,7 +70,7 @@ export default function CreateRoleForm({ `/org/${org?.org.orgId}/role`, { name: values.name, - description: values.description, + description: values.description } as CreateRoleBody ) .catch((e) => { @@ -80,7 +80,7 @@ export default function CreateRoleForm({ description: formatAxiosError( e, "An error occurred while creating the role." - ), + ) }); }); @@ -88,7 +88,7 @@ export default function CreateRoleForm({ toast({ variant: "default", title: "Role created", - description: "The role has been successfully created.", + description: "The role has been successfully created." }); if (open) { @@ -135,9 +135,7 @@ export default function CreateRoleForm({ Role Name - + @@ -150,9 +148,7 @@ export default function CreateRoleForm({ Description - + @@ -162,6 +158,9 @@ export default function CreateRoleForm({ + + + - - - diff --git a/src/app/[orgId]/settings/access/roles/DeleteRoleForm.tsx b/src/app/[orgId]/settings/access/roles/DeleteRoleForm.tsx index 6bd41df8..80d97267 100644 --- a/src/app/[orgId]/settings/access/roles/DeleteRoleForm.tsx +++ b/src/app/[orgId]/settings/access/roles/DeleteRoleForm.tsx @@ -7,7 +7,7 @@ import { FormField, FormItem, FormLabel, - FormMessage, + FormMessage } from "@app/components/ui/form"; import { toast } from "@app/hooks/useToast"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -23,7 +23,7 @@ import { CredenzaDescription, CredenzaFooter, CredenzaHeader, - CredenzaTitle, + CredenzaTitle } from "@app/components/Credenza"; import { useOrgContext } from "@app/hooks/useOrgContext"; import { ListRolesResponse } from "@server/routers/role"; @@ -32,10 +32,10 @@ import { SelectContent, SelectItem, SelectTrigger, - SelectValue, + SelectValue } from "@app/components/ui/select"; import { RoleRow } from "./RolesTable"; -import { formatAxiosError } from "@app/lib/api";; +import { formatAxiosError } from "@app/lib/api"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; @@ -47,14 +47,14 @@ type CreateRoleFormProps = { }; const formSchema = z.object({ - newRoleId: z.string({ message: "New role is required" }), + newRoleId: z.string({ message: "New role is required" }) }); export default function DeleteRoleForm({ open, roleToDelete, setOpen, - afterDelete, + afterDelete }: CreateRoleFormProps) { const { org } = useOrgContext(); @@ -66,9 +66,9 @@ export default function DeleteRoleForm({ useEffect(() => { async function fetchRoles() { const res = await api - .get>( - `/org/${org?.org.orgId}/roles` - ) + .get< + AxiosResponse + >(`/org/${org?.org.orgId}/roles`) .catch((e) => { console.error(e); toast({ @@ -77,7 +77,7 @@ export default function DeleteRoleForm({ description: formatAxiosError( e, "An error occurred while fetching the roles" - ), + ) }); }); @@ -96,8 +96,8 @@ export default function DeleteRoleForm({ const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - newRoleId: "", - }, + newRoleId: "" + } }); async function onSubmit(values: z.infer) { @@ -106,8 +106,8 @@ export default function DeleteRoleForm({ const res = await api .delete(`/role/${roleToDelete.roleId}`, { data: { - roleId: values.newRoleId, - }, + roleId: values.newRoleId + } }) .catch((e) => { toast({ @@ -116,7 +116,7 @@ export default function DeleteRoleForm({ description: formatAxiosError( e, "An error occurred while removing the role." - ), + ) }); }); @@ -124,7 +124,7 @@ export default function DeleteRoleForm({ toast({ variant: "default", title: "Role removed", - description: "The role has been successfully removed.", + description: "The role has been successfully removed." }); if (open) { @@ -214,6 +214,9 @@ export default function DeleteRoleForm({ + + + - - - diff --git a/src/app/[orgId]/settings/access/users/InviteUserForm.tsx b/src/app/[orgId]/settings/access/users/InviteUserForm.tsx index c629c0bc..0285123a 100644 --- a/src/app/[orgId]/settings/access/users/InviteUserForm.tsx +++ b/src/app/[orgId]/settings/access/users/InviteUserForm.tsx @@ -37,7 +37,7 @@ import { } from "@app/components/Credenza"; import { useOrgContext } from "@app/hooks/useOrgContext"; import { ListRolesResponse } from "@server/routers/role"; -import { formatAxiosError } from "@app/lib/api";; +import { formatAxiosError } from "@app/lib/api"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { Checkbox } from "@app/components/ui/checkbox"; @@ -194,9 +194,7 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) { Email - + @@ -340,6 +338,9 @@ export default function InviteUserForm({ open, setOpen }: InviteUserFormProps) { + + + - - - diff --git a/src/app/[orgId]/settings/access/users/UsersTable.tsx b/src/app/[orgId]/settings/access/users/UsersTable.tsx index 7c11c06b..29529d66 100644 --- a/src/app/[orgId]/settings/access/users/UsersTable.tsx +++ b/src/app/[orgId]/settings/access/users/UsersTable.tsx @@ -185,7 +185,7 @@ export default function UsersTable({ users: u }: UsersTableProps) { - diff --git a/src/app/[orgId]/settings/access/users/[userId]/layout.tsx b/src/app/[orgId]/settings/access/users/[userId]/layout.tsx index 11ae20ac..135c47a3 100644 --- a/src/app/[orgId]/settings/access/users/[userId]/layout.tsx +++ b/src/app/[orgId]/settings/access/users/[userId]/layout.tsx @@ -64,7 +64,7 @@ export default async function UserLayoutProps(props: UserLayoutProps) { -
+

User {user?.email}

diff --git a/src/app/[orgId]/settings/layout.tsx b/src/app/[orgId]/settings/layout.tsx index b0b561a2..b9912106 100644 --- a/src/app/[orgId]/settings/layout.tsx +++ b/src/app/[orgId]/settings/layout.tsx @@ -1,6 +1,13 @@ import { Metadata } from "next"; import { TopbarNav } from "@app/components/TopbarNav"; -import { Cog, Combine, Link, Settings, Users, Waypoints } from "lucide-react"; +import { + Cog, + Combine, + LinkIcon, + Settings, + Users, + Waypoints +} from "lucide-react"; import { Header } from "@app/components/Header"; import { verifySession } from "@app/lib/auth/verifySession"; import { redirect } from "next/navigation"; @@ -11,6 +18,14 @@ import { authCookieHeader } from "@app/lib/api/cookies"; import { cache } from "react"; import { GetOrgUserResponse } from "@server/routers/user"; import UserProvider from "@app/providers/UserProvider"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator +} from "@app/components/ui/breadcrumb"; +import Link from "next/link"; export const dynamic = "force-dynamic"; @@ -38,7 +53,7 @@ const topNavItems = [ { title: "Shareable Links", href: "/{orgId}/settings/share-links", - icon: + icon: }, { title: "General", @@ -95,19 +110,23 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { return ( <> -
-
-
- -
- +
+
+
+
+ +
+ +
+
-
-
- {children} +
+
+ {children} +
); diff --git a/src/app/[orgId]/settings/resources/CreateResourceForm.tsx b/src/app/[orgId]/settings/resources/CreateResourceForm.tsx index ea8542f6..cbf08d07 100644 --- a/src/app/[orgId]/settings/resources/CreateResourceForm.tsx +++ b/src/app/[orgId]/settings/resources/CreateResourceForm.tsx @@ -66,6 +66,7 @@ import CopyTextBox from "@app/components/CopyTextBox"; import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group"; import { Label } from "@app/components/ui/label"; import { ListDomainsResponse } from "@server/routers/domain"; +import LoaderPlaceholder from "@app/components/PlaceHolderLoader"; const createResourceFormSchema = z .object({ @@ -140,6 +141,7 @@ export default function CreateResourceForm({ const [domainType, setDomainType] = useState<"subdomain" | "basedomain">( "subdomain" ); + const [loadingPage, setLoadingPage] = useState(true); const form = useForm({ resolver: zodResolver(createResourceFormSchema), @@ -215,8 +217,16 @@ export default function CreateResourceForm({ } }; - fetchSites(); - fetchDomains(); + const load = async () => { + setLoadingPage(true); + + await fetchSites(); + await fetchDomains(); + + setLoadingPage(false); + }; + + load(); }, [open]); async function onSubmit(data: CreateResourceFormValues) { @@ -282,236 +292,482 @@ export default function CreateResourceForm({ - {!showSnippets && ( -
- - {!env.flags.allowRawResources || ( - ( - -
- - HTTP Resource - - - Toggle if this is an - HTTP resource or a - raw TCP/UDP - resource. - -
- - - -
+ {loadingPage ? ( + + ) : ( +
+ {!showSnippets && ( + + - )} - - ( - - Name - - - - - - This is display name for the - resource. - - - )} - /> - - {form.watch("http") && - env.flags.allowBaseDomainResources && ( -
- { - setDomainType( - val as any - ); - form.setValue( - "isBaseDomain", - val === "basedomain" - ); - }} - > -
- - -
-
- - -
-
-
- )} - - {form.watch("http") && ( - <> - {domainType === "subdomain" ? ( -
- {!env.flags - .allowBaseDomainResources && ( - - Subdomain - - )} -
-
- ( - - - - )} - /> -
-
- ( - - - - - )} - /> -
-
-
- ) : ( + className="space-y-4" + id="create-resource-form" + > + {!env.flags.allowRawResources || ( ( - - - + +
+ + HTTP + Resource + + + Toggle if + this is an + HTTP + resource or + a raw + TCP/UDP + resource. + +
+ + +
)} /> )} - - )} - {!form.watch("http") && ( + {!form.watch("http") && ( + + + Learn how to configure + TCP/UDP resources + + + + )} + + ( + + + Name + + + + + + + )} + /> + + {form.watch("http") && + env.flags + .allowBaseDomainResources && ( + ( + + + Domain Type + + + + + )} + /> + )} + + {form.watch("http") && ( + <> + {domainType === + "subdomain" ? ( +
+ + Subdomain + +
+
+ ( + + + + + + + )} + /> +
+
+ ( + + + + + )} + /> +
+
+
+ ) : ( + ( + + + Base + Domain + + + + + )} + /> + )} + + )} + + {!form.watch("http") && ( + <> + ( + + + Protocol + + + + + )} + /> + ( + + + Port Number + + + + field.onChange( + e + .target + .value + ? parseInt( + e + .target + .value + ) + : null + ) + } + /> + + + + The external + port number + to proxy + requests. + + + )} + /> + + )} + + ( + + + Site + + + + + + + + + + + + + No + site + found. + + + {sites.map( + ( + site + ) => ( + { + form.setValue( + "siteId", + site.siteId + ); + }} + > + + { + site.name + } + + ) + )} + + + + + + + + This site will + provide connectivity + to the resource. + + + )} + /> + + + )} + + {showSnippets && ( +
+
+
+

+ Traefik: Add Entrypoints +

+ +
+
+ +
+
+

+ Gerbil: Expose Ports in + Docker Compose +

+ +
+
+ - Learn how to configure TCP/UDP - resources + Make sure to follow the full + guide - )} - - {!form.watch("http") && ( - <> - ( - - - Protocol - - - - - The protocol to use - for the resource. - - - )} - /> - ( - - - Port Number - - - - field.onChange( - e.target - .value - ? parseInt( - e - .target - .value - ) - : null - ) - } - /> - - - - The port number to - proxy requests to - (required for - non-HTTP resources). - - - )} - /> - - )} - - ( - - Site - - - - - - - - - - - - No site - found. - - - {sites.map( - ( - site - ) => ( - { - form.setValue( - "siteId", - site.siteId - ); - }} - > - - { - site.name - } - - ) - )} - - - - - - - - This site will provide - connectivity to the - resource. - - - )} - /> - - - )} - - {showSnippets && ( -
-
-
- 1
-
-

- Traefik: Add Entrypoints -

- -
-
- -
-
- 2 -
-
-

- Gerbil: Expose Ports in Docker - Compose -

- -
-
- - - - Make sure to follow the full guide - - - + )}
)} + + + {!showSnippets && ( - diff --git a/src/app/[orgId]/settings/resources/ResourcesTable.tsx b/src/app/[orgId]/settings/resources/ResourcesTable.tsx index 848838b3..6ff9e730 100644 --- a/src/app/[orgId]/settings/resources/ResourcesTable.tsx +++ b/src/app/[orgId]/settings/resources/ResourcesTable.tsx @@ -233,7 +233,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) { - diff --git a/src/app/[orgId]/settings/resources/[resourceId]/authentication/SetResourcePasswordForm.tsx b/src/app/[orgId]/settings/resources/[resourceId]/authentication/SetResourcePasswordForm.tsx index 35eb29a3..f3ab705c 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/authentication/SetResourcePasswordForm.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/authentication/SetResourcePasswordForm.tsx @@ -8,7 +8,7 @@ import { FormField, FormItem, FormLabel, - FormMessage, + FormMessage } from "@app/components/ui/form"; import { Input } from "@app/components/ui/input"; import { toast } from "@app/hooks/useToast"; @@ -24,22 +24,22 @@ import { CredenzaDescription, CredenzaFooter, CredenzaHeader, - CredenzaTitle, + CredenzaTitle } from "@app/components/Credenza"; -import { formatAxiosError } from "@app/lib/api";; +import { formatAxiosError } from "@app/lib/api"; import { AxiosResponse } from "axios"; import { Resource } from "@server/db/schema"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; const setPasswordFormSchema = z.object({ - password: z.string().min(4).max(100), + password: z.string().min(4).max(100) }); type SetPasswordFormValues = z.infer; const defaultValues: Partial = { - password: "", + password: "" }; type SetPasswordFormProps = { @@ -53,7 +53,7 @@ export default function SetResourcePasswordForm({ open, setOpen, resourceId, - onSetPassword, + onSetPassword }: SetPasswordFormProps) { const api = createApiClient(useEnvContext()); @@ -61,7 +61,7 @@ export default function SetResourcePasswordForm({ const form = useForm({ resolver: zodResolver(setPasswordFormSchema), - defaultValues, + defaultValues }); useEffect(() => { @@ -76,7 +76,7 @@ export default function SetResourcePasswordForm({ setLoading(true); api.post>(`/resource/${resourceId}/password`, { - password: data.password, + password: data.password }) .catch((e) => { toast({ @@ -85,14 +85,14 @@ export default function SetResourcePasswordForm({ description: formatAxiosError( e, "An error occurred while setting the resource password" - ), + ) }); }) .then(() => { toast({ title: "Resource password set", description: - "The resource password has been set successfully", + "The resource password has been set successfully" }); if (onSetPassword) { @@ -153,6 +153,9 @@ export default function SetResourcePasswordForm({ + + + - - - diff --git a/src/app/[orgId]/settings/resources/[resourceId]/authentication/SetResourcePincodeForm.tsx b/src/app/[orgId]/settings/resources/[resourceId]/authentication/SetResourcePincodeForm.tsx index 4a850b33..9d89d3bd 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/authentication/SetResourcePincodeForm.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/authentication/SetResourcePincodeForm.tsx @@ -8,7 +8,7 @@ import { FormField, FormItem, FormLabel, - FormMessage, + FormMessage } from "@app/components/ui/form"; import { Input } from "@app/components/ui/input"; import { toast } from "@app/hooks/useToast"; @@ -24,27 +24,27 @@ import { CredenzaDescription, CredenzaFooter, CredenzaHeader, - CredenzaTitle, + CredenzaTitle } from "@app/components/Credenza"; -import { formatAxiosError } from "@app/lib/api";; +import { formatAxiosError } from "@app/lib/api"; import { AxiosResponse } from "axios"; import { Resource } from "@server/db/schema"; import { InputOTP, InputOTPGroup, - InputOTPSlot, + InputOTPSlot } from "@app/components/ui/input-otp"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; const setPincodeFormSchema = z.object({ - pincode: z.string().length(6), + pincode: z.string().length(6) }); type SetPincodeFormValues = z.infer; const defaultValues: Partial = { - pincode: "", + pincode: "" }; type SetPincodeFormProps = { @@ -58,7 +58,7 @@ export default function SetResourcePincodeForm({ open, setOpen, resourceId, - onSetPincode, + onSetPincode }: SetPincodeFormProps) { const [loading, setLoading] = useState(false); @@ -66,7 +66,7 @@ export default function SetResourcePincodeForm({ const form = useForm({ resolver: zodResolver(setPincodeFormSchema), - defaultValues, + defaultValues }); useEffect(() => { @@ -81,7 +81,7 @@ export default function SetResourcePincodeForm({ setLoading(true); api.post>(`/resource/${resourceId}/pincode`, { - pincode: data.pincode, + pincode: data.pincode }) .catch((e) => { toast({ @@ -89,15 +89,15 @@ export default function SetResourcePincodeForm({ title: "Error setting resource PIN code", description: formatAxiosError( e, - "An error occurred while setting the resource PIN code", - ), + "An error occurred while setting the resource PIN code" + ) }); }) .then(() => { toast({ title: "Resource PIN code set", description: - "The resource pincode has been set successfully", + "The resource pincode has been set successfully" }); if (onSetPincode) { @@ -181,6 +181,9 @@ export default function SetResourcePincodeForm({ + + + - - - diff --git a/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx index fa0491f0..c50afc4d 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx @@ -38,7 +38,8 @@ import { SettingsSectionHeader, SettingsSectionDescription, SettingsSectionBody, - SettingsSectionFooter + SettingsSectionFooter, + SettingsSectionForm } from "@app/components/Settings"; import { SwitchInput } from "@app/components/SwitchInput"; import { InfoPopup } from "@app/components/ui/info-popup"; @@ -438,6 +439,7 @@ export default function ResourceAuthenticationPage() { setActiveRolesTagIndex } placeholder="Select a role" + size="sm" tags={ usersRolesForm.getValues() .roles @@ -466,14 +468,6 @@ export default function ResourceAuthenticationPage() { true } sortTags={true} - styleClasses={{ - tag: { - body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full" - }, - input: "text-base md:text-sm border-none bg-transparent text-inherit placeholder:text-inherit shadow-none", - inlineTagsContainer: - "bg-transparent p-2" - }} /> @@ -504,6 +498,7 @@ export default function ResourceAuthenticationPage() { usersRolesForm.getValues() .users } + size="sm" setTags={( newUsers ) => { @@ -528,14 +523,6 @@ export default function ResourceAuthenticationPage() { true } sortTags={true} - styleClasses={{ - tag: { - body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full" - }, - input: "text-base md:text-sm border-none bg-transparent text-inherit placeholder:text-inherit shadow-none", - inlineTagsContainer: - "bg-transparent p-2" - }} /> @@ -582,7 +569,7 @@ export default function ResourceAuthenticationPage() {
- diff --git a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx index 0eef41d6..15edd9c7 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx @@ -129,6 +129,7 @@ export default function GeneralForm() { ListDomainsResponse["domains"] >([]); + const [loadingPage, setLoadingPage] = useState(true); const [domainType, setDomainType] = useState<"subdomain" | "basedomain">( resource.isBaseDomain ? "basedomain" : "subdomain" ); @@ -184,8 +185,14 @@ export default function GeneralForm() { } }; - fetchDomains(); - fetchSites(); + const load = async () => { + await fetchDomains(); + await fetchSites(); + + setLoadingPage(false); + }; + + load(); }, []); async function onSubmit(data: GeneralFormValues) { @@ -263,391 +270,399 @@ export default function GeneralForm() { } return ( - - - - - General Settings - - - Configure the general settings for this resource - - + !loadingPage && ( + + + + + General Settings + + + Configure the general settings for this resource + + - - -
- - ( - - Name - - - - - - This is the display name of the - resource. - - - )} - /> - - {resource.http && ( - <> - {env.flags.allowBaseDomainResources && ( -
- { - setDomainType( - val as any - ); - form.setValue( - "isBaseDomain", - val === "basedomain" - ); - }} - > -
- - -
-
- - -
-
-
- )} - - {domainType === "subdomain" ? ( -
- {!env.flags - .allowBaseDomainResources && ( - - Subdomain - - )} -
-
- ( - - - - - - - )} - /> -
-
- ( - - - - - )} - /> -
-
-
- ) : ( - ( - - - - - )} - /> - )} - - )} - - {!resource.http && ( + + + + ( - - Port Number - + Name - - field.onChange( - e.target.value - ? parseInt( - e - .target - .value - ) - : null - ) - } - /> + - - This is the port that will - be used to access the - resource. - )} /> - )} - - - - - - - -
+ {resource.http && ( + <> + {env.flags + .allowBaseDomainResources && ( + ( + + + Domain Type + + + + + )} + /> + )} - - - - Transfer Resource - - - Transfer this resource to a different site - - - - - -
- - ( - - - Destination Site - - - - - - - - - - - - No sites found. - - - {sites.map( - (site) => ( - { - transferForm.setValue( - "siteId", - site.siteId - ); - setOpen( - false - ); - }} - > - { - site.name - } - + {domainType === "subdomain" ? ( +
+ + Subdomain + +
+
+ ( + + + + + + + )} + /> +
+
+ ( + + + + + )} + /> +
+
+
+ ) : ( + ( + + + Base Domain + + + + + )} + /> + )} +
+ )} - /> - - - - - - - - - + {!resource.http && ( + ( + + + Port Number + + + + field.onChange( + e.target + .value + ? parseInt( + e + .target + .value + ) + : null + ) + } + /> + + + + )} + /> + )} + + + + + + + + + + + + + + Transfer Resource + + + Transfer this resource to a different site + + + + + +
+ + ( + + + Destination Site + + + + + + + + + + + + No sites found. + + + {sites.map( + (site) => ( + { + transferForm.setValue( + "siteId", + site.siteId + ); + setOpen( + false + ); + }} + > + { + site.name + } + + + ) + )} + + + + + + + )} + /> + + +
+
+ + + + +
+ + ) ); } diff --git a/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx index 2a4d0e51..5ed7a1a5 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx @@ -94,7 +94,7 @@ enum RuleAction { enum RuleMatch { PATH = "Path", IP = "IP", - CIDR = "IP Range", + CIDR = "IP Range" } export default function ResourceRules(props: { @@ -623,7 +623,7 @@ export default function ResourceRules(props: { onSubmit={addRuleForm.handleSubmit(addRule)} className="space-y-4" > -
+
)} /> +
- diff --git a/src/app/[orgId]/settings/share-links/CreateShareLinkForm.tsx b/src/app/[orgId]/settings/share-links/CreateShareLinkForm.tsx index bd5778ef..e91be2f2 100644 --- a/src/app/[orgId]/settings/share-links/CreateShareLinkForm.tsx +++ b/src/app/[orgId]/settings/share-links/CreateShareLinkForm.tsx @@ -152,13 +152,15 @@ export default function CreateShareLinkForm({ if (res?.status === 200) { setResources( - res.data.data.resources.filter((r) => { - return r.http; - }).map((r) => ({ - resourceId: r.resourceId, - name: r.name, - resourceUrl: `${r.ssl ? "https://" : "http://"}${r.fullDomain}/` - })) + res.data.data.resources + .filter((r) => { + return r.http; + }) + .map((r) => ({ + resourceId: r.resourceId, + name: r.name, + resourceUrl: `${r.ssl ? "https://" : "http://"}${r.fullDomain}/` + })) ); } } @@ -274,7 +276,7 @@ export default function CreateShareLinkForm({ name="resourceId" render={({ field }) => ( - + Resource @@ -318,9 +320,7 @@ export default function CreateShareLinkForm({ r ) => ( ( - + - + @@ -383,66 +381,68 @@ export default function CreateShareLinkForm({ />
- -
- ( - - - - - )} - /> +
+ Expire In +
+ ( + + + + + )} + /> - ( - - - - - - - )} - /> + ( + + + + + + + )} + /> +
@@ -552,6 +552,9 @@ export default function CreateShareLinkForm({
+ + + - - - diff --git a/src/app/[orgId]/settings/share-links/ShareLinksTable.tsx b/src/app/[orgId]/settings/share-links/ShareLinksTable.tsx index 5d5d341f..2acc1399 100644 --- a/src/app/[orgId]/settings/share-links/ShareLinksTable.tsx +++ b/src/app/[orgId]/settings/share-links/ShareLinksTable.tsx @@ -273,7 +273,21 @@ export default function ShareLinksTable({ } return "Never"; } + }, + { + id: "delete", + cell: ({ row }) => ( +
+ +
+ ) } + ]; return ( diff --git a/src/app/[orgId]/settings/sites/CreateSiteForm.tsx b/src/app/[orgId]/settings/sites/CreateSiteForm.tsx index 0a4cca14..ad7697e8 100644 --- a/src/app/[orgId]/settings/sites/CreateSiteForm.tsx +++ b/src/app/[orgId]/settings/sites/CreateSiteForm.tsx @@ -41,6 +41,7 @@ import Link from "next/link"; import { ArrowUpRight, ChevronsUpDown, + Loader2, SquareArrowOutUpRight } from "lucide-react"; import { @@ -48,6 +49,7 @@ import { CollapsibleContent, CollapsibleTrigger } from "@app/components/ui/collapsible"; +import LoaderPlaceholder from "@app/components/PlaceHolderLoader"; const createSiteFormSchema = z.object({ name: z @@ -97,6 +99,8 @@ export default function CreateSiteForm({ const [siteDefaults, setSiteDefaults] = useState(null); + const [loadingPage, setLoadingPage] = useState(true); + const handleCheckboxChange = (checked: boolean) => { // setChecked?.(checked); setIsChecked(checked); @@ -121,27 +125,35 @@ export default function CreateSiteForm({ useEffect(() => { if (!open) return; - // reset all values - setLoading?.(false); - setIsLoading(false); - form.reset(); - setChecked?.(false); - setKeypair(null); - setSiteDefaults(null); + const load = async () => { + setLoadingPage(true); + // reset all values + setLoading?.(false); + setIsLoading(false); + form.reset(); + setChecked?.(false); + setKeypair(null); + setSiteDefaults(null); - const generatedKeypair = generateKeypair(); - setKeypair(generatedKeypair); + const generatedKeypair = generateKeypair(); + setKeypair(generatedKeypair); - api.get(`/org/${orgId}/pick-site-defaults`) - .catch((e) => { - // update the default value of the form to be local method - form.setValue("method", "local"); - }) - .then((res) => { - if (res && res.status === 200) { - setSiteDefaults(res.data.data); - } - }); + await api + .get(`/org/${orgId}/pick-site-defaults`) + .catch((e) => { + // update the default value of the form to be local method + form.setValue("method", "local"); + }) + .then((res) => { + if (res && res.status === 200) { + setSiteDefaults(res.data.data); + } + }); + + setLoadingPage(false); + }; + + load(); }, [open]); async function onSubmit(data: CreateSiteFormValues) { @@ -257,7 +269,9 @@ PersistentKeepalive = 5` const newtConfigDockerRun = `docker run -it fosrl/newt --id ${siteDefaults?.newtId} --secret ${siteDefaults?.newtSecret} --endpoint ${env.app.dashboardUrl}`; - return ( + return loadingPage ? ( + + ) : (
- This is the the display name for the - site. + This is the the display name for the site. )} diff --git a/src/app/[orgId]/settings/sites/CreateSiteModal.tsx b/src/app/[orgId]/settings/sites/CreateSiteModal.tsx index fd6ff914..1666000d 100644 --- a/src/app/[orgId]/settings/sites/CreateSiteModal.tsx +++ b/src/app/[orgId]/settings/sites/CreateSiteModal.tsx @@ -58,6 +58,9 @@ export default function CreateSiteFormModal({
+ + + - - - diff --git a/src/app/[orgId]/settings/sites/SitesTable.tsx b/src/app/[orgId]/settings/sites/SitesTable.tsx index d9d0ba03..9b56aaeb 100644 --- a/src/app/[orgId]/settings/sites/SitesTable.tsx +++ b/src/app/[orgId]/settings/sites/SitesTable.tsx @@ -268,7 +268,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) { - diff --git a/src/app/[orgId]/settings/sites/[niceId]/layout.tsx b/src/app/[orgId]/settings/sites/[niceId]/layout.tsx index 7b0fa46d..509bd294 100644 --- a/src/app/[orgId]/settings/sites/[niceId]/layout.tsx +++ b/src/app/[orgId]/settings/sites/[niceId]/layout.tsx @@ -68,7 +68,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { -
+
{children} diff --git a/src/app/globals.css b/src/app/globals.css index bcb47510..d7349dd7 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -22,7 +22,7 @@ --destructive: 0 84.2% 60.2%; --destructive-foreground: 60 9.1% 97.8%; --border: 20 5.9% 85%; - --input: 20 5.9% 85%; + --input: 20 5.9% 80%; --ring: 24.6 95% 53.1%; --radius: 0.75rem; --chart-1: 12 76% 61%; @@ -50,7 +50,7 @@ --destructive: 0 72.2% 50.6%; --destructive-foreground: 60 9.1% 97.8%; --border: 12 6.5% 25.0%; - --input: 12 6.5% 25.0%; + --input: 12 6.5% 30.0%; --ring: 20.5 90.2% 48.2%; --chart-1: 220 70% 50%; --chart-2: 160 60% 45%; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 02a9daba..a892ccef 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -37,11 +37,11 @@ export default async function RootLayout({ > {/* Main content */} -
{children}
+
{children}
{/* Footer */} -