Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,4 @@ deadlock-analysis.md
# AI
.AGENT
graphify-out
superpowers/
6 changes: 5 additions & 1 deletion dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
"name": "pasarguard-dashboard",
"private": true,
"version": "5.1.0",
"engines": {
"node": ">=22.18.0"
},
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"gen:api": "orval",
"wait-port": "wait-port http://localhost:$UVICORN_PORT/openapi.json",
"wait-port-gen-api": "bun run wait-port && bun run gen:api"
"wait-port-gen-api": "bun run wait-port && bun run gen:api",
"test": "node --test 'src/**/*.test.ts'"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
Expand Down
13 changes: 12 additions & 1 deletion dashboard/public/statics/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2164,6 +2164,9 @@
"excludedInbound": "Excluded inbound",
"selectInbound": "Select inbound",
"inbound": "Inbound",
"apiServices": "API Services",
"apiServiceAlwaysOn": "always on",
"apiServicesUnknown": "Unrecognized API service(s): {{names}}. Remove or fix them to save.",
"generateKeyPair": "Key Pair",
"generateShortId": "Short ID",
"backendType": "Type",
Expand Down Expand Up @@ -2283,6 +2286,7 @@
"balancers": "Balancers",
"dns": "DNS",
"bindings": "Bindings",
"api": "API",
"advanced": "Advanced",
"interface": "Interface"
},
Expand All @@ -2294,7 +2298,14 @@
"routing": "Traffic rules and routing",
"balancers": "Outbound load balancing setup",
"dns": "DNS resolver and server config",
"bindings": "Inbound tag fallbacks and exclusions"
"bindings": "Inbound tag fallbacks and exclusions",
"api": "Xray gRPC API services"
},
"api": {
"title": "API Services",
"hint": "gRPC API services exposed by this core. The node always enables the required services; enable optional ones as needed.",
"alwaysOn": "always on",
"unknown": "Unrecognized API service(s): {{names}}. Fix them in the Advanced tab."
},
"wg": {
"interfaceBlurb": "WireGuard interface settings and key material for this core.",
Expand Down
15 changes: 13 additions & 2 deletions dashboard/public/statics/locales/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@
"traffic": "نظارت بر مصرف ترافیک مدیر در طول زمان",
"no_traffic": "داده‌ای برای ترافیک موجود نیست"
},
"disabled": "???????"
"disabled": "غیرفعال"
},
"apiKeys": {
"title": "کلیدهای API",
Expand Down Expand Up @@ -2079,6 +2079,9 @@
"excludedInbound": "ورودی‌های مستثنی",
"selectInbound": "ورودی را انتخاب کنید",
"inbound": "ورودی",
"apiServices": "سرویس‌های API",
"apiServiceAlwaysOn": "همیشه فعال",
"apiServicesUnknown": "سرویس(های) API ناشناخته: {{names}}. برای ذخیره آن‌ها را حذف یا اصلاح کنید.",
"generateKeyPair": "جفت کلید",
"backendType": "نوع",
"generateWireGuardKeyPair": "جفت‌کلید WireGuard",
Expand Down Expand Up @@ -2197,6 +2200,7 @@
"balancers": "متعادل‌کننده‌ها",
"dns": "DNS",
"bindings": "اتصال‌ها",
"api": "API",
"advanced": "پیشرفته",
"interface": "رابط"
},
Expand All @@ -2208,7 +2212,14 @@
"routing": "قوانین مسیریابی و ترافیک",
"balancers": "استراتژی‌های متعادل‌سازی بار",
"dns": "پیکربندی DNS و حل‌کننده",
"bindings": "پشتیبان و مستثنا برای برچسب‌های ورودی"
"bindings": "پشتیبان و مستثنا برای برچسب‌های ورودی",
"api": "سرویس‌های gRPC API مربوط به Xray"
},
"api": {
"title": "سرویس‌های API",
"hint": "سرویس‌های gRPC API که این هسته ارائه می‌دهد. گره همیشه سرویس‌های موردنیاز را فعال می‌کند؛ موارد اختیاری را در صورت نیاز فعال کنید.",
"alwaysOn": "همیشه فعال",
"unknown": "سرویس(های) API ناشناخته: {{names}}. آن‌ها را در تب پیشرفته اصلاح کنید."
},
"wg": {
"interfaceBlurb": "تنظیمات رابط وایرگارد و مواد کلیدی این هسته.",
Expand Down
15 changes: 13 additions & 2 deletions dashboard/public/statics/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@
"traffic": "Мониторинг использования трафика администратором во времени",
"no_traffic": "Данные о трафике отсутствуют"
},
"disabled": "????????"
"disabled": "Отключен"
},
"apiKeys": {
"title": "API ключи",
Expand Down Expand Up @@ -2052,6 +2052,9 @@
"excludedInbound": "Исключенные входящие",
"selectInbound": "Выберите входящий",
"inbound": "Входящий",
"apiServices": "Сервисы API",
"apiServiceAlwaysOn": "всегда включён",
"apiServicesUnknown": "Нераспознанные сервисы API: {{names}}. Удалите или исправьте их, чтобы сохранить.",
"generateKeyPair": "Пара ключей",
"backendType": "Тип",
"generateWireGuardKeyPair": "Пара ключей WireGuard",
Expand Down Expand Up @@ -2170,6 +2173,7 @@
"balancers": "Балансировщики",
"dns": "DNS",
"bindings": "Привязки",
"api": "API",
"advanced": "Дополнительно",
"interface": "Интерфейс"
},
Expand All @@ -2181,7 +2185,14 @@
"routing": "Правила маршрутизации трафика",
"balancers": "Стратегии балансировки нагрузки",
"dns": "Настройки DNS и резолвера",
"bindings": "Резервные и исключённые теги входящих"
"bindings": "Резервные и исключённые теги входящих",
"api": "Сервисы gRPC API Xray"
},
"api": {
"title": "Сервисы API",
"hint": "Сервисы gRPC API, предоставляемые этим ядром. Узел всегда включает обязательные сервисы; необязательные включайте по мере необходимости.",
"alwaysOn": "всегда включён",
"unknown": "Нераспознанные сервисы API: {{names}}. Исправьте их на вкладке «Дополнительно»."
},
"wg": {
"interfaceBlurb": "Настройки интерфейса WireGuard и ключи для этого ядра.",
Expand Down
15 changes: 13 additions & 2 deletions dashboard/public/statics/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@
"traffic": "监控管理员使用情况",
"no_traffic": "没有使用情况数据"
},
"disabled": "??"
"disabled": "已禁用"
},
"apiKeys": {
"title": "API 密钥",
Expand Down Expand Up @@ -2123,6 +2123,9 @@
"excludedInbound": "排除的入站",
"selectInbound": "选择入站",
"inbound": "入站",
"apiServices": "API 服务",
"apiServiceAlwaysOn": "始终启用",
"apiServicesUnknown": "无法识别的 API 服务:{{names}}。请删除或修复后再保存。",
"generateKeyPair": "密钥对",
"backendType": "类型",
"generateWireGuardKeyPair": "WireGuard 密钥对",
Expand Down Expand Up @@ -2241,6 +2244,7 @@
"balancers": "负载均衡",
"dns": "DNS",
"bindings": "绑定",
"api": "API",
"advanced": "高级",
"interface": "接口"
},
Expand All @@ -2252,7 +2256,14 @@
"routing": "路由规则与流量策略",
"balancers": "负载均衡与调度策略",
"dns": "DNS 解析与解析器",
"bindings": "入站标签回退与排除"
"bindings": "入站标签回退与排除",
"api": "Xray gRPC API 服务"
},
"api": {
"title": "API 服务",
"hint": "此核心提供的 gRPC API 服务。节点始终启用必需的服务;可按需启用可选服务。",
"alwaysOn": "始终启用",
"unknown": "无法识别的 API 服务:{{names}}。请在“高级”选项卡中修复。"
},
Comment on lines +2262 to 2267

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the presence of the new `alwaysOn` and `unknown` keys across all locale files.

