mirror of
https://github.com/fosrl/pangolin.git
synced 2026-04-01 15:36:38 +00:00
Add region-based resource rule
This commit is contained in:
@@ -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} />
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user