Add inbound country flags and node load in location picker
CI / Backend (build + test) (push) Failing after 1m36s
CI / Frontend (lint + typecheck + build) (push) Successful in 41s

- Inbound.CountryCode (ISO alpha-2), set on publish; flag shown before
  the location name (SVG via country-flag-icons on the site, emoji in bot)
- Node.Capacity (optional, not a limit); available inbounds report load
  percent of capacity or active config count, sorted least loaded first

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-06 19:45:17 +03:00
1 parent d7743977a1
commit bb5e59904f
51 files changed
+3160 -284

No files matched your search

@@ -11,8 +11,41 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
import { HttpError } from '@/shared/api/client'
import type { AvailableInboundDto } from '@/shared/api/types'
import { CountryFlag } from '@/shared/ui/country-flag'
import { cn } from '@/shared/lib/cn'
import { createConfig } from './api'
// Пороги «светофора» загрузки ноды, % от ёмкости.
const LOAD_MEDIUM = 50
const LOAD_HIGH = 80
function NodeLoad({ inbound }: { inbound: AvailableInboundDto }) {
const { t } = useTranslation()
if (inbound.loadPercent !== null) {
const level = inbound.loadPercent >= LOAD_HIGH ? 'high' : inbound.loadPercent >= LOAD_MEDIUM ? 'medium' : 'low'
return (
<span className="ml-auto flex items-center gap-1.5 pl-3 text-xs text-muted-foreground">
<span
className={cn(
'h-2 w-2 rounded-full',
level === 'high' ? 'bg-red-500' : level === 'medium' ? 'bg-amber-500' : 'bg-emerald-500',
)}
aria-hidden
/>
{t(`configs.load.${level}`)} · {inbound.loadPercent}%
</span>
)
}
if (inbound.nodeConfigCount !== null) {
return (
<span className="ml-auto pl-3 text-xs text-muted-foreground">
{t('configs.nodeConfigs', { count: inbound.nodeConfigCount })}
</span>
)
}
return null
}
export function CreateConfigDialog({ inbounds }: { inbounds: AvailableInboundDto[] }) {
const { t } = useTranslation()
const queryClient = useQueryClient()
@@ -74,7 +107,11 @@ export function CreateConfigDialog({ inbounds }: { inbounds: AvailableInboundDto
<SelectContent>
{inbounds.map((inbound) => (
<SelectItem key={inbound.inboundId} value={inbound.inboundId}>
{inbound.displayName} ({inbound.protocol})
<span className="flex w-full items-center gap-2">
<CountryFlag code={inbound.countryCode} />
{inbound.displayName} ({inbound.protocol})
<NodeLoad inbound={inbound} />
</span>
</SelectItem>
))}
</SelectContent>