mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 12:29:34 +00:00
ws/inbounds: realtime fixes + perf for 10k+ client inbounds (#4123)
* ws/inbounds: realtime fixes + perf for 10k+ client inbounds - hub: dedup, throttle, panic-restart, deadlock fix, race tests - client: backoff cap + slow-retry instead of giving up - broadcast: delta-only payload, count-based invalidate fallback - filter: fix empty online list (Inbound has no .id, use dbInbound.toInbound) - perf: O(N²)→O(N) traffic merge, bulk delete, /setEnable endpoint - traffic: monotonic all_time + UI clamp + propagate in delta handler - session: persist on update/logout (fixes logout-after-password-change) - ui: protocol tags flex, traffic bar normalize * Remove hub_test.go file * fix: ws hub, inbound service, and frontend correctness - propagate DelInbound error on disable path in SetInboundEnable - skip empty emails in updateClientTraffics to avoid constraint violations - use consistent IN ? clause, drop redundant ErrRecordNotFound guards - Hub.Unregister: direct removeClient fallback when channel is full - applyClientStatsDelta: O(1) email lookup via per-inbound Map cache - WS payload size check: Blob.size instead of .length for real byte count * fix: chunk large IN ? queries and fix IPv6 same-origin check * fix: chunk large IN ? queries and fix IPv6 same-origin check * fix: unify clientStats cache, throttle clarity, hub constants * fix(ui): align traffic/expiry cell columns across all rows * style(ui): redesign outbounds table for visual consistency * style(ui): redesign routing table for visual consistency * fix: * fix: * fix: * fix: * fix: * fix: font * refactor: simplify outbound tone functions for consistency and maintainability --------- Co-authored-by: lolka1333 <test123@gmail.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
{{define "settings/xray/outbounds"}}
|
||||
<a-space direction="vertical" size="middle">
|
||||
<a-row>
|
||||
<a-col :xs="12" :sm="12" :lg="12">
|
||||
<a-space direction="horizontal" size="small">
|
||||
<a-space direction="vertical" size="middle" class="outbounds-modern">
|
||||
<a-row :gutter="[12, 12]" align="middle" justify="space-between">
|
||||
<a-col :xs="24" :sm="14" :lg="14">
|
||||
<a-space direction="horizontal" size="small" class="outbounds-toolbar">
|
||||
<a-button type="primary" icon="plus" @click="addOutbound">
|
||||
<span v-if="!isMobile">{{ i18n
|
||||
"pages.xray.outbound.addOutbound" }}</span>
|
||||
@@ -11,7 +11,7 @@
|
||||
<a-button type="primary" icon="api" @click="showNord()">NordVPN</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
<a-col :xs="12" :sm="12" :lg="12" :style="{ textAlign: 'right' }">
|
||||
<a-col :xs="24" :sm="10" :lg="10" class="outbounds-toolbar-right">
|
||||
<a-button-group>
|
||||
<a-button icon="sync" @click="refreshOutboundTraffic()" :loading="refreshing"></a-button>
|
||||
<a-popconfirm placement="topRight" @confirm="resetOutboundTraffic(-1)"
|
||||
@@ -19,92 +19,141 @@
|
||||
:overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "reset"}}'
|
||||
cancel-text='{{ i18n "cancel"}}'>
|
||||
<a-icon slot="icon" type="question-circle-o"
|
||||
:style="{ color: themeSwitcher.isDarkTheme ? '#008771' : '#008771' }"></a-icon>
|
||||
:style="{ color: '#008771' }"></a-icon>
|
||||
<a-button icon="retweet"></a-button>
|
||||
</a-popconfirm>
|
||||
</a-button-group>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-table :columns="outboundColumns" bordered :row-key="r => r.key" :data-source="outboundData"
|
||||
:scroll="isMobile ? {} : { x: 800 }" :pagination="false" :indent-size="0"
|
||||
<a-table :columns="outboundColumns" :row-key="r => r.key"
|
||||
:data-source="outboundData"
|
||||
:scroll="isMobile ? { x: 720 } : {}"
|
||||
:pagination="false"
|
||||
:indent-size="0"
|
||||
class="outbounds-table"
|
||||
:locale='{ filterConfirm: `{{ i18n "confirm" }}`, filterReset: `{{ i18n "reset" }}` }'>
|
||||
<template slot="action" slot-scope="text, outbound, index">
|
||||
<span>[[ index+1 ]]</span>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-icon @click="e => e.preventDefault()" type="more"
|
||||
:style="{ fontSize: '16px', textDecoration: 'bold' }"></a-icon>
|
||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item v-if="index>0" @click="setFirstOutbound(index)">
|
||||
<a-icon type="vertical-align-top"></a-icon>
|
||||
<span>{{ i18n "pages.xray.rules.first"}}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="editOutbound(index)">
|
||||
<a-icon type="edit"></a-icon>
|
||||
<span>{{ i18n "edit" }}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="resetOutboundTraffic(index)">
|
||||
<span>
|
||||
<div class="outbound-action-cell">
|
||||
<span class="outbound-index">[[ index+1 ]]</span>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-button shape="circle" size="small" class="outbound-action-btn"
|
||||
@click="e => e.preventDefault()">
|
||||
<a-icon type="more"></a-icon>
|
||||
</a-button>
|
||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item v-if="index>0"
|
||||
@click="setFirstOutbound(index)">
|
||||
<a-icon type="vertical-align-top"></a-icon>
|
||||
<span>{{ i18n "pages.xray.rules.first"}}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="editOutbound(index)">
|
||||
<a-icon type="edit"></a-icon>
|
||||
<span>{{ i18n "edit" }}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="resetOutboundTraffic(index)">
|
||||
<a-icon type="retweet"></a-icon>
|
||||
<span>{{ i18n "pages.inbounds.resetTraffic"}}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="deleteOutbound(index)">
|
||||
<span :style="{ color: '#FF4D4F' }">
|
||||
<a-icon type="delete"></a-icon>
|
||||
<span>{{ i18n "delete"}}</span>
|
||||
</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<template slot="identity" slot-scope="text, outbound">
|
||||
<div class="outbound-identity-cell">
|
||||
<a-tooltip :title="outbound.tag" :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<span class="outbound-tag">[[ outbound.tag ]]</span>
|
||||
</a-tooltip>
|
||||
<div class="outbound-protocol-cell">
|
||||
<span class="outbound-pill"
|
||||
:class="outboundProtocolTone(outbound.protocol)">
|
||||
[[ outbound.protocol ]]
|
||||
</span>
|
||||
<template
|
||||
v-if="[Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
|
||||
<span class="outbound-pill"
|
||||
:class="outboundNetworkTone(outbound.streamSettings.network)">
|
||||
[[ outbound.streamSettings.network ]]
|
||||
</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="deleteOutbound(index)">
|
||||
<span :style="{ color: '#FF4D4F' }">
|
||||
<a-icon type="delete"></a-icon>
|
||||
<span>{{ i18n "delete"}}</span>
|
||||
<span class="outbound-pill"
|
||||
:class="outboundSecurityTone(outbound.streamSettings.security)"
|
||||
v-if="isOutboundSecurityVisible(outbound.streamSettings.security)">
|
||||
[[ outbound.streamSettings.security ]]
|
||||
</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template slot="address" slot-scope="text, outbound, index">
|
||||
<p :style="{ margin: '0 5px' }" v-for="addr in findOutboundAddress(outbound)">[[ addr ]]</p>
|
||||
<template slot="address" slot-scope="text, outbound">
|
||||
<div class="outbound-address-list">
|
||||
<a-tooltip
|
||||
v-for="addr in outboundAddresses(outbound)"
|
||||
:key="addr"
|
||||
:title="addr"
|
||||
:overlay-class-name="themeSwitcher.currentTheme">
|
||||
<span class="outbound-address-pill">[[ addr ]]</span>
|
||||
</a-tooltip>
|
||||
<span class="outbound-address-empty"
|
||||
v-if="outboundAddresses(outbound).length === 0">—</span>
|
||||
</div>
|
||||
</template>
|
||||
<template slot="protocol" slot-scope="text, outbound, index">
|
||||
<a-tag :style="{ margin: '0' }" color="purple">[[ outbound.protocol
|
||||
]]</a-tag>
|
||||
<template
|
||||
v-if="[Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
|
||||
<a-tag :style="{ margin: '0' }" color="blue">[[
|
||||
outbound.streamSettings.network ]]</a-tag>
|
||||
<a-tag :style="{ margin: '0' }" v-if="outbound.streamSettings.security=='tls'" color="green">tls</a-tag>
|
||||
<a-tag :style="{ margin: '0' }" v-if="outbound.streamSettings.security=='reality'"
|
||||
color="green">reality</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
<template slot="traffic" slot-scope="text, outbound, index">
|
||||
<a-tag color="green">[[ findOutboundTraffic(outbound) ]]</a-tag>
|
||||
<template slot="traffic" slot-scope="text, outbound">
|
||||
<div class="outbound-traffic-cell">
|
||||
<span class="outbound-traffic-up" :title='`{{ i18n "pages.index.upload" }}`'>
|
||||
<a-icon type="arrow-up"></a-icon>
|
||||
[[ SizeFormatter.sizeFormat(findOutboundUp(outbound)) ]]
|
||||
</span>
|
||||
<span class="outbound-traffic-sep" aria-hidden="true"></span>
|
||||
<span class="outbound-traffic-down" :title='`{{ i18n "pages.index.download" }}`'>
|
||||
<a-icon type="arrow-down"></a-icon>
|
||||
[[ SizeFormatter.sizeFormat(findOutboundDown(outbound)) ]]
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template slot="test" slot-scope="text, outbound, index">
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ i18n "pages.xray.outbound.test"
|
||||
}}</template>
|
||||
<a-button type="primary" shape="circle" icon="thunderbolt"
|
||||
:loading="outboundTestStates[index] && outboundTestStates[index].testing"
|
||||
<template slot="title">{{ i18n "pages.xray.outbound.test" }}</template>
|
||||
<a-button
|
||||
type="primary"
|
||||
shape="circle"
|
||||
icon="thunderbolt"
|
||||
class="outbound-test-btn"
|
||||
:loading="isOutboundTesting(index)"
|
||||
@click="testOutbound(index)"
|
||||
:disabled="(outbound.protocol === 'blackhole' || outbound.tag === 'blocked') || (outboundTestStates[index] && outboundTestStates[index].testing)">
|
||||
:disabled="isOutboundUntestable(outbound) || isOutboundTesting(index)">
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template slot="testResult" slot-scope="text, outbound, index">
|
||||
<div v-if="outboundTestStates[index] && outboundTestStates[index].result">
|
||||
<a-tag v-if="outboundTestStates[index].result.success" color="green">
|
||||
[[ outboundTestStates[index].result.delay ]]ms
|
||||
<span v-if="outboundTestStates[index].result.statusCode">
|
||||
([[ outboundTestStates[index].result.statusCode
|
||||
]])</span>
|
||||
</a-tag>
|
||||
<a-tooltip v-else :title="outboundTestStates[index].result.error">
|
||||
<a-tag color="red">
|
||||
Failed
|
||||
</a-tag>
|
||||
<div class="outbound-result-cell" v-if="outboundTestResult(index)">
|
||||
<span v-if="outboundTestResult(index).success"
|
||||
class="outbound-result-pill outbound-result-ok">
|
||||
<a-icon type="check-circle" theme="filled"></a-icon>
|
||||
[[ outboundTestResult(index).delay ]] ms
|
||||
<span class="outbound-result-status"
|
||||
v-if="outboundTestResult(index).statusCode">
|
||||
· [[ outboundTestResult(index).statusCode ]]
|
||||
</span>
|
||||
</span>
|
||||
<a-tooltip v-else
|
||||
:title="outboundTestResult(index).error"
|
||||
:overlay-class-name="themeSwitcher.currentTheme">
|
||||
<span class="outbound-result-pill outbound-result-fail">
|
||||
<a-icon type="close-circle" theme="filled"></a-icon>
|
||||
{{ i18n "pages.xray.outbound.testFailed" }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<span v-else-if="outboundTestStates[index] && outboundTestStates[index].testing">
|
||||
<a-icon type="loading" />
|
||||
<span class="outbound-result-loading" v-else-if="isOutboundTesting(index)">
|
||||
<a-icon type="loading"></a-icon>
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
<span class="outbound-result-idle" v-else>—</span>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-space>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,123 +1,193 @@
|
||||
{{define "settings/xray/routing"}}
|
||||
<a-space direction="vertical" size="middle">
|
||||
<a-button type="primary" icon="plus" @click="addRule">{{ i18n "pages.xray.rules.add" }}</a-button>
|
||||
<a-table-sortable :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered :row-key="r => r.key"
|
||||
:data-source="routingRuleData" :scroll="isMobile ? {} : { x: 1000 }" :pagination="false" :indent-size="0"
|
||||
<a-space direction="vertical" size="middle" class="routing-modern">
|
||||
<a-button type="primary" icon="plus" @click="addRule">{{ i18n
|
||||
"pages.xray.rules.add" }}</a-button>
|
||||
<a-table-sortable :columns="isMobile ? rulesMobileColumns : rulesColumns"
|
||||
:row-key="r => r.key"
|
||||
:data-source="routingRuleData"
|
||||
:scroll="{}"
|
||||
:pagination="false"
|
||||
:indent-size="0"
|
||||
class="routing-table"
|
||||
v-on:onSort="replaceRule">
|
||||
<template slot="action" slot-scope="text, rule, index">
|
||||
<a-table-sort-trigger :item-index="index"></a-table-sort-trigger>
|
||||
<span class="ant-table-row-index"> [[ index+1 ]] </span>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-icon @click="e => e.preventDefault()" type="more"
|
||||
:style="{ fontSize: '16px', textDecoration: 'bold' }"></a-icon>
|
||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item v-if="index>0" @click="replaceRule(index,0)">
|
||||
<a-icon type="vertical-align-top"></a-icon>
|
||||
{{ i18n "pages.xray.rules.first"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="index>0" @click="replaceRule(index,index-1)">
|
||||
<a-icon type="arrow-up"></a-icon>
|
||||
{{ i18n "pages.xray.rules.up"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="index<routingRuleData.length-1" @click="replaceRule(index,index+1)">
|
||||
<a-icon type="arrow-down"></a-icon>
|
||||
{{ i18n "pages.xray.rules.down"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="index<routingRuleData.length-1"
|
||||
@click="replaceRule(index,routingRuleData.length-1)">
|
||||
<a-icon type="vertical-align-bottom"></a-icon>
|
||||
{{ i18n "pages.xray.rules.last"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="editRule(index)">
|
||||
<a-icon type="edit"></a-icon>
|
||||
{{ i18n "edit" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="deleteRule(index)">
|
||||
<span :style="{ color: '#FF4D4F' }">
|
||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||
</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
<div class="routing-action-cell">
|
||||
<a-table-sort-trigger :item-index="index"></a-table-sort-trigger>
|
||||
<span class="routing-index">[[ index+1 ]]</span>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-button shape="circle" size="small" class="routing-action-btn"
|
||||
@click="e => e.preventDefault()">
|
||||
<a-icon type="more"></a-icon>
|
||||
</a-button>
|
||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item @click="editRule(index)">
|
||||
<a-icon type="edit"></a-icon>
|
||||
<span>{{ i18n "edit" }}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="deleteRule(index)">
|
||||
<span :style="{ color: '#FF4D4F' }">
|
||||
<a-icon type="delete"></a-icon>
|
||||
<span>{{ i18n "delete" }}</span>
|
||||
</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<template slot="inbound" slot-scope="text, rule, index">
|
||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-if="rule.inboundTag">Inbound Tag: [[ rule.inboundTag ]]</p>
|
||||
<p v-if="rule.user">User email: [[ rule.user ]]</p>
|
||||
</template>
|
||||
[[ [rule.inboundTag,rule.user].join('\n') ]]
|
||||
</a-popover>
|
||||
|
||||
<template slot="source" slot-scope="text, rule">
|
||||
<div class="criterion-flow">
|
||||
<a-tooltip v-if="rule.sourceIP"
|
||||
:title="'Source IP: ' + joinCsv(rule.sourceIP)"
|
||||
:overlay-class-name="themeSwitcher.currentTheme"
|
||||
:trigger="['hover', 'click']">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">IP</span>
|
||||
<span class="criterion-value">[[ csv(rule.sourceIP)[0] ]]</span>
|
||||
<span v-if="csv(rule.sourceIP).length > 1" class="criterion-more">+[[ csv(rule.sourceIP).length - 1 ]]</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="rule.sourcePort"
|
||||
:title="'Source Port: ' + joinCsv(rule.sourcePort)"
|
||||
:overlay-class-name="themeSwitcher.currentTheme"
|
||||
:trigger="['hover', 'click']">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">Port</span>
|
||||
<span class="criterion-value">[[ csv(rule.sourcePort)[0] ]]</span>
|
||||
<span v-if="csv(rule.sourcePort).length > 1" class="criterion-more">+[[ csv(rule.sourcePort).length - 1 ]]</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="rule.vlessRoute"
|
||||
:title="'VLESS Route: ' + joinCsv(rule.vlessRoute)"
|
||||
:overlay-class-name="themeSwitcher.currentTheme"
|
||||
:trigger="['hover', 'click']">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">VLESS</span>
|
||||
<span class="criterion-value">[[ csv(rule.vlessRoute)[0] ]]</span>
|
||||
<span v-if="csv(rule.vlessRoute).length > 1" class="criterion-more">+[[ csv(rule.vlessRoute).length - 1 ]]</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<span class="routing-criteria-empty"
|
||||
v-if="!rule.sourceIP && !rule.sourcePort && !rule.vlessRoute">—</span>
|
||||
</div>
|
||||
</template>
|
||||
<template slot="outbound" slot-scope="text, rule, index">
|
||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-if="rule.outboundTag">Outbound Tag: [[ rule.outboundTag ]]</p>
|
||||
</template>
|
||||
[[ rule.outboundTag ]]
|
||||
</a-popover>
|
||||
|
||||
<template slot="network" slot-scope="text, rule">
|
||||
<div class="criterion-flow">
|
||||
<a-tooltip v-if="rule.network"
|
||||
:title="'L4: ' + joinCsv(rule.network)"
|
||||
:overlay-class-name="themeSwitcher.currentTheme"
|
||||
:trigger="['hover', 'click']">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">L4</span>
|
||||
<span class="criterion-value">[[ csv(rule.network)[0] ]]</span>
|
||||
<span v-if="csv(rule.network).length > 1" class="criterion-more">+[[ csv(rule.network).length - 1 ]]</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="rule.protocol"
|
||||
:title="'Protocol: ' + joinCsv(rule.protocol)"
|
||||
:overlay-class-name="themeSwitcher.currentTheme"
|
||||
:trigger="['hover', 'click']">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">Protocol</span>
|
||||
<span class="criterion-value">[[ csv(rule.protocol)[0] ]]</span>
|
||||
<span v-if="csv(rule.protocol).length > 1" class="criterion-more">+[[ csv(rule.protocol).length - 1 ]]</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="rule.attrs"
|
||||
:title="'Attrs: ' + joinCsv(rule.attrs)"
|
||||
:overlay-class-name="themeSwitcher.currentTheme"
|
||||
:trigger="['hover', 'click']">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">Attrs</span>
|
||||
<span class="criterion-value">[[ csv(rule.attrs)[0] ]]</span>
|
||||
<span v-if="csv(rule.attrs).length > 1" class="criterion-more">+[[ csv(rule.attrs).length - 1 ]]</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<span class="routing-criteria-empty"
|
||||
v-if="!rule.network && !rule.protocol && !rule.attrs">—</span>
|
||||
</div>
|
||||
</template>
|
||||
<template slot="balancer" slot-scope="text, rule, index">
|
||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-if="rule.balancerTag">Balancer Tag: [[ rule.balancerTag ]]</p>
|
||||
</template>
|
||||
[[ rule.balancerTag ]]
|
||||
</a-popover>
|
||||
|
||||
<template slot="destination" slot-scope="text, rule">
|
||||
<div class="criterion-flow">
|
||||
<a-tooltip v-if="rule.ip"
|
||||
:title="'Destination IP: ' + joinCsv(rule.ip)"
|
||||
:overlay-class-name="themeSwitcher.currentTheme"
|
||||
:trigger="['hover', 'click']">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">IP</span>
|
||||
<span class="criterion-value">[[ csv(rule.ip)[0] ]]</span>
|
||||
<span v-if="csv(rule.ip).length > 1" class="criterion-more">+[[ csv(rule.ip).length - 1 ]]</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="rule.domain"
|
||||
:title="'Domain: ' + joinCsv(rule.domain)"
|
||||
:overlay-class-name="themeSwitcher.currentTheme"
|
||||
:trigger="['hover', 'click']">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">Domain</span>
|
||||
<span class="criterion-value">[[ csv(rule.domain)[0] ]]</span>
|
||||
<span v-if="csv(rule.domain).length > 1" class="criterion-more">+[[ csv(rule.domain).length - 1 ]]</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="rule.port"
|
||||
:title="'Destination Port: ' + joinCsv(rule.port)"
|
||||
:overlay-class-name="themeSwitcher.currentTheme"
|
||||
:trigger="['hover', 'click']">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">Port</span>
|
||||
<span class="criterion-value">[[ csv(rule.port)[0] ]]</span>
|
||||
<span v-if="csv(rule.port).length > 1" class="criterion-more">+[[ csv(rule.port).length - 1 ]]</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<span class="routing-criteria-empty"
|
||||
v-if="!rule.ip && !rule.domain && !rule.port">—</span>
|
||||
</div>
|
||||
</template>
|
||||
<template slot="info" slot-scope="text, rule, index">
|
||||
<a-popover placement="bottomRight"
|
||||
v-if="(rule.sourceIP+rule.sourcePort+rule.vlessRoute+rule.network+rule.protocol+rule.attrs+rule.ip+rule.domain+rule.port).length>0"
|
||||
:overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
||||
<template slot="content">
|
||||
<table cellpadding="2" :style="{ maxWidth: '300px' }">
|
||||
<tr v-if="rule.sourceIP">
|
||||
<td>Source IP</td>
|
||||
<td><a-tag color="blue" v-for="r in rule.sourceIP.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.sourcePort">
|
||||
<td>Source Port</td>
|
||||
<td><a-tag color="green" v-for="r in rule.sourcePort.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.vlessRoute">
|
||||
<td>VLESS Route</td>
|
||||
<td><a-tag color="geekblue" v-for="r in rule.vlessRoute.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.network">
|
||||
<td>Network</td>
|
||||
<td><a-tag color="blue" v-for="r in rule.network.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.protocol">
|
||||
<td>Protocol</td>
|
||||
<td><a-tag color="green" v-for="r in rule.protocol.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.attrs">
|
||||
<td>Attrs</td>
|
||||
<td><a-tag color="blue" v-for="r in rule.attrs.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.ip">
|
||||
<td>IP</td>
|
||||
<td><a-tag color="green" v-for="r in rule.ip.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.domain">
|
||||
<td>Domain</td>
|
||||
<td><a-tag color="blue" v-for="r in rule.domain.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.port">
|
||||
<td>Port</td>
|
||||
<td><a-tag color="green" v-for="r in rule.port.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.balancerTag">
|
||||
<td>Balancer Tag</td>
|
||||
<td><a-tag color="blue">[[ rule.balancerTag ]]</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
<a-button shape="round" size="small" :style="{ fontSize: '14px', padding: '0 10px' }">
|
||||
<a-icon type="info"></a-icon>
|
||||
</a-button>
|
||||
</a-popover>
|
||||
|
||||
<template slot="inbound" slot-scope="text, rule">
|
||||
<div class="criterion-flow">
|
||||
<a-tooltip v-if="rule.inboundTag"
|
||||
:title="'Inbound Tag: ' + joinCsv(rule.inboundTag)"
|
||||
:overlay-class-name="themeSwitcher.currentTheme"
|
||||
:trigger="['hover', 'click']">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">Tag</span>
|
||||
<span class="criterion-value">[[ csv(rule.inboundTag)[0] ]]</span>
|
||||
<span v-if="csv(rule.inboundTag).length > 1" class="criterion-more">+[[ csv(rule.inboundTag).length - 1 ]]</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="rule.user"
|
||||
:title="'Client: ' + joinCsv(rule.user)"
|
||||
:overlay-class-name="themeSwitcher.currentTheme"
|
||||
:trigger="['hover', 'click']">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">User</span>
|
||||
<span class="criterion-value">[[ csv(rule.user)[0] ]]</span>
|
||||
<span v-if="csv(rule.user).length > 1" class="criterion-more">+[[ csv(rule.user).length - 1 ]]</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<span class="routing-criteria-empty"
|
||||
v-if="!rule.inboundTag && !rule.user">—</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template slot="target" slot-scope="text, rule">
|
||||
<div class="routing-target-cell">
|
||||
<div class="routing-target-row" v-if="rule.outboundTag">
|
||||
<a-icon type="export" class="routing-target-icon"></a-icon>
|
||||
<span class="outbound-pill tone-emerald">[[ rule.outboundTag ]]</span>
|
||||
</div>
|
||||
<div class="routing-target-row" v-if="rule.balancerTag">
|
||||
<a-icon type="cluster" class="routing-target-icon"></a-icon>
|
||||
<span class="outbound-pill tone-violet">[[ rule.balancerTag ]]</span>
|
||||
</div>
|
||||
<span class="routing-criteria-empty"
|
||||
v-if="!rule.outboundTag && !rule.balancerTag">—</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</a-table-sortable>
|
||||
</a-space>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user