add hysteria inbound

Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
This commit is contained in:
MHSanaei
2026-04-20 16:05:27 +02:00
parent c188056f64
commit ae5ad505d0
19 changed files with 1081 additions and 511 deletions

View File

@@ -2,14 +2,18 @@
{{ template "page/head_end" .}}
{{ template "page/body_start" .}}
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme + ' inbounds-page'">
<a-layout id="app" v-cloak
:class="themeSwitcher.currentTheme + ' inbounds-page'">
<a-sidebar></a-sidebar>
<a-layout id="content-layout">
<a-layout-content>
<a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}' size="large">
<a-spin :spinning="loadingStates.spinning" :delay="500"
tip='{{ i18n "loading"}}' size="large">
<transition name="list" appear>
<a-alert type="error" v-if="showAlert && loadingStates.fetched" :style="{ marginBottom: '10px' }"
message='{{ i18n "secAlertTitle" }}' color="red" description='{{ i18n "secAlertSsl" }}' show-icon closable>
<a-alert type="error" v-if="showAlert && loadingStates.fetched"
:style="{ marginBottom: '10px' }"
message='{{ i18n "secAlertTitle" }}' color="red"
description='{{ i18n "secAlertSsl" }}' show-icon closable>
</a-alert>
</transition>
<transition name="list" appear>
@@ -21,7 +25,8 @@
<a-card size="small" :style="{ padding: '16px' }" hoverable>
<a-row>
<a-col :sm="12" :md="5">
<a-custom-statistic title='{{ i18n "pages.inbounds.totalDownUp" }}'
<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>
@@ -29,7 +34,8 @@
</a-custom-statistic>
</a-col>
<a-col :sm="12" :md="5">
<a-custom-statistic title='{{ i18n "pages.inbounds.totalUsage" }}'
<a-custom-statistic
title='{{ i18n "pages.inbounds.totalUsage" }}'
:value="SizeFormatter.sizeFormat(total.up + total.down)"
:style="{ marginTop: isMobile ? '10px' : 0 }">
<template #prefix>
@@ -38,15 +44,19 @@
</a-custom-statistic>
</a-col>
<a-col :sm="12" :md="5">
<a-custom-statistic title='{{ i18n "pages.inbounds.allTimeTrafficUsage" }}'
:value="SizeFormatter.sizeFormat(total.allTime)" :style="{ marginTop: isMobile ? '10px' : 0 }">
<a-custom-statistic
title='{{ i18n "pages.inbounds.allTimeTrafficUsage" }}'
:value="SizeFormatter.sizeFormat(total.allTime)"
:style="{ marginTop: isMobile ? '10px' : 0 }">
<template #prefix>
<a-icon type="history"></a-icon>
</template>
</a-custom-statistic>
</a-col>
<a-col :sm="12" :md="5">
<a-custom-statistic title='{{ i18n "pages.inbounds.inboundCount" }}' :value="dbInbounds.length"
<a-custom-statistic
title='{{ i18n "pages.inbounds.inboundCount" }}'
:value="dbInbounds.length"
:style="{ marginTop: isMobile ? '10px' : 0 }">
<template #prefix>
<a-icon type="bars"></a-icon>
@@ -60,33 +70,50 @@
<a-space direction="horizontal">
<a-icon type="team"></a-icon>
<div>
<a-back-top :target="() => document.getElementById('content-layout')"
<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">
<a-popover title='{{ i18n "disabled" }}'
:overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<div v-for="clientEmail in total.deactive"><span>[[ clientEmail ]]</span></div>
<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-tag v-if="total.deactive.length">[[
total.deactive.length ]]</a-tag>
</a-popover>
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
<a-popover title='{{ i18n "depleted" }}'
:overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<div v-for="clientEmail in total.depleted"><span>[[ clientEmail ]]</span></div>
<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-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>
<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-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">
<a-popover title='{{ i18n "online" }}'
:overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<div v-for="clientEmail in onlineClients"><span>[[ clientEmail ]]</span></div>
<div
v-for="clientEmail in onlineClients"><span>[[
clientEmail ]]</span></div>
</template>
<a-tag color="blue" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag>
<a-tag color="blue" v-if="onlineClients.length">[[
onlineClients.length ]]</a-tag>
</a-popover>
</div>
</a-space>
@@ -100,14 +127,18 @@
<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 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>
<template v-if="!isMobile">{{ i18n
"pages.inbounds.generalActions" }}</template>
</a-button>
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
<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" }}
@@ -118,7 +149,8 @@
</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>
@@ -128,7 +160,8 @@
<a-icon type="file-done"></a-icon>
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
</a-menu-item>
<a-menu-item key="delDepletedClients" :style="{ color: '#FF4D4F' }">
<a-menu-item key="delDepletedClients"
:style="{ color: '#FF4D4F' }">
<a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }}
</a-menu-item>
@@ -138,20 +171,28 @@
</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">
<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>
<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>
<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>
@@ -160,27 +201,38 @@
</a-button-group>
</template>
<a-space direction="vertical">
<div :style="isMobile ? {} : { display: 'flex', alignItems: 'center', justifyContent: 'flex-start' }">
<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"
<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-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"
<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' }"
@@ -189,7 +241,8 @@
<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)"
<a-menu slot="overlay"
@click="a => clickAction(a, dbInbound)"
:theme="themeSwitcher.currentTheme">
<a-menu-item key="edit">
<a-icon type="edit"></a-icon>
@@ -211,7 +264,8 @@
</a-menu-item>
<a-menu-item key="resetClients">
<a-icon type="file-done"></a-icon>
{{ i18n "pages.inbounds.resetInboundClientTraffics"}}
{{ i18n
"pages.inbounds.resetInboundClientTraffics"}}
</a-menu-item>
<a-menu-item key="export">
<a-icon type="export"></a-icon>
@@ -219,9 +273,11 @@
</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="delDepletedClients" :style="{ color: '#FF4D4F' }">
<a-menu-item key="delDepletedClients"
:style="{ color: '#FF4D4F' }">
<a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }}
</a-menu-item>
@@ -237,10 +293,12 @@
{{ i18n "pages.inbounds.exportInbound" }}
</a-menu-item>
<a-menu-item key="resetTraffic">
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.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-icon type="block"></a-icon> {{ i18n
"pages.inbounds.clone"}}
</a-menu-item>
<a-menu-item key="delete">
<span :style="{ color: '#FF4D4F' }">
@@ -256,26 +314,38 @@
</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"
<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"
<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">
<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"
<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">
<a-tooltip
:overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
[[
clientCount[dbInbound.id].comments.get(clientEmail)
]]
</template>
<a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
@@ -286,84 +356,113 @@
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">
<a-popover title='{{ i18n "depleted" }}'
:overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].depleted" :key="clientEmail"
<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">
<a-tooltip
:overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
[[
clientCount[dbInbound.id].comments.get(clientEmail)
]]
</template>
<a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip>
</div>
</template>
<a-tag :style="{ margin: '0', padding: '0 2px' }" color="red"
<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">
<a-popover title='{{ i18n "depletingSoon" }}'
:overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].expiring" :key="clientEmail"
<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">
<a-tooltip
:overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
[[
clientCount[dbInbound.id].comments.get(clientEmail)
]]
</template>
<a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip>
</div>
</template>
<a-tag :style="{ margin: '0', padding: '0 2px' }" color="orange"
<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">
<a-popover title='{{ i18n "online" }}'
:overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].online" :key="clientEmail"
<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">
<a-tooltip
:overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
[[
clientCount[dbInbound.id].comments.get(clientEmail)
]]
</template>
<a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip>
</div>
</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 :style="{ margin: '0', padding: '0 2px' }"
color="blue"
v-if="clientCount[dbInbound.id].online.length">[[
clientCount[dbInbound.id].online.length
]]</a-tag>
</a-popover>
</template>
</template>
<template slot="traffic" slot-scope="text, dbInbound">
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
<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>
<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">
<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>
<td>[[ SizeFormatter.sizeFormat(dbInbound.total -
dbInbound.up - dbInbound.down) ]]</td>
</tr>
</table>
</template>
<a-tag
:color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
[[ SizeFormatter.sizeFormat(dbInbound.up +
dbInbound.down) ]] /
<template v-if="dbInbound.total > 0">
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
</template>
<template v-else>
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<svg height="10px" width="14px"
viewBox="0 0 640 512" fill="currentColor">
<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"
fill="currentColor"></path>
@@ -372,25 +471,30 @@
</a-tag>
</a-popover>
</template>
<template slot="allTimeInbound" slot-scope="text, dbInbound">
<a-tag>[[ SizeFormatter.sizeFormat(dbInbound.allTime || 0) ]]</a-tag>
<template slot="allTimeInbound"
slot-scope="text, dbInbound">
<a-tag>[[ SizeFormatter.sizeFormat(dbInbound.allTime || 0)
]]</a-tag>
</template>
<template slot="enable" slot-scope="text, dbInbound">
<a-switch v-model="dbInbound.enable"
@change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
</template>
<template slot="expiryTime" slot-scope="text, dbInbound">
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
<a-popover v-if="dbInbound.expiryTime > 0"
:overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
[[ IntlUtil.formatDate(dbInbound.expiryTime) ]]
</template>
<a-tag :style="{ minWidth: '50px' }"
:color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
[[ IntlUtil.formatRelativeTime(dbInbound.expiryTime) ]]
[[ IntlUtil.formatRelativeTime(dbInbound.expiryTime)
]]
</a-tag>
</a-popover>
<a-tag v-else color="purple" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<svg height="10px" width="14px" viewBox="0 0 640 512"
fill="currentColor">
<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"
fill="currentColor"></path>
@@ -398,21 +502,28 @@
</a-tag>
</template>
<template slot="info" slot-scope="text, dbInbound">
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme"
<a-popover placement="bottomRight"
:overlay-class-name="themeSwitcher.currentTheme"
trigger="click">
<template slot="content">
<table cellpadding="2">
<tr>
<td>{{ i18n "pages.inbounds.protocol" }}</td>
<td>
<a-tag :style="{ margin: '0' }" color="purple">[[ dbInbound.protocol ]]</a-tag>
<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="blue">[[ dbInbound.toInbound().stream.network
<a-tag :style="{ margin: '0' }"
color="blue">[[
dbInbound.toInbound().stream.network
]]</a-tag>
<a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isTls"
<a-tag :style="{ margin: '0' }"
v-if="dbInbound.toInbound().stream.isTls"
color="green">tls</a-tag>
<a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isReality"
<a-tag :style="{ margin: '0' }"
v-if="dbInbound.toInbound().stream.isReality"
color="green">reality</a-tag>
</template>
</td>
@@ -424,111 +535,156 @@
<tr v-if="clientCount[dbInbound.id]">
<td>{{ i18n "clients" }}</td>
<td>
<a-tag :style="{ margin: '0' }" color="blue">[[ clientCount[dbInbound.id].clients
<a-tag :style="{ margin: '0' }" color="blue">[[
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"
<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">
<a-tooltip
:overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
[[
clientCount[dbInbound.id].comments.get(clientEmail)
]]
</template>
<a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip>
</div>
</template>
<a-tag :style="{ margin: '0', padding: '0 2px' }"
<a-tag
:style="{ margin: '0', padding: '0 2px' }"
v-if="clientCount[dbInbound.id].deactive.length">[[
clientCount[dbInbound.id].deactive.length ]]</a-tag>
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"
<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">
<a-tooltip
:overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
[[
clientCount[dbInbound.id].comments.get(clientEmail)
]]
</template>
<a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip>
</div>
</template>
<a-tag :style="{ margin: '0', padding: '0 2px' }" color="red"
<a-tag
:style="{ margin: '0', padding: '0 2px' }"
color="red"
v-if="clientCount[dbInbound.id].depleted.length">[[
clientCount[dbInbound.id].depleted.length ]]</a-tag>
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"
<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">
<a-tooltip
:overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
[[
clientCount[dbInbound.id].comments.get(clientEmail)
]]
</template>
<a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip>
</div>
</template>
<a-tag :style="{ margin: '0', padding: '0 2px' }" color="orange"
<a-tag
:style="{ margin: '0', padding: '0 2px' }"
color="orange"
v-if="clientCount[dbInbound.id].expiring.length">[[
clientCount[dbInbound.id].expiring.length ]]</a-tag>
clientCount[dbInbound.id].expiring.length
]]</a-tag>
</a-popover>
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
<a-popover title='{{ i18n "online" }}'
:overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].online" :key="clientEmail"
<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">
<a-tooltip
:overlay-class-name="themeSwitcher.currentTheme">
<template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
[[
clientCount[dbInbound.id].comments.get(clientEmail)
]]
</template>
<a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip>
</div>
</template>
<a-tag :style="{ margin: '0', padding: '0 2px' }" color="green"
<a-tag
:style="{ margin: '0', padding: '0 2px' }"
color="green"
v-if="clientCount[dbInbound.id].online.length">[[
clientCount[dbInbound.id].online.length ]]</a-tag>
clientCount[dbInbound.id].online.length
]]</a-tag>
</a-popover>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.traffic" }}</td>
<td>
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
<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>
<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>[[
SizeFormatter.sizeFormat(dbInbound.total
- dbInbound.up - dbInbound.down)
]]</td>
</tr>
</table>
</template>
<a-tag
:color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
[[ SizeFormatter.sizeFormat(dbInbound.up +
dbInbound.down) ]] /
<template v-if="dbInbound.total > 0">
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
[[
SizeFormatter.sizeFormat(dbInbound.total)
]]
</template>
<template v-else>
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<svg height="10px" width="14px"
viewBox="0 0 640 512"
fill="currentColor">
<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"
fill="currentColor"></path>
@@ -541,12 +697,17 @@
<tr>
<td>{{ i18n "pages.inbounds.expireDate" }}</td>
<td>
<a-tag :style="{ minWidth: '50px', textAlign: 'center' }"
v-if="dbInbound.expiryTime > 0" :color="dbInbound.isExpiry? 'red': 'blue'">
[[ IntlUtil.formatDate(dbInbound.expiryTime) ]]
<a-tag
:style="{ minWidth: '50px', textAlign: 'center' }"
v-if="dbInbound.expiryTime > 0"
:color="dbInbound.isExpiry? 'red': 'blue'">
[[ IntlUtil.formatDate(dbInbound.expiryTime)
]]
</a-tag>
<a-tag v-else :style="{ textAlign: 'center' }" color="purple" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<a-tag v-else :style="{ textAlign: 'center' }"
color="purple" class="infinite-tag">
<svg height="10px" width="14px"
viewBox="0 0 640 512" fill="currentColor">
<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"
fill="currentColor"></path>
@@ -555,25 +716,32 @@
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.periodicTrafficResetTitle" }}</td>
<td>{{ i18n
"pages.inbounds.periodicTrafficResetTitle"
}}</td>
<td>
<a-tag color="blue">[[ dbInbound.trafficReset ]]</a-tag>
<a-tag color="blue">[[ dbInbound.trafficReset
]]</a-tag>
</td>
</tr>
</table>
</template>
<a-badge>
<a-icon v-if="!dbInbound.enable" slot="count" type="pause-circle"
<a-icon v-if="!dbInbound.enable" slot="count"
type="pause-circle"
:style="{ color: themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc' }"></a-icon>
<a-button shape="round" size="small" :style="{ fontSize: '14px', padding: '0 10px' }">
<a-button shape="round" size="small"
:style="{ fontSize: '14px', padding: '0 10px' }">
<a-icon type="info"></a-icon>
</a-button>
</a-badge>
</a-popover>
</template>
<template slot="expandedRowRender" slot-scope="record">
<a-table :row-key="client => client.id" :columns="isMobile ? innerMobileColumns : innerColumns"
:data-source="getInboundClients(record)" :pagination=pagination(getInboundClients(record))
<a-table :row-key="client => client.id"
:columns="isMobile ? innerMobileColumns : innerColumns"
:data-source="getInboundClients(record)"
:pagination=pagination(getInboundClients(record))
:style="{ margin: `-10px ${isMobile ? '2px' : '22px'} -11px` }">
{{template "component/aClientTable"}}
</a-table>
@@ -589,11 +757,15 @@
</a-layout>
</a-layout>
{{template "page/body_scripts" .}}
<script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script>
<script
src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/uri/URI.min.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/reality_targets.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
<script
src="{{ .base_path }}assets/js/model/reality_targets.js?{{ .cur_ver }}"></script>
<script
src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script>
<script
src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
{{template "component/aSidebar" .}}
{{template "component/aThemeSwitch" .}}
{{template "component/aCustomStatistic" .}}
@@ -1247,6 +1419,7 @@
switch (protocol) {
case Protocols.TROJAN: return client.password;
case Protocols.SHADOWSOCKS: return client.email;
case Protocols.HYSTERIA: return client.auth;
default: return client.id;
}
},
@@ -1322,19 +1495,25 @@
this.submit(`/panel/api/inbounds/update/${dbInboundId}`, formData);
},
async switchEnableClient(dbInboundId, client) {
this.loading()
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
inbound = dbInbound.toInbound();
clients = inbound && inbound.clients ? inbound.clients : null;
if (!clients || !Array.isArray(clients)) return;
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
if (index < 0 || !clients[index]) return;
clients[index].enable = !clients[index].enable;
clientId = this.getClientId(dbInbound.protocol, clients[index]);
await this.updateClient(clients[index], dbInboundId, clientId);
this.loading(false);
async switchEnableClient(dbInboundId, client, state) {
this.loading();
try {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
inbound = dbInbound.toInbound();
clients = inbound && inbound.clients ? inbound.clients : null;
if (!clients || !Array.isArray(clients)) return;
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
if (index < 0 || !clients[index]) return;
clients[index].enable = typeof state === 'boolean' ? state : !!client.enable;
clientId = this.getClientId(dbInbound.protocol, clients[index]);
await this.updateClient(clients[index], dbInboundId, clientId);
} finally {
this.loading(false);
}
},
async submit(url, data, modal) {
const msg = await HttpUtil.postWithModal(url, data, modal);