Merge pull request #3331 from Fredkiss3/feat/geoip-tag-in-tables

feat: Show GeoIp country flags in site & rules page
This commit is contained in:
Owen Schwartz
2026-06-25 11:23:42 -07:00
committed by GitHub
4 changed files with 56 additions and 18 deletions

View File

@@ -795,10 +795,13 @@ export const COUNTRIES = [
name: "Serbia",
code: "RS"
},
{
name: "Serbia and Montenegro",
code: "CS"
},
// Removed as this is a deprecated ISO country code, not supported anymore
// Also the individual flags for Serbia & Montenegro are already included in the list
// more details: https://en.wikipedia.org/wiki/ISO_3166-2:CS
// {
// name: "Serbia and Montenegro",
// code: "CS"
// },
{
name: "Seychelles",
code: "SC"

View File

@@ -10,6 +10,7 @@ import logger from "@server/logger";
import stoi from "@server/lib/stoi";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { getCountryCodeForIp } from "@server/lib/geoip";
const getSiteSchema = z.strictObject({
siteId: z
@@ -47,6 +48,7 @@ type SiteQueryRow = NonNullable<Awaited<ReturnType<typeof query>>>;
export type GetSiteResponse = SiteQueryRow["sites"] & {
newtId: string | null;
newtVersion: string | null;
countryCode: string | null;
};
registry.registerPath({
@@ -134,7 +136,10 @@ export async function getSite(
const data: GetSiteResponse = {
...site.sites,
newtId: site.newt ? site.newt.newtId : null,
newtVersion: site.newt?.version ?? null
newtVersion: site.newt?.version ?? null,
countryCode: site.sites.endpoint
? ((await getCountryCodeForIp(site.sites.endpoint)) ?? null)
: null
};
return response<GetSiteResponse>(res, {

View File

@@ -9,6 +9,7 @@ import {
InfoSectionTitle
} from "@app/components/InfoSection";
import { useTranslations } from "next-intl";
import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji";
type SiteInfoCardProps = {};
@@ -52,7 +53,11 @@ export default function SiteInfoCard({}: SiteInfoCardProps) {
<InfoSection>
<InfoSectionTitle>{t("publicIpEndpoint")}</InfoSectionTitle>
<InfoSectionContent>
{formatPublicEndpoint(site.endpoint)}
{formatPublicEndpoint(site.endpoint)}&nbsp;
<span className="text-lg">
{site.countryCode &&
countryCodeToFlagEmoji(site.countryCode)}
</span>
</InfoSectionContent>
</InfoSection>
) : null;

View File

@@ -74,6 +74,7 @@ import {
sortPolicyRulesForResourceOverlay,
type PolicyAccessRule
} from "./policy-access-rule-utils";
import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji";
export type PolicyAccessRulesTableProps = {
rules: PolicyAccessRule[];
@@ -490,8 +491,17 @@ export function PolicyAccessRulesTable({
{
accessorKey: "value",
header: () => <span className="p-3">{t("value")}</span>,
cell: ({ row }) =>
row.original.match === "COUNTRY" ? (
cell: ({ row }) => {
let selectedCountry: (typeof COUNTRIES)[number] | undefined;
if (
row.original.match === "COUNTRY" &&
row.original.value
) {
selectedCountry = COUNTRIES.find(
(c) => c.code === row.original.value
);
}
return row.original.match === "COUNTRY" ? (
<Popover>
<PopoverTrigger asChild>
<Button
@@ -502,15 +512,22 @@ export function PolicyAccessRulesTable({
}
className="w-full min-w-0 justify-between"
>
{row.original.value
? COUNTRIES.find(
(c) =>
c.code === row.original.value
)?.name +
" (" +
row.original.value +
")"
: t("selectCountry")}
{selectedCountry ? (
<>
<span>
{selectedCountry.code === "ALL"
? "🌍"
: countryCodeToFlagEmoji(
selectedCountry.code
)}
&nbsp;&nbsp;
{selectedCountry.name} (
{selectedCountry.code})
</span>
</>
) : (
t("selectCountry")
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
@@ -540,6 +557,13 @@ export function PolicyAccessRulesTable({
<Check
className={`mr-2 h-4 w-4 ${row.original.value === country.code ? "opacity-100" : "opacity-0"}`}
/>
<span>
{country.code === "ALL"
? "🌍"
: countryCodeToFlagEmoji(
country.code
)}
</span>
{country.name} (
{country.code})
</CommandItem>
@@ -767,7 +791,8 @@ export function PolicyAccessRulesTable({
});
}}
/>
)
);
}
},
{
accessorKey: "enabled",