mirror of
https://github.com/perfect-panel/ppanel-web.git
synced 2026-02-06 03:30:25 -05:00
🐛 fix: Remove unnecessary comments and improve variable handling in GoTemplateEditor; ensure zero value displays as empty in EnhancedInput
This commit is contained in:
parent
6376ec1a79
commit
c4a47a47dd
@ -40,10 +40,6 @@ export const LABELS = {
|
||||
'mkcp': 'mKCP',
|
||||
'httpupgrade': 'HTTP Upgrade',
|
||||
'xhttp': 'XHTTP',
|
||||
// vless flow
|
||||
'xtls-rprx-direct': 'XTLS-RPRX-Direct',
|
||||
'xtls-rprx-splice': 'XTLS-RPRX-Splice',
|
||||
'xtls-rprx-vision': 'XTLS-RPRX-Vision',
|
||||
// security
|
||||
'none': 'NONE',
|
||||
'tls': 'TLS',
|
||||
@ -92,6 +88,8 @@ export const SECURITY = {
|
||||
vless: ['none', 'tls', 'reality'] as const,
|
||||
trojan: ['tls'] as const,
|
||||
hysteria2: ['tls'] as const,
|
||||
tuic: ['tls'] as const,
|
||||
anytls: ['tls'] as const,
|
||||
naive: ['none', 'tls'] as const,
|
||||
http: ['none', 'tls'] as const,
|
||||
} as const;
|
||||
@ -116,7 +114,8 @@ export const FINGERPRINTS = [
|
||||
export const multiplexLevels = ['off', 'low', 'middle', 'high'] as const;
|
||||
|
||||
export function getLabel(value: string): string {
|
||||
return (LABELS as Record<string, string>)[value] ?? value;
|
||||
const label = (LABELS as Record<string, string>)[value];
|
||||
return label ?? value.toUpperCase();
|
||||
}
|
||||
|
||||
const nullableString = z.string().nullish();
|
||||
@ -183,7 +182,6 @@ const hysteria2 = z.object({
|
||||
hop_ports: nullableString,
|
||||
hop_interval: z.number().nullish(),
|
||||
obfs_password: nullableString,
|
||||
host: nullableString,
|
||||
port: nullablePort,
|
||||
security: z.enum(SECURITY.hysteria2 as any).nullish(),
|
||||
sni: nullableString,
|
||||
@ -199,11 +197,22 @@ const tuic = z.object({
|
||||
reduce_rtt: z.boolean().nullish(),
|
||||
udp_relay_mode: z.enum(TUIC_UDP_RELAY_MODES as any).nullish(),
|
||||
congestion_controller: z.enum(TUIC_CONGESTION as any).nullish(),
|
||||
security: z.enum(SECURITY.tuic as any).nullish(),
|
||||
sni: nullableString,
|
||||
allow_insecure: nullableBool,
|
||||
fingerprint: nullableString,
|
||||
});
|
||||
|
||||
const anytls = z.object({
|
||||
type: z.literal('anytls'),
|
||||
port: nullablePort,
|
||||
security: z.enum(SECURITY.anytls as any).nullish(),
|
||||
sni: nullableString,
|
||||
allow_insecure: nullableBool,
|
||||
fingerprint: nullableString,
|
||||
padding_scheme: nullableString,
|
||||
});
|
||||
|
||||
const socks = z.object({
|
||||
type: z.literal('socks'),
|
||||
port: nullablePort,
|
||||
@ -234,15 +243,6 @@ const meru = z.object({
|
||||
transport: z.enum(TRANSPORTS.meru as any).nullish(),
|
||||
});
|
||||
|
||||
const anytls = z.object({
|
||||
type: z.literal('anytls'),
|
||||
port: nullablePort,
|
||||
sni: nullableString,
|
||||
allow_insecure: nullableBool,
|
||||
fingerprint: nullableString,
|
||||
padding_scheme: nullableString,
|
||||
});
|
||||
|
||||
export const protocolApiScheme = z.discriminatedUnion('type', [
|
||||
ss,
|
||||
vmess,
|
||||
@ -304,6 +304,10 @@ export function getProtocolDefaultConfig(proto: ProtocolType) {
|
||||
reduce_rtt: false,
|
||||
udp_relay_mode: 'native',
|
||||
congestion_controller: 'bbr',
|
||||
security: 'tls',
|
||||
sni: null,
|
||||
allow_insecure: false,
|
||||
fingerprint: 'chrome',
|
||||
} as any;
|
||||
case 'socks':
|
||||
return {
|
||||
@ -330,7 +334,15 @@ export function getProtocolDefaultConfig(proto: ProtocolType) {
|
||||
transport: 'tcp',
|
||||
} as any;
|
||||
case 'anytls':
|
||||
return { type: 'anytls', port: null, padding_scheme: null } as any;
|
||||
return {
|
||||
type: 'anytls',
|
||||
port: null,
|
||||
security: 'tls',
|
||||
padding_scheme: null,
|
||||
sni: null,
|
||||
allow_insecure: false,
|
||||
fingerprint: 'chrome',
|
||||
} as any;
|
||||
default:
|
||||
return {} as any;
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from '@workspace/ui/components/accordion';
|
||||
import { Badge } from '@workspace/ui/components/badge';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import {
|
||||
Form,
|
||||
@ -49,10 +50,6 @@ import {
|
||||
ServerFormValues,
|
||||
} from './form-schema';
|
||||
|
||||
function titleCase(s: string) {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
|
||||
function DynamicField({
|
||||
field,
|
||||
control,
|
||||
@ -196,6 +193,7 @@ function DynamicField({
|
||||
<FormControl>
|
||||
<textarea
|
||||
{...fieldProps}
|
||||
value={fieldProps.value ?? ''}
|
||||
className='border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex min-h-[80px] w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'
|
||||
placeholder={
|
||||
field.placeholder
|
||||
@ -287,7 +285,7 @@ export default function ServerForm(props: {
|
||||
const { trigger, title, loading, initialValues, onSubmit } = props;
|
||||
const t = useTranslations('servers');
|
||||
const [open, setOpen] = useState(false);
|
||||
const [protocolsEnabled, setProtocolsEnabled] = useState<string[]>([]);
|
||||
const [accordionValue, setAccordionValue] = useState<string>();
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(formSchema),
|
||||
@ -307,11 +305,6 @@ export default function ServerForm(props: {
|
||||
|
||||
useEffect(() => {
|
||||
if (initialValues) {
|
||||
const enabledProtocols = PROTOCOLS.filter((type) => {
|
||||
const protocol = initialValues.protocols?.find((p) => p.type === type);
|
||||
return protocol && protocol.port && Number(protocol.port) > 0;
|
||||
});
|
||||
setProtocolsEnabled(enabledProtocols);
|
||||
form.reset({
|
||||
name: '',
|
||||
address: '',
|
||||
@ -329,19 +322,11 @@ export default function ServerForm(props: {
|
||||
}, [initialValues]);
|
||||
|
||||
async function handleSubmit(values: Record<string, any>) {
|
||||
const filtered = (values?.protocols || [])
|
||||
.filter((p: any, index: number) => {
|
||||
const port = Number(p?.port);
|
||||
const protocolType = PROTOCOLS[index];
|
||||
return (
|
||||
protocolType &&
|
||||
protocolsEnabled.includes(protocolType) &&
|
||||
Number.isFinite(port) &&
|
||||
port > 0 &&
|
||||
port <= 65535
|
||||
);
|
||||
})
|
||||
.map((p: any) => ({ ...p, port: Number(p.port) }));
|
||||
const filtered = (values?.protocols || []).filter((p: any, index: number) => {
|
||||
const port = Number(p?.port);
|
||||
const protocolType = PROTOCOLS[index];
|
||||
return protocolType && p && Number.isFinite(port) && port > 0 && port <= 65535;
|
||||
});
|
||||
|
||||
if (filtered.length === 0) {
|
||||
toast.error(t('validation_failed'));
|
||||
@ -379,7 +364,6 @@ export default function ServerForm(props: {
|
||||
ratio: 1,
|
||||
protocols: full,
|
||||
});
|
||||
setProtocolsEnabled([]);
|
||||
}
|
||||
setOpen(true);
|
||||
}}
|
||||
@ -481,72 +465,66 @@ export default function ServerForm(props: {
|
||||
{t('protocol_configurations_desc')}
|
||||
</p>
|
||||
</div>
|
||||
<Accordion type='single' className='w-full space-y-3'>
|
||||
<Accordion
|
||||
type='single'
|
||||
collapsible
|
||||
className='w-full space-y-3'
|
||||
value={accordionValue}
|
||||
onValueChange={setAccordionValue}
|
||||
>
|
||||
{PROTOCOLS.map((type) => {
|
||||
const i = Math.max(
|
||||
0,
|
||||
PROTOCOLS.findIndex((t) => t === type),
|
||||
);
|
||||
const current = Array.isArray(protocolsValues) ? protocolsValues[i] || {} : {};
|
||||
const isEnabled = protocolsEnabled.includes(type);
|
||||
const current = (protocolsValues[i] || {}) as Record<string, any>;
|
||||
const isEnabled = current.port && Number(current.port) > 0;
|
||||
const fields = PROTOCOL_FIELDS[type] || [];
|
||||
|
||||
return (
|
||||
<AccordionItem key={type} value={type} className='mb-2 rounded-lg border'>
|
||||
<AccordionTrigger className='px-4 py-3 hover:no-underline'>
|
||||
<div className='flex w-full items-center justify-between'>
|
||||
<div className='flex flex-col items-start'>
|
||||
<span className='font-medium'>{titleCase(type)}</span>
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='font-medium capitalize'>{type}</span>
|
||||
</div>
|
||||
<span className='text-muted-foreground text-xs'>
|
||||
{isEnabled ? t('enabled') : t('disabled')}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center gap-3'>
|
||||
{isEnabled && (
|
||||
<div className='flex h-2 w-2 rounded-full bg-green-500'></div>
|
||||
<div className='mr-2 flex items-center gap-1'>
|
||||
{current.transport && (
|
||||
<Badge variant='secondary' className='text-xs'>
|
||||
{current.transport.toUpperCase()}
|
||||
</Badge>
|
||||
)}
|
||||
<Switch
|
||||
className='mr-2'
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
checked={isEnabled}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setProtocolsEnabled([...protocolsEnabled, type]);
|
||||
} else {
|
||||
setProtocolsEnabled(protocolsEnabled.filter((p) => p !== type));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{current.security && current.security !== 'none' && (
|
||||
<Badge variant='outline' className='text-xs'>
|
||||
{current.security.toUpperCase()}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{current.port && <Badge className='text-xs'>{current.port}</Badge>}
|
||||
</div>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
{isEnabled && (
|
||||
<AccordionContent className='px-4 pb-4 pt-0'>
|
||||
<div className='-mx-4 space-y-4 rounded-b-lg border-t px-4 pt-4'>
|
||||
{renderGroupCard('basic', fields, 'basic', control, i, current, t)}
|
||||
{renderGroupCard('plugin', fields, 'plugin', control, i, current, t)}
|
||||
{renderGroupCard(
|
||||
'transport',
|
||||
fields,
|
||||
'transport',
|
||||
control,
|
||||
i,
|
||||
current,
|
||||
t,
|
||||
)}
|
||||
{renderGroupCard(
|
||||
'security',
|
||||
fields,
|
||||
'security',
|
||||
control,
|
||||
i,
|
||||
current,
|
||||
t,
|
||||
)}
|
||||
{renderGroupCard('reality', fields, 'reality', control, i, current, t)}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
)}
|
||||
<AccordionContent className='px-4 pb-4 pt-0'>
|
||||
<div className='-mx-4 space-y-4 rounded-b-lg border-t px-4 pt-4'>
|
||||
{renderGroupCard('basic', fields, 'basic', control, i, current, t)}
|
||||
{renderGroupCard('plugin', fields, 'plugin', control, i, current, t)}
|
||||
{renderGroupCard(
|
||||
'transport',
|
||||
fields,
|
||||
'transport',
|
||||
control,
|
||||
i,
|
||||
current,
|
||||
t,
|
||||
)}
|
||||
{renderGroupCard('security', fields, 'security', control, i, current, t)}
|
||||
{renderGroupCard('reality', fields, 'reality', control, i, current, t)}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "Migrace dat se nezdařila",
|
||||
"migrated": "Data byla úspěšně migrována",
|
||||
"migrating": "Probíhá migrace...",
|
||||
"multiplex": "Multiplex",
|
||||
"name": "Název",
|
||||
"noData": "Žádná data",
|
||||
"notAvailable": "Není k dispozici",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "Datenmigration fehlgeschlagen",
|
||||
"migrated": "Daten erfolgreich migriert",
|
||||
"migrating": "Wird migriert...",
|
||||
"multiplex": "Multiplex",
|
||||
"name": "Name",
|
||||
"noData": "Keine Daten",
|
||||
"notAvailable": "Nicht verfügbar",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "Data migration failed",
|
||||
"migrated": "Data migrated successfully",
|
||||
"migrating": "Migrating...",
|
||||
"multiplex": "Multiplex",
|
||||
"name": "Name",
|
||||
"noData": "No data",
|
||||
"notAvailable": "N/A",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "La migración de datos falló",
|
||||
"migrated": "Datos migrados con éxito",
|
||||
"migrating": "Migrando...",
|
||||
"multiplex": "Multiplex",
|
||||
"name": "Nombre",
|
||||
"noData": "Sin datos",
|
||||
"notAvailable": "N/A",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "La migración de datos falló",
|
||||
"migrated": "Datos migrados con éxito",
|
||||
"migrating": "Migrando...",
|
||||
"multiplex": "Multiplex",
|
||||
"name": "Nombre",
|
||||
"noData": "Sin datos",
|
||||
"notAvailable": "N/A",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "انتقال داده ناموفق بود",
|
||||
"migrated": "داده با موفقیت منتقل شد",
|
||||
"migrating": "در حال انتقال...",
|
||||
"multiplex": "چندمنظوره",
|
||||
"name": "نام",
|
||||
"noData": "هیچ دادهای",
|
||||
"notAvailable": "غیرقابل دسترسی",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "Tietojen siirto epäonnistui",
|
||||
"migrated": "Tiedot siirretty onnistuneesti",
|
||||
"migrating": "Siirretään...",
|
||||
"multiplex": "Monistamo",
|
||||
"name": "Nimi",
|
||||
"noData": "Ei tietoja",
|
||||
"notAvailable": "Ei saatavilla",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "Échec de la migration des données",
|
||||
"migrated": "Données migrées avec succès",
|
||||
"migrating": "Migration en cours...",
|
||||
"multiplex": "Multiplex",
|
||||
"name": "Nom",
|
||||
"noData": "Aucune donnée",
|
||||
"notAvailable": "N/A",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "डेटा माइग्रेशन विफल",
|
||||
"migrated": "डेटा सफलतापूर्वक माइग्रेट किया गया",
|
||||
"migrating": "माइग्रेट किया जा रहा है...",
|
||||
"multiplex": "मल्टीप्लेक्स",
|
||||
"name": "नाम",
|
||||
"noData": "कोई डेटा नहीं",
|
||||
"notAvailable": "उपलब्ध नहीं",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "Az adatok migrálása sikertelen",
|
||||
"migrated": "Az adatok sikeresen migrálva",
|
||||
"migrating": "Migrálás...",
|
||||
"multiplex": "Multiplex",
|
||||
"name": "Név",
|
||||
"noData": "Nincs adat",
|
||||
"notAvailable": "N/A",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "データの移行に失敗しました",
|
||||
"migrated": "データが正常に移行されました",
|
||||
"migrating": "移行中...",
|
||||
"multiplex": "マルチプレックス",
|
||||
"name": "名前",
|
||||
"noData": "データなし",
|
||||
"notAvailable": "利用不可",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "데이터 마이그레이션 실패",
|
||||
"migrated": "데이터가 성공적으로 마이그레이션되었습니다",
|
||||
"migrating": "마이그레이션 중...",
|
||||
"multiplex": "멀티플렉스",
|
||||
"name": "이름",
|
||||
"noData": "데이터 없음",
|
||||
"notAvailable": "사용 불가",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "Datamigrering mislyktes",
|
||||
"migrated": "Data migrert med suksess",
|
||||
"migrating": "Migrerer...",
|
||||
"multiplex": "Multiplex",
|
||||
"name": "Navn",
|
||||
"noData": "Ingen data",
|
||||
"notAvailable": "Ikke tilgjengelig",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "Migracja danych nie powiodła się",
|
||||
"migrated": "Dane zostały pomyślnie zmigrowane",
|
||||
"migrating": "Migracja...",
|
||||
"multiplex": "Multiplex",
|
||||
"name": "Nazwa",
|
||||
"noData": "Brak danych",
|
||||
"notAvailable": "N/D",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "A migração de dados falhou",
|
||||
"migrated": "Dados migrados com sucesso",
|
||||
"migrating": "Migrando...",
|
||||
"multiplex": "Multiplex",
|
||||
"name": "Nome",
|
||||
"noData": "Sem dados",
|
||||
"notAvailable": "N/D",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "Migrarea datelor a eșuat",
|
||||
"migrated": "Datele au fost migrate cu succes",
|
||||
"migrating": "Se migrează...",
|
||||
"multiplex": "Multiplex",
|
||||
"name": "Nume",
|
||||
"noData": "Fără date",
|
||||
"notAvailable": "N/A",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "Ошибка при переносе данных",
|
||||
"migrated": "Данные успешно перенесены",
|
||||
"migrating": "Перенос данных...",
|
||||
"multiplex": "Мультиплекс",
|
||||
"name": "Имя",
|
||||
"noData": "Нет данных",
|
||||
"notAvailable": "Недоступно",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "การย้ายข้อมูลล้มเหลว",
|
||||
"migrated": "ย้ายข้อมูลสำเร็จ",
|
||||
"migrating": "กำลังย้ายข้อมูล...",
|
||||
"multiplex": "หลายช่อง",
|
||||
"name": "ชื่อ",
|
||||
"noData": "ไม่มีข้อมูล",
|
||||
"notAvailable": "ไม่สามารถใช้ได้",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "Veri taşıma işlemi başarısız oldu",
|
||||
"migrated": "Veri başarıyla taşındı",
|
||||
"migrating": "Taşınıyor...",
|
||||
"multiplex": "Çoklu Salon",
|
||||
"name": "İsim",
|
||||
"noData": "Veri yok",
|
||||
"notAvailable": "Mevcut değil",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "Міграція даних не вдалася",
|
||||
"migrated": "Дані успішно мігрували",
|
||||
"migrating": "Міграція...",
|
||||
"multiplex": "Мультиплекс",
|
||||
"name": "Ім'я",
|
||||
"noData": "Немає даних",
|
||||
"notAvailable": "Н/Д",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "Di chuyển dữ liệu thất bại",
|
||||
"migrated": "Dữ liệu đã được di chuyển thành công",
|
||||
"migrating": "Đang di chuyển...",
|
||||
"multiplex": "Rạp chiếu phim",
|
||||
"name": "Tên",
|
||||
"noData": "Không có dữ liệu",
|
||||
"notAvailable": "Không khả dụng",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "数据迁移失败",
|
||||
"migrated": "数据迁移成功",
|
||||
"migrating": "迁移中...",
|
||||
"multiplex": "多路复用",
|
||||
"name": "名称",
|
||||
"noData": "暂无数据",
|
||||
"notAvailable": "—",
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
"migrateFailed": "數據遷移失敗",
|
||||
"migrated": "數據已成功遷移",
|
||||
"migrating": "正在遷移...",
|
||||
"multiplex": "多元影院",
|
||||
"name": "名稱",
|
||||
"noData": "無數據",
|
||||
"notAvailable": "不可用",
|
||||
|
||||
@ -927,10 +927,8 @@ export function GoTemplateEditor({ schema, enableSprig = true, ...props }: GoTem
|
||||
let fieldPath = '';
|
||||
|
||||
if (isVarDot && varDotMatch) {
|
||||
// 处理变量后跟点的情况 ($n.)
|
||||
const variableName = varDotMatch[1];
|
||||
if (variableName === rangeVariable && activeRangeField) {
|
||||
// 使用activeRangeField作为字段路径
|
||||
fieldPath = activeRangeField;
|
||||
}
|
||||
} else if (dotMatches && dotMatches[1]) {
|
||||
|
||||
@ -99,7 +99,6 @@ export function EnhancedInput<T = string>({
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保0值显示为空
|
||||
if (value === '0') {
|
||||
setValue('');
|
||||
onValueBlur?.(processValue(0));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user