Add region-based resource rule

This commit is contained in:
Dennis
2025-12-22 17:44:56 +01:00
parent 9d9401d2ee
commit e051142334
9 changed files with 678 additions and 12 deletions

View File

@@ -6,7 +6,9 @@ import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue
} from "@/components/ui/select";
@@ -75,6 +77,7 @@ import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { COUNTRIES } from "@server/db/countries";
import { MAJOR_ASNS } from "@server/db/asns";
import { REGIONS, getRegionNameById, isValidRegionId } from "@server/db/regions";
import {
Command,
CommandEmpty,
@@ -119,6 +122,8 @@ export default function ResourceRules(props: {
useState(false);
const [openAddRuleAsnSelect, setOpenAddRuleAsnSelect] =
useState(false);
const [openAddRuleRegionSelect, setOpenAddRuleRegionSelect] =
useState(false);
const router = useRouter();
const t = useTranslations();
const { env } = useEnvContext();
@@ -138,7 +143,8 @@ export default function ResourceRules(props: {
IP: "IP",
CIDR: t("ipAddressRange"),
COUNTRY: t("country"),
ASN: "ASN"
ASN: "ASN",
REGION: t("region")
} as const;
const addRuleForm = useForm({
@@ -258,6 +264,20 @@ export default function ResourceRules(props: {
setLoading(false);
return;
}
if (
data.match === "REGION" &&
!isValidRegionId(data.value)
) {
toast({
variant: "destructive",
title: t("rulesErrorInvalidRegion"),
description:
t("rulesErrorInvalidRegionDescription") ||
"Invalid region."
});
setLoading(false);
return;
}
// find the highest priority and add one
let priority = data.priority;
@@ -311,6 +331,8 @@ export default function ResourceRules(props: {
return t("rulesMatchCountry");
case "ASN":
return "Enter an Autonomous System Number (e.g., AS15169 or 15169)";
case "REGION":
return t("rulesMatchRegion");
}
}
@@ -536,12 +558,12 @@ export default function ResourceRules(props: {
<Select
defaultValue={row.original.match}
onValueChange={(
value: "CIDR" | "IP" | "PATH" | "COUNTRY" | "ASN"
value: "CIDR" | "IP" | "PATH" | "COUNTRY" | "ASN" | "REGION"
) =>
updateRule(row.original.ruleId, {
match: value,
value:
value === "COUNTRY" ? "US" : value === "ASN" ? "AS15169" : row.original.value
value === "COUNTRY" ? "US" : value === "ASN" ? "AS15169" : value === "REGION" ? "021" : row.original.value
})
}
>
@@ -557,6 +579,11 @@ export default function ResourceRules(props: {
{RuleMatch.COUNTRY}
</SelectItem>
)}
{isMaxmindAvailable && (
<SelectItem value="REGION">
{RuleMatch.REGION}
</SelectItem>
)}
{isMaxmindAsnAvailable && (
<SelectItem value="ASN">
{RuleMatch.ASN}
@@ -638,14 +665,14 @@ export default function ResourceRules(props: {
>
{row.original.value
? (() => {
const found = MAJOR_ASNS.find(
const found = MAJOR_ASNS.find(
(asn) =>
asn.code ===
row.original.value
);
return found
? `${found.name} (${row.original.value})`
: `Custom (${row.original.value})`;
);
return found
? `${found.name} (${row.original.value})`
: `Custom (${row.original.value})`;
})()
: "Select ASN"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
@@ -715,6 +742,88 @@ export default function ResourceRules(props: {
</div>
</PopoverContent>
</Popover>
) : row.original.match === "REGION" ? (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className="min-w-[200px] justify-between"
>
{(() => {
const regionName = getRegionNameById(row.original.value);
if (!regionName) {
return t("selectRegion");
}
return `${t(regionName)} (${row.original.value})`;
})()}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="min-w-[200px] p-0">
<Command>
<CommandInput
placeholder={t("searchRegions")}
/>
<CommandList>
<CommandEmpty>
{t("noRegionFound")}
</CommandEmpty>
{REGIONS.map((continent) => (
<CommandGroup key={continent.id} heading={t(continent.name)}>
<CommandItem
value={continent.id}
keywords={[
t(continent.name),
continent.id
]}
onSelect={() => {
updateRule(
row.original.ruleId,
{ value: continent.id }
);
}}
>
<Check
className={`mr-2 h-4 w-4 ${
row.original.value === continent.id
? "opacity-100"
: "opacity-0"
}`}
/>
{t(continent.name)} ({continent.id})
</CommandItem>
{continent.includes.map((subregion) => (
<CommandItem
key={subregion.id}
value={subregion.id}
keywords={[
t(subregion.name),
subregion.id
]}
onSelect={() => {
updateRule(
row.original.ruleId,
{ value: subregion.id }
);
}}
>
<Check
className={`mr-2 h-4 w-4 ${
row.original.value === subregion.id
? "opacity-100"
: "opacity-0"
}`}
/>
{t(subregion.name)} ({subregion.id})
</CommandItem>
))}
</CommandGroup>
))}
</CommandList>
</Command>
</PopoverContent>
</Popover>
) : (
<Input
defaultValue={row.original.value}
@@ -925,6 +1034,13 @@ export default function ResourceRules(props: {
}
</SelectItem>
)}
{isMaxmindAvailable && (
<SelectItem value="REGION">
{
RuleMatch.REGION
}
</SelectItem>
)}
{isMaxmindAsnAvailable && (
<SelectItem value="ASN">
{
@@ -1163,6 +1279,112 @@ export default function ResourceRules(props: {
</div>
</PopoverContent>
</Popover>
) : addRuleForm.watch(
"match"
) === "REGION" ? (
<Popover
open={
openAddRuleRegionSelect
}
onOpenChange={
setOpenAddRuleRegionSelect
}
>
<PopoverTrigger
asChild
>
<Button
variant="outline"
role="combobox"
aria-expanded={
openAddRuleRegionSelect
}
className="w-full justify-between"
>
{field.value
? (() => {
const regionName = getRegionNameById(field.value);
const translatedName = regionName ? t(regionName) : field.value;
return `${translatedName} (${field.value})`;
})()
: t(
"selectRegion"
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput
placeholder={t(
"searchRegions"
)}
/>
<CommandList>
<CommandEmpty>
{t(
"noRegionFound"
)}
</CommandEmpty>
{REGIONS.map((continent) => (
<CommandGroup key={continent.id} heading={t(continent.name)}>
<CommandItem
value={continent.id}
keywords={[
t(continent.name),
continent.id
]}
onSelect={() => {
field.onChange(
continent.id
);
setOpenAddRuleRegionSelect(
false
);
}}
>
<Check
className={`mr-2 h-4 w-4 ${
field.value === continent.id
? "opacity-100"
: "opacity-0"
}`}
/>
{t(continent.name)} ({continent.id})
</CommandItem>
{continent.includes.map((subregion) => (
<CommandItem
key={subregion.id}
value={subregion.id}
keywords={[
t(subregion.name),
subregion.id
]}
onSelect={() => {
field.onChange(
subregion.id
);
setOpenAddRuleRegionSelect(
false
);
}}
>
<Check
className={`mr-2 h-4 w-4 ${
field.value === subregion.id
? "opacity-100"
: "opacity-0"
}`}
/>
{t(subregion.name)} ({subregion.id})
</CommandItem>
))}
</CommandGroup>
))}
</CommandList>
</Command>
</PopoverContent>
</Popover>
) : (
<Input {...field} />
)}