mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-15 13:46:10 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
402c713f06 | ||
|
|
a371bec2aa | ||
|
|
427d008bd1 | ||
|
|
e69d17be67 | ||
|
|
09c61976ea | ||
|
|
6e17c282e0 | ||
|
|
700973655c | ||
|
|
dcb54267f2 |
@@ -1 +1 @@
|
||||
1.1.2
|
||||
1.1.3
|
||||
@@ -212,6 +212,7 @@
|
||||
.ant-card-dark .ant-modal-close,
|
||||
.ant-card-dark i,
|
||||
.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,
|
||||
@@ -226,7 +227,7 @@
|
||||
.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: #004488;
|
||||
background-color: #11314d;
|
||||
}
|
||||
|
||||
.ant-card-dark tbody .ant-table-expanded-row,
|
||||
@@ -243,7 +244,7 @@
|
||||
.ant-card-dark .ant-select-selection,
|
||||
.ant-card-dark .ant-calendar-picker-clear {
|
||||
color: hsla(0,0%,100%,.65);
|
||||
background-color: #2e3b52;
|
||||
background-color: #193752;
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-select-disabled .ant-select-selection {
|
||||
@@ -264,12 +265,19 @@
|
||||
|
||||
.ant-card-dark .ant-modal-content,
|
||||
.ant-card-dark .ant-modal-body,
|
||||
.ant-card-dark .ant-modal-header,
|
||||
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date {
|
||||
.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 {
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
@@ -75,9 +75,15 @@ const UTLS_FINGERPRINT = {
|
||||
};
|
||||
|
||||
const ALPN_OPTION = {
|
||||
H3: "h3",
|
||||
H2: "h2",
|
||||
HTTP1: "http/1.1",
|
||||
H2: "h2",
|
||||
H3: "h3",
|
||||
};
|
||||
|
||||
const SNIFFING_OPTION = {
|
||||
HTTP: "http",
|
||||
TLS: "tls",
|
||||
QUIC: "quic",
|
||||
};
|
||||
|
||||
Object.freeze(Protocols);
|
||||
@@ -87,6 +93,7 @@ Object.freeze(TLS_FLOW_CONTROL);
|
||||
Object.freeze(TLS_VERSION_OPTION);
|
||||
Object.freeze(TLS_CIPHER_OPTION);
|
||||
Object.freeze(ALPN_OPTION);
|
||||
Object.freeze(SNIFFING_OPTION);
|
||||
|
||||
class XrayCommonClass {
|
||||
|
||||
@@ -741,7 +748,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
class Sniffing extends XrayCommonClass {
|
||||
constructor(enabled=true, destOverride=['http', 'tls']) {
|
||||
constructor(enabled=true, destOverride=['http', 'tls', 'quic']) {
|
||||
super();
|
||||
this.enabled = enabled;
|
||||
this.destOverride = destOverride;
|
||||
@@ -751,7 +758,7 @@ class Sniffing extends XrayCommonClass {
|
||||
let destOverride = ObjectUtil.clone(json.destOverride);
|
||||
if (!ObjectUtil.isEmpty(destOverride) && !ObjectUtil.isArrEmpty(destOverride)) {
|
||||
if (ObjectUtil.isEmpty(destOverride[0])) {
|
||||
destOverride = ['http', 'tls'];
|
||||
destOverride = ['http', 'tls', 'quic'];
|
||||
}
|
||||
}
|
||||
return new Sniffing(
|
||||
|
||||
@@ -37,6 +37,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/update", a.updateSetting)
|
||||
g.POST("/updateUser", a.updateUser)
|
||||
g.POST("/restartPanel", a.restartPanel)
|
||||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||
}
|
||||
|
||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||
@@ -118,3 +119,12 @@ func (a *SettingController) restartPanel(c *gin.Context) {
|
||||
err := a.panelService.RestartPanel(time.Second * 3)
|
||||
jsonMsg(c, I18n(c, "pages.setting.restartPanel"), err)
|
||||
}
|
||||
|
||||
func (a *SettingController) getDefaultXrayConfig(c *gin.Context) {
|
||||
defaultJsonConfig, err := a.settingService.GetDefaultXrayConfig()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, defaultJsonConfig, nil)
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
|
||||
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
|
||||
@@ -12,5 +12,10 @@
|
||||
</span>
|
||||
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
|
||||
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -56,7 +56,8 @@
|
||||
<td>uTLS</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 250px">
|
||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||
style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<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>
|
||||
@@ -76,7 +77,7 @@
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-checkbox-group v-model="inbound.stream.tls.alpn">
|
||||
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
|
||||
<a-checkbox v-for="key,value in ALPN_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
@@ -175,7 +176,8 @@
|
||||
<td>uTLS</td>
|
||||
<td>
|
||||
<a-form-item >
|
||||
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 250px">
|
||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||
style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
@@ -66,28 +66,42 @@
|
||||
<transition name="list" appear>
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<div slot="title">
|
||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="siderDrawer.theme">
|
||||
<a-menu-item key="export">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetInbounds">
|
||||
<a-icon type="reload"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetClients">
|
||||
<a-icon type="file-done"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delDepletedClients">
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
<a-row>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="siderDrawer.theme">
|
||||
<a-menu-item key="export">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetInbounds">
|
||||
<a-icon type="reload"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetClients">
|
||||
<a-icon type="file-done"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delDepletedClients">
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :lg="12" style="text-align: right;">
|
||||
<a-select v-model="refreshInterval"
|
||||
v-if="isRefreshEnabled"
|
||||
@change="changeRefreshInterval"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
||||
</a-select>
|
||||
<a-icon type="sync" :spin="isRefreshEnabled"></a-icon>
|
||||
<a-switch v-model="isRefreshEnabled" @change="toggleRefresh"></a-switch>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input>
|
||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||
@@ -312,25 +326,22 @@
|
||||
defaultCert: '',
|
||||
defaultKey: '',
|
||||
clientCount: {},
|
||||
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||
},
|
||||
methods: {
|
||||
loading(spinning=true) {
|
||||
this.spinning = spinning;
|
||||
},
|
||||
async getDBInbounds() {
|
||||
this.loading();
|
||||
const msg = await HttpUtil.post('/xui/inbound/list');
|
||||
this.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
this.setInbounds(msg.obj);
|
||||
this.searchKey = '';
|
||||
},
|
||||
async getDefaultSettings() {
|
||||
this.loading();
|
||||
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
||||
this.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
@@ -342,17 +353,16 @@
|
||||
setInbounds(dbInbounds) {
|
||||
this.inbounds.splice(0);
|
||||
this.dbInbounds.splice(0);
|
||||
this.searchedInbounds.splice(0);
|
||||
for (const inbound of dbInbounds) {
|
||||
const dbInbound = new DBInbound(inbound);
|
||||
to_inbound = dbInbound.toInbound()
|
||||
this.inbounds.push(to_inbound);
|
||||
this.dbInbounds.push(dbInbound);
|
||||
this.searchedInbounds.push(dbInbound);
|
||||
if([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol) ){
|
||||
this.clientCount[inbound.id] = this.getClientCounts(inbound,to_inbound);
|
||||
}
|
||||
}
|
||||
this.searchInbounds(this.searchKey);
|
||||
},
|
||||
getClientCounts(dbInbound,inbound){
|
||||
let clientCount = 0,active = [], deactive = [], depleted = [], expiring = [];
|
||||
@@ -785,6 +795,25 @@
|
||||
}
|
||||
txtModal.show('{{ i18n "pages.inbounds.export"}}',copyText,'All-Inbounds');
|
||||
},
|
||||
async startDataRefreshLoop() {
|
||||
while (this.isRefreshEnabled) {
|
||||
try {
|
||||
await this.getDBInbounds();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
await PromiseUtil.sleep(this.refreshInterval);
|
||||
}
|
||||
},
|
||||
toggleRefresh() {
|
||||
localStorage.setItem("isRefreshEnabled", this.isRefreshEnabled);
|
||||
if (this.isRefreshEnabled) {
|
||||
this.startDataRefreshLoop();
|
||||
}
|
||||
},
|
||||
changeRefreshInterval(){
|
||||
localStorage.setItem("refreshInterval", this.refreshInterval);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
searchKey: debounce(function (newVal) {
|
||||
@@ -792,8 +821,15 @@
|
||||
}, 500)
|
||||
},
|
||||
mounted() {
|
||||
this.loading();
|
||||
this.getDefaultSettings();
|
||||
this.getDBInbounds();
|
||||
if (this.isRefreshEnabled) {
|
||||
this.startDataRefreshLoop();
|
||||
}
|
||||
else {
|
||||
this.getDBInbounds();
|
||||
}
|
||||
this.loading(false);
|
||||
},
|
||||
computed: {
|
||||
total() {
|
||||
|
||||
@@ -113,6 +113,9 @@
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-divider>{{ i18n "pages.setting.completeTemplate"}}</a-divider>
|
||||
<a-space direction="horizontal" style="padding: 0 20px">
|
||||
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.setting.resetDefaultConfig" }}</a-button>
|
||||
</a-space>
|
||||
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigTemplate"}}' desc='{{ i18n "pages.setting.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
||||
</a-list>
|
||||
</a-tab-pane>
|
||||
@@ -201,7 +204,16 @@
|
||||
await PromiseUtil.sleep(5000);
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.getAllSetting();
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -326,3 +327,12 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||
}
|
||||
return common.Combine(errs...)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetDefaultXrayConfig() (interface{}, error) {
|
||||
var jsonData interface{}
|
||||
err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jsonData, nil
|
||||
}
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
"save" = "Save"
|
||||
"restartPanel" = "Restart Panel"
|
||||
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please go to the server to view the panel log information"
|
||||
"resetDefaultConfig" = "Reset to default config"
|
||||
"panelConfig" = "Panel Configuration"
|
||||
"userSetting" = "User Setting"
|
||||
"xrayConfiguration" = "xray Configuration"
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
"save" = "ذخیره"
|
||||
"restartPanel" = "ریستارت پنل"
|
||||
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
||||
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
|
||||
"panelConfig" = "تنظیمات پنل"
|
||||
"userSetting" = "تنظیمات مدیر"
|
||||
"xrayConfiguration" = "تنظیمات Xray"
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
"save" = "保存配置"
|
||||
"restartPanel" = "重启面板"
|
||||
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
|
||||
"resetDefaultConfig" = "重置为默认配置"
|
||||
"panelConfig" = "面板配置"
|
||||
"userSetting" = "用户设置"
|
||||
"xrayConfiguration" = "xray 相关设置"
|
||||
|
||||
Reference in New Issue
Block a user