mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-03 01:06:39 +00:00
clean up ui pass 1
This commit is contained in:
@@ -31,7 +31,7 @@ import CopyToClipboard from "@app/components/CopyToClipboard";
|
||||
import { Switch } from "@app/components/ui/switch";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { UpdateResourceResponse } from "@server/routers/resource";
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export type ResourceRow = {
|
||||
id: number;
|
||||
@@ -65,11 +65,11 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
const deleteResource = (resourceId: number) => {
|
||||
api.delete(`/resource/${resourceId}`)
|
||||
.catch((e) => {
|
||||
console.error(t('resourceErrorDelte'), e);
|
||||
console.error(t("resourceErrorDelte"), e);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t('resourceErrorDelte'),
|
||||
description: formatAxiosError(e, t('resourceErrorDelte'))
|
||||
title: t("resourceErrorDelte"),
|
||||
description: formatAxiosError(e, t("resourceErrorDelte"))
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
@@ -89,50 +89,16 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t('resourcesErrorUpdate'),
|
||||
description: formatAxiosError(e, t('resourcesErrorUpdateDescription'))
|
||||
title: t("resourcesErrorUpdate"),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
t("resourcesErrorUpdateDescription")
|
||||
)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const columns: ColumnDef<ResourceRow>[] = [
|
||||
{
|
||||
accessorKey: "dots",
|
||||
header: "",
|
||||
cell: ({ row }) => {
|
||||
const resourceRow = row.original;
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">{t('openMenu')}</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<Link
|
||||
className="block w-full"
|
||||
href={`/${resourceRow.orgId}/settings/resources/${resourceRow.id}`}
|
||||
>
|
||||
<DropdownMenuItem>
|
||||
{t('viewSettings')}
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setSelectedResource(resourceRow);
|
||||
setIsDeleteModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="text-red-500">{t('delete')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: ({ column }) => {
|
||||
@@ -143,7 +109,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
{t('name')}
|
||||
{t("name")}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -159,7 +125,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
{t('site')}
|
||||
{t("site")}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -170,7 +136,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
<Link
|
||||
href={`/${resourceRow.orgId}/settings/sites/${resourceRow.siteId}`}
|
||||
>
|
||||
<Button variant="outline">
|
||||
<Button variant="outline" size="sm">
|
||||
{resourceRow.site}
|
||||
<ArrowUpRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
@@ -180,7 +146,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
},
|
||||
{
|
||||
accessorKey: "protocol",
|
||||
header: t('protocol'),
|
||||
header: t("protocol"),
|
||||
cell: ({ row }) => {
|
||||
const resourceRow = row.original;
|
||||
return <span>{resourceRow.protocol.toUpperCase()}</span>;
|
||||
@@ -188,7 +154,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
},
|
||||
{
|
||||
accessorKey: "domain",
|
||||
header: t('access'),
|
||||
header: t("access"),
|
||||
cell: ({ row }) => {
|
||||
const resourceRow = row.original;
|
||||
return (
|
||||
@@ -218,7 +184,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
{t('authentication')}
|
||||
{t("authentication")}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -230,12 +196,12 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
{resourceRow.authState === "protected" ? (
|
||||
<span className="text-green-500 flex items-center space-x-2">
|
||||
<ShieldCheck className="w-4 h-4" />
|
||||
<span>{t('protected')}</span>
|
||||
<span>{t("protected")}</span>
|
||||
</span>
|
||||
) : resourceRow.authState === "not_protected" ? (
|
||||
<span className="text-yellow-500 flex items-center space-x-2">
|
||||
<ShieldOff className="w-4 h-4" />
|
||||
<span>{t('notProtected')}</span>
|
||||
<span>{t("notProtected")}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span>-</span>
|
||||
@@ -246,7 +212,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
},
|
||||
{
|
||||
accessorKey: "enabled",
|
||||
header: t('enabled'),
|
||||
header: t("enabled"),
|
||||
cell: ({ row }) => (
|
||||
<Switch
|
||||
defaultChecked={row.original.enabled}
|
||||
@@ -262,11 +228,41 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
const resourceRow = row.original;
|
||||
return (
|
||||
<div className="flex items-center justify-end">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">
|
||||
{t("openMenu")}
|
||||
</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<Link
|
||||
className="block w-full"
|
||||
href={`/${resourceRow.orgId}/settings/resources/${resourceRow.id}`}
|
||||
>
|
||||
<DropdownMenuItem>
|
||||
{t("viewSettings")}
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setSelectedResource(resourceRow);
|
||||
setIsDeleteModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="text-red-500">
|
||||
{t("delete")}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Link
|
||||
href={`/${resourceRow.orgId}/settings/resources/${resourceRow.id}`}
|
||||
>
|
||||
<Button variant={"outlinePrimary"} className="ml-2">
|
||||
{t('edit')}
|
||||
<Button variant={"secondary"} className="ml-2" size="sm">
|
||||
{t("edit")}
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
@@ -288,22 +284,22 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
dialog={
|
||||
<div>
|
||||
<p className="mb-2">
|
||||
{t('resourceQuestionRemove', {selectedResource: selectedResource?.name || selectedResource?.id})}
|
||||
{t("resourceQuestionRemove", {
|
||||
selectedResource:
|
||||
selectedResource?.name ||
|
||||
selectedResource?.id
|
||||
})}
|
||||
</p>
|
||||
|
||||
<p className="mb-2">
|
||||
{t('resourceMessageRemove')}
|
||||
</p>
|
||||
<p className="mb-2">{t("resourceMessageRemove")}</p>
|
||||
|
||||
<p>
|
||||
{t('resourceMessageConfirm')}
|
||||
</p>
|
||||
<p>{t("resourceMessageConfirm")}</p>
|
||||
</div>
|
||||
}
|
||||
buttonText={t('resourceDeleteConfirm')}
|
||||
buttonText={t("resourceDeleteConfirm")}
|
||||
onConfirm={async () => deleteResource(selectedResource!.id)}
|
||||
string={selectedResource.name}
|
||||
title={t('resourceDelete')}
|
||||
title={t("resourceDelete")}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -568,7 +568,7 @@ export default function ResourceAuthenticationPage() {
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="outlinePrimary"
|
||||
variant="secondary"
|
||||
onClick={
|
||||
authInfo.password
|
||||
? removeResourcePassword
|
||||
@@ -593,7 +593,7 @@ export default function ResourceAuthenticationPage() {
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="outlinePrimary"
|
||||
variant="secondary"
|
||||
onClick={
|
||||
authInfo.pincode
|
||||
? removeResourcePincode
|
||||
|
||||
@@ -128,7 +128,7 @@ export default function ReverseProxyTargets(props: {
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: t('proxyErrorInvalidHeader')
|
||||
message: t("proxyErrorInvalidHeader")
|
||||
}
|
||||
)
|
||||
});
|
||||
@@ -146,7 +146,7 @@ export default function ReverseProxyTargets(props: {
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: t('proxyErrorTls')
|
||||
message: t("proxyErrorTls")
|
||||
}
|
||||
)
|
||||
});
|
||||
@@ -203,10 +203,10 @@ export default function ReverseProxyTargets(props: {
|
||||
console.error(err);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t('targetErrorFetch'),
|
||||
title: t("targetErrorFetch"),
|
||||
description: formatAxiosError(
|
||||
err,
|
||||
t('targetErrorFetchDescription')
|
||||
t("targetErrorFetchDescription")
|
||||
)
|
||||
});
|
||||
} finally {
|
||||
@@ -228,10 +228,10 @@ export default function ReverseProxyTargets(props: {
|
||||
console.error(err);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t('siteErrorFetch'),
|
||||
title: t("siteErrorFetch"),
|
||||
description: formatAxiosError(
|
||||
err,
|
||||
t('siteErrorFetchDescription')
|
||||
t("siteErrorFetchDescription")
|
||||
)
|
||||
});
|
||||
}
|
||||
@@ -251,8 +251,8 @@ export default function ReverseProxyTargets(props: {
|
||||
if (isDuplicate) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t('targetErrorDuplicate'),
|
||||
description: t('targetErrorDuplicateDescription')
|
||||
title: t("targetErrorDuplicate"),
|
||||
description: t("targetErrorDuplicateDescription")
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -264,8 +264,8 @@ export default function ReverseProxyTargets(props: {
|
||||
if (!isIPInSubnet(targetIp, subnet)) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t('targetWireGuardErrorInvalidIp'),
|
||||
description: t('targetWireGuardErrorInvalidIpDescription')
|
||||
title: t("targetWireGuardErrorInvalidIp"),
|
||||
description: t("targetWireGuardErrorInvalidIpDescription")
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -307,10 +307,13 @@ export default function ReverseProxyTargets(props: {
|
||||
);
|
||||
}
|
||||
|
||||
async function saveTargets() {
|
||||
async function saveAllSettings() {
|
||||
try {
|
||||
setTargetsLoading(true);
|
||||
setHttpsTlsLoading(true);
|
||||
setProxySettingsLoading(true);
|
||||
|
||||
// Save targets
|
||||
for (let target of targets) {
|
||||
const data = {
|
||||
ip: target.ip,
|
||||
@@ -342,9 +345,31 @@ export default function ReverseProxyTargets(props: {
|
||||
});
|
||||
updateResource({ stickySession: stickySessionData.stickySession });
|
||||
|
||||
// Save TLS settings
|
||||
const tlsData = tlsSettingsForm.getValues();
|
||||
await api.post(`/resource/${params.resourceId}`, {
|
||||
ssl: tlsData.ssl,
|
||||
tlsServerName: tlsData.tlsServerName || null
|
||||
});
|
||||
updateResource({
|
||||
...resource,
|
||||
ssl: tlsData.ssl,
|
||||
tlsServerName: tlsData.tlsServerName || null
|
||||
});
|
||||
|
||||
// Save proxy settings
|
||||
const proxyData = proxySettingsForm.getValues();
|
||||
await api.post(`/resource/${params.resourceId}`, {
|
||||
setHostHeader: proxyData.setHostHeader || null
|
||||
});
|
||||
updateResource({
|
||||
...resource,
|
||||
setHostHeader: proxyData.setHostHeader || null
|
||||
});
|
||||
|
||||
toast({
|
||||
title: t('targetsUpdated'),
|
||||
description: t('targetsUpdatedDescription')
|
||||
title: t("settingsUpdated"),
|
||||
description: t("settingsUpdatedDescription")
|
||||
});
|
||||
|
||||
setTargetsToRemove([]);
|
||||
@@ -353,73 +378,15 @@ export default function ReverseProxyTargets(props: {
|
||||
console.error(err);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t('targetsErrorUpdate'),
|
||||
title: t("settingsErrorUpdate"),
|
||||
description: formatAxiosError(
|
||||
err,
|
||||
t('targetsErrorUpdateDescription')
|
||||
t("settingsErrorUpdateDescription")
|
||||
)
|
||||
});
|
||||
} finally {
|
||||
setTargetsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveTlsSettings(data: TlsSettingsValues) {
|
||||
try {
|
||||
setHttpsTlsLoading(true);
|
||||
await api.post(`/resource/${params.resourceId}`, {
|
||||
ssl: data.ssl,
|
||||
tlsServerName: data.tlsServerName || null
|
||||
});
|
||||
updateResource({
|
||||
...resource,
|
||||
ssl: data.ssl,
|
||||
tlsServerName: data.tlsServerName || null
|
||||
});
|
||||
toast({
|
||||
title: t('targetTlsUpdate'),
|
||||
description: t('targetTlsUpdateDescription')
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t('targetErrorTlsUpdate'),
|
||||
description: formatAxiosError(
|
||||
err,
|
||||
t('targetErrorTlsUpdateDescription')
|
||||
)
|
||||
});
|
||||
} finally {
|
||||
setHttpsTlsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveProxySettings(data: ProxySettingsValues) {
|
||||
try {
|
||||
setProxySettingsLoading(true);
|
||||
await api.post(`/resource/${params.resourceId}`, {
|
||||
setHostHeader: data.setHostHeader || null
|
||||
});
|
||||
updateResource({
|
||||
...resource,
|
||||
setHostHeader: data.setHostHeader || null
|
||||
});
|
||||
toast({
|
||||
title: t('proxyUpdated'),
|
||||
description: t('proxyUpdatedDescription')
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t('proxyErrorUpdate'),
|
||||
description: formatAxiosError(
|
||||
err,
|
||||
t('proxyErrorUpdateDescription')
|
||||
)
|
||||
});
|
||||
} finally {
|
||||
setProxySettingsLoading(false);
|
||||
}
|
||||
}
|
||||
@@ -427,7 +394,7 @@ export default function ReverseProxyTargets(props: {
|
||||
const columns: ColumnDef<LocalTarget>[] = [
|
||||
{
|
||||
accessorKey: "ip",
|
||||
header: t('targetAddr'),
|
||||
header: t("targetAddr"),
|
||||
cell: ({ row }) => (
|
||||
<Input
|
||||
defaultValue={row.original.ip}
|
||||
@@ -442,7 +409,7 @@ export default function ReverseProxyTargets(props: {
|
||||
},
|
||||
{
|
||||
accessorKey: "port",
|
||||
header: t('targetPort'),
|
||||
header: t("targetPort"),
|
||||
cell: ({ row }) => (
|
||||
<Input
|
||||
type="number"
|
||||
@@ -476,7 +443,7 @@ export default function ReverseProxyTargets(props: {
|
||||
// },
|
||||
{
|
||||
accessorKey: "enabled",
|
||||
header: t('enabled'),
|
||||
header: t("enabled"),
|
||||
cell: ({ row }) => (
|
||||
<Switch
|
||||
defaultChecked={row.original.enabled}
|
||||
@@ -503,7 +470,7 @@ export default function ReverseProxyTargets(props: {
|
||||
variant="outline"
|
||||
onClick={() => removeTarget(row.original.targetId)}
|
||||
>
|
||||
{t('delete')}
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
@@ -514,7 +481,7 @@ export default function ReverseProxyTargets(props: {
|
||||
if (resource.http) {
|
||||
const methodCol: ColumnDef<LocalTarget> = {
|
||||
accessorKey: "method",
|
||||
header: t('method'),
|
||||
header: t("method"),
|
||||
cell: ({ row }) => (
|
||||
<Select
|
||||
defaultValue={row.original.method ?? ""}
|
||||
@@ -561,11 +528,9 @@ export default function ReverseProxyTargets(props: {
|
||||
<SettingsContainer>
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
{t('targets')}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionTitle>{t("targets")}</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
{t('targetsDescription')}
|
||||
{t("targetsDescription")}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
@@ -573,7 +538,7 @@ export default function ReverseProxyTargets(props: {
|
||||
<Form {...targetsSettingsForm}>
|
||||
<form
|
||||
onSubmit={targetsSettingsForm.handleSubmit(
|
||||
saveTargets
|
||||
saveAllSettings
|
||||
)}
|
||||
className="space-y-4"
|
||||
id="targets-settings-form"
|
||||
@@ -587,8 +552,12 @@ export default function ReverseProxyTargets(props: {
|
||||
<FormControl>
|
||||
<SwitchInput
|
||||
id="sticky-toggle"
|
||||
label={t('targetStickySessions')}
|
||||
description={t('targetStickySessionsDescription')}
|
||||
label={t(
|
||||
"targetStickySessions"
|
||||
)}
|
||||
description={t(
|
||||
"targetStickySessionsDescription"
|
||||
)}
|
||||
defaultChecked={
|
||||
field.value
|
||||
}
|
||||
@@ -619,7 +588,9 @@ export default function ReverseProxyTargets(props: {
|
||||
name="method"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('method')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("method")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
value={
|
||||
@@ -635,8 +606,15 @@ export default function ReverseProxyTargets(props: {
|
||||
);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger id="method">
|
||||
<SelectValue placeholder={t('methodSelect')} />
|
||||
<SelectTrigger
|
||||
id="method"
|
||||
className="w-full"
|
||||
>
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"methodSelect"
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="http">
|
||||
@@ -662,7 +640,9 @@ export default function ReverseProxyTargets(props: {
|
||||
name="ip"
|
||||
render={({ field }) => (
|
||||
<FormItem className="relative">
|
||||
<FormLabel>{t('targetAddr')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("targetAddr")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input id="ip" {...field} />
|
||||
</FormControl>
|
||||
@@ -695,7 +675,9 @@ export default function ReverseProxyTargets(props: {
|
||||
name="port"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('targetPort')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("targetPort")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
id="port"
|
||||
@@ -710,11 +692,11 @@ export default function ReverseProxyTargets(props: {
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outlinePrimary"
|
||||
variant="secondary"
|
||||
className="mt-6"
|
||||
disabled={!(watchedIp && watchedPort)}
|
||||
>
|
||||
{t('targetSubmit')}
|
||||
{t("targetSubmit")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -758,189 +740,170 @@ export default function ReverseProxyTargets(props: {
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
{t('targetNoOne')}
|
||||
{t("targetNoOne")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
<TableCaption>
|
||||
{t('targetNoOneDescription')}
|
||||
</TableCaption>
|
||||
{/* <TableCaption> */}
|
||||
{/* {t('targetNoOneDescription')} */}
|
||||
{/* </TableCaption> */}
|
||||
</Table>
|
||||
</SettingsSectionBody>
|
||||
<SettingsSectionFooter>
|
||||
<Button
|
||||
onClick={saveTargets}
|
||||
loading={targetsLoading}
|
||||
disabled={targetsLoading}
|
||||
form="targets-settings-form"
|
||||
>
|
||||
{t('targetsSubmit')}
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
|
||||
{resource.http && (
|
||||
<SettingsSectionGrid cols={2}>
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
{t('targetTlsSettings')}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
{t('targetTlsSettingsDescription')}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<SettingsSectionForm>
|
||||
<Form {...tlsSettingsForm}>
|
||||
<form
|
||||
onSubmit={tlsSettingsForm.handleSubmit(
|
||||
saveTlsSettings
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
{t("proxyAdditional")}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
{t("proxyAdditionalDescription")}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<SettingsSectionForm>
|
||||
<Form {...tlsSettingsForm}>
|
||||
<form
|
||||
onSubmit={tlsSettingsForm.handleSubmit(
|
||||
saveAllSettings
|
||||
)}
|
||||
className="space-y-4"
|
||||
id="tls-settings-form"
|
||||
>
|
||||
<FormField
|
||||
control={tlsSettingsForm.control}
|
||||
name="ssl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<SwitchInput
|
||||
id="ssl-toggle"
|
||||
label={t(
|
||||
"proxyEnableSSL"
|
||||
)}
|
||||
defaultChecked={
|
||||
field.value
|
||||
}
|
||||
onCheckedChange={(
|
||||
val
|
||||
) => {
|
||||
field.onChange(val);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
className="space-y-4"
|
||||
id="tls-settings-form"
|
||||
/>
|
||||
<Collapsible
|
||||
open={isAdvancedOpen}
|
||||
onOpenChange={setIsAdvancedOpen}
|
||||
className="space-y-2"
|
||||
>
|
||||
<FormField
|
||||
control={tlsSettingsForm.control}
|
||||
name="ssl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<SwitchInput
|
||||
id="ssl-toggle"
|
||||
label={t('proxyEnableSSL')}
|
||||
defaultChecked={
|
||||
field.value
|
||||
}
|
||||
onCheckedChange={(
|
||||
val
|
||||
) => {
|
||||
field.onChange(
|
||||
val
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Collapsible
|
||||
open={isAdvancedOpen}
|
||||
onOpenChange={setIsAdvancedOpen}
|
||||
className="space-y-2"
|
||||
>
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button
|
||||
variant="text"
|
||||
size="sm"
|
||||
className="p-0 flex items-center justify-start gap-2 w-full"
|
||||
>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('targetTlsSettingsAdvanced')}
|
||||
</p>
|
||||
<div>
|
||||
<ChevronsUpDown className="h-4 w-4" />
|
||||
<span className="sr-only">
|
||||
Toggle
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
</div>
|
||||
<CollapsibleContent className="space-y-2">
|
||||
<FormField
|
||||
control={
|
||||
tlsSettingsForm.control
|
||||
}
|
||||
name="tlsServerName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t('targetTlsSni')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('targetTlsSniDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button
|
||||
variant="text"
|
||||
size="sm"
|
||||
className="p-0 flex items-center justify-start gap-2 w-full"
|
||||
>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(
|
||||
"targetTlsSettingsAdvanced"
|
||||
)}
|
||||
</p>
|
||||
<div>
|
||||
<ChevronsUpDown className="h-4 w-4" />
|
||||
<span className="sr-only">
|
||||
Toggle
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
</div>
|
||||
<CollapsibleContent className="space-y-2">
|
||||
<FormField
|
||||
control={
|
||||
tlsSettingsForm.control
|
||||
}
|
||||
name="tlsServerName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("targetTlsSni")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"targetTlsSniDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
|
||||
<SettingsSectionForm>
|
||||
<Form {...proxySettingsForm}>
|
||||
<form
|
||||
onSubmit={proxySettingsForm.handleSubmit(
|
||||
saveAllSettings
|
||||
)}
|
||||
className="space-y-4"
|
||||
id="proxy-settings-form"
|
||||
>
|
||||
<FormField
|
||||
control={proxySettingsForm.control}
|
||||
name="setHostHeader"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("proxyCustomHeader")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"proxyCustomHeaderDescription"
|
||||
)}
|
||||
/>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
</SettingsSectionBody>
|
||||
<SettingsSectionFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
loading={httpsTlsLoading}
|
||||
form="tls-settings-form"
|
||||
>
|
||||
{t('targetTlsSubmit')}
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
{t('proxyAdditional')}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
{t('proxyAdditionalDescription')}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<SettingsSectionForm>
|
||||
<Form {...proxySettingsForm}>
|
||||
<form
|
||||
onSubmit={proxySettingsForm.handleSubmit(
|
||||
saveProxySettings
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
className="space-y-4"
|
||||
id="proxy-settings-form"
|
||||
>
|
||||
<FormField
|
||||
control={proxySettingsForm.control}
|
||||
name="setHostHeader"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t('proxyCustomHeader')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('proxyCustomHeaderDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
</SettingsSectionBody>
|
||||
<SettingsSectionFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
loading={proxySettingsLoading}
|
||||
form="proxy-settings-form"
|
||||
>
|
||||
{t('targetTlsSubmit')}
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
</SettingsSectionGrid>
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
</SettingsSectionBody>
|
||||
</SettingsSection>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end mt-6">
|
||||
<Button
|
||||
onClick={saveAllSettings}
|
||||
loading={
|
||||
targetsLoading ||
|
||||
httpsTlsLoading ||
|
||||
proxySettingsLoading
|
||||
}
|
||||
disabled={
|
||||
targetsLoading ||
|
||||
httpsTlsLoading ||
|
||||
proxySettingsLoading
|
||||
}
|
||||
>
|
||||
{t("saveAllSettings")}
|
||||
</Button>
|
||||
</div>
|
||||
</SettingsContainer>
|
||||
);
|
||||
}
|
||||
@@ -953,7 +916,7 @@ function isIPInSubnet(subnet: string, ip: string): boolean {
|
||||
const t = useTranslations();
|
||||
|
||||
if (mask < 0 || mask > 32) {
|
||||
throw new Error(t('subnetMaskErrorInvalid'));
|
||||
throw new Error(t("subnetMaskErrorInvalid"));
|
||||
}
|
||||
|
||||
// Convert IP addresses to binary numbers
|
||||
@@ -973,14 +936,14 @@ function ipToNumber(ip: string): number {
|
||||
const t = useTranslations();
|
||||
|
||||
if (parts.length !== 4) {
|
||||
throw new Error(t('ipAddressErrorInvalidFormat'));
|
||||
throw new Error(t("ipAddressErrorInvalidFormat"));
|
||||
}
|
||||
|
||||
// Convert IP octets to 32-bit number
|
||||
return parts.reduce((num, octet) => {
|
||||
const oct = parseInt(octet);
|
||||
if (isNaN(oct) || oct < 0 || oct > 255) {
|
||||
throw new Error(t('ipAddressErrorInvalidOctet'));
|
||||
throw new Error(t("ipAddressErrorInvalidOctet"));
|
||||
}
|
||||
return (num << 8) + oct;
|
||||
}, 0);
|
||||
|
||||
@@ -36,7 +36,6 @@ import {
|
||||
TableBody,
|
||||
TableCaption,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow
|
||||
@@ -543,48 +542,48 @@ export default function ResourceRules(props: {
|
||||
|
||||
return (
|
||||
<SettingsContainer>
|
||||
<Alert className="hidden md:block">
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">{t('rulesAbout')}</AlertTitle>
|
||||
<AlertDescription className="mt-4">
|
||||
<div className="space-y-1 mb-4">
|
||||
<p>
|
||||
{t('rulesAboutDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<InfoSections cols={2}>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>{t('rulesActions')}</InfoSectionTitle>
|
||||
<ul className="text-sm text-muted-foreground space-y-1">
|
||||
<li className="flex items-center gap-2">
|
||||
<Check className="text-green-500 w-4 h-4" />
|
||||
{t('rulesActionAlwaysAllow')}
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<X className="text-red-500 w-4 h-4" />
|
||||
{t('rulesActionAlwaysDeny')}
|
||||
</li>
|
||||
</ul>
|
||||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t('rulesMatchCriteria')}
|
||||
</InfoSectionTitle>
|
||||
<ul className="text-sm text-muted-foreground space-y-1">
|
||||
<li className="flex items-center gap-2">
|
||||
{t('rulesMatchCriteriaIpAddress')}
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
{t('rulesMatchCriteriaIpAddressRange')}
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
{t('rulesMatchCriteriaUrl')}
|
||||
</li>
|
||||
</ul>
|
||||
</InfoSection>
|
||||
</InfoSections>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
{/* <Alert className="hidden md:block"> */}
|
||||
{/* <InfoIcon className="h-4 w-4" /> */}
|
||||
{/* <AlertTitle className="font-semibold">{t('rulesAbout')}</AlertTitle> */}
|
||||
{/* <AlertDescription className="mt-4"> */}
|
||||
{/* <div className="space-y-1 mb-4"> */}
|
||||
{/* <p> */}
|
||||
{/* {t('rulesAboutDescription')} */}
|
||||
{/* </p> */}
|
||||
{/* </div> */}
|
||||
{/* <InfoSections cols={2}> */}
|
||||
{/* <InfoSection> */}
|
||||
{/* <InfoSectionTitle>{t('rulesActions')}</InfoSectionTitle> */}
|
||||
{/* <ul className="text-sm text-muted-foreground space-y-1"> */}
|
||||
{/* <li className="flex items-center gap-2"> */}
|
||||
{/* <Check className="text-green-500 w-4 h-4" /> */}
|
||||
{/* {t('rulesActionAlwaysAllow')} */}
|
||||
{/* </li> */}
|
||||
{/* <li className="flex items-center gap-2"> */}
|
||||
{/* <X className="text-red-500 w-4 h-4" /> */}
|
||||
{/* {t('rulesActionAlwaysDeny')} */}
|
||||
{/* </li> */}
|
||||
{/* </ul> */}
|
||||
{/* </InfoSection> */}
|
||||
{/* <InfoSection> */}
|
||||
{/* <InfoSectionTitle> */}
|
||||
{/* {t('rulesMatchCriteria')} */}
|
||||
{/* </InfoSectionTitle> */}
|
||||
{/* <ul className="text-sm text-muted-foreground space-y-1"> */}
|
||||
{/* <li className="flex items-center gap-2"> */}
|
||||
{/* {t('rulesMatchCriteriaIpAddress')} */}
|
||||
{/* </li> */}
|
||||
{/* <li className="flex items-center gap-2"> */}
|
||||
{/* {t('rulesMatchCriteriaIpAddressRange')} */}
|
||||
{/* </li> */}
|
||||
{/* <li className="flex items-center gap-2"> */}
|
||||
{/* {t('rulesMatchCriteriaUrl')} */}
|
||||
{/* </li> */}
|
||||
{/* </ul> */}
|
||||
{/* </InfoSection> */}
|
||||
{/* </InfoSections> */}
|
||||
{/* </AlertDescription> */}
|
||||
{/* </Alert> */}
|
||||
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
@@ -634,7 +633,7 @@ export default function ResourceRules(props: {
|
||||
field.onChange
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -664,7 +663,7 @@ export default function ResourceRules(props: {
|
||||
field.onChange
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -690,7 +689,7 @@ export default function ResourceRules(props: {
|
||||
control={addRuleForm.control}
|
||||
name="value"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-0 mb-2">
|
||||
<FormItem className="gap-1">
|
||||
<InfoPopup
|
||||
text={t('value')}
|
||||
info={
|
||||
@@ -702,7 +701,7 @@ export default function ResourceRules(props: {
|
||||
}
|
||||
/>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
<Input {...field}/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -710,8 +709,7 @@ export default function ResourceRules(props: {
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outlinePrimary"
|
||||
className="mb-2"
|
||||
variant="secondary"
|
||||
disabled={!rulesEnabled}
|
||||
>
|
||||
{t('ruleSubmit')}
|
||||
@@ -762,9 +760,9 @@ export default function ResourceRules(props: {
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
<TableCaption>
|
||||
{t('rulesOrder')}
|
||||
</TableCaption>
|
||||
{/* <TableCaption> */}
|
||||
{/* {t('rulesOrder')} */}
|
||||
{/* </TableCaption> */}
|
||||
</Table>
|
||||
</SettingsSectionBody>
|
||||
<SettingsSectionFooter>
|
||||
|
||||
@@ -459,29 +459,31 @@ export default function Page() {
|
||||
</SettingsSectionBody>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
{t("resourceType")}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
{t("resourceTypeDescription")}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<StrategySelect
|
||||
options={resourceTypes}
|
||||
defaultValue="http"
|
||||
onChange={(value) => {
|
||||
baseForm.setValue(
|
||||
"http",
|
||||
value === "http"
|
||||
);
|
||||
}}
|
||||
cols={2}
|
||||
/>
|
||||
</SettingsSectionBody>
|
||||
</SettingsSection>
|
||||
{resourceTypes.length > 1 && (
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
{t("resourceType")}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
{t("resourceTypeDescription")}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<StrategySelect
|
||||
options={resourceTypes}
|
||||
defaultValue="http"
|
||||
onChange={(value) => {
|
||||
baseForm.setValue(
|
||||
"http",
|
||||
value === "http"
|
||||
);
|
||||
}}
|
||||
cols={2}
|
||||
/>
|
||||
</SettingsSectionBody>
|
||||
</SettingsSection>
|
||||
)}
|
||||
|
||||
{baseForm.watch("http") ? (
|
||||
<SettingsSection>
|
||||
|
||||
Reference in New Issue
Block a user