Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlolka1333 <xtrafcyz@gmail.com>2026-05-05 18:27:49 +0300
committerGitHub <noreply@github.com>2026-05-05 18:27:49 +0300
commit8177f6dc667f2edca66073e433baf5cff36cda41 (patch)
tree7fddbec4848c3c0ab0fc39e3dc45703c368e9340 /web/html/settings/xray/routing.html
parent77d94b25d054bd6cf7ace029571db9c58ae87fa9 (diff)
ws/inbounds: realtime fixes + perf for 10k+ client inbounds (#4123)HEADmain
* 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>
Diffstat (limited to 'web/html/settings/xray/routing.html')
-rw-r--r--web/html/settings/xray/routing.html296
1 files changed, 183 insertions, 113 deletions
diff --git a/web/html/settings/xray/routing.html b/web/html/settings/xray/routing.html
index 487f7963..866fa7f0 100644
--- a/web/html/settings/xray/routing.html
+++ b/web/html/settings/xray/routing.html
@@ -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}} \ No newline at end of file
+{{end}}