185 lines
5.8 KiB
Vue
185 lines
5.8 KiB
Vue
<script setup lang="ts">
|
||
import { computed, reactive, watch } from 'vue';
|
||
import {
|
||
NModal,
|
||
NForm,
|
||
NFormItem,
|
||
NInput,
|
||
NInputNumber,
|
||
NSelect,
|
||
NSwitch,
|
||
NButton,
|
||
NSpace,
|
||
useMessage,
|
||
} from 'naive-ui';
|
||
import { nodeInputSchema, PROTOCOLS, type NodeDto, type NodeInput } from '@proxy-station/shared';
|
||
|
||
const props = defineProps<{ show: boolean; node?: NodeDto | null }>();
|
||
const emit = defineEmits<{
|
||
(e: 'update:show', v: boolean): void;
|
||
(e: 'submit', input: NodeInput, id?: string): void;
|
||
(e: 'delete', id: string): void;
|
||
}>();
|
||
|
||
const message = useMessage();
|
||
|
||
const blank = (): NodeInput => ({
|
||
name: '',
|
||
protocol: 'vmess',
|
||
server: '',
|
||
port: 443,
|
||
network: 'ws',
|
||
wsPath: '/',
|
||
wsHost: null,
|
||
tls: true,
|
||
sni: null,
|
||
skipCertVerify: false,
|
||
uuid: null,
|
||
alterId: 0,
|
||
security: 'auto',
|
||
password: null,
|
||
method: 'chacha20-poly1305',
|
||
obfsType: null,
|
||
obfsPassword: null,
|
||
region: null,
|
||
tags: [],
|
||
enabled: true,
|
||
sortOrder: 0,
|
||
});
|
||
|
||
const form = reactive<NodeInput>(blank());
|
||
|
||
watch(
|
||
() => props.show,
|
||
(show) => {
|
||
if (!show) return;
|
||
Object.assign(form, blank(), props.node ?? {});
|
||
}
|
||
);
|
||
|
||
const isEdit = computed(() => !!props.node);
|
||
const protocolOptions = PROTOCOLS.map((p) => ({ label: p, value: p }));
|
||
const tagOptions = [
|
||
{ label: 'direct(直连出口)', value: 'direct' },
|
||
{ label: 'warp(WARP 出口)', value: 'warp' },
|
||
];
|
||
|
||
const showWs = computed(() => form.protocol !== 'hysteria2');
|
||
const showVmess = computed(() => form.protocol === 'vmess');
|
||
const showPassword = computed(() => form.protocol !== 'vmess');
|
||
const showSs = computed(() => form.protocol === 'shadowsocks');
|
||
const showHy2 = computed(() => form.protocol === 'hysteria2');
|
||
|
||
function submit() {
|
||
const result = nodeInputSchema.safeParse({ ...form });
|
||
if (!result.success) {
|
||
message.error(result.error.issues[0]?.message || '表单校验失败');
|
||
return;
|
||
}
|
||
emit('submit', { ...form }, props.node?.id);
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<NModal
|
||
:show="show"
|
||
preset="card"
|
||
:title="isEdit ? `编辑节点 · ${node?.name}` : '添加节点'"
|
||
style="max-width: 560px"
|
||
@update:show="emit('update:show', $event)"
|
||
>
|
||
<NForm label-placement="left" label-width="92">
|
||
<NFormItem label="名称" required>
|
||
<NInput v-model:value="form.name" placeholder="如 SG-vmess-warp" />
|
||
</NFormItem>
|
||
<NFormItem label="协议" required>
|
||
<NSelect v-model:value="form.protocol" :options="protocolOptions" :disabled="isEdit" />
|
||
</NFormItem>
|
||
<NFormItem label="服务器" required>
|
||
<NInput v-model:value="form.server" placeholder="域名或 IP" class="mono" />
|
||
</NFormItem>
|
||
<NFormItem label="端口" required>
|
||
<NInputNumber v-model:value="form.port" :min="1" :max="65535" style="width: 100%" />
|
||
</NFormItem>
|
||
|
||
<template v-if="showVmess">
|
||
<NFormItem label="UUID" required>
|
||
<NInput v-model:value="form.uuid" class="mono" />
|
||
</NFormItem>
|
||
</template>
|
||
<template v-if="showPassword">
|
||
<NFormItem label="密码" required>
|
||
<NInput v-model:value="form.password" type="password" show-password-on="click" class="mono" />
|
||
</NFormItem>
|
||
</template>
|
||
<template v-if="showSs">
|
||
<NFormItem label="加密方式" required>
|
||
<NInput v-model:value="form.method" placeholder="chacha20-poly1305" class="mono" />
|
||
</NFormItem>
|
||
</template>
|
||
<template v-if="showHy2">
|
||
<NFormItem label="混淆">
|
||
<NSelect
|
||
v-model:value="form.obfsType"
|
||
:options="[
|
||
{ label: '无', value: null as any },
|
||
{ label: 'salamander', value: 'salamander' },
|
||
]"
|
||
/>
|
||
</NFormItem>
|
||
<NFormItem v-if="form.obfsType === 'salamander'" label="混淆密码" required>
|
||
<NInput v-model:value="form.obfsPassword" type="password" show-password-on="click" class="mono" />
|
||
</NFormItem>
|
||
</template>
|
||
|
||
<template v-if="showWs">
|
||
<NFormItem label="传输">
|
||
<NSelect
|
||
v-model:value="form.network"
|
||
:options="[
|
||
{ label: 'TCP', value: 'tcp' },
|
||
{ label: 'WebSocket', value: 'ws' },
|
||
]"
|
||
/>
|
||
</NFormItem>
|
||
<template v-if="form.network === 'ws'">
|
||
<NFormItem label="WS 路径" required>
|
||
<NInput v-model:value="form.wsPath" placeholder="/xxxxxx" class="mono" />
|
||
</NFormItem>
|
||
<NFormItem label="WS Host">
|
||
<NInput v-model:value="form.wsHost" placeholder="默认与服务器相同" class="mono" />
|
||
</NFormItem>
|
||
</template>
|
||
</template>
|
||
|
||
<NFormItem label="TLS">
|
||
<NSwitch v-model:value="form.tls" :disabled="form.protocol === 'hysteria2'" />
|
||
</NFormItem>
|
||
<NFormItem v-if="form.tls" label="SNI">
|
||
<NInput v-model:value="form.sni" placeholder="默认与服务器相同" class="mono" />
|
||
</NFormItem>
|
||
|
||
<NFormItem label="出口标签">
|
||
<NSelect v-model:value="form.tags" multiple :options="tagOptions" placeholder="direct / warp,用于线路矩阵归位" />
|
||
</NFormItem>
|
||
<NFormItem label="站点代号">
|
||
<NInput v-model:value="form.region" placeholder="如 SG/LA/RN,留空则按域名推断" />
|
||
</NFormItem>
|
||
<NFormItem label="启用">
|
||
<NSwitch v-model:value="form.enabled" />
|
||
</NFormItem>
|
||
</NForm>
|
||
|
||
<template #footer>
|
||
<NSpace justify="space-between">
|
||
<NButton v-if="isEdit" type="error" quaternary @click="emit('delete', node!.id)">删除节点</NButton>
|
||
<span v-else />
|
||
<NSpace>
|
||
<NButton @click="emit('update:show', false)">取消</NButton>
|
||
<NButton type="primary" @click="submit">{{ isEdit ? '保存' : '添加' }}</NButton>
|
||
</NSpace>
|
||
</NSpace>
|
||
</template>
|
||
</NModal>
|
||
</template>
|