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
path: root/web
diff options
context:
space:
mode:
authorShishkevich D. <135337715+shishkevichd@users.noreply.github.com>2025-06-21 20:27:09 +0300
committerGitHub <noreply@github.com>2025-06-21 20:27:09 +0300
commit4b20f16024769a072fa7c665d605e38134ef2920 (patch)
tree8177fc89b390607c328b8ddfc2c79f8e4af888ca /web
parentd642774a4493912e76dbc294dce834cf5b635324 (diff)
refactor: new loading logic, icons for tabs
Diffstat (limited to 'web')
-rw-r--r--web/html/inbounds.html954
-rw-r--r--web/html/index.html41
-rw-r--r--web/html/settings.html126
-rw-r--r--web/html/settings/xray/advanced.html2
-rw-r--r--web/html/xray.html2682
5 files changed, 1959 insertions, 1846 deletions
diff --git a/web/html/inbounds.html b/web/html/inbounds.html
index 7b00ae5e..e1267190 100644
--- a/web/html/inbounds.html
+++ b/web/html/inbounds.html
@@ -148,9 +148,9 @@
<a-sidebar></a-sidebar>
<a-layout id="content-layout">
<a-layout-content>
- <a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
+ <a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}'>
<transition name="list" appear>
- <a-alert type="error" v-if="showAlert" :style="{ marginBottom: '10px' }"
+ <a-alert type="error" v-if="showAlert && loadingStates.fetched" :style="{ marginBottom: '10px' }"
message='{{ i18n "secAlertTitle" }}'
color="red"
description='{{ i18n "secAlertSsl" }}'
@@ -158,499 +158,508 @@
</a-alert>
</transition>
<transition name="list" appear>
- <a-card size="small" :style="{ padding: '16px' }" hoverable>
- <a-row>
- <a-col :sm="12" :md="6">
- <a-custom-statistic title='{{ i18n "pages.inbounds.totalDownUp" }}' :value="`${SizeFormatter.sizeFormat(total.up)} / ${SizeFormatter.sizeFormat(total.down)}`">
- <template #prefix>
- <a-icon type="swap"></a-icon>
- </template>
- </a-custom-statistic>
- </a-col>
- <a-col :sm="12" :md="6">
- <a-custom-statistic title='{{ i18n "pages.inbounds.totalUsage" }}' :value="SizeFormatter.sizeFormat(total.up + total.down)" :style="{ marginTop: isMobile ? '10px' : 0 }">
- <template #prefix>
- <a-icon type="pie-chart"></a-icon>
- </template>
- </a-custom-statistic>
- </a-col>
- <a-col :sm="12" :md="6">
- <a-custom-statistic title='{{ i18n "pages.inbounds.inboundCount" }}' :value="dbInbounds.length" :style="{ marginTop: isMobile ? '10px' : 0 }">
- <template #prefix>
- <a-icon type="bars"></a-icon>
- </template>
- </a-custom-statistic>
- </a-col>
- <a-col :sm="12" :md="6">
- <a-custom-statistic title='{{ i18n "clients" }}' value=" " :style="{ marginTop: isMobile ? '10px' : 0 }">
- <template #prefix>
- <a-space direction="horizontal">
- <a-icon type="team"></a-icon>
- <div>
- <a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"></a-back-top>
- <a-tag color="green">[[ total.clients ]]</a-tag>
- <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
- <template slot="content">
- <div v-for="clientEmail in total.deactive"><span>[[ clientEmail ]]</span></div>
- </template>
- <a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
- </a-popover>
- <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
- <template slot="content">
- <div v-for="clientEmail in total.depleted"><span>[[ clientEmail ]]</span></div>
- </template>
- <a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
- </a-popover>
- <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
- <template slot="content">
- <div v-for="clientEmail in total.expiring"><span>[[ clientEmail ]]</span></div>
- </template>
- <a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
- </a-popover>
- <a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
- <template slot="content">
- <div v-for="clientEmail in onlineClients"><span>[[ clientEmail ]]</span></div>
- </template>
- <a-tag color="blue" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag>
- </a-popover>
- </div>
- </a-space>
- </template>
- </a-custom-statistic>
- </a-col>
- </a-row>
- </a-card>
- </transition>
- <transition name="list" appear>
- <a-card hoverable>
- <template #title>
- <a-space direction="horizontal">
- <a-button type="primary" icon="plus" @click="openAddInbound">
- <template v-if="!isMobile">{{ i18n "pages.inbounds.addInbound" }}</template>
- </a-button>
- <a-dropdown :trigger="['click']">
- <a-button type="primary" icon="menu">
- <template v-if="!isMobile">{{ i18n "pages.inbounds.generalActions" }}</template>
- </a-button>
- <a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
- <a-menu-item key="import">
- <a-icon type="import"></a-icon>
- {{ i18n "pages.inbounds.importInbound" }}
- </a-menu-item>
- <a-menu-item key="export">
- <a-icon type="export"></a-icon>
- {{ i18n "pages.inbounds.export" }}
- </a-menu-item>
- <a-menu-item key="subs" v-if="subSettings.enable">
- <a-icon type="export"></a-icon>
- {{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
- </a-menu-item>
- <a-menu-item key="resetInbounds">
- <a-icon type="reload"></a-icon>
- {{ i18n "pages.inbounds.resetAllTraffic" }}
- </a-menu-item>
- <a-menu-item key="resetClients">
- <a-icon type="file-done"></a-icon>
- {{ i18n "pages.inbounds.resetAllClientTraffics" }}
- </a-menu-item>
- <a-menu-item key="delDepletedClients" :style="{ color: '#FF4D4F' }">
- <a-icon type="rest"></a-icon>
- {{ i18n "pages.inbounds.delDepletedClients" }}
- </a-menu-item>
- </a-menu>
- </a-dropdown>
- </a-space>
- </template>
- <template #extra>
- <a-button-group>
- <a-button icon="sync" @click="manualRefresh" :loading="refreshing"></a-button>
- <a-popover placement="bottomRight" trigger="click" :overlay-class-name="themeSwitcher.currentTheme">
- <template #title>
- <div class="ant-custom-popover-title">
- <a-switch v-model="isRefreshEnabled" @change="toggleRefresh" size="small"></a-switch>
- <span>{{ i18n "pages.inbounds.autoRefresh" }}</span>
- </div>
- </template>
- <template #content>
- <a-space direction="vertical">
- <span>{{ i18n "pages.inbounds.autoRefreshInterval" }}</span>
- <a-select v-model="refreshInterval"
- :disabled="!isRefreshEnabled"
- :style="{ width: '100%' }"
- @change="changeRefreshInterval"
- :dropdown-class-name="themeSwitcher.currentTheme">
- <a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
- </a-select>
- </a-space>
- </template>
- <a-button icon="down"></a-button>
- </a-popover>
- </a-button-group>
- </template>
- <a-space direction="vertical">
- <div :style="isMobile ? {} : { display: 'flex', alignItems: 'center', justifyContent: 'flex-start' }">
- <a-switch v-model="enableFilter"
- :style="isMobile ? { marginBottom: '.5rem', display: 'flex' } : { marginRight: '.5rem' }"
- @change="toggleFilter">
- <a-icon slot="checkedChildren" type="search"></a-icon>
- <a-icon slot="unCheckedChildren" type="filter"></a-icon>
- </a-switch>
- <a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus :style="{ maxWidth: '300px' }" :size="isMobile ? 'small' : ''"></a-input>
- <a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid" :size="isMobile ? 'small' : ''">
- <a-radio-button value="">{{ i18n "none" }}</a-radio-button>
- <a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
- <a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
- <a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
- <a-radio-button value="online">{{ i18n "online" }}</a-radio-button>
- </a-radio-group>
- </div>
- <a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id"
- :data-source="searchedInbounds"
- :scroll="isMobile ? {} : { x: 1000 }"
- :pagination=pagination(searchedInbounds)
- :expand-icon-as-cell="false"
- :expand-row-by-click="false"
- :expand-icon-column-index="0"
- :indent-size="0"
- :row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
- :style="{ marginTop: '10px' }"
- :locale='{ filterConfirm: `{{ i18n "confirm" }}`, filterReset: `{{ i18n "reset" }}`, emptyText: `{{ i18n "noData" }}` }'>
- <template slot="action" slot-scope="text, dbInbound">
- <a-dropdown :trigger="['click']">
- <a-icon @click="e => e.preventDefault()" type="more" :style="{ fontSize: '20px', textDecoration: 'solid' }"></a-icon>
- <a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
- <a-menu-item key="edit">
- <a-icon type="edit"></a-icon>
- {{ i18n "edit" }}
- </a-menu-item>
- <a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard">
- <a-icon type="qrcode"></a-icon>
- {{ i18n "qrCode" }}
- </a-menu-item>
- <template v-if="dbInbound.isMultiUser()">
- <a-menu-item key="addClient">
- <a-icon type="user-add"></a-icon>
- {{ i18n "pages.client.add"}}
- </a-menu-item>
- <a-menu-item key="addBulkClient">
- <a-icon type="usergroup-add"></a-icon>
- {{ i18n "pages.client.bulk"}}
- </a-menu-item>
- <a-menu-item key="resetClients">
- <a-icon type="file-done"></a-icon>
- {{ i18n "pages.inbounds.resetInboundClientTraffics"}}
+ <a-row v-if="!loadingStates.fetched">
+ <a-card :style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent', border: 'none' }">
+ <a-spin tip='{{ i18n "loading" }}'></a-spin>
+ </a-card>
+ </a-row>
+ <a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-else>
+ <a-col>
+ <a-card size="small" :style="{ padding: '16px' }" hoverable>
+ <a-row>
+ <a-col :sm="12" :md="6">
+ <a-custom-statistic title='{{ i18n "pages.inbounds.totalDownUp" }}' :value="`${SizeFormatter.sizeFormat(total.up)} / ${SizeFormatter.sizeFormat(total.down)}`">
+ <template #prefix>
+ <a-icon type="swap"></a-icon>
+ </template>
+ </a-custom-statistic>
+ </a-col>
+ <a-col :sm="12" :md="6">
+ <a-custom-statistic title='{{ i18n "pages.inbounds.totalUsage" }}' :value="SizeFormatter.sizeFormat(total.up + total.down)" :style="{ marginTop: isMobile ? '10px' : 0 }">
+ <template #prefix>
+ <a-icon type="pie-chart"></a-icon>
+ </template>
+ </a-custom-statistic>
+ </a-col>
+ <a-col :sm="12" :md="6">
+ <a-custom-statistic title='{{ i18n "pages.inbounds.inboundCount" }}' :value="dbInbounds.length" :style="{ marginTop: isMobile ? '10px' : 0 }">
+ <template #prefix>
+ <a-icon type="bars"></a-icon>
+ </template>
+ </a-custom-statistic>
+ </a-col>
+ <a-col :sm="12" :md="6">
+ <a-custom-statistic title='{{ i18n "clients" }}' value=" " :style="{ marginTop: isMobile ? '10px' : 0 }">
+ <template #prefix>
+ <a-space direction="horizontal">
+ <a-icon type="team"></a-icon>
+ <div>
+ <a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"></a-back-top>
+ <a-tag color="green">[[ total.clients ]]</a-tag>
+ <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
+ <template slot="content">
+ <div v-for="clientEmail in total.deactive"><span>[[ clientEmail ]]</span></div>
+ </template>
+ <a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
+ </a-popover>
+ <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
+ <template slot="content">
+ <div v-for="clientEmail in total.depleted"><span>[[ clientEmail ]]</span></div>
+ </template>
+ <a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
+ </a-popover>
+ <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
+ <template slot="content">
+ <div v-for="clientEmail in total.expiring"><span>[[ clientEmail ]]</span></div>
+ </template>
+ <a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
+ </a-popover>
+ <a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
+ <template slot="content">
+ <div v-for="clientEmail in onlineClients"><span>[[ clientEmail ]]</span></div>
+ </template>
+ <a-tag color="blue" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag>
+ </a-popover>
+ </div>
+ </a-space>
+ </template>
+ </a-custom-statistic>
+ </a-col>
+ </a-row>
+ </a-card>
+ </a-col>
+ <a-col>
+ <a-card hoverable>
+ <template #title>
+ <a-space direction="horizontal">
+ <a-button type="primary" icon="plus" @click="openAddInbound">
+ <template v-if="!isMobile">{{ i18n "pages.inbounds.addInbound" }}</template>
+ </a-button>
+ <a-dropdown :trigger="['click']">
+ <a-button type="primary" icon="menu">
+ <template v-if="!isMobile">{{ i18n "pages.inbounds.generalActions" }}</template>
+ </a-button>
+ <a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
+ <a-menu-item key="import">
+ <a-icon type="import"></a-icon>
+ {{ i18n "pages.inbounds.importInbound" }}
</a-menu-item>
<a-menu-item key="export">
<a-icon type="export"></a-icon>
- {{ i18n "pages.inbounds.export"}}
+ {{ i18n "pages.inbounds.export" }}
</a-menu-item>
<a-menu-item key="subs" v-if="subSettings.enable">
<a-icon type="export"></a-icon>
- {{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
+ {{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
+ </a-menu-item>
+ <a-menu-item key="resetInbounds">
+ <a-icon type="reload"></a-icon>
+ {{ i18n "pages.inbounds.resetAllTraffic" }}
+ </a-menu-item>
+ <a-menu-item key="resetClients">
+ <a-icon type="file-done"></a-icon>
+ {{ i18n "pages.inbounds.resetAllClientTraffics" }}
</a-menu-item>
<a-menu-item key="delDepletedClients" :style="{ color: '#FF4D4F' }">
<a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }}
</a-menu-item>
- </template>
- <template v-else>
- <a-menu-item key="showInfo">
- <a-icon type="info-circle"></a-icon>
- {{ i18n "info"}}
- </a-menu-item>
- </template>
- <a-menu-item key="clipboard">
- <a-icon type="copy"></a-icon>
- {{ i18n "pages.inbounds.exportInbound" }}
- </a-menu-item>
- <a-menu-item key="resetTraffic">
- <a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
- </a-menu-item>
- <a-menu-item key="clone">
- <a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}}
- </a-menu-item>
- <a-menu-item key="delete">
- <span :style="{ color: '#FF4D4F' }">
- <a-icon type="delete"></a-icon> {{ i18n "delete"}}
- </span>
- </a-menu-item>
- <a-menu-item v-if="isMobile">
- <a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
- {{ i18n "pages.inbounds.enable" }}
- </a-menu-item>
- </a-menu>
- </a-dropdown>
+ </a-menu>
+ </a-dropdown>
+ </a-space>
</template>
- <template slot="protocol" slot-scope="text, dbInbound">
- <a-tag :style="{ margin: '0' }" color="purple">[[ dbInbound.protocol ]]</a-tag>
- <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
- <a-tag :style="{ margin: '0' }" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
- <a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isTls" color="blue">TLS</a-tag>
- <a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag>
- </template>
- </template>
- <template slot="clients" slot-scope="text, dbInbound">
- <template v-if="clientCount[dbInbound.id]">
- <a-tag :style="{ margin: '0' }" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
- <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
- <template slot="content">
- <div v-for="clientEmail in clientCount[dbInbound.id].deactive" :key="clientEmail" class="client-popup-item">
- <span>[[ clientEmail ]]</span>
- <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
- <template #title>
- [[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
- </template>
- <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
- </a-tooltip>
- </div>
- </template>
- <a-tag :style="{ margin: '0', padding: '0 2px' }" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
- </a-popover>
- <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
- <template slot="content">
- <div v-for="clientEmail in clientCount[dbInbound.id].depleted" :key="clientEmail" class="client-popup-item">
- <span>[[ clientEmail ]]</span>
- <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
- <template #title>
- [[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
- </template>
- <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
- </a-tooltip>
+ <template #extra>
+ <a-button-group>
+ <a-button icon="sync" @click="manualRefresh" :loading="refreshing"></a-button>
+ <a-popover placement="bottomRight" trigger="click" :overlay-class-name="themeSwitcher.currentTheme">
+ <template #title>
+ <div class="ant-custom-popover-title">
+ <a-switch v-model="isRefreshEnabled" @change="toggleRefresh" size="small"></a-switch>
+ <span>{{ i18n "pages.inbounds.autoRefresh" }}</span>
</div>
</template>
- <a-tag :style="{ margin: '0', padding: '0 2px' }" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
- </a-popover>
- <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
- <template slot="content">
- <div v-for="clientEmail in clientCount[dbInbound.id].expiring" :key="clientEmail" class="client-popup-item">
- <span>[[ clientEmail ]]</span>
- <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
- <template #title>
- [[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
- </template>
- <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
- </a-tooltip>
- </div>
- </template>
- <a-tag :style="{ margin: '0', padding: '0 2px' }" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
- </a-popover>
- <a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
- <template slot="content">
- <div v-for="clientEmail in clientCount[dbInbound.id].online" :key="clientEmail" class="client-popup-item">
- <span>[[ clientEmail ]]</span>
- <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
- <template #title>
- [[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
- </template>
- <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
- </a-tooltip>
- </div>
+ <template #content>
+ <a-space direction="vertical">
+ <span>{{ i18n "pages.inbounds.autoRefreshInterval" }}</span>
+ <a-select v-model="refreshInterval"
+ :disabled="!isRefreshEnabled"
+ :style="{ width: '100%' }"
+ @change="changeRefreshInterval"
+ :dropdown-class-name="themeSwitcher.currentTheme">
+ <a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
+ </a-select>
+ </a-space>
</template>
- <a-tag :style="{ margin: '0', padding: '0 2px' }" color="blue" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
+ <a-button icon="down"></a-button>
</a-popover>
- </template>
+ </a-button-group>
</template>
- <template slot="traffic" slot-scope="text, dbInbound">
- <a-popover :overlay-class-name="themeSwitcher.currentTheme">
- <template slot="content">
- <table cellpadding="2" width="100%">
- <tr>
- <td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
- <td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
- </tr>
- <tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
- <td>{{ i18n "remained" }}</td>
- <td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
- </tr>
- </table>
+ <a-space direction="vertical">
+ <div :style="isMobile ? {} : { display: 'flex', alignItems: 'center', justifyContent: 'flex-start' }">
+ <a-switch v-model="enableFilter"
+ :style="isMobile ? { marginBottom: '.5rem', display: 'flex' } : { marginRight: '.5rem' }"
+ @change="toggleFilter">
+ <a-icon slot="checkedChildren" type="search"></a-icon>
+ <a-icon slot="unCheckedChildren" type="filter"></a-icon>
+ </a-switch>
+ <a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus :style="{ maxWidth: '300px' }" :size="isMobile ? 'small' : ''"></a-input>
+ <a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid" :size="isMobile ? 'small' : ''">
+ <a-radio-button value="">{{ i18n "none" }}</a-radio-button>
+ <a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
+ <a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
+ <a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
+ <a-radio-button value="online">{{ i18n "online" }}</a-radio-button>
+ </a-radio-group>
+ </div>
+ <a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id"
+ :data-source="searchedInbounds"
+ :scroll="isMobile ? {} : { x: 1000 }"
+ :pagination=pagination(searchedInbounds)
+ :expand-icon-as-cell="false"
+ :expand-row-by-click="false"
+ :expand-icon-column-index="0"
+ :indent-size="0"
+ :row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
+ :style="{ marginTop: '10px' }"
+ :locale='{ filterConfirm: `{{ i18n "confirm" }}`, filterReset: `{{ i18n "reset" }}`, emptyText: `{{ i18n "noData" }}` }'>
+ <template slot="action" slot-scope="text, dbInbound">
+ <a-dropdown :trigger="['click']">
+ <a-icon @click="e => e.preventDefault()" type="more" :style="{ fontSize: '20px', textDecoration: 'solid' }"></a-icon>
+ <a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
+ <a-menu-item key="edit">
+ <a-icon type="edit"></a-icon>
+ {{ i18n "edit" }}
+ </a-menu-item>
+ <a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard">
+ <a-icon type="qrcode"></a-icon>
+ {{ i18n "qrCode" }}
+ </a-menu-item>
+ <template v-if="dbInbound.isMultiUser()">
+ <a-menu-item key="addClient">
+ <a-icon type="user-add"></a-icon>
+ {{ i18n "pages.client.add"}}
+ </a-menu-item>
+ <a-menu-item key="addBulkClient">
+ <a-icon type="usergroup-add"></a-icon>
+ {{ i18n "pages.client.bulk"}}
+ </a-menu-item>
+ <a-menu-item key="resetClients">
+ <a-icon type="file-done"></a-icon>
+ {{ i18n "pages.inbounds.resetInboundClientTraffics"}}
+ </a-menu-item>
+ <a-menu-item key="export">
+ <a-icon type="export"></a-icon>
+ {{ i18n "pages.inbounds.export"}}
+ </a-menu-item>
+ <a-menu-item key="subs" v-if="subSettings.enable">
+ <a-icon type="export"></a-icon>
+ {{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
+ </a-menu-item>
+ <a-menu-item key="delDepletedClients" :style="{ color: '#FF4D4F' }">
+ <a-icon type="rest"></a-icon>
+ {{ i18n "pages.inbounds.delDepletedClients" }}
+ </a-menu-item>
+ </template>
+ <template v-else>
+ <a-menu-item key="showInfo">
+ <a-icon type="info-circle"></a-icon>
+ {{ i18n "info"}}
+ </a-menu-item>
+ </template>
+ <a-menu-item key="clipboard">
+ <a-icon type="copy"></a-icon>
+ {{ i18n "pages.inbounds.exportInbound" }}
+ </a-menu-item>
+ <a-menu-item key="resetTraffic">
+ <a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
+ </a-menu-item>
+ <a-menu-item key="clone">
+ <a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}}
+ </a-menu-item>
+ <a-menu-item key="delete">
+ <span :style="{ color: '#FF4D4F' }">
+ <a-icon type="delete"></a-icon> {{ i18n "delete"}}
+ </span>
+ </a-menu-item>
+ <a-menu-item v-if="isMobile">
+ <a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
+ {{ i18n "pages.inbounds.enable" }}
+ </a-menu-item>
+ </a-menu>
+ </a-dropdown>
+ </template>
+ <template slot="protocol" slot-scope="text, dbInbound">
+ <a-tag :style="{ margin: '0' }" color="purple">[[ dbInbound.protocol ]]</a-tag>
+ <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
+ <a-tag :style="{ margin: '0' }" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
+ <a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isTls" color="blue">TLS</a-tag>
+ <a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag>
+ </template>
</template>
- <a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
- [[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
- <template v-if="dbInbound.total > 0">
- [[ SizeFormatter.sizeFormat(dbInbound.total) ]]
+ <template slot="clients" slot-scope="text, dbInbound">
+ <template v-if="clientCount[dbInbound.id]">
+ <a-tag :style="{ margin: '0' }" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
+ <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
+ <template slot="content">
+ <div v-for="clientEmail in clientCount[dbInbound.id].deactive" :key="clientEmail" class="client-popup-item">
+ <span>[[ clientEmail ]]</span>
+ <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
+ <template #title>
+ [[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
+ </template>
+ <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
+ </a-tooltip>
+ </div>
+ </template>
+ <a-tag :style="{ margin: '0', padding: '0 2px' }" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
+ </a-popover>
+ <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
+ <template slot="content">
+ <div v-for="clientEmail in clientCount[dbInbound.id].depleted" :key="clientEmail" class="client-popup-item">
+ <span>[[ clientEmail ]]</span>
+ <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
+ <template #title>
+ [[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
+ </template>
+ <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
+ </a-tooltip>
+ </div>
+ </template>
+ <a-tag :style="{ margin: '0', padding: '0 2px' }" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
+ </a-popover>
+ <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
+ <template slot="content">
+ <div v-for="clientEmail in clientCount[dbInbound.id].expiring" :key="clientEmail" class="client-popup-item">
+ <span>[[ clientEmail ]]</span>
+ <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
+ <template #title>
+ [[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
+ </template>
+ <a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
+ </a-tooltip>
+ </div>
+ </template>
+ <a-tag :style="{ margin: '0', padding: '0 2px' }" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
+ </a-popover>
+ <a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
+ <templat