mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-11 21:26:37 +00:00
minor visual enhancements
This commit is contained in:
@@ -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<typeof setPasswordFormSchema>;
|
||||
|
||||
const defaultValues: Partial<SetPasswordFormValues> = {
|
||||
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<SetPasswordFormValues>({
|
||||
resolver: zodResolver(setPasswordFormSchema),
|
||||
defaultValues,
|
||||
defaultValues
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -76,7 +76,7 @@ export default function SetResourcePasswordForm({
|
||||
setLoading(true);
|
||||
|
||||
api.post<AxiosResponse<Resource>>(`/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({
|
||||
</Form>
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
</CredenzaClose>
|
||||
<Button
|
||||
type="submit"
|
||||
form="set-password-form"
|
||||
@@ -161,9 +164,6 @@ export default function SetResourcePasswordForm({
|
||||
>
|
||||
Enable Password Protection
|
||||
</Button>
|
||||
<CredenzaClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
</CredenzaClose>
|
||||
</CredenzaFooter>
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
|
||||
@@ -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<typeof setPincodeFormSchema>;
|
||||
|
||||
const defaultValues: Partial<SetPincodeFormValues> = {
|
||||
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<SetPincodeFormValues>({
|
||||
resolver: zodResolver(setPincodeFormSchema),
|
||||
defaultValues,
|
||||
defaultValues
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -81,7 +81,7 @@ export default function SetResourcePincodeForm({
|
||||
setLoading(true);
|
||||
|
||||
api.post<AxiosResponse<Resource>>(`/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({
|
||||
</Form>
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
</CredenzaClose>
|
||||
<Button
|
||||
type="submit"
|
||||
form="set-pincode-form"
|
||||
@@ -189,9 +192,6 @@ export default function SetResourcePincodeForm({
|
||||
>
|
||||
Enable PIN Code Protection
|
||||
</Button>
|
||||
<CredenzaClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
</CredenzaClose>
|
||||
</CredenzaFooter>
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
|
||||
@@ -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"
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -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"
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -582,7 +569,7 @@ export default function ResourceAuthenticationPage() {
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
variant="outlinePrimary"
|
||||
onClick={
|
||||
authInfo.password
|
||||
? removeResourcePassword
|
||||
@@ -608,7 +595,7 @@ export default function ResourceAuthenticationPage() {
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
variant="outlinePrimary"
|
||||
onClick={
|
||||
authInfo.pincode
|
||||
? removeResourcePincode
|
||||
@@ -664,6 +651,7 @@ export default function ResourceAuthenticationPage() {
|
||||
activeTagIndex={
|
||||
activeEmailTagIndex
|
||||
}
|
||||
size={"sm"}
|
||||
validateTag={(
|
||||
tag
|
||||
) => {
|
||||
@@ -708,14 +696,6 @@ export default function ResourceAuthenticationPage() {
|
||||
false
|
||||
}
|
||||
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"
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
|
||||
@@ -241,10 +241,7 @@ export default function ReverseProxyTargets(props: {
|
||||
>(`/resource/${params.resourceId}/target`, data);
|
||||
target.targetId = res.data.data.targetId;
|
||||
} else if (target.updated) {
|
||||
await api.post(
|
||||
`/target/${target.targetId}`,
|
||||
data
|
||||
);
|
||||
await api.post(`/target/${target.targetId}`, data);
|
||||
}
|
||||
|
||||
setTargets([
|
||||
@@ -261,9 +258,7 @@ export default function ReverseProxyTargets(props: {
|
||||
|
||||
for (const targetId of targetsToRemove) {
|
||||
await api.delete(`/target/${targetId}`);
|
||||
setTargets(
|
||||
targets.filter((t) => t.targetId !== targetId)
|
||||
);
|
||||
setTargets(targets.filter((t) => t.targetId !== targetId));
|
||||
}
|
||||
|
||||
toast({
|
||||
@@ -459,7 +454,8 @@ export default function ReverseProxyTargets(props: {
|
||||
SSL Configuration
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Setup SSL to secure your connections with Let's Encrypt certificates
|
||||
Setup SSL to secure your connections with Let's
|
||||
Encrypt certificates
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
@@ -490,7 +486,7 @@ export default function ReverseProxyTargets(props: {
|
||||
onSubmit={addTargetForm.handleSubmit(addTarget)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 items-end">
|
||||
{resource.http && (
|
||||
<FormField
|
||||
control={addTargetForm.control}
|
||||
@@ -545,18 +541,6 @@ export default function ReverseProxyTargets(props: {
|
||||
<Input id="ip" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
{site?.type === "newt" ? (
|
||||
<FormDescription>
|
||||
This is the IP or hostname
|
||||
of the target service on
|
||||
your network.
|
||||
</FormDescription>
|
||||
) : site?.type === "wireguard" ? (
|
||||
<FormDescription>
|
||||
This is the IP of the
|
||||
WireGuard peer.
|
||||
</FormDescription>
|
||||
) : null}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
@@ -575,26 +559,13 @@ export default function ReverseProxyTargets(props: {
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
{site?.type === "newt" ? (
|
||||
<FormDescription>
|
||||
This is the port of the
|
||||
target service on your
|
||||
network.
|
||||
</FormDescription>
|
||||
) : site?.type === "wireguard" ? (
|
||||
<FormDescription>
|
||||
This is the port exposed on
|
||||
an address on the WireGuard
|
||||
network.
|
||||
</FormDescription>
|
||||
) : null}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" variant="outlinePrimary">
|
||||
Add Target
|
||||
</Button>
|
||||
</div>
|
||||
<Button type="submit" variant="outline">
|
||||
Add Target
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<SettingsContainer>
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
General Settings
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Configure the general settings for this resource
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
!loadingPage && (
|
||||
<SettingsContainer>
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
General Settings
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Configure the general settings for this resource
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
|
||||
<SettingsSectionBody>
|
||||
<SettingsSectionForm>
|
||||
<Form {...form} key={formKey}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-4"
|
||||
id="general-settings-form"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
This is the display name of the
|
||||
resource.
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{resource.http && (
|
||||
<>
|
||||
{env.flags.allowBaseDomainResources && (
|
||||
<div>
|
||||
<RadioGroup
|
||||
className="flex space-x-4"
|
||||
defaultValue={domainType}
|
||||
onValueChange={(val) => {
|
||||
setDomainType(
|
||||
val as any
|
||||
);
|
||||
form.setValue(
|
||||
"isBaseDomain",
|
||||
val === "basedomain"
|
||||
);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem
|
||||
value="subdomain"
|
||||
id="r1"
|
||||
/>
|
||||
<Label htmlFor="r1">
|
||||
Subdomain
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem
|
||||
value="basedomain"
|
||||
id="r2"
|
||||
/>
|
||||
<Label htmlFor="r2">
|
||||
Base Domain
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{domainType === "subdomain" ? (
|
||||
<div className="w-fill space-y-2">
|
||||
{!env.flags
|
||||
.allowBaseDomainResources && (
|
||||
<FormLabel>
|
||||
Subdomain
|
||||
</FormLabel>
|
||||
)}
|
||||
<div className="flex">
|
||||
<div className="w-full mr-1">
|
||||
<FormField
|
||||
control={
|
||||
form.control
|
||||
}
|
||||
name="subdomain"
|
||||
render={({
|
||||
field
|
||||
}) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
className="text-right"
|
||||
placeholder="Enter subdomain"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="max-w-1/2">
|
||||
<FormField
|
||||
control={
|
||||
form.control
|
||||
}
|
||||
name="domainId"
|
||||
render={({
|
||||
field
|
||||
}) => (
|
||||
<FormItem>
|
||||
<Select
|
||||
onValueChange={
|
||||
field.onChange
|
||||
}
|
||||
defaultValue={
|
||||
field.value
|
||||
}
|
||||
value={
|
||||
field.value
|
||||
}
|
||||
>
|
||||
<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>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!resource.http && (
|
||||
<SettingsSectionBody>
|
||||
<SettingsSectionForm>
|
||||
<Form {...form} key={formKey}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid grid-cols-1 md:grid-cols-2 gap-4"
|
||||
id="general-settings-form"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="proxyPort"
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Port Number
|
||||
</FormLabel>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
value={
|
||||
field.value ?? ""
|
||||
}
|
||||
onChange={(e) =>
|
||||
field.onChange(
|
||||
e.target.value
|
||||
? parseInt(
|
||||
e
|
||||
.target
|
||||
.value
|
||||
)
|
||||
: null
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
This is the port that will
|
||||
be used to access the
|
||||
resource.
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
</SettingsSectionBody>
|
||||
|
||||
<SettingsSectionFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
loading={saveLoading}
|
||||
disabled={saveLoading}
|
||||
form="general-settings-form"
|
||||
>
|
||||
Save Settings
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
{resource.http && (
|
||||
<>
|
||||
{env.flags
|
||||
.allowBaseDomainResources && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="isBaseDomain"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Domain Type
|
||||
</FormLabel>
|
||||
<Select
|
||||
value={
|
||||
domainType
|
||||
}
|
||||
onValueChange={(
|
||||
val
|
||||
) =>
|
||||
setDomainType(
|
||||
val ===
|
||||
"basedomain"
|
||||
? "basedomain"
|
||||
: "subdomain"
|
||||
)
|
||||
}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="subdomain">
|
||||
Subdomain
|
||||
</SelectItem>
|
||||
<SelectItem value="basedomain">
|
||||
Base
|
||||
Domain
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Transfer Resource
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Transfer this resource to a different site
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
|
||||
<SettingsSectionBody>
|
||||
<SettingsSectionForm>
|
||||
<Form {...transferForm}>
|
||||
<form
|
||||
onSubmit={transferForm.handleSubmit(onTransfer)}
|
||||
className="space-y-4"
|
||||
id="transfer-form"
|
||||
>
|
||||
<FormField
|
||||
control={transferForm.control}
|
||||
name="siteId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Destination Site
|
||||
</FormLabel>
|
||||
<Popover
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
"w-full justify-between",
|
||||
!field.value &&
|
||||
"text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{field.value
|
||||
? sites.find(
|
||||
(site) =>
|
||||
site.siteId ===
|
||||
field.value
|
||||
)?.name
|
||||
: "Select site"}
|
||||
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-full p-0">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search sites"
|
||||
className="h-9"
|
||||
/>
|
||||
<CommandEmpty>
|
||||
No sites found.
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{sites.map(
|
||||
(site) => (
|
||||
<CommandItem
|
||||
value={`${site.name}:${site.siteId}`}
|
||||
key={
|
||||
site.siteId
|
||||
}
|
||||
onSelect={() => {
|
||||
transferForm.setValue(
|
||||
"siteId",
|
||||
site.siteId
|
||||
);
|
||||
setOpen(
|
||||
false
|
||||
);
|
||||
}}
|
||||
>
|
||||
{
|
||||
site.name
|
||||
}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
site.siteId ===
|
||||
<div className="col-span-2">
|
||||
{domainType === "subdomain" ? (
|
||||
<div className="w-fill space-y-2">
|
||||
<FormLabel>
|
||||
Subdomain
|
||||
</FormLabel>
|
||||
<div className="flex">
|
||||
<div className="w-1/2 mr-1">
|
||||
<FormField
|
||||
control={
|
||||
form.control
|
||||
}
|
||||
name="subdomain"
|
||||
render={({
|
||||
field
|
||||
}) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<FormField
|
||||
control={
|
||||
form.control
|
||||
}
|
||||
name="domainId"
|
||||
render={({
|
||||
field
|
||||
}) => (
|
||||
<FormItem>
|
||||
<Select
|
||||
onValueChange={
|
||||
field.onChange
|
||||
}
|
||||
defaultValue={
|
||||
field.value
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
)
|
||||
)}
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
}
|
||||
value={
|
||||
field.value
|
||||
}
|
||||
>
|
||||
<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>
|
||||
<FormLabel>
|
||||
Base Domain
|
||||
</FormLabel>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
</SettingsSectionBody>
|
||||
|
||||
<SettingsSectionFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
loading={transferLoading}
|
||||
disabled={transferLoading}
|
||||
form="transfer-form"
|
||||
>
|
||||
Transfer Resource
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
</SettingsContainer>
|
||||
{!resource.http && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="proxyPort"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Port Number
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
value={
|
||||
field.value ??
|
||||
""
|
||||
}
|
||||
onChange={(e) =>
|
||||
field.onChange(
|
||||
e.target
|
||||
.value
|
||||
? parseInt(
|
||||
e
|
||||
.target
|
||||
.value
|
||||
)
|
||||
: null
|
||||
)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
</SettingsSectionBody>
|
||||
|
||||
<SettingsSectionFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
loading={saveLoading}
|
||||
disabled={saveLoading}
|
||||
form="general-settings-form"
|
||||
>
|
||||
Save Settings
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Transfer Resource
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Transfer this resource to a different site
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
|
||||
<SettingsSectionBody>
|
||||
<SettingsSectionForm>
|
||||
<Form {...transferForm}>
|
||||
<form
|
||||
onSubmit={transferForm.handleSubmit(
|
||||
onTransfer
|
||||
)}
|
||||
className="space-y-4"
|
||||
id="transfer-form"
|
||||
>
|
||||
<FormField
|
||||
control={transferForm.control}
|
||||
name="siteId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Destination Site
|
||||
</FormLabel>
|
||||
<Popover
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
"w-full justify-between",
|
||||
!field.value &&
|
||||
"text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{field.value
|
||||
? sites.find(
|
||||
(
|
||||
site
|
||||
) =>
|
||||
site.siteId ===
|
||||
field.value
|
||||
)?.name
|
||||
: "Select site"}
|
||||
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-full p-0">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search sites"
|
||||
className="h-9"
|
||||
/>
|
||||
<CommandEmpty>
|
||||
No sites found.
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{sites.map(
|
||||
(site) => (
|
||||
<CommandItem
|
||||
value={`${site.name}:${site.siteId}`}
|
||||
key={
|
||||
site.siteId
|
||||
}
|
||||
onSelect={() => {
|
||||
transferForm.setValue(
|
||||
"siteId",
|
||||
site.siteId
|
||||
);
|
||||
setOpen(
|
||||
false
|
||||
);
|
||||
}}
|
||||
>
|
||||
{
|
||||
site.name
|
||||
}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
site.siteId ===
|
||||
field.value
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
)
|
||||
)}
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
</SettingsSectionBody>
|
||||
|
||||
<SettingsSectionFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
loading={transferLoading}
|
||||
disabled={transferLoading}
|
||||
form="transfer-form"
|
||||
>
|
||||
Transfer Resource
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
</SettingsContainer>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 items-end">
|
||||
<FormField
|
||||
control={addRuleForm.control}
|
||||
name="action"
|
||||
@@ -711,14 +711,14 @@ export default function ResourceRules(props: {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outlinePrimary"
|
||||
disabled={!rulesEnabled}
|
||||
>
|
||||
Add Rule
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
disabled={!rulesEnabled}
|
||||
>
|
||||
Add Rule
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
<TableContainer>
|
||||
|
||||
Reference in New Issue
Block a user