clean up ui pass 1

This commit is contained in:
miloschwartz
2025-06-30 09:33:48 -07:00
parent 3b6a44e683
commit a0381eb2c6
82 changed files with 17618 additions and 17258 deletions

View File

@@ -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")}
/>
)}

View File

@@ -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

View File

@@ -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);

View File

@@ -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>

View File

@@ -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>