-
Notifications
You must be signed in to change notification settings - Fork 401
feat(dashboard): select optional Xray API services in both core editors #662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
b63b612
d5ec192
d3d59e4
35444db
65f00c9
cf92bf7
f4114ca
e228375
1acea75
dc40d39
81fadea
441c179
7da7180
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -172,3 +172,4 @@ deadlock-analysis.md | |
| # AI | ||
| .AGENT | ||
| graphify-out | ||
| superpowers/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -745,7 +745,7 @@ | |
| "traffic": "监控管理员使用情况", | ||
| "no_traffic": "没有使用情况数据" | ||
| }, | ||
| "disabled": "??" | ||
| "disabled": "已禁用" | ||
| }, | ||
| "apiKeys": { | ||
| "title": "API 密钥", | ||
|
|
@@ -2123,6 +2123,9 @@ | |
| "excludedInbound": "排除的入站", | ||
| "selectInbound": "选择入站", | ||
| "inbound": "入站", | ||
| "apiServices": "API 服务", | ||
| "apiServiceAlwaysOn": "始终启用", | ||
| "apiServicesUnknown": "无法识别的 API 服务:{{names}}。请删除或修复后再保存。", | ||
| "generateKeyPair": "密钥对", | ||
| "backendType": "类型", | ||
| "generateWireGuardKeyPair": "WireGuard 密钥对", | ||
|
|
@@ -2241,6 +2244,7 @@ | |
| "balancers": "负载均衡", | ||
| "dns": "DNS", | ||
| "bindings": "绑定", | ||
| "api": "API", | ||
| "advanced": "高级", | ||
| "interface": "接口" | ||
| }, | ||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"
doneRepository: 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))
PYRepository: PasarGuard/panel Length of output: 1025 Add the missing 🤖 Prompt for AI Agents |
||
| "wg": { | ||
| "interfaceBlurb": "此核心的 WireGuard 接口与密钥设置。", | ||
|
|
||
| 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> | ||
| ) | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.