Skip to content

Commit 3d8d4a2

Browse files
committed
feat(hosts): add pinnedPeerCertSha256 in ui
1 parent 5f655b3 commit 3d8d4a2

File tree

8 files changed

+52
-36
lines changed

8 files changed

+52
-36
lines changed

dashboard/public/statics/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,7 @@
986986
"sni.info": "SNI (Server Name Indication) is used to specify which hostname the client is trying to connect to. This is particularly useful when you have multiple domains pointing to the same IP address.",
987987
"useSniAsHost": "Use SNI as Host",
988988
"echConfigList": "ECH Config List",
989+
"pinnedPeerCertSha256": "Pinned Peer Cert SHA256",
989990
"echConfigList.info": "Encrypted Client Hello (ECH) configuration list. This is a base64 encoded string.",
990991
"echConfigListPlaceholder": "Enter ECH Config List",
991992
"httpVersions": {

dashboard/public/statics/locales/fa.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,7 @@
858858
"security.info": "اگر سرور واسط این هاست از لایه امنیتی متفاوتی نسبت به پیش‌فرض ورودی شما استفاده می‌کند، می‌توانید لایه امنیتی سفارشی را اینجا تنظیم کنید.",
859859
"useSniAsHost": "استفاده از SNI به عنوان هاست",
860860
"echConfigList": "لیست پیکربندی ECH",
861+
"pinnedPeerCertSha256": "SHA256 گواهی همتای پین‌شده",
861862
"echConfigList.info": "لیست پیکربندی Encrypted Client Hello (ECH). این یک رشته کدگذاری شده با base64 است.",
862863
"echConfigListPlaceholder": "لیست پیکربندی ECH را وارد کنید",
863864
"httpVersions": {

dashboard/public/statics/locales/ru.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,7 @@
970970
"randomUserAgent": "Использовать случайный User-Agent",
971971
"useSniAsHost": "Использовать SNI как хост",
972972
"echConfigList": "Список конфигураций ECH",
973+
"pinnedPeerCertSha256": "SHA256 закреплённого сертификата узла",
973974
"echConfigList.info": "Список конфигураций Encrypted Client Hello (ECH). Это строка, закодированная в base64.",
974975
"echConfigListPlaceholder": "Введите список конфигураций ECH",
975976
"inboundDefault": "По умолчанию",

dashboard/public/statics/locales/zh.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,7 @@
959959
"sni.info": "SNI (Server Name Indication) 用于指定客户端尝试连接的主机名。当您有多个域名指向同一个 IP 地址时,这特别有用。",
960960
"useSniAsHost": "使用 SNI 作为主机",
961961
"echConfigList": "ECH 配置列表",
962+
"pinnedPeerCertSha256": "固定对端证书 SHA256",
962963
"echConfigList.info": "Encrypted Client Hello (ECH) 配置列表。这是一个 base64 编码的字符串。",
963964
"echConfigListPlaceholder": "输入 ECH 配置列表",
964965
"httpVersions": {

dashboard/src/components/dialogs/host-modal.tsx

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,6 +1156,36 @@ const HostModal: React.FC<HostModalProps> = ({ isDialogOpen, onOpenChange, onSub
11561156
</FormItem>
11571157
)}
11581158
/>
1159+
1160+
<FormField
1161+
control={form.control}
1162+
name="pinnedPeerCertSha256"
1163+
render={({ field }) => (
1164+
<FormItem>
1165+
<div className="flex items-center gap-2">
1166+
<FormLabel>{t('hostsDialog.pinnedPeerCertSha256', { defaultValue: 'Pinned Peer Cert SHA256' })}</FormLabel>
1167+
<Popover>
1168+
<PopoverTrigger asChild>
1169+
<Button type="button" variant="ghost" size="icon" className="h-4 w-4 p-0 hover:bg-transparent">
1170+
<Info className="h-4 w-4 text-muted-foreground" />
1171+
</Button>
1172+
</PopoverTrigger>
1173+
<PopoverContent className="w-[320px] p-3" side="right" align="start" sideOffset={5}>
1174+
<p className="text-[11px] text-muted-foreground">
1175+
{t('hostsDialog.pinnedPeerCertSha256.info', {
1176+
defaultValue: 'Optional certificate public key pin (SHA-256) used for TLS peer pinning.',
1177+
})}
1178+
</p>
1179+
</PopoverContent>
1180+
</Popover>
1181+
</div>
1182+
<FormControl>
1183+
<Input maxLength={128} placeholder={t('hostsDialog.pinnedPeerCertSha256Placeholder', { defaultValue: 'Enter SHA-256 pin' })} {...field} value={field.value ?? ''} />
1184+
</FormControl>
1185+
<FormMessage />
1186+
</FormItem>
1187+
)}
1188+
/>
11591189
</div>
11601190
</AccordionContent>
11611191
</AccordionItem>
@@ -1248,10 +1278,7 @@ const HostModal: React.FC<HostModalProps> = ({ isDialogOpen, onOpenChange, onSub
12481278
render={({ field }) => (
12491279
<FormItem>
12501280
<FormLabel>{t('hostsDialog.xhttp.mode')}</FormLabel>
1251-
<Select
1252-
onValueChange={value => field.onChange(value === '__default' ? undefined : value)}
1253-
value={field.value ?? '__default'}
1254-
>
1281+
<Select onValueChange={value => field.onChange(value === '__default' ? undefined : value)} value={field.value ?? '__default'}>
12551282
<FormControl>
12561283
<SelectTrigger>
12571284
<SelectValue />
@@ -1349,7 +1376,6 @@ const HostModal: React.FC<HostModalProps> = ({ isDialogOpen, onOpenChange, onSub
13491376
</FormItem>
13501377
)}
13511378
/>
1352-
13531379
</div>
13541380

13551381
{xPaddingObfsEnabled ? (
@@ -1946,10 +1972,7 @@ const HostModal: React.FC<HostModalProps> = ({ isDialogOpen, onOpenChange, onSub
19461972
render={({ field }) => (
19471973
<FormItem className="col-span-2">
19481974
<FormLabel>{t('hostsDialog.tcp.header')}</FormLabel>
1949-
<Select
1950-
onValueChange={value => field.onChange(value === '__default' ? '' : value)}
1951-
value={field.value === '' || field.value == null ? '__default' : field.value}
1952-
>
1975+
<Select onValueChange={value => field.onChange(value === '__default' ? '' : value)} value={field.value === '' || field.value == null ? '__default' : field.value}>
19531976
<FormControl>
19541977
<SelectTrigger>
19551978
<SelectValue />

dashboard/src/components/hosts/hosts-list.tsx

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export interface HostFormValues {
8383
vless_route?: string
8484
priority: number
8585
ech_config_list?: string
86+
pinnedPeerCertSha256?: string
8687
fragment_settings?: {
8788
xray?: {
8889
packets?: string
@@ -339,12 +340,11 @@ export const HostFormSchema = z.object({
339340
allowinsecure: z.boolean().default(false),
340341
random_user_agent: z.boolean().default(false),
341342
use_sni_as_host: z.boolean().default(false),
342-
vless_route: z
343-
.union([z.literal(''), z.string().regex(/^[0-9a-fA-F]{4}$/, 'VLESS route must be exactly 4 hex characters')])
344-
.optional(),
343+
vless_route: z.union([z.literal(''), z.string().regex(/^[0-9a-fA-F]{4}$/, 'VLESS route must be exactly 4 hex characters')]).optional(),
345344
priority: z.number().default(0),
346345
is_disabled: z.boolean().default(false),
347346
ech_config_list: z.string().optional(),
347+
pinnedPeerCertSha256: z.string().max(128, 'Pinned peer cert SHA256 must be at most 128 characters').optional(),
348348
fragment_settings: z
349349
.object({
350350
xray: z
@@ -459,6 +459,7 @@ const initialDefaultValues: HostFormValues = {
459459
vless_route: '',
460460
priority: 0,
461461
ech_config_list: undefined,
462+
pinnedPeerCertSha256: undefined,
462463
fragment_settings: undefined,
463464
}
464465

@@ -538,6 +539,7 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
538539
priority: host.priority || 0,
539540
is_disabled: host.is_disabled || false,
540541
ech_config_list: host.ech_config_list || undefined,
542+
pinnedPeerCertSha256: host.pinnedPeerCertSha256 || undefined,
541543
fragment_settings: host.fragment_settings
542544
? {
543545
xray: host.fragment_settings.xray ?? undefined,
@@ -598,8 +600,7 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
598600
mode: host.transport_settings.xhttp_settings.mode ?? undefined,
599601
no_grpc_header: host.transport_settings.xhttp_settings.no_grpc_header === null ? undefined : !!host.transport_settings.xhttp_settings.no_grpc_header,
600602
x_padding_bytes: host.transport_settings.xhttp_settings.x_padding_bytes ?? undefined,
601-
x_padding_obfs_mode:
602-
host.transport_settings.xhttp_settings.x_padding_obfs_mode === null ? undefined : !!host.transport_settings.xhttp_settings.x_padding_obfs_mode,
603+
x_padding_obfs_mode: host.transport_settings.xhttp_settings.x_padding_obfs_mode === null ? undefined : !!host.transport_settings.xhttp_settings.x_padding_obfs_mode,
603604
x_padding_key: host.transport_settings.xhttp_settings.x_padding_key ?? undefined,
604605
x_padding_header: host.transport_settings.xhttp_settings.x_padding_header ?? undefined,
605606
x_padding_placement: host.transport_settings.xhttp_settings.x_padding_placement ?? undefined,
@@ -642,10 +643,7 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
642643
tti: host.transport_settings.kcp_settings.tti ?? undefined,
643644
uplink_capacity: host.transport_settings.kcp_settings.uplink_capacity ?? undefined,
644645
downlink_capacity: host.transport_settings.kcp_settings.downlink_capacity ?? undefined,
645-
congestion:
646-
host.transport_settings.kcp_settings.congestion === null
647-
? undefined
648-
: !!host.transport_settings.kcp_settings.congestion,
646+
congestion: host.transport_settings.kcp_settings.congestion === null ? undefined : !!host.transport_settings.kcp_settings.congestion,
649647
read_buffer_size: host.transport_settings.kcp_settings.read_buffer_size ?? undefined,
650648
write_buffer_size: host.transport_settings.kcp_settings.write_buffer_size ?? undefined,
651649
}
@@ -708,6 +706,7 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
708706
vless_route: host.vless_route || undefined,
709707
priority: host.priority ?? 0, // Use the same priority as the original host
710708
ech_config_list: host.ech_config_list,
709+
pinnedPeerCertSha256: host.pinnedPeerCertSha256 || undefined,
711710
fragment_settings: host.fragment_settings,
712711
noise_settings: host.noise_settings,
713712
mux_settings: host.mux_settings,
@@ -815,6 +814,7 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
815814
vless_route: host.vless_route || undefined,
816815
priority: index, // New priority based on position
817816
ech_config_list: host.ech_config_list,
817+
pinnedPeerCertSha256: host.pinnedPeerCertSha256 || undefined,
818818
fragment_settings: host.fragment_settings,
819819
noise_settings: host.noise_settings,
820820
mux_settings: host.mux_settings
@@ -860,8 +860,7 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
860860
mode: host.transport_settings.xhttp_settings.mode ?? undefined,
861861
no_grpc_header: host.transport_settings.xhttp_settings.no_grpc_header === null ? undefined : !!host.transport_settings.xhttp_settings.no_grpc_header,
862862
x_padding_bytes: host.transport_settings.xhttp_settings.x_padding_bytes ?? undefined,
863-
x_padding_obfs_mode:
864-
host.transport_settings.xhttp_settings.x_padding_obfs_mode === null ? undefined : !!host.transport_settings.xhttp_settings.x_padding_obfs_mode,
863+
x_padding_obfs_mode: host.transport_settings.xhttp_settings.x_padding_obfs_mode === null ? undefined : !!host.transport_settings.xhttp_settings.x_padding_obfs_mode,
865864
x_padding_key: host.transport_settings.xhttp_settings.x_padding_key ?? undefined,
866865
x_padding_header: host.transport_settings.xhttp_settings.x_padding_header ?? undefined,
867866
x_padding_placement: host.transport_settings.xhttp_settings.x_padding_placement ?? undefined,
@@ -904,10 +903,7 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
904903
tti: host.transport_settings.kcp_settings.tti ?? undefined,
905904
uplink_capacity: host.transport_settings.kcp_settings.uplink_capacity ?? undefined,
906905
downlink_capacity: host.transport_settings.kcp_settings.downlink_capacity ?? undefined,
907-
congestion:
908-
host.transport_settings.kcp_settings.congestion === null
909-
? undefined
910-
: !!host.transport_settings.kcp_settings.congestion,
906+
congestion: host.transport_settings.kcp_settings.congestion === null ? undefined : !!host.transport_settings.kcp_settings.congestion,
911907
read_buffer_size: host.transport_settings.kcp_settings.read_buffer_size ?? undefined,
912908
write_buffer_size: host.transport_settings.kcp_settings.write_buffer_size ?? undefined,
913909
}
@@ -1046,18 +1042,11 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
10461042
getRowId={host => host.id ?? host.remark ?? 'host'}
10471043
isLoading={isCurrentlyLoading}
10481044
loadingRows={6}
1049-
className="gap-3 max-w-screen-[2000px] min-h-screen overflow-hidden"
1045+
className="max-w-screen-[2000px] min-h-screen gap-3 overflow-hidden"
10501046
mode="grid"
10511047
showEmptyState={false}
10521048
renderGridItem={host => (
1053-
<SortableHost
1054-
key={host.id ?? 'new'}
1055-
host={host}
1056-
onEdit={handleEdit}
1057-
onDuplicate={handleDuplicate}
1058-
onDataChanged={refreshHostsData}
1059-
disabled={isUpdatingPriorities}
1060-
/>
1049+
<SortableHost key={host.id ?? 'new'} host={host} onEdit={handleEdit} onDuplicate={handleDuplicate} onDataChanged={refreshHostsData} disabled={isUpdatingPriorities} />
10611050
)}
10621051
renderGridSkeleton={index => (
10631052
<Card key={index} className="animate-pulse">
@@ -1087,7 +1076,7 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
10871076
getRowId={host => host.id ?? host.remark ?? 'host'}
10881077
isLoading={isCurrentlyLoading}
10891078
loadingRows={6}
1090-
className="gap-3 max-w-screen-[2000px] min-h-screen overflow-hidden"
1079+
className="max-w-screen-[2000px] min-h-screen gap-3 overflow-hidden"
10911080
mode="list"
10921081
showEmptyState={false}
10931082
onRowClick={handleEdit}

dashboard/src/pages/_dashboard._index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ const Dashboard = () => {
155155
is_disabled: false,
156156
random_user_agent: false,
157157
use_sni_as_host: false,
158+
pinnedPeerCertSha256: undefined,
158159
mux_settings: undefined,
159160
fragment_settings: undefined,
160161
},
@@ -376,5 +377,3 @@ const Dashboard = () => {
376377
}
377378

378379
export default Dashboard
379-
380-

dashboard/src/pages/_dashboard.hosts.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export default function HostsPage() {
5959
alpn: formData.alpn as ProxyHostALPN[] | undefined,
6060
fingerprint: formData.fingerprint as ProxyHostFingerprint | undefined,
6161
ech_config_list: formData.ech_config_list || undefined,
62+
pinnedPeerCertSha256: formData.pinnedPeerCertSha256 || undefined,
6263
vless_route: formData.vless_route || undefined,
6364
transport_settings: formData.transport_settings
6465
? {

0 commit comments

Comments
 (0)