bug fix: del Depleted

This commit is contained in:
mhsanaei 2025-09-16 18:28:02 +02:00
parent f137b1af76
commit dc21f41932
No known key found for this signature in database
GPG key ID: D875CD086CF668A0
4 changed files with 1169 additions and 1080 deletions

View file

@ -37,7 +37,7 @@
<template slot="content" > <template slot="content" >
{{ i18n "lastOnline" }}: [[ formatLastOnline(client.email) ]] {{ i18n "lastOnline" }}: [[ formatLastOnline(client.email) ]]
</template> </template>
<template v-if="client.enable && isClientOnline(client.email)"> <template v-if="client.enable && isClientOnline(client.email) && !isClientDepleted">
<a-tag color="green">{{ i18n "online" }}</a-tag> <a-tag color="green">{{ i18n "online" }}</a-tag>
</template> </template>
<template v-else> <template v-else>
@ -49,9 +49,9 @@
<a-space direction="horizontal" :size="2"> <a-space direction="horizontal" :size="2">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template> <template v-if="isClientDepleted">{{ i18n "depleted" }}</template>
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template> <template v-if="!isClientDepleted && !client.enable">{{ i18n "disabled" }}</template>
<template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template> <template v-if="!isClientDepleted && client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template>
</template> </template>
<a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-badge> <a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-badge>
</a-tooltip> </a-tooltip>

View file

