mirror of
https://github.com/alireza0/x-ui.git
synced 2026-06-28 03:30:48 +00:00
[feat] GeoData object
Some checks failed
Release X-UI / build (386) (push) Has been cancelled
Release X-UI / build (amd64) (push) Has been cancelled
Release X-UI / build (arm64) (push) Has been cancelled
Release X-UI / build (armv5) (push) Has been cancelled
Release X-UI / build (armv6) (push) Has been cancelled
Release X-UI / build (armv7) (push) Has been cancelled
Release X-UI / build (s390x) (push) Has been cancelled
Release X-UI / Build for Windows (push) Has been cancelled
Some checks failed
Release X-UI / build (386) (push) Has been cancelled
Release X-UI / build (amd64) (push) Has been cancelled
Release X-UI / build (arm64) (push) Has been cancelled
Release X-UI / build (armv5) (push) Has been cancelled
Release X-UI / build (armv6) (push) Has been cancelled
Release X-UI / build (armv7) (push) Has been cancelled
Release X-UI / build (s390x) (push) Has been cancelled
Release X-UI / Build for Windows (push) Has been cancelled
This commit is contained in:
parent
682077f5c8
commit
75858aadd6
8 changed files with 314 additions and 3 deletions
|
|
@ -692,6 +692,52 @@
|
|||
</a-table>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tpl-geodata" tab='{{ i18n "pages.xray.geodata.title"}}' style="padding-top: 20px;" force-render="true">
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.geodata.enable" }}' desc='{{ i18n "pages.xray.geodata.enableDesc" }}' v-model="enableGeoData"></setting-list-item>
|
||||
<template v-if="enableGeoData">
|
||||
<setting-list-item type="text" title='{{ i18n "pages.xray.geodata.cron" }}' desc='{{ i18n "pages.xray.geodata.cronDesc" }}' v-model="geodataCron"></setting-list-item>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.xray.geodata.outbound" }}' description='{{ i18n "pages.xray.geodata.outboundDesc" }}' />
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-select v-model="geodataOutbound" allow-clear style="width: 100%"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="">Routing</a-select-option>
|
||||
<a-select-option v-for="tag in geodataOutboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
<a-button type="primary" icon="plus" @click="addGeoDataAsset()" style="margin-bottom: 10px;">{{ i18n "pages.xray.geodata.addAsset" }}</a-button>
|
||||
<a-table :columns="geodataAssetColumns" bordered v-if="geodataAssets.length>0"
|
||||
:row-key="(r, index) => index"
|
||||
:data-source="geodataAssets"
|
||||
:scroll="isMobile ? {} : { x: 200 }"
|
||||
:pagination="false"
|
||||
:indent-size="0"
|
||||
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
||||
<template slot="action" slot-scope="text, asset, index">
|
||||
[[ index+1 ]]
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item @click="editGeoDataAsset(index)">
|
||||
<a-icon type="edit"></a-icon>
|
||||
{{ i18n "edit" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="deleteGeoDataAsset(index)">
|
||||
<span style="color: #FF4D4F">
|
||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||
</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</a-table>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
|
||||
<a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta>
|
||||
<a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''">
|
||||
|
|
@ -716,6 +762,7 @@
|
|||
{{template "balancerModal"}}
|
||||
{{template "dnsModal"}}
|
||||
{{template "fakednsModal"}}
|
||||
{{template "geodataAssetModal"}}
|
||||
{{template "warpModal"}}
|
||||
<script>
|
||||
const rulesColumns = [
|
||||
|
|
@ -772,6 +819,12 @@
|
|||
{ title: '{{ i18n "pages.xray.fakedns.poolSize"}}', dataIndex: 'poolSize', align: 'center', width: 50 },
|
||||
];
|
||||
|
||||
const geodataAssetColumns = [
|
||||
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||
{ title: '{{ i18n "pages.xray.geodata.url"}}', dataIndex: 'url', align: 'center', width: 80, ellipsis: true },
|
||||
{ title: '{{ i18n "pages.xray.geodata.file"}}', dataIndex: 'file', align: 'center', width: 40 },
|
||||
];
|
||||
|
||||
const app = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#app',
|
||||
|
|
@ -1314,6 +1367,36 @@
|
|||
fakeDns.splice(index,1);
|
||||
this.fakeDns = fakeDns;
|
||||
},
|
||||
addGeoDataAsset() {
|
||||
geodataAssetModal.show({
|
||||
title: '{{ i18n "pages.xray.geodata.addAsset" }}',
|
||||
confirm: (asset) => {
|
||||
assets = this.geodataAssets;
|
||||
assets.push(asset);
|
||||
this.geodataAssets = assets;
|
||||
geodataAssetModal.close();
|
||||
},
|
||||
isEdit: false
|
||||
});
|
||||
},
|
||||
editGeoDataAsset(index) {
|
||||
geodataAssetModal.show({
|
||||
title: '{{ i18n "pages.xray.geodata.editAsset" }} #' + (index + 1),
|
||||
asset: this.geodataAssets[index],
|
||||
confirm: (asset) => {
|
||||
assets = this.geodataAssets;
|
||||
assets[index] = asset;
|
||||
this.geodataAssets = assets;
|
||||
geodataAssetModal.close();
|
||||
},
|
||||
isEdit: true
|
||||
});
|
||||
},
|
||||
deleteGeoDataAsset(index) {
|
||||
assets = this.geodataAssets;
|
||||
assets.splice(index, 1);
|
||||
this.geodataAssets = assets;
|
||||
},
|
||||
addRule(){
|
||||
ruleModal.show({
|
||||
title: '{{ i18n "pages.xray.rules.add"}}',
|
||||
|
|
@ -1707,6 +1790,83 @@
|
|||
newTemplateSettings.fakedns = newValue.length >0 ? newValue : null;
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
},
|
||||
geodataSettings: function () {
|
||||
if (!this.templateSettings) return null;
|
||||
return this.templateSettings.geodata ?? this.templateSettings.geoData ?? null;
|
||||
},
|
||||
enableGeoData: {
|
||||
get: function () { return this.geodataSettings != null; },
|
||||
set: function (newValue) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
if (newValue) {
|
||||
if (!newTemplateSettings.geodata) {
|
||||
newTemplateSettings.geodata = newTemplateSettings.geoData ?? { assets: [] };
|
||||
delete newTemplateSettings.geoData;
|
||||
}
|
||||
} else {
|
||||
delete newTemplateSettings.geodata;
|
||||
delete newTemplateSettings.geoData;
|
||||
}
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
},
|
||||
geodataCron: {
|
||||
get: function () {
|
||||
const g = this.geodataSettings;
|
||||
return g && g.cron ? g.cron : '';
|
||||
},
|
||||
set: function (newValue) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
if (!newTemplateSettings.geodata) {
|
||||
newTemplateSettings.geodata = newTemplateSettings.geoData ?? { assets: [] };
|
||||
delete newTemplateSettings.geoData;
|
||||
}
|
||||
if (newValue) {
|
||||
newTemplateSettings.geodata.cron = newValue;
|
||||
} else {
|
||||
delete newTemplateSettings.geodata.cron;
|
||||
}
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
},
|
||||
geodataOutbound: {
|
||||
get: function () {
|
||||
const g = this.geodataSettings;
|
||||
return g && g.outbound ? g.outbound : '';
|
||||
},
|
||||
set: function (newValue) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
if (!newTemplateSettings.geodata) {
|
||||
newTemplateSettings.geodata = newTemplateSettings.geoData ?? { assets: [] };
|
||||
delete newTemplateSettings.geoData;
|
||||
}
|
||||
if (newValue) {
|
||||
newTemplateSettings.geodata.outbound = newValue;
|
||||
} else {
|
||||
delete newTemplateSettings.geodata.outbound;
|
||||
}
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
},
|
||||
geodataOutboundTags: function () {
|
||||
if (!this.templateSettings) return [];
|
||||
return this.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map((o) => o.tag);
|
||||
},
|
||||
geodataAssets: {
|
||||
get: function () {
|
||||
const g = this.geodataSettings;
|
||||
return g && g.assets ? g.assets : [];
|
||||
},
|
||||
set: function (newValue) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
if (!newTemplateSettings.geodata) {
|
||||
newTemplateSettings.geodata = newTemplateSettings.geoData ?? { assets: [] };
|
||||
delete newTemplateSettings.geoData;
|
||||
}
|
||||
newTemplateSettings.geodata.assets = newValue;
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
79
web/html/xui/xray_geodata_asset_modal.html
Normal file
79
web/html/xui/xray_geodata_asset_modal.html
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
{{define "geodataAssetModal"}}
|
||||
<a-modal id="geodata-asset-modal" v-model="geodataAssetModal.visible" :title="geodataAssetModal.title" @ok="geodataAssetModal.ok"
|
||||
:confirm-loading="geodataAssetModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||
:ok-button-props="{ props: { disabled: !geodataAssetModal.isValid } }"
|
||||
:ok-text="geodataAssetModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.xray.geodata.url" }}' has-feedback
|
||||
:validate-status="geodataAssetModal.invalidUrl ? 'warning' : 'success'">
|
||||
<a-input v-model.trim="geodataAssetModal.asset.url" @change="geodataAssetModal.check()"
|
||||
placeholder="https://example.com/geoip.dat"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.geodata.file" }}' has-feedback
|
||||
:validate-status="geodataAssetModal.emptyFile ? 'warning' : 'success'">
|
||||
<a-input v-model.trim="geodataAssetModal.asset.file" @change="geodataAssetModal.check()"
|
||||
placeholder="geoip.dat"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<script>
|
||||
const geodataAssetModal = {
|
||||
title: '',
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
okText: '{{ i18n "confirm" }}',
|
||||
isEdit: false,
|
||||
confirm: null,
|
||||
invalidUrl: false,
|
||||
emptyFile: false,
|
||||
isValid: false,
|
||||
asset: {
|
||||
url: '',
|
||||
file: '',
|
||||
},
|
||||
ok() {
|
||||
if (!geodataAssetModal.check()) return;
|
||||
ObjectUtil.execute(geodataAssetModal.confirm, {
|
||||
url: geodataAssetModal.asset.url,
|
||||
file: geodataAssetModal.asset.file,
|
||||
});
|
||||
},
|
||||
show({ title = '', okText = '{{ i18n "confirm" }}', asset, confirm = (asset) => { }, isEdit = false }) {
|
||||
geodataAssetModal.title = title;
|
||||
geodataAssetModal.okText = okText;
|
||||
geodataAssetModal.confirm = confirm;
|
||||
geodataAssetModal.visible = true;
|
||||
geodataAssetModal.isEdit = isEdit;
|
||||
if (isEdit && asset) {
|
||||
geodataAssetModal.asset = { url: asset.url ?? '', file: asset.file ?? '' };
|
||||
} else {
|
||||
geodataAssetModal.asset = { url: '', file: '' };
|
||||
}
|
||||
geodataAssetModal.check();
|
||||
},
|
||||
close() {
|
||||
geodataAssetModal.visible = false;
|
||||
geodataAssetModal.loading(false);
|
||||
},
|
||||
loading(loading = true) {
|
||||
geodataAssetModal.confirmLoading = loading;
|
||||
},
|
||||
check() {
|
||||
const url = geodataAssetModal.asset.url.trim();
|
||||
const file = geodataAssetModal.asset.file.trim();
|
||||
geodataAssetModal.emptyFile = file.length === 0;
|
||||
geodataAssetModal.invalidUrl = url.length === 0 || !url.toLowerCase().startsWith('https://');
|
||||
geodataAssetModal.isValid = !geodataAssetModal.emptyFile && !geodataAssetModal.invalidUrl;
|
||||
return geodataAssetModal.isValid;
|
||||
},
|
||||
};
|
||||
|
||||
new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#geodata-asset-modal',
|
||||
data: {
|
||||
geodataAssetModal: geodataAssetModal,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
|
|
@ -660,6 +660,19 @@
|
|||
"ipPool" = "IP Pool Subnet"
|
||||
"poolSize" = "Pool Size"
|
||||
|
||||
[pages.xray.geodata]
|
||||
"title" = "GeoData"
|
||||
"enable" = "Enable GeoData"
|
||||
"enableDesc" = "Reload geodata files on a schedule and download new .dat files before reloading."
|
||||
"cron" = "Cron Schedule"
|
||||
"cronDesc" = "5-field cron expression in local time, e.g. 0 4 * * * for daily at 04:00. Leave empty to disable the schedule."
|
||||
"outbound" = "Download Outbound"
|
||||
"outboundDesc" = "Outbound tag used when downloading geodata. Empty means routing decides the path."
|
||||
"addAsset" = "Add Asset"
|
||||
"editAsset" = "Edit Asset"
|
||||
"url" = "URL"
|
||||
"file" = "File"
|
||||
|
||||
[tgbot]
|
||||
"noResult" = "❗ No result!"
|
||||
"wentWrong" = "❌ Something went wrong!"
|
||||
|
|
|
|||
|
|
@ -660,6 +660,19 @@
|
|||
"ipPool" = "زیرشبکه استخر آیپی"
|
||||
"poolSize" = "اندازه استخر"
|
||||
|
||||
[pages.xray.geodata]
|
||||
"title" = "GeoData"
|
||||
"enable" = "فعالسازی GeoData"
|
||||
"enableDesc" = "بارگذاری مجدد فایلهای geodata طبق زمانبندی و دانلود فایلهای .dat جدید قبل از reload."
|
||||
"cron" = "زمانبندی Cron"
|
||||
"cronDesc" = "عبارت cron پنجفیلدی بر اساس زمان محلی، مثلاً 0 4 * * * برای هر روز ساعت ۴. خالی = بدون زمانبندی."
|
||||
"outbound" = "خروجی دانلود"
|
||||
"outboundDesc" = "برچسب outbound برای دانلود geodata. خالی یعنی مسیریابی مسیر را تعیین میکند."
|
||||
"addAsset" = "افزودن فایل"
|
||||
"editAsset" = "ویرایش فایل"
|
||||
"url" = "آدرس"
|
||||
"file" = "نام فایل"
|
||||
|
||||
[tgbot]
|
||||
"noResult" = "❗نتیجهای یافت نشد"
|
||||
"wentWrong" = "❌ مشکلی رخ دادهاست"
|
||||
|
|
|
|||
|
|
@ -660,6 +660,19 @@
|
|||
"ipPool" = "Подсеть пула IP"
|
||||
"poolSize" = "Размер пула"
|
||||
|
||||
[pages.xray.geodata]
|
||||
"title" = "GeoData"
|
||||
"enable" = "Включить GeoData"
|
||||
"enableDesc" = "Перезагружать geodata по расписанию и скачивать новые .dat файлы перед перезагрузкой."
|
||||
"cron" = "Расписание Cron"
|
||||
"cronDesc" = "5-полевое cron-выражение в локальном времени, например 0 4 * * * для ежедневного запуска в 04:00. Пусто — без расписания."
|
||||
"outbound" = "Исходящий для загрузки"
|
||||
"outboundDesc" = "Тег исходящего для загрузки geodata. Пусто — маршрутизация выбирает путь."
|
||||
"addAsset" = "Добавить ресурс"
|
||||
"editAsset" = "Редактировать ресурс"
|
||||
"url" = "URL"
|
||||
"file" = "Файл"
|
||||
|
||||
[tgbot]
|
||||
"noResult" = "❗ Нет результатов!"
|
||||
"wentWrong" = "❌ Что-то пошло не так!"
|
||||
|
|
|
|||
|
|
@ -660,6 +660,19 @@
|
|||
"ipPool" = "Mạng con nhóm IP"
|
||||
"poolSize" = "Kích thước bể bơi"
|
||||
|
||||
[pages.xray.geodata]
|
||||
"title" = "GeoData"
|
||||
"enable" = "Bật GeoData"
|
||||
"enableDesc" = "Tải lại tệp geodata theo lịch và tải xuống tệp .dat mới trước khi reload."
|
||||
"cron" = "Lịch Cron"
|
||||
"cronDesc" = "Biểu thức cron 5 trường theo giờ địa phương, ví dụ 0 4 * * * chạy hàng ngày lúc 04:00. Để trống để tắt lịch."
|
||||
"outbound" = "Outbound tải xuống"
|
||||
"outboundDesc" = "Thẻ outbound dùng khi tải geodata. Để trống thì routing quyết định đường đi."
|
||||
"addAsset" = "Thêm tài nguyên"
|
||||
"editAsset" = "Sửa tài nguyên"
|
||||
"url" = "URL"
|
||||
"file" = "Tệp"
|
||||
|
||||
[tgbot]
|
||||
"noResult" = "❗ Không có kết quả!"
|
||||
"wentWrong" = "❌ Đã xảy ra lỗi!"
|
||||
|
|
|
|||
|
|
@ -660,6 +660,19 @@
|
|||
"ipPool" = "IP 池子网"
|
||||
"poolSize" = "池大小"
|
||||
|
||||
[pages.xray.geodata]
|
||||
"title" = "GeoData"
|
||||
"enable" = "启用 GeoData"
|
||||
"enableDesc" = "按计划重新加载 geodata 文件,并在重新加载前下载新的 .dat 文件。"
|
||||
"cron" = "Cron 计划"
|
||||
"cronDesc" = "本地时间的 5 字段 cron 表达式,例如 0 4 * * * 表示每天 04:00。留空则禁用计划任务。"
|
||||
"outbound" = "下载出站"
|
||||
"outboundDesc" = "下载 geodata 时使用的出站标签。留空则由路由决定路径。"
|
||||
"addAsset" = "添加资源"
|
||||
"editAsset" = "编辑资源"
|
||||
"url" = "URL"
|
||||
"file" = "文件"
|
||||
|
||||
[tgbot]
|
||||
"noResult" = "❗ 没有结果!"
|
||||
"wentWrong" = "❌ 出了点问题!"
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ type Config struct {
|
|||
FakeDNS json_util.RawMessage `json:"fakedns"`
|
||||
Observatory json_util.RawMessage `json:"observatory"`
|
||||
BurstObservatory json_util.RawMessage `json:"burstObservatory"`
|
||||
Metrics json_util.RawMessage `json:"metrics"`
|
||||
Metrics json_util.RawMessage `json:"metrics,omitEmpty"`
|
||||
GeoData json_util.RawMessage `json:"geodata,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Config) Equals(other *Config) bool {
|
||||
|
|
@ -55,14 +56,20 @@ func (c *Config) Equals(other *Config) bool {
|
|||
if !bytes.Equal(c.Stats, other.Stats) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(c.Reverse, other.Reverse) {
|
||||
if !bytes.Equal(c.FakeDNS, other.FakeDNS) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(c.FakeDNS, other.FakeDNS) {
|
||||
if !bytes.Equal(c.Observatory, other.Observatory) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(c.BurstObservatory, other.BurstObservatory) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(c.Metrics, other.Metrics) {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(c.GeoData, other.GeoData) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue