mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-31 16:14:46 +02:00
refactor subdomain inputs
This commit is contained in:
parent
82f990eb8b
commit
e49fb646b0
8 changed files with 404 additions and 144 deletions
|
@ -33,16 +33,17 @@ const listDomainsSchema = z
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
async function queryDomains(orgId: string, limit: number, offset: number) {
|
async function queryDomains(orgId: string, limit: number, offset: number) {
|
||||||
return await db
|
const res = await db
|
||||||
.select({
|
.select({
|
||||||
domainId: domains.domainId,
|
domainId: domains.domainId,
|
||||||
baseDomain: domains.baseDomain
|
baseDomain: domains.baseDomain
|
||||||
})
|
})
|
||||||
.from(orgDomains)
|
.from(orgDomains)
|
||||||
.where(eq(orgDomains.orgId, orgId))
|
.where(eq(orgDomains.orgId, orgId))
|
||||||
.leftJoin(domains, eq(domains.domainId, orgDomains.domainId))
|
.innerJoin(domains, eq(domains.domainId, orgDomains.domainId))
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.offset(offset);
|
.offset(offset);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ListDomainsResponse = {
|
export type ListDomainsResponse = {
|
||||||
|
|
|
@ -31,7 +31,7 @@ const createResourceParamsSchema = z
|
||||||
const createHttpResourceSchema = z
|
const createHttpResourceSchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
subdomain: subdomainSchema.optional(),
|
subdomain: z.string().optional(),
|
||||||
isBaseDomain: z.boolean().optional(),
|
isBaseDomain: z.boolean().optional(),
|
||||||
siteId: z.number(),
|
siteId: z.number(),
|
||||||
http: z.boolean(),
|
http: z.boolean(),
|
||||||
|
@ -39,6 +39,15 @@ const createHttpResourceSchema = z
|
||||||
domainId: z.string()
|
domainId: z.string()
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
.refine(
|
||||||
|
(data) => {
|
||||||
|
if (data.subdomain) {
|
||||||
|
return subdomainSchema.safeParse(data.subdomain).success;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
{ message: "Invalid subdomain" }
|
||||||
|
)
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
if (!config.getRawConfig().flags?.allow_base_domain_resources) {
|
if (!config.getRawConfig().flags?.allow_base_domain_resources) {
|
||||||
|
@ -199,6 +208,8 @@ async function createHttpResource(
|
||||||
fullDomain = `${subdomain}.${domain.baseDomain}`;
|
fullDomain = `${subdomain}.${domain.baseDomain}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug(`Full domain: ${fullDomain}`);
|
||||||
|
|
||||||
// make sure the full domain is unique
|
// make sure the full domain is unique
|
||||||
const existingResource = await db
|
const existingResource = await db
|
||||||
.select()
|
.select()
|
||||||
|
@ -221,7 +232,7 @@ async function createHttpResource(
|
||||||
.insert(resources)
|
.insert(resources)
|
||||||
.values({
|
.values({
|
||||||
siteId,
|
siteId,
|
||||||
fullDomain: http ? fullDomain : null,
|
fullDomain,
|
||||||
orgId,
|
orgId,
|
||||||
name,
|
name,
|
||||||
subdomain,
|
subdomain,
|
||||||
|
|
|
@ -43,6 +43,15 @@ const updateHttpResourceBodySchema = z
|
||||||
.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"
|
||||||
})
|
})
|
||||||
|
.refine(
|
||||||
|
(data) => {
|
||||||
|
if (data.subdomain) {
|
||||||
|
return subdomainSchema.safeParse(data.subdomain).success;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
{ message: "Invalid subdomain" }
|
||||||
|
)
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
if (!config.getRawConfig().flags?.allow_base_domain_resources) {
|
if (!config.getRawConfig().flags?.allow_base_domain_resources) {
|
||||||
|
@ -206,7 +215,7 @@ async function updateHttpResource(
|
||||||
if (updateData.isBaseDomain) {
|
if (updateData.isBaseDomain) {
|
||||||
fullDomain = domain.baseDomain;
|
fullDomain = domain.baseDomain;
|
||||||
} else if (subdomain && domain) {
|
} else if (subdomain && domain) {
|
||||||
fullDomain = `${subdomain}.${domain}`;
|
fullDomain = `${subdomain}.${domain.baseDomain}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fullDomain) {
|
if (fullDomain) {
|
||||||
|
|
|
@ -69,7 +69,7 @@ export async function copyInConfig() {
|
||||||
if (resource.isBaseDomain) {
|
if (resource.isBaseDomain) {
|
||||||
fullDomain = domain.baseDomain;
|
fullDomain = domain.baseDomain;
|
||||||
} else {
|
} else {
|
||||||
fullDomain = `${resource.subdomain}.${domain}`;
|
fullDomain = `${resource.subdomain}.${domain.baseDomain}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await trx
|
await trx
|
||||||
|
|
|
@ -65,10 +65,12 @@ import { SquareArrowOutUpRight } from "lucide-react";
|
||||||
import CopyTextBox from "@app/components/CopyTextBox";
|
import CopyTextBox from "@app/components/CopyTextBox";
|
||||||
import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group";
|
||||||
import { Label } from "@app/components/ui/label";
|
import { Label } from "@app/components/ui/label";
|
||||||
|
import { ListDomainsResponse } from "@server/routers/domain";
|
||||||
|
|
||||||
const createResourceFormSchema = z
|
const createResourceFormSchema = z
|
||||||
.object({
|
.object({
|
||||||
subdomain: z.string().optional(),
|
subdomain: z.string().optional(),
|
||||||
|
domainId: z.string().min(1).optional(),
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
siteId: z.number(),
|
siteId: z.number(),
|
||||||
http: z.boolean(),
|
http: z.boolean(),
|
||||||
|
@ -129,7 +131,9 @@ export default function CreateResourceForm({
|
||||||
const { env } = useEnvContext();
|
const { env } = useEnvContext();
|
||||||
|
|
||||||
const [sites, setSites] = useState<ListSitesResponse["sites"]>([]);
|
const [sites, setSites] = useState<ListSitesResponse["sites"]>([]);
|
||||||
const [domainSuffix, setDomainSuffix] = useState<string>(org.org.domain);
|
const [baseDomains, setBaseDomains] = useState<
|
||||||
|
{ domainId: string; baseDomain: string }[]
|
||||||
|
>([]);
|
||||||
const [showSnippets, setShowSnippets] = useState(false);
|
const [showSnippets, setShowSnippets] = useState(false);
|
||||||
const [resourceId, setResourceId] = useState<number | null>(null);
|
const [resourceId, setResourceId] = useState<number | null>(null);
|
||||||
const [domainType, setDomainType] = useState<"subdomain" | "basedomain">(
|
const [domainType, setDomainType] = useState<"subdomain" | "basedomain">(
|
||||||
|
@ -140,6 +144,7 @@ export default function CreateResourceForm({
|
||||||
resolver: zodResolver(createResourceFormSchema),
|
resolver: zodResolver(createResourceFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
subdomain: "",
|
subdomain: "",
|
||||||
|
domainId: "",
|
||||||
name: "",
|
name: "",
|
||||||
http: true,
|
http: true,
|
||||||
protocol: "tcp"
|
protocol: "tcp"
|
||||||
|
@ -161,17 +166,55 @@ export default function CreateResourceForm({
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
const fetchSites = async () => {
|
const fetchSites = async () => {
|
||||||
const res = await api.get<AxiosResponse<ListSitesResponse>>(
|
const res = await api
|
||||||
`/org/${orgId}/sites/`
|
.get<AxiosResponse<ListSitesResponse>>(`/org/${orgId}/sites/`)
|
||||||
);
|
.catch((e) => {
|
||||||
setSites(res.data.data.sites);
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Error fetching sites",
|
||||||
|
description: formatAxiosError(
|
||||||
|
e,
|
||||||
|
"An error occurred when fetching the sites"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (res.data.data.sites.length > 0) {
|
if (res?.status === 200) {
|
||||||
form.setValue("siteId", res.data.data.sites[0].siteId);
|
setSites(res.data.data.sites);
|
||||||
|
|
||||||
|
if (res.data.data.sites.length > 0) {
|
||||||
|
form.setValue("siteId", res.data.data.sites[0].siteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchDomains = async () => {
|
||||||
|
const res = await api
|
||||||
|
.get<
|
||||||
|
AxiosResponse<ListDomainsResponse>
|
||||||
|
>(`/org/${orgId}/domains/`)
|
||||||
|
.catch((e) => {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Error fetching domains",
|
||||||
|
description: formatAxiosError(
|
||||||
|
e,
|
||||||
|
"An error occurred when fetching the domains"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res?.status === 200) {
|
||||||
|
const domains = res.data.data.domains;
|
||||||
|
setBaseDomains(domains);
|
||||||
|
if (domains.length) {
|
||||||
|
form.setValue("domainId", domains[0].domainId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchSites();
|
fetchSites();
|
||||||
|
fetchDomains();
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
async function onSubmit(data: CreateResourceFormValues) {
|
async function onSubmit(data: CreateResourceFormValues) {
|
||||||
|
@ -181,6 +224,7 @@ export default function CreateResourceForm({
|
||||||
{
|
{
|
||||||
name: data.name,
|
name: data.name,
|
||||||
subdomain: data.http ? data.subdomain : undefined,
|
subdomain: data.http ? data.subdomain : undefined,
|
||||||
|
domainId: data.http ? data.domainId : undefined,
|
||||||
http: data.http,
|
http: data.http,
|
||||||
protocol: data.protocol,
|
protocol: data.protocol,
|
||||||
proxyPort: data.http ? undefined : data.proxyPort,
|
proxyPort: data.http ? undefined : data.proxyPort,
|
||||||
|
@ -278,7 +322,7 @@ export default function CreateResourceForm({
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Toggle if this is an
|
Toggle if this is an
|
||||||
HTTP resource or a
|
HTTP resource or a
|
||||||
raw TCP/UDP resource
|
raw TCP/UDP resource.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
@ -335,60 +379,98 @@ export default function CreateResourceForm({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{form.watch("http") && (
|
{form.watch("http") && (
|
||||||
<FormField
|
<>
|
||||||
control={form.control}
|
{domainType === "subdomain" ? (
|
||||||
name="subdomain"
|
<FormField
|
||||||
render={({ field }) => (
|
control={form.control}
|
||||||
<FormItem>
|
name="subdomain"
|
||||||
{!env.flags
|
render={({ field }) => (
|
||||||
.allowBaseDomainResources && (
|
<FormItem>
|
||||||
<FormLabel>
|
{!env.flags
|
||||||
Subdomain
|
.allowBaseDomainResources && (
|
||||||
</FormLabel>
|
<FormLabel>
|
||||||
|
Subdomain
|
||||||
|
</FormLabel>
|
||||||
|
)}
|
||||||
|
{domainType ===
|
||||||
|
"subdomain" && (
|
||||||
|
<FormControl>
|
||||||
|
<CustomDomainInput
|
||||||
|
value={
|
||||||
|
field.value ??
|
||||||
|
""
|
||||||
|
}
|
||||||
|
domainOptions={
|
||||||
|
baseDomains
|
||||||
|
}
|
||||||
|
placeholder="Subdomain"
|
||||||
|
onChange={(
|
||||||
|
value,
|
||||||
|
selectedDomainId
|
||||||
|
) => {
|
||||||
|
form.setValue(
|
||||||
|
"subdomain",
|
||||||
|
value
|
||||||
|
);
|
||||||
|
form.setValue(
|
||||||
|
"domainId",
|
||||||
|
selectedDomainId
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
{domainType ===
|
/>
|
||||||
"subdomain" ? (
|
) : (
|
||||||
<FormControl>
|
<FormField
|
||||||
<CustomDomainInput
|
control={form.control}
|
||||||
value={
|
name="domainId"
|
||||||
field.value ??
|
render={({ field }) => (
|
||||||
""
|
<FormItem>
|
||||||
|
<Select
|
||||||
|
onValueChange={
|
||||||
|
field.onChange
|
||||||
}
|
}
|
||||||
domainSuffix={
|
defaultValue={
|
||||||
domainSuffix
|
baseDomains[0]
|
||||||
|
?.domainId
|
||||||
}
|
}
|
||||||
placeholder="Subdomain"
|
>
|
||||||
onChange={(
|
<FormControl>
|
||||||
value
|
<SelectTrigger>
|
||||||
) =>
|
<SelectValue />
|
||||||
form.setValue(
|
</SelectTrigger>
|
||||||
"subdomain",
|
</FormControl>
|
||||||
value
|
<SelectContent>
|
||||||
)
|
{baseDomains.map(
|
||||||
}
|
(
|
||||||
/>
|
option
|
||||||
</FormControl>
|
) => (
|
||||||
) : (
|
<SelectItem
|
||||||
<FormControl>
|
key={
|
||||||
<Input
|
option.domainId
|
||||||
value={
|
}
|
||||||
domainSuffix
|
value={
|
||||||
}
|
option.domainId
|
||||||
readOnly
|
}
|
||||||
disabled
|
>
|
||||||
/>
|
{
|
||||||
</FormControl>
|
option.baseDomain
|
||||||
|
}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
<FormDescription>
|
/>
|
||||||
This is the fully
|
|
||||||
qualified domain name
|
|
||||||
that will be used to
|
|
||||||
access the resource.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
)}
|
||||||
/>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!form.watch("http") && (
|
{!form.watch("http") && (
|
||||||
|
|
|
@ -2,27 +2,68 @@
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
|
||||||
|
interface DomainOption {
|
||||||
|
baseDomain: string;
|
||||||
|
domainId: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface CustomDomainInputProps {
|
interface CustomDomainInputProps {
|
||||||
domainSuffix: string;
|
domainOptions: DomainOption[];
|
||||||
|
selectedDomainId?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value: string;
|
value: string;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string, selectedDomainId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CustomDomainInput({
|
export default function CustomDomainInput({
|
||||||
domainSuffix,
|
domainOptions,
|
||||||
placeholder = "Enter subdomain",
|
selectedDomainId,
|
||||||
|
placeholder = "Subdomain",
|
||||||
value: defaultValue,
|
value: defaultValue,
|
||||||
onChange
|
onChange
|
||||||
}: CustomDomainInputProps) {
|
}: CustomDomainInputProps) {
|
||||||
const [value, setValue] = React.useState(defaultValue);
|
const [value, setValue] = React.useState(defaultValue);
|
||||||
|
const [selectedDomain, setSelectedDomain] = React.useState<DomainOption>();
|
||||||
|
|
||||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
React.useEffect(() => {
|
||||||
|
if (domainOptions.length) {
|
||||||
|
if (selectedDomainId) {
|
||||||
|
const selectedDomainOption = domainOptions.find(
|
||||||
|
(option) => option.domainId === selectedDomainId
|
||||||
|
);
|
||||||
|
setSelectedDomain(selectedDomainOption || domainOptions[0]);
|
||||||
|
} else {
|
||||||
|
setSelectedDomain(domainOptions[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [domainOptions]);
|
||||||
|
|
||||||
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (!selectedDomain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newValue = event.target.value;
|
const newValue = event.target.value;
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange(newValue);
|
onChange(newValue, selectedDomain.domainId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDomainChange = (domainId: string) => {
|
||||||
|
const newSelectedDomain =
|
||||||
|
domainOptions.find((option) => option.domainId === domainId) ||
|
||||||
|
domainOptions[0];
|
||||||
|
setSelectedDomain(newSelectedDomain);
|
||||||
|
if (onChange) {
|
||||||
|
onChange(value, newSelectedDomain.domainId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,12 +74,28 @@ export default function CustomDomainInput({
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleChange}
|
onChange={handleInputChange}
|
||||||
className="rounded-r-none w-full"
|
className="w-1/2 mr-1 text-right"
|
||||||
/>
|
/>
|
||||||
<div className="max-w-1/2 flex items-center px-3 rounded-r-md border border-l-0 border-input bg-muted text-muted-foreground">
|
<Select
|
||||||
<span className="text-sm truncate">.{domainSuffix}</span>
|
onValueChange={handleDomainChange}
|
||||||
</div>
|
value={selectedDomain?.domainId}
|
||||||
|
defaultValue={selectedDomain?.domainId}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-1/2 pr-1">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{domainOptions.map((option) => (
|
||||||
|
<SelectItem
|
||||||
|
key={option.domainId}
|
||||||
|
value={option.domainId}
|
||||||
|
>
|
||||||
|
.{option.baseDomain}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { InfoIcon, ShieldCheck, ShieldOff } from "lucide-react";
|
import { InfoIcon, ShieldCheck, ShieldOff } from "lucide-react";
|
||||||
import { useOrgContext } from "@app/hooks/useOrgContext";
|
|
||||||
import { useResourceContext } from "@app/hooks/useResourceContext";
|
import { useResourceContext } from "@app/hooks/useResourceContext";
|
||||||
import { Separator } from "@app/components/ui/separator";
|
import { Separator } from "@app/components/ui/separator";
|
||||||
import CopyToClipboard from "@app/components/CopyToClipboard";
|
import CopyToClipboard from "@app/components/CopyToClipboard";
|
||||||
|
@ -17,17 +15,9 @@ import {
|
||||||
type ResourceInfoBoxType = {};
|
type ResourceInfoBoxType = {};
|
||||||
|
|
||||||
export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||||
const [copied, setCopied] = useState(false);
|
|
||||||
|
|
||||||
const { org } = useOrgContext();
|
|
||||||
const { resource, authInfo } = useResourceContext();
|
const { resource, authInfo } = useResourceContext();
|
||||||
|
|
||||||
let fullUrl = `${resource.ssl ? "https" : "http"}://`;
|
let fullUrl = `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`;
|
||||||
if (resource.isBaseDomain) {
|
|
||||||
fullUrl = fullUrl + org.org.domain;
|
|
||||||
} else {
|
|
||||||
fullUrl = fullUrl + `${resource.subdomain}.${org.org.domain}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert>
|
<Alert>
|
||||||
|
|
|
@ -33,7 +33,6 @@ import { useEffect, useState } from "react";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { GetResourceAuthInfoResponse } from "@server/routers/resource";
|
|
||||||
import { toast } from "@app/hooks/useToast";
|
import { toast } from "@app/hooks/useToast";
|
||||||
import {
|
import {
|
||||||
SettingsContainer,
|
SettingsContainer,
|
||||||
|
@ -53,6 +52,14 @@ import { subdomainSchema } from "@server/lib/schemas";
|
||||||
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
||||||
import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group";
|
||||||
import { Label } from "@app/components/ui/label";
|
import { Label } from "@app/components/ui/label";
|
||||||
|
import { ListDomainsResponse } from "@server/routers/domain";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
} from "@app/components/ui/select";
|
||||||
|
|
||||||
const GeneralFormSchema = z
|
const GeneralFormSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
@ -60,7 +67,8 @@ const GeneralFormSchema = z
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
proxyPort: z.number().optional(),
|
proxyPort: z.number().optional(),
|
||||||
http: z.boolean(),
|
http: z.boolean(),
|
||||||
isBaseDomain: z.boolean().optional()
|
isBaseDomain: z.boolean().optional(),
|
||||||
|
domainId: z.string().optional()
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
|
@ -113,9 +121,11 @@ export default function GeneralForm() {
|
||||||
|
|
||||||
const [sites, setSites] = useState<ListSitesResponse["sites"]>([]);
|
const [sites, setSites] = useState<ListSitesResponse["sites"]>([]);
|
||||||
const [saveLoading, setSaveLoading] = useState(false);
|
const [saveLoading, setSaveLoading] = useState(false);
|
||||||
const [domainSuffix, setDomainSuffix] = useState(org.org.domain);
|
|
||||||
const [transferLoading, setTransferLoading] = useState(false);
|
const [transferLoading, setTransferLoading] = useState(false);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [baseDomains, setBaseDomains] = useState<
|
||||||
|
ListDomainsResponse["domains"]
|
||||||
|
>([]);
|
||||||
|
|
||||||
const [domainType, setDomainType] = useState<"subdomain" | "basedomain">(
|
const [domainType, setDomainType] = useState<"subdomain" | "basedomain">(
|
||||||
resource.isBaseDomain ? "basedomain" : "subdomain"
|
resource.isBaseDomain ? "basedomain" : "subdomain"
|
||||||
|
@ -128,7 +138,8 @@ export default function GeneralForm() {
|
||||||
subdomain: resource.subdomain ? resource.subdomain : undefined,
|
subdomain: resource.subdomain ? resource.subdomain : undefined,
|
||||||
proxyPort: resource.proxyPort ? resource.proxyPort : undefined,
|
proxyPort: resource.proxyPort ? resource.proxyPort : undefined,
|
||||||
http: resource.http,
|
http: resource.http,
|
||||||
isBaseDomain: resource.isBaseDomain ? true : false
|
isBaseDomain: resource.isBaseDomain ? true : false,
|
||||||
|
domainId: resource.domainId || undefined
|
||||||
},
|
},
|
||||||
mode: "onChange"
|
mode: "onChange"
|
||||||
});
|
});
|
||||||
|
@ -147,6 +158,30 @@ export default function GeneralForm() {
|
||||||
);
|
);
|
||||||
setSites(res.data.data.sites);
|
setSites(res.data.data.sites);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchDomains = async () => {
|
||||||
|
const res = await api
|
||||||
|
.get<
|
||||||
|
AxiosResponse<ListDomainsResponse>
|
||||||
|
>(`/org/${orgId}/domains/`)
|
||||||
|
.catch((e) => {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Error fetching domains",
|
||||||
|
description: formatAxiosError(
|
||||||
|
e,
|
||||||
|
"An error occurred when fetching the domains"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res?.status === 200) {
|
||||||
|
const domains = res.data.data.domains;
|
||||||
|
setBaseDomains(domains);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchDomains();
|
||||||
fetchSites();
|
fetchSites();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -158,7 +193,8 @@ export default function GeneralForm() {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
subdomain: data.subdomain,
|
subdomain: data.subdomain,
|
||||||
proxyPort: data.proxyPort,
|
proxyPort: data.proxyPort,
|
||||||
isBaseDomain: data.isBaseDomain
|
isBaseDomain: data.isBaseDomain,
|
||||||
|
domainId: data.domainId
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
toast({
|
toast({
|
||||||
|
@ -292,60 +328,134 @@ export default function GeneralForm() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormField
|
{domainType === "subdomain" ? (
|
||||||
control={form.control}
|
<div className="w-fill space-y-2">
|
||||||
name="subdomain"
|
{!env.flags
|
||||||
render={({ field }) => (
|
.allowBaseDomainResources && (
|
||||||
<FormItem>
|
<FormLabel>
|
||||||
{!env.flags
|
Subdomain
|
||||||
.allowBaseDomainResources && (
|
</FormLabel>
|
||||||
<FormLabel>
|
)}
|
||||||
Subdomain
|
<div className="flex">
|
||||||
</FormLabel>
|
<div className="w-1/2 mr-1">
|
||||||
)}
|
<FormField
|
||||||
|
control={
|
||||||
{domainType ===
|
form.control
|
||||||
"subdomain" ? (
|
}
|
||||||
<FormControl>
|
name="subdomain"
|
||||||
<CustomDomainInput
|
render={({
|
||||||
value={
|
field
|
||||||
field.value ||
|
}) => (
|
||||||
""
|
<FormControl>
|
||||||
}
|
<Input
|
||||||
domainSuffix={
|
{...field}
|
||||||
domainSuffix
|
className="text-right"
|
||||||
}
|
/>
|
||||||
placeholder="Enter subdomain"
|
</FormControl>
|
||||||
onChange={(
|
)}
|
||||||
value
|
/>
|
||||||
) =>
|
</div>
|
||||||
form.setValue(
|
<div className="w-1/2">
|
||||||
"subdomain",
|
<FormField
|
||||||
value
|
control={
|
||||||
|
form.control
|
||||||
|
}
|
||||||
|
name="domainId"
|
||||||
|
render={({
|
||||||
|
field
|
||||||
|
}) => (
|
||||||
|
<FormItem>
|
||||||
|
<Select
|
||||||
|
onValueChange={
|
||||||
|
field.onChange
|
||||||
|
}
|
||||||
|
defaultValue={
|
||||||
|
field.value ||
|
||||||
|
baseDomains[0]
|
||||||
|
?.domainId
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{baseDomains.map(
|
||||||
|
(
|
||||||
|
option
|
||||||
|
) => (
|
||||||
|
<SelectItem
|
||||||
|
key={
|
||||||
|
option.domainId
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
option.domainId
|
||||||
|
}
|
||||||
|
>
|
||||||
|
.
|
||||||
|
{
|
||||||
|
option.baseDomain
|
||||||
|
}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="domainId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<Select
|
||||||
|
onValueChange={
|
||||||
|
field.onChange
|
||||||
|
}
|
||||||
|
defaultValue={
|
||||||
|
field.value ||
|
||||||
|
baseDomains[0]
|
||||||
|
?.domainId
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{baseDomains.map(
|
||||||
|
(
|
||||||
|
option
|
||||||
|
) => (
|
||||||
|
<SelectItem
|
||||||
|
key={
|
||||||
|
option.domainId
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
option.domainId
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
option.baseDomain
|
||||||
|
}
|
||||||
|
</SelectItem>
|
||||||
)
|
)
|
||||||
}
|
)}
|
||||||
/>
|
</SelectContent>
|
||||||
</FormControl>
|
</Select>
|
||||||
) : (
|
<FormMessage />
|
||||||
<FormControl>
|
</FormItem>
|
||||||
<Input
|
)}
|
||||||
value={
|
/>
|
||||||
domainSuffix
|
)}
|
||||||
}
|
|
||||||
readOnly
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
<FormDescription>
|
|
||||||
This is the subdomain
|
|
||||||
that will be used to
|
|
||||||
access the resource.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -427,7 +537,7 @@ export default function GeneralForm() {
|
||||||
control={transferForm.control}
|
control={transferForm.control}
|
||||||
name="siteId"
|
name="siteId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-col">
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
Destination Site
|
Destination Site
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue