Add load balancer marker for inbound country flag
Reserved CountryCode "XX" (Inbound.BalancerCountryCode) marks an inbound behind a load balancer whose exit country is unknown: the site shows a Shuffle icon with a tooltip instead of a flag, the bot shows 🔀. Admin can pick it in the publish dialog. Fits the existing column, no migration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
81562b5c56
commit
e979adca57
9 files changed
+48
-6
No files matched your search
@@ -8,7 +8,7 @@ import { Input } from '@/shared/ui/input'
|
||||
import { Label } from '@/shared/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
|
||||
import { CountryFlag } from '@/shared/ui/country-flag'
|
||||
import { COUNTRY_CODES, countryName } from '@/shared/lib/countries'
|
||||
import { BALANCER_COUNTRY_CODE, COUNTRY_CODES, countryName } from '@/shared/lib/countries'
|
||||
import { HttpError } from '@/shared/api/client'
|
||||
import { listRoles } from '@/features/admin/roles/api'
|
||||
import type { InboundDto } from '@/shared/api/types'
|
||||
@@ -94,6 +94,12 @@ export function PublishInboundDialog({
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={NO_COUNTRY}>{t('admin.nodes.noCountry')}</SelectItem>
|
||||
<SelectItem value={BALANCER_COUNTRY_CODE}>
|
||||
<span className="flex items-center gap-2">
|
||||
<CountryFlag code={BALANCER_COUNTRY_CODE} />
|
||||
{t('admin.nodes.balancerCountry')}
|
||||
</span>
|
||||
</SelectItem>
|
||||
{COUNTRY_CODES.map((code) => [code, countryName(code, i18n.language)] as const)
|
||||
.sort(([, a], [, b]) => a.localeCompare(b, i18n.language))
|
||||
.map(([code, name]) => (
|
||||
|
||||
@@ -17,6 +17,10 @@ export const FLAGS: Record<string, FlagComponent> = {
|
||||
|
||||
export const COUNTRY_CODES = Object.keys(FLAGS)
|
||||
|
||||
/** Зарезервированный код «инбаунд за балансировщиком, страна выхода неизвестна» (см.
|
||||
* Inbound.BalancerCountryCode на бэкенде) — вместо флага рисуется значок балансира. */
|
||||
export const BALANCER_COUNTRY_CODE = 'XX'
|
||||
|
||||
/** Локализованное имя страны по ISO-коду (Intl), без собственных переводов. */
|
||||
export function countryName(code: string, language: string): string {
|
||||
try {
|
||||
|
||||
@@ -13,6 +13,9 @@ const resources = {
|
||||
system: 'Системная',
|
||||
unlimited: 'Без лимита',
|
||||
|
||||
countries: {
|
||||
balancer: 'Через балансировщик — страна выхода может меняться',
|
||||
},
|
||||
auth: {
|
||||
loginTitle: 'Вход',
|
||||
registerTitle: 'Регистрация',
|
||||
@@ -487,6 +490,7 @@ const resources = {
|
||||
allowedRoles: 'Доступно ролям',
|
||||
country: 'Страна (флаг у пользователей)',
|
||||
noCountry: 'Без флага',
|
||||
balancerCountry: 'Балансировщик (страна выхода не определена)',
|
||||
isPublishedLabel: 'Опубликовать инбаунд',
|
||||
optional: 'необязательно',
|
||||
passwordKeepUnchanged: 'оставьте пустым, чтобы не менять',
|
||||
@@ -645,6 +649,9 @@ const resources = {
|
||||
system: 'System',
|
||||
unlimited: 'Unlimited',
|
||||
|
||||
countries: {
|
||||
balancer: 'Via load balancer — exit country may vary',
|
||||
},
|
||||
auth: {
|
||||
loginTitle: 'Log in',
|
||||
registerTitle: 'Register',
|
||||
@@ -1117,6 +1124,7 @@ const resources = {
|
||||
allowedRoles: 'Allowed for roles',
|
||||
country: 'Country (flag shown to users)',
|
||||
noCountry: 'No flag',
|
||||
balancerCountry: 'Load balancer (exit country varies)',
|
||||
isPublishedLabel: 'Publish inbound',
|
||||
optional: 'optional',
|
||||
passwordKeepUnchanged: 'leave blank to keep unchanged',
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
import { FLAGS } from '@/shared/lib/countries'
|
||||
import { Shuffle } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BALANCER_COUNTRY_CODE, FLAGS } from '@/shared/lib/countries'
|
||||
import { cn } from '@/shared/lib/cn'
|
||||
|
||||
export function CountryFlag({ code, className }: { code: string | null | undefined; className?: string }) {
|
||||
const { t } = useTranslation()
|
||||
if (!code) return null
|
||||
if (code.toUpperCase() === BALANCER_COUNTRY_CODE) {
|
||||
const label = t('countries.balancer')
|
||||
return (
|
||||
<span title={label} aria-label={label} className={cn('inline-flex shrink-0 text-sky-400', className)}>
|
||||
<Shuffle className="h-3.5 w-3.5" aria-hidden />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
const Flag = FLAGS[code.toUpperCase()]
|
||||
if (!Flag) {
|
||||
return <span className={cn('font-mono text-xs text-muted-foreground', className)}>{code.toUpperCase()}</span>
|
||||
|
||||
Reference in new issue
Block a user