for file in dashboard/public/statics/locales/*.json; do
  echo "--- $file ---"
  jq '.coreEditor.api | {title, alwaysOn, unknown}' "$file"
done

Repository: PasarGuard/panel

Length of output: 620


🏁 Script executed:

rg -n '"(alwaysOn|unknown)"|coreEditor\.api|XrayApiSection' dashboard/public/statics/locales dashboard -g '*.json' -g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx'

Repository: PasarGuard/panel

Length of output: 1712


🏁 Script executed:

python3 - <<'PY'
import json, pathlib
for name in ['en','fa','ru','zh']:
    p = pathlib.Path('dashboard/public/statics/locales/%s.json' % name)
    data = json.loads(p.read_text(encoding='utf-8'))
    api = data.get('coreEditor', {}).get('api', {})
    print(f'--- {p} ---')
    for k in ['title','hint','alwaysOn','unknown']:
        print(k, '=>', api.get(k))
PY

Repository: PasarGuard/panel

Length of output: 1025


Add the missing coreEditor.api locale keys.
XrayApiSection reads coreEditor.api.alwaysOn and coreEditor.api.unknown, and they’re missing from the non-English locale files in this PR (fa.json, ru.json, zh.json), so those users will fall back to the English defaults.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/public/statics/locales/zh.json` around lines 2196 - 2199, Add the
missing coreEditor.api.alwaysOn and coreEditor.api.unknown translation keys to
the api locale sections in fa.json, ru.json, and zh.json, using appropriate
translations consistent with each locale’s existing terminology and the English
defaults.

"wg": {
"interfaceBlurb": "此核心的 WireGuard 接口与密钥设置。",
Expand Down
10 changes: 7 additions & 3 deletions dashboard/src/components/layout/page-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import { useLocation } from 'react-router'

interface PageHeaderProps {
title: string
/** Fallback text when the `title` i18n key is missing (e.g. a stale cached locale bundle). */
titleDefault?: string
description?: string
/** Fallback text when the `description` i18n key is missing. */
descriptionDefault?: string
buttonText?: string
onButtonClick?: () => void
buttonIcon?: LucideIcon
Expand All @@ -19,7 +23,7 @@ interface PageHeaderProps {
className?: string
}

