mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-14 05:23:09 +00:00
DESIGN REFACTOR (#600)
### New features - New face + dark mode - [Change font to vazirmatn](057f3190de) - [use customized andtv](f956009fd2) - [popConfirm for del and reset client](66c98e8392) - [Separate page for xray config](9e1cd6315f) - Separate face for mobile view - [Show online users](bf892e9965) [#559](https://github.com/alireza0/x-ui/issues/559) - [Auto renew](96408967ae) ### Bug fixes - [[tgbot] Retry loop on start](211c05ec29) - [fix docker-compose version](1dcec91ce4) - [fix redirect after restart](81d25a032c)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ dist/
|
|||||||
release/
|
release/
|
||||||
/release.sh
|
/release.sh
|
||||||
/x-ui
|
/x-ui
|
||||||
|
.DS_Store
|
||||||
@@ -47,7 +47,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.s
|
|||||||
## Manual install & upgrade
|
## Manual install & upgrade
|
||||||
|
|
||||||
1. First download the latest compressed package from https://github.com/alireza0/x-ui/releases, generally choose Architecture `amd64`
|
1. First download the latest compressed package from https://github.com/alireza0/x-ui/releases, generally choose Architecture `amd64`
|
||||||
2. Then upload the compressed package to the server's `/root/` directory and `root` rootlog in to the server with user
|
2. Then upload the compressed package to the server's `/root/` directory and login to the server with user `root`
|
||||||
|
|
||||||
> If your server cpu architecture is not `amd64` replace another architecture
|
> If your server cpu architecture is not `amd64` replace another architecture
|
||||||
|
|
||||||
|
|||||||
@@ -76,4 +76,5 @@ type Client struct {
|
|||||||
Enable bool `json:"enable" form:"enable"`
|
Enable bool `json:"enable" form:"enable"`
|
||||||
TgID string `json:"tgId" form:"tgId"`
|
TgID string `json:"tgId" form:"tgId"`
|
||||||
SubID string `json:"subId" form:"subId"`
|
SubID string `json:"subId" form:"subId"`
|
||||||
|
Reset int `json:"reset" form:"reset"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
version: "3.9"
|
version: "3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
xui:
|
xui:
|
||||||
|
|||||||
BIN
web/assets/Vazirmatn-UI-NL-Regular.woff2
Normal file
BIN
web/assets/Vazirmatn-UI-NL-Regular.woff2
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,2 +1,6 @@
|
|||||||
@import "../lib/style/index.less";
|
@import "../lib/style/index.less";
|
||||||
@import "../lib/style/components.less";
|
@import "../lib/style/components.less";
|
||||||
|
|
||||||
|
@blue-6: #0E49B5;
|
||||||
|
@border-radius-base: 1rem;
|
||||||
|
@progress-remaining-color: #EDEDED;
|
||||||
2
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
2
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
File diff suppressed because one or more lines are too long
3
web/assets/ant-design-vue@1.7.2/antd.min.js
vendored
3
web/assets/ant-design-vue@1.7.2/antd.min.js
vendored
File diff suppressed because one or more lines are too long
1
web/assets/ant-design-vue@1.7.2/antd.min.js.map
Normal file
1
web/assets/ant-design-vue@1.7.2/antd.min.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -7,6 +7,28 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: rgba(0,0,0,.65);
|
||||||
|
font-size: 14px;
|
||||||
|
font-variant: tabular-nums;
|
||||||
|
line-height: 1.5;
|
||||||
|
background-color: #fff;
|
||||||
|
font-feature-settings: "tnum";
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
--antd-wave-shadow-color: #0e49b5;
|
||||||
|
line-height: 1.15;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
-ms-overflow-style: scrollbar;
|
||||||
|
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
color: #0e49b5;
|
||||||
|
background-color: #0e49b530;
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -19,6 +41,73 @@ body {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-layout, .ant-layout * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-spin-blur {
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
style attribute {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.ant-table-tbody>tr>td, .ant-table-thead>tr>th {
|
||||||
|
padding: 16px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
.ant-table-thead>tr>th {
|
||||||
|
color: rgba(0,0,0,.85);
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
transition: background .3s ease;
|
||||||
|
}
|
||||||
|
.ant-table-row-cell-break-word {
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table table {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 1rem 1rem 0 0;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
.ant-table {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: rgba(0,0,0,.65);
|
||||||
|
font-size: 14px;
|
||||||
|
font-variant: tabular-nums;
|
||||||
|
line-height: 1.5;
|
||||||
|
list-style: none;
|
||||||
|
font-feature-settings: "tnum";
|
||||||
|
position: relative;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.ant-card-hoverable {
|
||||||
|
cursor: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.ant-card {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: rgba(0,0,0,.65);
|
||||||
|
font-size: 14px;
|
||||||
|
font-variant: tabular-nums;
|
||||||
|
line-height: 1.5;
|
||||||
|
list-style: none;
|
||||||
|
font-feature-settings: "tnum";
|
||||||
|
position: relative;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all .3s;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-space {
|
.ant-space {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -31,10 +120,22 @@ body {
|
|||||||
.ant-layout-sider {
|
.ant-layout-sider {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.ant-card {
|
||||||
|
margin: .5rem;
|
||||||
|
}
|
||||||
|
.ant-tabs {
|
||||||
|
margin: .5rem;
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card {
|
.ant-layout-content {
|
||||||
border-radius: 30px;
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card,
|
||||||
|
.ant-tabs {
|
||||||
|
border-radius: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-hoverable {
|
.ant-card-hoverable {
|
||||||
@@ -64,13 +165,78 @@ body {
|
|||||||
border-radius: 0 4px 4px 0;
|
border-radius: 0 4px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
||||||
|
background-color: #04308f !important;
|
||||||
|
background-image: linear-gradient( 270deg, rgba(123, 199, 77, 0) 30%, #2f67c2, rgba(123, 199, 77, 0) 100% );
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
animation: ma-bg-move linear 6.6s infinite;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 0.5rem
|
||||||
|
}
|
||||||
|
@-webkit-keyframes ma-bg-move {
|
||||||
|
0% {background-position: -500px 0;}
|
||||||
|
100% {background-position: 1000px 0;}
|
||||||
|
}
|
||||||
|
@keyframes ma-bg-move {
|
||||||
|
0% {background-position: -500px 0;}
|
||||||
|
50% {background-position: 1000px 0;}
|
||||||
|
100% {background-position: 1000px 0;}
|
||||||
|
}
|
||||||
|
.ant-menu-item-active,
|
||||||
|
.ant-menu-item:hover,
|
||||||
|
.ant-menu-submenu-active,
|
||||||
|
.ant-menu-submenu-title:hover,
|
||||||
|
.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open{
|
||||||
|
color:#0e49b5;
|
||||||
|
background-color: #dce9f5;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-menu-inline .ant-menu-item {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-menu-inline .ant-menu-item:after,
|
||||||
|
.ant-menu {
|
||||||
|
border-right-width: 0;
|
||||||
|
}
|
||||||
|
.ant-layout-sider-children,
|
||||||
|
.ant-pagination ul {
|
||||||
|
margin-top:-.1px;
|
||||||
|
padding:0.5rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu,
|
||||||
|
.ant-select-dropdown-menu {
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
|
.ant-dropdown-menu-item,
|
||||||
|
.ant-dropdown-menu-item:hover,
|
||||||
|
.ant-select-dropdown-menu-item,
|
||||||
|
.ant-select-dropdown-menu-item:hover,
|
||||||
|
.ant-select-dropdown-menu-item-selected,
|
||||||
|
.ant-select-selection--multiple .ant-select-selection__choice {
|
||||||
|
border-radius: .5rem;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 769px) {
|
@media (min-width: 769px) {
|
||||||
.drawer-handle {
|
.drawer-handle {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.ant-tabs {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-in-enter, .fade-in-leave-active, .fade-in-linear-enter, .fade-in-linear-leave, .fade-in-linear-leave-active, .fade-in-linear-enter, .fade-in-linear-leave, .fade-in-linear-leave-active {
|
.fade-in-enter,
|
||||||
|
.fade-in-leave-active,
|
||||||
|
.fade-in-linear-enter,
|
||||||
|
.fade-in-linear-leave,
|
||||||
|
.fade-in-linear-leave-active,
|
||||||
|
.fade-in-linear-enter,
|
||||||
|
.fade-in-linear-leave,
|
||||||
|
.fade-in-linear-leave-active {
|
||||||
opacity: 0
|
opacity: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,8 +332,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-list-item-meta-title {
|
.ant-list-item-meta-title {
|
||||||
font-weight: bold;
|
font-size: 14px;
|
||||||
font-size: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-progress-inner {
|
.ant-progress-inner {
|
||||||
@@ -181,7 +346,7 @@ body {
|
|||||||
|
|
||||||
.ant-table-tbody>tr>td,
|
.ant-table-tbody>tr>td,
|
||||||
.ant-table-thead>tr>th{
|
.ant-table-thead>tr>th{
|
||||||
padding:16px;
|
padding:16px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-expand-icon-th,
|
.ant-table-expand-icon-th,
|
||||||
@@ -190,148 +355,14 @@ body {
|
|||||||
min-width: 30px;
|
min-width: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-menu-dark,
|
.ant-tabs {
|
||||||
.ant-menu-dark .ant-menu-sub,
|
|
||||||
.ant-layout-header,
|
|
||||||
.ant-layout-sider-dark,
|
|
||||||
.ant-layout-sider-zero-width-trigger,
|
|
||||||
.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu,
|
|
||||||
.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu {
|
|
||||||
background:#1a212a
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-tabs:not(.ant-card-dark) {
|
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #1a212a;
|
|
||||||
border-color:rgba(0,0,0,.09);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark:hover {
|
|
||||||
border-color: #e8e8e8;
|
|
||||||
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-setting-textarea {
|
.ant-setting-textarea {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark-box-nohover{
|
|
||||||
padding: 0 20px 20px !important;
|
|
||||||
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
|
||||||
}
|
|
||||||
.ant-card-dark-box-nohover:hover{
|
|
||||||
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-table-thead th {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #161b22;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-table-tbody tr td,
|
|
||||||
.ant-card-dark .ant-modal-title {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-collapse-content,
|
|
||||||
.ant-card-dark .ant-calendar,
|
|
||||||
.ant-card-dark .ant-table-placeholder,
|
|
||||||
.ant-card-dark .ant-select-selection__choice,
|
|
||||||
.ant-card-dark .ant-input-group-addon {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #262f3d;
|
|
||||||
border: 1px solid rgb(0 150 112 / 0%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-list-item-meta-title,
|
|
||||||
.ant-card-dark .ant-list-item-meta-description,
|
|
||||||
.ant-card-dark .ant-form-item-label>label,
|
|
||||||
.ant-card-dark .ant-form-item,
|
|
||||||
.ant-card-dark .ant-divider-inner-text,
|
|
||||||
.ant-card-dark .ant-modal-confirm-content,
|
|
||||||
.ant-card-dark .ant-modal-confirm-title,
|
|
||||||
.ant-card-dark .ant-progress-text,
|
|
||||||
.ant-card-dark .ant-modal-close,
|
|
||||||
.ant-card-dark i,
|
|
||||||
.ant-card-dark .ant-pagination-item a,
|
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item,
|
|
||||||
.ant-card-dark .ant-calendar-day-select,
|
|
||||||
.ant-card-dark .ant-calendar-month-select,
|
|
||||||
.ant-card-dark .ant-calendar-year-select,
|
|
||||||
.ant-card-dark .ant-calendar-date,
|
|
||||||
.ant-card-dark .ant-collapse>.ant-collapse-item>.ant-collapse-header,
|
|
||||||
.ant-card-dark .ant-empty-normal,
|
|
||||||
.ant-card-dark .ant-checkbox+span {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,
|
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled),
|
|
||||||
.ant-card-dark .ant-calendar-date:hover,
|
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item-active,
|
|
||||||
.ant-card-dark li.ant-calendar-time-picker-select-option-selected {
|
|
||||||
background-color: #11314d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark tbody .ant-table-expanded-row,
|
|
||||||
.ant-card-dark .ant-calendar-time-picker-inner {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #1a212a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-input-number {
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-input,
|
|
||||||
.ant-card-dark .ant-input-number,
|
|
||||||
.ant-card-dark .ant-input-number-handler-wrap,
|
|
||||||
.ant-card-dark .ant-calendar-input,
|
|
||||||
.ant-card-dark .ant-pagination-item,
|
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item-selected,
|
|
||||||
.ant-card-dark .ant-select-selection,
|
|
||||||
.ant-card-dark .ant-calendar-picker-clear {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #193752;
|
|
||||||
border: 1px solid rgba(0, 65, 150, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-select-disabled .ant-select-selection {
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
background-color: #242c3a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-collapse-item {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #161b22;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-dropdown-menu-dark,
|
|
||||||
.ant-card-dark .ant-modal-content {
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.65);
|
|
||||||
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-modal-content,
|
|
||||||
.ant-card-dark .ant-select-dropdown,
|
|
||||||
.ant-card-dark .ant-modal-body,
|
|
||||||
.ant-card-dark .ant-modal-header {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #222a37;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date {
|
|
||||||
background-color: #1668dc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-calendar-time-picker-select li:hover {
|
|
||||||
background: #1668dc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-table-header {
|
.client-table-header {
|
||||||
background-color: #f0f2f5;
|
background-color: #f0f2f5;
|
||||||
}
|
}
|
||||||
@@ -340,118 +371,362 @@ body {
|
|||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .client-table-header {
|
|
||||||
background-color: #1a212a;
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .client-table-odd-row {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #242c3a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-calendar-last-month-cell .ant-calendar-date,
|
|
||||||
.ant-card-dark .ant-calendar-next-month-btn-day .ant-calendar-date {
|
|
||||||
color: hsla(0,0%,100%,.30);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-drawer-dark {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-drawer-dark .ant-drawer-wrapper-body,
|
|
||||||
.ant-drawer-dark .drawer-handle {
|
|
||||||
background-color: #1a212a;
|
|
||||||
border: 1px solid hsla(0,0%,100%,.30);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-tag {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background: rgba(255,255,255,.04);
|
|
||||||
border-color: #434343;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-tag-blue {
|
|
||||||
color: #3c9ae8;
|
|
||||||
background: #111d2c;
|
|
||||||
border-color: #15395b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-tag-green {
|
|
||||||
color: #6abe39;
|
|
||||||
background: #162312;
|
|
||||||
border-color: #274916;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-tag-cyan {
|
|
||||||
color: #33bcb7;
|
|
||||||
background: #112123;
|
|
||||||
border-color: #144848;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-tag-red {
|
|
||||||
color: #e84749;
|
|
||||||
background: #2a1215;
|
|
||||||
border-color: #58181c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-tag-orange {
|
|
||||||
color: #e89a3c;
|
|
||||||
background: #2b1d11;
|
|
||||||
border-color: #593815;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-table-row-expand-icon,
|
|
||||||
.ant-card-dark .ant-checkbox-inner {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-switch-checked {
|
|
||||||
background-color: #0c61b0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-btn,
|
|
||||||
.ant-card-dark .ant-radio-button-wrapper {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background: none;
|
|
||||||
border: 1px solid hsla(0,0%,100%,.65);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-radio-button-wrapper:hover {
|
|
||||||
color: #177ddc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-dark .ant-btn-primary {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
background-color: #073763;
|
|
||||||
border-color: #1890ff;
|
|
||||||
text-shadow: 0 -1px 0 rgba(255,255,255,.12);
|
|
||||||
box-shadow: 0 2px 0 rgba(255,255,255,.045);
|
|
||||||
}
|
|
||||||
.ant-card-dark .ant-btn-primary:hover {
|
|
||||||
background-color: #40a9ff;
|
|
||||||
border-color: #40a9ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-dark .ant-popover-content {
|
|
||||||
border: 1px solid #e8e8e8;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-dark .ant-popover-inner {
|
|
||||||
background: #222a37;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-dark .ant-popover-title,
|
|
||||||
.ant-dark .ant-popover-inner-content {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-dark .ant-popover-placement-top>.ant-popover-content>.ant-popover-arrow {
|
|
||||||
border-color: transparent #2e3b52 #2e3b52 transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-pagination.ant-pagination {
|
.ant-table-pagination.ant-pagination {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* change basic colors */
|
||||||
|
.ant-tag-blue {
|
||||||
|
background-color: #edf4fa;
|
||||||
|
border-color: #a9c5e7;
|
||||||
|
color: #0e49b5;
|
||||||
|
}
|
||||||
|
.ant-tag-green {
|
||||||
|
background-color: #f6ffed;
|
||||||
|
border-color: #b7eb8f;
|
||||||
|
color: #389e0d;
|
||||||
|
}
|
||||||
|
.ant-tag-purple {
|
||||||
|
background-color: #f2eaf1;
|
||||||
|
border-color: #d5bed2;
|
||||||
|
color: #7a316f;
|
||||||
|
}
|
||||||
|
.ant-tag-orange,
|
||||||
|
.ant-alert-warning {
|
||||||
|
background-color:#fff6E6;
|
||||||
|
border-color: #ffd98c;
|
||||||
|
color: #ffa031;
|
||||||
|
}
|
||||||
|
.ant-tag-red,
|
||||||
|
.ant-alert-error {
|
||||||
|
background-color:#fff0f0;
|
||||||
|
border-color: #fb9d9d;
|
||||||
|
color: #e04141;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input:hover,
|
||||||
|
.ant-input:focus {
|
||||||
|
background-color: #edf4fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-icon:hover {
|
||||||
|
color: #E04141;
|
||||||
|
}
|
||||||
|
|
||||||
|
.normal-icon:hover {
|
||||||
|
color: #0E49B5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DARK THEME */
|
||||||
|
|
||||||
|
.dark ::selection {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #0e49b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .normal-icon:hover {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-layout-sider,
|
||||||
|
.dark .ant-drawer-content,
|
||||||
|
.ant-menu-dark,
|
||||||
|
.ant-menu-dark .ant-menu-sub,
|
||||||
|
.dark .ant-card,
|
||||||
|
.dark .ant-table,
|
||||||
|
.dark .ant-collapse-content,
|
||||||
|
.dark .ant-tabs {
|
||||||
|
background-color: #151F31;
|
||||||
|
color: #ffffffa6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-card-hoverable:hover,
|
||||||
|
.dark .ant-space-item>.ant-tabs:hover {
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark>.ant-layout,
|
||||||
|
.dark .drawer-handle,
|
||||||
|
.dark .ant-table-thead>tr>th,
|
||||||
|
.dark .ant-table-expanded-row,
|
||||||
|
.dark .ant-table-expanded-row:hover,
|
||||||
|
.dark .ant-table-expanded-row .ant-table-tbody,
|
||||||
|
.dark .ant-calendar {
|
||||||
|
background-color: #101828;
|
||||||
|
color: rgb(255 255 255 /65%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-table-expanded-row .ant-table-thead>tr:first-child>th {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-calendar,
|
||||||
|
.dark .ant-card-bordered {
|
||||||
|
border-color: #151f31;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-table-tbody>tr>td,
|
||||||
|
.dark .ant-table-thead>tr>th,
|
||||||
|
.dark .ant-card-head,
|
||||||
|
.dark .ant-modal-header,
|
||||||
|
.dark .ant-collapse>.ant-collapse-item,
|
||||||
|
.dark .ant-tabs-bar,
|
||||||
|
.dark .ant-list-split .ant-list-item,
|
||||||
|
.dark .ant-popover-title,
|
||||||
|
.dark .ant-calendar-header,
|
||||||
|
.dark .ant-calendar-input-wrap {
|
||||||
|
border-bottom-color: #2C3950;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-modal-footer,
|
||||||
|
.dark .ant-collapse-content,
|
||||||
|
.dark .ant-calendar-footer,
|
||||||
|
.dark .ant-divider-horizontal.ant-divider-with-text-center:before,
|
||||||
|
.dark .ant-divider-horizontal.ant-divider-with-text-center:after {
|
||||||
|
border-top-color: #2c3950;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-progress-text,
|
||||||
|
.dark .ant-card-head,
|
||||||
|
.dark .ant-form,
|
||||||
|
.dark .ant-collapse>.ant-collapse-item>.ant-collapse-header,
|
||||||
|
.dark .ant-form-item i,
|
||||||
|
.dark .ant-modal-close-x,
|
||||||
|
.dark .ant-pagination-item a,
|
||||||
|
.dark li:not(.ant-pagination-disabled) i,
|
||||||
|
.dark .ant-form .anticon,
|
||||||
|
.dark .ant-tabs-tab-arrow-show:not(.ant-tabs-tab-btn-disabled),
|
||||||
|
.dark .anticon-close,
|
||||||
|
.dark .ant-list-item-meta-title,
|
||||||
|
.dark .ant-list-item-meta-description,
|
||||||
|
.dark .ant-select-selection i,
|
||||||
|
.dark .ant-modal-confirm-title,
|
||||||
|
.dark .ant-modal-confirm-content,
|
||||||
|
.dark .ant-popover-message,
|
||||||
|
.dark .ant-modal,
|
||||||
|
.dark .ant-divider-inner-text,
|
||||||
|
.dark .ant-popover-title,
|
||||||
|
.dark .ant-popover-inner-content,
|
||||||
|
.dark h2 {
|
||||||
|
color: rgb(255 255 255 / 65%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-pagination-disabled i,
|
||||||
|
.dark .ant-tabs-tab-btn-disabled {
|
||||||
|
color: rgb(255 255 255 / 25%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-input,
|
||||||
|
.dark .ant-input-group-addon,
|
||||||
|
.dark .ant-collapse,
|
||||||
|
.dark .ant-select-selection,
|
||||||
|
.dark .ant-input-number,
|
||||||
|
.dark .ant-input-number-handler-wrap,
|
||||||
|
.dark .ant-pagination-item-active,
|
||||||
|
.dark .ant-table-placeholder,
|
||||||
|
.dark .ant-empty-normal,
|
||||||
|
.dark.ant-select-dropdown,
|
||||||
|
.dark .ant-select-dropdown,
|
||||||
|
.dark .ant-select-dropdown-menu-item,
|
||||||
|
.dark .ant-divider:not(.ant-divider-with-text-center),
|
||||||
|
.dark .ant-calendar-input,
|
||||||
|
.dark .ant-calendar-time-picker-inner {
|
||||||
|
background-color: #222D42;
|
||||||
|
border-color: #2c3950;
|
||||||
|
color: rgb(255 255 255 / 65%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-select-selection:hover,
|
||||||
|
.dark .ant-calendar-picker-clear,
|
||||||
|
.dark .ant-input-number:hover,
|
||||||
|
.dark .ant-input-number:focus,
|
||||||
|
.dark .ant-input:hover,
|
||||||
|
.dark .ant-input:focus {
|
||||||
|
background-color: rgb(14 73 181 / 30%);
|
||||||
|
border-color: #0E49B5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger) {
|
||||||
|
color: rgb(255 255 255 / 65%);
|
||||||
|
background-color: rgb(14 73 181 / 30%);
|
||||||
|
border: 1px solid #0e49b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-radio-button-wrapper,
|
||||||
|
.dark .ant-radio-button-wrapper:before {
|
||||||
|
color: rgb(255 255 255 / 65%);
|
||||||
|
background-color: rgb(14 73 181 / 30%);
|
||||||
|
border: 1px solid #0e49b5;
|
||||||
|
border-left: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger) ,
|
||||||
|
.dark .ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger) {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: rgb(14 73 181 / 50%);
|
||||||
|
border-color: #0e49b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-btn-primary[disabled],
|
||||||
|
.dark .ant-calendar-ok-btn-disabled {
|
||||||
|
color: rgb(255 255 255 / 35%);
|
||||||
|
background-color: #2c3950;
|
||||||
|
border-color: #42516c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td {
|
||||||
|
background-color: #122444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-table-row-expand-icon {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #fff0;
|
||||||
|
border-color: #9ea2a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-table-row-expand-icon:hover {
|
||||||
|
color: #0e49b5;
|
||||||
|
background-color: #fff0;
|
||||||
|
border-color: #0e49b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-switch:not(.ant-switch-checked) {
|
||||||
|
background-color: #2C3950;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-progress-line .ant-progress-inner {
|
||||||
|
background-color: #2c3950;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-progress-circle-trail {
|
||||||
|
stroke: #2c3950 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu-dark,
|
||||||
|
.dark .ant-popover-inner {
|
||||||
|
background-color: #222D42;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark>.ant-popover-content>.ant-popover-arrow {
|
||||||
|
border-color: #222D42;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,
|
||||||
|
.dark .ant-select-dropdown-menu-item-selected,
|
||||||
|
.dark .ant-select-dropdown-menu-item:hover,
|
||||||
|
.dark .ant-calendar-time-picker-select-option-selected {
|
||||||
|
background-color: #313f5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-menu-dark .ant-menu-item:hover {
|
||||||
|
background-color: #2c3950;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-alert-message {
|
||||||
|
color: rgb(255 255 255 /85%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tag {
|
||||||
|
color: rgb(255 255 255 / 65%);
|
||||||
|
background-color: #ffffff0a;
|
||||||
|
border-color: #344461;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tag-blue {
|
||||||
|
background-color: #111a2c;
|
||||||
|
border-color: #0f367e;
|
||||||
|
color: #3c89e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tag-red,
|
||||||
|
.dark .ant-alert-error {
|
||||||
|
background-color: #291515;
|
||||||
|
border-color: #5C2626;
|
||||||
|
color: #e04141;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tag-orange,
|
||||||
|
.dark .ant-alert-warning {
|
||||||
|
background-color: #312313;
|
||||||
|
border-color: #593914;
|
||||||
|
color: #ffa031;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tag-green {
|
||||||
|
background-color: #142429;
|
||||||
|
border-color: #23432c;
|
||||||
|
color: #61bf39;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tag-purple {
|
||||||
|
background-color: #2c1e32;
|
||||||
|
border-color: #49394e;
|
||||||
|
color: #f2eaf1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-modal-content,
|
||||||
|
.dark .ant-modal-header {
|
||||||
|
background-color: #181f2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-modal-title,
|
||||||
|
.dark .ant-form-item-label>label,
|
||||||
|
.dark .ant-checkbox-wrapper,
|
||||||
|
.dark .ant-form-item,
|
||||||
|
.dark .ant-calendar-footer .ant-calendar-today-btn,
|
||||||
|
.dark .ant-calendar-footer .ant-calendar-time-picker-btn,
|
||||||
|
.dark .ant-calendar-day-select,
|
||||||
|
.dark .ant-calendar-month-select,
|
||||||
|
.dark .ant-calendar-year-select,
|
||||||
|
.dark .ant-calendar-date {
|
||||||
|
color: rgb(255 255 255 / 65%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-calendar-next-month-btn-day .ant-calendar-date,
|
||||||
|
.dark .ant-calendar-last-month-cell .ant-calendar-date {
|
||||||
|
color: #2c3950;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-calendar-selected-day .ant-calendar-date {
|
||||||
|
background-color: #0e49b5 !important;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-calendar-date:hover,
|
||||||
|
.dark .ant-calendar-time-picker-select li:hover {
|
||||||
|
background-color: #313f5a;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-calendar-header a:hover,
|
||||||
|
.dark .ant-calendar-header a:hover::before,
|
||||||
|
.dark .ant-calendar-header a:hover::after {
|
||||||
|
border-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-calendar-time-picker-select li:focus {
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
|
outline: none;
|
||||||
|
background-color: #0e49b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-calendar-time-picker-select {
|
||||||
|
border-right-color: #2C3950;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .anticon-close-circle {
|
||||||
|
color: #E04141;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-spin-nested-loading>div>.ant-spin .ant-spin-text {
|
||||||
|
text-shadow: 0 1px 2px #00000077;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-spin {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-spin-dot-item {
|
||||||
|
background-color: #ffffffff;
|
||||||
|
}
|
||||||
@@ -139,6 +139,19 @@ class DBInbound {
|
|||||||
return Inbound.fromJson(config);
|
return Inbound.fromJson(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isMultiUser() {
|
||||||
|
switch (this.protocol) {
|
||||||
|
case Protocols.VMESS:
|
||||||
|
case Protocols.VLESS:
|
||||||
|
case Protocols.TROJAN:
|
||||||
|
return true;
|
||||||
|
case Protocols.SHADOWSOCKS:
|
||||||
|
return this.toInbound().isSSMultiUser;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hasLink() {
|
hasLink() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
@@ -183,7 +196,6 @@ class AllSetting {
|
|||||||
this.tgBotLoginNotify = false;
|
this.tgBotLoginNotify = false;
|
||||||
this.tgCpu = "";
|
this.tgCpu = "";
|
||||||
this.tgLang = "";
|
this.tgLang = "";
|
||||||
this.xrayTemplateConfig = "";
|
|
||||||
this.subEnable = false;
|
this.subEnable = false;
|
||||||
this.subListen = "";
|
this.subListen = "";
|
||||||
this.subPort = "2096";
|
this.subPort = "2096";
|
||||||
|
|||||||
@@ -1577,7 +1577,7 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
@@ -1586,6 +1586,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
|||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
@@ -1597,6 +1598,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
|||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
get _expiryTime() {
|
get _expiryTime() {
|
||||||
@@ -1665,7 +1667,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
|||||||
|
|
||||||
};
|
};
|
||||||
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
@@ -1675,6 +1677,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
|||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
@@ -1687,6 +1690,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
|||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1786,7 +1790,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
constructor(password=RandomUtil.randomSeq(10), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
constructor(password=RandomUtil.randomSeq(10), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
|
||||||
super();
|
super();
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
@@ -1795,6 +1799,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
@@ -1806,6 +1811,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||||||
enable: this.enable,
|
enable: this.enable,
|
||||||
tgId: this.tgId,
|
tgId: this.tgId,
|
||||||
subId: this.subId,
|
subId: this.subId,
|
||||||
|
reset: this.reset,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1818,6 +1824,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1922,7 +1929,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(9), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
|
||||||
super();
|
super();
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
@@ -1932,6 +1939,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
|||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
@@ -1944,6 +1952,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
|||||||
enable: this.enable,
|
enable: this.enable,
|
||||||
tgId: this.tgId,
|
tgId: this.tgId,
|
||||||
subId: this.subId,
|
subId: this.subId,
|
||||||
|
reset: this.reset,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1957,6 +1966,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
|||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,13 +33,15 @@ function safeBase64(str) {
|
|||||||
|
|
||||||
function formatSecond(second) {
|
function formatSecond(second) {
|
||||||
if (second < 60) {
|
if (second < 60) {
|
||||||
return second.toFixed(0) + ' s';
|
return second.toFixed(0) + 's';
|
||||||
} else if (second < 3600) {
|
} else if (second < 3600) {
|
||||||
return (second / 60).toFixed(0) + ' m';
|
return (second / 60).toFixed(0) + 'm';
|
||||||
} else if (second < 3600 * 24) {
|
} else if (second < 3600 * 24) {
|
||||||
return (second / 3600).toFixed(0) + ' h';
|
return (second / 3600).toFixed(0) + 'h';
|
||||||
} else {
|
} else {
|
||||||
return (second / 3600 / 24).toFixed(0) + ' d';
|
day = Math.floor(second / 3600 / 24);
|
||||||
|
remain = ((second/3600) - (day*24)).toFixed(0);
|
||||||
|
return day + 'd' + (remain > 0 ? ' ' + remain + 'h' : '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +55,7 @@ function addZero(num) {
|
|||||||
|
|
||||||
function toFixed(num, n) {
|
function toFixed(num, n) {
|
||||||
n = Math.pow(10, n);
|
n = Math.pow(10, n);
|
||||||
return Math.round(num * n) / n;
|
return Math.floor(num * n) / n;
|
||||||
}
|
}
|
||||||
|
|
||||||
function debounce(fn, delay) {
|
function debounce(fn, delay) {
|
||||||
@@ -94,11 +96,13 @@ function setCookie(cname, cvalue, exdays) {
|
|||||||
function usageColor(data, threshold, total) {
|
function usageColor(data, threshold, total) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case data === null:
|
case data === null:
|
||||||
|
return "green";
|
||||||
|
case total < 0:
|
||||||
return "blue";
|
return "blue";
|
||||||
case total <= 0:
|
case total == 0:
|
||||||
return "blue";
|
return "purple";
|
||||||
case data < total - threshold:
|
case data < total - threshold:
|
||||||
return "cyan";
|
return "blue";
|
||||||
case data < total:
|
case data < total:
|
||||||
return "orange";
|
return "orange";
|
||||||
default:
|
default:
|
||||||
@@ -106,6 +110,28 @@ function usageColor(data, threshold, total) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function userExpiryColor(threshold, client, isDark = false) {
|
||||||
|
if (!client.enable) {
|
||||||
|
return isDark ? '#2c3950' : '#bcbcbc';
|
||||||
|
}
|
||||||
|
now = new Date().getTime(),
|
||||||
|
expiry = client.expiryTime;
|
||||||
|
switch (true) {
|
||||||
|
case expiry === null:
|
||||||
|
return "#389e0d";
|
||||||
|
case expiry < 0:
|
||||||
|
return "#0e49b5";
|
||||||
|
case expiry == 0:
|
||||||
|
return "#7a316f";
|
||||||
|
case now < expiry - threshold:
|
||||||
|
return "#0e49b5";
|
||||||
|
case now < expiry:
|
||||||
|
return "#ffa031";
|
||||||
|
default:
|
||||||
|
return "#e04141";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function doAllItemsExist(array1, array2) {
|
function doAllItemsExist(array1, array2) {
|
||||||
for (let i = 0; i < array1.length; i++) {
|
for (let i = 0; i < array1.length; i++) {
|
||||||
if (!array2.includes(array1[i])) {
|
if (!array2.includes(array1[i])) {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||||
|
g.POST("/onlines", a.onlines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) getInbounds(c *gin.Context) {
|
func (a *InboundController) getInbounds(c *gin.Context) {
|
||||||
@@ -253,3 +253,7 @@ func (a *InboundController) delDepletedClients(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
jsonMsg(c, "All delpeted clients are deleted", nil)
|
jsonMsg(c, "All delpeted clients are deleted", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *InboundController) onlines(c *gin.Context) {
|
||||||
|
jsonObj(c, a.inboundService.GetOnlineClinets(), nil)
|
||||||
|
}
|
||||||
|
|||||||
50
web/controller/xraySetting.go
Normal file
50
web/controller/xraySetting.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XraySettingController struct {
|
||||||
|
XraySettingService service.XraySettingService
|
||||||
|
SettingService service.SettingService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXraySettingController(g *gin.RouterGroup) *XraySettingController {
|
||||||
|
a := &XraySettingController{}
|
||||||
|
a.initRouter(g)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
|
||||||
|
g = g.Group("/xray")
|
||||||
|
|
||||||
|
g.POST("/", a.getXraySetting)
|
||||||
|
g.POST("/update", a.updateSetting)
|
||||||
|
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
||||||
|
xraySetting, err := a.SettingService.GetXrayConfigTemplate()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, xraySetting, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) updateSetting(c *gin.Context) {
|
||||||
|
xraySetting := c.PostForm("xraySetting")
|
||||||
|
err := a.XraySettingService.SaveXraySetting(xraySetting)
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) getDefaultXrayConfig(c *gin.Context) {
|
||||||
|
defaultJsonConfig, err := a.SettingService.GetDefaultXrayConfig()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, defaultJsonConfig, nil)
|
||||||
|
}
|
||||||
@@ -7,8 +7,9 @@ import (
|
|||||||
type XUIController struct {
|
type XUIController struct {
|
||||||
BaseController
|
BaseController
|
||||||
|
|
||||||
inboundController *InboundController
|
inboundController *InboundController
|
||||||
settingController *SettingController
|
settingController *SettingController
|
||||||
|
xraySettingController *XraySettingController
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXUIController(g *gin.RouterGroup) *XUIController {
|
func NewXUIController(g *gin.RouterGroup) *XUIController {
|
||||||
@@ -24,9 +25,11 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.GET("/", a.index)
|
g.GET("/", a.index)
|
||||||
g.GET("/inbounds", a.inbounds)
|
g.GET("/inbounds", a.inbounds)
|
||||||
g.GET("/settings", a.settings)
|
g.GET("/settings", a.settings)
|
||||||
|
g.GET("/xray", a.xraySettings)
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundController = NewInboundController(g)
|
||||||
a.settingController = NewSettingController(g)
|
a.settingController = NewSettingController(g)
|
||||||
|
a.xraySettingController = NewXraySettingController(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XUIController) index(c *gin.Context) {
|
func (a *XUIController) index(c *gin.Context) {
|
||||||
@@ -40,3 +43,7 @@ func (a *XUIController) inbounds(c *gin.Context) {
|
|||||||
func (a *XUIController) settings(c *gin.Context) {
|
func (a *XUIController) settings(c *gin.Context) {
|
||||||
html(c, "settings.html", "pages.settings.title", nil)
|
html(c, "settings.html", "pages.settings.title", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *XUIController) xraySettings(c *gin.Context) {
|
||||||
|
html(c, "xray.html", "pages.xray.title", nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ package entity
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
"x-ui/xray"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Msg struct {
|
type Msg struct {
|
||||||
@@ -17,36 +15,35 @@ type Msg struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AllSetting struct {
|
type AllSetting struct {
|
||||||
WebListen string `json:"webListen" form:"webListen"`
|
WebListen string `json:"webListen" form:"webListen"`
|
||||||
WebDomain string `json:"webDomain" form:"webDomain"`
|
WebDomain string `json:"webDomain" form:"webDomain"`
|
||||||
WebPort int `json:"webPort" form:"webPort"`
|
WebPort int `json:"webPort" form:"webPort"`
|
||||||
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
||||||
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
||||||
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
||||||
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
|
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
|
||||||
PageSize int `json:"pageSize" form:"pageSize"`
|
PageSize int `json:"pageSize" form:"pageSize"`
|
||||||
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
||||||
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
||||||
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
||||||
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
|
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
|
||||||
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
||||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
||||||
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
|
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
|
||||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||||
TgLang string `json:"tgLang" form:"tgLang"`
|
TgLang string `json:"tgLang" form:"tgLang"`
|
||||||
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||||
SubEnable bool `json:"subEnable" form:"subEnable"`
|
SubListen string `json:"subListen" form:"subListen"`
|
||||||
SubListen string `json:"subListen" form:"subListen"`
|
SubPort int `json:"subPort" form:"subPort"`
|
||||||
SubPort int `json:"subPort" form:"subPort"`
|
SubPath string `json:"subPath" form:"subPath"`
|
||||||
SubPath string `json:"subPath" form:"subPath"`
|
SubDomain string `json:"subDomain" form:"subDomain"`
|
||||||
SubDomain string `json:"subDomain" form:"subDomain"`
|
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
||||||
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
||||||
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
||||||
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||||
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AllSetting) CheckValid() error {
|
func (s *AllSetting) CheckValid() error {
|
||||||
@@ -97,13 +94,7 @@ func (s *AllSetting) CheckValid() error {
|
|||||||
s.WebBasePath += "/"
|
s.WebBasePath += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
xrayConfig := &xray.Config{}
|
_, err := time.LoadLocation(s.TimeLocation)
|
||||||
err := json.Unmarshal([]byte(s.XrayTemplateConfig), xrayConfig)
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("xray template config invalid:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = time.LoadLocation(s.TimeLocation)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewError("time location not exist:", s.TimeLocation)
|
return common.NewError("time location not exist:", s.TimeLocation)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,20 @@
|
|||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
/* vazirmatn-regular - arabic_latin_latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
font-family: 'Vazirmatn';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('{{ .base_path }}assets/Vazirmatn-UI-NL-Regular.woff2') format('woff2');
|
||||||
|
unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC, U+0030-0039;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Vazirmatn', 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
|
||||||
|
'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji',
|
||||||
|
'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<title>{{ .host }}-{{ i18n .title}}</title>
|
<title>{{ .host }}-{{ i18n .title}}</title>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{{define "promptModal"}}
|
{{define "promptModal"}}
|
||||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
||||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
||||||
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}'>
|
|
||||||
<a-input id="prompt-modal-input" :type="promptModal.type"
|
<a-input id="prompt-modal-input" :type="promptModal.type"
|
||||||
v-model="promptModal.value"
|
v-model="promptModal.value"
|
||||||
:autosize="{minRows: 10, maxRows: 20}"
|
:autosize="{minRows: 10, maxRows: 20}"
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
{{define "qrcodeModal"}}
|
{{define "qrcodeModal"}}
|
||||||
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:footer="null"
|
:footer="null"
|
||||||
width="300px">
|
width="300px" :class="themeSwitcher.currentTheme">
|
||||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
|
<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">
|
<template v-if="app.subSettings.enable && qrModal.subId">
|
||||||
<a-divider>Subscription</a-divider>
|
<a-divider>Subscription</a-divider>
|
||||||
@@ -11,7 +10,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
<template v-for="(row, index) in qrModal.qrcodes">
|
<template v-for="(row, index) in qrModal.qrcodes">
|
||||||
<a-tag color="orange" style="margin-top: 10px;display: block;text-align: center;">[[ row.remark ]]</a-tag>
|
<a-tag color="blue" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
|
||||||
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
|
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{{define "textModal"}}
|
{{define "textModal"}}
|
||||||
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
||||||
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
||||||
:class="themeSwitcher.darkCardClass"
|
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}" :class="themeSwitcher.currentTheme">
|
||||||
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
|
||||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
||||||
:download="txtModal.fileName">
|
:download="txtModal.fileName">
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
{{template "head" .}}
|
{{template "head" .}}
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
#app {
|
|
||||||
padding-top: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 20px 0 50px 0;
|
margin: 20px 0 50px 0;
|
||||||
@@ -43,23 +38,88 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#login {
|
||||||
|
animation: charge .5s both;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 2rem;
|
||||||
|
padding: 3rem;
|
||||||
|
}
|
||||||
|
#login:hover {
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,.09);
|
||||||
|
}
|
||||||
|
@keyframes charge {
|
||||||
|
from {transform: translateY(5rem);opacity: 0}
|
||||||
|
to {transform: translateY(0);opacity: 1}
|
||||||
|
}
|
||||||
|
@keyframes wave {
|
||||||
|
from {transform: rotate(0deg);}
|
||||||
|
to {transform: rotate(360deg);}
|
||||||
|
}
|
||||||
|
.wave {
|
||||||
|
opacity: .6;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 40%;
|
||||||
|
left: 50%;
|
||||||
|
width: 6000px;
|
||||||
|
height: 6000px;
|
||||||
|
background: #000;
|
||||||
|
margin-left: -3000px;
|
||||||
|
transform-origin: 50% 48%;
|
||||||
|
border-radius: 46%;
|
||||||
|
animation: wave 72s infinite linear;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.wave2 {
|
||||||
|
animation: wave 88s infinite linear;
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.wave3 {
|
||||||
|
animation: wave 80s infinite linear;
|
||||||
|
opacity: .1;
|
||||||
|
}
|
||||||
|
.wave {
|
||||||
|
background: #0e49b515;
|
||||||
|
}
|
||||||
|
.under {
|
||||||
|
background-color: #dce9f5;
|
||||||
|
}
|
||||||
|
.dark .wave {
|
||||||
|
background: rgb(14 73 181 / 20%);
|
||||||
|
}
|
||||||
|
.dark .under {
|
||||||
|
background-color: #101828;
|
||||||
|
}
|
||||||
|
.dark #login {
|
||||||
|
background-color: #151F31;
|
||||||
|
}
|
||||||
|
.dark h1 {
|
||||||
|
color: rgb(255 255 255 / 85%);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak :class="themeSwitcher.darkCardClass">
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-layout-content>
|
<a-layout-content class="under">
|
||||||
|
<div class='wave'></div>
|
||||||
|
<div class='wave wave2'></div>
|
||||||
|
<div class='wave wave3'></div>
|
||||||
|
<a-row type="flex" justify="center" align="middle" style="height: 100%;">
|
||||||
|
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="6" id="login">
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
|
<a-col>
|
||||||
<h1 class="title" :style="themeSwitcher.textStyle">{{ i18n "pages.login.title" }}</h1>
|
<h1 class="title">{{ i18n "pages.login.title" }}</h1>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
|
<a-col span="24">
|
||||||
<a-form>
|
<a-form>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
||||||
@keydown.enter.native="login" autofocus>
|
@keydown.enter.native="login" autofocus>
|
||||||
<a-icon slot="prefix" type="user" :style="'font-size: 16px;' + themeSwitcher.textStyle"/>
|
<a-icon slot="prefix" type="user" style="font-size: 16px;"/>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -77,8 +137,8 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<a-row justify="center" class="centered">
|
||||||
<a-col :span="12">
|
<a-col :span="24">
|
||||||
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" style="width: 150px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||||
<span v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
@@ -89,12 +149,19 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<a-row justify="center" class="centered">
|
||||||
<theme-switch />
|
<a-col>
|
||||||
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
|
</a-col>
|
||||||
|
<a-col>
|
||||||
|
<theme-switch />
|
||||||
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</transition>
|
</transition>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
{{define "clientsBulkModal"}}
|
{{define "clientsBulkModal"}}
|
||||||
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
|
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
|
||||||
:confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
|
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<table width="100%" class="ant-table-tbody">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "pages.client.method" }}</td>
|
<td>{{ i18n "pages.client.method" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 250px"
|
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option :value="0">Random</a-select-option>
|
<a-select-option :value="0">Random</a-select-option>
|
||||||
<a-select-option :value="1">Random+Prefix</a-select-option>
|
<a-select-option :value="1">Random+Prefix</a-select-option>
|
||||||
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
||||||
@@ -64,7 +62,7 @@
|
|||||||
<td>Flow</td>
|
<td>Flow</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="clientsBulkModal.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="clientsBulkModal.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -132,11 +130,27 @@
|
|||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
|
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr v-if="clientsBulkModal.expiryTime != 0">
|
||||||
|
<td>
|
||||||
|
<span>{{ i18n "pages.client.renew" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.client.renewDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -162,6 +176,7 @@
|
|||||||
tgId: "",
|
tgId: "",
|
||||||
flow: "",
|
flow: "",
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
|
reset: 0,
|
||||||
ok() {
|
ok() {
|
||||||
clients = [];
|
clients = [];
|
||||||
method=clientsBulkModal.emailMethod;
|
method=clientsBulkModal.emailMethod;
|
||||||
@@ -186,6 +201,7 @@
|
|||||||
if(clientsBulkModal.inbound.canEnableTlsFlow()){
|
if(clientsBulkModal.inbound.canEnableTlsFlow()){
|
||||||
newClient.flow = clientsBulkModal.flow;
|
newClient.flow = clientsBulkModal.flow;
|
||||||
}
|
}
|
||||||
|
newClient.reset = clientsBulkModal.reset;
|
||||||
clients.push(newClient);
|
clients.push(newClient);
|
||||||
}
|
}
|
||||||
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
||||||
@@ -209,6 +225,7 @@
|
|||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.delayedStart = false;
|
this.delayedStart = false;
|
||||||
|
this.reset = 0;
|
||||||
},
|
},
|
||||||
newClient(protocol) {
|
newClient(protocol) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{{define "clientsModal"}}
|
{{define "clientsModal"}}
|
||||||
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
||||||
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
<template v-if="isEdit">
|
||||||
|
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
|
||||||
|
</template>
|
||||||
{{template "form/client"}}
|
{{template "form/client"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
@@ -111,6 +113,12 @@
|
|||||||
get statsColor() {
|
get statsColor() {
|
||||||
return usageColor(clientStats.up + clientStats.down, app.trafficDiff, this.client.totalGB);
|
return usageColor(clientStats.up + clientStats.down, app.trafficDiff, this.client.totalGB);
|
||||||
},
|
},
|
||||||
|
get delayedStart() {
|
||||||
|
return this.clientModal.delayedStart;
|
||||||
|
},
|
||||||
|
set delayedStart(value) {
|
||||||
|
this.clientModal.delayedStart = value;
|
||||||
|
},
|
||||||
get delayedExpireDays() {
|
get delayedExpireDays() {
|
||||||
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||||
},
|
},
|
||||||
@@ -123,7 +131,7 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
<a-icon type="setting"></a-icon>
|
<a-icon type="setting"></a-icon>
|
||||||
<span>{{ i18n "menu.settings"}}</span>
|
<span>{{ i18n "menu.settings"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item key="{{ .base_path }}xui/xray">
|
||||||
|
<a-icon type="tool"></a-icon>
|
||||||
|
<span>{{ i18n "menu.xray"}}</span>
|
||||||
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}logout">
|
<a-menu-item key="{{ .base_path }}logout">
|
||||||
<a-icon type="logout"></a-icon>
|
<a-icon type="logout"></a-icon>
|
||||||
<span>{{ i18n "menu.logout"}}</span>
|
<span>{{ i18n "menu.logout"}}</span>
|
||||||
@@ -22,7 +26,7 @@
|
|||||||
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
<a-menu-item mode="inline">
|
<a-menu-item mode="inline">
|
||||||
<a-icon type="bg-colors"></a-icon>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
<theme-switch />
|
<theme-switch />
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
@@ -31,17 +35,16 @@
|
|||||||
{{template "menuItems" .}}
|
{{template "menuItems" .}}
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
<a-drawer id="sider-drawer" placement="left" :closable="false"
|
<a-drawer id="sider-drawer" placement="left" :closable="false" :class="themeSwitcher.currentTheme"
|
||||||
@close="siderDrawer.close()"
|
@close="siderDrawer.close()"
|
||||||
:visible="siderDrawer.visible"
|
:visible="siderDrawer.visible"
|
||||||
:wrap-class-name="themeSwitcher.darkDrawerClass"
|
|
||||||
:wrap-style="{ padding: 0 }">
|
:wrap-style="{ padding: 0 }">
|
||||||
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
||||||
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
||||||
</div>
|
</div>
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
<a-menu-item mode="inline">
|
<a-menu-item mode="inline">
|
||||||
<a-icon type="bg-colors"></a-icon>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
<theme-switch />
|
<theme-switch />
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@input="$emit('input', $event.target.value)">
|
@input="$emit('input', $event.target.value)">
|
||||||
<template v-if="icon" #prefix>
|
<template v-if="icon" #prefix>
|
||||||
<a-icon :type="icon" :style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
<a-icon :type="icon" style="font-size: 16px;" />
|
||||||
</template>
|
</template>
|
||||||
<template #addonAfter>
|
<template #addonAfter>
|
||||||
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
|
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
|
||||||
@click="toggleShowPassword"
|
@click="toggleShowPassword"
|
||||||
:style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
style="font-size: 16px;" />
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
{{define "component/themeSwitchTemplate"}}
|
{{define "component/themeSwitchTemplate"}}
|
||||||
<template>
|
<template>
|
||||||
<a-switch :default-checked="themeSwitcher.isDarkTheme"
|
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme"
|
||||||
checked-children="☀"
|
|
||||||
un-checked-children="🌙"
|
|
||||||
@change="themeSwitcher.toggleTheme()">
|
@change="themeSwitcher.toggleTheme()">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
</template>
|
</template>
|
||||||
@@ -10,39 +8,17 @@
|
|||||||
|
|
||||||
{{define "component/themeSwitcher"}}
|
{{define "component/themeSwitcher"}}
|
||||||
<script>
|
<script>
|
||||||
const colors = {
|
|
||||||
dark: {
|
|
||||||
bg: "#242c3a",
|
|
||||||
text: "hsla(0,0%,100%,.65)"
|
|
||||||
},
|
|
||||||
light: {
|
|
||||||
bg: '#f0f2f5',
|
|
||||||
text: "rgba(0, 0, 0, 0.7)",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createThemeSwitcher() {
|
function createThemeSwitcher() {
|
||||||
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
||||||
const theme = isDarkTheme ? 'dark' : 'light';
|
const theme = isDarkTheme ? 'dark' : 'light';
|
||||||
return {
|
return {
|
||||||
isDarkTheme,
|
isDarkTheme,
|
||||||
bgStyle: `background: ${colors[theme].bg};`,
|
|
||||||
textStyle: `color: ${colors[theme].text};`,
|
|
||||||
darkClass: isDarkTheme ? 'ant-dark' : '',
|
|
||||||
darkCardClass: isDarkTheme ? 'ant-card-dark' : '',
|
|
||||||
darkDrawerClass: isDarkTheme ? 'ant-drawer-dark' : '',
|
|
||||||
get currentTheme() {
|
get currentTheme() {
|
||||||
return this.isDarkTheme ? 'dark' : 'light';
|
return this.isDarkTheme ? 'dark' : 'light';
|
||||||
},
|
},
|
||||||
toggleTheme() {
|
toggleTheme() {
|
||||||
this.isDarkTheme = !this.isDarkTheme;
|
this.isDarkTheme = !this.isDarkTheme;
|
||||||
this.theme = this.isDarkTheme ? 'dark' : 'light';
|
|
||||||
localStorage.setItem('dark-mode', this.isDarkTheme);
|
localStorage.setItem('dark-mode', this.isDarkTheme);
|
||||||
this.bgStyle = `background: ${colors[this.theme].bg};`;
|
|
||||||
this.textStyle = `color: ${colors[this.theme].text};`;
|
|
||||||
this.darkClass = this.isDarkTheme ? 'ant-dark' : '';
|
|
||||||
this.darkCardClass = this.isDarkTheme ? 'ant-card-dark' : '';
|
|
||||||
this.darkDrawerClass = this.isDarkTheme ? 'ant-drawer-dark' : '';
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
{{define "form/client"}}
|
{{define "form/client"}}
|
||||||
<a-form layout="inline" v-if="client">
|
<a-form layout="inline" v-if="client">
|
||||||
<template v-if="isEdit">
|
|
||||||
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
|
|
||||||
</template>
|
|
||||||
<table width="100%" class="ant-table-tbody">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "pages.inbounds.enable" }}</td>
|
<td>{{ i18n "pages.inbounds.enable" }}</td>
|
||||||
@@ -66,7 +63,7 @@
|
|||||||
<td>Flow</td>
|
<td>Flow</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="client.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="client.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -107,11 +104,11 @@
|
|||||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="clientModal.delayedStart">
|
<tr v-if="delayedStart">
|
||||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
<td>{{ i18n "pages.client.expireDays" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -132,9 +129,25 @@
|
|||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
|
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="client.expiryTime != 0">
|
||||||
|
<td>
|
||||||
|
<span>{{ i18n "pages.client.renew" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.client.renewDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="client.reset" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<td>{{ i18n "protocol" }}</td>
|
<td>{{ i18n "protocol" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.protocol" style="width: 250px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.protocol" style="width: 250px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<td>{{ i18n "pages.inbounds.network"}}</td>
|
<td>{{ i18n "pages.inbounds.network"}}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||||
<a-select-option value="tcp">tcp</a-select-option>
|
<a-select-option value="tcp">tcp</a-select-option>
|
||||||
<a-select-option value="udp">udp</a-select-option>
|
<a-select-option value="udp">udp</a-select-option>
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
{{define "form/http"}}
|
{{define "form/http"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||||
<a-row>
|
<tr>
|
||||||
<a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
</a-row>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
<a-input-group v-for="(account, index) in inbound.settings.accounts">
|
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())">+</a-button></td>
|
||||||
<a-input style="width: 45%" v-model.trim="account.user"
|
</tr>
|
||||||
addon-before='{{ i18n "username" }}'></a-input>
|
</table>
|
||||||
<a-input style="width: 55%" v-model.trim="account.pass"
|
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
||||||
addon-before='{{ i18n "password" }}'>
|
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||||
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
|
</a-input>
|
||||||
|
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -3,100 +3,7 @@
|
|||||||
<template v-if="inbound.isSSMultiUser">
|
<template v-if="inbound.isSSMultiUser">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<table width="100%" class="ant-table-tbody">
|
{{template "form/client"}}
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>password
|
|
||||||
<a-icon @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.password" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
|
||||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.tgBotEnable">
|
|
||||||
<td>Telegram Username</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="delayedStart">
|
|
||||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-else>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
@@ -119,7 +26,7 @@
|
|||||||
<td>{{ i18n "encryption" }}</td>
|
<td>{{ i18n "encryption" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.darkCardClass" @change="SSMethodChange">
|
<a-select v-model="inbound.settings.method" style="width: 250px;" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -139,7 +46,7 @@
|
|||||||
<td>{{ i18n "pages.inbounds.network" }}</td>
|
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||||
<a-select-option value="tcp">tcp</a-select-option>
|
<a-select-option value="tcp">tcp</a-select-option>
|
||||||
<a-select-option value="udp">udp</a-select-option>
|
<a-select-option value="udp">udp</a-select-option>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<table width="100%" class="ant-table-tbody">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "password" }}</td>
|
<td style="width: 30%;">{{ i18n "password" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-switch :checked="inbound.settings.auth === 'password'"
|
<a-switch :checked="inbound.settings.auth === 'password'"
|
||||||
@@ -12,21 +12,23 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr v-if="inbound.settings.auth === 'password'">
|
<tr v-if="inbound.settings.auth === 'password'">
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<a-form-item>
|
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||||
<a-row>
|
<tr>
|
||||||
<a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
</a-row>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
<a-input-group v-for="(account, index) in inbound.settings.accounts">
|
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td>
|
||||||
<a-input style="width: 45%" v-model.trim="account.user"
|
</tr>
|
||||||
addon-before='{{ i18n "username" }}'></a-input>
|
</table>
|
||||||
<a-input style="width: 55%" v-model.trim="account.pass"
|
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
||||||
addon-before='{{ i18n "password" }}'>
|
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
</a-input>
|
||||||
</template>
|
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||||
</a-input>
|
<template slot="addonAfter">
|
||||||
</a-input-group>
|
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||||
</a-form-item>
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -2,98 +2,7 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<table width="100%" class="ant-table-tbody">
|
{{template "form/client"}}
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Password</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.password" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
|
||||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.tgBotEnable">
|
|
||||||
<td>Telegram Username</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="delayedStart">
|
|
||||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-else>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
@@ -124,7 +33,7 @@
|
|||||||
|
|
||||||
<!-- trojan fallbacks -->
|
<!-- trojan fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
||||||
<a-divider>
|
<a-divider style="margin:0;">
|
||||||
fallback[[ index + 1 ]]
|
fallback[[ index + 1 ]]
|
||||||
<a-icon type="delete" @click="() => inbound.settings.delTrojanFallback(index)"
|
<a-icon type="delete" @click="() => inbound.settings.delTrojanFallback(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
||||||
@@ -144,7 +53,7 @@
|
|||||||
<a-form-item label="xver">
|
<a-form-item label="xver">
|
||||||
<a-input-number v-model.number="fallback.xver"></a-input-number>
|
<a-input-number v-model.number="fallback.xver"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
|
<a-divider style="margin:0;"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -2,109 +2,7 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<table width="100%" class="ant-table-tbody">
|
{{template "form/client"}}
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(9)" type="sync"> </a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon></td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.id" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="inbound.canEnableTlsFlow()">
|
|
||||||
<td>Flow</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
|
||||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.tgBotEnable">
|
|
||||||
<td>Telegram Username</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="delayedStart">
|
|
||||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-else>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
@@ -137,7 +35,7 @@
|
|||||||
|
|
||||||
<!-- vless fallbacks -->
|
<!-- vless fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
||||||
<a-divider>
|
<a-divider style="margin:0;">
|
||||||
fallback[[ index + 1 ]]
|
fallback[[ index + 1 ]]
|
||||||
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
||||||
@@ -157,7 +55,7 @@
|
|||||||
<a-form-item label="xver">
|
<a-form-item label="xver">
|
||||||
<a-input-number v-model.number="fallback.xver"></a-input-number>
|
<a-input-number v-model.number="fallback.xver"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
|
<a-divider style="margin:0;"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -2,98 +2,7 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<table width="100%" class="ant-table-tbody">
|
{{template "form/client"}}
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon></td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.id" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.subSettings.enable">
|
|
||||||
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="client.email && app.tgBotEnable">
|
|
||||||
<td>Telegram Username</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="delayedStart">
|
|
||||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-else>
|
|
||||||
<td>
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<td>{{ i18n "camouflage" }}</td>
|
<td>{{ i18n "camouflage" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||||
<a-select-option value="utp">utp (BT download)</a-select-option>
|
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
|
<td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.quic.security" style="width: 200px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.quic.security" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">none</a-select-option>
|
<a-select-option value="none">none</a-select-option>
|
||||||
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||||
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<td>{{ i18n "password" }}</td>
|
<td>{{ i18n "password" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="inbound.stream.quic.key" style="width: 200px;"></a-input>
|
<a-input v-model.trim="inbound.stream.quic.key" style="width: 250px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<td>{{ i18n "camouflage" }}</td>
|
<td>{{ i18n "camouflage" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.quic.type" style="width: 200px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.quic.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||||
<a-select-option value="utp">utp (BT download)</a-select-option>
|
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
{{define "form/streamSettings"}}
|
{{define "form/streamSettings"}}
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-divider style="margin:0;"></a-divider>
|
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="{{ i18n "transmission" }}">
|
<a-form-item label="{{ i18n "transmission" }}">
|
||||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass" style="width: 150px;">
|
style="width: 150px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="kcp">KCP</a-select-option>
|
<a-select-option value="kcp">KCP</a-select-option>
|
||||||
<a-select-option value="ws">WebSocket</a-select-option>
|
<a-select-option value="ws">WebSocket</a-select-option>
|
||||||
|
|||||||
@@ -33,8 +33,7 @@
|
|||||||
<td>T-Proxy</td>
|
<td>T-Proxy</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;"
|
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="off">OFF</a-select-option>
|
<a-select-option value="off">OFF</a-select-option>
|
||||||
<a-select-option value="redirect">Redirect</a-select-option>
|
<a-select-option value="redirect">Redirect</a-select-option>
|
||||||
<a-select-option value="tproxy">T-Proxy</a-select-option>
|
<a-select-option value="tproxy">T-Proxy</a-select-option>
|
||||||
|
|||||||
@@ -42,25 +42,16 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2" width="100%">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
<a-form-item>
|
||||||
<a-row>
|
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
|
||||||
<a-button size="small"
|
<a-button size="small" style="margin-left: 10px" @click="inbound.stream.tcp.request.addHeader('', '')">+</a-button>
|
||||||
@click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
|
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||||
+
|
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||||
</a-button>
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
</a-row>
|
</a-input>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
|
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
<a-button slot="addonAfter" size="small" @click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
|
|
||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
|
||||||
<template slot="addonAfter">
|
|
||||||
<a-button size="small"
|
|
||||||
@click="inbound.stream.tcp.request.removeHeader(index)">
|
|
||||||
-
|
|
||||||
</a-button>
|
|
||||||
</template>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -92,24 +83,19 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2" width="100%">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
|
<a-form-item>
|
||||||
<a-row>
|
<span>{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}:</span>
|
||||||
<a-button size="small"
|
<a-button size="small" style="margin-left: 10px"
|
||||||
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
|
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
|
||||||
+
|
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers">
|
||||||
</a-button>
|
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||||
</a-row>
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers">
|
</a-input>
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
|
|
||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<a-input style="width: 50%" v-model.trim="header.value"
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button size="small"
|
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
|
||||||
@click="inbound.stream.tcp.response.removeHeader(index)">
|
|
||||||
-
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
|
|||||||
@@ -1,47 +1,24 @@
|
|||||||
{{define "form/streamWS"}}
|
{{define "form/streamWS"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<table width="100%" class="ant-table-tbody">
|
<a-form-item label="AcceptProxyProtocol">
|
||||||
<tr v-if="inbound.canEnableTls()">
|
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
||||||
<td>Accept Proxy Protocol</td>
|
</a-form-item>
|
||||||
<td>
|
<br>
|
||||||
<a-form-item>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
<br>
|
||||||
</tr>
|
<a-form-item style="width: 100%;">
|
||||||
<tr>
|
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
|
||||||
<td>{{ i18n "path" }}</td>
|
<a-button size="small" style="margin-left: 10px" @click="inbound.stream.ws.addHeader()">+</a-button>
|
||||||
<td>
|
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
<a-form-item>
|
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||||
<a-input v-model.trim="inbound.stream.ws.path" style="width: 250px;"></a-input>
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
</a-form-item>
|
</a-input>
|
||||||
</td>
|
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
</tr>
|
<a-button slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)">-</a-button>
|
||||||
<tr>
|
</a-input>
|
||||||
<td colspan="2">
|
</a-input-group>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
</a-form-item>
|
||||||
<a-row>
|
|
||||||
<a-button size="small"
|
|
||||||
@click="inbound.stream.ws.addHeader('Host', '')">
|
|
||||||
+
|
|
||||||
</a-button>
|
|
||||||
</a-row>
|
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.ws.headers">
|
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.name"}}'></a-input>
|
|
||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
|
||||||
<template slot="addonAfter">
|
|
||||||
<a-button size="small"
|
|
||||||
@click="inbound.stream.ws.removeHeader(index)">
|
|
||||||
-
|
|
||||||
</a-button>
|
|
||||||
</template>
|
|
||||||
</a-input>
|
|
||||||
</a-input-group>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/tlsSettings"}}
|
{{define "form/tlsSettings"}}
|
||||||
<!-- tls enable -->
|
<!-- tls enable -->
|
||||||
<a-divider style="margin:0;"></a-divider>
|
|
||||||
<a-form v-if="inbound.canSetTls()" layout="inline">
|
<a-form v-if="inbound.canSetTls()" layout="inline">
|
||||||
|
<a-divider style="margin:0;"></a-divider>
|
||||||
<a-form-item label="TLS">
|
<a-form-item label="TLS">
|
||||||
<a-switch v-model="inbound.tls">
|
<a-switch v-model="inbound.tls">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
@@ -26,30 +26,25 @@
|
|||||||
<td>CipherSuites</td>
|
<td>CipherSuites</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="">auto</a-select-option>
|
<a-select-option value="">auto</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>MinVersion</td>
|
<td>Min/Max Version</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-input-group compact>
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select style="width: 125px" v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
</a-select>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-form-item>
|
</a-select>
|
||||||
</td>
|
<a-select style="width: 125px" v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
</tr>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
<tr>
|
</a-select>
|
||||||
<td>MaxVersion</td>
|
</a-input-group>
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-select v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -58,32 +53,30 @@
|
|||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||||
style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value=''>None</a-select-option>
|
<a-select-option value=''>None</a-select-option>
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr style="line-height: 40px;">
|
||||||
<td>Multi Domain</td>
|
<td>Multi Domain</td>
|
||||||
<td>
|
<td>
|
||||||
<a-switch v-model="multiDomain"></a-switch>
|
<a-switch v-model="multiDomain"></a-switch>
|
||||||
<a-button v-if="multiDomain" size="small" @click="inbound.stream.tls.settings.domains.push({remark: '', domain: ''})">+</a-button>
|
<a-button v-if="multiDomain" style="margin-left: 10px" size="small" @click="inbound.stream.tls.settings.domains.push({remark: '', domain: ''})">+</a-button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="multiDomain">
|
<tr v-if="multiDomain" style="line-height: 40px;">
|
||||||
<td colspan="2">
|
<td colspan="2" width="100%">
|
||||||
<a-form-item>
|
<a-input-group style="margin-top:5px;" compact v-for="(row, index) in inbound.stream.tls.settings.domains">
|
||||||
<a-input-group v-for="(row, index) in inbound.stream.tls.settings.domains">
|
<a-input style="width: 50%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'>
|
||||||
<a-input style="width: 40%" v-model.trim="row.remark" addon-before='{{ i18n "remark" }}'></a-input>
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
<a-input style="width: 60%" v-model.trim="row.domain" addon-before='{{ i18n "host" }}'>
|
</a-input>
|
||||||
<template slot="addonAfter">
|
<a-input style="width: 50%" v-model.trim="row.domain" placeholder='{{ i18n "host" }}'>
|
||||||
<a-button size="small" style="margin: 0px" @click="inbound.stream.tls.settings.domains.splice(index, 1)">-</a-button>
|
<a-button slot="addonAfter" size="small" style="margin: 0px" @click="inbound.stream.tls.settings.domains.splice(index, 1)">-</a-button>
|
||||||
</template>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-else>
|
<tr v-else>
|
||||||
@@ -101,7 +94,6 @@
|
|||||||
<a-select
|
<a-select
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
style="width: 250px"
|
style="width: 250px"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="inbound.stream.tls.alpn">
|
v-model="inbound.stream.tls.alpn">
|
||||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -126,7 +118,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<template v-for="cert,index in inbound.stream.tls.certs">
|
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2" width="100%">
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
<a-radio-group v-model="cert.useFile" button-style="solid">
|
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||||
@@ -223,7 +215,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||||
style="width: 250px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -2,34 +2,65 @@
|
|||||||
<template slot="actions" slot-scope="text, client, index">
|
<template slot="actions" slot-scope="text, client, index">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "qrCode" }}</template>
|
<template slot="title">{{ i18n "qrCode" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="qrcode" v-if="record.hasLink()" @click="showQrcode(record.id,client);"></a-icon>
|
<a-icon style="font-size: 24px;" class="normal-icon" type="qrcode" v-if="record.hasLink()" @click="showQrcode(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.client.edit" }}</template>
|
<template slot="title">{{ i18n "pages.client.edit" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="edit" @click="openEditClient(record.id,client);"></a-icon>
|
<a-icon style="font-size: 24px;" class="normal-icon" type="edit" @click="openEditClient(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "info" }}</template>
|
<template slot="title">{{ i18n "info" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="info-circle" @click="showInfo(record.id,client);"></a-icon>
|
<a-icon style="font-size: 24px;" class="normal-icon" type="info-circle" @click="showInfo(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="retweet" @click="resetClientTraffic(client,record.id)" v-if="client.email.length > 0"></a-icon>
|
<a-popconfirm @confirm="resetClientTraffic(client,record.id,false)"
|
||||||
|
title='{{ i18n "pages.inbounds.resetTrafficContent"}}'
|
||||||
|
:overlay-class-name="themeSwitcher.currentTheme"
|
||||||
|
ok-text='{{ i18n "reset"}}'
|
||||||
|
cancel-text='{{ i18n "cancel"}}'>
|
||||||
|
<a-icon slot="icon" type="question-circle-o" style="color: blue"></a-icon>
|
||||||
|
<a-icon style="font-size: 24px;" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon>
|
||||||
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title"><span style="color: #FF4D4F"> {{ i18n "delete"}}</span></template>
|
<template slot="title"><span style="color: #FF4D4F"> {{ i18n "delete"}}</span></template>
|
||||||
<a-icon style="font-size: 24px;" type="delete" v-if="isRemovable(record.id)" @click="delClient(record.id,client)"></a-icon>
|
<a-popconfirm @confirm="delClient(record.id,client,false)"
|
||||||
|
title='{{ i18n "pages.inbounds.deleteClientContent"}}'
|
||||||
|
:overlay-class-name="themeSwitcher.currentTheme"
|
||||||
|
ok-text='{{ i18n "delete"}}'
|
||||||
|
ok-type="danger"
|
||||||
|
cancel-text='{{ i18n "cancel"}}'>
|
||||||
|
<a-icon slot="icon" type="question-circle-o" style="color: #e04141"></a-icon>
|
||||||
|
<a-icon style="font-size: 24px" class="delete-icon" type="delete" v-if="isRemovable(record.id)"></a-icon>
|
||||||
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<template slot="enable" slot-scope="text, client, index">
|
<template slot="enable" slot-scope="text, client, index">
|
||||||
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
|
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
|
||||||
</template>
|
</template>
|
||||||
|
<template slot="online" slot-scope="text, client, index">
|
||||||
|
<template v-if="isClientOnline(client.email)">
|
||||||
|
<a-tag color="green">{{ i18n "online" }}</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-tag>{{ i18n "offline" }}</a-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
<template slot="client" slot-scope="text, client">
|
<template slot="client" slot-scope="text, client">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template>
|
||||||
|
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
|
||||||
|
<template v-else-if="isClientOnline(client.email)">{{ i18n "online" }}</template>
|
||||||
|
</template>
|
||||||
|
<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 ]]
|
[[ client.email ]]
|
||||||
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, client">
|
<template slot="traffic" slot-scope="text, client">
|
||||||
<a-popover :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content" v-if="client.email">
|
<template slot="content" v-if="client.email">
|
||||||
<table cellpadding="2" width="100%">
|
<table cellpadding="2" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -38,24 +69,200 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr v-if="client.totalGB > 0">
|
<tr v-if="client.totalGB > 0">
|
||||||
<td>{{ i18n "remained" }}</td>
|
<td>{{ i18n "remained" }}</td>
|
||||||
<td>[[ sizeFormat(client.totalGB - getUpStats(record, client.email) - getDownStats(record, client.email)) ]]</td>
|
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<a-tag :color="statsColor(record, client.email)">
|
<table>
|
||||||
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] /
|
<tr>
|
||||||
<template v-if="client.totalGB > 0">[[client._totalGB]]GB</template>
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
<template v-else>∞</template>
|
[[ sizeFormat(getSumStats(record, client.email)) ]]
|
||||||
</a-tag>
|
</td>
|
||||||
|
<td width="120px" v-if="!client.enable">
|
||||||
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'"
|
||||||
|
:show-info="false"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else-if="client.totalGB > 0">
|
||||||
|
<a-progress :stroke-color="statsColor(record, client.email)"
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else class="infinite-bar">
|
||||||
|
<a-progress
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : ''"
|
||||||
|
:percent="100"></a-progress>
|
||||||
|
</td>
|
||||||
|
<td width="60px">
|
||||||
|
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
|
||||||
|
<span v-else style="font-weight: 100;font-size: 14pt;">∞</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expiryTime" slot-scope="text, client, index">
|
<template slot="expiryTime" slot-scope="text, client, index">
|
||||||
<template v-if="client.expiryTime > 0">
|
<template v-if="client.expiryTime !=0 && client.reset >0">
|
||||||
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, client.expiryTime)">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
<template slot="content">
|
||||||
</a-tag>
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</td>
|
||||||
|
<td width="120px" class="infinite-bar">
|
||||||
|
<a-progress :show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
||||||
|
</td>
|
||||||
|
<td width="60px">[[ client.reset + "d" ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-else-if="client.expiryTime < 0" color="cyan">[[ client._expiryTime ]] {{ i18n "pages.client.days" }}</a-tag>
|
<template v-else>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-tag v-else :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" style="border: 0;" class="infinite-tag">∞</a-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template slot="actionMenu" slot-scope="text, client, index">
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="ellipsis" style="font-size: 20px;"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item v-if="record.hasLink()" @click="showQrcode(record.id,client);">
|
||||||
|
<a-icon style="font-size: 14px;" type="qrcode"></a-icon>
|
||||||
|
{{ i18n "qrCode" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="openEditClient(record.id,client);">
|
||||||
|
<a-icon style="font-size: 14px;" type="edit"></a-icon>
|
||||||
|
{{ i18n "pages.client.edit" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="showInfo(record.id,client);">
|
||||||
|
<a-icon style="font-size: 14px;" type="info-circle"></a-icon>
|
||||||
|
{{ i18n "info" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="resetClientTraffic(client,record.id)" v-if="client.email.length > 0">
|
||||||
|
<a-icon style="font-size: 14px;" type="retweet"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="isRemovable(record.id)" @click="delClient(record.id,client)">
|
||||||
|
<a-icon style="font-size: 14px;" type="delete"></a-icon>
|
||||||
|
<span style="color: #FF4D4F"> {{ i18n "delete"}}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item>
|
||||||
|
<a-switch v-model="client.enable" size="small" @change="switchEnableClient(record.id,client)">
|
||||||
|
</a-switch>
|
||||||
|
{{ i18n "enable"}}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
<template slot="info" slot-scope="text, client, index">
|
||||||
|
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
||||||
|
<template slot="content">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
|
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]]
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-if="!client.enable">
|
||||||
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'"
|
||||||
|
:show-info="false"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else-if="client.totalGB > 0">
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content" v-if="client.email">
|
||||||
|
<table cellpadding="2" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td>
|
||||||
|
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-progress :stroke-color="statsColor(record, client.email)"
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else class="infinite-bar">
|
||||||
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'"
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : ''"
|
||||||
|
:percent="100"></a-progress>
|
||||||
|
</td>
|
||||||
|
<td width="80px">
|
||||||
|
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
|
||||||
|
<span v-else style="font-weight: 100;font-size: 14pt;">∞</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" style="text-align: center;">
|
||||||
|
<a-divider style="margin: 0; border-collapse: separate;"></a-divider>
|
||||||
|
{{ i18n "pages.inbounds.expireDate" }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<template v-if="client.expiryTime !=0 && client.reset >0">
|
||||||
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</td>
|
||||||
|
<td width="120px" class="infinite-bar">
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<a-progress :show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
<td width="60px">[[ client.reset + "d" ]]</td>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<td colspan="3" style="text-align: center;">
|
||||||
|
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<a-tag style="min-width: 50px; border: none;"
|
||||||
|
:color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag">∞</a-tag>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-badge>
|
||||||
|
<a-icon v-if="!client.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="solution"></a-icon></a-button>
|
||||||
|
</a-badge>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:mask-closable="true"
|
:mask-closable="true"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:footer="null"
|
:footer="null"
|
||||||
width="600px"
|
width="600px"
|
||||||
|
:class="themeSwitcher.currentTheme"
|
||||||
>
|
>
|
||||||
<table style="margin-bottom: 10px; width: 100%;">
|
<table style="margin-bottom: 10px; width: 100%;">
|
||||||
<tr><td>
|
<tr><td>
|
||||||
@@ -311,7 +311,7 @@
|
|||||||
if(infoModal.clientStats){
|
if(infoModal.clientStats){
|
||||||
return infoModal.clientStats.enable;
|
return infoModal.clientStats.enable;
|
||||||
}
|
}
|
||||||
return infoModal.dbInbound.isEnable;
|
return true;
|
||||||
},
|
},
|
||||||
get isEnable() {
|
get isEnable() {
|
||||||
if(infoModal.clientSettings){
|
if(infoModal.clientSettings){
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{{define "inboundModal"}}
|
{{define "inboundModal"}}
|
||||||
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
||||||
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
|
||||||
{{template "form/inbound"}}
|
{{template "form/inbound"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -8,20 +8,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.ant-card-body {
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
margin-top: 10px;
|
margin: 0.5rem -2rem 0.5rem 2rem;
|
||||||
}
|
}
|
||||||
tr.hideExpandIcon .ant-table-row-expand-icon {
|
tr.hideExpandIcon .ant-table-row-expand-icon {
|
||||||
display: none;
|
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>
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
@@ -32,53 +61,63 @@
|
|||||||
</a-alert>
|
</a-alert>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable style="margin-bottom: 20px;" :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.inbounds.totalDownUp" }}:
|
{{ i18n "pages.inbounds.totalDownUp" }}:
|
||||||
<a-tag color="green">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag>
|
<a-tag color="blue">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.inbounds.totalUsage" }}:
|
{{ i18n "pages.inbounds.totalUsage" }}:
|
||||||
<a-tag color="green">[[ sizeFormat(total.up + total.down) ]]</a-tag>
|
<a-tag color="blue">[[ sizeFormat(total.up + total.down) ]]</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.inbounds.inboundCount" }}:
|
{{ i18n "pages.inbounds.inboundCount" }}:
|
||||||
<a-tag color="green">[[ dbInbounds.length ]]</a-tag>
|
<a-tag color="blue">[[ dbInbounds.length ]]</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "clients" }}:
|
{{ i18n "clients" }}:
|
||||||
<a-tag color="green">[[ total.clients ]]</a-tag>
|
<a-tag color="blue">[[ 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">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
||||||
</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.darkClass">
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
|
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
|
<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="green" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<div slot="title">
|
<div slot="title">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="12" :sm="12" :lg="12">
|
||||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
<a-button type="primary" icon="plus" @click="openAddInbound">
|
||||||
|
<template v-if="!isMobile">{{ i18n "pages.inbounds.addInbound" }}</template>
|
||||||
|
</a-button>
|
||||||
<a-dropdown :trigger="['click']">
|
<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 slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
|
||||||
<a-menu-item key="export">
|
<a-menu-item key="export">
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
@@ -99,11 +138,11 @@
|
|||||||
</a-menu>
|
</a-menu>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</a-col>
|
</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"
|
<a-select v-model="refreshInterval"
|
||||||
v-if="isRefreshEnabled"
|
v-if="isRefreshEnabled"
|
||||||
@change="changeRefreshInterval"
|
style="width: 70px;"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
@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-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
|
<a-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
|
||||||
@@ -111,29 +150,35 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
<a-switch v-model="enableFilter"
|
<div style="display: flex; align-items: center; justify-content: flex-start;">
|
||||||
checked-children='{{ i18n "search" }}' un-checked-children='{{ i18n "filter" }}'
|
<a-switch v-model="enableFilter"
|
||||||
@change="toggleFilter" style="margin-right: 10px;">
|
style="margin-right: .5rem;"
|
||||||
</a-switch>
|
@change="toggleFilter">
|
||||||
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
<a-icon slot="checkedChildren" type="search"></a-icon>
|
||||||
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid">
|
<a-icon slot="unCheckedChildren" type="filter"></a-icon>
|
||||||
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
</a-switch>
|
||||||
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px" :size="isMobile ? 'small' : ''"></a-input>
|
||||||
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid" :size="isMobile ? 'small' : ''">
|
||||||
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
||||||
</a-radio-group>
|
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
||||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
<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"
|
:data-source="searchedInbounds"
|
||||||
:loading="spinning" :scroll="{ x: 1200 }"
|
:scroll="isMobile ? {} : { x: 1000 }"
|
||||||
:pagination=pagination(searchedInbounds)
|
:pagination=pagination(searchedInbounds)
|
||||||
:expand-icon-as-cell="false"
|
:expand-icon-as-cell="false"
|
||||||
:expand-row-by-click="false"
|
:expand-row-by-click="false"
|
||||||
:expand-icon-column-index="0"
|
:expand-icon-column-index="0"
|
||||||
:row-class-name="dbInbound => (dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser) ? '' : 'hideExpandIcon')"
|
:indent-size="0"
|
||||||
style="margin-top: 20px">
|
:row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
|
||||||
|
style="margin-top: 10px">
|
||||||
<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="menu"></a-icon>
|
<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 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>
|
||||||
@@ -143,7 +188,7 @@
|
|||||||
<a-icon type="qrcode"></a-icon>
|
<a-icon type="qrcode"></a-icon>
|
||||||
{{ i18n "qrCode" }}
|
{{ i18n "qrCode" }}
|
||||||
</a-menu-item>
|
</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-menu-item key="addClient">
|
||||||
<a-icon type="user-add"></a-icon>
|
<a-icon type="user-add"></a-icon>
|
||||||
{{ i18n "pages.client.add"}}
|
{{ i18n "pages.client.add"}}
|
||||||
@@ -182,42 +227,52 @@
|
|||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
</span>
|
</span>
|
||||||
</a-menu-item>
|
</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-menu>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</template>
|
</template>
|
||||||
<template slot="protocol" slot-scope="text, dbInbound">
|
<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">
|
<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="blue">[[ 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.isTls" color="green">tls</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.isReality" color="green">reality</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="clients" slot-scope="text, dbInbound">
|
<template slot="clients" slot-scope="text, dbInbound">
|
||||||
<template v-if="clientCount[dbInbound.id]">
|
<template v-if="clientCount[dbInbound.id]">
|
||||||
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
<a-tag style="margin:0;" color="blue">[[ 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">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||||
</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.darkClass">
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||||
</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.darkClass">
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||||
</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">
|
||||||
|
<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>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, dbInbound">
|
<template slot="traffic" slot-scope="text, dbInbound">
|
||||||
<a-popover :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<table cellpadding="2" width="100%">
|
<table cellpadding="2" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -230,7 +285,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</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) ]] /
|
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||||
<template v-if="dbInbound.total > 0">
|
<template v-if="dbInbound.total > 0">
|
||||||
[[ sizeFormat(dbInbound.total) ]]
|
[[ sizeFormat(dbInbound.total) ]]
|
||||||
@@ -243,35 +298,117 @@
|
|||||||
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expiryTime" slot-scope="text, dbInbound">
|
<template slot="expiryTime" slot-scope="text, dbInbound">
|
||||||
<template v-if="dbInbound.expiryTime > 0">
|
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<a-tag v-if="dbInbound.isExpiry" color="red">
|
<template slot="content">
|
||||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
[[ 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>
|
</a-tag>
|
||||||
<a-tag v-else color="blue">
|
</a-popover>
|
||||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
<a-tag v-else color="purple" class="infinite-tag">∞</a-tag>
|
||||||
</a-tag>
|
</template>
|
||||||
</template>
|
<template slot="info" slot-scope="text, dbInbound">
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<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>∞</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">∞</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>
|
||||||
<template slot="expandedRowRender" slot-scope="record">
|
<template slot="expandedRowRender" slot-scope="record">
|
||||||
<a-table
|
<a-table
|
||||||
v-if="(record.protocol === Protocols.VLESS) || (record.protocol === Protocols.VMESS)"
|
|
||||||
:row-key="client => client.id"
|
:row-key="client => client.id"
|
||||||
:columns="innerColumns"
|
:columns="isMobile ? innerMobileColumns : innerColumns"
|
||||||
:data-source="getInboundClients(record)"
|
:data-source="getInboundClients(record)"
|
||||||
:pagination=pagination(getInboundClients(record))
|
:pagination=pagination(getInboundClients(record))
|
||||||
style="margin-left: 20px;"
|
:style="isMobile ? 'margin: -16px -5px -17px;' : 'margin-left: 10px;'">
|
||||||
>
|
|
||||||
{{template "client_table"}}
|
|
||||||
</a-table>
|
|
||||||
<a-table
|
|
||||||
v-else-if="record.protocol === Protocols.TROJAN || record.toInbound().isSSMultiUser"
|
|
||||||
:row-key="client => client.id"
|
|
||||||
:columns="innerTrojanColumns"
|
|
||||||
:data-source="getInboundClients(record)"
|
|
||||||
:pagination=pagination(getInboundClients(record))
|
|
||||||
style="margin-left: 20px;"
|
|
||||||
>
|
|
||||||
{{template "client_table"}}
|
{{template "client_table"}}
|
||||||
</a-table>
|
</a-table>
|
||||||
</template>
|
</template>
|
||||||
@@ -290,7 +427,6 @@
|
|||||||
align: 'right',
|
align: 'right',
|
||||||
dataIndex: "id",
|
dataIndex: "id",
|
||||||
width: 30,
|
width: 30,
|
||||||
responsive: ["xs"],
|
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.operate" }}',
|
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
@@ -319,7 +455,7 @@
|
|||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "clients" }}',
|
title: '{{ i18n "clients" }}',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 40,
|
width: 50,
|
||||||
scopedSlots: { customRender: 'clients' },
|
scopedSlots: { customRender: 'clients' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.traffic" }}',
|
title: '{{ i18n "pages.inbounds.traffic" }}',
|
||||||
@@ -329,26 +465,46 @@
|
|||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 80,
|
width: 40,
|
||||||
scopedSlots: { customRender: 'expiryTime' },
|
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 = [
|
const innerColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 50, scopedSlots: { customRender: 'actions' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
{ 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.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 60, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 55, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
|
||||||
{ title: 'UUID', width: 120, dataIndex: "id" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const innerTrojanColumns = [
|
const innerMobileColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 10, align: 'center', scopedSlots: { customRender: 'actionMenu' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 90, align: 'left', scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.info" }}', width: 10, align: 'center', scopedSlots: { customRender: 'info' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
|
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 55, scopedSlots: { customRender: 'expiryTime' } },
|
|
||||||
{ title: '{{ i18n "password" }}', width: 165, dataIndex: "password" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
@@ -369,6 +525,7 @@
|
|||||||
defaultCert: '',
|
defaultCert: '',
|
||||||
defaultKey: '',
|
defaultKey: '',
|
||||||
clientCount: [],
|
clientCount: [],
|
||||||
|
onlineClients: [],
|
||||||
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||||
@@ -382,6 +539,7 @@
|
|||||||
tgBotEnable: false,
|
tgBotEnable: false,
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
pageSize: 0,
|
pageSize: 0,
|
||||||
|
isMobile: window.innerWidth <= 768,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
@@ -394,11 +552,19 @@
|
|||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await this.getOnlineUsers();
|
||||||
this.setInbounds(msg.obj);
|
this.setInbounds(msg.obj);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
|
async getOnlineUsers() {
|
||||||
|
const msg = await HttpUtil.post('/xui/inbound/onlines');
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onlineClients = msg.obj != null ? msg.obj : [];
|
||||||
|
},
|
||||||
async getDefaultSettings() {
|
async getDefaultSettings() {
|
||||||
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
@@ -443,7 +609,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
getClientCounts(dbInbound, inbound) {
|
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);
|
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||||
clientStats = dbInbound.clientStats
|
clientStats = dbInbound.clientStats
|
||||||
now = new Date().getTime()
|
now = new Date().getTime()
|
||||||
@@ -452,6 +618,7 @@
|
|||||||
if (dbInbound.enable) {
|
if (dbInbound.enable) {
|
||||||
clients.forEach(client => {
|
clients.forEach(client => {
|
||||||
client.enable ? active.push(client.email) : deactive.push(client.email);
|
client.enable ? active.push(client.email) : deactive.push(client.email);
|
||||||
|
if(this.isClientOnline(client.email)) online.push(client.email);
|
||||||
});
|
});
|
||||||
clientStats.forEach(client => {
|
clientStats.forEach(client => {
|
||||||
if (!client.enable) {
|
if (!client.enable) {
|
||||||
@@ -473,6 +640,7 @@
|
|||||||
deactive: deactive,
|
deactive: deactive,
|
||||||
depleted: depleted,
|
depleted: depleted,
|
||||||
expiring: expiring,
|
expiring: expiring,
|
||||||
|
online: online,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
searchInbounds(key) {
|
searchInbounds(key) {
|
||||||
@@ -549,10 +717,10 @@
|
|||||||
clickAction(action, dbInbound) {
|
clickAction(action, dbInbound) {
|
||||||
switch (action.key) {
|
switch (action.key) {
|
||||||
case "qrcode":
|
case "qrcode":
|
||||||
this.showQrcode(dbInbound);
|
this.showQrcode(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
case "showInfo":
|
case "showInfo":
|
||||||
this.showInfo(dbInbound);
|
this.showInfo(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
case "edit":
|
case "edit":
|
||||||
this.openEditInbound(dbInbound.id);
|
this.openEditInbound(dbInbound.id);
|
||||||
@@ -618,6 +786,7 @@
|
|||||||
title: '{{ i18n "pages.inbounds.cloneInbound"}} \"' + dbInbound.remark + '\"',
|
title: '{{ i18n "pages.inbounds.cloneInbound"}} \"' + dbInbound.remark + '\"',
|
||||||
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
||||||
okText: '{{ i18n "pages.inbounds.update"}}',
|
okText: '{{ i18n "pages.inbounds.update"}}',
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel" }}',
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
const baseInbound = dbInbound.toInbound();
|
const baseInbound = dbInbound.toInbound();
|
||||||
@@ -754,7 +923,7 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
@@ -769,23 +938,27 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/del/' + dbInboundId),
|
onOk: () => this.submit('/xui/inbound/del/' + dbInboundId),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
delClient(dbInboundId, client) {
|
delClient(dbInboundId, client,confirmation = true) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
clientId = this.getClientId(dbInbound.protocol, client);
|
clientId = this.getClientId(dbInbound.protocol, client);
|
||||||
this.$confirm({
|
if (confirmation){
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
this.$confirm({
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
title: '{{ i18n "pages.inbounds.deleteClient"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
||||||
okText: '{{ i18n "delete"}}',
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
onOk: () => this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`),
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
});
|
onOk: () => this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
getClients(protocol, clientSettings) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
@@ -832,9 +1005,12 @@
|
|||||||
},
|
},
|
||||||
showInfo(dbInboundId, client) {
|
showInfo(dbInboundId, client) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
inbound = dbInbound.toInbound();
|
index=0;
|
||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
if (dbInbound.isMultiUser()){
|
||||||
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
inbound = dbInbound.toInbound();
|
||||||
|
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||||
|
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||||
|
}
|
||||||
newDbInbound = this.checkFallback(dbInbound);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
infoModal.show(newDbInbound, index);
|
infoModal.show(newDbInbound, index);
|
||||||
},
|
},
|
||||||
@@ -870,21 +1046,25 @@
|
|||||||
return dbInbound.toInbound().settings.shadowsockses;
|
return dbInbound.toInbound().settings.shadowsockses;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetClientTraffic(client, dbInboundId) {
|
resetClientTraffic(client, dbInboundId, confirmation = true) {
|
||||||
this.$confirm({
|
if (confirmation){
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
this.$confirm({
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
okText: '{{ i18n "reset"}}',
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
})
|
onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
resetAllTraffic() {
|
resetAllTraffic() {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
|
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/resetAllTraffics'),
|
onOk: () => this.submit('/xui/inbound/resetAllTraffics'),
|
||||||
@@ -894,7 +1074,7 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
title: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
||||||
content: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
content: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
|
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
|
||||||
@@ -904,36 +1084,98 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
||||||
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId),
|
onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
isExpiry(dbInbound, index) {
|
isExpiry(dbInbound, index) {
|
||||||
return dbInbound.toInbound().isExpiry(index)
|
return dbInbound.toInbound().isExpiry(index);
|
||||||
},
|
},
|
||||||
getUpStats(dbInbound, email) {
|
getUpStats(dbInbound, email) {
|
||||||
if (email.length == 0) return 0
|
if (email.length == 0) return 0;
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
return clientStats ? clientStats.up : 0
|
return clientStats ? clientStats.up : 0;
|
||||||
},
|
},
|
||||||
getDownStats(dbInbound, email) {
|
getDownStats(dbInbound, email) {
|
||||||
if (email.length == 0) return 0
|
if (email.length == 0) return 0;
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
return clientStats ? clientStats.down : 0
|
return clientStats ? clientStats.down : 0;
|
||||||
|
},
|
||||||
|
getSumStats(dbInbound, email) {
|
||||||
|
if (email.length == 0) return 0;
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
return clientStats ? clientStats.up + clientStats.down : 0;
|
||||||
|
},
|
||||||
|
getRemStats(dbInbound, email) {
|
||||||
|
if (email.length == 0) return 0;
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
if (!clientStats) return 0;
|
||||||
|
remained = clientStats.totalGB - (clientStats.up + clientStats.down);
|
||||||
|
return remained>0 ? remained : 0;
|
||||||
},
|
},
|
||||||
statsColor(dbInbound, email) {
|
statsColor(dbInbound, email) {
|
||||||
if (email.length == 0) return 'blue';
|
if (email.length == 0) return '#0e49b5';
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
return usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
|
switch (true) {
|
||||||
|
case !clientStats:
|
||||||
|
return "#0e49b5";
|
||||||
|
case clientStats.up + clientStats.down < clientStats.total - app.trafficDiff:
|
||||||
|
return "#0e49b5";
|
||||||
|
case clientStats.up + clientStats.down < clientStats.total:
|
||||||
|
return "#FFA031";
|
||||||
|
default:
|
||||||
|
return "#E04141";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
statsProgress(dbInbound, email) {
|
||||||
|
if (email.length == 0) return 100;
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
if (!clientStats) return 0;
|
||||||
|
if (clientStats.total == 0) return 100;
|
||||||
|
return 100*(clientStats.down + clientStats.up)/clientStats.total;
|
||||||
|
},
|
||||||
|
expireProgress(expTime, reset) {
|
||||||
|
now = new Date().getTime();
|
||||||
|
remainedSeconds = expTime < 0 ? -expTime/1000 : (expTime-now)/1000;
|
||||||
|
resetSeconds = reset * 86400;
|
||||||
|
if (remainedSeconds >= resetSeconds) return 0;
|
||||||
|
return 100*(1-(remainedSeconds/resetSeconds));
|
||||||
|
},
|
||||||
|
remainedDays(expTime){
|
||||||
|
if (expTime == 0) return null;
|
||||||
|
if (expTime < 0) return formatSecond(expTime/-1000);
|
||||||
|
now = new Date().getTime();
|
||||||
|
if (expTime < now) return '{{ i18n "depleted" }}';
|
||||||
|
return formatSecond((expTime-now)/1000);
|
||||||
|
},
|
||||||
|
statsExpColor(dbInbound, email){
|
||||||
|
if (email.length == 0) return '#7a316f';
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
if (!clientStats) return '#7a316f';
|
||||||
|
statsColor = usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
|
||||||
|
expColor = usageColor(new Date().getTime(), this.expireDiff, clientStats.expiryTime);
|
||||||
|
switch (true) {
|
||||||
|
case statsColor == "red" || expColor == "red":
|
||||||
|
return "#E04141";
|
||||||
|
case statsColor == "orange" || expColor == "orange":
|
||||||
|
return "#FFA031";
|
||||||
|
case statsColor == "blue" || expColor == "blue":
|
||||||
|
return "#0e49b5";
|
||||||
|
default:
|
||||||
|
return "#7a316f";
|
||||||
|
}
|
||||||
},
|
},
|
||||||
isClientEnabled(dbInbound, email) {
|
isClientEnabled(dbInbound, email) {
|
||||||
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;
|
||||||
|
},
|
||||||
|
isClientOnline(email) {
|
||||||
|
return this.onlineClients.includes(email);
|
||||||
},
|
},
|
||||||
isRemovable(dbInboundId) {
|
isRemovable(dbInboundId) {
|
||||||
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInboundId)).length > 1
|
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInboundId)).length > 1;
|
||||||
},
|
},
|
||||||
inboundLinks(dbInboundId) {
|
inboundLinks(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
@@ -943,7 +1185,7 @@
|
|||||||
exportAllLinks() {
|
exportAllLinks() {
|
||||||
let copyText = '';
|
let copyText = '';
|
||||||
for (const dbInbound of this.dbInbounds) {
|
for (const dbInbound of this.dbInbounds) {
|
||||||
copyText += dbInbound.genInboundLinks
|
copyText += dbInbound.genInboundLinks;
|
||||||
}
|
}
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText, 'All-Inbounds');
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText, 'All-Inbounds');
|
||||||
},
|
},
|
||||||
@@ -976,7 +1218,7 @@
|
|||||||
pagination(obj){
|
pagination(obj){
|
||||||
if (this.pageSize > 0 && obj.length>this.pageSize) {
|
if (this.pageSize > 0 && obj.length>this.pageSize) {
|
||||||
// Set page options based on object size
|
// Set page options based on object size
|
||||||
sizeOptions = []
|
sizeOptions = [];
|
||||||
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
|
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
|
||||||
sizeOptions.push(i.toString());
|
sizeOptions.push(i.toString());
|
||||||
}
|
}
|
||||||
@@ -989,10 +1231,13 @@
|
|||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
pageSize: this.pageSize,
|
pageSize: this.pageSize,
|
||||||
pageSizeOptions: sizeOptions
|
pageSizeOptions: sizeOptions
|
||||||
}
|
};
|
||||||
return p
|
return p;
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
},
|
||||||
|
onResize() {
|
||||||
|
this.isMobile = window.innerWidth <= 768;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -1004,6 +1249,8 @@
|
|||||||
if (window.location.protocol !== "https:") {
|
if (window.location.protocol !== "https:") {
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
}
|
}
|
||||||
|
window.addEventListener('resize', this.onResize);
|
||||||
|
this.onResize();
|
||||||
this.loading();
|
this.loading();
|
||||||
this.getDefaultSettings();
|
this.getDefaultSettings();
|
||||||
if (this.isRefreshEnabled) {
|
if (this.isRefreshEnabled) {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
.ant-layout-content {
|
.ant-layout-content {
|
||||||
margin: 24px 16px;
|
margin: 24px 16px;
|
||||||
}
|
}
|
||||||
|
.ant-card-hoverable {
|
||||||
|
margin-inline: 0.3rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
@@ -17,9 +20,9 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
@@ -33,21 +36,19 @@
|
|||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.cpu.color"
|
:stroke-color="status.cpu.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.cpu.percent"></a-progress>
|
:percent="status.cpu.percent"></a-progress>
|
||||||
<div>CPU: ([[ status.cpuCount ]]core)</div>
|
<div>CPU: ([[ status.cpuCount ]]core)</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.mem.color"
|
:stroke-color="status.mem.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.mem.percent"></a-progress>
|
:percent="status.mem.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
||||||
@@ -60,7 +61,6 @@
|
|||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.swap.color"
|
:stroke-color="status.swap.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.swap.percent"></a-progress>
|
:percent="status.swap.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
||||||
@@ -69,7 +69,6 @@
|
|||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.disk.color"
|
:stroke-color="status.disk.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.disk.percent"></a-progress>
|
:percent="status.disk.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
||||||
@@ -84,16 +83,16 @@
|
|||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
X-UI: <a href="https://github.com/alireza0/x-ui/releases" target="_blank"><a-tag color="green">{{ .cur_ver }}</a-tag></a>
|
X-UI: <a href="https://github.com/alireza0/x-ui/releases" target="_blank"><a-tag color="blue">{{ .cur_ver }}</a-tag></a>
|
||||||
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
|
Xray: <a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.operationHours" }}:
|
{{ i18n "pages.index.operationHours" }}:
|
||||||
Xray
|
Xray
|
||||||
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
<a-tag color="blue">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||||
OS
|
OS
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
@@ -101,11 +100,11 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
<a-tag color="blue">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.xrayStatus" }}:
|
{{ i18n "pages.index.xrayStatus" }}:
|
||||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||||
<a-tooltip v-if="status.xray.state === State.Error">
|
<a-tooltip v-if="status.xray.state === State.Error">
|
||||||
@@ -114,26 +113,26 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "menu.link" }}:
|
{{ i18n "menu.link" }}:
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "usage"}}:
|
{{ i18n "usage"}}:
|
||||||
Memory: [[ sizeFormat(status.appStats.mem) ]] -
|
Memory: [[ sizeFormat(status.appStats.mem) ]] -
|
||||||
Threads: [[ status.appStats.threads ]]
|
Threads: [[ status.appStats.threads ]]
|
||||||
@@ -141,7 +140,7 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
Host: [[ status.hostInfo.hostname ]] -
|
Host: [[ status.hostInfo.hostname ]] -
|
||||||
<template v-if="status.hostInfo.ipv4">IPv4:
|
<template v-if="status.hostInfo.ipv4">IPv4:
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -162,7 +161,7 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.connectionCount" }}: TCP: [[ status.tcpCount ]] UDP: [[ status.udpCount ]]
|
{{ i18n "pages.index.connectionCount" }}: TCP: [[ status.tcpCount ]] UDP: [[ status.udpCount ]]
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
@@ -173,7 +172,7 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="arrow-up"></a-icon>
|
<a-icon type="arrow-up"></a-icon>
|
||||||
@@ -199,7 +198,7 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="cloud-upload"></a-icon>
|
<a-icon type="cloud-upload"></a-icon>
|
||||||
@@ -231,12 +230,12 @@
|
|||||||
|
|
||||||
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
|
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
|
||||||
:closable="true" @ok="() => versionModal.visible = false"
|
:closable="true" @ok="() => versionModal.visible = false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
footer="">
|
footer="">
|
||||||
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
|
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
|
||||||
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
|
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
|
||||||
<template v-for="version, index in versionModal.versions">
|
<template v-for="version, index in versionModal.versions">
|
||||||
<a-tag :color="index % 2 == 0 ? 'blue' : 'green'"
|
<a-tag :color="index % 2 == 0 ? 'purple' : 'blue'"
|
||||||
style="margin: 10px" @click="switchV2rayVersion(version)">
|
style="margin: 10px" @click="switchV2rayVersion(version)">
|
||||||
[[ version ]]
|
[[ version ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
@@ -245,15 +244,14 @@
|
|||||||
|
|
||||||
<a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs"
|
<a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs"
|
||||||
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
width="800px"
|
width="800px"
|
||||||
footer="">
|
footer="">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Count">
|
<a-form-item label="Count">
|
||||||
<a-select v-model="logModal.rows"
|
<a-select v-model="logModal.rows"
|
||||||
style="width: 80px"
|
style="width: 80px"
|
||||||
@change="openLogs()"
|
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="10">10</a-select-option>
|
<a-select-option value="10">10</a-select-option>
|
||||||
<a-select-option value="20">20</a-select-option>
|
<a-select-option value="20">20</a-select-option>
|
||||||
<a-select-option value="50">50</a-select-option>
|
<a-select-option value="50">50</a-select-option>
|
||||||
@@ -263,8 +261,7 @@
|
|||||||
<a-form-item label="Log Level">
|
<a-form-item label="Log Level">
|
||||||
<a-select v-model="logModal.level"
|
<a-select v-model="logModal.level"
|
||||||
style="width: 120px"
|
style="width: 120px"
|
||||||
@change="openLogs()"
|
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="debug">Debug</a-select-option>
|
<a-select-option value="debug">Debug</a-select-option>
|
||||||
<a-select-option value="info">Info</a-select-option>
|
<a-select-option value="info">Info</a-select-option>
|
||||||
<a-select-option value="warning">Warning</a-select-option>
|
<a-select-option value="warning">Warning</a-select-option>
|
||||||
@@ -289,12 +286,13 @@
|
|||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
||||||
:closable="true" :class="themeSwitcher.darkCardClass"
|
:closable="true"
|
||||||
|
:class="themeSwitcher.currentTheme"
|
||||||
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
||||||
<p style="color: inherit; font-size: 16px; padding: 4px 2px;">
|
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 20px;"></a-icon>
|
:message="backupModal.description"
|
||||||
[[ backupModal.description ]]
|
show-icon
|
||||||
</p>
|
></a-alert>
|
||||||
<a-space direction="horizontal" style="text-align: center" style="margin-bottom: 10px;">
|
<a-space direction="horizontal" style="text-align: center" style="margin-bottom: 10px;">
|
||||||
<a-button type="primary" @click="exportDatabase()">
|
<a-button type="primary" @click="exportDatabase()">
|
||||||
[[ backupModal.exportText ]]
|
[[ backupModal.exportText ]]
|
||||||
@@ -335,11 +333,11 @@
|
|||||||
get color() {
|
get color() {
|
||||||
const percent = this.percent;
|
const percent = this.percent;
|
||||||
if (percent < 80) {
|
if (percent < 80) {
|
||||||
return '#67C23A';
|
return '#0e49b5';
|
||||||
} else if (percent < 90) {
|
} else if (percent < 90) {
|
||||||
return '#E6A23C';
|
return '#ffa031';
|
||||||
} else {
|
} else {
|
||||||
return '#F56C6C';
|
return '#e04141';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,7 +380,7 @@
|
|||||||
this.xray = data.xray;
|
this.xray = data.xray;
|
||||||
switch (this.xray.state) {
|
switch (this.xray.state) {
|
||||||
case State.Running:
|
case State.Running:
|
||||||
this.xray.color = "green";
|
this.xray.color = "blue";
|
||||||
break;
|
break;
|
||||||
case State.Stop:
|
case State.Stop:
|
||||||
this.xray.color = "orange";
|
this.xray.color = "orange";
|
||||||
@@ -487,8 +485,8 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
||||||
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "confirm"}}',
|
okText: '{{ i18n "confirm"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
versionModal.hide();
|
versionModal.hide();
|
||||||
|
|||||||
@@ -8,8 +8,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
@media (max-width: 768px) {
|
||||||
margin-top: 10px;
|
.ant-tabs-nav .ant-tabs-tab {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px .5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-tabs-bar {
|
.ant-tabs-bar {
|
||||||
@@ -20,19 +23,6 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-msg {
|
|
||||||
color: inherit;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 18px;
|
|
||||||
padding: 20px 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-msg > i {
|
|
||||||
color: inherit;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapse-title {
|
.collapse-title {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -47,11 +37,11 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
@@ -62,19 +52,25 @@
|
|||||||
</a-alert>
|
</a-alert>
|
||||||
</transition>
|
</transition>
|
||||||
<a-space direction="vertical">
|
<a-space direction="vertical">
|
||||||
<a-space direction="horizontal">
|
<a-card hoverable style="margin-bottom: .5rem;">
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
<a-row>
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
<a-col :xs="24" :sm="8" style="padding: 4px;">
|
||||||
</a-space>
|
<a-space direction="horizontal">
|
||||||
<a-tabs default-active-key="1" :class="themeSwitcher.darkCardClass">
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
||||||
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="16">
|
||||||
|
<a-alert type="warning" style="float: right; width: fit-content"
|
||||||
|
message='{{ i18n "pages.settings.infoDesc" }}'
|
||||||
|
show-icon
|
||||||
|
>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
<a-tabs default-active-key="1">
|
||||||
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelConfig"}}'>
|
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelConfig"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-list item-layout="horizontal">
|
||||||
<h2 class="alert-msg">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model.number="allSetting.webPort"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model.number="allSetting.webPort"></setting-list-item>
|
||||||
@@ -98,9 +94,7 @@
|
|||||||
ref="selectLang"
|
ref="selectLang"
|
||||||
v-model="lang"
|
v-model="lang"
|
||||||
@change="setLang(lang)"
|
@change="setLang(lang)"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||||
<span v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
@@ -113,7 +107,7 @@
|
|||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" tab='{{ i18n "pages.settings.userSettings"}}'>
|
<a-tab-pane key="2" tab='{{ i18n "pages.settings.userSettings"}}'>
|
||||||
<a-form :style="'padding: 20px;' + themeSwitcher.textStyle">
|
<a-form style="padding: 20px;">
|
||||||
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
|
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
|
||||||
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
|
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -131,172 +125,8 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
||||||
<a-tab-pane key="3" tab='{{ i18n "pages.settings.xrayConfiguration"}}'>
|
<a-list item-layout="horizontal">
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="alert-msg">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
|
||||||
<a-tabs class="ant-card-dark-box-nohover" default-active-key="tpl-1" :class="themeSwitcher.darkCardClass" style="padding: 20px 20px;">
|
|
||||||
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.settings.templates.basicTemplate"}}' style="padding-top: 20px;">
|
|
||||||
<a-collapse>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.generalConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.generalConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-list-item>
|
|
||||||
<a-row style="padding: 20px">
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta
|
|
||||||
title='{{ i18n "pages.settings.templates.xrayConfigFreedomStrategy" }}'
|
|
||||||
description='{{ i18n "pages.settings.templates.xrayConfigFreedomStrategyDesc" }}'/>
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<template>
|
|
||||||
<a-select
|
|
||||||
v-model="freedomStrategy"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
style="width: 100%">
|
|
||||||
<a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-list-item>
|
|
||||||
<a-row style="padding: 20px">
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta
|
|
||||||
title='{{ i18n "pages.settings.templates.xrayConfigRoutingStrategy" }}'
|
|
||||||
description='{{ i18n "pages.settings.templates.xrayConfigRoutingStrategyDesc" }}'/>
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<template>
|
|
||||||
<a-select
|
|
||||||
v-model="routingStrategy"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
style="width: 100%">
|
|
||||||
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.blockConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigTorrent"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigAds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigAdsDesc"}}' v-model="AdsSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigFamily"}}' desc='{{ i18n "pages.settings.templates.xrayConfigFamilyDesc"}}' v-model="familyProtectSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockCountryConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.blockCountryConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigIRIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigIRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigIRDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigIRDomainDesc"}}' v-model="IRDomainSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigChinaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigChinaIpDesc"}}' v-model="ChinaIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigChinaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigRussiaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigRussiaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.directCountryConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.directCountryConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectIRIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectIRIpDesc"}}' v-model="IRIpDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectIRDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectIRDomainDesc"}}' v-model="IRDomainDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectChinaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectChinaIpDesc"}}' v-model="ChinaIpDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectChinaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectChinaDomainDesc"}}' v-model="ChinaDomainDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaIpDesc"}}' v-model="RussiaIpDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaDomainDesc"}}' v-model="RussiaDomainDirectSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.ipv4Configs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.ipv4ConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigGoogleIPv4"}}' desc='{{ i18n "pages.settings.templates.xrayConfigGoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigNetflixIPv4"}}' desc='{{ i18n "pages.settings.templates.xrayConfigNetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
|
||||||
<a-space direction="horizontal" style="padding: 0 20px">
|
|
||||||
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
|
||||||
</a-space>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.settings.templates.manualLists"}}' style="padding-top: 20px;">
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.manualListsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-collapse>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualBlockedIPs"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualBlockedIPs"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualBlockedDomains"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualBlockedDomains"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualDirectIPs"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualDirectIPs"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualDirectDomains"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualDirectDomains"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualIPv4Domains"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualIPv4Domains"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.settings.templates.advancedTemplate"}}' style="padding-top: 20px;">
|
|
||||||
<a-collapse>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}'>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigInboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigOutbounds"}}'>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigOutbounds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigOutboundsDesc"}}' v-model="outboundSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigRoutings"}}'>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigRoutings"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRoutingsDesc"}}' v-model="routingRuleSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.settings.templates.completeTemplate"}}' style="padding-top: 20px;">
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigTemplate"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
|
||||||
</a-tab-pane>
|
|
||||||
</a-tabs>
|
|
||||||
</a-list>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="4" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="alert-msg">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramToken"}}' desc='{{ i18n "pages.settings.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramToken"}}' desc='{{ i18n "pages.settings.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramChatId"}}' desc='{{ i18n "pages.settings.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramChatId"}}' desc='{{ i18n "pages.settings.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item>
|
||||||
@@ -315,8 +145,8 @@
|
|||||||
<a-select
|
<a-select
|
||||||
ref="selectBotLang"
|
ref="selectBotLang"
|
||||||
v-model="allSetting.tgLang"
|
v-model="allSetting.tgLang"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
>
|
>
|
||||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||||
@@ -329,14 +159,8 @@
|
|||||||
</a-list-item>
|
</a-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }}'>
|
<a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-list item-layout="horizontal">
|
||||||
<h2 class="alert-msg">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEncrypt"}}' desc='{{ i18n "pages.settings.subEncryptDesc"}}' v-model="allSetting.subEncrypt"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEncrypt"}}' desc='{{ i18n "pages.settings.subEncryptDesc"}}' v-model="allSetting.subEncrypt"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
|
||||||
@@ -373,62 +197,7 @@
|
|||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
user: {},
|
user: {},
|
||||||
lang: getLang(),
|
lang: getLang(),
|
||||||
showAlert: false,
|
showAlert: false
|
||||||
ipv4Settings: {
|
|
||||||
tag: "IPv4",
|
|
||||||
protocol: "freedom",
|
|
||||||
settings: {
|
|
||||||
domainStrategy: "UseIPv4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
directSettings: {
|
|
||||||
tag: "direct",
|
|
||||||
protocol: "freedom"
|
|
||||||
},
|
|
||||||
outboundDomainStrategies: ["AsIs", "UseIP", "UseIPv4", "UseIPv6"],
|
|
||||||
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
|
||||||
settingsData: {
|
|
||||||
protocols: {
|
|
||||||
bittorrent: ["bittorrent"],
|
|
||||||
},
|
|
||||||
ips: {
|
|
||||||
local: ["geoip:private"],
|
|
||||||
cn: ["geoip:cn"],
|
|
||||||
ir: ["geoip:ir"],
|
|
||||||
ru: ["geoip:ru"],
|
|
||||||
},
|
|
||||||
domains: {
|
|
||||||
ads: [
|
|
||||||
"geosite:category-ads-all",
|
|
||||||
"ext:iran.dat:ads"
|
|
||||||
],
|
|
||||||
google: ["geosite:google"],
|
|
||||||
netflix: ["geosite:netflix"],
|
|
||||||
cn: [
|
|
||||||
"geosite:cn",
|
|
||||||
"regexp:.*\\.cn$"
|
|
||||||
],
|
|
||||||
ru: [
|
|
||||||
"geosite:category-gov-ru",
|
|
||||||
"regexp:.*\\.ru$"
|
|
||||||
],
|
|
||||||
ir: [
|
|
||||||
"regexp:.*\\.ir$",
|
|
||||||
"ext:iran.dat:ir",
|
|
||||||
"ext:iran.dat:other",
|
|
||||||
"geosite:category-ir"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
familyProtectDNS: {
|
|
||||||
"servers": [
|
|
||||||
"1.1.1.3",
|
|
||||||
"1.0.0.3",
|
|
||||||
"94.140.14.15",
|
|
||||||
"94.140.15.16"
|
|
||||||
],
|
|
||||||
"queryStrategy": "UseIPv4"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
@@ -466,6 +235,7 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.settings.restartPanel" }}',
|
title: '{{ i18n "pages.settings.restartPanel" }}',
|
||||||
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "sure" }}',
|
okText: '{{ i18n "sure" }}',
|
||||||
cancelText: '{{ i18n "cancel" }}',
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
onOk: () => resolve(),
|
onOk: () => resolve(),
|
||||||
@@ -477,88 +247,13 @@
|
|||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
await PromiseUtil.sleep(5000);
|
await PromiseUtil.sleep(5000);
|
||||||
const { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
|
var { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
|
||||||
|
if (host == this.oldAllSetting.webDomain) host = null;
|
||||||
|
if (port == this.oldAllSetting.webPort) port = null;
|
||||||
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
||||||
const url = buildURL({ host, port, isTLS, base, path: "xui/settings" });
|
const url = buildURL({ host, port, isTLS, base, path: "xui/settings" });
|
||||||
window.location.replace(url);
|
window.location.replace(url);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
async resetXrayConfigToDefault() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.get("/xui/setting/getDefaultJsonConfig");
|
|
||||||
this.loading(false);
|
|
||||||
if (msg.success) {
|
|
||||||
this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
|
|
||||||
this.saveBtnDisable = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
syncRulesWithOutbound(tag, setting) {
|
|
||||||
const newTemplateSettings = this.templateSettings;
|
|
||||||
const haveRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === tag);
|
|
||||||
const outboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.tag === tag);
|
|
||||||
if (!haveRules && outboundIndex > 0) {
|
|
||||||
newTemplateSettings.outbounds.splice(outboundIndex);
|
|
||||||
}
|
|
||||||
if (haveRules && outboundIndex < 0) {
|
|
||||||
newTemplateSettings.outbounds.push(setting);
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
templateRuleGetter(routeSettings) {
|
|
||||||
const { property, outboundTag } = routeSettings;
|
|
||||||
let result = [];
|
|
||||||
if (this.templateSettings != null) {
|
|
||||||
this.templateSettings.routing.rules.forEach(
|
|
||||||
(routingRule) => {
|
|
||||||
if (
|
|
||||||
routingRule.hasOwnProperty(property) &&
|
|
||||||
routingRule.hasOwnProperty("outboundTag") &&
|
|
||||||
routingRule.outboundTag === outboundTag
|
|
||||||
) {
|
|
||||||
result.push(...routingRule[property]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
templateRuleSetter(routeSettings) {
|
|
||||||
const { data, property, outboundTag } = routeSettings;
|
|
||||||
const oldTemplateSettings = this.templateSettings;
|
|
||||||
const newTemplateSettings = oldTemplateSettings;
|
|
||||||
currentProperty = this.templateRuleGetter({ outboundTag, property })
|
|
||||||
if (currentProperty.length == 0) {
|
|
||||||
const propertyRule = {
|
|
||||||
type: "field",
|
|
||||||
outboundTag,
|
|
||||||
[property]: data
|
|
||||||
};
|
|
||||||
newTemplateSettings.routing.rules.push(propertyRule);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const newRules = [];
|
|
||||||
insertedOnce = false;
|
|
||||||
newTemplateSettings.routing.rules.forEach(
|
|
||||||
(routingRule) => {
|
|
||||||
if (
|
|
||||||
routingRule.hasOwnProperty(property) &&
|
|
||||||
routingRule.hasOwnProperty("outboundTag") &&
|
|
||||||
routingRule.outboundTag === outboundTag
|
|
||||||
) {
|
|
||||||
if (!insertedOnce && data.length > 0) {
|
|
||||||
insertedOnce = true;
|
|
||||||
routingRule[property] = data;
|
|
||||||
newRules.push(routingRule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
newRules.push(routingRule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
newTemplateSettings.routing.rules = newRules;
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@@ -570,357 +265,7 @@
|
|||||||
await PromiseUtil.sleep(1000);
|
await PromiseUtil.sleep(1000);
|
||||||
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
computed: {
|
|
||||||
templateSettings: {
|
|
||||||
get: function () { return this.allSetting.xrayTemplateConfig ? JSON.parse(this.allSetting.xrayTemplateConfig) : null; },
|
|
||||||
set: function (newValue) { this.allSetting.xrayTemplateConfig = JSON.stringify(newValue, null, 2); },
|
|
||||||
},
|
|
||||||
inboundSettings: {
|
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.inbounds = JSON.parse(newValue);
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
outboundSettings: {
|
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.outbounds = JSON.parse(newValue);
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
routingRuleSettings: {
|
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.routing.rules = JSON.parse(newValue);
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
freedomStrategy: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings) return "AsIs";
|
|
||||||
freedomOutbound = this.templateSettings.outbounds.find((o) => o.protocol === "freedom" && !o.tag);
|
|
||||||
if (!freedomOutbound) return "AsIs";
|
|
||||||
if (!freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs";
|
|
||||||
return freedomOutbound.settings.domainStrategy;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
freedomOutboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.protocol === "freedom" && !o.tag);
|
|
||||||
if (!newTemplateSettings.outbounds[freedomOutboundIndex].settings) {
|
|
||||||
newTemplateSettings.outbounds[freedomOutboundIndex].settings = { "domainStrategy": newValue };
|
|
||||||
} else {
|
|
||||||
newTemplateSettings.outbounds[freedomOutboundIndex].settings.domainStrategy = newValue;
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
routingStrategy: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings || !this.templateSettings.routing || !this.templateSettings.routing.domainStrategy) return "AsIs";
|
|
||||||
return this.templateSettings.routing.domainStrategy;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.routing.domainStrategy = newValue;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
blockedIPs: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "blocked", property: "ip", data: newValue });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
blockedDomains: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "domain" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "blocked", property: "domain", data: newValue });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
blockedProtocols: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "protocol" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "blocked", property: "protocol", data: newValue });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
directIPs: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "direct", property: "ip" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "direct", property: "ip", data: newValue });
|
|
||||||
this.syncRulesWithOutbound("direct", this.directSettings);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
directDomains: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "direct", property: "domain" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "direct", property: "domain", data: newValue });
|
|
||||||
this.syncRulesWithOutbound("direct", this.directSettings);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ipv4Domains: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "IPv4", property: "domain", data: newValue });
|
|
||||||
this.syncRulesWithOutbound("IPv4", this.ipv4Settings);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
manualBlockedIPs: {
|
|
||||||
get: function () { return JSON.stringify(this.blockedIPs, null, 2); },
|
|
||||||
set: debounce(function (value) { this.blockedIPs = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
manualBlockedDomains: {
|
|
||||||
get: function () { return JSON.stringify(this.blockedDomains, null, 2); },
|
|
||||||
set: debounce(function (value) { this.blockedDomains = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
manualDirectIPs: {
|
|
||||||
get: function () { return JSON.stringify(this.directIPs, null, 2); },
|
|
||||||
set: debounce(function (value) { this.directIPs = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
manualDirectDomains: {
|
|
||||||
get: function () { return JSON.stringify(this.directDomains, null, 2); },
|
|
||||||
set: debounce(function (value) { this.directDomains = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
manualIPv4Domains: {
|
|
||||||
get: function () { return JSON.stringify(this.ipv4Domains, null, 2); },
|
|
||||||
set: debounce(function (value) { this.ipv4Domains = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
torrentSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedProtocols = [...this.blockedProtocols, ...this.settingsData.protocols.bittorrent];
|
|
||||||
} else {
|
|
||||||
this.blockedProtocols = this.blockedProtocols.filter(data => !this.settingsData.protocols.bittorrent.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
privateIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.local, this.blockedIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.local];
|
|
||||||
} else {
|
|
||||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.local.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
AdsSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ads, this.blockedDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ads];
|
|
||||||
} else {
|
|
||||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ads.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
familyProtectSettings: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
|
||||||
return doAllItemsExist(this.templateSettings.dns.servers, this.settingsData.familyProtectDNS.servers);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
if (newValue) {
|
|
||||||
newTemplateSettings.dns = this.settingsData.familyProtectDNS;
|
|
||||||
} else {
|
|
||||||
delete newTemplateSettings.dns;
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
GoogleIPv4Settings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.google, this.ipv4Domains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.google];
|
|
||||||
} else {
|
|
||||||
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.google.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
NetflixIPv4Settings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.netflix, this.ipv4Domains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.netflix];
|
|
||||||
} else {
|
|
||||||
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.netflix.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
IRIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.ir, this.blockedIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ir];
|
|
||||||
} else {
|
|
||||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ir.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
IRDomainSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ir, this.blockedDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ir];
|
|
||||||
} else {
|
|
||||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ir.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ChinaIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.cn, this.blockedIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.cn];
|
|
||||||
} else {
|
|
||||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.cn.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ChinaDomainSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.cn, this.blockedDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.cn];
|
|
||||||
} else {
|
|
||||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.cn.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RussiaIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.ru, this.blockedIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ru];
|
|
||||||
} else {
|
|
||||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ru.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RussiaDomainSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ru, this.blockedDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ru];
|
|
||||||
} else {
|
|
||||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ru.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
IRIpDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.ir, this.directIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ir];
|
|
||||||
} else {
|
|
||||||
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ir.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
IRDomainDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ir, this.directDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ir];
|
|
||||||
} else {
|
|
||||||
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ir.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ChinaIpDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.cn, this.directIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directIPs = [...this.directIPs, ...this.settingsData.ips.cn];
|
|
||||||
} else {
|
|
||||||
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.cn.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ChinaDomainDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.cn, this.directDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directDomains = [...this.directDomains, ...this.settingsData.domains.cn];
|
|
||||||
} else {
|
|
||||||
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.cn.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RussiaIpDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.ru, this.directIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ru];
|
|
||||||
} else {
|
|
||||||
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ru.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RussiaDomainDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ru, this.directDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ru];
|
|
||||||
} else {
|
|
||||||
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ru.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
783
web/html/xui/xray.html
Normal file
783
web/html/xui/xray.html
Normal file
@@ -0,0 +1,783 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{template "head" .}}
|
||||||
|
<style>
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
.ant-layout-content {
|
||||||
|
margin: 24px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.ant-tabs-nav .ant-tabs-tab {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-bar {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-list-item {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-title {
|
||||||
|
color: inherit;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-title > i {
|
||||||
|
color: inherit;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
</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>
|
||||||
|
<a-space direction="vertical">
|
||||||
|
<a-card hoverable style="margin-bottom: .5rem;">
|
||||||
|
<a-row>
|
||||||
|
<a-col :xs="24" :sm="8" style="padding: 4px;">
|
||||||
|
<a-space direction="horizontal">
|
||||||
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">{{ i18n "pages.settings.save" }}</a-button>
|
||||||
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="16">
|
||||||
|
<a-alert type="warning" style="float: right; width: fit-content"
|
||||||
|
message='{{ i18n "pages.settings.infoDesc" }}'
|
||||||
|
show-icon
|
||||||
|
>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
<a-tabs default-active-key="1">
|
||||||
|
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.xray.basicTemplate"}}' style="padding-top: 20px;">
|
||||||
|
<a-collapse>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<a-alert type="warning" style="text-align: center;">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||||
|
{{ i18n "pages.xray.generalConfigsDesc" }}
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<a-list-item>
|
||||||
|
<a-row style="padding: 20px">
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-list-item-meta
|
||||||
|
title='{{ i18n "pages.xray.FreedomStrategy" }}'
|
||||||
|
description='{{ i18n "pages.xray.FreedomStrategyDesc" }}'/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<template>
|
||||||
|
<a-select
|
||||||
|
v-model="freedomStrategy"
|
||||||
|
style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-list-item>
|
||||||
|
<a-row style="padding: 20px">
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-list-item-meta
|
||||||
|
title='{{ i18n "pages.xray.RoutingStrategy" }}'
|
||||||
|
description='{{ i18n "pages.xray.RoutingStrategyDesc" }}'/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<template>
|
||||||
|
<a-select
|
||||||
|
v-model="routingStrategy"
|
||||||
|
style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<a-alert type="warning" style="text-align: center;">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||||
|
{{ i18n "pages.xray.generalConfigsDesc" }}
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.Torrent"}}' desc='{{ i18n "pages.xray.TorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.PrivateIp"}}' desc='{{ i18n "pages.xray.PrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.Ads"}}' desc='{{ i18n "pages.xray.AdsDesc"}}' v-model="AdsSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.Family"}}' desc='{{ i18n "pages.xray.FamilyDesc"}}' v-model="familyProtectSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.blockCountryConfigs"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<a-alert type="warning" style="text-align: center;">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||||
|
{{ i18n "pages.xray.blockCountryConfigsDesc" }}
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.IRIp"}}' desc='{{ i18n "pages.xray.IRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.IRDomain"}}' desc='{{ i18n "pages.xray.IRDomainDesc"}}' v-model="IRDomainSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.ChinaIp"}}' desc='{{ i18n "pages.xray.ChinaIpDesc"}}' v-model="ChinaIpSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.ChinaDomain"}}' desc='{{ i18n "pages.xray.ChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.RussiaIp"}}' desc='{{ i18n "pages.xray.RussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.RussiaDomain"}}' desc='{{ i18n "pages.xray.RussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.directCountryConfigs"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<a-alert type="warning" style="text-align: center;">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||||
|
{{ i18n "pages.xray.directCountryConfigsDesc" }}
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectIRIp"}}' desc='{{ i18n "pages.xray.DirectIRIpDesc"}}' v-model="IRIpDirectSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectIRDomain"}}' desc='{{ i18n "pages.xray.DirectIRDomainDesc"}}' v-model="IRDomainDirectSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectChinaIp"}}' desc='{{ i18n "pages.xray.DirectChinaIpDesc"}}' v-model="ChinaIpDirectSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectChinaDomain"}}' desc='{{ i18n "pages.xray.DirectChinaDomainDesc"}}' v-model="ChinaDomainDirectSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectRussiaIp"}}' desc='{{ i18n "pages.xray.DirectRussiaIpDesc"}}' v-model="RussiaIpDirectSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectRussiaDomain"}}' desc='{{ i18n "pages.xray.DirectRussiaDomainDesc"}}' v-model="RussiaDomainDirectSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.ipv4Configs"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<a-alert type="warning" style="text-align: center;">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||||
|
{{ i18n "pages.xray.ipv4ConfigsDesc" }}
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.GoogleIPv4"}}' desc='{{ i18n "pages.xray.GoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixIPv4"}}' desc='{{ i18n "pages.xray.NetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
||||||
|
<a-space direction="horizontal" style="padding: 0 20px">
|
||||||
|
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.xray.manualLists"}}' style="padding-top: 20px;">
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12" style="margin-bottom: 10px;">
|
||||||
|
<a-alert type="warning" style="float: left; width: fit-content">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||||
|
{{ i18n "pages.xray.manualListsDesc" }}
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<a-collapse>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.manualBlockedIPs"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualBlockedIPs"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.manualBlockedDomains"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualBlockedDomains"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.manualDirectIPs"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualDirectIPs"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.manualDirectDomains"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualDirectDomains"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.manualIPv4Domains"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualIPv4Domains"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;">
|
||||||
|
<a-collapse>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.Inbounds"}}'>
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.xray.Inbounds"}}' desc='{{ i18n "pages.xray.InboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.Outbounds"}}'>
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.xray.Outbounds"}}' desc='{{ i18n "pages.xray.OutboundsDesc"}}' v-model="outboundSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.xray.Routings"}}'>
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.xray.Routings"}}' desc='{{ i18n "pages.xray.RoutingsDesc"}}' v-model="routingRuleSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.completeTemplate"}}' style="padding-top: 20px;">
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.xray.Template"}}' desc='{{ i18n "pages.xray.TemplateDesc"}}' v-model="xraySetting"></setting-list-item>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</a-space>
|
||||||
|
</a-spin>
|
||||||
|
</a-layout-content>
|
||||||
|
</a-layout>
|
||||||
|
</a-layout>
|
||||||
|
{{template "js" .}}
|
||||||
|
{{template "component/themeSwitcher" .}}
|
||||||
|
{{template "component/setting"}}
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const app = new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
siderDrawer,
|
||||||
|
themeSwitcher,
|
||||||
|
spinning: false,
|
||||||
|
oldXraySetting: '',
|
||||||
|
xraySetting: '',
|
||||||
|
saveBtnDisable: true,
|
||||||
|
showAlert: false,
|
||||||
|
ipv4Settings: {
|
||||||
|
tag: "IPv4",
|
||||||
|
protocol: "freedom",
|
||||||
|
settings: {
|
||||||
|
domainStrategy: "UseIPv4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directSettings: {
|
||||||
|
tag: "direct",
|
||||||
|
protocol: "freedom"
|
||||||
|
},
|
||||||
|
outboundDomainStrategies: ["AsIs", "UseIP", "UseIPv4", "UseIPv6"],
|
||||||
|
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
||||||
|
settingsData: {
|
||||||
|
protocols: {
|
||||||
|
bittorrent: ["bittorrent"],
|
||||||
|
},
|
||||||
|
ips: {
|
||||||
|
local: ["geoip:private"],
|
||||||
|
cn: ["geoip:cn"],
|
||||||
|
ir: ["geoip:ir"],
|
||||||
|
ru: ["geoip:ru"],
|
||||||
|
},
|
||||||
|
domains: {
|
||||||
|
ads: [
|
||||||
|
"geosite:category-ads-all",
|
||||||
|
"ext:iran.dat:ads"
|
||||||
|
],
|
||||||
|
google: ["geosite:google"],
|
||||||
|
netflix: ["geosite:netflix"],
|
||||||
|
cn: [
|
||||||
|
"geosite:cn",
|
||||||
|
"regexp:.*\\.cn$"
|
||||||
|
],
|
||||||
|
ru: [
|
||||||
|
"geosite:category-gov-ru",
|
||||||
|
"regexp:.*\\.ru$"
|
||||||
|
],
|
||||||
|
ir: [
|
||||||
|
"regexp:.*\\.ir$",
|
||||||
|
"ext:iran.dat:ir",
|
||||||
|
"ext:iran.dat:other",
|
||||||
|
"geosite:category-ir"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
familyProtectDNS: {
|
||||||
|
"servers": [
|
||||||
|
"1.1.1.3",
|
||||||
|
"1.0.0.3",
|
||||||
|
"94.140.14.15",
|
||||||
|
"94.140.15.16"
|
||||||
|
],
|
||||||
|
"queryStrategy": "UseIPv4"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loading(spinning = true) {
|
||||||
|
this.spinning = spinning;
|
||||||
|
},
|
||||||
|
async getXraySetting() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/xui/xray/");
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.oldXraySetting = msg.obj;
|
||||||
|
this.xraySetting = msg.obj;
|
||||||
|
this.saveBtnDisable = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async updateXraySetting() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/xui/xray/update", {xraySetting : this.xraySetting});
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
await this.getXraySetting();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async restartPanel() {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.settings.restartPanel" }}',
|
||||||
|
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
|
okText: '{{ i18n "sure" }}',
|
||||||
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
|
onOk: () => resolve(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/xui/setting/restartPanel");
|
||||||
|
this.loading(false);
|
||||||
|
},
|
||||||
|
async resetXrayConfigToDefault() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.get("/xui/xray/getDefaultJsonConfig");
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
|
||||||
|
this.saveBtnDisable = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
syncRulesWithOutbound(tag, setting) {
|
||||||
|
const newTemplateSettings = this.templateSettings;
|
||||||
|
const haveRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === tag);
|
||||||
|
const outboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.tag === tag);
|
||||||
|
if (!haveRules && outboundIndex > 0) {
|
||||||
|
newTemplateSettings.outbounds.splice(outboundIndex);
|
||||||
|
}
|
||||||
|
if (haveRules && outboundIndex < 0) {
|
||||||
|
newTemplateSettings.outbounds.push(setting);
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
},
|
||||||
|
templateRuleGetter(routeSettings) {
|
||||||
|
const { property, outboundTag } = routeSettings;
|
||||||
|
let result = [];
|
||||||
|
if (this.templateSettings != null) {
|
||||||
|
this.templateSettings.routing.rules.forEach(
|
||||||
|
(routingRule) => {
|
||||||
|
if (
|
||||||
|
routingRule.hasOwnProperty(property) &&
|
||||||
|
routingRule.hasOwnProperty("outboundTag") &&
|
||||||
|
routingRule.outboundTag === outboundTag
|
||||||
|
) {
|
||||||
|
result.push(...routingRule[property]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
templateRuleSetter(routeSettings) {
|
||||||
|
const { data, property, outboundTag } = routeSettings;
|
||||||
|
const oldTemplateSettings = this.templateSettings;
|
||||||
|
const newTemplateSettings = oldTemplateSettings;
|
||||||
|
currentProperty = this.templateRuleGetter({ outboundTag, property })
|
||||||
|
if (currentProperty.length == 0) {
|
||||||
|
const propertyRule = {
|
||||||
|
type: "field",
|
||||||
|
outboundTag,
|
||||||
|
[property]: data
|
||||||
|
};
|
||||||
|
newTemplateSettings.routing.rules.push(propertyRule);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const newRules = [];
|
||||||
|
insertedOnce = false;
|
||||||
|
newTemplateSettings.routing.rules.forEach(
|
||||||
|
(routingRule) => {
|
||||||
|
if (
|
||||||
|
routingRule.hasOwnProperty(property) &&
|
||||||
|
routingRule.hasOwnProperty("outboundTag") &&
|
||||||
|
routingRule.outboundTag === outboundTag
|
||||||
|
) {
|
||||||
|
if (!insertedOnce && data.length > 0) {
|
||||||
|
insertedOnce = true;
|
||||||
|
routingRule[property] = data;
|
||||||
|
newRules.push(routingRule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newRules.push(routingRule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
newTemplateSettings.routing.rules = newRules;
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
if (window.location.protocol !== "https:") {
|
||||||
|
this.showAlert = true;
|
||||||
|
}
|
||||||
|
await this.getXraySetting();
|
||||||
|
while (true) {
|
||||||
|
await PromiseUtil.sleep(1000);
|
||||||
|
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
templateSettings: {
|
||||||
|
get: function () { return this.xraySetting ? JSON.parse(this.xraySetting) : null; },
|
||||||
|
set: function (newValue) { this.xraySetting = JSON.stringify(newValue, null, 2); },
|
||||||
|
},
|
||||||
|
inboundSettings: {
|
||||||
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.inbounds = JSON.parse(newValue);
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outboundSettings: {
|
||||||
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.outbounds = JSON.parse(newValue);
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routingRuleSettings: {
|
||||||
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.routing.rules = JSON.parse(newValue);
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
freedomStrategy: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.templateSettings) return "AsIs";
|
||||||
|
freedomOutbound = this.templateSettings.outbounds.find((o) => o.protocol === "freedom" && !o.tag);
|
||||||
|
if (!freedomOutbound) return "AsIs";
|
||||||
|
if (!freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs";
|
||||||
|
return freedomOutbound.settings.domainStrategy;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
freedomOutboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.protocol === "freedom" && !o.tag);
|
||||||
|
if (!newTemplateSettings.outbounds[freedomOutboundIndex].settings) {
|
||||||
|
newTemplateSettings.outbounds[freedomOutboundIndex].settings = { "domainStrategy": newValue };
|
||||||
|
} else {
|
||||||
|
newTemplateSettings.outbounds[freedomOutboundIndex].settings.domainStrategy = newValue;
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
routingStrategy: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.templateSettings || !this.templateSettings.routing || !this.templateSettings.routing.domainStrategy) return "AsIs";
|
||||||
|
return this.templateSettings.routing.domainStrategy;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.routing.domainStrategy = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
blockedIPs: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "blocked", property: "ip", data: newValue });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
blockedDomains: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "blocked", property: "domain" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "blocked", property: "domain", data: newValue });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
blockedProtocols: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "blocked", property: "protocol" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "blocked", property: "protocol", data: newValue });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directIPs: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "direct", property: "ip" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "direct", property: "ip", data: newValue });
|
||||||
|
this.syncRulesWithOutbound("direct", this.directSettings);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directDomains: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "direct", property: "domain" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "direct", property: "domain", data: newValue });
|
||||||
|
this.syncRulesWithOutbound("direct", this.directSettings);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ipv4Domains: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "IPv4", property: "domain", data: newValue });
|
||||||
|
this.syncRulesWithOutbound("IPv4", this.ipv4Settings);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
manualBlockedIPs: {
|
||||||
|
get: function () { return JSON.stringify(this.blockedIPs, null, 2); },
|
||||||
|
set: debounce(function (value) { this.blockedIPs = JSON.parse(value); }, 1000)
|
||||||
|
},
|
||||||
|
manualBlockedDomains: {
|
||||||
|
get: function () { return JSON.stringify(this.blockedDomains, null, 2); },
|
||||||
|
set: debounce(function (value) { this.blockedDomains = JSON.parse(value); }, 1000)
|
||||||
|
},
|
||||||
|
manualDirectIPs: {
|
||||||
|
get: function () { return JSON.stringify(this.directIPs, null, 2); },
|
||||||
|
set: debounce(function (value) { this.directIPs = JSON.parse(value); }, 1000)
|
||||||
|
},
|
||||||
|
manualDirectDomains: {
|
||||||
|
get: function () { return JSON.stringify(this.directDomains, null, 2); },
|
||||||
|
set: debounce(function (value) { this.directDomains = JSON.parse(value); }, 1000)
|
||||||
|
},
|
||||||
|
manualIPv4Domains: {
|
||||||
|
get: function () { return JSON.stringify(this.ipv4Domains, null, 2); },
|
||||||
|
set: debounce(function (value) { this.ipv4Domains = JSON.parse(value); }, 1000)
|
||||||
|
},
|
||||||
|
torrentSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedProtocols = [...this.blockedProtocols, ...this.settingsData.protocols.bittorrent];
|
||||||
|
} else {
|
||||||
|
this.blockedProtocols = this.blockedProtocols.filter(data => !this.settingsData.protocols.bittorrent.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
privateIpSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.local, this.blockedIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.local];
|
||||||
|
} else {
|
||||||
|
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.local.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AdsSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.ads, this.blockedDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ads];
|
||||||
|
} else {
|
||||||
|
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ads.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
familyProtectSettings: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
||||||
|
return doAllItemsExist(this.templateSettings.dns.servers, this.settingsData.familyProtectDNS.servers);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
if (newValue) {
|
||||||
|
newTemplateSettings.dns = this.settingsData.familyProtectDNS;
|
||||||
|
} else {
|
||||||
|
delete newTemplateSettings.dns;
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
GoogleIPv4Settings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.google, this.ipv4Domains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.google];
|
||||||
|
} else {
|
||||||
|
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.google.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NetflixIPv4Settings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.netflix, this.ipv4Domains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.netflix];
|
||||||
|
} else {
|
||||||
|
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.netflix.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
IRIpSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.ir, this.blockedIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ir];
|
||||||
|
} else {
|
||||||
|
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ir.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
IRDomainSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.ir, this.blockedDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ir];
|
||||||
|
} else {
|
||||||
|
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ir.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChinaIpSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.cn, this.blockedIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.cn];
|
||||||
|
} else {
|
||||||
|
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.cn.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChinaDomainSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.cn, this.blockedDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.cn];
|
||||||
|
} else {
|
||||||
|
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.cn.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RussiaIpSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.ru, this.blockedIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ru];
|
||||||
|
} else {
|
||||||
|
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ru.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RussiaDomainSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.ru, this.blockedDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ru];
|
||||||
|
} else {
|
||||||
|
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ru.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
IRIpDirectSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.ir, this.directIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ir];
|
||||||
|
} else {
|
||||||
|
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ir.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
IRDomainDirectSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.ir, this.directDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ir];
|
||||||
|
} else {
|
||||||
|
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ir.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChinaIpDirectSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.cn, this.directIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.directIPs = [...this.directIPs, ...this.settingsData.ips.cn];
|
||||||
|
} else {
|
||||||
|
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.cn.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChinaDomainDirectSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.cn, this.directDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.directDomains = [...this.directDomains, ...this.settingsData.domains.cn];
|
||||||
|
} else {
|
||||||
|
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.cn.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RussiaIpDirectSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.ru, this.directIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ru];
|
||||||
|
} else {
|
||||||
|
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ru.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RussiaDomainDirectSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.ru, this.directDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ru];
|
||||||
|
} else {
|
||||||
|
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ru.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"routing": {
|
"routing": {
|
||||||
"domainStrategy": "IPIfNonMatch",
|
"domainStrategy": "AsIs",
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
|
|||||||
@@ -249,7 +249,18 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
|
|
||||||
tag := oldInbound.Tag
|
tag := oldInbound.Tag
|
||||||
|
|
||||||
err = s.updateClientTraffics(oldInbound, inbound)
|
db := database.GetDB()
|
||||||
|
tx := db.Begin()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
} else {
|
||||||
|
tx.Commit()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = s.updateClientTraffics(tx, oldInbound, inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return inbound, false, err
|
return inbound, false, err
|
||||||
}
|
}
|
||||||
@@ -290,11 +301,10 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
}
|
}
|
||||||
s.xrayApi.Close()
|
s.xrayApi.Close()
|
||||||
|
|
||||||
db := database.GetDB()
|
return inbound, needRestart, tx.Save(oldInbound).Error
|
||||||
return inbound, needRestart, db.Save(oldInbound).Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbound *model.Inbound) error {
|
func (s *InboundService) updateClientTraffics(tx *gorm.DB, oldInbound *model.Inbound, newInbound *model.Inbound) error {
|
||||||
oldClients, err := s.GetClients(oldInbound)
|
oldClients, err := s.GetClients(oldInbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -304,17 +314,6 @@ func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbo
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db := database.GetDB()
|
|
||||||
tx := db.Begin()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
} else {
|
|
||||||
tx.Commit()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var emailExists bool
|
var emailExists bool
|
||||||
|
|
||||||
for _, oldClient := range oldClients {
|
for _, oldClient := range oldClients {
|
||||||
@@ -581,7 +580,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
|
|
||||||
if len(clients[0].Email) > 0 {
|
if len(clients[0].Email) > 0 {
|
||||||
if len(oldEmail) > 0 {
|
if len(oldEmail) > 0 {
|
||||||
err = s.UpdateClientStat(oldEmail, &clients[0])
|
err = s.UpdateClientStat(tx, oldEmail, &clients[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -648,6 +647,13 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
|
|||||||
return err, false
|
return err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needRestart0, count, err := s.autoRenewClients(tx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Error in renew clients:", err)
|
||||||
|
} else if count > 0 {
|
||||||
|
logger.Debugf("%v clients renewed", count)
|
||||||
|
}
|
||||||
|
|
||||||
needRestart1, count, err := s.disableInvalidClients(tx)
|
needRestart1, count, err := s.disableInvalidClients(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Error in disabling invalid clients:", err)
|
logger.Warning("Error in disabling invalid clients:", err)
|
||||||
@@ -661,7 +667,7 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
|
|||||||
} else if count > 0 {
|
} else if count > 0 {
|
||||||
logger.Debugf("%v inbounds disabled", count)
|
logger.Debugf("%v inbounds disabled", count)
|
||||||
}
|
}
|
||||||
return nil, (needRestart1 || needRestart2)
|
return nil, (needRestart0 || needRestart1 || needRestart2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
|
func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
|
||||||
@@ -688,9 +694,15 @@ func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic
|
|||||||
|
|
||||||
func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) {
|
func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) {
|
||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
|
// Empty onlineUsers
|
||||||
|
if p != nil {
|
||||||
|
p.SetOnlineClients(nil)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var onlineClients []string
|
||||||
|
|
||||||
emails := make([]string, 0, len(traffics))
|
emails := make([]string, 0, len(traffics))
|
||||||
for _, traffic := range traffics {
|
for _, traffic := range traffics {
|
||||||
emails = append(emails, traffic.Email)
|
emails = append(emails, traffic.Email)
|
||||||
@@ -716,11 +728,19 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
|
|||||||
if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email {
|
if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email {
|
||||||
dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up
|
dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up
|
||||||
dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down
|
dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down
|
||||||
|
|
||||||
|
// Add user in onlineUsers array on traffic
|
||||||
|
if traffics[traffic_index].Up+traffics[traffic_index].Down > 0 {
|
||||||
|
onlineClients = append(onlineClients, traffics[traffic_index].Email)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set onlineUsers
|
||||||
|
p.SetOnlineClients(onlineClients)
|
||||||
|
|
||||||
err = tx.Save(dbClientTraffics).Error
|
err = tx.Save(dbClientTraffics).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("AddClientTraffic update data ", err)
|
logger.Warning("AddClientTraffic update data ", err)
|
||||||
@@ -781,6 +801,102 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
|
|||||||
return dbClientTraffics, nil
|
return dbClientTraffics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
|
||||||
|
// check for time expired
|
||||||
|
var traffics []*xray.ClientTraffic
|
||||||
|
now := time.Now().Unix() * 1000
|
||||||
|
var err, err1 error
|
||||||
|
|
||||||
|
err = tx.Model(xray.ClientTraffic{}).Where("reset > 0 and expiry_time > 0 and expiry_time <= ?", now).Find(&traffics).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
// return if there is no client to renew
|
||||||
|
if len(traffics) == 0 {
|
||||||
|
return false, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var inbound_ids []int
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
needRestart := false
|
||||||
|
var clientsToAdd []struct {
|
||||||
|
protocol string
|
||||||
|
tag string
|
||||||
|
client map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, traffic := range traffics {
|
||||||
|
inbound_ids = append(inbound_ids, traffic.InboundId)
|
||||||
|
}
|
||||||
|
err = tx.Model(model.Inbound{}).Where("id IN ?", inbound_ids).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
for inbound_index := range inbounds {
|
||||||
|
settings := map[string]interface{}{}
|
||||||
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
|
clients := settings["clients"].([]interface{})
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
for traffic_index, traffic := range traffics {
|
||||||
|
if traffic.Email == c["email"].(string) {
|
||||||
|
newExpiryTime := traffic.ExpiryTime
|
||||||
|
for newExpiryTime < now {
|
||||||
|
newExpiryTime += (int64(traffic.Reset) * 86400000)
|
||||||
|
}
|
||||||
|
c["expiryTime"] = newExpiryTime
|
||||||
|
traffics[traffic_index].ExpiryTime = newExpiryTime
|
||||||
|
traffics[traffic_index].Down = 0
|
||||||
|
traffics[traffic_index].Up = 0
|
||||||
|
if !traffic.Enable {
|
||||||
|
traffics[traffic_index].Enable = true
|
||||||
|
clientsToAdd = append(clientsToAdd,
|
||||||
|
struct {
|
||||||
|
protocol string
|
||||||
|
tag string
|
||||||
|
client map[string]interface{}
|
||||||
|
}{
|
||||||
|
protocol: string(inbounds[inbound_index].Protocol),
|
||||||
|
tag: inbounds[inbound_index].Tag,
|
||||||
|
client: c,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
clients[client_index] = interface{}(c)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings["clients"] = clients
|
||||||
|
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
inbounds[inbound_index].Settings = string(newSettings)
|
||||||
|
}
|
||||||
|
err = tx.Save(inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
err = tx.Save(traffics).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
if p != nil {
|
||||||
|
err1 = s.xrayApi.Init(p.GetAPIPort())
|
||||||
|
if err1 != nil {
|
||||||
|
return true, int64(len(traffics)), nil
|
||||||
|
}
|
||||||
|
for _, clientToAdd := range clientsToAdd {
|
||||||
|
err1 = s.xrayApi.AddUser(clientToAdd.protocol, clientToAdd.tag, clientToAdd.client)
|
||||||
|
if err1 != nil {
|
||||||
|
needRestart = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.xrayApi.Close()
|
||||||
|
}
|
||||||
|
return needRestart, int64(len(traffics)), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error) {
|
func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error) {
|
||||||
now := time.Now().Unix() * 1000
|
now := time.Now().Unix() * 1000
|
||||||
needRestart := false
|
needRestart := false
|
||||||
@@ -874,6 +990,7 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
|
|||||||
clientTraffic.Enable = true
|
clientTraffic.Enable = true
|
||||||
clientTraffic.Up = 0
|
clientTraffic.Up = 0
|
||||||
clientTraffic.Down = 0
|
clientTraffic.Down = 0
|
||||||
|
clientTraffic.Reset = client.Reset
|
||||||
result := tx.Create(&clientTraffic)
|
result := tx.Create(&clientTraffic)
|
||||||
err := result.Error
|
err := result.Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -882,16 +999,15 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateClientStat(email string, client *model.Client) error {
|
func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error {
|
||||||
db := database.GetDB()
|
result := tx.Model(xray.ClientTraffic{}).
|
||||||
|
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
|
||||||
Where("email = ?", email).
|
Where("email = ?", email).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"email": client.Email,
|
"email": client.Email,
|
||||||
"total": client.TotalGB,
|
"total": client.TotalGB,
|
||||||
"expiry_time": client.ExpiryTime})
|
"expiry_time": client.ExpiryTime,
|
||||||
|
"reset": client.Reset})
|
||||||
err := result.Error
|
err := result.Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1011,7 +1127,7 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
whereText := "inbound_id "
|
whereText := "reset = 0 and inbound_id "
|
||||||
if id < 0 {
|
if id < 0 {
|
||||||
whereText += "> ?"
|
whereText += "> ?"
|
||||||
} else {
|
} else {
|
||||||
@@ -1248,3 +1364,7 @@ func (s *InboundService) MigrateDB() {
|
|||||||
s.MigrationRequirements()
|
s.MigrationRequirements()
|
||||||
s.MigrationRemoveOrphanedTraffics()
|
s.MigrationRemoveOrphanedTraffics()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetOnlineClinets() []string {
|
||||||
|
return p.GetOnlineClients()
|
||||||
|
}
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
status.AppStats.Mem = rtm.Sys
|
status.AppStats.Mem = rtm.Sys
|
||||||
status.AppStats.Threads = uint32(runtime.NumGoroutine())
|
status.AppStats.Threads = uint32(runtime.NumGoroutine())
|
||||||
status.CpuCount = runtime.NumCPU()
|
status.CpuCount = runtime.NumCPU()
|
||||||
if p.IsRunning() {
|
if p != nil && p.IsRunning() {
|
||||||
status.AppStats.Uptime = p.GetUptime()
|
status.AppStats.Uptime = p.GetUptime()
|
||||||
} else {
|
} else {
|
||||||
status.AppStats.Uptime = 0
|
status.AppStats.Uptime = 0
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ type SettingService struct {
|
|||||||
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
settings := make([]*model.Setting, 0)
|
settings := make([]*model.Setting, 0)
|
||||||
err := db.Model(model.Setting{}).Find(&settings).Error
|
err := db.Model(model.Setting{}).Not("key = ?", "xrayTemplateConfig").Find(&settings).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,10 +75,16 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bot, err = tgbotapi.NewBotAPI(tgBottoken)
|
for {
|
||||||
if err != nil {
|
bot, err = tgbotapi.NewBotAPI(tgBottoken)
|
||||||
fmt.Println("Get tgbot's api error:", err)
|
if err != nil {
|
||||||
return err
|
fmt.Println("Get tgbot's api error:", err)
|
||||||
|
fmt.Println("Retrying after 10 secound...")
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Tgbot connected!")
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bot.Debug = false
|
bot.Debug = false
|
||||||
|
|
||||||
@@ -207,8 +213,14 @@ func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bo
|
|||||||
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
|
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
|
||||||
case "client_commands":
|
case "client_commands":
|
||||||
t.SendMsgToTgbot(callbackQuery.From.ID, t.I18nBot("tgbot.commands.helpClientCommands"))
|
t.SendMsgToTgbot(callbackQuery.From.ID, t.I18nBot("tgbot.commands.helpClientCommands"))
|
||||||
|
case "onlines":
|
||||||
|
t.onlineClients(callbackQuery.From.ID)
|
||||||
case "commands":
|
case "commands":
|
||||||
t.SendMsgToTgbot(callbackQuery.From.ID, t.I18nBot("tgbot.commands.helpAdminCommands"))
|
t.SendMsgToTgbot(callbackQuery.From.ID, t.I18nBot("tgbot.commands.helpAdminCommands"))
|
||||||
|
default:
|
||||||
|
if callbackQuery.Data[:7] == "client_" {
|
||||||
|
t.searchClient(callbackQuery.From.ID, callbackQuery.Data[7:])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,6 +245,7 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
|
|||||||
),
|
),
|
||||||
tgbotapi.NewInlineKeyboardRow(
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
tgbotapi.NewInlineKeyboardButtonData(t.I18nBot("tgbot.buttons.commands"), "commands"),
|
tgbotapi.NewInlineKeyboardButtonData(t.I18nBot("tgbot.buttons.commands"), "commands"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData(t.I18nBot("tgbot.buttons.onlines"), "onlines"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
numericKeyboardClient := tgbotapi.NewInlineKeyboardMarkup(
|
numericKeyboardClient := tgbotapi.NewInlineKeyboardMarkup(
|
||||||
@@ -429,77 +442,7 @@ func (t *Tgbot) getInboundUsages() string {
|
|||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
func (t *Tgbot) clientInfoMsg(traffic *xray.ClientTraffic) string {
|
||||||
if len(tgUserName) == 0 {
|
|
||||||
msg := t.I18nBot("tgbot.answers.askToAddUser")
|
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserName)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning(err)
|
|
||||||
msg := t.I18nBot("tgbot.wentWrong")
|
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(traffics) == 0 {
|
|
||||||
msg := t.I18nBot("tgbot.answers.askToAddUserName", "TgUserName=="+tgUserName)
|
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, traffic := range traffics {
|
|
||||||
expiryTime := ""
|
|
||||||
if traffic.ExpiryTime == 0 {
|
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else if traffic.ExpiryTime < 0 {
|
|
||||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
|
||||||
} else {
|
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
total := ""
|
|
||||||
if traffic.Total == 0 {
|
|
||||||
total = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else {
|
|
||||||
total = common.FormatTraffic((traffic.Total))
|
|
||||||
}
|
|
||||||
|
|
||||||
active := ""
|
|
||||||
if traffic.Enable {
|
|
||||||
active = t.I18nBot("tgbot.messages.yes")
|
|
||||||
} else {
|
|
||||||
active = t.I18nBot("tgbot.messages.no")
|
|
||||||
}
|
|
||||||
|
|
||||||
output := ""
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+active)
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
|
||||||
|
|
||||||
t.SendMsgToTgbot(chatId, output)
|
|
||||||
}
|
|
||||||
t.SendAnswer(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tgbot) searchClient(chatId int64, email string) {
|
|
||||||
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning(err)
|
|
||||||
msg := t.I18nBot("tgbot.wentWrong")
|
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if traffic == nil {
|
|
||||||
msg := t.I18nBot("tgbot.noResult")
|
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expiryTime := ""
|
expiryTime := ""
|
||||||
if traffic.ExpiryTime == 0 {
|
if traffic.ExpiryTime == 0 {
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||||
@@ -523,14 +466,70 @@ func (t *Tgbot) searchClient(chatId int64, email string) {
|
|||||||
active = t.I18nBot("tgbot.messages.no")
|
active = t.I18nBot("tgbot.messages.no")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status := t.I18nBot("offline")
|
||||||
|
if p.IsRunning() {
|
||||||
|
for _, online := range p.GetOnlineClients() {
|
||||||
|
if online == traffic.Email {
|
||||||
|
status = t.I18nBot("online")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
output := ""
|
output := ""
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+active)
|
output += t.I18nBot("tgbot.messages.active", "Enable=="+active)
|
||||||
|
output += t.I18nBot("tgbot.messages.online", "Status=="+status)
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
||||||
|
if len(tgUserName) == 0 {
|
||||||
|
msg := t.I18nBot("tgbot.answers.askToAddUser")
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserName)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
msg := t.I18nBot("tgbot.wentWrong")
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(traffics) == 0 {
|
||||||
|
msg := t.I18nBot("tgbot.answers.askToAddUserName", "TgUserName=="+tgUserName)
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, traffic := range traffics {
|
||||||
|
output := t.clientInfoMsg(traffic)
|
||||||
|
t.SendMsgToTgbot(chatId, output)
|
||||||
|
}
|
||||||
|
t.SendAnswer(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) searchClient(chatId int64, email string) {
|
||||||
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
msg := t.I18nBot("tgbot.wentWrong")
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if traffic == nil {
|
||||||
|
msg := t.I18nBot("tgbot.noResult")
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output := t.clientInfoMsg(traffic)
|
||||||
t.SendMsgToTgbot(chatId, output)
|
t.SendMsgToTgbot(chatId, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,37 +562,7 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
|||||||
t.SendMsgToTgbot(chatId, info)
|
t.SendMsgToTgbot(chatId, info)
|
||||||
|
|
||||||
for _, traffic := range inbound.ClientStats {
|
for _, traffic := range inbound.ClientStats {
|
||||||
expiryTime := ""
|
output := t.clientInfoMsg(&traffic)
|
||||||
if traffic.ExpiryTime == 0 {
|
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else if traffic.ExpiryTime < 0 {
|
|
||||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
|
||||||
} else {
|
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
total := ""
|
|
||||||
if traffic.Total == 0 {
|
|
||||||
total = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else {
|
|
||||||
total = common.FormatTraffic((traffic.Total))
|
|
||||||
}
|
|
||||||
|
|
||||||
active := ""
|
|
||||||
if traffic.Enable {
|
|
||||||
active = t.I18nBot("tgbot.messages.yes")
|
|
||||||
} else {
|
|
||||||
active = t.I18nBot("tgbot.messages.no")
|
|
||||||
}
|
|
||||||
|
|
||||||
output := ""
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+active)
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
|
||||||
|
|
||||||
t.SendMsgToTgbot(chatId, output)
|
t.SendMsgToTgbot(chatId, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -613,37 +582,7 @@ func (t *Tgbot) searchForClient(chatId int64, query string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expiryTime := ""
|
output := t.clientInfoMsg(traffic)
|
||||||
if traffic.ExpiryTime == 0 {
|
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else if traffic.ExpiryTime < 0 {
|
|
||||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
|
||||||
} else {
|
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
total := ""
|
|
||||||
if traffic.Total == 0 {
|
|
||||||
total = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else {
|
|
||||||
total = common.FormatTraffic((traffic.Total))
|
|
||||||
}
|
|
||||||
|
|
||||||
active := ""
|
|
||||||
if traffic.Enable {
|
|
||||||
active = t.I18nBot("tgbot.messages.yes")
|
|
||||||
} else {
|
|
||||||
active = t.I18nBot("tgbot.messages.no")
|
|
||||||
}
|
|
||||||
|
|
||||||
output := ""
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+active)
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
|
||||||
|
|
||||||
t.SendMsgToTgbot(chatId, output)
|
t.SendMsgToTgbot(chatId, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -725,35 +664,7 @@ func (t *Tgbot) getExhausted() string {
|
|||||||
output += t.I18nBot("tgbot.messages.exhaustedMsg", "Type=="+t.I18nBot("tgbot.clients"))
|
output += t.I18nBot("tgbot.messages.exhaustedMsg", "Type=="+t.I18nBot("tgbot.clients"))
|
||||||
|
|
||||||
for _, traffic := range exhaustedClients {
|
for _, traffic := range exhaustedClients {
|
||||||
expiryTime := ""
|
output += t.clientInfoMsg(&traffic)
|
||||||
if traffic.ExpiryTime == 0 {
|
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else if traffic.ExpiryTime < 0 {
|
|
||||||
expiryTime += fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
|
||||||
} else {
|
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
total := ""
|
|
||||||
if traffic.Total == 0 {
|
|
||||||
total = t.I18nBot("tgbot.unlimited")
|
|
||||||
} else {
|
|
||||||
total = common.FormatTraffic((traffic.Total))
|
|
||||||
}
|
|
||||||
|
|
||||||
active := ""
|
|
||||||
if traffic.Enable {
|
|
||||||
active = t.I18nBot("tgbot.messages.yes")
|
|
||||||
} else {
|
|
||||||
active = t.I18nBot("tgbot.messages.no")
|
|
||||||
}
|
|
||||||
|
|
||||||
output += t.I18nBot("tgbot.messages.active", "Enable=="+active)
|
|
||||||
output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email)
|
|
||||||
output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up))
|
|
||||||
output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down))
|
|
||||||
output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total)
|
|
||||||
output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime)
|
|
||||||
output += "\r\n \r\n"
|
output += "\r\n \r\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -761,6 +672,25 @@ func (t *Tgbot) getExhausted() string {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) onlineClients(chatId int64) {
|
||||||
|
if !p.IsRunning() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
onlines := p.GetOnlineClients()
|
||||||
|
output := t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(len(onlines)))
|
||||||
|
if len(onlines) > 0 {
|
||||||
|
keyboard := tgbotapi.NewInlineKeyboardMarkup()
|
||||||
|
for index, online := range onlines {
|
||||||
|
keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData(fmt.Sprintf("%d: %s\r\n", index+1, online), "client_"+online)))
|
||||||
|
}
|
||||||
|
t.SendMsgToTgbot(chatId, output, keyboard)
|
||||||
|
} else {
|
||||||
|
t.SendMsgToTgbot(chatId, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tgbot) sendBackup(chatId int64) {
|
func (t *Tgbot) sendBackup(chatId int64) {
|
||||||
if !t.IsRunning() {
|
if !t.IsRunning() {
|
||||||
return
|
return
|
||||||
|
|||||||
28
web/service/xraySettings.go
Normal file
28
web/service/xraySettings.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"x-ui/util/common"
|
||||||
|
"x-ui/xray"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XraySettingService struct {
|
||||||
|
SettingService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XraySettingService) SaveXraySetting(newXraySettings string) error {
|
||||||
|
if err := s.CheckXrayConfig(newXraySettings); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.SettingService.saveSetting("xrayTemplateConfig", newXraySettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error {
|
||||||
|
xrayConfig := &xray.Config{}
|
||||||
|
err := json.Unmarshal([]byte(XrayTemplateConfig), xrayConfig)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError("xray template config invalid:", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"protocol" = "Protocol"
|
"protocol" = "Protocol"
|
||||||
"search" = "Search"
|
"search" = "Search"
|
||||||
"filter" = "Filter"
|
"filter" = "Filter"
|
||||||
"loading" = "Loading"
|
"loading" = "Loading..."
|
||||||
"second" = "Second"
|
"second" = "Second"
|
||||||
"minute" = "Minute"
|
"minute" = "Minute"
|
||||||
"hour" = "Hour"
|
"hour" = "Hour"
|
||||||
@@ -37,7 +37,9 @@
|
|||||||
"enabled" = "Enabled"
|
"enabled" = "Enabled"
|
||||||
"disabled" = "Disabled"
|
"disabled" = "Disabled"
|
||||||
"depleted" = "Depleted"
|
"depleted" = "Depleted"
|
||||||
"depletingSoon" = "Depleting soon"
|
"depletingSoon" = "Depleting"
|
||||||
|
"offline" = "Offline"
|
||||||
|
"online" = "Online"
|
||||||
"domainName" = "Domain name"
|
"domainName" = "Domain name"
|
||||||
"monitor" = "Listen IP"
|
"monitor" = "Listen IP"
|
||||||
"certificate" = "Certificate"
|
"certificate" = "Certificate"
|
||||||
@@ -55,6 +57,7 @@
|
|||||||
"dashboard" = "System Status"
|
"dashboard" = "System Status"
|
||||||
"inbounds" = "Inbounds"
|
"inbounds" = "Inbounds"
|
||||||
"settings" = "Panel Settings"
|
"settings" = "Panel Settings"
|
||||||
|
"xray" = "Xray Settings"
|
||||||
"logout" = "Logout"
|
"logout" = "Logout"
|
||||||
"link" = "Other"
|
"link" = "Other"
|
||||||
|
|
||||||
@@ -121,6 +124,8 @@
|
|||||||
"modifyInbound" = "Modify Inbound"
|
"modifyInbound" = "Modify Inbound"
|
||||||
"deleteInbound" = "Delete Inbound"
|
"deleteInbound" = "Delete Inbound"
|
||||||
"deleteInboundContent" = "Are you sure you want to delete inbound?"
|
"deleteInboundContent" = "Are you sure you want to delete inbound?"
|
||||||
|
"deleteClient" = "Delete Client"
|
||||||
|
"deleteClientContent" = "Are you sure you want to delete client?"
|
||||||
"resetTrafficContent" = "Are you sure you want to reset traffic?"
|
"resetTrafficContent" = "Are you sure you want to reset traffic?"
|
||||||
"copyLink" = "Copy Link"
|
"copyLink" = "Copy Link"
|
||||||
"address" = "Address"
|
"address" = "Address"
|
||||||
@@ -132,8 +137,8 @@
|
|||||||
"totalFlow" = "Total Flow"
|
"totalFlow" = "Total Flow"
|
||||||
"leaveBlankToNeverExpire" = "Leave blank to never expire"
|
"leaveBlankToNeverExpire" = "Leave blank to never expire"
|
||||||
"noRecommendKeepDefault" = "No special requirements to keep the default"
|
"noRecommendKeepDefault" = "No special requirements to keep the default"
|
||||||
"certificatePath" = "Certificate File Path"
|
"certificatePath" = "File Path"
|
||||||
"certificateContent" = "Certificate File Content"
|
"certificateContent" = "File Content"
|
||||||
"publicKeyPath" = "Public Key Path"
|
"publicKeyPath" = "Public Key Path"
|
||||||
"publicKeyContent" = "Public Key Content"
|
"publicKeyContent" = "Public Key Content"
|
||||||
"keyPath" = "Private Key Path"
|
"keyPath" = "Private Key Path"
|
||||||
@@ -160,8 +165,9 @@
|
|||||||
"email" = "Email"
|
"email" = "Email"
|
||||||
"emailDesc" = "Please provide a unique email address."
|
"emailDesc" = "Please provide a unique email address."
|
||||||
"setDefaultCert" = "Set cert from panel"
|
"setDefaultCert" = "Set cert from panel"
|
||||||
"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot or use '/id' command in bot )"
|
"telegramDesc" = "Use Telegram ID without @ or chat IDs ( you can get it here @userinfobot or use '/id' command in bot )"
|
||||||
"subscriptionDesc" = "you can find your sub link on Details, also you can use the same name for several configurations"
|
"subscriptionDesc" = "You can find your sub link on Details, also you can use the same name for several configurations"
|
||||||
|
"info" = "Info"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Add Client"
|
"add" = "Add Client"
|
||||||
@@ -178,6 +184,8 @@
|
|||||||
"delayedStart" = "Start after first use"
|
"delayedStart" = "Start after first use"
|
||||||
"expireDays" = "Expire days"
|
"expireDays" = "Expire days"
|
||||||
"days" = "day(s)"
|
"days" = "day(s)"
|
||||||
|
"renew" = "Auto renew"
|
||||||
|
"renewDesc" = "Auto renew days after expiration. 0 = disable"
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
[pages.inbounds.toasts]
|
||||||
"obtain" = "Obtain"
|
"obtain" = "Obtain"
|
||||||
@@ -208,7 +216,6 @@
|
|||||||
"resetDefaultConfig" = "Reset to default config"
|
"resetDefaultConfig" = "Reset to default config"
|
||||||
"panelConfig" = "Panel Configurations"
|
"panelConfig" = "Panel Configurations"
|
||||||
"userSettings" = "User Settings"
|
"userSettings" = "User Settings"
|
||||||
"xrayConfiguration" = "Xray Configurations"
|
|
||||||
"TGBotSettings" = "Telegram Bot Settings"
|
"TGBotSettings" = "Telegram Bot Settings"
|
||||||
"panelListeningIP" = "Panel Listening IP"
|
"panelListeningIP" = "Panel Listening IP"
|
||||||
"panelListeningIPDesc" = "Leave blank by default to monitor all IPs."
|
"panelListeningIPDesc" = "Leave blank by default to monitor all IPs."
|
||||||
@@ -272,8 +279,8 @@
|
|||||||
"subShowInfo" = "Show usage info"
|
"subShowInfo" = "Show usage info"
|
||||||
"subShowInfoDesc" = "Show remianed traffic and date after config name"
|
"subShowInfoDesc" = "Show remianed traffic and date after config name"
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.xray]
|
||||||
"title" = "Templates"
|
"title" = "Xray Settings"
|
||||||
"basicTemplate" = "Basic Template"
|
"basicTemplate" = "Basic Template"
|
||||||
"advancedTemplate" = "Advanced Template"
|
"advancedTemplate" = "Advanced Template"
|
||||||
"completeTemplate" = "Complete Template"
|
"completeTemplate" = "Complete Template"
|
||||||
@@ -287,54 +294,54 @@
|
|||||||
"directCountryConfigsDesc" = "These options will connect users directly to specific country domains."
|
"directCountryConfigsDesc" = "These options will connect users directly to specific country domains."
|
||||||
"ipv4Configs" = "IPv4 Configs"
|
"ipv4Configs" = "IPv4 Configs"
|
||||||
"ipv4ConfigsDesc" = "These options will route to target domains only via IPv4."
|
"ipv4ConfigsDesc" = "These options will route to target domains only via IPv4."
|
||||||
"xrayConfigTemplate" = "Xray Configuration Template"
|
"Template" = "Xray Configuration Template"
|
||||||
"xrayConfigTemplateDesc" = "Generate the final Xray configuration file based on this template."
|
"TemplateDesc" = "Generate the final Xray configuration file based on this template."
|
||||||
"xrayConfigFreedomStrategy" = "Configure Strategy for Freedom Protocol"
|
"FreedomStrategy" = "Configure Strategy for Freedom Protocol"
|
||||||
"xrayConfigFreedomStrategyDesc" = "Set the output strategy of the network in the Freedom Protocol."
|
"FreedomStrategyDesc" = "Set the output strategy of the network in the Freedom Protocol."
|
||||||
"xrayConfigRoutingStrategy" = "Configure Domains Routing Strategy"
|
"RoutingStrategy" = "Configure Domains Routing Strategy"
|
||||||
"xrayConfigRoutingStrategyDesc" = "Set the overall routing strategy for DNS resolving."
|
"RoutingStrategyDesc" = "Set the overall routing strategy for DNS resolving."
|
||||||
"xrayConfigTorrent" = "Ban BitTorrent Usage"
|
"Torrent" = "Ban BitTorrent Usage"
|
||||||
"xrayConfigTorrentDesc" = "Change the configuration template to avoid using BitTorrent by users."
|
"TorrentDesc" = "Change the configuration template to avoid using BitTorrent by users."
|
||||||
"xrayConfigPrivateIp" = "Ban Private IP Ranges to Connect"
|
"PrivateIp" = "Ban Private IP Ranges to Connect"
|
||||||
"xrayConfigPrivateIpDesc" = "Change the configuration template to avoid connecting to private IP ranges."
|
"PrivateIpDesc" = "Change the configuration template to avoid connecting to private IP ranges."
|
||||||
"xrayConfigAds" = "Block Ads"
|
"Ads" = "Block Ads"
|
||||||
"xrayConfigAdsDesc" = "Change the configuration template to block ads"
|
"AdsDesc" = "Change the configuration template to block ads"
|
||||||
"xrayConfigFamily" = "Enable Family-Friendly Configuration"
|
"Family" = "Enable Family-Friendly Configuration"
|
||||||
"xrayConfigFamilyDesc" = "Avoid connecting to unsafe websites for family protection."
|
"FamilyDesc" = "Avoid connecting to unsafe websites for family protection."
|
||||||
"xrayConfigIRIp" = "Disable connection to Iran IP ranges"
|
"IRIp" = "Disable connection to Iran IP ranges"
|
||||||
"xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting to Iran IP ranges."
|
"IRIpDesc" = "Change the configuration template to avoid connecting to Iran IP ranges."
|
||||||
"xrayConfigIRDomain" = "Disable connection to Iran domains"
|
"IRDomain" = "Disable connection to Iran domains"
|
||||||
"xrayConfigIRDomainDesc" = "Change the configuration template to avoid connecting to Iran domains."
|
"IRDomainDesc" = "Change the configuration template to avoid connecting to Iran domains."
|
||||||
"xrayConfigChinaIp" = "Disable connection to China IP ranges"
|
"ChinaIp" = "Disable connection to China IP ranges"
|
||||||
"xrayConfigChinaIpDesc" = "Change the configuration template to avoid connecting to China IP ranges."
|
"ChinaIpDesc" = "Change the configuration template to avoid connecting to China IP ranges."
|
||||||
"xrayConfigChinaDomain" = "Disable connection to China domains"
|
"ChinaDomain" = "Disable connection to China domains"
|
||||||
"xrayConfigChinaDomainDesc" = "Change the configuration template to avoid connecting to China domains."
|
"ChinaDomainDesc" = "Change the configuration template to avoid connecting to China domains."
|
||||||
"xrayConfigRussiaIp" = "Disable connection to Russia IP ranges"
|
"RussiaIp" = "Disable connection to Russia IP ranges"
|
||||||
"xrayConfigRussiaIpDesc" = "Change the configuration template to avoid connecting to Russia IP ranges."
|
"RussiaIpDesc" = "Change the configuration template to avoid connecting to Russia IP ranges."
|
||||||
"xrayConfigRussiaDomain" = "Disable connection to Russia domains"
|
"RussiaDomain" = "Disable connection to Russia domains"
|
||||||
"xrayConfigRussiaDomainDesc" = "Change the configuration template to avoid connecting to Russia domains."
|
"RussiaDomainDesc" = "Change the configuration template to avoid connecting to Russia domains."
|
||||||
"xrayConfigDirectIRIp" = "Direct connection to Iran IP ranges"
|
"DirectIRIp" = "Direct connection to Iran IP ranges"
|
||||||
"xrayConfigDirectIRIpDesc" = "Change the configuration template for direct connecting to Iran IP ranges."
|
"DirectIRIpDesc" = "Change the configuration template for direct connecting to Iran IP ranges."
|
||||||
"xrayConfigDirectIRDomain" = "Direct connection to Iran domains"
|
"DirectIRDomain" = "Direct connection to Iran domains"
|
||||||
"xrayConfigDirectIRDomainDesc" = "Change the configuration template for direct connecting to Iran domains."
|
"DirectIRDomainDesc" = "Change the configuration template for direct connecting to Iran domains."
|
||||||
"xrayConfigDirectChinaIp" = "Direct connection to China IP ranges"
|
"DirectChinaIp" = "Direct connection to China IP ranges"
|
||||||
"xrayConfigDirectChinaIpDesc" = "Change the configuration template for direct connecting to China IP ranges."
|
"DirectChinaIpDesc" = "Change the configuration template for direct connecting to China IP ranges."
|
||||||
"xrayConfigDirectChinaDomain" = "Direct connection to China domains"
|
"DirectChinaDomain" = "Direct connection to China domains"
|
||||||
"xrayConfigDirectChinaDomainDesc" = "Change the configuration template for direct connecting to China domains."
|
"DirectChinaDomainDesc" = "Change the configuration template for direct connecting to China domains."
|
||||||
"xrayConfigDirectRussiaIp" = "Direct connection to Russia IP ranges"
|
"DirectRussiaIp" = "Direct connection to Russia IP ranges"
|
||||||
"xrayConfigDirectRussiaIpDesc" = "Change the configuration template for direct connecting to Russia IP ranges."
|
"DirectRussiaIpDesc" = "Change the configuration template for direct connecting to Russia IP ranges."
|
||||||
"xrayConfigDirectRussiaDomain" = "Direct connection to Russia domains"
|
"DirectRussiaDomain" = "Direct connection to Russia domains"
|
||||||
"xrayConfigDirectRussiaDomainDesc" = "Change the configuration template for direct connecting to Russia domains."
|
"DirectRussiaDomainDesc" = "Change the configuration template for direct connecting to Russia domains."
|
||||||
"xrayConfigGoogleIPv4" = "Use IPv4 for Google"
|
"GoogleIPv4" = "Use IPv4 for Google"
|
||||||
"xrayConfigGoogleIPv4Desc" = "Add routing for Google to connect with IPv4."
|
"GoogleIPv4Desc" = "Add routing for Google to connect with IPv4."
|
||||||
"xrayConfigNetflixIPv4" = "Use IPv4 for Netflix"
|
"NetflixIPv4" = "Use IPv4 for Netflix"
|
||||||
"xrayConfigNetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4."
|
"NetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4."
|
||||||
"xrayConfigInbounds" = "Configuration of Inbounds"
|
"Inbounds" = "Configuration of Inbounds"
|
||||||
"xrayConfigInboundsDesc" = "Change the configuration template to accept specific clients."
|
"InboundsDesc" = "Change the configuration template to accept specific clients."
|
||||||
"xrayConfigOutbounds" = "Configuration of Outbounds"
|
"Outbounds" = "Configuration of Outbounds"
|
||||||
"xrayConfigOutboundsDesc" = "Change the configuration template to define outgoing ways for this server."
|
"OutboundsDesc" = "Change the configuration template to define outgoing ways for this server."
|
||||||
"xrayConfigRoutings" = "Configuration of routing rules"
|
"Routings" = "Configuration of routing rules"
|
||||||
"xrayConfigRoutingsDesc" = "Change the configuration template to define routing rules for this server."
|
"RoutingsDesc" = "Change the configuration template to define routing rules for this server."
|
||||||
"manualLists" = "Manual Lists"
|
"manualLists" = "Manual Lists"
|
||||||
"manualListsDesc" = "Please use the JSON array format."
|
"manualListsDesc" = "Please use the JSON array format."
|
||||||
"manualBlockedIPs" = "List of Blocked IPs"
|
"manualBlockedIPs" = "List of Blocked IPs"
|
||||||
@@ -398,12 +405,14 @@
|
|||||||
"expire" = "📅 Expire Date: {{ .DateTime }}\r\n \r\n"
|
"expire" = "📅 Expire Date: {{ .DateTime }}\r\n \r\n"
|
||||||
"expireIn" = "📅 Expire In: {{ .Time }}\r\n \r\n"
|
"expireIn" = "📅 Expire In: {{ .Time }}\r\n \r\n"
|
||||||
"active" = "💡 Active: {{ .Enable }}\r\n"
|
"active" = "💡 Active: {{ .Enable }}\r\n"
|
||||||
|
"online" = "🌐 Connection status: {{ .Status }}\r\n"
|
||||||
"email" = "📧 Email: {{ .Email }}\r\n"
|
"email" = "📧 Email: {{ .Email }}\r\n"
|
||||||
"upload" = "🔼 Upload↑: {{ .Upload }}\r\n"
|
"upload" = "🔼 Upload↑: {{ .Upload }}\r\n"
|
||||||
"download" = "🔽 Download↓: {{ .Download }}\r\n"
|
"download" = "🔽 Download↓: {{ .Download }}\r\n"
|
||||||
"total" = "🔄 Total: {{ .UpDown }} / {{ .Total }}\r\n"
|
"total" = "🔄 Total: {{ .UpDown }} / {{ .Total }}\r\n"
|
||||||
"exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n"
|
"exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n"
|
||||||
"exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n"
|
"exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n"
|
||||||
|
"onlinesCount" = "🌐 Online clients count: {{ .Count }}\r\n"
|
||||||
"disabled" = "🛑 Disabled: {{ .Disabled }}\r\n"
|
"disabled" = "🛑 Disabled: {{ .Disabled }}\r\n"
|
||||||
"depleteSoon" = "🔜 Deplete soon: {{ .Deplete }}\r\n \r\n"
|
"depleteSoon" = "🔜 Deplete soon: {{ .Deplete }}\r\n \r\n"
|
||||||
"backupTime" = "🗄 Backup Time: {{ .Time }}\r\n"
|
"backupTime" = "🗄 Backup Time: {{ .Time }}\r\n"
|
||||||
@@ -416,6 +425,7 @@
|
|||||||
"getInbounds" = "Get Inbounds"
|
"getInbounds" = "Get Inbounds"
|
||||||
"depleteSoon" = "Deplete soon"
|
"depleteSoon" = "Deplete soon"
|
||||||
"clientUsage" = "Get Usage"
|
"clientUsage" = "Get Usage"
|
||||||
|
"onlines" = "Online Clients"
|
||||||
"commands" = "Commands"
|
"commands" = "Commands"
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"protocol" = "پروتکل"
|
"protocol" = "پروتکل"
|
||||||
"search" = "جستجو"
|
"search" = "جستجو"
|
||||||
"filter" = "فیلتر"
|
"filter" = "فیلتر"
|
||||||
"loading" = "در حال بروزرسانی.."
|
"loading" = "در حال بروزرسانی..."
|
||||||
"second" = "ثانیه"
|
"second" = "ثانیه"
|
||||||
"minute" = "دقیقه"
|
"minute" = "دقیقه"
|
||||||
"hour" = "ساعت"
|
"hour" = "ساعت"
|
||||||
@@ -38,6 +38,8 @@
|
|||||||
"disabled" = "غیرفعال"
|
"disabled" = "غیرفعال"
|
||||||
"depleted" = "منقضی"
|
"depleted" = "منقضی"
|
||||||
"depletingSoon" = "در حال انقضا"
|
"depletingSoon" = "در حال انقضا"
|
||||||
|
"offline" = "آفلاین"
|
||||||
|
"online" = "آنلاین"
|
||||||
"domainName" = "آدرس دامنه"
|
"domainName" = "آدرس دامنه"
|
||||||
"monitor" = "آی پی اتصال"
|
"monitor" = "آی پی اتصال"
|
||||||
"certificate" = "گواهی دیجیتال"
|
"certificate" = "گواهی دیجیتال"
|
||||||
@@ -55,11 +57,12 @@
|
|||||||
"dashboard" = "وضعیت سیستم"
|
"dashboard" = "وضعیت سیستم"
|
||||||
"inbounds" = "سرویس ها"
|
"inbounds" = "سرویس ها"
|
||||||
"settings" = "تنظیمات پنل"
|
"settings" = "تنظیمات پنل"
|
||||||
|
"xray" = "الگوی ایکسری"
|
||||||
"logout" = "خروج"
|
"logout" = "خروج"
|
||||||
"link" = "دیگر"
|
"link" = "دیگر"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
"title" = "ورود به سیستم X-UI"
|
"title" = "ورود به سیستم"
|
||||||
"loginAgain" = "مدت زمان استفاده به اتمام رسیده ، لطفا دوباره وارد شوید"
|
"loginAgain" = "مدت زمان استفاده به اتمام رسیده ، لطفا دوباره وارد شوید"
|
||||||
|
|
||||||
[pages.login.toasts]
|
[pages.login.toasts]
|
||||||
@@ -121,6 +124,8 @@
|
|||||||
"modifyInbound" = "ویرایش سرویس"
|
"modifyInbound" = "ویرایش سرویس"
|
||||||
"deleteInbound" = "حذف سرویس"
|
"deleteInbound" = "حذف سرویس"
|
||||||
"deleteInboundContent" = "آیا مطمئن به حذف سرویس هستید ؟"
|
"deleteInboundContent" = "آیا مطمئن به حذف سرویس هستید ؟"
|
||||||
|
"deleteClient" = "حذف کاربر"
|
||||||
|
"deleteClientContent" = "آیا مطمئن به حذف کاربر هستید ؟"
|
||||||
"resetTrafficContent" = "آیا مطمئن به ریست ترافیک هستید ؟"
|
"resetTrafficContent" = "آیا مطمئن به ریست ترافیک هستید ؟"
|
||||||
"copyLink" = "کپی لینک"
|
"copyLink" = "کپی لینک"
|
||||||
"address" = "آدرس"
|
"address" = "آدرس"
|
||||||
@@ -132,8 +137,8 @@
|
|||||||
"totalFlow" = "کل ترافیک"
|
"totalFlow" = "کل ترافیک"
|
||||||
"leaveBlankToNeverExpire" = "خالی بگذارید تا هرگز منقضی نشود"
|
"leaveBlankToNeverExpire" = "خالی بگذارید تا هرگز منقضی نشود"
|
||||||
"noRecommendKeepDefault" = "توصیه می شود به عنوان پیش فرض حفظ شود"
|
"noRecommendKeepDefault" = "توصیه می شود به عنوان پیش فرض حفظ شود"
|
||||||
"certificatePath" = "مسیر فایل گواهی"
|
"certificatePath" = "مسیر فایل"
|
||||||
"certificateContent" = "محتوای فایل گواهی"
|
"certificateContent" = "محتوای فایل"
|
||||||
"publicKeyPath" = "مسیر کلید عمومی"
|
"publicKeyPath" = "مسیر کلید عمومی"
|
||||||
"publicKeyContent" = "محتوای کلید عمومی"
|
"publicKeyContent" = "محتوای کلید عمومی"
|
||||||
"keyPath" = "مسیر کلید خصوصی"
|
"keyPath" = "مسیر کلید خصوصی"
|
||||||
@@ -161,6 +166,7 @@
|
|||||||
"setDefaultCert" = "استفاده از گواهی پنل"
|
"setDefaultCert" = "استفاده از گواهی پنل"
|
||||||
"telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot یا در ربات دستور '/id' را وارد کنید)"
|
"telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot یا در ربات دستور '/id' را وارد کنید)"
|
||||||
"subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید"
|
"subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید"
|
||||||
|
"info" = "اطلاعات"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "کاربر جدید"
|
"add" = "کاربر جدید"
|
||||||
@@ -177,6 +183,8 @@
|
|||||||
"delayedStart" = "شروع بعد از اولین استفاده"
|
"delayedStart" = "شروع بعد از اولین استفاده"
|
||||||
"expireDays" = "روزهای اعتبار"
|
"expireDays" = "روزهای اعتبار"
|
||||||
"days" = "(روز)"
|
"days" = "(روز)"
|
||||||
|
"renew" = "تمدید خودکار"
|
||||||
|
"renewDesc" = "روزهای تمدید خودکار پس از انقضا. 0 = غیرفعال"
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
[pages.inbounds.toasts]
|
||||||
"obtain" = "Obtain"
|
"obtain" = "Obtain"
|
||||||
@@ -207,7 +215,6 @@
|
|||||||
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
|
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
|
||||||
"panelConfig" = "تنظیمات پنل"
|
"panelConfig" = "تنظیمات پنل"
|
||||||
"userSettings" = "تنظیمات مدیر"
|
"userSettings" = "تنظیمات مدیر"
|
||||||
"xrayConfiguration" = "تنظیمات Xray"
|
|
||||||
"TGBotSettings" = "تنظیمات ربات تلگرام"
|
"TGBotSettings" = "تنظیمات ربات تلگرام"
|
||||||
"panelListeningIP" = "محدودیت آی پی پنل"
|
"panelListeningIP" = "محدودیت آی پی پنل"
|
||||||
"panelListeningIPDesc" = "برای استفاده از تمام آیپیها به طور پیش فرض خالی بگذارید"
|
"panelListeningIPDesc" = "برای استفاده از تمام آیپیها به طور پیش فرض خالی بگذارید"
|
||||||
@@ -271,8 +278,8 @@
|
|||||||
"subShowInfo" = "نمایش اطلاعات مصرف"
|
"subShowInfo" = "نمایش اطلاعات مصرف"
|
||||||
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در هر کانفیگ نمایش میدهد"
|
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در هر کانفیگ نمایش میدهد"
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.xray]
|
||||||
"title" = "الگوها"
|
"title" = "تنظیمات Xray"
|
||||||
"basicTemplate" = "بخش الگو پایه"
|
"basicTemplate" = "بخش الگو پایه"
|
||||||
"advancedTemplate" = "بخش الگو پیشرفته"
|
"advancedTemplate" = "بخش الگو پیشرفته"
|
||||||
"completeTemplate" = "بخش الگو کامل"
|
"completeTemplate" = "بخش الگو کامل"
|
||||||
@@ -286,54 +293,54 @@
|
|||||||
"directCountryConfigsDesc" = "این گزینه کاربران را به دامنه های کشوری خاص را به طور مستقیم، متصل می کند"
|
"directCountryConfigsDesc" = "این گزینه کاربران را به دامنه های کشوری خاص را به طور مستقیم، متصل می کند"
|
||||||
"ipv4Configs" = "تنظیمات برای IPv4"
|
"ipv4Configs" = "تنظیمات برای IPv4"
|
||||||
"ipv4ConfigsDesc" = "این گزینه فقط از طریق آیپی ورژن ۴ به دامنه های هدف هدایت می شود"
|
"ipv4ConfigsDesc" = "این گزینه فقط از طریق آیپی ورژن ۴ به دامنه های هدف هدایت می شود"
|
||||||
"xrayConfigTemplate" = "تنظیمات الگو ایکس ری"
|
"Template" = "تنظیمات الگو ایکس ری"
|
||||||
"xrayConfigTemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید!"
|
"TemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید!"
|
||||||
"xrayConfigFreedomStrategy" = "روش استفاده از شبکه خروجی مستقیم"
|
"FreedomStrategy" = "روش استفاده از شبکه خروجی مستقیم"
|
||||||
"xrayConfigFreedomStrategyDesc" = "تعیین روش استفاده از خروجی برای پرتکل مستقیم"
|
"FreedomStrategyDesc" = "تعیین روش استفاده از خروجی برای پرتکل مستقیم"
|
||||||
"xrayConfigRoutingStrategy" = "پیکربندی استراتژی حل دامنه در مسیریابی"
|
"RoutingStrategy" = "پیکربندی استراتژی حل دامنه در مسیریابی"
|
||||||
"xrayConfigRoutingStrategyDesc" = "تعیین استراتژی مسیریابی کلی برای پیدا کردن دامنه"
|
"RoutingStrategyDesc" = "تعیین استراتژی مسیریابی کلی برای پیدا کردن دامنه"
|
||||||
"xrayConfigTorrent" = "فیلتر کردن بیت تورنت"
|
"Torrent" = "فیلتر کردن بیت تورنت"
|
||||||
"xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد"
|
"TorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد"
|
||||||
"xrayConfigPrivateIp" = "جلوگیری از اتصال آیپی های خصوصی یا محلی"
|
"PrivateIp" = "جلوگیری از اتصال آیپی های خصوصی یا محلی"
|
||||||
"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های خصوصی یا محلی و بسته های سرگردان تغییر میدهد"
|
"PrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های خصوصی یا محلی و بسته های سرگردان تغییر میدهد"
|
||||||
"xrayConfigAds" = "مسدود کردن تبلیغات"
|
"Ads" = "مسدود کردن تبلیغات"
|
||||||
"xrayConfigAdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد"
|
"AdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد"
|
||||||
"xrayConfigFamily" = "فعال کردن حالت خانواده"
|
"Family" = "فعال کردن حالت خانواده"
|
||||||
"xrayConfigFamilyDesc" = "برای جلوگیری از ارتباط با وبسایت های ناامن"
|
"FamilyDesc" = "برای جلوگیری از ارتباط با وبسایت های ناامن"
|
||||||
"xrayConfigIRIp" = "جلوگیری از اتصال آیپی های ایران"
|
"IRIp" = "جلوگیری از اتصال آیپی های ایران"
|
||||||
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد"
|
"IRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد"
|
||||||
"xrayConfigIRDomain" = "جلوگیری از اتصال دامنه های ایران"
|
"IRDomain" = "جلوگیری از اتصال دامنه های ایران"
|
||||||
"xrayConfigIRDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد"
|
"IRDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد"
|
||||||
"xrayConfigChinaIp" = "جلوگیری از اتصال آیپی های چین"
|
"ChinaIp" = "جلوگیری از اتصال آیپی های چین"
|
||||||
"xrayConfigChinaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های چین تغییر میدهد"
|
"ChinaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های چین تغییر میدهد"
|
||||||
"xrayConfigChinaDomain" = "جلوگیری از اتصال دامنه های چین"
|
"ChinaDomain" = "جلوگیری از اتصال دامنه های چین"
|
||||||
"xrayConfigChinaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های چین تغییر میدهد"
|
"ChinaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های چین تغییر میدهد"
|
||||||
"xrayConfigRussiaIp" = "جلوگیری از اتصال آیپی های روسیه"
|
"RussiaIp" = "جلوگیری از اتصال آیپی های روسیه"
|
||||||
"xrayConfigRussiaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های روسیه تغییر میدهد"
|
"RussiaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های روسیه تغییر میدهد"
|
||||||
"xrayConfigRussiaDomain" = "جلوگیری از اتصال دامنه های روسیه"
|
"RussiaDomain" = "جلوگیری از اتصال دامنه های روسیه"
|
||||||
"xrayConfigRussiaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های روسیه تغییر میدهد"
|
"RussiaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های روسیه تغییر میدهد"
|
||||||
"xrayConfigDirectIRIp" = "ارتباط مستقیم به آیپی های ایران"
|
"DirectIRIp" = "ارتباط مستقیم به آیپی های ایران"
|
||||||
"xrayConfigDirectIRIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های ایران تغییر میدهد"
|
"DirectIRIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های ایران تغییر میدهد"
|
||||||
"xrayConfigDirectIRDomain" = "ارتباط مستقیم به دامنه های ایران"
|
"DirectIRDomain" = "ارتباط مستقیم به دامنه های ایران"
|
||||||
"xrayConfigDirectIRDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های ایران تغییر میدهد"
|
"DirectIRDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های ایران تغییر میدهد"
|
||||||
"xrayConfigDirectChinaIp" = "ارتباط مستقیم به آیپی های چین"
|
"DirectChinaIp" = "ارتباط مستقیم به آیپی های چین"
|
||||||
"xrayConfigDirectChinaIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های چین تغییر میدهد"
|
"DirectChinaIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های چین تغییر میدهد"
|
||||||
"xrayConfigDirectChinaDomain" = "ارتباط مستقیم به دامنه های چین"
|
"DirectChinaDomain" = "ارتباط مستقیم به دامنه های چین"
|
||||||
"xrayConfigDirectChinaDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های چین تغییر میدهد"
|
"DirectChinaDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های چین تغییر میدهد"
|
||||||
"xrayConfigDirectRussiaIp" = "ارتباط مستقیم به آیپی های روسیه"
|
"DirectRussiaIp" = "ارتباط مستقیم به آیپی های روسیه"
|
||||||
"xrayConfigDirectRussiaIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های روسیه تغییر میدهد"
|
"DirectRussiaIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های روسیه تغییر میدهد"
|
||||||
"xrayConfigDirectRussiaDomain" = "ارتباط مستقیم به دامنه های روسیه"
|
"DirectRussiaDomain" = "ارتباط مستقیم به دامنه های روسیه"
|
||||||
"xrayConfigDirectRussiaDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های روسیه تغییر میدهد"
|
"DirectRussiaDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های روسیه تغییر میدهد"
|
||||||
"xrayConfigGoogleIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به گوگل"
|
"GoogleIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به گوگل"
|
||||||
"xrayConfigGoogleIPv4Desc" = "مسیردهی جدید برای اتصال به گوگل با آیپی ورژن 4 اضافه میکند"
|
"GoogleIPv4Desc" = "مسیردهی جدید برای اتصال به گوگل با آیپی ورژن 4 اضافه میکند"
|
||||||
"xrayConfigNetflixIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به نتفلیکس"
|
"NetflixIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به نتفلیکس"
|
||||||
"xrayConfigNetflixIPv4Desc" = "مسیردهی جدید برای اتصال به نتفلیکس با آیپی ورژن 4 اضافه میکند"
|
"NetflixIPv4Desc" = "مسیردهی جدید برای اتصال به نتفلیکس با آیپی ورژن 4 اضافه میکند"
|
||||||
"xrayConfigInbounds" = "تنظیمات ورودی"
|
"Inbounds" = "تنظیمات ورودی"
|
||||||
"xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید"
|
"InboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید"
|
||||||
"xrayConfigOutbounds" = "تنظیمات خروجی"
|
"Outbounds" = "تنظیمات خروجی"
|
||||||
"xrayConfigOutboundsDesc" = "میتوانید الگوی تنظیمات را برای خروجی اینترنت تنظیم نمایید"
|
"OutboundsDesc" = "میتوانید الگوی تنظیمات را برای خروجی اینترنت تنظیم نمایید"
|
||||||
"xrayConfigRoutings" = "تنظیمات قوانین مسیریابی"
|
"Routings" = "تنظیمات قوانین مسیریابی"
|
||||||
"xrayConfigRoutingsDesc" = "میتوانید الگوی تنظیمات را برای مسیریابی تنظیم نمایید"
|
"RoutingsDesc" = "میتوانید الگوی تنظیمات را برای مسیریابی تنظیم نمایید"
|
||||||
"manualLists" = "لیست های دستی"
|
"manualLists" = "لیست های دستی"
|
||||||
"manualListsDesc" = "فرمت: JSON Array"
|
"manualListsDesc" = "فرمت: JSON Array"
|
||||||
"manualBlockedIPs" = "لیست آیپی های مسدود شده"
|
"manualBlockedIPs" = "لیست آیپی های مسدود شده"
|
||||||
@@ -397,12 +404,14 @@
|
|||||||
"expire" = "📅 تاریخ انقضا: {{ .DateTime }}\r\n \r\n"
|
"expire" = "📅 تاریخ انقضا: {{ .DateTime }}\r\n \r\n"
|
||||||
"expireIn" = "📅 باقیمانده از انقضا: {{ .Time }}\r\n \r\n"
|
"expireIn" = "📅 باقیمانده از انقضا: {{ .Time }}\r\n \r\n"
|
||||||
"active" = "💡 فعال: {{ .Enable }}\r\n"
|
"active" = "💡 فعال: {{ .Enable }}\r\n"
|
||||||
|
"online" = "🌐 وضعیت اتصال: {{ .Status }}\r\n"
|
||||||
"email" = "📧 ایمیل: {{ .Email }}\r\n"
|
"email" = "📧 ایمیل: {{ .Email }}\r\n"
|
||||||
"upload" = "🔼 آپلود↑: {{ .Upload }}\r\n"
|
"upload" = "🔼 آپلود↑: {{ .Upload }}\r\n"
|
||||||
"download" = "🔽 دانلود↓: {{ .Download }}\r\n"
|
"download" = "🔽 دانلود↓: {{ .Download }}\r\n"
|
||||||
"total" = "🔄 کل: {{ .UpDown }} / {{ .Total }}\r\n"
|
"total" = "🔄 کل: {{ .UpDown }} / {{ .Total }}\r\n"
|
||||||
"exhaustedMsg" = "🚨 {{ .Type }} به اتمام رسیده است:\r\n"
|
"exhaustedMsg" = "🚨 {{ .Type }} به اتمام رسیده است:\r\n"
|
||||||
"exhaustedCount" = "🚨 تعداد {{ .Type }} به اتمام رسیده:\r\n"
|
"exhaustedCount" = "🚨 تعداد {{ .Type }} به اتمام رسیده:\r\n"
|
||||||
|
"onlinesCount" = "🌐 تعداد کاربران آنلاین: {{ .Count }}\r\n"
|
||||||
"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n"
|
"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n"
|
||||||
"depleteSoon" = "🔜 به زودی به پایان خواهد رسید: {{ .Deplete }}\r\n \r\n"
|
"depleteSoon" = "🔜 به زودی به پایان خواهد رسید: {{ .Deplete }}\r\n \r\n"
|
||||||
"backupTime" = "🗄 زمان پشتیبانگیری: {{ .Time }}\r\n"
|
"backupTime" = "🗄 زمان پشتیبانگیری: {{ .Time }}\r\n"
|
||||||
@@ -415,6 +424,7 @@
|
|||||||
"getInbounds" = "دریافت ورودیها"
|
"getInbounds" = "دریافت ورودیها"
|
||||||
"depleteSoon" = "به زودی به پایان خواهد رسید"
|
"depleteSoon" = "به زودی به پایان خواهد رسید"
|
||||||
"clientUsage" = "دریافت آمار کاربر"
|
"clientUsage" = "دریافت آمار کاربر"
|
||||||
|
"onlines" = "کاربران آنلاین"
|
||||||
"commands" = "دستورات"
|
"commands" = "دستورات"
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"protocol" = "Протокол"
|
"protocol" = "Протокол"
|
||||||
"search" = "Поиск"
|
"search" = "Поиск"
|
||||||
"filter" = "Фильтр"
|
"filter" = "Фильтр"
|
||||||
"loading" = "Загрузка"
|
"loading" = "Загрузка..."
|
||||||
"second" = "Секунда"
|
"second" = "Секунда"
|
||||||
"minute" = "Минута"
|
"minute" = "Минута"
|
||||||
"hour" = "Час"
|
"hour" = "Час"
|
||||||
@@ -38,6 +38,8 @@
|
|||||||
"disabled" = "Отключено"
|
"disabled" = "Отключено"
|
||||||
"depleted" = "Отключены"
|
"depleted" = "Отключены"
|
||||||
"depletingSoon" = "Почти отключены"
|
"depletingSoon" = "Почти отключены"
|
||||||
|
"offline" = "Офлайн"
|
||||||
|
"online" = "Онлайн"
|
||||||
"domainName" = "Домен"
|
"domainName" = "Домен"
|
||||||
"monitor" = "Прослушиваемый IP"
|
"monitor" = "Прослушиваемый IP"
|
||||||
"certificate" = "Сертификат"
|
"certificate" = "Сертификат"
|
||||||
@@ -55,6 +57,7 @@
|
|||||||
"dashboard" = "Статус системы"
|
"dashboard" = "Статус системы"
|
||||||
"inbounds" = "Подключения"
|
"inbounds" = "Подключения"
|
||||||
"settings" = "Настройки"
|
"settings" = "Настройки"
|
||||||
|
"xray" = "Xray Настройки"
|
||||||
"logout" = "Выйти"
|
"logout" = "Выйти"
|
||||||
"link" = "Другое"
|
"link" = "Другое"
|
||||||
|
|
||||||
@@ -121,6 +124,8 @@
|
|||||||
"modifyInbound" = "Изменить данные"
|
"modifyInbound" = "Изменить данные"
|
||||||
"deleteInbound" = "Удалить подключение"
|
"deleteInbound" = "Удалить подключение"
|
||||||
"deleteInboundContent" = "Вы уверены, что хотите удалить подключение?"
|
"deleteInboundContent" = "Вы уверены, что хотите удалить подключение?"
|
||||||
|
"deleteClient" = "Удалить клиента"
|
||||||
|
"deleteClientContent" = "Вы уверены, что хотите удалить клиента?"
|
||||||
"resetTrafficContent" = "Подтвердите обнуление трафика?"
|
"resetTrafficContent" = "Подтвердите обнуление трафика?"
|
||||||
"copyLink" = "Копировать ключ"
|
"copyLink" = "Копировать ключ"
|
||||||
"address" = "Адрес"
|
"address" = "Адрес"
|
||||||
@@ -132,8 +137,8 @@
|
|||||||
"totalFlow" = "Общий расход"
|
"totalFlow" = "Общий расход"
|
||||||
"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы сделать бессрочно"
|
"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы сделать бессрочно"
|
||||||
"noRecommendKeepDefault" = "Нет особых требований для сохранения настроек по умолчанию"
|
"noRecommendKeepDefault" = "Нет особых требований для сохранения настроек по умолчанию"
|
||||||
"certificatePath" = "Путь файла сертификата"
|
"certificatePath" = "Путь файла"
|
||||||
"certificateContent" = "Содержимое файла сертификата"
|
"certificateContent" = "Содержимое файла"
|
||||||
"publicKeyPath" = "Путь к публичному ключу"
|
"publicKeyPath" = "Путь к публичному ключу"
|
||||||
"publicKeyContent" = "Содержимое публичного ключа"
|
"publicKeyContent" = "Содержимое публичного ключа"
|
||||||
"keyPath" = "Путь к приватному ключу"
|
"keyPath" = "Путь к приватному ключу"
|
||||||
@@ -162,6 +167,7 @@
|
|||||||
"setDefaultCert" = "Установить сертификат с панели"
|
"setDefaultCert" = "Установить сертификат с панели"
|
||||||
"telegramDesc" = "Используйте идентификатор Telegram без символа @ или идентификатора чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)"
|
"telegramDesc" = "Используйте идентификатор Telegram без символа @ или идентификатора чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)"
|
||||||
"subscriptionDesc" = "вы можете найти свою ссылку подписки в разделе «Подробнее», также вы можете использовать одно и то же имя для нескольких конфигов"
|
"subscriptionDesc" = "вы можете найти свою ссылку подписки в разделе «Подробнее», также вы можете использовать одно и то же имя для нескольких конфигов"
|
||||||
|
"info" = "Информация"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Добавить клиента"
|
"add" = "Добавить клиента"
|
||||||
@@ -178,6 +184,8 @@
|
|||||||
"delayedStart" = "Начать со времени первого подключения"
|
"delayedStart" = "Начать со времени первого подключения"
|
||||||
"expireDays" = "Срок действия"
|
"expireDays" = "Срок действия"
|
||||||
"days" = "дней"
|
"days" = "дней"
|
||||||
|
"renew" = "Автопродление"
|
||||||
|
"renewDesc" = "Автоматическое продление через несколько дней после истечения срока действия. 0 = отключить"
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
[pages.inbounds.toasts]
|
||||||
"obtain" = "Получить"
|
"obtain" = "Получить"
|
||||||
@@ -208,7 +216,6 @@
|
|||||||
"resetDefaultConfig" = "Сбросить всё по-умолчанию"
|
"resetDefaultConfig" = "Сбросить всё по-умолчанию"
|
||||||
"panelConfig" = "Настройки панели"
|
"panelConfig" = "Настройки панели"
|
||||||
"userSettings" = "Настройки безопасности"
|
"userSettings" = "Настройки безопасности"
|
||||||
"xrayConfiguration" = "Конфигурация Xray"
|
|
||||||
"TGBotSettings" = "Настройки Телеграм-бота"
|
"TGBotSettings" = "Настройки Телеграм-бота"
|
||||||
"panelListeningIP" = "IP-адрес прослушивания панели"
|
"panelListeningIP" = "IP-адрес прослушивания панели"
|
||||||
"panelListeningIPDesc" = "Оставьте пустым, чтобы прослушивать все IP-адреса."
|
"panelListeningIPDesc" = "Оставьте пустым, чтобы прослушивать все IP-адреса."
|
||||||
@@ -272,8 +279,8 @@
|
|||||||
"subShowInfo" = "Показать информацию об использовании"
|
"subShowInfo" = "Показать информацию об использовании"
|
||||||
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.xray]
|
||||||
"title" = "Шаблоны"
|
"title" = "Xray Настройки"
|
||||||
"basicTemplate" = "Базовые шаблоны"
|
"basicTemplate" = "Базовые шаблоны"
|
||||||
"advancedTemplate" = "Расширенные шаблоны"
|
"advancedTemplate" = "Расширенные шаблоны"
|
||||||
"completeTemplate" = "Итоговый шаблон"
|
"completeTemplate" = "Итоговый шаблон"
|
||||||
@@ -287,54 +294,54 @@
|
|||||||
"directCountryConfigsDesc" = "Эти параметры будут подключать пользователей напрямую к доменам определенной страны."
|
"directCountryConfigsDesc" = "Эти параметры будут подключать пользователей напрямую к доменам определенной страны."
|
||||||
"ipv4Configs" = "Настройки IPv4"
|
"ipv4Configs" = "Настройки IPv4"
|
||||||
"ipv4ConfigsDesc" = "Эти параметры будут маршрутизироваться к целевым доменам только через IPv4"
|
"ipv4ConfigsDesc" = "Эти параметры будут маршрутизироваться к целевым доменам только через IPv4"
|
||||||
"xrayConfigTemplate" = "Шаблон конфигурации Xray"
|
"Template" = "Шаблон конфигурации Xray"
|
||||||
"xrayConfigTemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона."
|
"TemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона."
|
||||||
"xrayConfigFreedomStrategy" = "Настроить стратегию протокола Freedom"
|
"FreedomStrategy" = "Настроить стратегию протокола Freedom"
|
||||||
"xrayConfigFreedomStrategyDesc" = "Установить стратегию вывода сети в протоколе Freedom"
|
"FreedomStrategyDesc" = "Установить стратегию вывода сети в протоколе Freedom"
|
||||||
"xrayConfigRoutingStrategy" = "Настроить доменную стратегию маршрутизации"
|
"RoutingStrategy" = "Настроить доменную стратегию маршрутизации"
|
||||||
"xrayConfigRoutingStrategyDesc" = "Установить общую стратегию маршрутизации разрешения DNS"
|
"RoutingStrategyDesc" = "Установить общую стратегию маршрутизации разрешения DNS"
|
||||||
"xrayConfigTorrent" = "Запретить использование BitTorrent"
|
"Torrent" = "Запретить использование BitTorrent"
|
||||||
"xrayConfigTorrentDesc" = "Измените конфигурацию, чтобы пользователи не использовали BitTorrent."
|
"TorrentDesc" = "Измените конфигурацию, чтобы пользователи не использовали BitTorrent."
|
||||||
"xrayConfigPrivateIp" = "Запрет частных диапазонов IP-адресов для подключения"
|
"PrivateIp" = "Запрет частных диапазонов IP-адресов для подключения"
|
||||||
"xrayConfigPrivateIpDesc" = "Измените конфигурацию, чтобы избежать подключения к диапазонам частных IP-адресов."
|
"PrivateIpDesc" = "Измените конфигурацию, чтобы избежать подключения к диапазонам частных IP-адресов."
|
||||||
"xrayConfigAds" = "Блокировка рекламы"
|
"Ads" = "Блокировка рекламы"
|
||||||
"xrayConfigAdsDesc" = "Измените конфигурацию, чтобы заблокировать рекламу."
|
"AdsDesc" = "Измените конфигурацию, чтобы заблокировать рекламу."
|
||||||
"xrayConfigFamily" = "Включить семейную конфигурацию"
|
"Family" = "Включить семейную конфигурацию"
|
||||||
"xrayConfigFamilyDesc" = "Избегать подключения к небезопасным веб-сайтам для всей семьи"
|
"FamilyDesc" = "Избегать подключения к небезопасным веб-сайтам для всей семьи"
|
||||||
"xrayConfigIRIp" = "Отключить подключение к диапазонам IP-адресов Ирана"
|
"IRIp" = "Отключить подключение к диапазонам IP-адресов Ирана"
|
||||||
"xrayConfigIRIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Ирана."
|
"IRIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Ирана."
|
||||||
"xrayConfigIRDomain" = "Отключить подключение к доменам Ирана"
|
"IRDomain" = "Отключить подключение к доменам Ирана"
|
||||||
"xrayConfigIRDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Ирана."
|
"IRDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Ирана."
|
||||||
"xrayConfigChinaIp" = "Отключить подключение к диапазонам IP-адресов Китая"
|
"ChinaIp" = "Отключить подключение к диапазонам IP-адресов Китая"
|
||||||
"xrayConfigChinaIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Китая."
|
"ChinaIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Китая."
|
||||||
"xrayConfigChinaDomain" = "Отключить подключение к доменам Китая"
|
"ChinaDomain" = "Отключить подключение к доменам Китая"
|
||||||
"xrayConfigChinaDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Китая."
|
"ChinaDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Китая."
|
||||||
"xrayConfigRussiaIp" = "Отключить подключение к диапазонам IP-адресов России"
|
"RussiaIp" = "Отключить подключение к диапазонам IP-адресов России"
|
||||||
"xrayConfigRussiaIpDesc" = "Измените конфигурацию, чтобы отключить соединения с диапазонами IP-адресов России."
|
"RussiaIpDesc" = "Измените конфигурацию, чтобы отключить соединения с диапазонами IP-адресов России."
|
||||||
"xrayConfigRussiaDomain" = "Отключить подключение к доменам России"
|
"RussiaDomain" = "Отключить подключение к доменам России"
|
||||||
"xrayConfigRussiaDomainDesc" = "Измените конфигурацию, чтобы избежать подключения к доменам России."
|
"RussiaDomainDesc" = "Измените конфигурацию, чтобы избежать подключения к доменам России."
|
||||||
"xrayConfigDirectIRIp" = "Прямое подключение к диапазонам IP-адресов Ирана"
|
"DirectIRIp" = "Прямое подключение к диапазонам IP-адресов Ирана"
|
||||||
"xrayConfigDirectIRIpDesc" = "Измените шаблон конфигурации для прямого подключения к диапазонам IP-адресов Ирана"
|
"DirectIRIpDesc" = "Измените шаблон конфигурации для прямого подключения к диапазонам IP-адресов Ирана"
|
||||||
"xrayConfigDirectIRDomain" = "Прямое подключение к доменам Ирана"
|
"DirectIRDomain" = "Прямое подключение к доменам Ирана"
|
||||||
"xrayConfigDirectIRDomainDesc" = "Измените шаблон конфигурации для прямого подключения к доменам Ирана"
|
"DirectIRDomainDesc" = "Измените шаблон конфигурации для прямого подключения к доменам Ирана"
|
||||||
"xrayConfigDirectChinaIp" = "Прямое подключение к диапазонам IP-адресов Китая"
|
"DirectChinaIp" = "Прямое подключение к диапазонам IP-адресов Китая"
|
||||||
"xrayConfigDirectChinaIpDesc" = "Измените шаблон конфигурации для прямого подключения к диапазонам IP-адресов Китая"
|
"DirectChinaIpDesc" = "Измените шаблон конфигурации для прямого подключения к диапазонам IP-адресов Китая"
|
||||||
"xrayConfigDirectChinaDomain" = "Прямое подключение к доменам Китая"
|
"DirectChinaDomain" = "Прямое подключение к доменам Китая"
|
||||||
"xrayConfigDirectChinaDomainDesc" = "Измените шаблон конфигурации для прямого подключения к доменам Китая"
|
"DirectChinaDomainDesc" = "Измените шаблон конфигурации для прямого подключения к доменам Китая"
|
||||||
"xrayConfigDirectRussiaIp" = "Прямое подключение к диапазонам IP-адресов России"
|
"DirectRussiaIp" = "Прямое подключение к диапазонам IP-адресов России"
|
||||||
"xrayConfigDirectRussiaIpDesc" = "Изменить шаблон конфигурации для прямого подключения к диапазонам IP-адресов России"
|
"DirectRussiaIpDesc" = "Изменить шаблон конфигурации для прямого подключения к диапазонам IP-адресов России"
|
||||||
"xrayConfigDirectRussiaDomain" = "Прямое подключение к доменам России"
|
"DirectRussiaDomain" = "Прямое подключение к доменам России"
|
||||||
"xrayConfigDirectRussiaDomainDesc" = "Изменить шаблон конфигурации для прямого подключения к доменам России"
|
"DirectRussiaDomainDesc" = "Изменить шаблон конфигурации для прямого подключения к доменам России"
|
||||||
"xrayConfigGoogleIPv4" = "Использовать IPv4 для Google"
|
"GoogleIPv4" = "Использовать IPv4 для Google"
|
||||||
"xrayConfigGoogleIPv4Desc" = "Применить маршрутизацию Google для подключения к IPv4."
|
"GoogleIPv4Desc" = "Применить маршрутизацию Google для подключения к IPv4."
|
||||||
"xrayConfigNetflixIPv4" = "Использовать IPv4 для Netflix"
|
"NetflixIPv4" = "Использовать IPv4 для Netflix"
|
||||||
"xrayConfigNetflixIPv4Desc" = "Применить маршрутизацию Netflix для подключения к IPv4."
|
"NetflixIPv4Desc" = "Применить маршрутизацию Netflix для подключения к IPv4."
|
||||||
"xrayConfigInbounds" = "Конфигурация подключений"
|
"Inbounds" = "Конфигурация подключений"
|
||||||
"xrayConfigInboundsDesc" = "Изменение шаблона конфигурации, для подключения определенных пользователей."
|
"InboundsDesc" = "Изменение шаблона конфигурации, для подключения определенных пользователей."
|
||||||
"xrayConfigOutbounds" = "Конфигурация исходящих"
|
"Outbounds" = "Конфигурация исходящих"
|
||||||
"xrayConfigOutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера."
|
"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера."
|
||||||
"xrayConfigRoutings" = "Настройка правил маршрутизации"
|
"Routings" = "Настройка правил маршрутизации"
|
||||||
"xrayConfigRoutingsDesc" = "Изменение шаблона конфигурации, для определения правил маршрутизации для этого сервера."
|
"RoutingsDesc" = "Изменение шаблона конфигурации, для определения правил маршрутизации для этого сервера."
|
||||||
"manualLists" = "Пользовательские списки"
|
"manualLists" = "Пользовательские списки"
|
||||||
"manualListsDesc" = "Пожалуйста, используйте формат массива JSON"
|
"manualListsDesc" = "Пожалуйста, используйте формат массива JSON"
|
||||||
"manualBlockedIPs" = "Список заблокированных IP-адресов"
|
"manualBlockedIPs" = "Список заблокированных IP-адресов"
|
||||||
@@ -398,12 +405,14 @@
|
|||||||
"expire" = "📅 Дата окончания: {{ .DateTime }}\r\n \r\n"
|
"expire" = "📅 Дата окончания: {{ .DateTime }}\r\n \r\n"
|
||||||
"expireIn" = "📅 Окончание через: {{ .Time }}\r\n \r\n"
|
"expireIn" = "📅 Окончание через: {{ .Time }}\r\n \r\n"
|
||||||
"active" = "💡 Активен: {{ .Enable }}\r\n"
|
"active" = "💡 Активен: {{ .Enable }}\r\n"
|
||||||
|
"online" = "🌐 Статус соединения: {{ .Status }}\r\n"
|
||||||
"email" = "📧 Email: {{ .Email }}\r\n"
|
"email" = "📧 Email: {{ .Email }}\r\n"
|
||||||
"upload" = "🔼 Загрузка↑: {{ .Upload }}\r\n"
|
"upload" = "🔼 Загрузка↑: {{ .Upload }}\r\n"
|
||||||
"download" = "🔽 Скачивание↓: {{ .Download }}\r\n"
|
"download" = "🔽 Скачивание↓: {{ .Download }}\r\n"
|
||||||
"total" = "🔄 Всего: {{ .UpDown }} / {{ .Total }}\r\n"
|
"total" = "🔄 Всего: {{ .UpDown }} / {{ .Total }}\r\n"
|
||||||
"exhaustedMsg" = "🚨 Истекли {{ .Type }}:\r\n"
|
"exhaustedMsg" = "🚨 Истекли {{ .Type }}:\r\n"
|
||||||
"exhaustedCount" = "🚨 Количество истекших {{ .Type }}:\r\n"
|
"exhaustedCount" = "🚨 Количество истекших {{ .Type }}:\r\n"
|
||||||
|
"onlinesCount" = "🌐 Количество онлайн-клиентов: {{ .Count }}\r\n"
|
||||||
"disabled" = "🛑 Отключено: {{ .Disabled }}\r\n"
|
"disabled" = "🛑 Отключено: {{ .Disabled }}\r\n"
|
||||||
"depleteSoon" = "🔜 Скоро отключатся: {{ .Deplete }}\r\n \r\n"
|
"depleteSoon" = "🔜 Скоро отключатся: {{ .Deplete }}\r\n \r\n"
|
||||||
"backupTime" = "🗄 Время резервного копирования: {{ .Time }}\r\n"
|
"backupTime" = "🗄 Время резервного копирования: {{ .Time }}\r\n"
|
||||||
@@ -416,6 +425,7 @@
|
|||||||
"getInbounds" = "Получить список подключений"
|
"getInbounds" = "Получить список подключений"
|
||||||
"depleteSoon" = "Скоро отключатся"
|
"depleteSoon" = "Скоро отключатся"
|
||||||
"clientUsage" = "Получить статистику"
|
"clientUsage" = "Получить статистику"
|
||||||
|
"onlines" = "Онлайн-клиенты"
|
||||||
"commands" = "Команды"
|
"commands" = "Команды"
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"protocol" = "协议"
|
"protocol" = "协议"
|
||||||
"search" = "搜尋"
|
"search" = "搜尋"
|
||||||
"filter" = "过滤器"
|
"filter" = "过滤器"
|
||||||
"loading" = "加载中"
|
"loading" = "加载中..."
|
||||||
"second" = "秒"
|
"second" = "秒"
|
||||||
"minute" = "分钟"
|
"minute" = "分钟"
|
||||||
"hour" = "小时"
|
"hour" = "小时"
|
||||||
@@ -38,6 +38,8 @@
|
|||||||
"disabled" = "关闭"
|
"disabled" = "关闭"
|
||||||
"depleted" = "耗尽"
|
"depleted" = "耗尽"
|
||||||
"depletingSoon" = "即将耗尽"
|
"depletingSoon" = "即将耗尽"
|
||||||
|
"offline" = "离线"
|
||||||
|
"online" = "在线"
|
||||||
"domainName" = "域名"
|
"domainName" = "域名"
|
||||||
"monitor" = "监听"
|
"monitor" = "监听"
|
||||||
"certificate" = "证书"
|
"certificate" = "证书"
|
||||||
@@ -55,6 +57,7 @@
|
|||||||
"dashboard" = "系统状态"
|
"dashboard" = "系统状态"
|
||||||
"inbounds" = "入站列表"
|
"inbounds" = "入站列表"
|
||||||
"settings" = "面板设置"
|
"settings" = "面板设置"
|
||||||
|
"xray" = "Xray 设置"
|
||||||
"logout" = "退出登录"
|
"logout" = "退出登录"
|
||||||
"link" = "其他"
|
"link" = "其他"
|
||||||
|
|
||||||
@@ -121,6 +124,8 @@
|
|||||||
"modifyInbound" = "修改入站"
|
"modifyInbound" = "修改入站"
|
||||||
"deleteInbound" = "删除入站"
|
"deleteInbound" = "删除入站"
|
||||||
"deleteInboundContent" = "确定要删除入站吗?"
|
"deleteInboundContent" = "确定要删除入站吗?"
|
||||||
|
"deleteClient" = "删除客户端"
|
||||||
|
"deleteClientContent" = "您确定要删除客户端吗?"
|
||||||
"resetTrafficContent" = "确定要重置流量吗?"
|
"resetTrafficContent" = "确定要重置流量吗?"
|
||||||
"copyLink" = "复制链接"
|
"copyLink" = "复制链接"
|
||||||
"address" = "地址"
|
"address" = "地址"
|
||||||
@@ -132,8 +137,8 @@
|
|||||||
"totalFlow" = "总流量"
|
"totalFlow" = "总流量"
|
||||||
"leaveBlankToNeverExpire" = "留空则永不到期"
|
"leaveBlankToNeverExpire" = "留空则永不到期"
|
||||||
"noRecommendKeepDefault" = "没有特殊需求保持默认即可"
|
"noRecommendKeepDefault" = "没有特殊需求保持默认即可"
|
||||||
"certificatePath" = "证书文件路径"
|
"certificatePath" = "文件路径"
|
||||||
"certificateContent" = "证书文件内容"
|
"certificateContent" = "文件内容"
|
||||||
"publicKeyPath" = "公钥文件路径"
|
"publicKeyPath" = "公钥文件路径"
|
||||||
"publicKeyContent" = "公钥内容"
|
"publicKeyContent" = "公钥内容"
|
||||||
"keyPath" = "密钥文件路径"
|
"keyPath" = "密钥文件路径"
|
||||||
@@ -162,6 +167,7 @@
|
|||||||
"setDefaultCert" = "从面板设置证书"
|
"setDefaultCert" = "从面板设置证书"
|
||||||
"telegramDesc" = "使用 Telegram ID,不包含 @ 符号或聊天 ID(可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)"
|
"telegramDesc" = "使用 Telegram ID,不包含 @ 符号或聊天 ID(可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)"
|
||||||
"subscriptionDesc" = "您可以在详细信息上找到您的子链接,也可以对多个配置使用相同的名称"
|
"subscriptionDesc" = "您可以在详细信息上找到您的子链接,也可以对多个配置使用相同的名称"
|
||||||
|
"info" = "信息"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "添加客户端"
|
"add" = "添加客户端"
|
||||||
@@ -178,6 +184,8 @@
|
|||||||
"delayedStart" = "首次使用后开始"
|
"delayedStart" = "首次使用后开始"
|
||||||
"expireDays" = "过期天数"
|
"expireDays" = "过期天数"
|
||||||
"days" = "天"
|
"days" = "天"
|
||||||
|
"renew" = "自动续订"
|
||||||
|
"renewDesc" = "过期后自动续订。0 = 禁用"
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
[pages.inbounds.toasts]
|
||||||
"obtain" = "获取"
|
"obtain" = "获取"
|
||||||
@@ -208,7 +216,6 @@
|
|||||||
"resetDefaultConfig" = "重置为默认配置"
|
"resetDefaultConfig" = "重置为默认配置"
|
||||||
"panelConfig" = "面板配置"
|
"panelConfig" = "面板配置"
|
||||||
"userSettings" = "用户设置"
|
"userSettings" = "用户设置"
|
||||||
"xrayConfiguration" = "Xray 相关设置"
|
|
||||||
"TGBotSettings" = "TG提醒相关设置"
|
"TGBotSettings" = "TG提醒相关设置"
|
||||||
"panelListeningIP" = "面板监听 IP"
|
"panelListeningIP" = "面板监听 IP"
|
||||||
"panelListeningIPDesc" = "默认留空监听所有 IP"
|
"panelListeningIPDesc" = "默认留空监听所有 IP"
|
||||||
@@ -273,7 +280,7 @@
|
|||||||
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.settings.templates]
|
||||||
"title" = "模板"
|
"title" = "Xray 设置"
|
||||||
"basicTemplate" = "基本模板"
|
"basicTemplate" = "基本模板"
|
||||||
"advancedTemplate" = "高级模板部件"
|
"advancedTemplate" = "高级模板部件"
|
||||||
"completeTemplate" = "Xray 配置的完整模板"
|
"completeTemplate" = "Xray 配置的完整模板"
|
||||||
@@ -287,54 +294,54 @@
|
|||||||
"directCountryConfigsDesc" = "这些选项会将用户直接连接到特定国家/地区的域。"
|
"directCountryConfigsDesc" = "这些选项会将用户直接连接到特定国家/地区的域。"
|
||||||
"ipv4Configs" = "IPv4 配置"
|
"ipv4Configs" = "IPv4 配置"
|
||||||
"ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域"
|
"ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域"
|
||||||
"xrayConfigTemplate" = "Xray 配置模板"
|
"Template" = "Xray 配置模板"
|
||||||
"xrayConfigTemplateDesc" = "以该模型为基础生成最终的Xray配置文件,重新启动面板生成效率"
|
"TemplateDesc" = "以该模型为基础生成最终的Xray配置文件,重新启动面板生成效率"
|
||||||
"xrayConfigFreedomStrategy" = "配置自由协议的策略"
|
"FreedomStrategy" = "配置自由协议的策略"
|
||||||
"xrayConfigFreedomStrategyDesc" = "在自由协议中设置网络输出策略"
|
"FreedomStrategyDesc" = "在自由协议中设置网络输出策略"
|
||||||
"xrayConfigRoutingStrategy" = "配置路由域策略"
|
"RoutingStrategy" = "配置路由域策略"
|
||||||
"xrayConfigRoutingStrategyDesc" = "设置DNS解析的整体路由策略"
|
"RoutingStrategyDesc" = "设置DNS解析的整体路由策略"
|
||||||
"xrayConfigTorrent" = "禁止使用 bittorrent"
|
"Torrent" = "禁止使用 bittorrent"
|
||||||
"xrayConfigTorrentDesc" = "更改配置模板避免用户使用bittorrent"
|
"TorrentDesc" = "更改配置模板避免用户使用bittorrent"
|
||||||
"xrayConfigPrivateIp" = "禁止私人 IP 范围连接"
|
"PrivateIp" = "禁止私人 IP 范围连接"
|
||||||
"xrayConfigPrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围"
|
"PrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围"
|
||||||
"xrayConfigAds" = "屏蔽广告"
|
"Ads" = "屏蔽广告"
|
||||||
"xrayConfigAdsDesc" = "修改配置模板屏蔽广告"
|
"AdsDesc" = "修改配置模板屏蔽广告"
|
||||||
"xrayConfigFamily" = "启用家庭友好配置"
|
"Family" = "启用家庭友好配置"
|
||||||
"xrayConfigFamilyDesc" = "避免为家人连接到不安全的网站"
|
"FamilyDesc" = "避免为家人连接到不安全的网站"
|
||||||
"xrayConfigIRIp" = "禁止伊朗 IP 范围连接"
|
"IRIp" = "禁止伊朗 IP 范围连接"
|
||||||
"xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP段"
|
"IRIpDesc" = "修改配置模板避免连接伊朗IP段"
|
||||||
"xrayConfigIRDomain" = "禁止伊朗域连接"
|
"IRDomain" = "禁止伊朗域连接"
|
||||||
"xrayConfigIRDomainDesc" = "更改配置模板避免连接伊朗域名"
|
"IRDomainDesc" = "更改配置模板避免连接伊朗域名"
|
||||||
"xrayConfigChinaIp" = "禁止中国 IP 范围连接"
|
"ChinaIp" = "禁止中国 IP 范围连接"
|
||||||
"xrayConfigChinaIpDesc" = "修改配置模板避免连接中国IP段"
|
"ChinaIpDesc" = "修改配置模板避免连接中国IP段"
|
||||||
"xrayConfigChinaDomain" = "禁止中国域名连接"
|
"ChinaDomain" = "禁止中国域名连接"
|
||||||
"xrayConfigChinaDomainDesc" = "更改配置模板避免连接中国域"
|
"ChinaDomainDesc" = "更改配置模板避免连接中国域"
|
||||||
"xrayConfigRussiaIp" = "禁止俄罗斯 IP 范围连接"
|
"RussiaIp" = "禁止俄罗斯 IP 范围连接"
|
||||||
"xrayConfigRussiaIpDesc" = "修改配置模板避免连接俄罗斯IP范围"
|
"RussiaIpDesc" = "修改配置模板避免连接俄罗斯IP范围"
|
||||||
"xrayConfigRussiaDomain" = "禁止俄罗斯域连接"
|
"RussiaDomain" = "禁止俄罗斯域连接"
|
||||||
"xrayConfigRussiaDomainDesc" = "更改配置模板避免连接俄罗斯域"
|
"RussiaDomainDesc" = "更改配置模板避免连接俄罗斯域"
|
||||||
"xrayConfigDirectIRIp" = "直接连接到伊朗 IP 范围"
|
"DirectIRIp" = "直接连接到伊朗 IP 范围"
|
||||||
"xrayConfigDirectIRIpDesc" = "更改直接连接到伊朗 IP 范围的配置模板"
|
"DirectIRIpDesc" = "更改直接连接到伊朗 IP 范围的配置模板"
|
||||||
"xrayConfigDirectIRDomain" = "直接连接到伊朗域"
|
"DirectIRDomain" = "直接连接到伊朗域"
|
||||||
"xrayConfigDirectIRDomainDesc" = "更改直接连接到伊朗域的配置模板"
|
"DirectIRDomainDesc" = "更改直接连接到伊朗域的配置模板"
|
||||||
"xrayConfigDirectChinaIp" = "直连中国IP范围"
|
"DirectChinaIp" = "直连中国IP范围"
|
||||||
"xrayConfigDirectChinaIpDesc" = "更改直连中国 IP 范围的配置模板"
|
"DirectChinaIpDesc" = "更改直连中国 IP 范围的配置模板"
|
||||||
"xrayConfigDirectChinaDomain" = "直连中国域名"
|
"DirectChinaDomain" = "直连中国域名"
|
||||||
"xrayConfigDirectChinaDomainDesc" = "修改中国域名直连配置模板"
|
"DirectChinaDomainDesc" = "修改中国域名直连配置模板"
|
||||||
"xrayConfigDirectRussiaIp" = "直接连接到俄罗斯 IP 范围"
|
"DirectRussiaIp" = "直接连接到俄罗斯 IP 范围"
|
||||||
"xrayConfigDirectRussiaIpDesc" = "更改直接连接到俄罗斯 IP 范围的配置模板"
|
"DirectRussiaIpDesc" = "更改直接连接到俄罗斯 IP 范围的配置模板"
|
||||||
"xrayConfigDirectRussiaDomain" = "直接连接到俄罗斯域"
|
"DirectRussiaDomain" = "直接连接到俄罗斯域"
|
||||||
"xrayConfigDirectRussiaDomainDesc" = "更改直接连接到俄罗斯域的配置模板"
|
"DirectRussiaDomainDesc" = "更改直接连接到俄罗斯域的配置模板"
|
||||||
"xrayConfigGoogleIPv4" = "为谷歌使用 IPv4"
|
"GoogleIPv4" = "为谷歌使用 IPv4"
|
||||||
"xrayConfigGoogleIPv4Desc" = "添加谷歌连接IPv4的路由"
|
"GoogleIPv4Desc" = "添加谷歌连接IPv4的路由"
|
||||||
"xrayConfigNetflixIPv4" = "为 Netflix 使用 IPv4"
|
"NetflixIPv4" = "为 Netflix 使用 IPv4"
|
||||||
"xrayConfigNetflixIPv4Desc" = "添加Netflix连接IPv4的路由"
|
"NetflixIPv4Desc" = "添加Netflix连接IPv4的路由"
|
||||||
"xrayConfigInbounds" = "入站配置"
|
"Inbounds" = "入站配置"
|
||||||
"xrayConfigInboundsDesc" = "更改配置模板接受特殊客户端"
|
"InboundsDesc" = "更改配置模板接受特殊客户端"
|
||||||
"xrayConfigOutbounds" = "出站配置"
|
"Outbounds" = "出站配置"
|
||||||
"xrayConfigOutboundsDesc" = "更改配置模板定义此服务器的传出方式"
|
"OutboundsDesc" = "更改配置模板定义此服务器的传出方式"
|
||||||
"xrayConfigRoutings" = "路由规则配置"
|
"Routings" = "路由规则配置"
|
||||||
"xrayConfigRoutingsDesc" = "更改配置模板为该服务器定义路由规则"
|
"RoutingsDesc" = "更改配置模板为该服务器定义路由规则"
|
||||||
"manualLists" = "手动列表"
|
"manualLists" = "手动列表"
|
||||||
"manualListsDesc" = "请使用 JSON 数组格式"
|
"manualListsDesc" = "请使用 JSON 数组格式"
|
||||||
"manualBlockedIPs" = "被阻止的 IP 列表"
|
"manualBlockedIPs" = "被阻止的 IP 列表"
|
||||||
@@ -398,12 +405,14 @@
|
|||||||
"expire" = "📅 过期日期:{{ .DateTime }}\r\n \r\n"
|
"expire" = "📅 过期日期:{{ .DateTime }}\r\n \r\n"
|
||||||
"expireIn" = "📅 剩余时间:{{ .Time }}\r\n \r\n"
|
"expireIn" = "📅 剩余时间:{{ .Time }}\r\n \r\n"
|
||||||
"active" = "💡 激活:{{ .Enable }}\r\n"
|
"active" = "💡 激活:{{ .Enable }}\r\n"
|
||||||
|
"online" = "🌐 连接状态: {{ .Status }}\r\n"
|
||||||
"email" = "📧 邮箱:{{ .Email }}\r\n"
|
"email" = "📧 邮箱:{{ .Email }}\r\n"
|
||||||
"upload" = "🔼 上传↑:{{ .Upload }}\r\n"
|
"upload" = "🔼 上传↑:{{ .Upload }}\r\n"
|
||||||
"download" = "🔽 下载↓:{{ .Download }}\r\n"
|
"download" = "🔽 下载↓:{{ .Download }}\r\n"
|
||||||
"total" = "🔄 总计:{{ .UpDown }} / {{ .Total }}\r\n"
|
"total" = "🔄 总计:{{ .UpDown }} / {{ .Total }}\r\n"
|
||||||
"exhaustedMsg" = "🚨 耗尽的{{ .Type }}:\r\n"
|
"exhaustedMsg" = "🚨 耗尽的{{ .Type }}:\r\n"
|
||||||
"exhaustedCount" = "🚨 耗尽的{{ .Type }}数量:\r\n"
|
"exhaustedCount" = "🚨 耗尽的{{ .Type }}数量:\r\n"
|
||||||
|
"onlinesCount" = "🌐 Количество онлайн-клиентов: {{ .Count }}\r\n"
|
||||||
"disabled" = "🛑 禁用:{{ .Disabled }}\r\n"
|
"disabled" = "🛑 禁用:{{ .Disabled }}\r\n"
|
||||||
"depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n \r\n"
|
"depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n \r\n"
|
||||||
"backupTime" = "🗄 备份时间:{{ .Time }}\r\n"
|
"backupTime" = "🗄 备份时间:{{ .Time }}\r\n"
|
||||||
@@ -416,6 +425,7 @@
|
|||||||
"getInbounds" = "获取入站信息"
|
"getInbounds" = "获取入站信息"
|
||||||
"depleteSoon" = "即将耗尽"
|
"depleteSoon" = "即将耗尽"
|
||||||
"clientUsage" = "获取使用情况"
|
"clientUsage" = "获取使用情况"
|
||||||
|
"onlineUsers" = "在线客户"
|
||||||
"commands" = "命令"
|
"commands" = "命令"
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
|
|||||||
@@ -354,7 +354,7 @@ func (s *Server) Start() (err error) {
|
|||||||
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
||||||
if (err == nil) && (isTgbotenabled) {
|
if (err == nil) && (isTgbotenabled) {
|
||||||
tgBot := s.tgbotService.NewTgbot()
|
tgBot := s.tgbotService.NewTgbot()
|
||||||
tgBot.Start(i18nFS)
|
go tgBot.Start(i18nFS)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ type ClientTraffic struct {
|
|||||||
Down int64 `json:"down" form:"down"`
|
Down int64 `json:"down" form:"down"`
|
||||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||||
Total int64 `json:"total" form:"total"`
|
Total int64 `json:"total" form:"total"`
|
||||||
|
Reset int `json:"reset" form:"reset" gorm:"default:0"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ type process struct {
|
|||||||
version string
|
version string
|
||||||
apiPort int
|
apiPort int
|
||||||
|
|
||||||
|
onlineClients []string
|
||||||
|
|
||||||
config *Config
|
config *Config
|
||||||
lines *queue.Queue
|
lines *queue.Queue
|
||||||
exitErr error
|
exitErr error
|
||||||
@@ -114,6 +116,14 @@ func (p *Process) GetConfig() *Config {
|
|||||||
return p.config
|
return p.config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Process) GetOnlineClients() []string {
|
||||||
|
return p.onlineClients
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) SetOnlineClients(users []string) {
|
||||||
|
p.onlineClients = users
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Process) GetUptime() uint64 {
|
func (p *Process) GetUptime() uint64 {
|
||||||
return uint64(time.Since(p.startTime).Seconds())
|
return uint64(time.Since(p.startTime).Seconds())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user