mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-19 15:25:49 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f20b71c45 | ||
|
|
4779b37e6e | ||
|
|
7a20d2c83c | ||
|
|
7ead999f12 | ||
|
|
812c9fbe17 | ||
|
|
5adbfd9528 | ||
|
|
1b93c7c0f2 | ||
|
|
58e7f51313 | ||
|
|
78e97af4db |
@@ -1 +1 @@
|
|||||||
1.3.0
|
1.3.1
|
||||||
@@ -11,16 +11,6 @@
|
|||||||
<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-sub-menu>
|
|
||||||
<template slot="title">
|
|
||||||
<a-icon type="link"></a-icon>
|
|
||||||
<span>{{ i18n "menu.link"}}</span>
|
|
||||||
</template>
|
|
||||||
<a-menu-item key="https://github.com/alireza0/x-ui/">
|
|
||||||
<a-icon type="github"></a-icon>
|
|
||||||
<span>Github</span>
|
|
||||||
</a-menu-item>
|
|
||||||
</a-sub-menu>
|
|
||||||
<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>
|
||||||
|
|||||||
@@ -97,9 +97,9 @@
|
|||||||
<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>
|
||||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||||
<a-button type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin: 0 10px">+</a-button>
|
|
||||||
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)">-</a-button>
|
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
|
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
|
||||||
|
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -104,7 +104,17 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
<a-input v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
||||||
|
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid">
|
||||||
|
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
||||||
|
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
||||||
|
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
||||||
|
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
<a-switch v-model="enableFilter"
|
||||||
|
checked-children="{{ i18n "search" }}" un-checked-children="{{ i18n "filter" }}"
|
||||||
|
@change="toggleFilter">
|
||||||
|
</a-switch>
|
||||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||||
:data-source="searchedInbounds"
|
:data-source="searchedInbounds"
|
||||||
:loading="spinning" :scroll="{ x: 1300 }"
|
:loading="spinning" :scroll="{ x: 1300 }"
|
||||||
@@ -319,6 +329,8 @@
|
|||||||
inbounds: [],
|
inbounds: [],
|
||||||
dbInbounds: [],
|
dbInbounds: [],
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
|
enableFilter: false,
|
||||||
|
filterBy: '',
|
||||||
searchedInbounds: [],
|
searchedInbounds: [],
|
||||||
expireDiff: 0,
|
expireDiff: 0,
|
||||||
trafficDiff: 0,
|
trafficDiff: 0,
|
||||||
@@ -327,7 +339,7 @@
|
|||||||
clientCount: {},
|
clientCount: {},
|
||||||
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
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
@@ -362,11 +374,15 @@
|
|||||||
to_inbound = dbInbound.toInbound()
|
to_inbound = dbInbound.toInbound()
|
||||||
this.inbounds.push(to_inbound);
|
this.inbounds.push(to_inbound);
|
||||||
this.dbInbounds.push(dbInbound);
|
this.dbInbounds.push(dbInbound);
|
||||||
if ([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol)) {
|
if ([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(inbound.protocol)) {
|
||||||
this.clientCount[inbound.id] = this.getClientCounts(inbound, to_inbound);
|
this.clientCount[inbound.id] = this.getClientCounts(inbound, to_inbound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.searchInbounds(this.searchKey);
|
if(this.enableFilter){
|
||||||
|
this.filterInbounds();
|
||||||
|
} else {
|
||||||
|
this.searchInbounds(this.searchKey);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getClientCounts(dbInbound, inbound) {
|
getClientCounts(dbInbound, inbound) {
|
||||||
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [];
|
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [];
|
||||||
@@ -424,6 +440,38 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
filterInbounds() {
|
||||||
|
if (ObjectUtil.isEmpty(this.filterBy)) {
|
||||||
|
this.searchedInbounds = this.dbInbounds.slice();
|
||||||
|
} else {
|
||||||
|
this.searchedInbounds.splice(0, this.searchedInbounds.length);
|
||||||
|
this.dbInbounds.forEach(inbound => {
|
||||||
|
const newInbound = new DBInbound(inbound);
|
||||||
|
const inboundSettings = JSON.parse(inbound.settings);
|
||||||
|
if (this.clientCount[inbound.id] && this.clientCount[inbound.id].hasOwnProperty(this.filterBy)){
|
||||||
|
const list = this.clientCount[inbound.id][this.filterBy];
|
||||||
|
if (list.length > 0) {
|
||||||
|
const filteredSettings = { "clients": [] };
|
||||||
|
inboundSettings.clients.forEach(client => {
|
||||||
|
if (list.includes(client.email)) {
|
||||||
|
filteredSettings.clients.push(client);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
newInbound.settings = Inbound.Settings.fromJson(inbound.protocol, filteredSettings);
|
||||||
|
this.searchedInbounds.push(newInbound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleFilter(){
|
||||||
|
if(this.enableFilter) {
|
||||||
|
this.searchKey = '';
|
||||||
|
} else {
|
||||||
|
this.filterBy = '';
|
||||||
|
this.searchedInbounds = this.dbInbounds.slice();
|
||||||
|
}
|
||||||
|
},
|
||||||
generalActions(action) {
|
generalActions(action) {
|
||||||
switch (action.key) {
|
switch (action.key) {
|
||||||
case "export":
|
case "export":
|
||||||
|
|||||||
@@ -19,10 +19,6 @@
|
|||||||
.ant-list-item {
|
.ant-list-item {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
:not(.ant-card-dark)>.ant-tabs-top-bar {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak>
|
||||||
@@ -35,7 +31,7 @@
|
|||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
<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-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-tabs default-active-key="1" :class="themeSwitcher.darkCardClass">
|
<a-tabs default-active-key="1" :class="themeSwitcher.darkCardClass" :style="!themeSwitcher.isDarkTheme? 'background: white':''">
|
||||||
<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-row :xs="24" :sm="24" :lg="12">
|
||||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 20px 20px; text-align: center;">
|
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 20px 20px; text-align: center;">
|
||||||
@@ -217,6 +213,11 @@
|
|||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectIPs"}}' v-model="manualDirectIPs"></setting-list-item>
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectIPs"}}' v-model="manualDirectIPs"></setting-list-item>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectDomains"}}' v-model="manualDirectDomains"></setting-list-item>
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectDomains"}}' v-model="manualDirectDomains"></setting-list-item>
|
||||||
</a-collapse-panel>
|
</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-collapse>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.settings.templates.advancedTemplate"}}' style="padding-top: 20px;">
|
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.settings.templates.advancedTemplate"}}' style="padding-top: 20px;">
|
||||||
@@ -233,9 +234,6 @@
|
|||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.settings.templates.completeTemplate"}}' style="padding-top: 20px;">
|
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.settings.templates.completeTemplate"}}' style="padding-top: 20px;">
|
||||||
<a-space direction="horizontal" style="padding: 0 20px">
|
|
||||||
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
|
||||||
</a-space>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigTemplate"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
<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-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
@@ -300,7 +298,6 @@
|
|||||||
},
|
},
|
||||||
ips: {
|
ips: {
|
||||||
local: ["geoip:private"],
|
local: ["geoip:private"],
|
||||||
google: ["geoip:google"],
|
|
||||||
cn: ["geoip:cn"],
|
cn: ["geoip:cn"],
|
||||||
ir: ["geoip:ir"],
|
ir: ["geoip:ir"],
|
||||||
ru: ["geoip:ru"],
|
ru: ["geoip:ru"],
|
||||||
@@ -310,12 +307,10 @@
|
|||||||
"geosite:category-ads-all",
|
"geosite:category-ads-all",
|
||||||
"geosite:category-ads",
|
"geosite:category-ads",
|
||||||
"geosite:google-ads",
|
"geosite:google-ads",
|
||||||
"geosite:spotify-ads"
|
"geosite:spotify-ads",
|
||||||
|
"ext:iran.dat:ads"
|
||||||
],
|
],
|
||||||
porn: ["geosite:category-porn"],
|
|
||||||
openai: ["geosite:openai"],
|
|
||||||
google: ["geosite:google"],
|
google: ["geosite:google"],
|
||||||
spotify: ["geosite:spotify"],
|
|
||||||
netflix: ["geosite:netflix"],
|
netflix: ["geosite:netflix"],
|
||||||
cn: [
|
cn: [
|
||||||
"geosite:cn",
|
"geosite:cn",
|
||||||
@@ -329,16 +324,15 @@
|
|||||||
"regexp:.*\\.ir$",
|
"regexp:.*\\.ir$",
|
||||||
"ext:iran.dat:ir",
|
"ext:iran.dat:ir",
|
||||||
"ext:iran.dat:other",
|
"ext:iran.dat:other",
|
||||||
"ext:iran.dat:ads",
|
|
||||||
"geosite:category-ir"
|
"geosite:category-ir"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
familyProtectDNS: {
|
familyProtectDNS: {
|
||||||
"servers": [
|
"servers": [
|
||||||
"1.1.1.2",
|
"1.1.1.3",
|
||||||
"1.0.0.2",
|
"1.0.0.3",
|
||||||
"94.140.14.14",
|
"94.140.14.15",
|
||||||
"94.140.15.15"
|
"94.140.15.16"
|
||||||
],
|
],
|
||||||
"queryStrategy": "UseIPv4"
|
"queryStrategy": "UseIPv4"
|
||||||
},
|
},
|
||||||
@@ -511,7 +505,7 @@
|
|||||||
freedomStrategy: {
|
freedomStrategy: {
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!this.templateSettings) return "AsIs";
|
if (!this.templateSettings) return "AsIs";
|
||||||
freedomOutbound = this.templateSettings.outbounds.find((o) => o.tag === "direct");
|
freedomOutbound = this.templateSettings.outbounds.find((o) => o.protocol === "freedom" && !o.tag);
|
||||||
if (!freedomOutbound) return "AsIs";
|
if (!freedomOutbound) return "AsIs";
|
||||||
if (!freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs";
|
if (!freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs";
|
||||||
return freedomOutbound.settings.domainStrategy;
|
return freedomOutbound.settings.domainStrategy;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"enable" = "Enable"
|
"enable" = "Enable"
|
||||||
"protocol" = "Protocol"
|
"protocol" = "Protocol"
|
||||||
"search" = "Search"
|
"search" = "Search"
|
||||||
|
"filter" = "Filter"
|
||||||
|
|
||||||
"loading" = "Loading"
|
"loading" = "Loading"
|
||||||
"second" = "Second"
|
"second" = "Second"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"enable" = "فعال"
|
"enable" = "فعال"
|
||||||
"protocol" = "پروتکل"
|
"protocol" = "پروتکل"
|
||||||
"search" = "جستجو"
|
"search" = "جستجو"
|
||||||
|
"filter" = "فیلتر"
|
||||||
|
|
||||||
"loading" = "در حال بروزرسانی.."
|
"loading" = "در حال بروزرسانی.."
|
||||||
"second" = "ثانیه"
|
"second" = "ثانیه"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"enable" = "включить"
|
"enable" = "включить"
|
||||||
"protocol" = "протокол"
|
"protocol" = "протокол"
|
||||||
"search" = "поиск"
|
"search" = "поиск"
|
||||||
|
"filter" = "Фильтр"
|
||||||
|
|
||||||
"loading" = "загрузка"
|
"loading" = "загрузка"
|
||||||
"second" = "секунда"
|
"second" = "секунда"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"enable" = "启用"
|
"enable" = "启用"
|
||||||
"protocol" = "协议"
|
"protocol" = "协议"
|
||||||
"search" = "搜尋"
|
"search" = "搜尋"
|
||||||
|
"filter" = "过滤器"
|
||||||
|
|
||||||
"loading" = "加载中"
|
"loading" = "加载中"
|
||||||
"second" = "秒"
|
"second" = "秒"
|
||||||
|
|||||||
@@ -276,8 +276,6 @@ func (s *Server) initI18n(engine *gin.Engine) error {
|
|||||||
engine.FuncMap["i18n"] = I18n
|
engine.FuncMap["i18n"] = I18n
|
||||||
|
|
||||||
engine.Use(func(c *gin.Context) {
|
engine.Use(func(c *gin.Context) {
|
||||||
//accept := c.GetHeader("Accept-Language")
|
|
||||||
|
|
||||||
var lang string
|
var lang string
|
||||||
|
|
||||||
if cookie, err := c.Request.Cookie("lang"); err == nil {
|
if cookie, err := c.Request.Cookie("lang"); err == nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user