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:
authorTara Rostami <132676256+TaraRostami@users.noreply.github.com>2024-03-20 13:43:37 +0300
committerGitHub <noreply@github.com>2024-03-20 13:43:37 +0300
commit0bec29f2ba5d92087704d997b9d116f0b352cf2d (patch)
treed76811ecc8604d9545049d20eda68f303f615e24
parenta7418d9708e4f03503315530e68133507ba53278 (diff)
UI Improvements (#2067)
-rw-r--r--media/3X-UI.pngbin202221 -> 231347 bytes
-rw-r--r--sub/subJsonService.go1
-rw-r--r--web/assets/css/custom.css159
-rw-r--r--web/html/common/qrcode_modal.html49
-rw-r--r--web/html/xui/inbound_client_table.html4
-rw-r--r--web/html/xui/inbounds.html867
-rw-r--r--web/html/xui/index.html589
-rw-r--r--web/html/xui/settings.html696
-rw-r--r--web/html/xui/xray.html1205
-rw-r--r--web/translation/translate.en_US.toml9
-rw-r--r--web/translation/translate.es_ES.toml9
-rw-r--r--web/translation/translate.fa_IR.toml11
-rw-r--r--web/translation/translate.id_ID.toml7
-rw-r--r--web/translation/translate.ru_RU.toml7
-rw-r--r--web/translation/translate.uk_UA.toml7
-rw-r--r--web/translation/translate.vi_VN.toml7
-rw-r--r--web/translation/translate.zh_Hans.toml7
17 files changed, 1922 insertions, 1712 deletions
diff --git a/media/3X-UI.png b/media/3X-UI.png
index 8b1af8a7..e5f76b19 100644
--- a/media/3X-UI.png
+++ b/media/3X-UI.png
Binary files differ
diff --git a/sub/subJsonService.go b/sub/subJsonService.go
index c037eb00..70d58ebe 100644
--- a/sub/subJsonService.go
+++ b/sub/subJsonService.go
@@ -44,7 +44,6 @@ func NewSubJsonService(fragment string, mux string, rules string, subService *Su
defaultRules, _ := routing["rules"].([]interface{})
json.Unmarshal([]byte(rules), &newRules)
defaultRules = append(newRules, defaultRules...)
- fmt.Printf("routing: %#v\n\nRules: %#v\n\n", routing, defaultRules)
routing["rules"] = defaultRules
configJson["routing"] = routing
}
diff --git a/web/assets/css/custom.css b/web/assets/css/custom.css
index 3be33763..eb3965f5 100644
--- a/web/assets/css/custom.css
+++ b/web/assets/css/custom.css
@@ -36,6 +36,7 @@
--dark-color-codemirror-line-selection: rgba(0, 135, 113, 0.3);
--dark-color-login-background: var(--dark-color-background);
--dark-color-login-wave: var(--dark-color-surface-200);
+ --dark-color-tooltip: rgba(61, 76, 104, 0.9);
}
html[data-theme='ultra-dark'] {
@@ -69,6 +70,7 @@ html[data-theme='ultra-dark'] {
--dark-color-codemirror-line-selection: rgba(85, 85, 85, 0.4);
--dark-color-login-background: #0a2227;
--dark-color-login-wave: #0f2d32;
+ --dark-color-tooltip: rgba(88, 93, 100, 0.9);
.ant-dropdown-menu-dark {
background-color: var(--dark-color-surface-500);
}
@@ -143,10 +145,13 @@ html {
style attribute {
text-align: center;
}
-.ant-table-tbody > tr > td,
+
.ant-table-thead > tr > th {
+ padding: 16px 8px;
+}
+
+.ant-table-tbody > tr > td {
padding: 12px 8px;
- overflow-wrap: break-word;
}
.ant-table-thead > tr > th {
color: rgba(0, 0, 0, 0.85);
@@ -155,10 +160,6 @@ style attribute {
border-bottom: 1px solid #e8e8e8;
transition: background 0.3s ease;
}
-.ant-table-row-cell-break-word {
- word-wrap: break-word;
- word-break: break-word;
-}
.ant-table table {
width: 100%;
@@ -649,11 +650,17 @@ style attribute {
.dark .ant-modal-footer,
.dark .ant-collapse-content,
.dark .ant-calendar-footer,
+.dark .ant-divider-horizontal.ant-divider-with-text-left:before,
+.dark .ant-divider-horizontal.ant-divider-with-text-left:after,
.dark .ant-divider-horizontal.ant-divider-with-text-center:before,
.dark .ant-divider-horizontal.ant-divider-with-text-center:after {
border-top-color: var(--dark-color-surface-300);
}
+.ant-divider-horizontal.ant-divider-with-text-left:before {
+ width: 10%;
+}
+
.dark .ant-progress-text,
.dark .ant-card-head,
.dark .ant-form,
@@ -712,7 +719,6 @@ style attribute {
.dark .ant-select-dropdown,
.dark .ant-select-dropdown li,
.dark .ant-select-dropdown-menu-item,
-.dark .ant-divider:not(.ant-divider-with-text-center),
.dark .client-table-header,
.dark .ant-select-selection--multiple .ant-select-selection__choice,
.dark .ant-calendar-time-picker-inner {
@@ -948,10 +954,15 @@ style attribute {
background-color: #fff;
}
+.ant-checkbox-wrapper,
+.ant-input-group-addon,
+.ant-tabs-tab,
+.ant-input::placeholder,
+.ant-collapse-header,
.ant-menu,
.ant-radio-button-wrapper {
- user-select: none;
-webkit-user-select: none;
+ user-select: none;
}
.ant-calendar-date,
@@ -1065,6 +1076,7 @@ li.ant-select-dropdown-menu-item:empty:after {
color: rgba(255, 255, 255, 0.35);
}
+.dark .ant-divider:not(.ant-divider-with-text-center, .ant-divider-with-text-left, .ant-divider-with-text-right),
.ant-dropdown-menu-dark,
.dark .ant-calendar-year-panel-year:hover,
.dark .ant-calendar-month-panel-month:hover,
@@ -1216,15 +1228,39 @@ li.ant-select-dropdown-menu-item:empty:after {
overflow-y: auto;
}
-.qr-bg {
+.qr-cv {
width: 100%;
height: 100%;
- background-color: #fff;
+ opacity: 0.8;
+ transition: all 0.3s;
+}
+
+.qr-cv:hover {
+ opacity: 1;
+}
+
+.qr-cv:active {
+ transform: scale(0.98);
+ transition: all 0.1s;
+}
+
+.dark .qr-cv {
+ filter: invert(1);
+}
+
+.qr-bg {
+ background-color: #ffffff;
display: flex;
justify-content: center;
align-content: center;
- padding: 0.5rem;
+ padding: 0.8rem;
border-radius: 1rem;
+ border: solid 1px #e8e8e8;
+}
+
+.dark .qr-bg {
+ background-color: var(--dark-color-surface-700);
+ border-color: var(--dark-color-surface-300);
}
.ant-input-group-addon:not(:first-child):not(:last-child) {
@@ -1276,3 +1312,104 @@ b, strong {
background-color: transparent !important;
cursor: default !important;
}
+
+.dark .ant-tooltip-inner,
+.dark .ant-tooltip-arrow:before {
+ background-color: var(--dark-color-tooltip);
+}
+
+.ant-select-sm .ant-select-selection__rendered {
+ margin-left: 10px;
+}
+
+.ant-collapse {
+ -moz-animation: collfade 0.3s ease;
+ -webkit-animation: 0.3s collfade 0.3s ease;
+ animation: collfade 0.3s ease;
+}
+
+@-webkit-keyframes collfade {
+ 0% {
+ transform: scaleY(.8);
+ transform-origin: 0% 0%;
+ opacity: 0;
+ }
+
+ 100% {
+ transform: scaleY(1);
+ transform-origin: 0% 0%;
+ opacity: 1;
+ }
+}
+
+@keyframes collfade {
+ 0% {
+ transform: scaleY(.8);
+ transform-origin: 0% 0%;
+ opacity: 0;
+ }
+
+ 100% {
+ transform: scaleY(1);
+ transform-origin: 0% 0%;
+ opacity: 1;
+ }
+}
+
+.ant-table-tbody>tr>td {
+ border-color: #f0f0f0;
+}
+
+.ant-table-row-expand-icon {
+ vertical-align: middle;
+ margin-inline-end: 8px;
+ position: relative;
+ transform: scale(0.9411764705882353);
+}
+
+.ant-table-row-collapsed::before {
+ transform: rotate(-180deg);
+ top: 7px;
+ inset-inline-end: 3px;
+ inset-inline-start: 3px;
+ height: 1px;
+ position: absolute;
+ background: currentcolor;
+ transition: transform 0.3s ease-out;
+ content: "";
+}
+
+.ant-table-row-collapsed::after {
+ transform: rotate(0deg);
+ top: 3px;
+ bottom: 3px;
+ inset-inline-start: 7px;
+ width: 1px;
+ position: absolute;
+ background: currentcolor;
+ transition: transform 0.3s ease-out;
+ content: "";
+}
+
+.ant-table-row-expanded::before {
+ top: 7px;
+ inset-inline-end: 3px;
+ inset-inline-start: 3px;
+ height: 1px;
+ position: absolute;
+ background: currentcolor;
+ transition: transform 0.3s ease-out;
+ content: "";
+}
+
+.ant-table-row-expanded::after {
+ top: 3px;
+ bottom: 3px;
+ inset-inline-start: 7px;
+ width: 1px;
+ transform: rotate(90deg);
+ position: absolute;
+ background: currentcolor;
+ transition: transform 0.3s ease-out;
+ content: "";
+}
diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html
index 0e2b3a63..5cbe7858 100644
--- a/web/html/common/qrcode_modal.html
+++ b/web/html/common/qrcode_modal.html
@@ -1,32 +1,23 @@
{{define "qrcodeModal"}}
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
- :dialog-style="{ top: '20px' }"
- :closable="true"
- :class="themeSwitcher.currentTheme"
- :footer="null"
- width="300px">
- <a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
- {{ i18n "pages.inbounds.clickOnQRcode" }}
- </a-tag>
- <template v-if="app.subSettings.enable && qrModal.subId">
- <a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider>
- <canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))"
- id="qrCode-sub"
- class="qr-bg">
- </canvas>
- <a-divider>{{ i18n "pages.settings.subSettings"}} Json</a-divider>
- <canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))"
- id="qrCode-subJson"
- style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
- </canvas>
- </template>
- <a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
- <template v-for="(row, index) in qrModal.qrcodes">
- <a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
- <canvas @click="copyToClipboard('qrCode-'+index, row.link)"
- :id="'qrCode-'+index"
- class="qr-bg"></canvas>
- </template>
+ :dialog-style="{ top: '20px' }"
+ :closable="true"
+ :class="themeSwitcher.currentTheme"
+ :footer="null" width="300px">
+ <a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
+ {{ i18n "pages.inbounds.clickOnQRcode" }}
+ </a-tag>
+ <template v-if="app.subSettings.enable && qrModal.subId">
+ <a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider>
+ <div class="qr-bg"><canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" class="qr-cv"></canvas></div>
+ <a-divider>{{ i18n "pages.settings.subSettings"}} Json</a-divider>
+ <div class="qr-bg"><canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))" id="qrCode-subJson" class="qr-cv"></canvas></div>
+ </template>
+ <a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
+ <template v-for="(row, index) in qrModal.qrcodes">
+ <a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
+ <div class="qr-bg"><canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas></div>
+ </template>
</a-modal>
<script>
@@ -87,8 +78,10 @@
setQrCode(elmentId, content) {
new QRious({
element: document.querySelector('#' + elmentId),
- size: 260,
+ size: 300,
value: content,
+ background: 'transparent',
+ foreground: 'black'
});
},
genSubLink(subID) {
diff --git a/web/html/xui/inbound_client_table.html b/web/html/xui/inbound_client_table.html
index 3cc115ee..7eaed854 100644
--- a/web/html/xui/inbound_client_table.html
+++ b/web/html/xui/inbound_client_table.html
@@ -54,7 +54,7 @@
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
<template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template>
</template>
- <a-badge :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
+ <a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
</a-badge>
</a-tooltip>
[[ client.email ]]
@@ -258,7 +258,7 @@
</table>
</template>
<a-badge>
- <a-icon v-if="!client.enable" slot="count" type="pause-circle" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
+ <a-icon v-if="!client.enable" slot="count" type="pause-circle" theme="filled" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;"><a-icon type="solution"></a-icon></a-button>
</a-badge>
</a-popover>
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index a58372ef..737834fd 100644
--- a/web/html/xui/inbounds.html
+++ b/web/html/xui/inbounds.html
@@ -2,446 +2,459 @@
<html lang="en">
{{template "head" .}}
<style>
- @media (min-width: 769px) {
- .ant-layout-content {
- margin: 24px 16px;
- }
+ @media (min-width: 769px) {
+ .ant-layout-content {
+ margin: 24px 16px;
}
-
- @media (max-width: 768px) {
- .ant-card-body {
- padding: .5rem;
- }
- }
-
- .ant-col-sm-24 {
- 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;
+ }
+ @media (max-width: 768px) {
+ .ant-card-body {
+ padding: .5rem;
}
- .ant-collapse {
- margin: 5px 0;
+ }
+ .ant-col-sm-24 {
+ 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: #7a316f;
+ border: #7a316f solid 1px;
+ }
+ .ant-collapse {
+ margin: 5px 0;
+ }
+ .info-large-tag {
+ max-width: 200px;
+ overflow: hidden;
+ }
+ .online-animation .ant-badge-status-dot {
+ animation: onlineAnimation 1.2s linear infinite;
+ }
+ @keyframes onlineAnimation {
+ 0%,
+ 50%,
+ 100% {
+ transform: scale(1);
+ opacity: 1;
}
- .info-large-tag {
- max-width: 200px;
- overflow: hidden;
+ 10% {
+ transform: scale(1.5);
+ opacity: .2;
}
+ }
</style>
<body>
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
- {{ template "commonSider" . }}
- <a-layout id="content-layout">
- <a-layout-content>
- <a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
- <transition name="list" appear>
- <a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
- message='{{ i18n "secAlertTitle" }}'
- color="red"
- description='{{ i18n "secAlertSsl" }}'
- show-icon closable
- >
- </a-alert>
- </transition>
- <transition name="list" appear>
- <a-card hoverable>
- <a-row>
- <a-col :xs="24" :sm="24" :lg="12">
- {{ i18n "pages.inbounds.totalDownUp" }}:
- <a-tag color="green">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag>
- </a-col>
- <a-col :xs="24" :sm="24" :lg="12">
- {{ i18n "pages.inbounds.totalUsage" }}:
- <a-tag color="green">[[ sizeFormat(total.up + total.down) ]]</a-tag>
- </a-col>
- <a-col :xs="24" :sm="24" :lg="12">
- {{ i18n "pages.inbounds.inboundCount" }}:
- <a-tag color="green">[[ dbInbounds.length ]]</a-tag>
- </a-col>
- <a-col :xs="24" :sm="24" :lg="12">
- <template>
- <div>
- <a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
- </a-back-top>
- {{ i18n "clients" }}:
- <a-tag color="green">[[ total.clients ]]</a-tag>
- <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.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.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>
- </div>
- </template>
- </a-col>
- </a-row>
- </a-card>
- </transition>
- <transition name="list" appear>
- <a-card hoverable>
- <div slot="title">
- <a-row>
- <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">
- <template v-if="!isMobile">{{ i18n "pages.inbounds.generalActions" }}</template>
- </a-button>
- <a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
- <a-menu-item key="import">
- <a-icon type="import"></a-icon>
- {{ i18n "pages.inbounds.importInbound" }}
- </a-menu-item>
- <a-menu-item key="export">
- <a-icon type="export"></a-icon>
- {{ i18n "pages.inbounds.export" }}
- </a-menu-item>
- <a-menu-item key="subs" v-if="subSettings.enable">
- <a-icon type="export"></a-icon>
- {{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
- </a-menu-item>
- <a-menu-item key="resetInbounds">
- <a-icon type="reload"></a-icon>
- {{ i18n "pages.inbounds.resetAllTraffic" }}
- </a-menu-item>
- <a-menu-item key="resetClients">
- <a-icon type="file-done"></a-icon>
- {{ i18n "pages.inbounds.resetAllClientTraffics" }}
- </a-menu-item>
- <a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
- <a-icon type="rest"></a-icon>
- {{ i18n "pages.inbounds.delDepletedClients" }}
- </a-menu-item>
- </a-menu>
- </a-dropdown>
- </a-col>
- <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.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>
- <a-switch v-model="isRefreshEnabled" @change="toggleRefresh"></a-switch>
- </a-col>
- </a-row>
- </div>
- <div :style="isMobile ? '' : 'display: flex; align-items: center; justify-content: flex-start;'">
- <a-switch v-model="enableFilter"
- :style="isMobile ? 'margin-bottom: .5rem; display: flex;' : '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-back-top></a-back-top>
- <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="margin-top: 10px">
- <template slot="action" slot-scope="text, dbInbound">
- <a-dropdown :trigger="['click']">
- <a-icon @click="e => e.preventDefault()" type="more" style="font-size: 20px; text-decoration: solid;"></a-icon>
- <a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
- <a-menu-item key="edit">
- <a-icon type="edit"></a-icon>
- {{ i18n "edit" }}
- </a-menu-item>
- <a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard">
- <a-icon type="qrcode"></a-icon>
- {{ i18n "qrCode" }}
- </a-menu-item>
- <template v-if="dbInbound.isMultiUser()">
- <a-menu-item key="addClient">
- <a-icon type="user-add"></a-icon>
- {{ i18n "pages.client.add"}}
- </a-menu-item>
- <a-menu-item key="addBulkClient">
- <a-icon type="usergroup-add"></a-icon>
- {{ i18n "pages.client.bulk"}}
- </a-menu-item>
- <a-menu-item key="resetClients">
- <a-icon type="file-done"></a-icon>
- {{ i18n "pages.inbounds.resetInboundClientTraffics"}}
- </a-menu-item>
- <a-menu-item key="export">
- <a-icon type="export"></a-icon>
- {{ i18n "pages.inbounds.export"}}
- </a-menu-item>
- <a-menu-item key="subs" v-if="subSettings.enable">
- <a-icon type="export"></a-icon>
- {{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
- </a-menu-item>
- <a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
- <a-icon type="rest"></a-icon>
- {{ i18n "pages.inbounds.delDepletedClients" }}
- </a-menu-item>
- </template>
- <template v-else>
- <a-menu-item key="showInfo">
- <a-icon type="info-circle"></a-icon>
- {{ i18n "info"}}
- </a-menu-item>
- </template>
- <a-menu-item key="clipboard">
- <a-icon type="copy"></a-icon>
- {{ i18n "pages.inbounds.exportInbound" }}
- </a-menu-item>
- <a-menu-item key="resetTraffic">
- <a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
- </a-menu-item>
- <a-menu-item key="clone">
- <a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}}
- </a-menu-item>
- <a-menu-item key="delete">
- <span style="color: #FF4D4F">
- <a-icon type="delete"></a-icon> {{ i18n "delete"}}
- </span>
- </a-menu-item>
- <a-menu-item v-if="isMobile">
- <a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
- {{ i18n "pages.inbounds.enable" }}
- </a-menu-item>
- </a-menu>
- </a-dropdown>
+ {{ template "commonSider" . }}
+ <a-layout id="content-layout">
+ <a-layout-content>
+ <a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
+ <transition name="list" appear>
+ <a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
+ message='{{ i18n "secAlertTitle" }}'
+ color="red"
+ description='{{ i18n "secAlertSsl" }}'
+ show-icon closable>
+ </a-alert>
+ </transition>
+ <transition name="list" appear>
+ <a-card hoverable>
+ <a-row>
+ <a-col :xs="24" :sm="24" :lg="12">
+ {{ i18n "pages.inbounds.totalDownUp" }}:
+ <a-tag color="green">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag>
+ </a-col>
+ <a-col :xs="24" :sm="24" :lg="12">
+ {{ i18n "pages.inbounds.totalUsage" }}:
+ <a-tag color="green">[[ sizeFormat(total.up + total.down) ]]</a-tag>
+ </a-col>
+ <a-col :xs="24" :sm="24" :lg="12">
+ {{ i18n "pages.inbounds.inboundCount" }}:
+ <a-tag color="green">[[ dbInbounds.length ]]</a-tag>
+ </a-col>
+ <a-col :xs="24" :sm="24" :lg="12">
+ <template>
+ <div>
+ <a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"></a-back-top>
+ {{ i18n "clients" }}:
+ <a-tag color="green">[[ total.clients ]]</a-tag>
+ <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.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.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