export default function PageHeader({ title, description, buttonText, onButtonClick, buttonIcon: Icon = Plus, buttonTooltip, tutorialUrl, className }: PageHeaderProps) {
export default function PageHeader({ title, titleDefault, description, descriptionDefault, buttonText, onButtonClick, buttonIcon: Icon = Plus, buttonTooltip, tutorialUrl, className }: PageHeaderProps) {
const { t } = useTranslation()
const dir = useDirDetection()
const location = useLocation()
Expand All @@ -32,7 +36,7 @@ export default function PageHeader({ title, description, buttonText, onButtonCli
<Snowfall className="snowfall--header" />
<div className="relative z-10 flex min-w-0 flex-1 flex-col gap-y-1">
<div className="flex min-w-0 items-center gap-2.5">
<h1 className="truncate text-lg font-medium sm:text-xl">{t(title)}</h1>
<h1 className="truncate text-lg font-medium sm:text-xl">{t(title, titleDefault ? { defaultValue: titleDefault } : undefined)}</h1>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
Expand All @@ -52,7 +56,7 @@ export default function PageHeader({ title, description, buttonText, onButtonCli
</Tooltip>
</TooltipProvider>
</div>
{description && <span className="text-muted-foreground text-xs whitespace-normal sm:text-sm">{t(description)}</span>}
{description && <span className="text-muted-foreground text-xs whitespace-normal sm:text-sm">{t(description, descriptionDefault ? { defaultValue: descriptionDefault } : undefined)}</span>}
</div>
{buttonText && onButtonClick && (
<div className="relative z-10 shrink-0">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Checkbox } from '@/components/ui/checkbox'
import { useCoreEditorStore } from '@/features/core-editor/state/core-editor-store'
import { findUnknownApiServices, getSelectedOptional, OPTIONAL_API_SERVICES, REQUIRED_API_SERVICES, setRawOptionalService } from '@/lib/xray-api-services'
import type { Profile } from '@pasarguard/xray-config-kit'
import { Webhook } from 'lucide-react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'

// The structured editor keeps unmodeled top-level keys (incl. `api`) on
// `profile.raw.topLevel` (see UNMODELED_TOP_LEVEL_KEYS_TO_PRESERVE in xray-adapter.ts),
// which is structurally a config object for the api-services helpers.
function readTopLevel(profile: Profile): Record<string, unknown> {
return (profile.raw?.topLevel ?? {}) as Record<string, unknown>
}

function withApiService(profile: Profile, service: string, enabled: boolean): Profile {
return { ...profile, raw: setRawOptionalService(profile.raw, service, enabled) } as Profile
}

export function XrayApiSection() {
const { t } = useTranslation()
const profile = useCoreEditorStore(s => s.xrayProfile)
const updateXrayProfile = useCoreEditorStore(s => s.updateXrayProfile)

const { selected, unknown } = useMemo(() => {
const topLevel = profile ? readTopLevel(profile) : {}
return {
selected: getSelectedOptional(topLevel),
unknown: findUnknownApiServices(topLevel),
}
}, [profile])

if (!profile) return null

const toggle = (service: string, enabled: boolean) => {
updateXrayProfile(p => withApiService(p, service, enabled))
}

return (
<div className="space-y-6">
<Card>
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<Webhook className="text-muted-foreground h-4 w-4 shrink-0" aria-hidden />
{t('coreEditor.api.title', { defaultValue: 'API Services' })}
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<p className="text-muted-foreground text-xs">
{t('coreEditor.api.hint', {
defaultValue: 'gRPC API services exposed by this core. The node always enables the required services; enable optional ones as needed.',
})}
</p>
<div className="space-y-2 rounded-md border p-3">
{REQUIRED_API_SERVICES.map(svc => (
<label key={svc} className="flex items-center gap-2 opacity-60">
<Checkbox checked disabled />
<span className="text-sm">{svc}</span>
<span className="text-muted-foreground text-xs">{t('coreEditor.api.alwaysOn', { defaultValue: 'always on' })}</span>
</label>
))}
{OPTIONAL_API_SERVICES.map(svc => (
<label key={svc} className="flex cursor-pointer items-center gap-2">
<Checkbox checked={selected.includes(svc)} onCheckedChange={checked => toggle(svc, checked === true)} />
<span className="text-sm">{svc}</span>
</label>
))}
</div>
{unknown.length > 0 && (
<p className="text-destructive text-xs">
{t('coreEditor.api.unknown', {
defaultValue: 'Unrecognized API service(s): {{names}}. Fix them in the Advanced tab.',
names: unknown.join(', '),
})}
</p>
)}
</CardContent>
</Card>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { XrayInboundTagSelectors } from '@/features/core-editor/components/shared/xray-inbound-tag-selectors'
import { XrayAdvancedSection } from '@/features/core-editor/components/xray/xray-advanced-section'
import { XrayApiSection } from '@/features/core-editor/components/xray/xray-api-section'
import { XrayBalancersSection } from '@/features/core-editor/components/xray/xray-balancers-section'
import { XrayDnsSection } from '@/features/core-editor/components/xray/xray-dns-section'
import { XrayInboundsSection } from '@/features/core-editor/components/xray/xray-inbounds-section'
Expand Down Expand Up @@ -31,6 +32,7 @@ export function XrayCoreEditor({ headerAddPulse, headerAddEpoch }: XrayCoreEdito
{section === 'balancers' && <XrayBalancersSection headerAddPulse={headerAddPulse} headerAddEpoch={headerAddEpoch} />}
{section === 'dns' && <XrayDnsSection headerAddPulse={headerAddPulse} headerAddEpoch={headerAddEpoch} />}
{section === 'bindings' && <XrayInboundTagSelectors inboundTags={inboundTags} fallbackTags={fallbacks} excludedTags={excludes} onFallbackChange={setFallbacks} onExcludedChange={setExcludes} />}
{section === 'api' && <XrayApiSection />}
{section === 'advanced' && <XrayAdvancedSection />}
</div>
)
Expand Down
3 changes: 2 additions & 1 deletion dashboard/src/features/core-editor/kit/core-section-nav.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { LucideIcon } from 'lucide-react'
import { ArrowDownToLine, ArrowUpFromLine, Braces, Cable, Globe, Link2, Scale, Waypoints } from 'lucide-react'
import { ArrowDownToLine, ArrowUpFromLine, Braces, Cable, Globe, Link2, Scale, Waypoints, Webhook } from 'lucide-react'
import type { WgCoreSection, XrayCoreSection } from '@/features/core-editor/state/core-editor-store'

export type XraySectionNavItem = {
Expand All @@ -23,6 +23,7 @@ export const XRAY_CORE_SECTION_NAV: XraySectionNavItem[] = [
{ id: 'balancers', labelKey: 'coreEditor.section.balancers', defaultLabel: 'Balancers', icon: Scale },
{ id: 'dns', labelKey: 'coreEditor.section.dns', defaultLabel: 'DNS', icon: Globe },
{ id: 'bindings', labelKey: 'coreEditor.section.bindings', defaultLabel: 'Bindings', icon: Link2 },
{ id: 'api', labelKey: 'coreEditor.section.api', defaultLabel: 'API', icon: Webhook },
{ id: 'advanced', labelKey: 'coreEditor.section.advanced', defaultLabel: 'Advanced', icon: Braces },
]

Expand Down
Loading