@ -9,15 +9,13 @@
<a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}'> <a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}'>
<transition name="list" appear> <transition name="list" appear>
<a-alert type="error" v-if="showAlert && loadingStates.fetched" :style="{ marginBottom: '10px' }" <a-alert type="error" v-if="showAlert && loadingStates.fetched" :style="{ marginBottom: '10px' }"
message='{{ i18n "secAlertTitle" }}' message='{{ i18n "secAlertTitle" }}' color="red" description='{{ i18n "secAlertSsl" }}' show-icon closable>
color="red"
description='{{ i18n "secAlertSsl" }}'
show-icon closable>
</a-alert> </a-alert>
</transition> </transition>
<transition name="list" appear> <transition name="list" appear>
<a-row v-if="!loadingStates.fetched"> <a-row v-if="!loadingStates.fetched">
<a-card :style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent', border: 'none' }"> <a-card
:style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent', border: 'none' }">
<a-spin tip='{{ i18n "loading" }}'></a-spin> <a-spin tip='{{ i18n "loading" }}'></a-spin>
</a-card> </a-card>
</a-row> </a-row>
@ -26,40 +24,47 @@
<a-card size="small" :style="{ padding: '16px' }" hoverable> <a-card size="small" :style="{ padding: '16px' }" hoverable>
<a-row> <a-row>
<a-col :sm="12" :md="5"> <a-col :sm="12" :md="5">
<a-custom-statistic title='{{ i18n "pages.inbounds.totalDownUp" }}' :value="`${SizeFormatter.sizeFormat(total.up)} / ${SizeFormatter.sizeFormat(total.down)}`"> <a-custom-statistic title='{{ i18n "pages.inbounds.totalDownUp" }}'
:value="`${SizeFormatter.sizeFormat(total.up)} / ${SizeFormatter.sizeFormat(total.down)}`">
<template #prefix> <template #prefix>
<a-icon type="swap"></a-icon> <a-icon type="swap"></a-icon>
</template> </template>
</a-custom-statistic> </a-custom-statistic>
</a-col> </a-col>
<a-col :sm="12" :md="5"> <a-col :sm="12" :md="5">
<a-custom-statistic title='{{ i18n "pages.inbounds.totalUsage" }}' :value="SizeFormatter.sizeFormat(total.up + total.down)" :style="{ marginTop: isMobile ? '10px' : 0 }"> <a-custom-statistic title='{{ i18n "pages.inbounds.totalUsage" }}'
:value="SizeFormatter.sizeFormat(total.up + total.down)"
:style="{ marginTop: isMobile ? '10px' : 0 }">
<template #prefix> <template #prefix>
<a-icon type="pie-chart"></a-icon> <a-icon type="pie-chart"></a-icon>
</template> </template>
</a-custom-statistic> </a-custom-statistic>
</a-col> </a-col>
<a-col :sm="12" :md="5"> <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> <template #prefix>
<a-icon type="history"></a-icon> <a-icon type="history"></a-icon>
</template> </template>
</a-custom-statistic> </a-custom-statistic>
</a-col> </a-col>
<a-col :sm="12" :md="5"> <a-col :sm="12" :md="5">
<a-custom-statistic title='{{ i18n "pages.inbounds.inboundCount" }}' :value="dbInbounds.length" :style="{ marginTop: isMobile ? '10px' : 0 }"> <a-custom-statistic title='{{ i18n "pages.inbounds.inboundCount" }}' :value="dbInbounds.length"
:style="{ marginTop: isMobile ? '10px' : 0 }">
<template #prefix> <template #prefix>
<a-icon type="bars"></a-icon> <a-icon type="bars"></a-icon>
</template> </template>
</a-custom-statistic> </a-custom-statistic>
</a-col> </a-col>
<a-col :sm="12" :md="4"> <a-col :sm="12" :md="4">
<a-custom-statistic title='{{ i18n "clients" }}' value=" " :style="{ marginTop: isMobile ? '10px' : 0 }"> <a-custom-statistic title='{{ i18n "clients" }}' value=" "
:style="{ marginTop: isMobile ? '10px' : 0 }">
<template #prefix> <template #prefix>
<a-space direction="horizontal"> <a-space direction="horizontal">
<a-icon type="team"></a-icon> <a-icon type="team"></a-icon>
<div> <div>
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"></a-back-top> <a-back-top :target="() => document.getElementById('content-layout')"
visibility-height="200"></a-back-top>
<a-tag color="green">[[ total.clients ]]</a-tag> <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"> <template slot="content">
@ -73,7 +78,8 @@
</template> </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>
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-popover title='{{ i18n "depletingSoon" }}'
:overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <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> </template>
@ -146,11 +152,8 @@
<template #content> <template #content>
<a-space direction="vertical"> <a-space direction="vertical">
<span>{{ i18n "pages.inbounds.autoRefreshInterval" }}</span> <span>{{ i18n "pages.inbounds.autoRefreshInterval" }}</span>
<a-select v-model="refreshInterval" <a-select v-model="refreshInterval" :disabled="!isRefreshEnabled" :style="{ width: '100%' }"
:disabled="!isRefreshEnabled" @change="changeRefreshInterval" :dropdown-class-name="themeSwitcher.currentTheme">
: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-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
</a-select> </a-select>
</a-space> </a-space>
@ -167,8 +170,10 @@
<a-icon slot="checkedChildren" type="search"></a-icon> <a-icon slot="checkedChildren" type="search"></a-icon>
<a-icon slot="unCheckedChildren" type="filter"></a-icon> <a-icon slot="unCheckedChildren" type="filter"></a-icon>
</a-switch> </a-switch>
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus :style="{ maxWidth: '300px' }" :size="isMobile ? 'small' : ''"></a-input> <a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid" :size="isMobile ? 'small' : ''"> :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="">{{ i18n "none" }}</a-radio-button>
<a-radio-button value="deactive">{{ i18n "disabled" }}</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="depleted">{{ i18n "depleted" }}</a-radio-button>
@ -177,25 +182,24 @@
</a-radio-group> </a-radio-group>
</div> </div>
<a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id" <a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id"
:data-source="searchedInbounds" :data-source="searchedInbounds" :scroll="isMobile ? {} : { x: 1000 }"
:scroll="isMobile ? {} : { x: 1000 }" :pagination=pagination(searchedInbounds) :expand-icon-as-cell="false" :expand-row-by-click="false"
:pagination=pagination(searchedInbounds) :expand-icon-column-index="0" :indent-size="0"
: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')" :row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
:style="{ marginTop: '10px' }" :style="{ marginTop: '10px' }"
:locale='{ filterConfirm: `{{ i18n "confirm" }}`, filterReset: `{{ i18n "reset" }}`, emptyText: `{{ i18n "noData" }}` }'> :locale='{ filterConfirm: `{{ i18n "confirm" }}`, filterReset: `{{ i18n "reset" }}`, emptyText: `{{ i18n "noData" }}` }'>
<template slot="action" slot-scope="text, dbInbound"> <template slot="action" slot-scope="text, dbInbound">
<a-dropdown :trigger="['click']"> <a-dropdown :trigger="['click']">
<a-icon @click="e => e.preventDefault()" type="more" :style="{ fontSize: '20px', textDecoration: 'solid' }"></a-icon> <a-icon @click="e => e.preventDefault()" type="more"
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme"> :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-menu-item key="edit">
<a-icon type="edit"></a-icon> <a-icon type="edit"></a-icon>
{{ i18n "edit" }} {{ i18n "edit" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard"> <a-menu-item key="qrcode"
v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard">
<a-icon type="qrcode"></a-icon> <a-icon type="qrcode"></a-icon>
{{ i18n "qrCode" }} {{ i18n "qrCode" }}
</a-menu-item> </a-menu-item>
@ -247,7 +251,8 @@
</span> </span>
</a-menu-item> </a-menu-item>
<a-menu-item v-if="isMobile"> <a-menu-item v-if="isMobile">
<a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch> <a-switch size="small" v-model="dbInbound.enable"
@change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
{{ i18n "pages.inbounds.enable" }} {{ i18n "pages.inbounds.enable" }}
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
@ -257,8 +262,10 @@
<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"> <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' }" 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.isTls"
<a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag> color="blue">TLS</a-tag>
<a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isReality"
color="blue">Reality</a-tag>
</template> </template>
</template> </template>
<template slot="clients" slot-scope="text, dbInbound"> <template slot="clients" slot-scope="text, dbInbound">
@ -266,59 +273,75 @@
<a-tag :style="{ margin: '0' }" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag> <a-tag :style="{ margin: '0' }" color="green">[[ clientCount[dbInbound.id].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"> <template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].deactive" :key="clientEmail" class="client-popup-item"> <div v-for="clientEmail in clientCount[dbInbound.id].deactive" :key="clientEmail"
class="client-popup-item">
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon> <a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </template>
<a-tag :style="{ margin: '0', padding: '0 2px' }" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag> <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>
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].depleted" :key="clientEmail" class="client-popup-item"> <div v-for="clientEmail in clientCount[dbInbound.id].depleted" :key="clientEmail"
class="client-popup-item">
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon> <a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </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-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>
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].expiring" :key="clientEmail" class="client-popup-item"> <div v-for="clientEmail in clientCount[dbInbound.id].expiring" :key="clientEmail"
class="client-popup-item">
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon> <a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </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-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>
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].online" :key="clientEmail" class="client-popup-item"> <div v-for="clientEmail in clientCount[dbInbound.id].online" :key="clientEmail"
class="client-popup-item">
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon> <a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </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-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> </a-popover>
</template> </template>
</template> </template>
@ -336,14 +359,17 @@
</tr> </tr>
</table> </table>
</template> </template>
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)"> <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"> <template v-if="dbInbound.total > 0">
[[ SizeFormatter.sizeFormat(dbInbound.total) ]] [[ SizeFormatter.sizeFormat(dbInbound.total) ]]
</template> </template>
<template v-else> <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> <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>
</svg> </svg>
</template> </template>
</a-tag> </a-tag>
@ -353,7 +379,8 @@
<a-tag>[[ SizeFormatter.sizeFormat(dbInbound.allTime || 0) ]]</a-tag> <a-tag>[[ SizeFormatter.sizeFormat(dbInbound.allTime || 0) ]]</a-tag>
</template> </template>
<template slot="enable" slot-scope="text, dbInbound"> <template slot="enable" slot-scope="text, dbInbound">
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch> <a-switch v-model="dbInbound.enable"
@change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
</template> </template>
<template slot="expiryTime" slot-scope="text, dbInbound"> <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">
@ -363,28 +390,36 @@
<template v-else slot="content"> <template v-else slot="content">
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]] [[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
</template> </template>
<a-tag :style="{ minWidth: '50px' }" :color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)"> <a-tag :style="{ minWidth: '50px' }"
:color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
[[ remainedDays(dbInbound._expiryTime) ]] [[ remainedDays(dbInbound._expiryTime) ]]
</a-tag> </a-tag>
</a-popover> </a-popover>
<a-tag v-else color="purple" class="infinite-tag"> <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> <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>
</svg> </svg>
</a-tag> </a-tag>
</template> </template>
<template slot="info" slot-scope="text, dbInbound"> <template slot="info" slot-scope="text, dbInbound">
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click"> <a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme"
trigger="click">
<template slot="content"> <template slot="content">
<table cellpadding="2"> <table cellpadding="2">
<tr> <tr>
<td>{{ i18n "pages.inbounds.protocol" }}</td> <td>{{ i18n "pages.inbounds.protocol" }}</td>
<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"> <template
<a-tag :style="{ margin: '0' }" color="blue">[[ dbInbound.toInbound().stream.network ]]</a-tag> v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isTls" color="green">tls</a-tag> <a-tag :style="{ margin: '0' }" color="blue">[[ dbInbound.toInbound().stream.network
<a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isReality" color="green">reality</a-tag> ]]</a-tag>
<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"
color="green">reality</a-tag>
</template> </template>
</td> </td>
</tr> </tr>
@ -395,62 +430,82 @@
<tr v-if="clientCount[dbInbound.id]"> <tr v-if="clientCount[dbInbound.id]">
<td>{{ i18n "clients" }}</td> <td>{{ i18n "clients" }}</td>
<td> <td>
<a-tag :style="{ margin: '0' }" color="blue">[[ clientCount[dbInbound.id].clients ]]</a-tag> <a-tag :style="{ margin: '0' }" color="blue">[[ clientCount[dbInbound.id].clients
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme"> ]]</a-tag>
<a-popover title='{{ i18n "disabled" }}'
:overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].deactive" :key="clientEmail" class="client-popup-item"> <div v-for="clientEmail in clientCount[dbInbound.id].deactive" :key="clientEmail"
class="client-popup-item">
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon> <a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </template>
<a-tag :style="{ margin: '0', padding: '0 2px' }" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag> <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>
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-popover title='{{ i18n "depleted" }}'
:overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].depleted" :key="clientEmail" class="client-popup-item"> <div v-for="clientEmail in clientCount[dbInbound.id].depleted" :key="clientEmail"
class="client-popup-item">
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon> <a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </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-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>
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-popover title='{{ i18n "depletingSoon" }}'
:overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].expiring" :key="clientEmail" class="client-popup-item"> <div v-for="clientEmail in clientCount[dbInbound.id].expiring" :key="clientEmail"
class="client-popup-item">
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon> <a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </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-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>
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme"> <a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <template slot="content">
<div v-for="clientEmail in clientCount[dbInbound.id].online" :key="clientEmail" class="client-popup-item"> <div v-for="clientEmail in clientCount[dbInbound.id].online" :key="clientEmail"
class="client-popup-item">
<span>[[ clientEmail ]]</span> <span>[[ clientEmail ]]</span>
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme"> <a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
<template #title> <template #title>
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]] [[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
</template> </template>
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon> <a-icon type="message"
v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </template>
<a-tag :style="{ margin: '0', padding: '0 2px' }" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag> <a-tag :style="{ margin: '0', padding: '0 2px' }" color="green"
v-if="clientCount[dbInbound.id].online.length">[[
clientCount[dbInbound.id].online.length ]]</a-tag>
</a-popover> </a-popover>
</td> </td>
</tr> </tr>
@ -464,20 +519,25 @@
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td> <td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td> <td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
</tr> </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>{{ i18n "remained" }}</td>
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td> <td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down)
]]</td>
</tr> </tr>
</table> </table>
</template> </template>
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)"> <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"> <template v-if="dbInbound.total > 0">
[[ SizeFormatter.sizeFormat(dbInbound.total) ]] [[ SizeFormatter.sizeFormat(dbInbound.total) ]]
</template> </template>
<template v-else> <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> <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>
</svg> </svg>
</template> </template>
</a-tag> </a-tag>
@ -487,8 +547,8 @@
<tr> <tr>
<td>{{ i18n "pages.inbounds.expireDate" }}</td> <td>{{ i18n "pages.inbounds.expireDate" }}</td>
<td> <td>
<a-tag :style="{ minWidth: '50px', textAlign: 'center' }" v-if="dbInbound.expiryTime > 0" <a-tag :style="{ minWidth: '50px', textAlign: 'center' }"
:color="dbInbound.isExpiry? 'red': 'blue'"> v-if="dbInbound.expiryTime > 0" :color="dbInbound.isExpiry? 'red': 'blue'">
<template v-if="app.datepicker === 'gregorian'"> <template v-if="app.datepicker === 'gregorian'">
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]] [[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
</template> </template>
@ -498,7 +558,9 @@
</a-tag> </a-tag>
<a-tag v-else :style="{ textAlign: 'center' }" color="purple" class="infinite-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"> <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> <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>
</svg> </svg>
</a-tag> </a-tag>
</td> </td>
@ -506,13 +568,15 @@
<tr> <tr>
<td>{{ i18n "pages.inbounds.periodicTrafficResetTitle" }}</td> <td>{{ i18n "pages.inbounds.periodicTrafficResetTitle" }}</td>
<td> <td>
<a-tag color="blue">[[ i18n("pages.inbounds.periodicTrafficReset." + dbInbound.trafficReset) ]]</a-tag> <a-tag color="blue">[[ i18n("pages.inbounds.periodicTrafficReset." +
dbInbound.trafficReset) ]]</a-tag>
</td> </td>
</tr> </tr>
</table> </table>
</template> </template>
<a-badge> <a-badge>
<a-icon v-if="!dbInbound.enable" slot="count" type="pause-circle" :style="{ color: themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc' }"></a-icon> <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-icon type="info"></a-icon>
</a-button> </a-button>
@ -520,11 +584,8 @@
</a-popover> </a-popover>
</template> </template>
<template slot="expandedRowRender" slot-scope="record"> <template slot="expandedRowRender" slot-scope="record">
<a-table <a-table :row-key="client => client.id" :columns="isMobile ? innerMobileColumns : innerColumns"
:row-key="client => client.id" :data-source="getInboundClients(record)" :pagination=pagination(getInboundClients(record))
:columns="isMobile ? innerMobileColumns : innerColumns"
:data-source="getInboundClients(record)"
:pagination=pagination(getInboundClients(record))
:style="{ margin: `-10px ${isMobile ? '2px' : '22px'} -11px` }"> :style="{ margin: `-10px ${isMobile ? '2px' : '22px'} -11px` }">
{{template "component/aClientTable"}} {{template "component/aClientTable"}}
</a-table> </a-table>
@ -787,12 +848,15 @@
deactive.push(client.email); deactive.push(client.email);
} }
}); });
clientStats.forEach(client => { clientStats.forEach(stats => {
if (!client.enable) { const exhausted = stats.total > 0 && (stats.up + stats.down) >= stats.total;
depleted.push(client.email); const expired = stats.expiryTime > 0 && stats.expiryTime <= now;
if (expired || exhausted) {
depleted.push(stats.email);
} else { } else {
if ((client.expiryTime > 0 && (client.expiryTime - now < this.expireDiff)) || const expiringSoon = (stats.expiryTime > 0 && (stats.expiryTime - now < this.expireDiff)) ||
(client.total > 0 && (client.total - (client.up + client.down) < this.trafficDiff))) expiring.push(client.email); (stats.total > 0 && (stats.total - (stats.up + stats.down) < this.trafficDiff));
if (expiringSoon) expiring.push(stats.email);
} }
}); });
} else { } else {
@ -1369,6 +1433,16 @@
clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null; clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null;
return clientStats ? clientStats['enable'] : true; return clientStats ? clientStats['enable'] : true;
}, },
// Returns true when client's traffic is exhausted or expiry time is passed
isClientDepleted(dbInbound, email) {
if (!email || !dbInbound || !dbInbound.clientStats) return false;
const stats = dbInbound.clientStats.find(s => s.email === email);
if (!stats) return false;
const now = new Date().getTime();
const exhausted = stats.total > 0 && (stats.up + stats.down) >= stats.total;
const expired = stats.expiryTime > 0 && now >= stats.expiryTime;
return exhausted || expired;
},
isClientOnline(email) { isClientOnline(email) {
return this.onlineClients.includes(email); return this.onlineClients.includes(email);
}, },

