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

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlireza Ahmadi <alireza7@gmail.com>2023-12-04 21:17:38 +0300
committerAlireza Ahmadi <alireza7@gmail.com>2023-12-04 21:18:53 +0300
commit729d8549e2b536a10c402a88e0d4cfde9e4baf3f (patch)
treed108e2f29516766318ac89e4ee86c719eb14c1a3 /web/html/xui/inbounds.html
parentf734c821d6e62eca5a206b6048f3cd7d65f3e665 (diff)
new frontend and mobile view #1286
Diffstat (limited to 'web/html/xui/inbounds.html')
-rw-r--r--web/html/xui/inbounds.html474
1 files changed, 360 insertions, 114 deletions
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index 4e1873f8..27612917 100644
--- a/web/html/xui/inbounds.html
+++ b/web/html/xui/inbounds.html
@@ -8,27 +8,56 @@
}
}
+ @media (max-width: 768px) {
+ .ant-card-body {
+ padding: .5rem;
+ }
+ }
+
.ant-col-sm-24 {
- margin-top: 10px;
+ margin: 0.5rem -2rem 0.5rem 2rem;
}
tr.hideExpandIcon .ant-table-row-expand-icon {
display: none;
}
+ .infinite-tag {
+ padding: 0 5px;
+ border-radius: 2rem;
+ min-width: 50px;
+ }
+ .infinite-bar .ant-progress-inner .ant-progress-bg {
+ background-color: #F2EAF1;
+ border: #D5BED2 solid 1px;
+ }
+ .dark .infinite-bar .ant-progress-inner .ant-progress-bg {
+ background-color: #3c1536;
+ border: #7a316f solid 1px;
+ }
+ .ant-collapse {
+ margin: 5px 0;
+ }
+ .online-animation .ant-badge-status-dot {
+ animation: 1.2s ease infinite normal none running onlineAnimation;
+ }
+ @keyframes onlineAnimation {
+ 0%, 50%, 100% { transform: scale(1); opacity: 1; }
+ 10% { transform: scale(1.5); opacity: .2; }
+ }
</style>
<body>
-<a-layout id="app" v-cloak>
+<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
{{ template "commonSider" . }}
- <a-layout id="content-layout" :style="themeSwitcher.bgStyle">
+ <a-layout id="content-layout">
<a-layout-content>
- <a-spin :spinning="spinning" :delay="500" tip="loading">
+ <a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
<transition name="list" appear>
<a-tag v-if="false" color="red" style="margin-bottom: 10px">
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
</a-tag>
</transition>
<transition name="list" appear>
- <a-card hoverable style="margin-bottom: 20px;" :class="themeSwitcher.darkCardClass">
+ <a-card hoverable>
<a-row>
<a-col :xs="24" :sm="24" :lg="12">
{{ i18n "pages.inbounds.totalDownUp" }}:
@@ -45,36 +74,46 @@
<a-col :xs="24" :sm="24" :lg="12">
{{ i18n "clients" }}:
<a-tag color="green">[[ total.clients ]]</a-tag>
- <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.darkClass">
+ <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
</template>
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
</a-popover>
- <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass">
+ <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
</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.darkClass">
+ <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
</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">
+ <p v-for="clientEmail in onlineClients">[[ clientEmail ]]</p>
+ </template>
+ <a-tag color="blue" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag>
+ </a-popover>
</a-col>
</a-row>
</a-card>
</transition>
<transition name="list" appear>
- <a-card hoverable :class="themeSwitcher.darkCardClass">
+ <a-card hoverable>
<div slot="title">
<a-row>
- <a-col :xs="24" :sm="24" :lg="12">
- <a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
+ <a-col :xs="12" :sm="12" :lg="12">
+ <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">{{ i18n "pages.inbounds.generalActions" }}</a-button>
+ <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="export">
<a-icon type="export"></a-icon>
@@ -95,12 +134,12 @@
</a-menu>
</a-dropdown>
</a-col>
- <a-col :xs="24" :sm="24" :lg="12" style="text-align: right;">
+ <a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
<a-select v-model="refreshInterval"
style="width: 65px;"
v-if="isRefreshEnabled"
@change="changeRefreshInterval"
- :dropdown-class-name="themeSwitcher.darkCardClass">
+ :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-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
@@ -108,26 +147,32 @@
</a-col>
</a-row>
</div>
- <a-switch v-model="enableFilter"
- checked-children='{{ i18n "search" }}' un-checked-children='{{ i18n "filter" }}'
- @change="toggleFilter" style="margin-right: 10px;">
- </a-switch>
- <a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
- <a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid">
- <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-group>
- <a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
+ <div style="display: flex; align-items: center; justify-content: flex-start;">
+ <a-switch v-model="enableFilter"
+ style="margin-right: .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="max-width: 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 ? mobileColums : columns" :row-key="dbInbound => dbInbound.id"
:data-source="searchedInbounds"
- :loading="spinning" :scroll="{ x: 1200 }"
+ :scroll="isMobile ? {} : { x: 1000 }"
:pagination="false"
:expand-icon-as-cell="false"
:expand-row-by-click="false"
:expand-icon-column-index="0"
- :row-class-name="dbInbound => (dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser) ? '' : 'hideExpandIcon')"
- style="margin-top: 20px"
+ :indent-size="0"
+ :row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
+ style="margin-top: 10px"
@change="() => getDBInbounds()">
<template slot="action" slot-scope="text, dbInbound">
<a-icon type="edit" style="font-size: 22px" @click="openEditInbound(dbInbound.id);"></a-icon>
@@ -142,7 +187,7 @@
<a-icon type="qrcode"></a-icon>
{{ i18n "qrCode" }}
</a-menu-item>
- <template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser)">
+ <template v-if="dbInbound.isMultiUser()">
<a-menu-item key="addClient">
<a-icon type="user-add"></a-icon>
{{ i18n "pages.client.add"}}
@@ -181,43 +226,53 @@
<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)"></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="blue">[[ 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="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
- <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">TLS</a-tag>
- <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="cyan">XTLS</a-tag>
- <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">Reality</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.isXtls" color="blue">XTLS</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.darkClass">
+ <a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
</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.darkClass">
+ <a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
</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.darkClass">
+ <a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
</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">
+ <p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
+ </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-popover>
</template>
</template>
<template slot="traffic" slot-scope="text, dbInbound">
- <a-popover :overlay-class-name="themeSwitcher.darkClass">
+ <a-popover :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<table cellpadding="2" width="100%">
<tr>
@@ -230,7 +285,7 @@
</tr>
</table>
</template>
- <a-tag :color="dbInbound.total == 0 ? 'green' : dbInbound.up + dbInbound.down < dbInbound.total ? 'cyan' : 'red'">
+ <a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
<template v-if="dbInbound.total > 0">
[[ sizeFormat(dbInbound.total) ]]
@@ -245,35 +300,117 @@
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
</template>
<template slot="expiryTime" slot-scope="text, dbInbound">
- <template v-if="dbInbound.expiryTime > 0">
- <a-tag v-if="dbInbound.isExpiry" color="red">
- [[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
- </a-tag>
- <a-tag v-else color="blue">
+ <a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
+ <template slot="content">
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
+ </template>
+ <a-tag style="min-width: 50px;" :color="usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
+ [[ remainedDays(dbInbound._expiryTime) ]]
</a-tag>
- </template>
- <a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
+ </a-popover>
+ <a-tag v-else color="purple" class="infinite-tag">&infin;</a-tag>
+ </template>
+ <template slot="info" slot-scope="text, dbInbound">
+ <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>
+ <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
+ <a-tag style="margin:0;" color="blue">[[ dbInbound.toInbound().stream.network ]]</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>
+ </td>
+ </tr>
+ <tr>
+ <td>{{ i18n "pages.inbounds.port" }}</td>
+ <td><a-tag>[[ dbInbound.port ]]</a-tag></td>
+ </tr>
+ <tr v-if="clientCount[dbInbound.id]">
+ <td>{{ i18n "clients" }}</td>
+ <td>
+ <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">
+ <p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
+ </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">
+ <p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
+ </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">
+ <p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
+ </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">
+ <p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
+ </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-popover>
+ </td>
+ </tr>
+ <tr>
+ <td>{{ i18n "pages.inbounds.traffic" }}</td>
+ <td>
+ <a-popover :overlay-class-name="themeSwitcher.currentTheme">
+ <template slot="content">
+ <table cellpadding="2" width="100%">
+ <tr>
+ <td>↑[[ sizeFormat(dbInbound.up) ]]</td>
+ <td>↓[[ sizeFormat(dbInbound.down) ]]</td>
+ </tr>
+ <tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
+ <td>{{ i18n "remained" }}</td>
+ <td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
+ </tr>
+ </table>
+ </template>
+ <a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
+ [[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
+ <template v-if="dbInbound.total > 0">
+ [[ sizeFormat(dbInbound.total) ]]
+ </template>
+ <template v-else>&infin;</template>
+ </a-tag>
+ </a-popover>
+ </td>
+ </tr>
+ <tr>
+ <td>{{ i18n "pages.inbounds.expireDate" }}</td>
+ <td>
+ <a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0" :color="dbInbound.isExpiry? 'red': 'blue'">
+ [[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
+ </a-tag>
+ <a-tag v-else style="text-align: center;" color="purple" class="infinite-tag">&infin;</a-tag>
+ </td>
+ </tr>
+ </table>
+ </template>
+ <a-badge>
+ <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="font-size: 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
- v-if="(record.protocol === Protocols.VLESS) || (record.protocol === Protocols.VMESS)"
- :row-key="client => client.id"
- :columns="innerColumns"
- :data-source="getInboundClients(record)"
- :pagination="false"
- style="margin-left: 20px;"
- >
- {{template "client_table"}}
- </a-table>
- <a-table
- v-else-if="record.protocol === Protocols.TROJAN || record.toInbound().isSSMultiUser"
:row-key="client => client.id"
- :columns="innerTrojanColumns"
+ :columns="isMobile ? innerMobileColumns : innerColumns"
:data-source="getInboundClients(record)"
:pagination="false"
- style="margin-left: 20px;"
- >
+ :style="isMobile ? 'margin: -16px -5px -17px;' : 'margin-left: 10px;'">
{{template "client_table"}}
</a-table>
</template>
@@ -292,6 +429,7 @@
align: 'right',
dataIndex: "id",
width: 30,
+ responsive: ["xs"],
}, {
title: '{{ i18n "pages.inbounds.operate" }}',
align: 'center',
@@ -320,7 +458,7 @@
}, {
title: '{{ i18n "clients" }}',
align: 'left',
- width: 40,
+ width: 50,
scopedSlots: { customRender: 'clients' },
}, {
title: '{{ i18n "pages.inbounds.traffic" }}',
@@ -330,26 +468,46 @@
}, {
title: '{{ i18n "pages.inbounds.expireDate" }}',
align: 'center',
- width: 80,
+ width: 40,
scopedSlots: { customRender: 'expiryTime' },
}];
+ const mobileColums = [{
+ title: "ID",
+ align: 'right',
+ dataIndex: "id",
+ width: 10,
+ responsive: ["s"],
+ }, {
+ title: '{{ i18n "pages.inbounds.operate" }}',
+ align: 'center',
+ width: 25,
+ scopedSlots: { customRender: 'action' },
+ }, {
+ title: '{{ i18n "pages.inbounds.remark" }}',
+ align: 'left',
+ width: 70,
+ dataIndex: "remark",
+ }, {
+ title: '{{ i18n "pages.inbounds.info" }}',
+ align: 'center',
+ width: 10,
+ scopedSlots: { customRender: 'info' },
+ }];
+
const innerColumns = [
- { title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
- { title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
+ { title: '{{ i18n "pages.inbounds.operate" }}', width: 50, scopedSlots: { customRender: 'actions' } },
+ { title: '{{ i18n "pages.inbounds.enable" }}', width: 20, scopedSlots: { customRender: 'enable' } },
+ { title: '{{ i18n "online" }}', width: 20, scopedSlots: { customRender: 'online' } },
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
- { title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
- { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
- { title: 'UUID', width: 120, dataIndex: "id" },
+ { title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
+ { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
];
- const innerTrojanColumns = [
- { title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
- { title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
- { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
- { title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
- { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
- { title: '{{ i18n "password" }}', width: 170, dataIndex: "password" },
+ const innerMobileColumns = [
+ { title: '{{ i18n "pages.inbounds.operate" }}', width: 10, align: 'center', scopedSlots: { customRender: 'actionMenu' } },
+ { title: '{{ i18n "pages.inbounds.client" }}', width: 90, align: 'left', scopedSlots: { customRender: 'client' } },
+ { title: '{{ i18n "pages.inbounds.info" }}', width: 10, align: 'center', scopedSlots: { customRender: 'info' } },
];
const app = new Vue({
@@ -370,6 +528,7 @@
defaultCert: '',
defaultKey: '',
clientCount: [],
+ onlineClients: [],
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
refreshing: false,
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
@@ -380,7 +539,8 @@
domain: '',
tls: false
},
- tgBotEnable: false
+ tgBotEnable: false,
+ isMobile: window.innerWidth <= 768,
},
methods: {
loading(spinning = true) {
@@ -393,11 +553,19 @@
this.refreshing = false;
return;
}
+ await this.getOnlineUsers();
this.setInbounds(msg.obj);
setTimeout(() => {
this.refreshing = false;
}, 500);
},
+ async getOnlineUsers() {
+ const msg = await HttpUtil.post('/panel/inbound/onlines');
+ if (!msg.success) {
+ return;
+ }
+ this.onlineClients = msg.obj != null ? msg.obj : [];
+ },
async getDefaultSettings() {
const msg = await HttpUtil.post('/panel/setting/defaultSettings');
if (!msg.success) {
@@ -441,7 +609,7 @@
}
},
getClientCounts(dbInbound, inbound) {
- let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [];
+ let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [], online = [];
clients = this.getClients(dbInbound.protocol, inbound.settings);
clientStats = dbInbound.clientStats
now = new Date().getTime()
@@ -450,6 +618,7 @@
if (dbInbound.enable) {
clients.forEach(client => {
client.enable ? active.push(client.email) : deactive.push(client.email);
+ if(this.isClientOnline(client.email)) online.push(client.email);
});
clientStats.forEach(client => {
if (!client.enable) {
@@ -471,6 +640,7 @@
deactive: deactive,
depleted: depleted,
expiring: expiring,
+ online: online,
};
},
searchInbounds(key) {
@@ -547,10 +717,10 @@
clickAction(action, dbInbound) {
switch (action.key) {
case "qrcode":
- this.showQrcode(dbInbound);
+ this.showQrcode(dbInbound.id);
break;
case "showInfo":
- this.showInfo(dbInbound);
+ this.showInfo(dbInbound.id);
break;
case "edit":
this.openEditInbound(dbInbound.id);
@@ -586,6 +756,7 @@
title: '{{ i18n "pages.inbounds.cloneInbound"}} \"' + dbInbound.remark + '\"',
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
okText: '{{ i18n "pages.inbounds.cloneInboundOk"}}',
+ class: themeSwitcher.currentTheme,
cancelText: '{{ i18n "cancel" }}',
onOk: () => {
const baseInbound = dbInbound.toInbound();
@@ -752,7 +923,7 @@
this.$confirm({
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
- class: themeSwitcher.darkCardClass,
+ class: themeSwitcher.currentTheme,
okText: '{{ i18n "reset"}}',
cancelText: '{{ i18n "cancel"}}',
onOk: () => {
@@ -767,23 +938,27 @@
this.$confirm({
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
- class: themeSwitcher.darkCardClass,
+ class: themeSwitcher.currentTheme,
okText: '{{ i18n "delete"}}',
cancelText: '{{ i18n "cancel"}}',
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
});
},
- delClient(dbInboundId, client) {
+ delClient(dbInboundId, client,confirmation = true) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
clientId = this.getClientId(dbInbound.protocol, client);
- this.$confirm({
- title: '{{ i18n "pages.inbounds.deleteInbound"}}',
- content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
- class: themeSwitcher.darkCardClass,
- okText: '{{ i18n "delete"}}',
- cancelText: '{{ i18n "cancel"}}',
- onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
- });
+ if (confirmation){
+ this.$confirm({
+ title: '{{ i18n "pages.inbounds.deleteClient"}}',
+ content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
+ class: themeSwitcher.currentTheme,
+ okText: '{{ i18n "delete"}}',
+ cancelText: '{{ i18n "cancel"}}',
+ onOk: () => this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`),
+ });
+ } else {
+ this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`);
+ }
},
getClients(protocol, clientSettings) {
switch (protocol) {
@@ -860,21 +1035,25 @@
return dbInbound.toInbound().settings.shadowsockses;
}
},
- resetClientTraffic(client, dbInboundId) {
- this.$confirm({
- title: '{{ i18n "pages.inbounds.resetTraffic"}}',
- content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
- class: themeSwitcher.darkCardClass,
- okText: '{{ i18n "reset"}}',
- cancelText: '{{ i18n "cancel"}}',
- onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
- })
+ resetClientTraffic(client, dbInboundId, confirmation = true) {
+ if (confirmation){
+ this.$confirm({