mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-18 14:55:49 +00:00
完成xray启动
This commit is contained in:
@@ -9,10 +9,11 @@
|
||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/uri/URI.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/models.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/util/common.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/util/utils.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/models.js?{{ .cur_ver }}"></script>
|
||||
<script>
|
||||
const basePath = '{{ .base_path }}';
|
||||
axios.defaults.baseURL = basePath;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{{define "promptModel"}}
|
||||
{{define "promptModal"}}
|
||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
||||
:ok-text="promptModal.okText" cancel-text="取消">
|
||||
|
||||
59
web/html/common/qrcode_modal.html
Normal file
59
web/html/common/qrcode_modal.html
Normal file
@@ -0,0 +1,59 @@
|
||||
{{define "qrcodeModal"}}
|
||||
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
||||
:closable="true" width="300px" :ok-text="qrModal.okText"
|
||||
cancel-text='{{ i18n "close" }}' :ok-button-props="{attrs:{id:'qr-modal-ok-btn'}}">
|
||||
<canvas id="qrCode" style="width: 100%; height: 100%;"></canvas>
|
||||
</a-modal>
|
||||
|
||||
<script>
|
||||
|
||||
const qrModal = {
|
||||
title: '',
|
||||
content: '',
|
||||
okText: '',
|
||||
copyText: '',
|
||||
qrcode: null,
|
||||
clipboard: null,
|
||||
visible: false,
|
||||
show: function (title='', content='', okText='{{ i18n "copy" }}', copyText='') {
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
this.okText = okText;
|
||||
if (ObjectUtil.isEmpty(copyText)) {
|
||||
this.copyText = content;
|
||||
} else {
|
||||
this.copyText = copyText;
|
||||
}
|
||||
this.visible = true;
|
||||
qrModalApp.$nextTick(() => {
|
||||
if (this.clipboard === null) {
|
||||
this.clipboard = new ClipboardJS('#qr-modal-ok-btn', {
|
||||
text: () => this.copyText,
|
||||
});
|
||||
this.clipboard.on('success', () => app.$message.success('{{ i18n "copied" }}'));
|
||||
}
|
||||
if (this.qrcode === null) {
|
||||
this.qrcode = new QRious({
|
||||
element: document.querySelector('#qrCode'),
|
||||
size: 260,
|
||||
value: content,
|
||||
});
|
||||
} else {
|
||||
this.qrcode.value = content;
|
||||
}
|
||||
});
|
||||
},
|
||||
close: function () {
|
||||
this.visible = false;
|
||||
},
|
||||
};
|
||||
|
||||
const qrModalApp = new Vue({
|
||||
el: '#qrcode-modal',
|
||||
data: {
|
||||
qrModal: qrModal,
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
58
web/html/common/text_modal.html
Normal file
58
web/html/common/text_modal.html
Normal file
@@ -0,0 +1,58 @@
|
||||
{{define "textModal"}}
|
||||
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
||||
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
||||
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
||||
@click="downloader.download(txtModal.fileName, txtModal.content)">
|
||||
{{ i18n "download" }} [[ txtModal.fileName ]]
|
||||
</a-button>
|
||||
<a-input type="textarea" v-model="txtModal.content"
|
||||
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
||||
</a-modal>
|
||||
|
||||
<script>
|
||||
|
||||
const txtModal = {
|
||||
title: '',
|
||||
content: '',
|
||||
fileName: '',
|
||||
qrcode: null,
|
||||
clipboard: null,
|
||||
visible: false,
|
||||
show: function (title='', content='', fileName='') {
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
this.fileName = fileName;
|
||||
this.visible = true;
|
||||
textModalApp.$nextTick(() => {
|
||||
if (this.clipboard === null) {
|
||||
this.clipboard = new ClipboardJS('#txt-modal-ok-btn', {
|
||||
text: () => this.content,
|
||||
});
|
||||
this.clipboard.on('success', () => app.$message.success('{{ i18n "copied" }}'));
|
||||
}
|
||||
if (this.qrcode === null) {
|
||||
this.qrcode = new QRious({
|
||||
element: document.querySelector('#qrCode'),
|
||||
size: 260,
|
||||
value: content,
|
||||
});
|
||||
} else {
|
||||
this.qrcode.value = content;
|
||||
}
|
||||
});
|
||||
},
|
||||
close: function () {
|
||||
this.visible = false;
|
||||
},
|
||||
};
|
||||
|
||||
const textModalApp = new Vue({
|
||||
el: '#text-modal',
|
||||
data: {
|
||||
txtModal: txtModal,
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -3,7 +3,7 @@
|
||||
<a-icon type="dashboard"></a-icon>
|
||||
<span>系统状态</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}xui/accounts">
|
||||
<a-menu-item key="{{ .base_path }}xui/inbounds">
|
||||
<a-icon type="user"></a-icon>
|
||||
<span>账号列表</span>
|
||||
</a-menu-item>
|
||||
|
||||
535
web/html/x-ui/inbound_modal.html
Normal file
535
web/html/x-ui/inbound_modal.html
Normal file
@@ -0,0 +1,535 @@
|
||||
{{define "inboundModal"}}
|
||||
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
||||
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||
|
||||
<!-- base -->
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "remark" }}'>
|
||||
<a-input v-model.trim="inModal.inbound.remark"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "enable" }}'>
|
||||
<a-switch v-model="inModal.inbound.enable"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "protocol" }}'>
|
||||
<a-select v-model="inModal.inbound.protocol" style="width: 160px;"
|
||||
@change="protocolChange">
|
||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
监听 IP
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
不懂请保持默认
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input v-model.trim="inModal.inbound.listen"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="端口">
|
||||
<a-input type="number" v-model.number="inModal.inbound.port"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- vmess settings -->
|
||||
<a-form v-if="inModal.inbound.protocol === Protocols.VMESS"
|
||||
layout="inline">
|
||||
<a-form-item label="id">
|
||||
<a-input v-model.trim="inModal.inbound.settings.vmesses[0].id"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="额外 ID">
|
||||
<a-input type="number" v-model.number="inModal.inbound.settings.vmesses[0].alterId"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="禁用不安全加密">
|
||||
<a-switch v-model.number="inModal.inbound.settings.disableInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- vless settings -->
|
||||
<a-form v-if="inModal.inbound.protocol === Protocols.VLESS"
|
||||
layout="inline">
|
||||
<a-form-item label="id">
|
||||
<a-input v-model.trim="inModal.inbound.settings.vlesses[0].id"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="flow">
|
||||
<a-select v-model="inModal.inbound.settings.vlesses[0].flow" style="width: 150px">
|
||||
<a-select-option value="">无</a-select-option>
|
||||
<a-select-option v-for="key in VLESS_FLOW" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<a-form v-if="inModal.inbound.protocol === Protocols.VLESS"
|
||||
layout="inline">
|
||||
<a-form-item label="fallbacks">
|
||||
<a-row>
|
||||
<a-button type="primary" size="small"
|
||||
@click="inModal.inbound.settings.addFallback()">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- vless fallbacks -->
|
||||
<a-form v-for="(fallback, index) in inModal.inbound.settings.fallbacks" layout="inline">
|
||||
<a-form-item>
|
||||
<a-divider>
|
||||
fallback[[ index + 1 ]]
|
||||
<a-icon type="delete" @click="() => inModal.inbound.settings.delFallback(index)" style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
||||
</a-divider>
|
||||
</a-form-item>
|
||||
<a-form-item label="name">
|
||||
<a-input v-model="fallback.name"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="alpn">
|
||||
<a-input v-model="fallback.alpn"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="path">
|
||||
<a-input v-model="fallback.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="dest">
|
||||
<a-input v-model="fallback.dest"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="xver">
|
||||
<a-input type="number" v-model.number="fallback.xver"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- trojan settings -->
|
||||
<a-form v-if="inModal.inbound.protocol === Protocols.TROJAN"
|
||||
layout="inline">
|
||||
<a-form-item label="密码">
|
||||
<a-input v-model.trim="inModal.inbound.settings.clients[0].password"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- shadowsocks -->
|
||||
<a-form v-if="inModal.inbound.protocol === Protocols.SHADOWSOCKS"
|
||||
layout="inline">
|
||||
<a-form-item label="加密">
|
||||
<a-select v-model="inModal.inbound.settings.method" style="width: 165px;">
|
||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="密码">
|
||||
<a-input v-model.trim="inModal.inbound.settings.password"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="网络">
|
||||
<a-select v-model="inModal.inbound.settings.network" style="width: 100px;">
|
||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||
<a-select-option value="tcp">tcp</a-select-option>
|
||||
<a-select-option value="udp">udp</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- stream settings -->
|
||||
<template v-if="inModal.inbound.protocol === Protocols.VMESS
|
||||
|| inModal.inbound.protocol === Protocols.VLESS
|
||||
|| inModal.inbound.protocol === Protocols.SHADOWSOCKS">
|
||||
|
||||
<!-- select stream network -->
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="传输">
|
||||
<a-select v-model="inModal.inbound.stream.network" @change="streamNetworkChange">
|
||||
<a-select-option value="tcp">tcp</a-select-option>
|
||||
<a-select-option value="kcp">kcp</a-select-option>
|
||||
<a-select-option value="ws">ws</a-select-option>
|
||||
<a-select-option value="http">http</a-select-option>
|
||||
<a-select-option value="quic">quic</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- vmess tcp -->
|
||||
<template v-if="inModal.inbound.stream.network === 'tcp'">
|
||||
<!-- vmess tcp type -->
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="http 伪装">
|
||||
<a-switch
|
||||
:checked="inModal.inbound.stream.tcp.type === 'http'"
|
||||
@change="checked => inModal.inbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- vmess tcp request -->
|
||||
<a-form v-if="inModal.inbound.stream.tcp.type === 'http'"
|
||||
layout="inline">
|
||||
<a-form-item label="请求版本">
|
||||
<a-input v-model.trim="inModal.inbound.stream.tcp.request.version"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="请求方法">
|
||||
<a-input v-model.trim="inModal.inbound.stream.tcp.request.method"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="请求路径">
|
||||
<a-row v-for="(path, index) in inModal.inbound.stream.tcp.request.path">
|
||||
<a-input v-model.trim="inModal.inbound.stream.tcp.request.path[index]"></a-input>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item label="请求头">
|
||||
<a-row>
|
||||
<a-button size="small"
|
||||
@click="inModal.inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
<a-input-group v-for="(header, index) in inModal.inbound.stream.tcp.request.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
addon-before="名称"></a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
addon-before="值">
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small"
|
||||
@click="inModal.inbound.stream.tcp.request.removeHeader(index)">
|
||||
-
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- vmess tcp response -->
|
||||
<a-form v-if="inModal.inbound.stream.tcp.type === 'http'"
|
||||
layout="inline">
|
||||
<a-form-item label="响应版本">
|
||||
<a-input v-model.trim="inModal.inbound.stream.tcp.response.version"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="响应状态">
|
||||
<a-input v-model.trim="inModal.inbound.stream.tcp.response.status"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="响应状态说明">
|
||||
<a-input v-model.trim="inModal.inbound.stream.tcp.response.reason"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="响应头">
|
||||
<a-row>
|
||||
<a-button size="small"
|
||||
@click="inModal.inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
<a-input-group v-for="(header, index) in inModal.inbound.stream.tcp.response.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
addon-before="名称"></a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
addon-before="值">
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small"
|
||||
@click="inModal.inbound.stream.tcp.response.removeHeader(index)">
|
||||
-
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!-- vmess kcp -->
|
||||
<a-form v-if="inModal.inbound.stream.network === 'kcp'"
|
||||
layout="inline">
|
||||
<a-form-item label="伪装">
|
||||
<a-select v-model="inModal.inbound.stream.kcp.type" style="width: 280px;">
|
||||
<a-select-option value="none">none(not camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">srtp(camouflage video call)</a-select-option>
|
||||
<a-select-option value="utp">utp(camouflage BT download)</a-select-option>
|
||||
<a-select-option value="wechat-video">wechat-video(camouflage WeChat video)</a-select-option>
|
||||
<a-select-option value="dtls">dtls(camouflage DTLS 1.2 packages)</a-select-option>
|
||||
<a-select-option value="wireguard">wireguard(camouflage wireguard packages)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="密码">
|
||||
<a-input v-model.number="inModal.inbound.stream.kcp.seed"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="mtu">
|
||||
<a-input type="number" v-model.number="inModal.inbound.stream.kcp.mtu"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="tti (ms)">
|
||||
<a-input type="number" v-model.number="inModal.inbound.stream.kcp.tti"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="uplink capacity (MB/S)">
|
||||
<a-input type="number" v-model.number="inModal.inbound.stream.kcp.upCap"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="downlink capacity (MB/S)">
|
||||
<a-input type="number" v-model.number="inModal.inbound.stream.kcp.downCap"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="congestion">
|
||||
<a-switch v-model="inModal.inbound.stream.kcp.congestion"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="read buffer size (MB)">
|
||||
<a-input type="number" v-model.number="inModal.inbound.stream.kcp.readBuffer"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="write buffer size (MB)">
|
||||
<a-input type="number" v-model.number="inModal.inbound.stream.kcp.writeBuffer"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- vmess ws -->
|
||||
<a-form v-if="inModal.inbound.stream.network === 'ws'"
|
||||
layout="inline">
|
||||
<a-form-item label="路径">
|
||||
<a-input v-model.trim="inModal.inbound.stream.ws.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="请求头">
|
||||
<a-row>
|
||||
<a-button size="small"
|
||||
@click="inModal.inbound.stream.ws.addHeader('Host', '')">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
<a-input-group v-for="(header, index) in inModal.inbound.stream.ws.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
addon-before="名称"></a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
addon-before="值">
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small"
|
||||
@click="inModal.inbound.stream.ws.removeHeader(index)">
|
||||
-
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- vmess http -->
|
||||
<a-form v-if="inModal.inbound.stream.network === 'http'"
|
||||
layout="inline">
|
||||
<a-form-item label="路径">
|
||||
<a-input v-model.trim="inModal.inbound.stream.http.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="host">
|
||||
<a-row v-for="(host, index) in inModal.inbound.stream.http.host">
|
||||
<a-input v-model.trim="inModal.inbound.stream.http.host[index]"></a-input>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- vmess quic -->
|
||||
<a-form v-if="inModal.inbound.stream.network === 'quic'"
|
||||
layout="inline">
|
||||
<a-form-item label="加密">
|
||||
<a-select v-model="inModal.inbound.stream.quic.security" style="width: 165px;">
|
||||
<a-select-option value="none">none</a-select-option>
|
||||
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="密码">
|
||||
<a-input v-model.trim="inModal.inbound.stream.quic.key"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="伪装">
|
||||
<a-select v-model="inModal.inbound.stream.quic.type" style="width: 280px;">
|
||||
<a-select-option value="none">none(not camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">srtp(camouflage video call)</a-select-option>
|
||||
<a-select-option value="utp">utp(camouflage BT download)</a-select-option>
|
||||
<a-select-option value="wechat-video">wechat-video(camouflage WeChat video)</a-select-option>
|
||||
<a-select-option value="dtls">dtls(camouflage DTLS 1.2 packages)</a-select-option>
|
||||
<a-select-option value="wireguard">wireguard(camouflage wireguard packages)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!-- dokodemo-door -->
|
||||
<a-form v-if="inModal.inbound.protocol === Protocols.DOKODEMO"
|
||||
layout="inline">
|
||||
<a-form-item label="目标地址">
|
||||
<a-input v-model.trim="inModal.inbound.settings.address"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="目标端口">
|
||||
<a-input type="number" v-model.number="inModal.inbound.settings.port"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="网络">
|
||||
<a-select v-model="inModal.inbound.settings.network" style="width: 100px;">
|
||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||
<a-select-option value="tcp">tcp</a-select-option>
|
||||
<a-select-option value="udp">udp</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- socks -->
|
||||
<a-form v-if="inModal.inbound.protocol === Protocols.SOCKS"
|
||||
layout="inline">
|
||||
<a-form-item label="密码认证">
|
||||
<a-switch :checked="inModal.inbound.settings.auth === 'password'"
|
||||
@change="checked => inModal.inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||
</a-form-item>
|
||||
<template v-if="inModal.inbound.settings.auth === 'password'">
|
||||
<a-form-item label="用户名">
|
||||
<a-input v-model.trim="inModal.inbound.settings.accounts[0].user"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="密码">
|
||||
<a-input v-model.trim="inModal.inbound.settings.accounts[0].pass"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item label="启用 udp">
|
||||
<a-switch v-model="inModal.inbound.settings.udp"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inModal.inbound.settings.udp"
|
||||
label="IP">
|
||||
<a-input v-model.trim="inModal.inbound.settings.ip"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- http -->
|
||||
<a-form v-if="inModal.inbound.protocol === Protocols.HTTP"
|
||||
layout="inline">
|
||||
<a-form-item label="用户名">
|
||||
<a-input v-model.trim="inModal.inbound.settings.accounts[0].user"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="密码">
|
||||
<a-input v-model.trim="inModal.inbound.settings.accounts[0].pass"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- tls settings -->
|
||||
<template v-if="(inModal.inbound.protocol === Protocols.VMESS
|
||||
|| inModal.inbound.protocol === Protocols.VLESS
|
||||
|| inModal.inbound.protocol === Protocols.TROJAN
|
||||
|| inModal.inbound.protocol === Protocols.SHADOWSOCKS)
|
||||
&& ['tcp', 'ws', 'http', 'quic'].indexOf(inModal.inbound.stream.network) >= 0">
|
||||
|
||||
<!-- tls enable -->
|
||||
<a-form layout="inline" v-if="inModal.inbound.protocol !== Protocols.TROJAN">
|
||||
<a-form-item label="tls">
|
||||
<a-switch
|
||||
:checked="inModal.inbound.stream.security === 'tls'"
|
||||
@change="checked => inModal.inbound.stream.security = checked ? 'tls' : 'none'">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inModal.inbound.protocol === Protocols.VLESS && inModal.inbound.stream.security === 'tls' && inModal.inbound.stream.network === 'tcp'" label="xtls">
|
||||
<a-switch v-model="inModal.inbound.stream.is_xtls"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- tls settings -->
|
||||
<a-form v-if="inModal.inbound.stream.security === 'tls'"
|
||||
layout="inline">
|
||||
<a-form-item label="域名">
|
||||
<a-input v-model.trim="inModal.inbound.stream.tls.server"></a-input>
|
||||
</a-form-item>
|
||||
{# <a-form-item label="允许不安全">#}
|
||||
{# <a-switch v-model="inModal.inbound.stream.tls.allowInsecure"></a-switch>#}
|
||||
{# </a-form-item>#}
|
||||
<a-form-item label="证书">
|
||||
<a-radio-group v-model="inModal.inbound.stream.tls.certs[0].useFile"
|
||||
button-style="solid">
|
||||
<a-radio-button :value="true">certificate file path</a-radio-button>
|
||||
<a-radio-button :value="false">certificate file content</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<template v-if="inModal.inbound.stream.tls.certs[0].useFile">
|
||||
<a-form-item label="公钥文件路径">
|
||||
<a-input v-model.trim="inModal.inbound.stream.tls.certs[0].certFile"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="密钥文件路径">
|
||||
<a-input v-model.trim="inModal.inbound.stream.tls.certs[0].keyFile"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-form-item label="公钥内容">
|
||||
<a-input type="textarea" :rows="2"
|
||||
v-model="inModal.inbound.stream.tls.certs[0].cert"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="密钥内容">
|
||||
<a-input type="textarea" :rows="2"
|
||||
v-model="inModal.inbound.stream.tls.certs[0].key"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!-- sniffing -->
|
||||
<a-form layout="inline">
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
sniffing
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
没有特殊需求保持默认即可
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-switch v-model="inModal.inbound.sniffing.enabled"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<script>
|
||||
|
||||
const inModal = {
|
||||
title: '',
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
okText: '确定',
|
||||
confirm: null,
|
||||
inbound: new Inbound(),
|
||||
ok() {
|
||||
ObjectUtil.execute(inModal.confirm);
|
||||
},
|
||||
show({ title='', okText='确定', inbound=null, confirm=()=>{} }) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
if (inbound) {
|
||||
this.inbound = Inbound.fromJson(inbound.toJson());
|
||||
} else {
|
||||
this.inbound = new Inbound();
|
||||
}
|
||||
this.confirm = confirm;
|
||||
this.visible = true;
|
||||
},
|
||||
close() {
|
||||
inModal.visible = false;
|
||||
inModal.closeLoading();
|
||||
},
|
||||
loading() {
|
||||
inModal.confirmLoading = true;
|
||||
},
|
||||
closeLoading() {
|
||||
inModal.confirmLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const protocols = {
|
||||
VMESS: Protocols.VMESS,
|
||||
VLESS: Protocols.VLESS,
|
||||
TROJAN: Protocols.TROJAN,
|
||||
SHADOWSOCKS: Protocols.SHADOWSOCKS,
|
||||
DOKODEMO: Protocols.DOKODEMO,
|
||||
SOCKS: Protocols.SOCKS,
|
||||
HTTP: Protocols.HTTP,
|
||||
};
|
||||
|
||||
new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#inbound-modal',
|
||||
data: {
|
||||
inModal: inModal,
|
||||
Protocols: protocols,
|
||||
SSMethods: SSMethods,
|
||||
},
|
||||
methods: {
|
||||
streamNetworkChange(oldValue) {
|
||||
if (oldValue === 'kcp') {
|
||||
this.inModal.inbound.stream.security = 'none';
|
||||
}
|
||||
},
|
||||
protocolChange(value) {
|
||||
this.inModal.inbound.settings = Inbound.Settings.getSettings(value);
|
||||
if (value === Protocols.TROJAN) {
|
||||
this.inModal.inbound.stream.security = 'tls';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
286
web/html/x-ui/inbounds.html
Normal file
286
web/html/x-ui/inbounds.html
Normal file
@@ -0,0 +1,286 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{{template "head" .}}
|
||||
<style>
|
||||
.ant-layout-content {
|
||||
margin: 24px 16px;
|
||||
}
|
||||
|
||||
.ant-col-sm-24 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<a-layout id="app" v-cloak>
|
||||
{{ template "commonSider" . }}
|
||||
<a-layout id="content-layout">
|
||||
<a-layout-content>
|
||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
||||
<transition name="list" appear>
|
||||
<a-tag v-if="true" color="red" style="margin-bottom: 10px">
|
||||
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
|
||||
</a-tag>
|
||||
</transition>
|
||||
<transition name="list" appear>
|
||||
<a-card hoverable style="margin-bottom: 20px;">
|
||||
<div slot="title">
|
||||
<a-button type="primary" icon="plus" @click="openAddInbound"></a-button>
|
||||
</div>
|
||||
<a-row>
|
||||
<a-input v-model="searchKey" placeholder="search" autofocus></a-input>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
upload / download:
|
||||
<a-tag color="green">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
total traffic:
|
||||
<a-popconfirm title="Are you sure you want to reset all traffic to 0? It\'s unrecoverable"
|
||||
@confirm="resetAllTraffic()"
|
||||
ok-text="confirm" cancel-text="cancel">
|
||||
<a-tag color="green">[[ sizeFormat(total.up + total.down) ]]</a-tag>
|
||||
</a-popconfirm>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
number of accounts:
|
||||
<a-tag color="green">[[ dbInbounds.length ]]</a-tag>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</transition>
|
||||
<transition name="list" appear>
|
||||
<a-card hoverable>
|
||||
<a-table :columns="columns" :row-key="inbound => inbound.id"
|
||||
:data-source="dbInbounds"
|
||||
:loading="spinning" :scroll="{ x: 1500 }"
|
||||
:pagination="false"
|
||||
@change="() => getDBInbounds()">
|
||||
<template slot="protocol" slot-scope="text, inbound">
|
||||
<a-tag color="blue">[[ inbound.protocol ]]</a-tag>
|
||||
</template>
|
||||
<template slot="settings" slot-scope="text, inbound">
|
||||
<a-button type="link">查看</a-button>
|
||||
</template>
|
||||
<template slot="streamSettings" slot-scope="text, inbound">
|
||||
<a-button type="link">查看</a-button>
|
||||
</template>
|
||||
<template slot="enable" slot-scope="text, inbound">
|
||||
<a-tag v-if="inbound.enable" color="green">启用</a-tag>
|
||||
<a-tag v-else color="red">禁用</a-tag>
|
||||
</template>
|
||||
<template slot="expiryTime" slot-scope="text, inbound">
|
||||
<span v-if="inbound.expiryTime > 0" color="red">[[ DateUtil.formatMillis(inbound.expiryTime) ]]</span>
|
||||
<span v-else>无限期</span>
|
||||
</template>
|
||||
<template slot="action" slot-scope="text, inbound">
|
||||
<a-button type="primary" icon="qrcode"></a-button>
|
||||
<a-button type="primary" icon="edit"></a-button>
|
||||
<a-button type="danger" icon="delete" @click="delInbound(inbound)"></a-button>
|
||||
</template>
|
||||
|
||||
<template slot="expandedRowRender" slot-scope="inbound" style="margin: 0">
|
||||
[[ inbound.id ]]
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</transition>
|
||||
</a-spin>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
<script>
|
||||
|
||||
const columns = [{
|
||||
title: "id",
|
||||
align: 'center',
|
||||
dataIndex: "id",
|
||||
width: 60,
|
||||
}, {
|
||||
title: "protocol",
|
||||
align: 'center',
|
||||
width: 60,
|
||||
scopedSlots: { customRender: 'protocol' },
|
||||
}, {
|
||||
title: "port",
|
||||
align: 'center',
|
||||
dataIndex: "port",
|
||||
width: 60,
|
||||
}, {
|
||||
title: "settings",
|
||||
align: 'center',
|
||||
width: 60,
|
||||
scopedSlots: { customRender: 'settings' },
|
||||
}, {
|
||||
title: "streamSettings",
|
||||
align: 'center',
|
||||
width: 60,
|
||||
scopedSlots: { customRender: 'streamSettings' },
|
||||
}, {
|
||||
title: "enable",
|
||||
align: 'center',
|
||||
width: 60,
|
||||
scopedSlots: { customRender: 'enable' },
|
||||
}, {
|
||||
title: "expiryTime",
|
||||
align: 'center',
|
||||
width: 60,
|
||||
scopedSlots: { customRender: 'expiryTime' },
|
||||
}, {
|
||||
title: "action",
|
||||
align: 'center',
|
||||
width: 60,
|
||||
scopedSlots: { customRender: 'action' },
|
||||
}];
|
||||
|
||||
const app = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#app',
|
||||
data: {
|
||||
ip: location.hostname,
|
||||
spinning: false,
|
||||
dbInbounds: [],
|
||||
searchKey: '',
|
||||
},
|
||||
methods: {
|
||||
loading(spinning=true) {
|
||||
this.spinning = spinning;
|
||||
},
|
||||
empDefault(o, defaultValue='') {return ObjectUtil.isEmpty(o) ? defaultValue : o},
|
||||
async getDBInbounds() {
|
||||
this.loading();
|
||||
const msg = await HttpUtil.post('/xui/inbounds');
|
||||
this.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
this.setInbounds(msg.obj);
|
||||
},
|
||||
setInbounds(dbInbounds) {
|
||||
this.dbInbounds.splice(0);
|
||||
for (const inbound of dbInbounds) {
|
||||
this.dbInbounds.push(new DBInbound(inbound));
|
||||
}
|
||||
},
|
||||
searchInbounds(key) {
|
||||
if (ObjectUtil.isEmpty(key)) {
|
||||
this.searchedInbounds = this.dbInbounds.slice();
|
||||
} else {
|
||||
this.searchedInbounds.splice(0, this.searchedInbounds.length);
|
||||
this.dbInbounds.forEach(inbound => {
|
||||
if (ObjectUtil.deepSearch(inbound, key)) {
|
||||
this.searchedInbounds.push(inbound);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
openAddInbound() {
|
||||
inModal.show({
|
||||
title: 'add account',
|
||||
okText: 'add',
|
||||
confirm: async () => {
|
||||
inModal.loading();
|
||||
await this.addInbound(inModal.inbound);
|
||||
inModal.closeLoading();
|
||||
}
|
||||
});
|
||||
},
|
||||
openEditInbound(inbound) {
|
||||
inModal.show({
|
||||
title: 'update account',
|
||||
okText: 'update',
|
||||
inbound: inbound,
|
||||
confirm: async () => {
|
||||
inModal.loading();
|
||||
inModal.inbound.id = inbound.id;
|
||||
await this.updateInbound(inModal.inbound);
|
||||
inModal.closeLoading();
|
||||
}
|
||||
});
|
||||
},
|
||||
async addInbound(inbound) {
|
||||
let data = {
|
||||
port: inbound.port,
|
||||
listen: inbound.listen,
|
||||
protocol: inbound.protocol,
|
||||
settings: inbound.settings.toString(false),
|
||||
stream_settings: inbound.stream.toString(false),
|
||||
sniffing: [Protocols.VMESS, Protocols.VLESS, Protocols.SHADOWSOCKS].indexOf(inbound.protocol) >= 0 ? inbound.sniffing.toString(false) : '{}',
|
||||
remark: inbound.remark,
|
||||
};
|
||||
await this.submit('/xui/inbound/add', data, inModal);
|
||||
},
|
||||
async updateInbound(inbound) {
|
||||
const data = {
|
||||
port: inbound.port,
|
||||
listen: inbound.listen,
|
||||
protocol: inbound.protocol,
|
||||
settings: inbound.settings.toString(false),
|
||||
stream_settings: inbound.stream.toString(false),
|
||||
sniffing: [Protocols.VMESS, Protocols.VLESS, Protocols.SHADOWSOCKS].indexOf(inbound.protocol) >= 0 ? inbound.sniffing.toString(false) : '{}',
|
||||
remark: inbound.remark,
|
||||
enable: inbound.enable,
|
||||
};
|
||||
await this.submit(`/xui/inbound/update/${inbound.id}`, data, inModal);
|
||||
},
|
||||
delInbound(inbound) {
|
||||
this.$confirm({
|
||||
title: 'delete account',
|
||||
content: 'Cannot be restored after deletion, confirm deletion?',
|
||||
okText: 'delete',
|
||||
cancelText: 'cancel',
|
||||
onOk: () => this.submit('/xui/inbound/del/' + inbound.id),
|
||||
});
|
||||
},
|
||||
resetTraffic(inbound) {
|
||||
this.submit(`/xui/reset_traffic/${inbound.id}`);
|
||||
},
|
||||
resetAllTraffic() {
|
||||
this.submit('/xui/reset_all_traffic');
|
||||
},
|
||||
setEnable(inbound, enable) {
|
||||
let data = {enable: enable};
|
||||
this.submit(`/xui/inbound/update/${inbound.id}`, data);
|
||||
},
|
||||
async submit(url, data, modal) {
|
||||
const msg = await HttpUtil.post(url, data);
|
||||
if (msg.success) {
|
||||
this.getDBInbounds();
|
||||
if (modal != null) {
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
searchKey(value) {
|
||||
this.searchInbounds(value);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getDBInbounds();
|
||||
},
|
||||
computed: {
|
||||
total() {
|
||||
let down = 0, up = 0;
|
||||
for (let i = 0; i < this.dbInbounds.length; ++i) {
|
||||
down += this.dbInbounds[i].down;
|
||||
up += this.dbInbounds[i].up;
|
||||
}
|
||||
return {
|
||||
down: down,
|
||||
up: up,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
{{template "inboundModal"}}
|
||||
{{template "promptModal"}}
|
||||
{{template "qrcodeModal"}}
|
||||
{{template "textModal"}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -68,7 +68,7 @@
|
||||
<a-card hoverable>
|
||||
xray 状态:
|
||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||
<a-tooltip v-if="status.xray.stat === State.Error">
|
||||
<a-tooltip v-if="status.xray.state === State.Error">
|
||||
<template slot="title">
|
||||
<p v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
||||
</template>
|
||||
@@ -175,7 +175,6 @@
|
||||
</template>
|
||||
</a-modal>
|
||||
</a-layout>
|
||||
</body>
|
||||
{{template "js" .}}
|
||||
<script>
|
||||
|
||||
@@ -330,4 +329,5 @@
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user