View file

@ -180,9 +180,9 @@
<tr> <tr>
<td>{{ i18n "status" }}</td> <td>{{ i18n "status" }}</td>
<td> <td>
<a-tag v-if="isEnable" color="green">{{ i18n "enabled" }}</a-tag> <a-tag v-if="isEnable && isActive && !isDepleted" color="green">{{ i18n "enabled" }}</a-tag>
<a-tag v-else>{{ i18n "disabled" }}</a-tag> <a-tag v-if="!isEnable && !isDepleted">{{ i18n "disabled" }}</a-tag>
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag> <a-tag v-if="isDepleted" color="red">{{ i18n "depleted" }}</a-tag>
</td> </td>
</tr> </tr>
<tr v-if="infoModal.clientStats"> <tr v-if="infoModal.clientStats">
@ -587,6 +587,14 @@
} }
return infoModal.dbInbound.isEnable; return infoModal.dbInbound.isEnable;
}, },
get isDepleted() {
const stats = this.infoModal.clientStats;
if (!stats) return false;
const now = new Date().getTime();
const expired = stats.expiryTime > 0 && now >= stats.expiryTime;
const exhausted = stats.total > 0 && (stats.up + stats.down) >= stats.total;
return expired || exhausted;
},
}, },
methods: { methods: {
copy(content) { copy(content) {

View file

@ -1837,8 +1837,14 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
whereText += "= ?" whereText += "= ?"
} }
// Only consider truly depleted clients: expired OR traffic exhausted
now := time.Now().Unix() * 1000
depletedClients := []xray.ClientTraffic{} depletedClients := []xray.ClientTraffic{}
err = db.Model(xray.ClientTraffic{}).Where(whereText+" and enable = ?", id, false).Select("inbound_id, GROUP_CONCAT(email) as email").Group("inbound_id").Find(&depletedClients).Error err = db.Model(xray.ClientTraffic{}).
Where(whereText+" and ((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?))", id, now).
Select("inbound_id, GROUP_CONCAT(email) as email").
Group("inbound_id").
Find(&depletedClients).Error
if err != nil { if err != nil {
return err return err
} }
@ -1889,7 +1895,8 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
} }
} }
err = tx.Where(whereText+" and enable = ?", id, false).Delete(xray.ClientTraffic{}).Error // Delete stats only for truly depleted clients
err = tx.Where(whereText+" and ((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?))", id, now).Delete(xray.ClientTraffic{}).Error
if err != nil { if err != nil {
return err return err
} }