mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-04-19 21:42:24 +00:00
Merge pull request #420 from hamid-gh98/main
[fix] russia domains in settings and More....
This commit is contained in:
commit
5468069bef
27 changed files with 225 additions and 88 deletions
45
README.md
45
README.md
|
@ -1,4 +1,5 @@
|
||||||
# 3x-ui
|
# 3x-ui
|
||||||
|
|
||||||
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
|
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
|
||||||
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||||
|
@ -12,7 +13,8 @@
|
||||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||||
|
|
||||||
**Buy Me a Coffee :**
|
**Buy Me a Coffee :**
|
||||||
- Tron USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
|
||||||
|
- Tron USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
|
|
||||||
# Install & Upgrade
|
# Install & Upgrade
|
||||||
|
|
||||||
|
@ -47,12 +49,12 @@ or you can use x-ui menu then number '16' (Apply for an SSL Certificate)
|
||||||
|
|
||||||
Before you set ssl on settings
|
Before you set ssl on settings
|
||||||
|
|
||||||
- http://ip:2053/xui
|
- http://ip:2053/panel
|
||||||
- http://domain:2053/xui
|
- http://domain:2053/panel
|
||||||
|
|
||||||
After you set ssl on settings
|
After you set ssl on settings
|
||||||
|
|
||||||
- https://yourdomain:2053/xui
|
- https://yourdomain:2053/panel
|
||||||
|
|
||||||
# Environment Variables
|
# Environment Variables
|
||||||
|
|
||||||
|
@ -69,6 +71,31 @@ Example:
|
||||||
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Install with Docker
|
||||||
|
|
||||||
|
1. Install Docker:
|
||||||
|
```sh
|
||||||
|
bash <(curl -sSL https://get.docker.com)
|
||||||
|
```
|
||||||
|
2. Run 3x-ui:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run -itd \
|
||||||
|
-e XRAY_VMESS_AEAD_FORCED=false \
|
||||||
|
-v $PWD/db/:/etc/x-ui/ \
|
||||||
|
-v $PWD/cert/:/root/cert/ \
|
||||||
|
--network=host \
|
||||||
|
--restart=unless-stopped \
|
||||||
|
--name 3x-ui \
|
||||||
|
ghcr.io/mhsanaei/3x-ui:latest
|
||||||
|
```
|
||||||
|
|
||||||
# Xray Configurations:
|
# Xray Configurations:
|
||||||
|
|
||||||
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
||||||
|
@ -173,19 +200,19 @@ Reference syntax:
|
||||||
| `POST` | `"/clientIps/:email"` | Client Ip address |
|
| `POST` | `"/clientIps/:email"` | Client Ip address |
|
||||||
| `POST` | `"/clearClientIps/:email"` | Clear Client Ip address |
|
| `POST` | `"/clearClientIps/:email"` | Clear Client Ip address |
|
||||||
| `POST` | `"/addClient"` | Add Client to inbound |
|
| `POST` | `"/addClient"` | Add Client to inbound |
|
||||||
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId* |
|
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId\* |
|
||||||
| `POST` | `"/updateClient/:clientId"` | Update Client by clientId* |
|
| `POST` | `"/updateClient/:clientId"` | Update Client by clientId\* |
|
||||||
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
|
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
|
||||||
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
||||||
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
|
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
|
||||||
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
||||||
|
|
||||||
*- The field `clientId` should be filled by:
|
\*- The field `clientId` should be filled by:
|
||||||
|
|
||||||
- `client.id` for VMESS and VLESS
|
- `client.id` for VMESS and VLESS
|
||||||
- `client.password` for TROJAN
|
- `client.password` for TROJAN
|
||||||
- `client.email` for Shadowsocks
|
- `client.email` for Shadowsocks
|
||||||
|
|
||||||
|
|
||||||
- [Postman Collection](https://gist.github.com/mehdikhody/9a862801a2e41f6b5fb6bbc7e1326044)
|
- [Postman Collection](https://gist.github.com/mehdikhody/9a862801a2e41f6b5fb6bbc7e1326044)
|
||||||
|
|
||||||
# A Special Thanks To
|
# A Special Thanks To
|
||||||
|
|
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
3x-ui:
|
||||||
|
image: ghcr.io/mhsanaei/3x-ui:latest
|
||||||
|
container_name: 3x-ui
|
||||||
|
volumes:
|
||||||
|
- $PWD/db/:/etc/x-ui/
|
||||||
|
- $PWD/cert/:/root/cert/
|
||||||
|
environment:
|
||||||
|
XRAY_VMESS_AEAD_FORCED: "false"
|
||||||
|
tty: true
|
||||||
|
network_mode: host
|
||||||
|
restart: unless-stopped
|
|
@ -39,7 +39,7 @@ func (a *IndexController) initRouter(g *gin.RouterGroup) {
|
||||||
|
|
||||||
func (a *IndexController) index(c *gin.Context) {
|
func (a *IndexController) index(c *gin.Context) {
|
||||||
if session.IsLogin(c) {
|
if session.IsLogin(c) {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, "xui/")
|
c.Redirect(http.StatusTemporaryRedirect, "panel/")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
html(c, "login.html", "pages.login.title", nil)
|
html(c, "login.html", "pages.login.title", nil)
|
||||||
|
@ -101,5 +101,4 @@ func (a *IndexController) getSecretStatus(c *gin.Context) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
jsonObj(c, status, nil)
|
jsonObj(c, status, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package controller
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
"x-ui/util/common"
|
||||||
"x-ui/web/entity"
|
"x-ui/web/entity"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
@ -44,6 +45,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
||||||
g.GET("/getDefaultJsonConfig", a.getDefaultJsonConfig)
|
g.GET("/getDefaultJsonConfig", a.getDefaultJsonConfig)
|
||||||
g.POST("/updateUserSecret", a.updateSecret)
|
g.POST("/updateUserSecret", a.updateSecret)
|
||||||
g.POST("/getUserSecret", a.getUserSecret)
|
g.POST("/getUserSecret", a.getUserSecret)
|
||||||
|
g.GET("/searchDatafiles", a.searchDatafiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||||
|
@ -149,6 +151,7 @@ func (a *SettingController) updateSecret(c *gin.Context) {
|
||||||
}
|
}
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifyUser"), err)
|
jsonMsg(c, I18n(c, "pages.settings.toasts.modifyUser"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) getUserSecret(c *gin.Context) {
|
func (a *SettingController) getUserSecret(c *gin.Context) {
|
||||||
loginUser := session.GetLoginUser(c)
|
loginUser := session.GetLoginUser(c)
|
||||||
user := a.userService.GetUserSecret(loginUser.Id)
|
user := a.userService.GetUserSecret(loginUser.Id)
|
||||||
|
@ -156,3 +159,18 @@ func (a *SettingController) getUserSecret(c *gin.Context) {
|
||||||
jsonObj(c, user, nil)
|
jsonObj(c, user, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *SettingController) searchDatafiles(c *gin.Context) {
|
||||||
|
searchString := c.Query("query")
|
||||||
|
if searchString == "" {
|
||||||
|
err := common.NewError("data query parameter is empty")
|
||||||
|
jsonMsg(c, "Invalid query:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
found, err := a.settingService.SearchDatafiles(searchString)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, found, nil)
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ func NewXUIController(g *gin.RouterGroup) *XUIController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XUIController) initRouter(g *gin.RouterGroup) {
|
func (a *XUIController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/xui")
|
g = g.Group("/panel")
|
||||||
g.Use(a.checkLogin)
|
g.Use(a.checkLogin)
|
||||||
|
|
||||||
g.GET("/", a.index)
|
g.GET("/", a.index)
|
||||||
|
|
|
@ -7,7 +7,10 @@
|
||||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
|
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
|
||||||
{{ i18n "pages.inbounds.clickOnQRcode" }}
|
{{ i18n "pages.inbounds.clickOnQRcode" }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%;"></canvas>
|
<a-tag v-if="qrModal.clientName" color="orange" style="margin-bottom: 10px;display: block;text-align: center;">
|
||||||
|
{{ i18n "pages.inbounds.email" }}: "[[ qrModal.clientName ]]"
|
||||||
|
</a-tag>
|
||||||
|
<canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%; margin-top: 10px;"></canvas>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -18,14 +21,16 @@
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
copyText: '',
|
copyText: '',
|
||||||
|
clientName: null,
|
||||||
qrcode: null,
|
qrcode: null,
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
visible: false,
|
visible: false,
|
||||||
show: function (title = '', content = '', dbInbound = new DBInbound(), copyText = '') {
|
show: function (title = '', content = '', dbInbound = new DBInbound(), copyText = '', clientName = null) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.dbInbound = dbInbound;
|
this.dbInbound = dbInbound;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
|
this.clientName = clientName;
|
||||||
if (ObjectUtil.isEmpty(copyText)) {
|
if (ObjectUtil.isEmpty(copyText)) {
|
||||||
this.copyText = content;
|
this.copyText = content;
|
||||||
} else {
|
} else {
|
||||||
|
@ -50,6 +55,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const qrModalApp = new Vue({
|
const qrModalApp = new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
el: '#qrcode-modal',
|
el: '#qrcode-modal',
|
||||||
data: {
|
data: {
|
||||||
qrModal: qrModal,
|
qrModal: qrModal,
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak :class="themeSwitcher.darkClass">
|
<a-layout id="app" v-cloak :class="themeSwitcher.darkCardClass">
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
|
@ -120,10 +120,10 @@
|
||||||
secretEnable: false,
|
secretEnable: false,
|
||||||
lang: ""
|
lang: ""
|
||||||
},
|
},
|
||||||
created() {
|
async created() {
|
||||||
this.updateBackground();
|
this.updateBackground();
|
||||||
this.lang = getLang();
|
this.lang = getLang();
|
||||||
this.secretEnable = this.getSecretStatus();
|
this.secretEnable = await this.getSecretStatus();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async login() {
|
async login() {
|
||||||
|
@ -131,7 +131,7 @@
|
||||||
const msg = await HttpUtil.post('/login', this.user);
|
const msg = await HttpUtil.post('/login', this.user);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
location.href = basePath + 'xui/';
|
location.href = basePath + 'panel/';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getSecretStatus() {
|
async getSecretStatus() {
|
||||||
|
|
|
@ -135,7 +135,7 @@
|
||||||
client.email = string;
|
client.email = string;
|
||||||
},
|
},
|
||||||
async getDBClientIps(email, event) {
|
async getDBClientIps(email, event) {
|
||||||
const msg = await HttpUtil.post('/xui/inbound/clientIps/' + email);
|
const msg = await HttpUtil.post('/panel/inbound/clientIps/' + email);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async clearDBClientIps(email) {
|
async clearDBClientIps(email) {
|
||||||
const msg = await HttpUtil.post('/xui/inbound/clearClientIps/' + email);
|
const msg = await HttpUtil.post('/panel/inbound/clearClientIps/' + email);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
iconElement.disabled = true;
|
iconElement.disabled = true;
|
||||||
const msg = await HttpUtil.postWithModal('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + email);
|
const msg = await HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + email);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.clientModal.clientStats.up = 0;
|
this.clientModal.clientStats.up = 0;
|
||||||
this.clientModal.clientStats.down = 0;
|
this.clientModal.clientStats.down = 0;
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
{{define "menuItems"}}
|
{{define "menuItems"}}
|
||||||
<a-menu-item key="{{ .base_path }}xui/">
|
<a-menu-item key="{{ .base_path }}panel/">
|
||||||
<a-icon type="dashboard"></a-icon>
|
<a-icon type="dashboard"></a-icon>
|
||||||
<span>{{ i18n "menu.dashboard"}}</span>
|
<span>{{ i18n "menu.dashboard"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}xui/inbounds">
|
<a-menu-item key="{{ .base_path }}panel/inbounds">
|
||||||
<a-icon type="user"></a-icon>
|
<a-icon type="user"></a-icon>
|
||||||
<span>{{ i18n "menu.inbounds"}}</span>
|
<span>{{ i18n "menu.inbounds"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}xui/settings">
|
<a-menu-item key="{{ .base_path }}panel/settings">
|
||||||
<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/clients">-->
|
<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
|
||||||
<!-- <a-icon type="laptop"></a-icon>-->
|
<!-- <a-icon type="laptop"></a-icon>-->
|
||||||
<!-- <span>Client</span>-->
|
<!-- <span>Client</span>-->
|
||||||
<!--</a-menu-item>-->
|
<!--</a-menu-item>-->
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
isDarkTheme,
|
isDarkTheme,
|
||||||
bgStyle: `background: ${colors[theme].bg};`,
|
bgStyle: `background: ${colors[theme].bg};`,
|
||||||
textStyle: `color: ${colors[theme].text};`,
|
textStyle: `color: ${colors[theme].text};`,
|
||||||
darkClass: isDarkTheme ? 'ant-card-dark' : '',
|
darkClass: isDarkTheme ? 'ant-dark' : '',
|
||||||
darkCardClass: isDarkTheme ? 'ant-card-dark' : '',
|
darkCardClass: isDarkTheme ? 'ant-card-dark' : '',
|
||||||
darkDrawerClass: isDarkTheme ? 'ant-drawer-dark' : '',
|
darkDrawerClass: isDarkTheme ? 'ant-drawer-dark' : '',
|
||||||
get currentTheme() {
|
get currentTheme() {
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
localStorage.setItem('dark-mode', this.isDarkTheme);
|
localStorage.setItem('dark-mode', this.isDarkTheme);
|
||||||
this.bgStyle = `background: ${colors[this.theme].bg};`;
|
this.bgStyle = `background: ${colors[this.theme].bg};`;
|
||||||
this.textStyle = `color: ${colors[this.theme].text};`;
|
this.textStyle = `color: ${colors[this.theme].text};`;
|
||||||
this.darkClass = this.isDarkTheme ? 'ant-card-dark' : '';
|
this.darkClass = this.isDarkTheme ? 'ant-dark' : '';
|
||||||
this.darkCardClass = this.isDarkTheme ? 'ant-card-dark' : '';
|
this.darkCardClass = this.isDarkTheme ? 'ant-card-dark' : '';
|
||||||
this.darkDrawerClass = this.isDarkTheme ? 'ant-drawer-dark' : '';
|
this.darkDrawerClass = this.isDarkTheme ? 'ant-drawer-dark' : '';
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,10 +11,10 @@
|
||||||
<br>
|
<br>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon @click="getNewEmail(client)" type="sync"></a-icon>
|
<a-icon @click="getNewEmail(client)" type="sync"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
|
@ -114,6 +114,7 @@
|
||||||
<tr v-if="infoModal.clientSettings.subId">
|
<tr v-if="infoModal.clientSettings.subId">
|
||||||
<td>Subscription link</td>
|
<td>Subscription link</td>
|
||||||
<td><a :href="[[ subBase + infoModal.clientSettings.subId ]]" target="_blank">[[ subBase + infoModal.clientSettings.subId ]]</a></td>
|
<td><a :href="[[ subBase + infoModal.clientSettings.subId ]]" target="_blank">[[ subBase + infoModal.clientSettings.subId ]]</a></td>
|
||||||
|
<td><a-icon id="copy-sub-link" type="snippets" @click="copyToClipboard('copy-sub-link', subBase + infoModal.clientSettings.subId)"></a-icon></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="infoModal.clientSettings.tgId">
|
<tr v-if="infoModal.clientSettings.tgId">
|
||||||
<td>Telegram ID</td>
|
<td>Telegram ID</td>
|
||||||
|
@ -190,7 +191,7 @@
|
||||||
<div v-if="dbInbound.hasLink()">
|
<div v-if="dbInbound.hasLink()">
|
||||||
<a-divider>URL</a-divider>
|
<a-divider>URL</a-divider>
|
||||||
<p>[[ infoModal.link ]]</p>
|
<p>[[ infoModal.link ]]</p>
|
||||||
<button class="ant-btn ant-btn-primary" id="copy-url-link"><a-icon type="snippets"></a-icon>{{ i18n "copy" }}</button>
|
<button class="ant-btn ant-btn-primary" id="copy-url-link" @click="copyToClipboard('copy-url-link', infoModal.link)"><a-icon type="snippets"></a-icon>{{ i18n "copy" }}</button>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
@ -218,14 +219,6 @@
|
||||||
this.isExpired = this.inbound.isExpiry(index);
|
this.isExpired = this.inbound.isExpiry(index);
|
||||||
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
infoModalApp.$nextTick(() => {
|
|
||||||
if (this.clipboard === null) {
|
|
||||||
this.clipboard = new ClipboardJS('#copy-url-link', {
|
|
||||||
text: () => this.link,
|
|
||||||
});
|
|
||||||
this.clipboard.on('success', () => app.$message.success('{{ i18n "copied" }}'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
infoModal.visible = false;
|
infoModal.visible = false;
|
||||||
|
@ -263,7 +256,7 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyTextToClipboard(elmentId, content) {
|
copyToClipboard(elmentId, content) {
|
||||||
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
|
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
|
||||||
text: () => content,
|
text: () => content,
|
||||||
});
|
});
|
||||||
|
|
|
@ -338,7 +338,7 @@
|
||||||
},
|
},
|
||||||
async getDBInbounds() {
|
async getDBInbounds() {
|
||||||
this.refreshing = true;
|
this.refreshing = true;
|
||||||
const msg = await HttpUtil.post('/xui/inbound/list');
|
const msg = await HttpUtil.post('/panel/inbound/list');
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -346,7 +346,7 @@
|
||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
},
|
},
|
||||||
async getDefaultSettings() {
|
async getDefaultSettings() {
|
||||||
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
const msg = await HttpUtil.post('/panel/setting/defaultSettings');
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -509,7 +509,7 @@
|
||||||
streamSettings: baseInbound.stream.toString(),
|
streamSettings: baseInbound.stream.toString(),
|
||||||
sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
|
sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
|
||||||
};
|
};
|
||||||
await this.submit('/xui/inbound/add', data, inModal);
|
await this.submit('/panel/inbound/add', data, inModal);
|
||||||
},
|
},
|
||||||
openAddInbound() {
|
openAddInbound() {
|
||||||
inModal.show({
|
inModal.show({
|
||||||
|
@ -558,7 +558,7 @@
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||||
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit('/xui/inbound/add', data, inModal);
|
await this.submit('/panel/inbound/add', data, inModal);
|
||||||
},
|
},
|
||||||
async updateInbound(inbound, dbInbound) {
|
async updateInbound(inbound, dbInbound) {
|
||||||
const data = {
|
const data = {
|
||||||
|
@ -577,7 +577,7 @@
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||||
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
|
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
|
||||||
},
|
},
|
||||||
openAddClient(dbInboundId) {
|
openAddClient(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
@ -632,14 +632,14 @@
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + clients.toString() + ']}',
|
settings: '{"clients": [' + clients.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/xui/inbound/addClient`, data);
|
await this.submit(`/panel/inbound/addClient`, data);
|
||||||
},
|
},
|
||||||
async updateClient(client, dbInboundId, clientId) {
|
async updateClient(client, dbInboundId, clientId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + client.toString() + ']}',
|
settings: '{"clients": [' + client.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/xui/inbound/updateClient/${clientId}`, data);
|
await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
|
||||||
},
|
},
|
||||||
resetTraffic(dbInboundId) {
|
resetTraffic(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
@ -664,7 +664,7 @@
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/del/' + dbInboundId),
|
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
delClient(dbInboundId, client) {
|
delClient(dbInboundId, client) {
|
||||||
|
@ -676,7 +676,7 @@
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`),
|
onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
getClients(protocol, clientSettings) {
|
||||||
|
@ -696,15 +696,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showQrcode(dbInbound, clientIndex) {
|
showQrcode(dbInbound, clientIndex) {
|
||||||
|
const clientName = JSON.parse(dbInbound.settings).clients[clientIndex].email;
|
||||||
const link = dbInbound.genLink(clientIndex);
|
const link = dbInbound.genLink(clientIndex);
|
||||||
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
|
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound, '', clientName);
|
||||||
},
|
},
|
||||||
showInfo(dbInbound, index) {
|
showInfo(dbInbound, index) {
|
||||||
infoModal.show(dbInbound, index);
|
infoModal.show(dbInbound, index);
|
||||||
},
|
},
|
||||||
switchEnable(dbInboundId) {
|
switchEnable(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
this.submit(`/xui/inbound/update/${dbInboundId}`, dbInbound);
|
this.submit(`/panel/inbound/update/${dbInboundId}`, dbInbound);
|
||||||
},
|
},
|
||||||
async switchEnableClient(dbInboundId, client) {
|
async switchEnableClient(dbInboundId, client) {
|
||||||
this.loading()
|
this.loading()
|
||||||
|
@ -741,7 +742,7 @@
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
resetAllTraffic() {
|
resetAllTraffic() {
|
||||||
|
@ -751,7 +752,7 @@
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/resetAllTraffics'),
|
onOk: () => this.submit('/panel/inbound/resetAllTraffics'),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
resetAllClientTraffics(dbInboundId) {
|
resetAllClientTraffics(dbInboundId) {
|
||||||
|
@ -761,7 +762,7 @@
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
|
onOk: () => this.submit('/panel/inbound/resetAllClientTraffics/' + dbInboundId),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
delDepletedClients(dbInboundId) {
|
delDepletedClients(dbInboundId) {
|
||||||
|
@ -771,7 +772,7 @@
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId),
|
onOk: () => this.submit('/panel/inbound/delDepletedClients/' + dbInboundId),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
isExpiry(dbInbound, index) {
|
isExpiry(dbInbound, index) {
|
||||||
|
|
|
@ -500,7 +500,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const restartMsg = await HttpUtil.post("/xui/setting/restartPanel");
|
const restartMsg = await HttpUtil.post("/panel/setting/restartPanel");
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (restartMsg.success) {
|
if (restartMsg.success) {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
|
|
|
@ -153,6 +153,7 @@
|
||||||
<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.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.xrayConfigAds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigAdsDesc"}}' v-model="AdsSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigPorn"}}' desc='{{ i18n "pages.settings.templates.xrayConfigPornDesc"}}' v-model="PornSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigPorn"}}' desc='{{ i18n "pages.settings.templates.xrayConfigPornDesc"}}' v-model="PornSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigSpeedtest"}}' desc='{{ i18n "pages.settings.templates.xrayConfigSpeedtestDesc"}}' v-model="SpeedTestSettings"></setting-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.countryConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.countryConfigs"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
@ -285,6 +286,7 @@
|
||||||
"geosite:spotify-ads"
|
"geosite:spotify-ads"
|
||||||
],
|
],
|
||||||
porn: ["geosite:category-porn"],
|
porn: ["geosite:category-porn"],
|
||||||
|
speedtest: ["geosite:speedtest"],
|
||||||
openai: ["geosite:openai"],
|
openai: ["geosite:openai"],
|
||||||
google: ["geosite:google"],
|
google: ["geosite:google"],
|
||||||
spotify: ["geosite:spotify"],
|
spotify: ["geosite:spotify"],
|
||||||
|
@ -307,13 +309,16 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
this.checkForGeosites();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true, obj) {
|
loading(spinning = true, obj) {
|
||||||
if (obj == null) this.spinning = spinning;
|
if (obj == null) this.spinning = spinning;
|
||||||
},
|
},
|
||||||
async getAllSetting() {
|
async getAllSetting() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("/xui/setting/all");
|
const msg = await HttpUtil.post("/panel/setting/all");
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.oldAllSetting = new AllSetting(msg.obj);
|
this.oldAllSetting = new AllSetting(msg.obj);
|
||||||
|
@ -324,7 +329,7 @@
|
||||||
},
|
},
|
||||||
async updateAllSetting() {
|
async updateAllSetting() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("/xui/setting/update", this.allSetting);
|
const msg = await HttpUtil.post("/panel/setting/update", this.allSetting);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
await this.getAllSetting();
|
await this.getAllSetting();
|
||||||
|
@ -332,7 +337,7 @@
|
||||||
},
|
},
|
||||||
async updateUser() {
|
async updateUser() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("/xui/setting/updateUser", this.user);
|
const msg = await HttpUtil.post("/panel/setting/updateUser", this.user);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.user = {};
|
this.user = {};
|
||||||
|
@ -350,7 +355,7 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("/xui/setting/restartPanel");
|
const msg = await HttpUtil.post("/panel/setting/restartPanel");
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
|
@ -359,7 +364,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getUserSecret() {
|
async getUserSecret() {
|
||||||
const user_msg = await HttpUtil.post("/xui/setting/getUserSecret", this.user);
|
const user_msg = await HttpUtil.post("/panel/setting/getUserSecret", this.user);
|
||||||
if (user_msg.success) {
|
if (user_msg.success) {
|
||||||
this.user = user_msg.obj;
|
this.user = user_msg.obj;
|
||||||
}
|
}
|
||||||
|
@ -367,7 +372,7 @@
|
||||||
},
|
},
|
||||||
async updateSecret() {
|
async updateSecret() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("/xui/setting/updateUserSecret", this.user);
|
const msg = await HttpUtil.post("/panel/setting/updateUserSecret", this.user);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.user = msg.obj;
|
this.user = msg.obj;
|
||||||
window.location.replace(basePath + "logout")
|
window.location.replace(basePath + "logout")
|
||||||
|
@ -394,13 +399,34 @@
|
||||||
},
|
},
|
||||||
async resetXrayConfigToDefault() {
|
async resetXrayConfigToDefault() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.get("/xui/setting/getDefaultJsonConfig");
|
const msg = await HttpUtil.get("/panel/setting/getDefaultJsonConfig");
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
|
this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
|
||||||
this.saveBtnDisable = true;
|
this.saveBtnDisable = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
checkForGeosites() {
|
||||||
|
const domainsToCheck = [
|
||||||
|
{
|
||||||
|
query: "category-ru-gov",
|
||||||
|
key: "this.settingsData.domains.ru",
|
||||||
|
data: [
|
||||||
|
"geosite:category-ru-gov",
|
||||||
|
"regexp:.*\\.ru$"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
];
|
||||||
|
this.loading(true);
|
||||||
|
domainsToCheck.forEach(async (dd) => {
|
||||||
|
const msg = await HttpUtil.get(`/panel/setting/searchDatafiles?query=${dd.query}`);
|
||||||
|
if (msg.success && msg.obj) {
|
||||||
|
[dd.key] = dd.data;
|
||||||
|
console.log([dd.key])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.loading(false);
|
||||||
|
},
|
||||||
checkRequiredOutbounds() {
|
checkRequiredOutbounds() {
|
||||||
const newTemplateSettings = this.templateSettings;
|
const newTemplateSettings = this.templateSettings;
|
||||||
const haveIPv4Outbounds = newTemplateSettings.outbounds.some((o) => o?.tag === "IPv4");
|
const haveIPv4Outbounds = newTemplateSettings.outbounds.some((o) => o?.tag === "IPv4");
|
||||||
|
@ -573,6 +599,23 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
SpeedTestSettings: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({
|
||||||
|
outboundTag: "blocked",
|
||||||
|
property: "domain",
|
||||||
|
data: this.settingsData.domains.speedtest
|
||||||
|
});
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({
|
||||||
|
newValue,
|
||||||
|
outboundTag: "blocked",
|
||||||
|
property: "domain",
|
||||||
|
data: this.settingsData.domains.speedtest
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
GoogleIPv4Settings: {
|
GoogleIPv4Settings: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return this.templateRuleGetter({
|
return this.templateRuleGetter({
|
||||||
|
|
|
@ -179,7 +179,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||||
url := "https://api.github.com/repos/mhsanaei/Xray-core/releases"
|
url := "https://api.github.com/repos/MHSanaei/Xray-core/releases"
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -246,7 +246,7 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
||||||
url := fmt.Sprintf("https://github.com/mhsanaei/Xray-core/releases/download/%s/%s", version, fileName)
|
url := fmt.Sprintf("https://github.com/MHSanaei/Xray-core/releases/download/%s/%s", version, fileName)
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -16,6 +18,7 @@ import (
|
||||||
"x-ui/util/random"
|
"x-ui/util/random"
|
||||||
"x-ui/util/reflect_util"
|
"x-ui/util/reflect_util"
|
||||||
"x-ui/web/entity"
|
"x-ui/web/entity"
|
||||||
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed config.json
|
//go:embed config.json
|
||||||
|
@ -351,3 +354,27 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||||
}
|
}
|
||||||
return common.Combine(errs...)
|
return common.Combine(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SearchDatafiles(query string) (bool, error) {
|
||||||
|
// Open the file for reading
|
||||||
|
file, err := os.Open(xray.GetGeositePath())
|
||||||
|
if err != nil {
|
||||||
|
return false, common.NewErrorf("Error opening geosite.dat: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Create a scanner to read the file line-by-line
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.Contains(strings.ToLower(line), strings.ToLower(query)) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
return false, common.NewErrorf("Error while scanning geosite.dat: %v", err)
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.SubID == subId {
|
if client.Enable && client.SubID == subId {
|
||||||
link := s.getLink(inbound, client.Email)
|
link := s.getLink(inbound, client.Email)
|
||||||
result = append(result, link)
|
result = append(result, link)
|
||||||
clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email))
|
clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email))
|
||||||
|
@ -73,7 +73,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
|
||||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error
|
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ? and enable = ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId), true).Find(&inbounds).Error
|
||||||
if err != nil && err != gorm.ErrRecordNotFound {
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,8 +150,6 @@
|
||||||
"resetAllTrafficContent" = "Are you sure you want to reset all inbounds traffic?"
|
"resetAllTrafficContent" = "Are you sure you want to reset all inbounds traffic?"
|
||||||
"resetAllTrafficOkText" = "Confirm"
|
"resetAllTrafficOkText" = "Confirm"
|
||||||
"resetAllTrafficCancelText" = "Cancel"
|
"resetAllTrafficCancelText" = "Cancel"
|
||||||
"IPLimit" = "IP Limit"
|
|
||||||
"IPLimitDesc" = "Disable inbound if the count exceeds the entered value (enter 0 to disable IP limit)."
|
|
||||||
"resetInboundClientTraffics" = "Reset Clients Traffic"
|
"resetInboundClientTraffics" = "Reset Clients Traffic"
|
||||||
"resetInboundClientTrafficTitle" = "Reset all client traffic"
|
"resetInboundClientTrafficTitle" = "Reset all client traffic"
|
||||||
"resetInboundClientTrafficContent" = "Are you sure you want to reset all traffic for this inbound's clients?"
|
"resetInboundClientTrafficContent" = "Are you sure you want to reset all traffic for this inbound's clients?"
|
||||||
|
@ -161,8 +159,10 @@
|
||||||
"delDepletedClients" = "Delete Depleted Clients"
|
"delDepletedClients" = "Delete Depleted Clients"
|
||||||
"delDepletedClientsTitle" = "Delete depleted clients"
|
"delDepletedClientsTitle" = "Delete depleted clients"
|
||||||
"delDepletedClientsContent" = "Are you sure you want to delete all depleted clients?"
|
"delDepletedClientsContent" = "Are you sure you want to delete all depleted clients?"
|
||||||
"Email" = "Email"
|
"email" = "Email"
|
||||||
"EmailDesc" = "Please provide a unique email address."
|
"emailDesc" = "Please provide a unique email address."
|
||||||
|
"IPLimit" = "IP Limit"
|
||||||
|
"IPLimitDesc" = "Disable inbound if the count exceeds the entered value (enter 0 to disable IP limit)."
|
||||||
"IPLimitlog" = "IP Log"
|
"IPLimitlog" = "IP Log"
|
||||||
"IPLimitlogDesc" = "IPs history log (before enabling inbound after it has been disabled by IP limit, you should clear the log)."
|
"IPLimitlogDesc" = "IPs history log (before enabling inbound after it has been disabled by IP limit, you should clear the log)."
|
||||||
"IPLimitlogclear" = "Clear The Log"
|
"IPLimitlogclear" = "Clear The Log"
|
||||||
|
@ -277,6 +277,8 @@
|
||||||
"xrayConfigAdsDesc" = "Change the configuration template to block ads. Restart the panel to apply changes."
|
"xrayConfigAdsDesc" = "Change the configuration template to block ads. Restart the panel to apply changes."
|
||||||
"xrayConfigPorn" = "Block Porn Websites"
|
"xrayConfigPorn" = "Block Porn Websites"
|
||||||
"xrayConfigPornDesc" = "Change the configuration template to avoid connecting to porn websites. Restart the panel to apply changes."
|
"xrayConfigPornDesc" = "Change the configuration template to avoid connecting to porn websites. Restart the panel to apply changes."
|
||||||
|
"xrayConfigSpeedtest" = "Block Speedtest Websites"
|
||||||
|
"xrayConfigSpeedtestDesc" = "Change the configuration template to avoid connecting to speedtest websites. Restart the panel to apply changes."
|
||||||
"xrayConfigIRIp" = "Disable connection to Iran IP ranges"
|
"xrayConfigIRIp" = "Disable connection to Iran IP ranges"
|
||||||
"xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting with Iran IP ranges. Restart the panel to apply changes."
|
"xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting with Iran IP ranges. Restart the panel to apply changes."
|
||||||
"xrayConfigIRDomain" = "Disable connection to Iran domains"
|
"xrayConfigIRDomain" = "Disable connection to Iran domains"
|
||||||
|
|
|
@ -157,10 +157,10 @@
|
||||||
"delDepletedClients" = "حذف کاربران منقضی"
|
"delDepletedClients" = "حذف کاربران منقضی"
|
||||||
"delDepletedClientsTitle" = "حذف کاربران منقضی"
|
"delDepletedClientsTitle" = "حذف کاربران منقضی"
|
||||||
"delDepletedClientsContent" = "آیا مطمئن هستید مه میخواهید تمامی کاربران منقضی شده را حذف کنید؟"
|
"delDepletedClientsContent" = "آیا مطمئن هستید مه میخواهید تمامی کاربران منقضی شده را حذف کنید؟"
|
||||||
|
"email" = "ایمیل"
|
||||||
|
"emailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
|
||||||
"IPLimit" = "محدودیت ای پی"
|
"IPLimit" = "محدودیت ای پی"
|
||||||
"IPLimitDesc" = "غیرفعال کردن ورودی در صورت بیش از تعداد وارد شده (0 برای غیرفعال کردن محدودیت ای پی )"
|
"IPLimitDesc" = "غیرفعال کردن ورودی در صورت بیش از تعداد وارد شده (0 برای غیرفعال کردن محدودیت ای پی )"
|
||||||
"Email" = "ایمیل"
|
|
||||||
"EmailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
|
|
||||||
"IPLimitlog" = "گزارش ها"
|
"IPLimitlog" = "گزارش ها"
|
||||||
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
|
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
|
||||||
"IPLimitlogclear" = "پاک کردن گزارش ها"
|
"IPLimitlogclear" = "پاک کردن گزارش ها"
|
||||||
|
@ -275,6 +275,8 @@
|
||||||
"xrayConfigAdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigAdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"xrayConfigPorn" = "جلوگیری از اتصال به سایت های پورن"
|
"xrayConfigPorn" = "جلوگیری از اتصال به سایت های پورن"
|
||||||
"xrayConfigPornDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های پورن تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigPornDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های پورن تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigSpeedtest" = "جلوگیری از اتصال به سایت های تست سرعت"
|
||||||
|
"xrayConfigSpeedtestDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های تست سرعت تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"xrayConfigIRIp" = "جلوگیری از اتصال آیپی های ایران"
|
"xrayConfigIRIp" = "جلوگیری از اتصال آیپی های ایران"
|
||||||
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"xrayConfigIRDomain" = "جلوگیری از اتصال دامنه های ایران"
|
"xrayConfigIRDomain" = "جلوگیری از اتصال دامنه های ایران"
|
||||||
|
|
|
@ -150,8 +150,6 @@
|
||||||
"resetAllTrafficContent" = "Подтверждаете обнуление всего траффика пользователей?"
|
"resetAllTrafficContent" = "Подтверждаете обнуление всего траффика пользователей?"
|
||||||
"resetAllTrafficOkText" = "Подтвердить"
|
"resetAllTrafficOkText" = "Подтвердить"
|
||||||
"resetAllTrafficCancelText" = "Отмена"
|
"resetAllTrafficCancelText" = "Отмена"
|
||||||
"IPLimit" = "ограничение по IP"
|
|
||||||
"IPLimitDesc" = "Отключить ключ, если подключено больше введенного значения (введите 0, чтобы отключить ограничение IP-адресов)."
|
|
||||||
"resetInboundClientTraffics" = "Обнулить траффик пользователей"
|
"resetInboundClientTraffics" = "Обнулить траффик пользователей"
|
||||||
"resetInboundClientTrafficTitle" = "Обнуление траффика пользователей"
|
"resetInboundClientTrafficTitle" = "Обнуление траффика пользователей"
|
||||||
"resetInboundClientTrafficContent" = "Вы уверены, что хотите обнулить весь трафик для этих пользователей?"
|
"resetInboundClientTrafficContent" = "Вы уверены, что хотите обнулить весь трафик для этих пользователей?"
|
||||||
|
@ -161,8 +159,10 @@
|
||||||
"delDepletedClients" = "Удалить отключенных пользователей"
|
"delDepletedClients" = "Удалить отключенных пользователей"
|
||||||
"delDepletedClientsTitle" = "Удаление отключенных пользователей"
|
"delDepletedClientsTitle" = "Удаление отключенных пользователей"
|
||||||
"delDepletedClientsContent" = "Подтверждаете удаление отключенных пользователей?"
|
"delDepletedClientsContent" = "Подтверждаете удаление отключенных пользователей?"
|
||||||
"Email" = "Email"
|
"email" = "Email"
|
||||||
"EmailDesc" = "Пожалуйста, укажите уникальный Email"
|
"emailDesc" = "Пожалуйста, укажите уникальный Email"
|
||||||
|
"IPLimit" = "ограничение по IP"
|
||||||
|
"IPLimitDesc" = "Отключить ключ, если подключено больше введенного значения (введите 0, чтобы отключить ограничение IP-адресов)."
|
||||||
"IPLimitlog" = "IP лог"
|
"IPLimitlog" = "IP лог"
|
||||||
"IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)."
|
"IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)."
|
||||||
"IPLimitlogclear" = "Очистить список"
|
"IPLimitlogclear" = "Очистить список"
|
||||||
|
@ -277,6 +277,8 @@
|
||||||
"xrayConfigAdsDesc" = "Измените конфигурацию, чтобы заблокировать рекламу. Перезагрузите панель для применения настроек."
|
"xrayConfigAdsDesc" = "Измените конфигурацию, чтобы заблокировать рекламу. Перезагрузите панель для применения настроек."
|
||||||
"xrayConfigPorn" = "Блокировка порносайтов"
|
"xrayConfigPorn" = "Блокировка порносайтов"
|
||||||
"xrayConfigPornDesc" = "Измените конфигурацию, чтобы отключить подключения к порносайтам. Перезагрузите панель для применения настроек."
|
"xrayConfigPornDesc" = "Измените конфигурацию, чтобы отключить подключения к порносайтам. Перезагрузите панель для применения настроек."
|
||||||
|
"xrayConfigSpeedtest" = "Блокировать сайты для проверки скорости"
|
||||||
|
"xrayConfigSpeedtestDesc" = "Измените шаблон конфигурации, чтобы избежать подключения к веб-сайтам для тестирования скорости. Перезапустите панель, чтобы применить изменения."
|
||||||
"xrayConfigIRIp" = "Отключить подключение к диапазонам IP-адресов Ирана"
|
"xrayConfigIRIp" = "Отключить подключение к диапазонам IP-адресов Ирана"
|
||||||
"xrayConfigIRIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Ирана. Перезагрузите панель для применения настроек."
|
"xrayConfigIRIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Ирана. Перезагрузите панель для применения настроек."
|
||||||
"xrayConfigIRDomain" = "Отключить подключение к доменам Ирана"
|
"xrayConfigIRDomain" = "Отключить подключение к доменам Ирана"
|
||||||
|
|
|
@ -157,10 +157,10 @@
|
||||||
"delDepletedClients" = "删除耗尽的客户端"
|
"delDepletedClients" = "删除耗尽的客户端"
|
||||||
"delDepletedClientsTitle" = "删除耗尽的客户"
|
"delDepletedClientsTitle" = "删除耗尽的客户"
|
||||||
"delDepletedClientsContent" = "你确定要删除所有耗尽的客户端吗?"
|
"delDepletedClientsContent" = "你确定要删除所有耗尽的客户端吗?"
|
||||||
|
"email" = "电子邮件"
|
||||||
|
"emailDesc" = "电子邮件必须完全唯"
|
||||||
"IPLimit" = "IP限制"
|
"IPLimit" = "IP限制"
|
||||||
"IPLimitDesc" = "如果超过输入的计数则禁用入站(0 表示禁用限制 ip)"
|
"IPLimitDesc" = "如果超过输入的计数则禁用入站(0 表示禁用限制 ip)"
|
||||||
"Email" = "电子邮件"
|
|
||||||
"EmailDesc" = "电子邮件必须完全唯"
|
|
||||||
"IPLimitlog" = "IP日志"
|
"IPLimitlog" = "IP日志"
|
||||||
"IPLimitlogDesc" = "IP 历史日志 (通过IP限制禁用inbound之前,需要清空日志)"
|
"IPLimitlogDesc" = "IP 历史日志 (通过IP限制禁用inbound之前,需要清空日志)"
|
||||||
"IPLimitlogclear" = "清除日志"
|
"IPLimitlogclear" = "清除日志"
|
||||||
|
@ -275,6 +275,8 @@
|
||||||
"xrayConfigAdsDesc" = "修改配置模板屏蔽广告,重启面板生效"
|
"xrayConfigAdsDesc" = "修改配置模板屏蔽广告,重启面板生效"
|
||||||
"xrayConfigPorn" = "禁止色情网站连接"
|
"xrayConfigPorn" = "禁止色情网站连接"
|
||||||
"xrayConfigPornDesc" = "更改配置模板避免连接色情网站,重启面板生效"
|
"xrayConfigPornDesc" = "更改配置模板避免连接色情网站,重启面板生效"
|
||||||
|
"xrayConfigSpeedtest" = "阻止测速网站"
|
||||||
|
"xrayConfigSpeedtestDesc" = "更改配置模板以避免连接到速度测试网站。 重新启动面板以应用更改。"
|
||||||
"xrayConfigIRIp" = "禁止伊朗 IP 范围连接"
|
"xrayConfigIRIp" = "禁止伊朗 IP 范围连接"
|
||||||
"xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP段,重启面板生效"
|
"xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP段,重启面板生效"
|
||||||
"xrayConfigIRDomain" = "禁止伊朗域连接"
|
"xrayConfigIRDomain" = "禁止伊朗域连接"
|
||||||
|
|
|
@ -83,7 +83,7 @@ type Server struct {
|
||||||
|
|
||||||
index *controller.IndexController
|
index *controller.IndexController
|
||||||
server *controller.ServerController
|
server *controller.ServerController
|
||||||
xui *controller.XUIController
|
panel *controller.XUIController
|
||||||
api *controller.APIController
|
api *controller.APIController
|
||||||
sub *controller.SUBController
|
sub *controller.SUBController
|
||||||
|
|
||||||
|
@ -207,7 +207,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||||
|
|
||||||
s.index = controller.NewIndexController(g)
|
s.index = controller.NewIndexController(g)
|
||||||
s.server = controller.NewServerController(g)
|
s.server = controller.NewServerController(g)
|
||||||
s.xui = controller.NewXUIController(g)
|
s.panel = controller.NewXUIController(g)
|
||||||
s.api = controller.NewAPIController(g)
|
s.api = controller.NewAPIController(g)
|
||||||
s.sub = controller.NewSUBController(g)
|
s.sub = controller.NewSUBController(g)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue