feat(xray/dns): align DNS settings with Xray docs + UI polish
- DNS server modal: rename expectIPs -> expectedIPs (per docs); add per-server tag, clientIP, serveStale, serveExpiredTTL, timeoutMs; flip skipFallback default to false; hydration still accepts legacy expectIPs for back-compat. - DNS tab: add hosts editor (domain -> IP/array), serveStale + serveExpiredTTL controls, "Use Preset" button bringing back the legacy preset gallery (Google / Cloudflare / AdGuard + Family variants — fixed AdGuard Family IPs that were wrong in legacy), and a "Delete All" button to wipe the server list at once. - i18n: add 15 new dns.* keys across all 13 locales. - Frontend-wide formatter pass on Vue components (whitespace and attribute layout only, no behavior changes). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -50,12 +50,12 @@ const prefix = props.basePath?.startsWith('/') ? props.basePath : `/${props.base
|
||||
// Labels are i18n-driven so the sidebar matches the locale picked
|
||||
// in panel settings without a page reload of the sidebar component.
|
||||
const tabs = computed(() => [
|
||||
{ key: `${prefix}panel/`, icon: 'dashboard', title: t('menu.dashboard') },
|
||||
{ key: `${prefix}panel/inbounds`, icon: 'user', title: t('menu.inbounds') },
|
||||
{ key: `${prefix}panel/nodes`, icon: 'cluster', title: t('menu.nodes') },
|
||||
{ key: `${prefix}panel/settings`, icon: 'setting', title: t('menu.settings') },
|
||||
{ key: `${prefix}panel/xray`, icon: 'tool', title: t('menu.xray') },
|
||||
{ key: `${prefix}logout`, icon: 'logout', title: t('logout') },
|
||||
{ key: `${prefix}panel/`, icon: 'dashboard', title: t('menu.dashboard') },
|
||||
{ key: `${prefix}panel/inbounds`, icon: 'user', title: t('menu.inbounds') },
|
||||
{ key: `${prefix}panel/nodes`, icon: 'cluster', title: t('menu.nodes') },
|
||||
{ key: `${prefix}panel/settings`, icon: 'setting', title: t('menu.settings') },
|
||||
{ key: `${prefix}panel/xray`, icon: 'tool', title: t('menu.xray') },
|
||||
{ key: `${prefix}logout`, icon: 'logout', title: t('logout') },
|
||||
]);
|
||||
|
||||
const activeTab = ref([props.requestUri]);
|
||||
@@ -90,20 +90,9 @@ function closeDrawer() {
|
||||
|
||||
<template>
|
||||
<div class="ant-sidebar">
|
||||
<a-layout-sider
|
||||
:theme="currentTheme"
|
||||
collapsible
|
||||
:collapsed="collapsed"
|
||||
breakpoint="md"
|
||||
@collapse="onCollapse"
|
||||
>
|
||||
<a-layout-sider :theme="currentTheme" collapsible :collapsed="collapsed" breakpoint="md" @collapse="onCollapse">
|
||||
<ThemeSwitch />
|
||||
<a-menu
|
||||
:theme="currentTheme"
|
||||
mode="inline"
|
||||
:selected-keys="activeTab"
|
||||
@click="({ key }) => openLink(key)"
|
||||
>
|
||||
<a-menu :theme="currentTheme" mode="inline" :selected-keys="activeTab" @click="({ key }) => openLink(key)">
|
||||
<a-menu-item v-for="tab in tabs" :key="tab.key">
|
||||
<component :is="iconByName[tab.icon]" />
|
||||
<span>{{ tab.title }}</span>
|
||||
@@ -111,22 +100,10 @@ function closeDrawer() {
|
||||
</a-menu>
|
||||
</a-layout-sider>
|
||||
|
||||
<a-drawer
|
||||
placement="left"
|
||||
:closable="false"
|
||||
:open="drawerOpen"
|
||||
:wrap-class-name="currentTheme"
|
||||
:wrap-style="{ padding: 0 }"
|
||||
:style="{ height: '100%' }"
|
||||
@close="closeDrawer"
|
||||
>
|
||||
<a-drawer placement="left" :closable="false" :open="drawerOpen" :wrap-class-name="currentTheme"
|
||||
:wrap-style="{ padding: 0 }" :style="{ height: '100%' }" @close="closeDrawer">
|
||||
<ThemeSwitch />
|
||||
<a-menu
|
||||
:theme="currentTheme"
|
||||
mode="inline"
|
||||
:selected-keys="activeTab"
|
||||
@click="({ key }) => openLink(key)"
|
||||
>
|
||||
<a-menu :theme="currentTheme" mode="inline" :selected-keys="activeTab" @click="({ key }) => openLink(key)">
|
||||
<a-menu-item v-for="tab in tabs" :key="tab.key">
|
||||
<component :is="iconByName[tab.icon]" />
|
||||
<span>{{ tab.title }}</span>
|
||||
@@ -142,7 +119,7 @@ function closeDrawer() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ant-sidebar > .ant-layout-sider {
|
||||
.ant-sidebar>.ant-layout-sider {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -171,12 +148,12 @@ function closeDrawer() {
|
||||
/* On mobile the drawer is the menu — hide the inline sider's content
|
||||
* + the collapse trigger so the sider stops taking layout space and
|
||||
* leaves no remnant button next to the page. */
|
||||
.ant-sidebar > .ant-layout-sider :deep(.ant-layout-sider-children),
|
||||
.ant-sidebar > .ant-layout-sider :deep(.ant-layout-sider-trigger) {
|
||||
.ant-sidebar>.ant-layout-sider :deep(.ant-layout-sider-children),
|
||||
.ant-sidebar>.ant-layout-sider :deep(.ant-layout-sider-trigger) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-sidebar > .ant-layout-sider {
|
||||
.ant-sidebar>.ant-layout-sider {
|
||||
flex: 0 0 0 !important;
|
||||
max-width: 0 !important;
|
||||
min-width: 0 !important;
|
||||
|
||||
@@ -7,8 +7,12 @@ defineProps({
|
||||
|
||||
<template>
|
||||
<a-statistic :title="title" :value="value">
|
||||
<template #prefix><slot name="prefix" /></template>
|
||||
<template #suffix><slot name="suffix" /></template>
|
||||
<template #prefix>
|
||||
<slot name="prefix" />
|
||||
</template>
|
||||
<template #suffix>
|
||||
<slot name="suffix" />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -51,29 +51,11 @@ function onAntChange(next) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PersianDatePicker
|
||||
v-if="isJalali"
|
||||
v-model="stringValue"
|
||||
:format="ISO_FORMAT"
|
||||
:display-format="persianDisplayFormat"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
color="#1677ff"
|
||||
auto-submit
|
||||
append-to="body"
|
||||
input-class="ant-input persian-datepicker-input"
|
||||
class="jalali-datepicker"
|
||||
/>
|
||||
<a-date-picker
|
||||
v-else
|
||||
:value="value"
|
||||
:show-time="showTime ? { format: 'HH:mm:ss' } : false"
|
||||
:format="format"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:style="{ width: '100%' }"
|
||||
@update:value="onAntChange"
|
||||
/>
|
||||
<PersianDatePicker v-if="isJalali" v-model="stringValue" :format="ISO_FORMAT" :display-format="persianDisplayFormat"
|
||||
:placeholder="placeholder" :disabled="disabled" color="#1677ff" auto-submit append-to="body"
|
||||
input-class="ant-input persian-datepicker-input" class="jalali-datepicker" />
|
||||
<a-date-picker v-else :value="value" :show-time="showTime ? { format: 'HH:mm:ss' } : false" :format="format"
|
||||
:placeholder="placeholder" :disabled="disabled" :style="{ width: '100%' }" @update:value="onAntChange" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -142,8 +124,8 @@ function onAntChange(next) {
|
||||
background: #fff;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||
0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -166,7 +148,7 @@ function onAntChange(next) {
|
||||
}
|
||||
|
||||
.vpd-wrapper .vpd-body .vpd-month-label,
|
||||
.vpd-wrapper .vpd-body .vpd-month-label > span {
|
||||
.vpd-wrapper .vpd-body .vpd-month-label>span {
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
}
|
||||
|
||||
@@ -271,8 +253,8 @@ body.dark .vpd-wrapper .vpd-content {
|
||||
background: #1a2c4d;
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.32),
|
||||
0 3px 6px -4px rgba(0, 0, 0, 0.48),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.2);
|
||||
0 3px 6px -4px rgba(0, 0, 0, 0.48),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
body.dark .vpd-wrapper .vpd-body {
|
||||
@@ -281,7 +263,7 @@ body.dark .vpd-wrapper .vpd-body {
|
||||
}
|
||||
|
||||
body.dark .vpd-wrapper .vpd-body .vpd-month-label,
|
||||
body.dark .vpd-wrapper .vpd-body .vpd-month-label > span {
|
||||
body.dark .vpd-wrapper .vpd-body .vpd-month-label>span {
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,27 +66,23 @@ function newNoiseItem() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-form
|
||||
v-if="showTcp || showUdp || showQuic"
|
||||
:colon="false"
|
||||
:label-col="{ md: { span: 8 } }"
|
||||
:wrapper-col="{ md: { span: 14 } }"
|
||||
>
|
||||
<a-form v-if="showTcp || showUdp || showQuic" :colon="false" :label-col="{ md: { span: 8 } }"
|
||||
:wrapper-col="{ md: { span: 14 } }">
|
||||
<!-- ============================== TCP MASKS ============================== -->
|
||||
<template v-if="showTcp">
|
||||
<a-form-item label="TCP Masks">
|
||||
<a-button type="primary" size="small" @click="stream.addTcpMask('fragment')">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
|
||||
<template v-for="(mask, mIdx) in (stream.finalmask.tcp || [])" :key="`tcp-${mIdx}`">
|
||||
<a-divider :style="{ margin: '0' }">
|
||||
TCP Mask {{ mIdx + 1 }}
|
||||
<DeleteOutlined
|
||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="stream.delTcpMask(mIdx)"
|
||||
/>
|
||||
<DeleteOutlined :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="stream.delTcpMask(mIdx)" />
|
||||
</a-divider>
|
||||
|
||||
<a-form-item label="Type">
|
||||
@@ -144,16 +140,16 @@ function newNoiseItem() {
|
||||
<!-- Clients -->
|
||||
<a-form-item label="Clients">
|
||||
<a-button type="primary" size="small" @click="mask.settings.clients.push([newClientServerItem()])">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
<template v-for="(group, gi) in mask.settings.clients" :key="`tcp-cg-${mIdx}-${gi}`">
|
||||
<a-divider :style="{ margin: '0' }">
|
||||
Clients Group {{ gi + 1 }}
|
||||
<DeleteOutlined
|
||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="mask.settings.clients.splice(gi, 1)"
|
||||
/>
|
||||
<DeleteOutlined :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="mask.settings.clients.splice(gi, 1)" />
|
||||
</a-divider>
|
||||
<template v-for="(item, ii) in group" :key="`tcp-ci-${mIdx}-${gi}-${ii}`">
|
||||
<a-form-item label="Type">
|
||||
@@ -177,13 +173,12 @@ function newNoiseItem() {
|
||||
</template>
|
||||
<a-form-item v-else label="Packet">
|
||||
<a-input-group v-if="item.type === 'base64'" compact>
|
||||
<a-input
|
||||
v-model:value="item.packet"
|
||||
placeholder="binary data"
|
||||
:style="{ width: 'calc(100% - 32px)' }"
|
||||
/>
|
||||
<a-input v-model:value="item.packet" placeholder="binary data"
|
||||
:style="{ width: 'calc(100% - 32px)' }" />
|
||||
<a-button @click="item.packet = RandomUtil.randomBase64()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<a-input v-else v-model:value="item.packet" placeholder="binary data" />
|
||||
@@ -194,16 +189,16 @@ function newNoiseItem() {
|
||||
<!-- Servers -->
|
||||
<a-form-item label="Servers">
|
||||
<a-button type="primary" size="small" @click="mask.settings.servers.push([newClientServerItem()])">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
<template v-for="(group, gi) in mask.settings.servers" :key="`tcp-sg-${mIdx}-${gi}`">
|
||||
<a-divider :style="{ margin: '0' }">
|
||||
Servers Group {{ gi + 1 }}
|
||||
<DeleteOutlined
|
||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="mask.settings.servers.splice(gi, 1)"
|
||||
/>
|
||||
<DeleteOutlined :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="mask.settings.servers.splice(gi, 1)" />
|
||||
</a-divider>
|
||||
<template v-for="(item, ii) in group" :key="`tcp-si-${mIdx}-${gi}-${ii}`">
|
||||
<a-form-item label="Type">
|
||||
@@ -227,13 +222,12 @@ function newNoiseItem() {
|
||||
</template>
|
||||
<a-form-item v-else label="Packet">
|
||||
<a-input-group v-if="item.type === 'base64'" compact>
|
||||
<a-input
|
||||
v-model:value="item.packet"
|
||||
placeholder="binary data"
|
||||
:style="{ width: 'calc(100% - 32px)' }"
|
||||
/>
|
||||
<a-input v-model:value="item.packet" placeholder="binary data"
|
||||
:style="{ width: 'calc(100% - 32px)' }" />
|
||||
<a-button @click="item.packet = RandomUtil.randomBase64()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<a-input v-else v-model:value="item.packet" placeholder="binary data" />
|
||||
@@ -248,17 +242,17 @@ function newNoiseItem() {
|
||||
<template v-if="showUdp">
|
||||
<a-form-item label="UDP Masks">
|
||||
<a-button type="primary" size="small" @click="addUdpMaskWithDefault">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
|
||||
<template v-for="(mask, mIdx) in (stream.finalmask.udp || [])" :key="`udp-${mIdx}`">
|
||||
<a-divider :style="{ margin: '0' }">
|
||||
UDP Mask {{ mIdx + 1 }}
|
||||
<DeleteOutlined
|
||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="stream.delUdpMask(mIdx)"
|
||||
/>
|
||||
<DeleteOutlined :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="stream.delUdpMask(mIdx)" />
|
||||
</a-divider>
|
||||
|
||||
<a-form-item label="Type">
|
||||
@@ -290,13 +284,8 @@ function newNoiseItem() {
|
||||
<a-input v-model:value="mask.settings.domain" placeholder="e.g., www.example.com" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="mask.type === 'xdns'" label="Domains">
|
||||
<a-select
|
||||
v-model:value="mask.settings.domains"
|
||||
mode="tags"
|
||||
:style="{ width: '100%' }"
|
||||
:token-separators="[',']"
|
||||
placeholder="e.g., www.example.com"
|
||||
/>
|
||||
<a-select v-model:value="mask.settings.domains" mode="tags" :style="{ width: '100%' }"
|
||||
:token-separators="[',']" placeholder="e.g., www.example.com" />
|
||||
</a-form-item>
|
||||
|
||||
<!-- Noise -->
|
||||
@@ -306,16 +295,16 @@ function newNoiseItem() {
|
||||
</a-form-item>
|
||||
<a-form-item label="Noise">
|
||||
<a-button type="primary" size="small" @click="mask.settings.noise.push(newNoiseItem())">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
<template v-for="(n, ni) in mask.settings.noise" :key="`udp-noise-${mIdx}-${ni}`">
|
||||
<a-divider :style="{ margin: '0' }">
|
||||
Noise {{ ni + 1 }}
|
||||
<DeleteOutlined
|
||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="mask.settings.noise.splice(ni, 1)"
|
||||
/>
|
||||
<DeleteOutlined :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="mask.settings.noise.splice(ni, 1)" />
|
||||
</a-divider>
|
||||
<a-form-item label="Type">
|
||||
<a-select :value="n.type" @change="(t) => changeItemType(n, t)">
|
||||
@@ -335,13 +324,11 @@ function newNoiseItem() {
|
||||
</template>
|
||||
<a-form-item v-else label="Packet">
|
||||
<a-input-group v-if="n.type === 'base64'" compact>
|
||||
<a-input
|
||||
v-model:value="n.packet"
|
||||
placeholder="binary data"
|
||||
:style="{ width: 'calc(100% - 32px)' }"
|
||||
/>
|
||||
<a-input v-model:value="n.packet" placeholder="binary data" :style="{ width: 'calc(100% - 32px)' }" />
|
||||
<a-button @click="n.packet = RandomUtil.randomBase64()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<a-input v-else v-model:value="n.packet" placeholder="binary data" />
|
||||
@@ -356,16 +343,16 @@ function newNoiseItem() {
|
||||
<template v-if="mask.type === 'header-custom'">
|
||||
<a-form-item label="Client">
|
||||
<a-button type="primary" size="small" @click="mask.settings.client.push(newUdpClientServerItem())">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
<template v-for="(c, ci) in mask.settings.client" :key="`udp-c-${mIdx}-${ci}`">
|
||||
<a-divider :style="{ margin: '0' }">
|
||||
Client {{ ci + 1 }}
|
||||
<DeleteOutlined
|
||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="mask.settings.client.splice(ci, 1)"
|
||||
/>
|
||||
<DeleteOutlined :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="mask.settings.client.splice(ci, 1)" />
|
||||
</a-divider>
|
||||
<a-form-item label="Type">
|
||||
<a-select :value="c.type" @change="(t) => changeItemType(c, t)">
|
||||
@@ -385,13 +372,11 @@ function newNoiseItem() {
|
||||
</template>
|
||||
<a-form-item v-else label="Packet">
|
||||
<a-input-group v-if="c.type === 'base64'" compact>
|
||||
<a-input
|
||||
v-model:value="c.packet"
|
||||
placeholder="binary data"
|
||||
:style="{ width: 'calc(100% - 32px)' }"
|
||||
/>
|
||||
<a-input v-model:value="c.packet" placeholder="binary data" :style="{ width: 'calc(100% - 32px)' }" />
|
||||
<a-button @click="c.packet = RandomUtil.randomBase64()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<a-input v-else v-model:value="c.packet" placeholder="binary data" />
|
||||
@@ -401,16 +386,16 @@ function newNoiseItem() {
|
||||
<a-divider :style="{ margin: '0' }" />
|
||||
<a-form-item label="Server">
|
||||
<a-button type="primary" size="small" @click="mask.settings.server.push(newUdpClientServerItem())">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
<template v-for="(s, si) in mask.settings.server" :key="`udp-s-${mIdx}-${si}`">
|
||||
<a-divider :style="{ margin: '0' }">
|
||||
Server {{ si + 1 }}
|
||||
<DeleteOutlined
|
||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="mask.settings.server.splice(si, 1)"
|
||||
/>
|
||||
<DeleteOutlined :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer', marginLeft: '8px' }"
|
||||
@click="mask.settings.server.splice(si, 1)" />
|
||||
</a-divider>
|
||||
<a-form-item label="Type">
|
||||
<a-select :value="s.type" @change="(t) => changeItemType(s, t)">
|
||||
@@ -430,13 +415,11 @@ function newNoiseItem() {
|
||||
</template>
|
||||
<a-form-item v-else label="Packet">
|
||||
<a-input-group v-if="s.type === 'base64'" compact>
|
||||
<a-input
|
||||
v-model:value="s.packet"
|
||||
placeholder="binary data"
|
||||
:style="{ width: 'calc(100% - 32px)' }"
|
||||
/>
|
||||
<a-input v-model:value="s.packet" placeholder="binary data" :style="{ width: 'calc(100% - 32px)' }" />
|
||||
<a-button @click="s.packet = RandomUtil.randomBase64()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<a-input v-else v-model:value="s.packet" placeholder="binary data" />
|
||||
@@ -502,39 +485,24 @@ function newNoiseItem() {
|
||||
<a-switch v-model:checked="stream.finalmask.quicParams.disablePathMTUDiscovery" />
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Incoming Streams">
|
||||
<a-input-number
|
||||
v-model:value="stream.finalmask.quicParams.maxIncomingStreams"
|
||||
:min="8"
|
||||
placeholder="1024 = default"
|
||||
/>
|
||||
<a-input-number v-model:value="stream.finalmask.quicParams.maxIncomingStreams" :min="8"
|
||||
placeholder="1024 = default" />
|
||||
</a-form-item>
|
||||
<a-form-item label="Init Stream Window">
|
||||
<a-input-number
|
||||
v-model:value="stream.finalmask.quicParams.initStreamReceiveWindow"
|
||||
:min="16384"
|
||||
placeholder="8388608 = default"
|
||||
/>
|
||||
<a-input-number v-model:value="stream.finalmask.quicParams.initStreamReceiveWindow" :min="16384"
|
||||
placeholder="8388608 = default" />
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Stream Window">
|
||||
<a-input-number
|
||||
v-model:value="stream.finalmask.quicParams.maxStreamReceiveWindow"
|
||||
:min="16384"
|
||||
placeholder="8388608 = default"
|
||||
/>
|
||||
<a-input-number v-model:value="stream.finalmask.quicParams.maxStreamReceiveWindow" :min="16384"
|
||||
placeholder="8388608 = default" />
|
||||
</a-form-item>
|
||||
<a-form-item label="Init Conn Window">
|
||||
<a-input-number
|
||||
v-model:value="stream.finalmask.quicParams.initConnectionReceiveWindow"
|
||||
:min="16384"
|
||||
placeholder="20971520 = default"
|
||||
/>
|
||||
<a-input-number v-model:value="stream.finalmask.quicParams.initConnectionReceiveWindow" :min="16384"
|
||||
placeholder="20971520 = default" />
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Conn Window">
|
||||
<a-input-number
|
||||
v-model:value="stream.finalmask.quicParams.maxConnectionReceiveWindow"
|
||||
:min="16384"
|
||||
placeholder="20971520 = default"
|
||||
/>
|
||||
<a-input-number v-model:value="stream.finalmask.quicParams.maxConnectionReceiveWindow" :min="16384"
|
||||
placeholder="20971520 = default" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -10,16 +10,9 @@ defineProps({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="width"
|
||||
:height="height"
|
||||
viewBox="0 0 640 512"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
style="vertical-align: -1px; display: inline-block;"
|
||||
>
|
||||
<svg :width="width" :height="height" viewBox="0 0 640 512" fill="currentColor" aria-hidden="true"
|
||||
style="vertical-align: -1px; display: inline-block;">
|
||||
<path
|
||||
d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"
|
||||
/>
|
||||
d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
@@ -43,28 +43,10 @@ function onKeydown(e) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-modal
|
||||
:open="open"
|
||||
:title="title"
|
||||
:ok-text="okText"
|
||||
cancel-text="Cancel"
|
||||
:mask-closable="false"
|
||||
:confirm-loading="loading"
|
||||
@ok="ok"
|
||||
@cancel="close"
|
||||
>
|
||||
<a-textarea
|
||||
v-if="type === 'textarea'"
|
||||
v-model:value="value"
|
||||
:auto-size="{ minRows: 10, maxRows: 20 }"
|
||||
autofocus
|
||||
@keydown="onKeydown"
|
||||
/>
|
||||
<a-input
|
||||
v-else
|
||||
v-model:value="value"
|
||||
autofocus
|
||||
@keydown="onKeydown"
|
||||
/>
|
||||
<a-modal :open="open" :title="title" :ok-text="okText" cancel-text="Cancel" :mask-closable="false"
|
||||
:confirm-loading="loading" @ok="ok" @cancel="close">
|
||||
<a-textarea v-if="type === 'textarea'" v-model:value="value" :auto-size="{ minRows: 10, maxRows: 20 }" autofocus
|
||||
@keydown="onKeydown" />
|
||||
<a-input v-else v-model:value="value" autofocus @keydown="onKeydown" />
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
@@ -19,8 +19,12 @@ const padding = computed(() =>
|
||||
<a-row :gutter="[8, 16]">
|
||||
<a-col :xs="24" :lg="12">
|
||||
<a-list-item-meta>
|
||||
<template #title><slot name="title" /></template>
|
||||
<template #description><slot name="description" /></template>
|
||||
<template #title>
|
||||
<slot name="title" />
|
||||
</template>
|
||||
<template #description>
|
||||
<slot name="description" />
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :xs="24" :lg="12">
|
||||
|
||||
@@ -220,16 +220,8 @@ const gradId = `spkGrad-${Math.random().toString(36).slice(2, 9)}`;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
ref="svgRef"
|
||||
width="100%"
|
||||
:height="height"
|
||||
:viewBox="viewBoxAttr"
|
||||
preserveAspectRatio="none"
|
||||
class="sparkline-svg"
|
||||
@mousemove="onMouseMove"
|
||||
@mouseleave="onMouseLeave"
|
||||
>
|
||||
<svg ref="svgRef" width="100%" :height="height" :viewBox="viewBoxAttr" preserveAspectRatio="none"
|
||||
class="sparkline-svg" @mousemove="onMouseMove" @mouseleave="onMouseLeave">
|
||||
<defs>
|
||||
<linearGradient :id="gradId" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" :stop-color="stroke" :stop-opacity="fillOpacity" />
|
||||
@@ -238,70 +230,28 @@ const gradId = `spkGrad-${Math.random().toString(36).slice(2, 9)}`;
|
||||
</defs>
|
||||
|
||||
<g v-if="showGrid">
|
||||
<line
|
||||
v-for="(g, i) in gridLines"
|
||||
:key="i"
|
||||
:x1="g.x1" :y1="g.y1" :x2="g.x2" :y2="g.y2"
|
||||
:stroke="gridColor" stroke-width="1"
|
||||
class="cpu-grid-line"
|
||||
/>
|
||||
<line v-for="(g, i) in gridLines" :key="i" :x1="g.x1" :y1="g.y1" :x2="g.x2" :y2="g.y2" :stroke="gridColor"
|
||||
stroke-width="1" class="cpu-grid-line" />
|
||||
</g>
|
||||
|
||||
<g v-if="showAxes">
|
||||
<text
|
||||
v-for="(t, i) in yTicks"
|
||||
:key="'y' + i"
|
||||
class="cpu-grid-y-text"
|
||||
:x="Math.max(0, paddingLeft - 4)"
|
||||
:y="t.y + 4"
|
||||
text-anchor="end"
|
||||
font-size="10"
|
||||
>{{ t.label }}</text>
|
||||
<text
|
||||
v-for="(t, i) in xTicks"
|
||||
:key="'x' + i"
|
||||
class="cpu-grid-x-text"
|
||||
:x="t.x"
|
||||
:y="paddingTop + drawHeight + 14"
|
||||
text-anchor="middle"
|
||||
font-size="10"
|
||||
>{{ t.label }}</text>
|
||||
<text v-for="(t, i) in yTicks" :key="'y' + i" class="cpu-grid-y-text" :x="Math.max(0, paddingLeft - 4)"
|
||||
:y="t.y + 4" text-anchor="end" font-size="10">{{ t.label }}</text>
|
||||
<text v-for="(t, i) in xTicks" :key="'x' + i" class="cpu-grid-x-text" :x="t.x" :y="paddingTop + drawHeight + 14"
|
||||
text-anchor="middle" font-size="10">{{ t.label }}</text>
|
||||
</g>
|
||||
|
||||
<path v-if="areaPath" :d="areaPath" :fill="`url(#${gradId})`" stroke="none" />
|
||||
<polyline
|
||||
:points="pointsStr"
|
||||
fill="none"
|
||||
:stroke="stroke"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle
|
||||
v-if="showMarker && lastPoint"
|
||||
:cx="lastPoint[0]" :cy="lastPoint[1]"
|
||||
:r="markerRadius"
|
||||
:fill="stroke"
|
||||
/>
|
||||
<polyline :points="pointsStr" fill="none" :stroke="stroke" :stroke-width="strokeWidth" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<circle v-if="showMarker && lastPoint" :cx="lastPoint[0]" :cy="lastPoint[1]" :r="markerRadius" :fill="stroke" />
|
||||
|
||||
<g v-if="showTooltip && hoverIdx >= 0 && pointsArr[hoverIdx]">
|
||||
<line
|
||||
class="cpu-grid-h-line"
|
||||
:x1="pointsArr[hoverIdx][0]" :x2="pointsArr[hoverIdx][0]"
|
||||
:y1="paddingTop" :y2="paddingTop + drawHeight"
|
||||
stroke="rgba(0,0,0,0.2)" stroke-width="1"
|
||||
/>
|
||||
<circle
|
||||
:cx="pointsArr[hoverIdx][0]" :cy="pointsArr[hoverIdx][1]"
|
||||
r="3.5" :fill="stroke"
|
||||
/>
|
||||
<text
|
||||
class="cpu-grid-text"
|
||||
:x="pointsArr[hoverIdx][0]"
|
||||
:y="paddingTop + 12"
|
||||
text-anchor="middle"
|
||||
font-size="11"
|
||||
>{{ fmtHoverText() }}</text>
|
||||
<line class="cpu-grid-h-line" :x1="pointsArr[hoverIdx][0]" :x2="pointsArr[hoverIdx][0]" :y1="paddingTop"
|
||||
:y2="paddingTop + drawHeight" stroke="rgba(0,0,0,0.2)" stroke-width="1" />
|
||||
<circle :cx="pointsArr[hoverIdx][0]" :cy="pointsArr[hoverIdx][1]" r="3.5" :fill="stroke" />
|
||||
<text class="cpu-grid-text" :x="pointsArr[hoverIdx][0]" :y="paddingTop + 12" text-anchor="middle"
|
||||
font-size="11">{{ fmtHoverText() }}</text>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
@@ -266,33 +266,44 @@ export default defineComponent({
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.sortable-icon:hover {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.sortable-icon:active { cursor: grabbing; }
|
||||
|
||||
.sortable-icon:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.sortable-icon:focus-visible {
|
||||
outline: 2px solid #008771;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.light .sortable-icon { color: rgba(0, 0, 0, 0.45); }
|
||||
.light .sortable-icon {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.light .sortable-icon:hover {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.sortable-table-dragging .sortable-source-row > td {
|
||||
.sortable-table-dragging .sortable-source-row>td {
|
||||
background: rgba(0, 135, 113, 0.10) !important;
|
||||
transition: background-color 0.18s ease;
|
||||
}
|
||||
|
||||
.sortable-table-dragging .sortable-source-row .routing-index,
|
||||
.sortable-table-dragging .sortable-source-row .outbound-index {
|
||||
opacity: 0.45;
|
||||
}
|
||||
.sortable-table-dragging .sortable-row > td {
|
||||
|
||||
.sortable-table-dragging .sortable-row>td {
|
||||
transition: background-color 0.18s ease;
|
||||
}
|
||||
|
||||
.sortable-table-dragging,
|
||||
.sortable-table-dragging * {
|
||||
user-select: none;
|
||||
|
||||
@@ -39,19 +39,18 @@ function download(content, name) {
|
||||
|
||||
<template>
|
||||
<a-modal :open="open" :title="title" :closable="true" @cancel="close">
|
||||
<a-textarea
|
||||
:value="content"
|
||||
readonly
|
||||
:auto-size="{ minRows: 10, maxRows: 20 }"
|
||||
class="text-modal-content"
|
||||
/>
|
||||
<a-textarea :value="content" readonly :auto-size="{ minRows: 10, maxRows: 20 }" class="text-modal-content" />
|
||||
<template #footer>
|
||||
<a-button v-if="fileName" @click="download(content, fileName)">
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
</template>
|
||||
{{ fileName }}
|
||||
</a-button>
|
||||
<a-button type="primary" @click="copy(content)">
|
||||
<template #icon><CopyOutlined /></template>
|
||||
<template #icon>
|
||||
<CopyOutlined />
|
||||
</template>
|
||||
Copy
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user