UI Improvements (#2228)

* UI Improvements

Better Table
Update QR Code Modal
Better Info Modal
Compression HTML files
Better Dropdown Menu
Better Calendar
and more ..
Remove files
Minor Fixes
This commit is contained in:
Tara Rostami 2024-04-20 22:15:36 +03:30 committed by GitHub
parent 9ebdd783a2
commit 7826117fde
35 changed files with 2591 additions and 12398 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,86 +0,0 @@
/*
Copyright (C) 2011 by MarkLogic Corporation
Author: Mike Brevoort <mike@brevoort.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
.cm-s-xq.CodeMirror { border-radius: 1.5rem; border: 1px solid #d9d9d9; height: auto; }
.cm-s-xq.CodeMirror:hover { background-color: rgb(232 244 242); border-color: #18947b; transition: all .3s; }
.cm-s-xq .CodeMirror-gutters { border-right: 1px solid #ddd; background-color: rgb(221 221 221 / 20%); white-space: nowrap; }
.cm-s-xq span.cm-keyword { line-height: 1em; font-weight: bold; color: #5A5CAD; }
.cm-s-xq span.cm-atom { color: #7A316F; font-weight:bold; }
.cm-s-xq span.cm-number { color: #e36209; }
.cm-s-xq span.cm-def { text-decoration:underline; }
.cm-s-xq span.cm-variable { color: black; }
.cm-s-xq span.cm-variable-2 { color:black; }
.cm-s-xq span.cm-variable-3, .cm-s-xq span.cm-type { color: black; }
.cm-s-xq span.cm-property { color: #008771; }
.cm-s-xq span.cm-operator {}
.cm-s-xq span.cm-comment { color: #bbbbbb; font-style: italic; }
.cm-s-xq span.cm-string {}
.cm-s-xq span.cm-meta { color: yellow; }
.cm-s-xq span.cm-qualifier { color: grey; }
.cm-s-xq span.cm-builtin { color: #7EA656; }
.cm-s-xq span.cm-bracket { color: #cc7; }
.cm-s-xq span.cm-tag { color: #3F7F7F; }
.cm-s-xq span.cm-attribute { color: #7F007F; }
.cm-s-xq span.cm-error { color: #e04141; }
.cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; }
.cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; }
.dark .cm-s-xq.CodeMirror { background-color: var(--dark-color-surface-200); border-color: var(--dark-color-surface-300); color: rgb(255 255 255 / 65%); }
.dark .cm-s-xq.CodeMirror:hover { background-color: rgb(0 50 42 / 30%); border-color: #008771; transition: all .3s; }
.dark .cm-s-xq div.CodeMirror-selected { background: var(--dark-color-codemirror-line-selection); }
.dark .cm-s-xq .CodeMirror-line::selection, .dark .cm-s-xq .CodeMirror-line > span::selection, .dark .cm-s-xq .CodeMirror-line > span > span::selection { background: var(--dark-color-codemirror-line-selection); }
.dark .cm-s-xq .CodeMirror-line::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span > span::-moz-selection { background: var(--dark-color-codemirror-line-selection); }
.dark .cm-s-xq .CodeMirror-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid var(--dark-color-surface-300); }
.dark .cm-s-xq .CodeMirror-guttermarker { color: #FFBD40; }
.dark .cm-s-xq .CodeMirror-guttermarker-subtle { color: rgb(255 255 255 / 70%); }
.dark .cm-s-xq .CodeMirror-linenumber { color: rgb(255 255 255 / 50%); }
.dark .cm-s-xq .CodeMirror-cursor { border-left: 1px solid white; }
.dark .cm-s-xq span.cm-keyword { color: #FFBD40; }
.dark .cm-s-xq span.cm-atom { color: #c099ff; }
.dark .cm-s-xq span.cm-number { color: #9ccfd8; }
.dark .cm-s-xq span.cm-def { color: #FFF; text-decoration:underline; }
.dark .cm-s-xq span.cm-variable { color: #FFF; }
.dark .cm-s-xq span.cm-variable-2 { color: #EEE; }
.dark .cm-s-xq span.cm-variable-3, .dark .cm-s-xq span.cm-type { color: #DDD; }
.dark .cm-s-xq span.cm-property { color: #f6c177; }
.dark .cm-s-xq span.cm-operator {}
.dark .cm-s-xq span.cm-comment { color: gray; }
.dark .cm-s-xq span.cm-string {}
.dark .cm-s-xq span.cm-meta { color: yellow; }
.dark .cm-s-xq span.cm-qualifier { color: #FFF700; }
.dark .cm-s-xq span.cm-builtin { color: #30a; }
.dark .cm-s-xq span.cm-bracket { color: #cc7; }
.dark .cm-s-xq span.cm-tag { color: #FFBD40; }
.dark .cm-s-xq span.cm-attribute { color: #FFF700; }
.dark .cm-s-xq span.cm-error { color: #e04141; }
.dark .cm-s-xq .CodeMirror-activeline-background { background: #27282E; }
.dark .cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; }
.Line-Hover{transition: all .2s;}
.Line-Hover:hover{ background-color: rgba(0, 102, 85, 0.05) !important; }
.dark .Line-Hover:hover{ background-color: var(--dark-color-codemirror-line-hover) !important; }
.CodeMirror-foldmarker { color: #fc8800; text-shadow: #ffd8aa 1px 1px 2px, #ffd8aa -1px -1px 2px, #ffd8aa 1px -1px 2px, #ffd8aa -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
.dark .CodeMirror-foldmarker { color: #ffffff; text-shadow: #bbb 1px 1px 2px, #bbb -1px -1px 2px, #bbb 1px -1px 2px, #bbb -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }

1
web/assets/codemirror/xq.min.css vendored Normal file
View file

@ -0,0 +1 @@
.CodeMirror{background-color:#f6fbfa;border:1px solid #d9d9d9}.cm-s-xq.CodeMirror{border-radius:1.5rem;height:auto;transition:background-color .3s,border-color 0.3s}.cm-s-xq.CodeMirror:hover{background-color:#e8f4f2;border-color:#18947b}.cm-s-xq div.CodeMirror-selected{background:rgb(0 102 85 / .1)}.cm-s-xq .CodeMirror-line::selection,.cm-s-xq .CodeMirror-line>span::selection,.cm-s-xq .CodeMirror-line>span>span::selection{background:rgb(0 102 85 / .1)}.cm-s-xq .CodeMirror-line::-moz-selection,.cm-s-xq .CodeMirror-line>span::-moz-selection,.cm-s-xq .CodeMirror-line>span>span::-moz-selection{background:rgb(0 102 85 / .1)}.cm-s-xq .CodeMirror-gutters{border-right:1px solid #ddd;background-color:rgb(221 221 221 / 20%);white-space:nowrap}.cm-s-xq span.cm-keyword{line-height:1em;font-weight:700;color:#5A5CAD}.cm-s-xq span.cm-atom{color:#7A316F;font-weight:700}.cm-s-xq span.cm-number{color:#e36209}.cm-s-xq span.cm-def{text-decoration:underline}.cm-s-xq span.cm-variable{color:#000}.cm-s-xq span.cm-variable-2{color:#000}.cm-s-xq span.cm-variable-3,.cm-s-xq span.cm-type{color:#000}.cm-s-xq span.cm-property{color:#008771}.cm-s-xq span.cm-comment{color:#bbb;font-style:italic}.cm-s-xq span.cm-meta{color:#ff0}.cm-s-xq span.cm-qualifier{color:grey}.cm-s-xq span.cm-builtin{color:#7EA656}.cm-s-xq span.cm-bracket{color:#cc7}.cm-s-xq span.cm-tag{color:#3F7F7F}.cm-s-xq span.cm-attribute{color:#7F007F}.cm-s-xq span.cm-error{color:#e04141}.cm-s-xq .CodeMirror-activeline-background{background:#e8f2ff}.cm-s-xq .CodeMirror-matchingbracket{outline:1px solid grey;color:black!important;background:#ff0}.dark .CodeMirror{background-color:var(--dark-color-surface-200);border-color:var(--dark-color-surface-300)}.dark .cm-s-xq.CodeMirror{background-color:var(--dark-color-surface-200);border-color:var(--dark-color-surface-300);color:rgb(255 255 255 / 65%)}.dark .cm-s-xq.CodeMirror:hover{background-color:rgb(0 50 42 / 30%);border-color:#008771}.dark .cm-s-xq div.CodeMirror-selected{background:var(--dark-color-codemirror-line-selection)}.dark .cm-s-xq .CodeMirror-line::selection,.dark .cm-s-xq .CodeMirror-line>span::selection,.dark .cm-s-xq .CodeMirror-line>span>span::selection{background:var(--dark-color-codemirror-line-selection)}.dark .cm-s-xq .CodeMirror-line::-moz-selection,.dark .cm-s-xq .CodeMirror-line>span::-moz-selection,.dark .cm-s-xq .CodeMirror-line>span>span::-moz-selection{background:var(--dark-color-codemirror-line-selection)}.dark .cm-s-xq .CodeMirror-gutters{background:rgb(0 0 0 / 30%);border-right:1px solid var(--dark-color-surface-300)}.dark .cm-s-xq .CodeMirror-guttermarker{color:#FFBD40}.dark .cm-s-xq .CodeMirror-guttermarker-subtle{color:rgb(255 255 255 / 70%)}.dark .cm-s-xq .CodeMirror-linenumber{color:rgb(255 255 255 / 50%)}.dark .cm-s-xq .CodeMirror-cursor{border-left:1px solid #fff}.dark .cm-s-xq span.cm-keyword{color:#FFBD40}.dark .cm-s-xq span.cm-atom{color:#c099ff}.dark .cm-s-xq span.cm-number{color:#9ccfd8}.dark .cm-s-xq span.cm-def{color:#FFF;text-decoration:underline}.dark .cm-s-xq span.cm-variable{color:#FFF}.dark .cm-s-xq span.cm-variable-2{color:#EEE}.dark .cm-s-xq span.cm-variable-3,.dark .cm-s-xq span.cm-type{color:#DDD}.dark .cm-s-xq span.cm-property{color:#f6c177}.dark .cm-s-xq span.cm-comment{color:gray}.dark .cm-s-xq span.cm-meta{color:#ff0}.dark .cm-s-xq span.cm-qualifier{color:#FFF700}.dark .cm-s-xq span.cm-builtin{color:#30a}.dark .cm-s-xq span.cm-bracket{color:#cc7}.dark .cm-s-xq span.cm-tag{color:#FFBD40}.dark .cm-s-xq span.cm-attribute{color:#FFF700}.dark .cm-s-xq span.cm-error{color:#e04141}.dark .cm-s-xq .CodeMirror-activeline-background{background:#27282E}.dark .cm-s-xq .CodeMirror-matchingbracket{outline:1px solid grey;color:white!important}.Line-Hover{transition:all .2s}.CodeMirror pre.CodeMirror-line:hover,.CodeMirror pre.CodeMirror-line-like:hover{background-color:rgb(0 102 85 / .05)}.dark .CodeMirror pre.CodeMirror-line:hover,.CodeMirror pre.CodeMirror-line-like:hover{background-color:var(--dark-color-codemirror-line-hover)}.CodeMirror-foldmarker{color:#fc8800;text-shadow:#ffd8aa 1px 1px 2px,#ffd8aa -1px -1px 2px,#ffd8aa 1px -1px 2px,#ffd8aa -1px 1px 2px;font-family:arial;line-height:.3;cursor:pointer}.dark .CodeMirror-foldmarker{color:#fff;text-shadow:#bbb 1px 1px 2px,#bbb -1px -1px 2px,#bbb 1px -1px 2px,#bbb -1px 1px 2px;font-family:arial;line-height:.3;cursor:pointer}

File diff suppressed because one or more lines are too long

1
web/assets/css/custom.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css"> <link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css">
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css"> <link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}"> <link rel="stylesheet" href="{{ .base_path }}assets/css/custom.min.css?{{ .cur_ver }}">
<style> <style>
[v-cloak] { [v-cloak] {
display: none; display: none;

View file

@ -1,27 +1,41 @@
{{define "qrcodeModal"}} {{define "qrcodeModal"}}
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title" <a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
:dialog-style="{ top: '20px' }" :dialog-style="isMobileQr ? { top: '18px' } : {}"
:closable="true" :closable="true"
:class="themeSwitcher.currentTheme" :class="themeSwitcher.currentTheme"
:footer="null" width="300px"> :footer="null" width="fit-content">
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;"> <tr-qr-modal class="qr-modal">
{{ i18n "pages.inbounds.clickOnQRcode" }}
</a-tag>
<template v-if="app.subSettings.enable && qrModal.subId"> <template v-if="app.subSettings.enable && qrModal.subId">
<a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider> <tr-qr-box class="qr-box">
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" class="qr-cv"></canvas></div> <a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}}</span></a-tag>
<a-divider>{{ i18n "pages.settings.subSettings"}} Json</a-divider> <tr-qr-bg class="qr-bg-sub">
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))" id="qrCode-subJson" class="qr-cv"></canvas></div> <tr-qr-bg-inner class="qr-bg-sub-inner">
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" class="qr-cv"></canvas>
</tr-qr-bg-inner>
</tr-qr-bg>
</tr-qr-box>
<tr-qr-box class="qr-box">
<a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}} Json</span></a-tag>
<tr-qr-bg class="qr-bg-sub">
<tr-qr-bg-inner class="qr-bg-sub-inner">
<canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))" id="qrCode-subJson" class="qr-cv"></canvas>
</tr-qr-bg-inner>
</tr-qr-bg>
</tr-qr-box>
</template> </template>
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<template v-for="(row, index) in qrModal.qrcodes"> <template v-for="(row, index) in qrModal.qrcodes">
<a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag> <tr-qr-box class="qr-box">
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas></div> <a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
<tr-qr-bg class="qr-bg">
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas>
</tr-qr-bg>
</tr-qr-box>
</template> </template>
</tr-qr-modal>
</a-modal> </a-modal>
<script> <script>
const isMobileQr = window.innerWidth <= 768;
const qrModal = { const qrModal = {
title: '', title: '',
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
@ -30,17 +44,17 @@
clipboard: null, clipboard: null,
visible: false, visible: false,
subId: '', subId: '',
show: function (title = '', dbInbound, client) { show: function(title = '', dbInbound, client) {
this.title = title; this.title = title;
this.dbInbound = dbInbound; this.dbInbound = dbInbound;
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
this.client = client; this.client = client;
this.subId = ''; this.subId = '';
this.qrcodes = []; this.qrcodes = [];
if (this.inbound.protocol == Protocols.WIREGUARD){ if (this.inbound.protocol == Protocols.WIREGUARD) {
this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l,index) =>{ this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l, index) => {
this.qrcodes.push({ this.qrcodes.push({
remark: "Peer " + (index+1), remark: "Peer " + (index + 1),
link: l link: l
}); });
}); });
@ -54,11 +68,10 @@
} }
this.visible = true; this.visible = true;
}, },
close: function () { close: function() {
this.visible = false; this.visible = false;
}, },
}; };
const qrModalApp = new Vue({ const qrModalApp = new Vue({
delimiters: ['[[', ']]'], delimiters: ['[[', ']]'],
el: '#qrcode-modal', el: '#qrcode-modal',
@ -88,13 +101,28 @@
}); });
}, },
genSubLink(subID) { genSubLink(subID) {
return app.subSettings.subURI+subID; return app.subSettings.subURI + subID;
}, },
genSubJsonLink(subID) { genSubJsonLink(subID) {
return app.subSettings.subJsonURI+subID; return app.subSettings.subJsonURI + subID;
},
revertOverflow() {
const elements = document.querySelectorAll(".qr-tag");
elements.forEach((element) => {
element.classList.remove("tr-marquee");
element.children[0].style.animation = '';
while (element.children.length > 1) {
element.removeChild(element.lastChild);
}
});
} }
}, },
updated() { updated() {
if (this.qrModal.visible) {
fixOverflow();
} else {
this.revertOverflow();
}
if (qrModal.client && qrModal.client.subId) { if (qrModal.client && qrModal.client.subId) {
qrModal.subId = qrModal.client.subId; qrModal.subId = qrModal.client.subId;
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId)); this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
@ -106,5 +134,32 @@
} }
}); });
function fixOverflow() {
const elements = document.querySelectorAll(".qr-tag");
elements.forEach((element) => {
function isElementOverflowing(element) {
const overflowX = element.offsetWidth < element.scrollWidth,
overflowY = element.offsetHeight < element.scrollHeight;
return overflowX || overflowY;
}
function wrapContentsInMarquee(element) {
element.classList.add("tr-marquee");
element.children[0].style.animation = `move-ltr ${
(element.children[0].clientWidth / element.clientWidth) * 5
}s ease-in-out infinite`;
const marqueeText = element.children[0];
if (element.children.length < 2) {
for (let i = 0; i < 1; i++) {
const marqueeText = element.children[0].cloneNode(true);
element.children[0].after(marqueeText);
}
}
}
if (isElementOverflowing(element)) {
wrapContentsInMarquee(element);
}
});
}
</script> </script>
{{end}} {{end}}

View file

@ -400,7 +400,7 @@
</g> </g>
</svg> </svg>
</div> </div>
<a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto;"> <a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto; overflow-x: hidden;">
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;"> <a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;">
<a-row type="flex" justify="center"> <a-row type="flex" justify="center">
<a-col style="width: 100%;"> <a-col style="width: 100%;">
@ -461,7 +461,7 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-row justify="center" class="centered"> <a-row justify="center" class="centered">
<theme-switch></theme-switch> <theme-switch-login></theme-switch-login>
</a-row> </a-row>
</a-form-item> </a-form-item>
</a-form> </a-form>
@ -482,7 +482,6 @@
this.password = ""; this.password = "";
} }
} }
const app = new Vue({ const app = new Vue({
delimiters: ['[[', ']]'], delimiters: ['[[', ']]'],
el: '#app', el: '#app',

View file

@ -1,46 +1,50 @@
{{define "menuItems"}} {{define "menuItems"}}
<a-menu-item key="{{ .base_path }}panel/"> <a-menu-item key="{{ .base_path }}panel/">
<a-icon type="dashboard"></a-icon> <a-icon type="dashboard"></a-icon>
<span><b>{{ i18n "menu.dashboard"}}</b></span> <span>
<b>{{ i18n "menu.dashboard"}}</b>
</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}panel/inbounds"> <a-menu-item key="{{ .base_path }}panel/inbounds">
<a-icon type="user"></a-icon> <a-icon type="user"></a-icon>
<span><b>{{ i18n "menu.inbounds"}}</b></span> <span>
<b>{{ i18n "menu.inbounds"}}</b>
</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}panel/settings"> <a-menu-item key="{{ .base_path }}panel/settings">
<a-icon type="setting"></a-icon> <a-icon type="setting"></a-icon>
<span><b>{{ i18n "menu.settings"}}</b></span> <span>
<b>{{ i18n "menu.settings"}}</b>
</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}panel/xray"> <a-menu-item key="{{ .base_path }}panel/xray">
<a-icon type="tool"></a-icon> <a-icon type="tool"></a-icon>
<span><b>{{ i18n "menu.xray"}}</b></span> <span>
<b>{{ i18n "menu.xray"}}</b>
</span>
</a-menu-item> </a-menu-item>
<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><b>{{ i18n "menu.logout"}}</b></span> <span>
<b>{{ i18n "menu.logout"}}</b>
</span>
</a-menu-item> </a-menu-item>
{{end}} {{end}}
{{define "commonSider"}} {{define "commonSider"}}
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0"> <a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md">
<theme-switch></theme-switch> <theme-switch></theme-switch>
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" <a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
{{template "menuItems" .}} {{template "menuItems" .}}
</a-menu> </a-menu>
</a-layout-sider> </a-layout-sider>
<a-drawer id="sider-drawer" placement="left" :closable="false" <a-drawer id="sider-drawer" placement="left" :closable="false" @close="siderDrawer.close()" :visible="siderDrawer.visible" :wrap-class-name="themeSwitcher.currentTheme" :wrap-style="{ padding: 0 }">
@close="siderDrawer.close()"
:visible="siderDrawer.visible"
:wrap-class-name="themeSwitcher.currentTheme"
:wrap-style="{ padding: 0 }">
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle"> <div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon> <a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
</div> </div>
<theme-switch></theme-switch> <theme-switch></theme-switch>
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" <a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
{{template "menuItems" .}} {{template "menuItems" .}}
</a-menu> </a-menu>
</a-drawer> </a-drawer>

View file

@ -1,5 +1,5 @@
{{define "component/sortableTableTrigger"}} {{define "component/sortableTableTrigger"}}
<a-icon type="drag" <a-icon type="drag"
class="sortable-icon" class="sortable-icon"
style="cursor: move;" style="cursor: move;"
@mouseup="mouseUpHandler" @mouseup="mouseUpHandler"
@ -10,7 +10,6 @@
{{define "component/sortableTable"}} {{define "component/sortableTable"}}
<script> <script>
const DRAGGABLE_ROW_CLASS = 'draggable-row'; const DRAGGABLE_ROW_CLASS = 'draggable-row';
const findParentRowElement = (el) => { const findParentRowElement = (el) => {
if (!el || !el.tagName) { if (!el || !el.tagName) {
return null; return null;
@ -22,7 +21,6 @@
return null; return null;
} }
} }
Vue.component('a-table-sortable', { Vue.component('a-table-sortable', {
data() { data() {
return { return {
@ -34,22 +32,19 @@
inheritAttrs: false, inheritAttrs: false,
provide() { provide() {
const sortable = {} const sortable = {}
Object.defineProperty(sortable, "setSortableIndex", { Object.defineProperty(sortable, "setSortableIndex", {
enumerable: true, enumerable: true,
get: () => this.setCurrentSortableIndex, get: () => this.setCurrentSortableIndex,
}); });
Object.defineProperty(sortable, "resetSortableIndex", { Object.defineProperty(sortable, "resetSortableIndex", {
enumerable: true, enumerable: true,
get: () => this.resetSortableIndex, get: () => this.resetSortableIndex,
}); });
return { return {
sortable, sortable,
} }
}, },
render: function (createElement) { render: function(createElement) {
return createElement('a-table', { return createElement('a-table', {
class: { class: {
'ant-table-is-sorting': this.isDragging(), 'ant-table-is-sorting': this.isDragging(),
@ -101,23 +96,18 @@
if (!this.isDragging()) { if (!this.isDragging()) {
return; return;
} }
e.preventDefault(); e.preventDefault();
const currentIndex = this.sortingElementIndex; const currentIndex = this.sortingElementIndex;
if (index === currentIndex) { if (index === currentIndex) {
this.newElementIndex = null; this.newElementIndex = null;
return; return;
} }
const row = findParentRowElement(e.target); const row = findParentRowElement(e.target);
if (!row) { if (!row) {
return; return;
} }
const rect = row.getBoundingClientRect(); const rect = row.getBoundingClientRect();
const offsetTop = e.pageY - rect.top; const offsetTop = e.pageY - rect.top;
if (offsetTop < rect.height / 2) { if (offsetTop < rect.height / 2) {
this.newElementIndex = Math.max(index - 1, 0); this.newElementIndex = Math.max(index - 1, 0);
} else { } else {
@ -133,7 +123,6 @@
const parentMethodResult = this.customRow?.(record, index) || {}; const parentMethodResult = this.customRow?.(record, index) || {};
const newIndex = this.newElementIndex; const newIndex = this.newElementIndex;
const currentIndex = this.sortingElementIndex; const currentIndex = this.sortingElementIndex;
return { return {
...parentMethodResult, ...parentMethodResult,
attrs: { attrs: {
@ -149,9 +138,7 @@
class: { class: {
...(parentMethodResult?.class || {}), ...(parentMethodResult?.class || {}),
[DRAGGABLE_ROW_CLASS]: true, [DRAGGABLE_ROW_CLASS]: true,
['dragging']: this.isDragging() ['dragging']: this.isDragging() ? (newIndex === null ? index === currentIndex : index === newIndex) : false,
? (newIndex === null ? index === currentIndex : index === newIndex)
: false,
}, },
}; };
} }
@ -160,28 +147,22 @@
records() { records() {
const newIndex = this.newElementIndex; const newIndex = this.newElementIndex;
const currentIndex = this.sortingElementIndex; const currentIndex = this.sortingElementIndex;
if (!this.isDragging() || newIndex === null || currentIndex === newIndex) { if (!this.isDragging() || newIndex === null || currentIndex === newIndex) {
return this.dataSource; return this.dataSource;
} }
if (this.$memoSort.newIndex === newIndex) { if (this.$memoSort.newIndex === newIndex) {
return this.$memoSort.list; return this.$memoSort.list;
} }
let list = [...this.dataSource]; let list = [...this.dataSource];
list.splice(newIndex, 0, list.splice(currentIndex, 1)[0]); list.splice(newIndex, 0, list.splice(currentIndex, 1)[0]);
this.$memoSort = { this.$memoSort = {
newIndex, newIndex,
list, list,
}; };
return list; return list;
} }
} }
}); });
Vue.component('table-sort-trigger', { Vue.component('table-sort-trigger', {
template: `{{template "component/sortableTableTrigger"}}`, template: `{{template "component/sortableTableTrigger"}}`,
props: ['item-index'], props: ['item-index'],
@ -203,7 +184,6 @@
} }
}) })
</script> </script>
<style> <style>
@media only screen and (max-width: 767px) { @media only screen and (max-width: 767px) {
.sortable-icon { .sortable-icon {

View file

@ -1,6 +1,23 @@
{{define "component/themeSwitchTemplate"}} {{define "component/themeSwitchTemplate"}}
<template> <template>
<a-menu class="change-theme" :theme="themeSwitcher.currentTheme" mode="inline" selected-keys=""> <a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
<a-sub-menu>
<span slot="title">
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
<span>Theme</span>
</span>
<a-menu-item id="change-theme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOff()"> Dark <a-switch style="margin-left: 2px;" size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch>
</a-menu-item>
<a-menu-item id="change-theme-ultra" v-if="themeSwitcher.isDarkTheme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOffUltra()"> Ultra <a-checkbox style="margin-left: 2px;" :checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()"></a-checkbox>
</a-menu-item>
</a-sub-menu>
</a-menu>
</template>
{{end}}
{{define "component/themeSwitchTemplateLogin"}}
<template>
<a-menu @mousedown="themeSwitcher.animationsOff()" id="change-theme" :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
<a-menu-item mode="inline" class="ant-menu-theme-switch"> <a-menu-item mode="inline" class="ant-menu-theme-switch">
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon> <a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch> <a-switch size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch>
@ -23,6 +40,26 @@
const theme = isDarkTheme ? 'dark' : 'light'; const theme = isDarkTheme ? 'dark' : 'light';
document.querySelector('body').setAttribute('class', theme); document.querySelector('body').setAttribute('class', theme);
return { return {
animationsOff() {
document.documentElement.setAttribute('data-theme-animations', 'off');
const themeAnimations = document.querySelector('#change-theme');
themeAnimations.addEventListener('mouseleave', () => {
document.documentElement.removeAttribute('data-theme-animations');
});
themeAnimations.addEventListener('touchend', () => {
document.documentElement.removeAttribute('data-theme-animations');
});
},
animationsOffUltra() {
document.documentElement.setAttribute('data-theme-animations', 'off');
const themeAnimationsUltra = document.querySelector('#change-theme-ultra');
themeAnimationsUltra.addEventListener('mouseleave', () => {
document.documentElement.removeAttribute('data-theme-animations');
});
themeAnimationsUltra.addEventListener('touchend', () => {
document.documentElement.removeAttribute('data-theme-animations');
});
},
isDarkTheme, isDarkTheme,
isUltra, isUltra,
get currentTheme() { get currentTheme() {
@ -57,13 +94,19 @@
getContainer: () => document.getElementById('message') getContainer: () => document.getElementById('message')
}); });
document.getElementById('message').className = themeSwitcher.currentTheme; document.getElementById('message').className = themeSwitcher.currentTheme;
const themeAnimations = document.querySelector('.change-theme'); }
themeAnimations.addEventListener('mousedown', () => {
document.documentElement.setAttribute('data-theme-animations', 'off');
}); });
themeAnimations.addEventListener('mouseleave', () => { Vue.component('theme-switch-login', {
document.documentElement.removeAttribute('data-theme-animations'); props: [],
template: `{{template "component/themeSwitchTemplateLogin"}}`,
data: () => ({
themeSwitcher
}),
mounted() {
this.$message.config({
getContainer: () => document.getElementById('message')
}); });
document.getElementById('message').className = themeSwitcher.currentTheme;
} }
}); });
</script> </script>

View file

@ -1,27 +1,20 @@
{{define "dnsModal"}} {{define "dnsModal"}}
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok" <a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok" :closable="true" :mask-closable="false" :ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
:closable="true" :mask-closable="false"
:ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'> <a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input> <a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'> <a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
<a-button size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')">+</a-button> <a-button icon="plus" size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')"></a-button>
<template v-for="(domain, index) in dnsModal.dnsServer.domains"> <template v-for="(domain, index) in dnsModal.dnsServer.domains">
<a-input v-model.trim="dnsModal.dnsServer.domains[index]"> <a-input v-model.trim="dnsModal.dnsServer.domains[index]">
<a-button size="small" slot="addonAfter" @click="dnsModal.dnsServer.domains.splice(index,1)">-</a-button> <a-button icon="minus" size="small" slot="addonAfter" @click="dnsModal.dnsServer.domains.splice(index,1)"></a-button>
</a-input> </a-input>
</template> </template>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced"> <a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
<a-select <a-select v-model="dnsModal.dnsServer.queryStrategy" style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
v-model="dnsModal.dnsServer.queryStrategy" <a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']"> [[ l ]] </a-select-option>
style="width: 100%"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
[[ l ]]
</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</a-form> </a-form>
@ -39,18 +32,24 @@
queryStrategy: 'UseIP', queryStrategy: 'UseIP',
}, },
ok() { ok() {
domains = dnsModal.dnsServer.domains.filter(d => d.length>0); domains = dnsModal.dnsServer.domains.filter(d => d.length > 0);
dnsModal.dnsServer.domains = domains; dnsModal.dnsServer.domains = domains;
newDnsServer = domains.length > 0 ? dnsModal.dnsServer : dnsModal.dnsServer.address; newDnsServer = domains.length > 0 ? dnsModal.dnsServer : dnsModal.dnsServer.address;
ObjectUtil.execute(dnsModal.confirm, newDnsServer); ObjectUtil.execute(dnsModal.confirm, newDnsServer);
}, },
show({ title='', okText='{{ i18n "confirm" }}', dnsServer, confirm=(dnsServer)=>{}, isEdit=false }) { show({
title = '',
okText = '{{ i18n "confirm" }}',
dnsServer,
confirm = (dnsServer) => {},
isEdit = false
}) {
this.title = title; this.title = title;
this.okText = okText; this.okText = okText;
this.confirm = confirm; this.confirm = confirm;
this.visible = true; this.visible = true;
if(isEdit) { if (isEdit) {
if (typeof dnsServer == 'object'){ if (typeof dnsServer == 'object') {
this.dnsServer = dnsServer; this.dnsServer = dnsServer;
} else { } else {
this.dnsServer = { this.dnsServer = {
@ -72,7 +71,6 @@
dnsModal.visible = false; dnsModal.visible = false;
}, },
}; };
new Vue({ new Vue({
delimiters: ['[[', ']]'], delimiters: ['[[', ']]'],
el: '#dns-modal', el: '#dns-modal',
@ -81,10 +79,11 @@
}, },
computed: { computed: {
isAdvanced: { isAdvanced: {
get: function () { return dnsModal.dnsServer.domains.length>0 } get: function() {
return dnsModal.dnsServer.domains.length > 0
}
} }
} }
}); });
</script> </script>
{{end}} {{end}}

View file

@ -1,8 +1,8 @@
{{define "form/outbound"}} {{define "form/outbound"}}
<!-- base --> <!-- base -->
<a-tabs :active-key="outModal.activeKey" style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }"> <a-tabs :active-key="outModal.activeKey" style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }">
<a-tab-pane key="1" tab="Form"> <a-tab-pane key="1" tab="Form">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "protocol" }}'> <a-form-item label='{{ i18n "protocol" }}'>
<a-select v-model="outbound.protocol" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.protocol" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option> <a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option>
@ -15,26 +15,19 @@
<a-input v-model="outbound.sendThrough"></a-input> <a-input v-model="outbound.sendThrough"></a-input>
</a-form-item> </a-form-item>
<!-- freedom settings--> <!-- freedom settings-->
<template v-if="outbound.protocol === Protocols.Freedom"> <template v-if="outbound.protocol === Protocols.Freedom">
<a-form-item label='Strategy'> <a-form-item label='Strategy'>
<a-select <a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
v-model="outbound.settings.domainStrategy"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Fragment'> <a-form-item label='Fragment'>
<a-switch <a-switch :checked="Object.keys(outbound.settings.fragment).length >0" @change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}"></a-switch>
:checked="Object.keys(outbound.settings.fragment).length >0"
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
</a-switch>
</a-form-item> </a-form-item>
<template v-if="Object.keys(outbound.settings.fragment).length >0"> <template v-if="Object.keys(outbound.settings.fragment).length >0">
<a-form-item label='Packets'> <a-form-item label='Packets'>
<a-select <a-select v-model="outbound.settings.fragment.packets" :dropdown-class-name="themeSwitcher.currentTheme">
v-model="outbound.settings.fragment.packets"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -45,39 +38,36 @@
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input> <a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
<!-- blackhole settings --> <!-- blackhole settings -->
<template v-if="outbound.protocol === Protocols.Blackhole"> <template v-if="outbound.protocol === Protocols.Blackhole">
<a-form-item label='Response Type'> <a-form-item label='Response Type'>
<a-select <a-select v-model="outbound.settings.type" :dropdown-class-name="themeSwitcher.currentTheme">
v-model="outbound.settings.type"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>
<!-- dns settings --> <!-- dns settings -->
<template v-if="outbound.protocol === Protocols.DNS"> <template v-if="outbound.protocol === Protocols.DNS">
<a-form-item label='{{ i18n "pages.inbounds.network" }}'> <a-form-item label='{{ i18n "pages.inbounds.network" }}'>
<a-select <a-select v-model="outbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
v-model="outbound.settings.network"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>
<!-- wireguard settings --> <!-- wireguard settings -->
<template v-if="outbound.protocol === Protocols.Wireguard"> <template v-if="outbound.protocol === Protocols.Wireguard">
<a-form-item> <a-form-item>
<template slot="label"> <template slot="label">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> </template>
{{ i18n "pages.xray.outbound.address" }} <a-icon type="question-circle"></a-icon> {{ i18n "pages.xray.outbound.address" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="outbound.settings.address"></a-input> <a-input v-model.trim="outbound.settings.address"></a-input>
@ -89,9 +79,7 @@
<span>{{ i18n "reset" }}</span> <span>{{ i18n "reset" }}</span>
</template> </template>
{{ i18n "pages.xray.wireguard.secretKey" }} {{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync" <a-icon type="sync" @click="[outbound.settings.pubKey, outbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())"></a-icon>
@click="[outbound.settings.pubKey, outbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())">
</a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="outbound.settings.secretKey"></a-input> <a-input v-model.trim="outbound.settings.secretKey"></a-input>
@ -108,7 +96,7 @@
<a-input-number v-model.number="outbound.settings.mtu"></a-input-number> <a-input-number v-model.number="outbound.settings.mtu"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Workers'> <a-form-item label='Workers'>
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input> <a-input-number min="0" v-model.number="outbound.settings.workers"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Kernel Mode'> <a-form-item label='Kernel Mode'>
<a-switch v-model="outbound.settings.kernelMode"></a-switch> <a-switch v-model="outbound.settings.kernelMode"></a-switch>
@ -118,22 +106,17 @@
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> </template> Reserved <a-icon type="question-circle"></a-icon>
Reserved <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model="outbound.settings.reserved"></a-input> <a-input v-model="outbound.settings.reserved"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Peers"> <a-form-item label="Peers">
<a-button type="primary" size="small" @click="outbound.settings.addPeer()">+</a-button> <a-button icon="plus" type="primary" size="small" @click="outbound.settings.addPeer()"></a-button>
</a-form-item> </a-form-item>
<a-form v-for="(peer, index) in outbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form v-for="(peer, index) in outbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;"> <a-divider style="margin:0;"> Peer [[ index + 1 ]] <a-icon v-if="outbound.settings.peers.length>1" type="delete" @click="() => outbound.settings.delPeer(index)" style="color: rgb(255, 77, 79);cursor: pointer;"></a-icon>
Peer [[ index + 1 ]]
<a-icon v-if="outbound.settings.peers.length>1" type="delete" @click="() => outbound.settings.delPeer(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider> </a-divider>
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'> <a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
<a-input v-model.trim="peer.endpoint"></a-input> <a-input v-model.trim="peer.endpoint"></a-input>
</a-form-item> </a-form-item>
@ -145,35 +128,37 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<template slot="label"> <template slot="label">
{{ i18n "pages.xray.wireguard.allowedIPs" }} <a-button type="primary" size="small" @click="peer.allowedIPs.push('')">+</a-button> {{ i18n "pages.xray.wireguard.allowedIPs" }}
<a-button icon="plus" type="primary" size="small" @click="peer.allowedIPs.push('')"></a-button>
</template> </template>
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;"> <template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
<a-input v-model.trim="peer.allowedIPs[index]"> <a-input v-model.trim="peer.allowedIPs[index]">
<a-button v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)">-</a-button> <a-button icon="minus" v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)"></a-button>
</a-input> </a-input>
</template> </template>
</a-form-item> </a-form-item>
<a-form-item label='Keep Alive'> <a-form-item label='Keep Alive'>
<a-input-number v-model.number="peer.keepAlive" :min="0"></a-input> <a-input-number v-model.number="peer.keepAlive" :min="0"></a-input-number>
</a-form-item> </a-form-item>
</a-form> </a-form>
</template> </template>
<!-- Address + Port --> <!-- Address + Port -->
<template v-if="outbound.hasAddressPort()"> <template v-if="outbound.hasAddressPort()">
<a-form-item label='{{ i18n "pages.inbounds.address" }}'> <a-form-item label='{{ i18n "pages.inbounds.address" }}'>
<a-input v-model.trim="outbound.settings.address"></a-input> <a-input v-model.trim="outbound.settings.address"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'> <a-form-item label='{{ i18n "pages.inbounds.port" }}'>
<a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number> <a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
<!-- Vnext (vless/vmess) settings --> <!-- Vnext (vless/vmess) settings -->
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)"> <template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
<a-form-item label='ID'> <a-form-item label='ID'>
<a-input v-model.trim="outbound.settings.id"></a-input> <a-input v-model.trim="outbound.settings.id"></a-input>
</a-form-item> </a-form-item>
<!-- vless settings --> <!-- vless settings -->
<template v-if="outbound.canEnableTlsFlow()"> <template v-if="outbound.canEnableTlsFlow()">
<a-form-item label='Flow'> <a-form-item label='Flow'>
@ -183,10 +168,10 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
<!-- Servers (trojan/shadowsocks/socks/http) settings --> <!-- Servers (trojan/shadowsocks/socks/http) settings -->
<template v-if="outbound.hasServers()"> <template v-if="outbound.hasServers()">
<!-- http / socks --> <!-- http / socks -->
<template v-if="outbound.hasUsername()"> <template v-if="outbound.hasUsername()">
<a-form-item label='{{ i18n "username" }}'> <a-form-item label='{{ i18n "username" }}'>
@ -196,12 +181,14 @@
<a-input v-model.trim="outbound.settings.pass"></a-input> <a-input v-model.trim="outbound.settings.pass"></a-input>
</a-form-item> </a-form-item>
</template> </template>
<!-- trojan/shadowsocks -->
<template v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)"> <!-- trojan/shadowsocks -->
<template v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
<a-form-item label='{{ i18n "password" }}'> <a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.settings.password"></a-input> <a-input v-model.trim="outbound.settings.password"></a-input>
</a-form-item> </a-form-item>
</template> </template>
<!-- shadowsocks --> <!-- shadowsocks -->
<template v-if="outbound.protocol === Protocols.Shadowsocks"> <template v-if="outbound.protocol === Protocols.Shadowsocks">
<a-form-item label='{{ i18n "encryption" }}'> <a-form-item label='{{ i18n "encryption" }}'>
@ -213,13 +200,12 @@
<a-switch v-model="outbound.settings.uot"></a-switch> <a-switch v-model="outbound.settings.uot"></a-switch>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
<!-- stream settings --> <!-- stream settings -->
<template v-if="outbound.canEnableStream()"> <template v-if="outbound.canEnableStream()">
<a-form-item label='{{ i18n "transmission" }}'> <a-form-item label='{{ i18n "transmission" }}'>
<a-select v-model="outbound.stream.network" @change="streamNetworkChange" <a-select v-model="outbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option> <a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option> <a-select-option value="kcp">mKCP</a-select-option>
<a-select-option value="ws">WebSocket</a-select-option> <a-select-option value="ws">WebSocket</a-select-option>
@ -231,9 +217,7 @@
</a-form-item> </a-form-item>
<template v-if="outbound.stream.network === 'tcp'"> <template v-if="outbound.stream.network === 'tcp'">
<a-form-item label='HTTP {{ i18n "camouflage" }}'> <a-form-item label='HTTP {{ i18n "camouflage" }}'>
<a-switch :checked="outbound.stream.tcp.type === 'http'" <a-switch :checked="outbound.stream.tcp.type === 'http'" @change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'"></a-switch>
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
</a-switch>
</a-form-item> </a-form-item>
<template v-if="outbound.stream.tcp.type == 'http'"> <template v-if="outbound.stream.tcp.type == 'http'">
<a-form-item label='{{ i18n "host" }}'> <a-form-item label='{{ i18n "host" }}'>
@ -350,10 +334,10 @@
<a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input> <a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
<!-- tls settings --> <!-- tls settings -->
<template v-if="outbound.canEnableTls()"> <template v-if="outbound.canEnableTls()">
<a-form-item label='{{ i18n "security" }}'> <a-form-item label='{{ i18n "security" }}'>
<a-radio-group v-model="outbound.stream.security" button-style="solid"> <a-radio-group v-model="outbound.stream.security" button-style="solid">
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button> <a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
@ -366,16 +350,13 @@
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input> <a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="uTLS"> <a-form-item label="uTLS">
<a-select v-model="outbound.stream.tls.fingerprint" <a-select v-model="outbound.stream.tls.fingerprint" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option> <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-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="ALPN"> <a-form-item label="ALPN">
<a-select mode="multiple" <a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" v-model="outbound.stream.tls.alpn">
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="outbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option> <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -390,8 +371,7 @@
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input> <a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="uTLS"> <a-form-item label="uTLS">
<a-select v-model="outbound.stream.reality.fingerprint" <a-select v-model="outbound.stream.reality.fingerprint" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -405,9 +385,9 @@
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input> <a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
<!-- sockopt settings --> <!-- sockopt settings -->
<a-form-item label="Sockopts"> <a-form-item label="Sockopts">
<a-switch v-model="outbound.stream.sockoptSwitch"></a-switch> <a-switch v-model="outbound.stream.sockoptSwitch"></a-switch>
</a-form-item> </a-form-item>
@ -426,10 +406,10 @@
<a-form-item label="TCP No-Delay"> <a-form-item label="TCP No-Delay">
<a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch> <a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch>
</a-form-item> </a-form-item>
</template> </template>
<!-- mux settings --> <!-- mux settings -->
<template v-if="outbound.canEnableMux()"> <template v-if="outbound.canEnableMux()">
<a-form-item label="Mux"> <a-form-item label="Mux">
<a-switch v-model="outbound.mux.enabled"></a-switch> <a-switch v-model="outbound.mux.enabled"></a-switch>
</a-form-item> </a-form-item>
@ -446,16 +426,16 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
</a-form>
</a-form> </a-tab-pane>
</a-tab-pane> <a-tab-pane key="2" tab="JSON" force-render="true">
<a-tab-pane key="2" tab="JSON" force-render="true"> <a-form-item style="margin: 10px 0"> Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss://"></a-input>
<a-form-item style="margin: 10px 0"> <a-button @click="convertLink" type="primary">
Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss://"></a-input> <a-icon type="form"></a-icon>
<a-button @click="convertLink" type="primary"><a-icon type="form"></a-icon></a-button> </a-button>
</a-form-item> </a-form-item>
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea> <textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
{{end}} {{end}}

View file

@ -4,7 +4,9 @@
<tr> <tr>
<td width="45%">{{ i18n "username" }}</td> <td width="45%">{{ i18n "username" }}</td>
<td width="45%">{{ i18n "password" }}</td> <td width="45%">{{ i18n "password" }}</td>
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())">+</a-button></td> <td>
<a-button icon="plus" size="small" @click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())"></a-button>
</td>
</tr> </tr>
</table> </table>
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;"> <a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
@ -13,7 +15,7 @@
</a-input> </a-input>
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'> <a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
<template slot="addonAfter"> <template slot="addonAfter">
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button> <a-button icon="minus" size="small" @click="inbound.settings.delAccount(index)"></a-button>
</template> </template>
</a-input> </a-input>
</a-input-group> </a-input-group>

View file

@ -7,15 +7,16 @@
<a-input v-model.trim="inbound.settings.ip"></a-input> <a-input v-model.trim="inbound.settings.ip"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "password" }}'> <a-form-item label='{{ i18n "password" }}'>
<a-switch :checked="inbound.settings.auth === 'password'" <a-switch :checked="inbound.settings.auth === 'password'" @change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
</a-form-item> </a-form-item>
<template v-if="inbound.settings.auth === 'password'"> <template v-if="inbound.settings.auth === 'password'">
<table style="width: 100%; text-align: center; margin: 1rem 0;"> <table style="width: 100%; text-align: center; margin: 1rem 0;">
<tr> <tr>
<td width="45%">{{ i18n "username" }}</td> <td width="45%">{{ i18n "username" }}</td>
<td width="45%">{{ i18n "password" }}</td> <td width="45%">{{ i18n "password" }}</td>
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td> <td>
<a-button icon="plus" size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())"></a-button>
</td>
</tr> </tr>
</table> </table>
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;"> <a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
@ -24,7 +25,7 @@
</a-input> </a-input>
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'> <a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
<template slot="addonAfter"> <template slot="addonAfter">
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button> <a-button icon="minus" size="small" @click="inbound.settings.delAccount(index)"></a-button>
</template> </template>
</a-input> </a-input>
</a-input-group> </a-input-group>

View file

@ -21,17 +21,13 @@
<template v-if="inbound.isTcp && !inbound.stream.isReality"> <template v-if="inbound.isTcp && !inbound.stream.isReality">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Fallbacks"> <a-form-item label="Fallbacks">
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button> <a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
</a-form-item> </a-form-item>
</a-form> </a-form>
<!-- trojan fallbacks --> <!-- trojan fallbacks -->
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }" <a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
:wrapper-col="{ md: {span:14} }"> <a-divider style="margin:0;"> Fallback [[ index + 1 ]] <a-icon type="delete" @click="() => inbound.settings.delFallback(index)" style="color: rgb(255, 77, 79);cursor: pointer;"></a-icon>
<a-divider style="margin:0;">
Fallback [[ index + 1 ]]
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
style="color: rgb(255, 77, 79);cursor: pointer;" />
</a-divider> </a-divider>
<a-form-item label='SNI'> <a-form-item label='SNI'>
<a-input v-model="fallback.name"></a-input> <a-input v-model="fallback.name"></a-input>

View file

@ -23,17 +23,13 @@
<template v-if="inbound.isTcp && !inbound.stream.isReality"> <template v-if="inbound.isTcp && !inbound.stream.isReality">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Fallbacks"> <a-form-item label="Fallbacks">
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button> <a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
</a-form-item> </a-form-item>
</a-form> </a-form>
<!-- vless fallbacks --> <!-- vless fallbacks -->
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }" <a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
:wrapper-col="{ md: {span:14} }"> <a-divider style="margin:0;"> Fallback [[ index + 1 ]] <a-icon type="delete" @click="() => inbound.settings.delFallback(index)" style="color: rgb(255, 77, 79);cursor: pointer;"></a-icon>
<a-divider style="margin:0;">
Fallback [[ index + 1 ]]
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
style="color: rgb(255, 77, 79);cursor: pointer;" />
</a-divider> </a-divider>
<a-form-item label='SNI'> <a-form-item label='SNI'>
<a-input v-model="fallback.name"></a-input> <a-input v-model="fallback.name"></a-input>

View file

@ -7,9 +7,7 @@
<span>{{ i18n "reset" }}</span> <span>{{ i18n "reset" }}</span>
</template> </template>
{{ i18n "pages.xray.wireguard.secretKey" }} {{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync" <a-icon type="sync" @click="[inbound.settings.pubKey, inbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())"></a-icon>
@click="[inbound.settings.pubKey, inbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())">
</a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="inbound.settings.secretKey"></a-input> <a-input v-model.trim="inbound.settings.secretKey"></a-input>
@ -24,13 +22,10 @@
<a-switch v-model="inbound.settings.kernelMode"></a-switch> <a-switch v-model="inbound.settings.kernelMode"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="Peers"> <a-form-item label="Peers">
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button> <a-button icon="plus" type="primary" size="small" @click="inbound.settings.addPeer()"></a-button>
</a-form-item> </a-form-item>
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;"> <a-divider style="margin:0;"> Peer [[ index + 1 ]] <a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)" style="color: rgb(255, 77, 79);cursor: pointer;"></a-icon>
Peer [[ index + 1 ]]
<a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider> </a-divider>
<a-form-item> <a-form-item>
<template slot="label"> <template slot="label">
@ -39,7 +34,7 @@
<span>{{ i18n "reset" }}</span> <span>{{ i18n "reset" }}</span>
</template> </template>
{{ i18n "pages.xray.wireguard.secretKey" }} {{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon @click="[peer.publicKey, peer.privateKey] = Object.values(Wireguard.generateKeypair())"type="sync"> </a-icon> <a-icon @click="[peer.publicKey, peer.privateKey] = Object.values(Wireguard.generateKeypair())" type="sync"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="peer.privateKey"></a-input> <a-input v-model.trim="peer.privateKey"></a-input>
@ -57,23 +52,24 @@
<span>{{ i18n "reset" }}</span> <span>{{ i18n "reset" }}</span>
</template> </template>
{{ i18n "pages.xray.wireguard.psk" }} {{ i18n "pages.xray.wireguard.psk" }}
<a-icon @click="peer.psk = Wireguard.keyToBase64(Wireguard.generatePresharedKey())"type="sync"> </a-icon> <a-icon @click="peer.psk = Wireguard.keyToBase64(Wireguard.generatePresharedKey())" type="sync"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="peer.psk"></a-input> <a-input v-model.trim="peer.psk"></a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<template slot="label"> <template slot="label">
{{ i18n "pages.xray.wireguard.allowedIPs" }} <a-button type="primary" size="small" @click="peer.allowedIPs.push('')">+</a-button> {{ i18n "pages.xray.wireguard.allowedIPs" }}
<a-button icon="plus" type="primary" size="small" @click="peer.allowedIPs.push('')"></a-button>
</template> </template>
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;"> <template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
<a-input v-model.trim="peer.allowedIPs[index]"> <a-input v-model.trim="peer.allowedIPs[index]">
<a-button v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)">-</a-button> <a-button icon="minus" v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)"></a-button>
</a-input> </a-input>
</template> </template>
</a-form-item> </a-form-item>
<a-form-item label='Keep Alive'> <a-form-item label='Keep Alive'>
<a-input-number v-model.number="peer.keepAlive" :min="0"></a-input> <a-input-number v-model.number="peer.keepAlive" :min="0"></a-input-number>
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-form> </a-form>

View file

@ -3,7 +3,7 @@
<a-divider style="margin:5px 0 0;"></a-divider> <a-divider style="margin:5px 0 0;"></a-divider>
<a-form-item label="External Proxy"> <a-form-item label="External Proxy">
<a-switch v-model="externalProxy"></a-switch> <a-switch v-model="externalProxy"></a-switch>
<a-button v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button> <a-button icon="plus" v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})"></a-button>
</a-form-item> </a-form-item>
<a-input-group style="margin: 8px 0;" compact v-for="(row, index) in inbound.stream.externalProxy"> <a-input-group style="margin: 8px 0;" compact v-for="(row, index) in inbound.stream.externalProxy">
<template> <template>
@ -19,8 +19,11 @@
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'> <a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number> <a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
</a-tooltip> </a-tooltip>
<a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input> <a-input style="width: 30%; top: 0;" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'>
<a-button style="width: 10%; margin: 0px; border-radius: 0 1rem 1rem 0;" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button> <template slot="addonAfter">
<a-button icon="minus" size="small" @click="inbound.stream.externalProxy.splice(index, 1)"></a-button>
</template>
</a-input>
</a-input-group> </a-input-group>
</a-form> </a-form>
{{end}} {{end}}

View file

@ -5,13 +5,11 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<template slot="label">{{ i18n "host" }} <template slot="label">{{ i18n "host" }}
<a-button size="small" @click="inbound.stream.http.addHost()">+</a-button> <a-button icon="plus" size="small" @click="inbound.stream.http.addHost()"></a-button>
</template> </template>
<template v-for="(host, index) in inbound.stream.http.host"> <template v-for="(host, index) in inbound.stream.http.host">
<a-input v-model.trim="inbound.stream.http.host[index]"> <a-input v-model.trim="inbound.stream.http.host[index]">
<a-button size="small" slot="addonAfter" <a-button icon="minus" size="small" slot="addonAfter" @click="inbound.stream.http.removeHost(index)" v-if="inbound.stream.http.host.length>1"></a-button>
@click="inbound.stream.http.removeHost(index)"
v-if="inbound.stream.http.host.length>1">-</a-button>
</a-input> </a-input>
</template> </template>
</a-form-item> </a-form-item>

View file

@ -10,7 +10,7 @@
<a-input v-model.trim="inbound.stream.httpupgrade.path"></a-input> <a-input v-model.trim="inbound.stream.httpupgrade.path"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button size="small" @click="inbound.stream.httpupgrade.addHeader('host', '')">+</a-button> <a-button icon="plus" size="small" @click="inbound.stream.httpupgrade.addHeader('host', '')"></a-button>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.httpupgrade.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.httpupgrade.headers">
@ -18,7 +18,7 @@
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input> </a-input>
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="inbound.stream.httpupgrade.removeHeader(index)">-</a-button> <a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.httpupgrade.removeHeader(index)"></a-button>
</a-input> </a-input>
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>

View file

@ -5,14 +5,11 @@
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch> <a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='HTTP {{ i18n "camouflage" }}'> <a-form-item label='HTTP {{ i18n "camouflage" }}'>
<a-switch :checked="inbound.stream.tcp.type === 'http'" <a-switch :checked="inbound.stream.tcp.type === 'http'" @change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'"></a-switch>
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
</a-switch>
</a-form-item> </a-form-item>
</a-form> </a-form>
<a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }" <a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
:wrapper-col="{ md: {span:14} }">
<!-- tcp request --> <!-- tcp request -->
<a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.request" }}</a-divider> <a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.request" }}</a-divider>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
@ -23,28 +20,24 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<template slot="label">{{ i18n "pages.inbounds.stream.tcp.path" }} <template slot="label">{{ i18n "pages.inbounds.stream.tcp.path" }}
<a-button size="small" @click="inbound.stream.tcp.request.addPath('/')">+</a-button> <a-button icon="plus" size="small" @click="inbound.stream.tcp.request.addPath('/')"></a-button>
</template> </template>
<template v-for="(path, index) in inbound.stream.tcp.request.path"> <template v-for="(path, index) in inbound.stream.tcp.request.path">
<a-input v-model.trim="inbound.stream.tcp.request.path[index]"> <a-input v-model.trim="inbound.stream.tcp.request.path[index]">
<a-button size="small" slot="addonAfter" @click="inbound.stream.tcp.request.removePath(index)" <a-button icon="minus" size="small" slot="addonAfter" @click="inbound.stream.tcp.request.removePath(index)" v-if="inbound.stream.tcp.request.path.length>1"></a-button>
v-if="inbound.stream.tcp.request.path.length>1">-</a-button>
</a-input> </a-input>
</template> </template>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('Host', '')">+</a-button> <a-button icon="plus" size="small" @click="inbound.stream.tcp.request.addHeader('Host', '')"></a-button>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
<a-input style="width: 50%" v-model.trim="header.name" <a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input> </a-input>
<a-input style="width: 50%" v-model.trim="header.value" <a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.tcp.request.removeHeader(index)"></a-button>
<a-button slot="addonAfter" size="small"
@click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
</a-input> </a-input>
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
@ -61,19 +54,16 @@
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input> <a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
<a-button size="small" <a-button icon="plus" size="small" @click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')"></a-button>
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers">
<a-input style="width: 50%" v-model.trim="header.name" <a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input> </a-input>
<a-input style="width: 50%" v-model.trim="header.value" <a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter"> <template slot="addonAfter">
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button> <a-button icon="minus" size="small" @click="inbound.stream.tcp.response.removeHeader(index)"></a-button>
</template> </template>
</a-input> </a-input>
</a-input-group> </a-input-group>

View file

@ -10,7 +10,7 @@
<a-input v-model.trim="inbound.stream.ws.path"></a-input> <a-input v-model.trim="inbound.stream.ws.path"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button size="small" @click="inbound.stream.ws.addHeader('host', '')">+</a-button> <a-button icon="plus" size="small" @click="inbound.stream.ws.addHeader('host', '')"></a-button>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
@ -18,7 +18,7 @@
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input> </a-input>
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)">-</a-button> <a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)"></a-button>
</a-input> </a-input>
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>

View file

@ -43,15 +43,13 @@
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
<a-form-item label="uTLS"> <a-form-item label="uTLS">
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 50%" <a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option> <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-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="ALPN"> <a-form-item label="ALPN">
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" <a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" v-model="inbound.stream.tls.alpn">
v-model="inbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option> <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -67,10 +65,8 @@
<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-radio-group> </a-radio-group>
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" <a-button icon="plus" v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px"></a-button>
style="margin-left: 10px">+</a-button> <a-button icon="minus" 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-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>
<template v-if="cert.useFile"> <template v-if="cert.useFile">
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'> <a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
@ -80,8 +76,7 @@
<a-input v-model.trim="cert.keyFile"></a-input> <a-input v-model.trim="cert.keyFile"></a-input>
</a-form-item> </a-form-item>
<a-form-item label=" "> <a-form-item label=" ">
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n <a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
"pages.inbounds.setDefaultCert" }}</a-button>
</a-form-item> </a-form-item>
</template> </template>
<template v-else> <template v-else>
@ -104,8 +99,7 @@
<a-input v-model.trim="inbound.stream.xtls.sni"></a-input> <a-input v-model.trim="inbound.stream.xtls.sni"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="ALPN"> <a-form-item label="ALPN">
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" <a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" v-model="inbound.stream.xtls.alpn">
v-model="inbound.stream.xtls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option> <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -118,10 +112,8 @@
<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-radio-group> </a-radio-group>
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.xtls.addCert()" <a-button icon="plus" v-if="index === 0" type="primary" size="small" @click="inbound.stream.xtls.addCert()" style="margin-left: 10px"></a-button>
style="margin-left: 10px">+</a-button> <a-button icon="minus" v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small" @click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px"></a-button>
<a-button v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small"
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
</a-form-item> </a-form-item>
<template v-if="cert.useFile"> <template v-if="cert.useFile">
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'> <a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
@ -131,8 +123,7 @@
<a-input v-model.trim="cert.keyFile"></a-input> <a-input v-model.trim="cert.keyFile"></a-input>
</a-form-item> </a-form-item>
<a-form-item label=" "> <a-form-item label=" ">
<a-button type="primary" icon="import" @click="setDefaultCertXtls(index)">{{ i18n <a-button type="primary" icon="import" @click="setDefaultCertXtls(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
"pages.inbounds.setDefaultCert" }}</a-button>
</a-form-item> </a-form-item>
</template> </template>
<template v-else> <template v-else>
@ -145,6 +136,7 @@
</template> </template>
</template> </template>
</template> </template>
<!-- reality settings --> <!-- reality settings -->
<template v-if="inbound.stream.isReality"> <template v-if="inbound.stream.isReality">
<a-form-item label='Show'> <a-form-item label='Show'>
@ -154,8 +146,7 @@
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number> <a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='uTLS'> <a-form-item label='uTLS'>
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 50%" <a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -170,10 +161,8 @@
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "reset" }}</span> <span>{{ i18n "reset" }}</span>
</template> </template> Short ID <a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId()" type="sync"></a-icon>
Short ID </a-tooltip>
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId()" type="sync"> </a-icon>
</a-icon>
</template> </template>
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input> <a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
</a-form-item> </a-form-item>

View file

@ -14,23 +14,16 @@
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template> <template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
<a-popconfirm @confirm="resetClientTraffic(client,record.id,false)" <a-popconfirm @confirm="resetClientTraffic(client,record.id,false)" title='{{ i18n "pages.inbounds.resetTrafficContent"}}' :overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "reset"}}' cancel-text='{{ i18n "cancel"}}'>
title='{{ i18n "pages.inbounds.resetTrafficContent"}}'
:overlay-class-name="themeSwitcher.currentTheme"
ok-text='{{ i18n "reset"}}'
cancel-text='{{ i18n "cancel"}}'>
<a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: var(--color-primary-100)' : 'color: var(--color-primary-100)'"></a-icon> <a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: var(--color-primary-100)' : 'color: var(--color-primary-100)'"></a-icon>
<a-icon style="font-size: 24px; cursor: pointer;" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon> <a-icon style="font-size: 24px; cursor: pointer;" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon>
</a-popconfirm> </a-popconfirm>
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
<template slot="title"><span style="color: #FF4D4F"> {{ i18n "delete"}}</span></template> <template slot="title">
<a-popconfirm @confirm="delClient(record.id,client,false)" <span style="color: #FF4D4F"> {{ i18n "delete"}}</span>
title='{{ i18n "pages.inbounds.deleteClientContent"}}' </template>
:overlay-class-name="themeSwitcher.currentTheme" <a-popconfirm @confirm="delClient(record.id,client,false)" title='{{ i18n "pages.inbounds.deleteClientContent"}}' :overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "delete"}}' ok-type="danger" cancel-text='{{ i18n "cancel"}}'>
ok-text='{{ i18n "delete"}}'
ok-type="danger"
cancel-text='{{ i18n "cancel"}}'>
<a-icon slot="icon" type="question-circle-o" style="color: #e04141"></a-icon> <a-icon slot="icon" type="question-circle-o" style="color: #e04141"></a-icon>
<a-icon style="font-size: 24px; cursor: pointer;" class="delete-icon" type="delete" v-if="isRemovable(record.id)"></a-icon> <a-icon style="font-size: 24px; cursor: pointer;" class="delete-icon" type="delete" v-if="isRemovable(record.id)"></a-icon>
</a-popconfirm> </a-popconfirm>
@ -54,10 +47,8 @@
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template> <template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
<template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template> <template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template>
</template> </template>
<a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"> <a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-badge>
</a-badge> </a-tooltip> [[ client.email ]]
</a-tooltip>
[[ client.email ]]
</template> </template>
<template slot="traffic" slot-scope="text, client"> <template slot="traffic" slot-scope="text, client">
<a-popover :overlay-class-name="themeSwitcher.currentTheme"> <a-popover :overlay-class-name="themeSwitcher.currentTheme">
@ -74,29 +65,20 @@
</table> </table>
</template> </template>
<table> <table>
<tr> <tr class="tr-table-box">
<td width="80px" style="margin:0; text-align: right;font-size: 1em;"> <td class="tr-table-rt"> [[ sizeFormat(getSumStats(record, client.email)) ]] </td>
[[ sizeFormat(getSumStats(record, client.email)) ]] <td class="tr-table-bar" v-if="!client.enable">
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
</td> </td>
<td width="120px" v-if="!client.enable"> <td class="tr-table-bar" v-else-if="client.totalGB > 0">
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" <a-progress :stroke-color="clientStatsColor(record, client.email)" :show-info="false" :status="isClientEnabled(record, client.email)? 'exception' : ''" :percent="statsProgress(record, client.email)" />
:show-info="false"
:percent="statsProgress(record, client.email)"/>
</td> </td>
<td width="120px" v-else-if="client.totalGB > 0"> <td v-else class="infinite-bar tr-table-bar">
<a-progress :stroke-color="clientStatsColor(record, client.email)" <a-progress :show-info="false" :percent="100"></a-progress>
:show-info="false"
:status="isClientEnabled(record, client.email)? 'exception' : ''"
:percent="statsProgress(record, client.email)"/>
</td> </td>
<td width="120px" v-else class="infinite-bar"> <td class="tr-table-lt">
<a-progress
:show-info="false"
:percent="100"></a-progress>
</td>
<td width="60px">
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template> <template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
<span v-else style="font-weight: 100;font-size: 14pt;">&infin;</span> <span v-else class="tr-infinity-ch">&infin;</span>
</td> </td>
</tr> </tr>
</table> </table>
@ -106,20 +88,17 @@
<template v-if="client.expiryTime !=0 && client.reset >0"> <template v-if="client.expiryTime !=0 && client.reset >0">
<a-popover :overlay-class-name="themeSwitcher.currentTheme"> <a-popover :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <template slot="content">
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span> <span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
</span>
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span> <span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
</template> </template>
<table> <table>
<tr> <tr class="tr-table-box">
<td width="80px" style="margin:0; text-align: right;font-size: 1em;"> <td class="tr-table-rt"> [[ remainedDays(client.expiryTime) ]] </td>
[[ remainedDays(client.expiryTime) ]] <td class="infinite-bar tr-table-bar">
<a-progress :show-info="false" :status="isClientEnabled(record, client.email)? 'exception' : ''" :percent="expireProgress(client.expiryTime, client.reset)" />
</td> </td>
<td width="120px" class="infinite-bar"> <td class="tr-table-lt">[[ client.reset + "d" ]]</td>
<a-progress :show-info="false"
:status="isClientEnabled(record, client.email)? 'exception' : ''"
:percent="expireProgress(client.expiryTime, client.reset)"/>
</td>
<td width="60px">[[ client.reset + "d" ]]</td>
</tr> </tr>
</table> </table>
</a-popover> </a-popover>
@ -127,14 +106,17 @@
<template v-else> <template v-else>
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme"> <a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <template slot="content">
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span> <span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
</span>
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span> <span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
</template> </template>
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> <a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
[[ remainedDays(client.expiryTime) ]]
</a-tag>
</a-popover> </a-popover>
<a-tag v-else :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" style="border: none;" class="infinite-tag">&infin;</a-tag> <a-tag v-else :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" style="border: none;" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg>
</a-tag>
</template> </template>
</template> </template>
<template slot="actionMenu" slot-scope="text, client, index"> <template slot="actionMenu" slot-scope="text, client, index">
@ -162,8 +144,7 @@
<span style="color: #FF4D4F"> {{ i18n "delete"}}</span> <span style="color: #FF4D4F"> {{ i18n "delete"}}</span>
</a-menu-item> </a-menu-item>
<a-menu-item> <a-menu-item>
<a-switch v-model="client.enable" size="small" @change="switchEnableClient(record.id,client)"> <a-switch v-model="client.enable" size="small" @change="switchEnableClient(record.id,client)"></a-switch>
</a-switch>
{{ i18n "enable"}} {{ i18n "enable"}}
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
@ -177,13 +158,9 @@
<td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td> <td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td>
</tr> </tr>
<tr> <tr>
<td width="80px" style="margin:0; text-align: right;font-size: 1em;"> <td width="80px" style="margin:0; text-align: right;font-size: 1em;"> [[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] </td>
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]]
</td>
<td width="120px" v-if="!client.enable"> <td width="120px" v-if="!client.enable">
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" <a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
:show-info="false"
:percent="statsProgress(record, client.email)"/>
</td> </td>
<td width="120px" v-else-if="client.totalGB > 0"> <td width="120px" v-else-if="client.totalGB > 0">
<a-popover :overlay-class-name="themeSwitcher.currentTheme"> <a-popover :overlay-class-name="themeSwitcher.currentTheme">
@ -199,20 +176,15 @@
</tr> </tr>
</table> </table>
</template> </template>
<a-progress :stroke-color="clientStatsColor(record, client.email)" <a-progress :stroke-color="clientStatsColor(record, client.email)" :show-info="false" :status="isClientEnabled(record, client.email)? 'exception' : ''" :percent="statsProgress(record, client.email)" />
:show-info="false"
:status="isClientEnabled(record, client.email)? 'exception' : ''"
:percent="statsProgress(record, client.email)"/>
</a-popover> </a-popover>
</td> </td>
<td width="120px" v-else class="infinite-bar"> <td width="120px" v-else class="infinite-bar">
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'" <a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'" :show-info="false" :percent="100"></a-progress>
:show-info="false"
:percent="100"></a-progress>
</td> </td>
<td width="80px"> <td width="80px">
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template> <template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
<span v-else style="font-weight: 100;font-size: 14pt;">&infin;</span> <span v-else class="tr-infinity-ch">&infin;</span>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -223,18 +195,15 @@
</tr> </tr>
<tr> <tr>
<template v-if="client.expiryTime !=0 && client.reset >0"> <template v-if="client.expiryTime !=0 && client.reset >0">
<td width="80px" style="margin:0; text-align: right;font-size: 1em;"> <td width="80px" style="margin:0; text-align: right;font-size: 1em;"> [[ remainedDays(client.expiryTime) ]] </td>
[[ remainedDays(client.expiryTime) ]]
</td>
<td width="120px" class="infinite-bar"> <td width="120px" class="infinite-bar">
<a-popover :overlay-class-name="themeSwitcher.currentTheme"> <a-popover :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <template slot="content">
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span> <span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
</span>
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span> <span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
</template> </template>
<a-progress :show-info="false" <a-progress :show-info="false" :status="isClientEnabled(record, client.email)? 'exception' : ''" :percent="expireProgress(client.expiryTime, client.reset)" />
:status="isClientEnabled(record, client.email)? 'exception' : ''"
:percent="expireProgress(client.expiryTime, client.reset)"/>
</a-popover> </a-popover>
</td> </td>
<td width="60px">[[ client.reset + "d" ]]</td> <td width="60px">[[ client.reset + "d" ]]</td>
@ -243,15 +212,17 @@
<td colspan="3" style="text-align: center;"> <td colspan="3" style="text-align: center;">
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme"> <a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content"> <template slot="content">
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span> <span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
</span>
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span> <span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
</template> </template>
<a-tag style="min-width: 50px; border: none;" <a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
:color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)">
[[ remainedDays(client.expiryTime) ]]
</a-tag>
</a-popover> </a-popover>
<a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag">&infin;</a-tag> <a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg>
</a-tag>
</template> </template>
</td> </td>
</tr> </tr>
@ -259,7 +230,9 @@
</template> </template>
<a-badge> <a-badge>
<a-icon v-if="!client.enable" slot="count" type="pause-circle" theme="filled" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon> <a-icon v-if="!client.enable" slot="count" type="pause-circle" theme="filled" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;"><a-icon type="solution"></a-icon></a-button> <a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
<a-icon type="solution"></a-icon>
</a-button>
</a-badge> </a-badge>
</a-popover> </a-popover>
</template> </template>

View file

@ -1,29 +1,38 @@
{{define "inboundInfoModal"}} {{define "inboundInfoModal"}}
<a-modal id="inbound-info-modal" <a-modal id="inbound-info-modal" v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}' :closable="true" :mask-closable="true" :footer="null" width="600px" :class="themeSwitcher.currentTheme">
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
:closable="true"
:mask-closable="true"
:footer="null"
width="600px"
:class="themeSwitcher.currentTheme"
>
<a-row> <a-row>
<a-col :xs="24" :md="12"> <a-col :xs="24" :md="12">
<table> <table>
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="purple">[[ dbInbound.protocol ]]</a-tag></td></tr> <tr>
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td> <td>{{ i18n "protocol" }}</td>
<td>
<a-tag color="purple">[[ dbInbound.protocol ]]</a-tag>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.address" }}</td>
<td>
<a-tooltip :title="[[ dbInbound.address ]]"> <a-tooltip :title="[[ dbInbound.address ]]">
<a-tag class="info-large-tag">[[ dbInbound.address ]]</a-tag> <a-tag class="info-large-tag">[[ dbInbound.address ]]</a-tag>
</a-tooltip> </a-tooltip>
</td></tr> </td>
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag>[[ dbInbound.port ]]</a-tag></td></tr> </tr>
<tr>
<td>{{ i18n "pages.inbounds.port" }}</td>
<td>
<a-tag>[[ dbInbound.port ]]</a-tag>
</td>
</tr>
</table> </table>
</a-col> </a-col>
<a-col :xs="24" :md="12"> <a-col :xs="24" :md="12">
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS"> <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<table> <table>
<tr> <tr>
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td> <td>{{ i18n "transmission" }}</td>
<td>
<a-tag color="green">[[ inbound.network ]]</a-tag>
</td>
</tr> </tr>
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade "> <template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade ">
<tr> <tr>
@ -33,7 +42,10 @@
<a-tag class="info-large-tag">[[ inbound.host ]]</a-tag> <a-tag class="info-large-tag">[[ inbound.host ]]</a-tag>
</a-tooltip> </a-tooltip>
</td> </td>
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr> <td v-else>
<a-tag color="orange">{{ i18n "none" }}</a-tag>
</td>
</tr>
</tr> </tr>
<tr> <tr>
<td>{{ i18n "path" }}</td> <td>{{ i18n "path" }}</td>
@ -41,27 +53,58 @@
<a-tooltip :title="[[ inbound.path ]]"> <a-tooltip :title="[[ inbound.path ]]">
<a-tag class="info-large-tag">[[ inbound.path ]]</a-tag> <a-tag class="info-large-tag">[[ inbound.path ]]</a-tag>
</a-tooltip> </a-tooltip>
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td> <td v-else>
<a-tag color="orange">{{ i18n "none" }}</a-tag>
</td>
</tr> </tr>
</template> </template>
<template v-if="inbound.isQuic"> <template v-if="inbound.isQuic">
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.quicSecurity ]]</a-tag></td></tr> <tr>
<tr><td>quic {{ i18n "password" }}</td><td><a-tag>[[ inbound.quicKey ]]</a-tag></td></tr> <td>quic {{ i18n "encryption" }}</td>
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag>[[ inbound.quicType ]]</a-tag></td></tr> <td>
<a-tag>[[ inbound.quicSecurity ]]</a-tag>
</td>
</tr>
<tr>
<td>quic {{ i18n "password" }}</td>
<td>
<a-tag>[[ inbound.quicKey ]]</a-tag>
</td>
</tr>
<tr>
<td>quic {{ i18n "camouflage" }}</td>
<td>
<a-tag>[[ inbound.quicType ]]</a-tag>
</td>
</tr>
</template> </template>
<template v-if="inbound.isKcp"> <template v-if="inbound.isKcp">
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.kcpType ]]</a-tag></td></tr> <tr>
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag>[[ inbound.kcpSeed ]]</a-tag></td></tr> <td>kcp {{ i18n "encryption" }}</td>
<td>
<a-tag>[[ inbound.kcpType ]]</a-tag>
</td>
</tr>
<tr>
<td>kcp {{ i18n "password" }}</td>
<td>
<a-tag>[[ inbound.kcpSeed ]]</a-tag>
</td>
</tr>
</template> </template>
<template v-if="inbound.isGrpc"> <template v-if="inbound.isGrpc">
<tr><td>grpc serviceName</td><td> <tr>
<td>grpc serviceName</td>
<td>
<a-tooltip :title="[[ inbound.serviceName ]]"> <a-tooltip :title="[[ inbound.serviceName ]]">
<a-tag class="info-large-tag">[[ inbound.serviceName ]]</a-tag> <a-tag class="info-large-tag">[[ inbound.serviceName ]]</a-tag>
</a-tooltip> </a-tooltip>
<tr><td>grpc multiMode</td><td><a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr> <tr>
<td>grpc multiMode</td>
<td>
<a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag>
</td>
</tr>
</template> </template>
</table> </table>
</template> </template>
@ -72,23 +115,30 @@
<br /> <br />
<template v-if="inbound.stream.security != 'none'"> <template v-if="inbound.stream.security != 'none'">
{{ i18n "domainName" }} {{ i18n "domainName" }}
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> <a-tag v-if="inbound.serverName" :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
<a-tag v-else color="orange">{{ i18n "none" }}</a-tag>
</template> </template>
</template> </template>
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;"> <table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
<tr> <tr>
<td>{{ i18n "encryption" }}</td> <td>{{ i18n "encryption" }}</td>
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td> <td>
</tr><tr v-if="inbound.isSS2022"> <a-tag color="green">[[ inbound.settings.method ]]</a-tag>
</td>
</tr>
<tr v-if="inbound.isSS2022">
<td>{{ i18n "password" }}</td> <td>{{ i18n "password" }}</td>
<td> <td>
<a-tooltip :title="[[ inbound.settings.password ]]"> <a-tooltip :title="[[ inbound.settings.password ]]">
<a-tag class="info-large-tag">[[ inbound.settings.password ]]</a-tag> <a-tag class="info-large-tag">[[ inbound.settings.password ]]</a-tag>
</a-tooltip> </a-tooltip>
</td> </td>
</tr><tr> </tr>
<tr>
<td>{{ i18n "pages.inbounds.network" }}</td> <td>{{ i18n "pages.inbounds.network" }}</td>
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td> <td>
<a-tag color="green">[[ inbound.settings.network ]]</a-tag>
</td>
</tr> </tr>
</table> </table>
<template v-if="infoModal.clientSettings"> <template v-if="infoModal.clientSettings">
@ -96,19 +146,36 @@
<table style="margin-bottom: 10px;"> <table style="margin-bottom: 10px;">
<tr> <tr>
<td>{{ i18n "pages.inbounds.email" }}</td> <td>{{ i18n "pages.inbounds.email" }}</td>
<td><a-tag color="green">[[ infoModal.clientSettings.email ]]</a-tag></td> <td v-if="infoModal.clientSettings.email">
<a-tag color="green">[[ infoModal.clientSettings.email ]]</a-tag>
</td>
<td v-else>
<a-tag color="red">{{ i18n "none" }}</a-tag>
</td>
</tr> </tr>
<tr v-if="infoModal.clientSettings.id"> <tr v-if="infoModal.clientSettings.id">
<td>ID</td> <td>ID</td>
<td><a-tag>[[ infoModal.clientSettings.id ]]</a-tag></td> <td>
<a-tag>[[ infoModal.clientSettings.id ]]</a-tag>
</td>
</tr> </tr>
<tr v-if="infoModal.inbound.canEnableTlsFlow()"> <tr v-if="infoModal.inbound.canEnableTlsFlow()">
<td>Flow</td> <td>Flow</td>
<td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td> <td v-if="infoModal.clientSettings.flow">
<a-tag>[[ infoModal.clientSettings.flow ]]</a-tag>
</td>
<td v-else>
<a-tag color="orange">{{ i18n "none" }}</a-tag>
</td>
</tr> </tr>
<tr v-if="infoModal.inbound.xtls"> <tr v-if="infoModal.inbound.xtls">
<td>Flow</td> <td>Flow</td>
<td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td> <td v-if="infoModal.clientSettings.flow">
<a-tag>[[ infoModal.clientSettings.flow ]]</a-tag>
</td>
<td v-else>
<a-tag color="orange">{{ i18n "none" }}</a-tag>
</td>
</tr> </tr>
<tr v-if="infoModal.clientSettings.password"> <tr v-if="infoModal.clientSettings.password">
<td>{{ i18n "password" }}</td> <td>{{ i18n "password" }}</td>
@ -134,7 +201,7 @@
</td> </td>
</tr> </tr>
</table> </table>
<table style="margin-bottom: 10px; width: 100%; text-align: center;"> <table style="display: inline-table; margin-block: 10px; width: 100%; text-align: center;">
<tr> <tr>
<th>{{ i18n "remained" }}</th> <th>{{ i18n "remained" }}</th>
<th>{{ i18n "pages.inbounds.totalFlow" }}</th> <th>{{ i18n "pages.inbounds.totalFlow" }}</th>
@ -142,139 +209,161 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> <a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[ getRemStats() ]] </a-tag>
[[ getRemStats() ]]
</a-tag>
</td> </td>
<td> <td>
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> <a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[ sizeFormat(infoModal.clientSettings.totalGB) ]] </a-tag>
[[ sizeFormat(infoModal.clientSettings.totalGB) ]] <a-tag v-else color="purple" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg>
</a-tag> </a-tag>
<a-tag v-else color="purple" class="infinite-tag">&infin;</a-tag>
</td> </td>
<td> <td>
<template v-if="infoModal.clientSettings.expiryTime > 0"> <template v-if="infoModal.clientSettings.expiryTime > 0">
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)"> <a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)"> [[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]] </a-tag>
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
</a-tag>
</template> </template>
<a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="green">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }}</a-tag> <a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="green">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }}
<a-tag v-else color="purple" class="infinite-tag">&infin;</a-tag> </a-tag>
<a-tag v-else color="purple" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg>
</a-tag>
</td> </td>
</tr> </tr>
</table> </table>
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId"> <template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
<a-divider>Subscription URL</a-divider> <a-divider>Subscription URL</a-divider>
<a-row> <tr-info-row class="tr-info-row">
<a-col :sx="24" :md="22">SUB: <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col> <tr-info-title class="tr-info-title">
<a-col :sx="24" :md="2" style="text-align: right;"> <a-tag color="purple">Subscription Link</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)"> <a-button size="small" icon="snippets" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)"></a-button>
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip> </a-tooltip>
</a-col> </tr-info-title>
</a-row> <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a>
<a-row> </tr-info-row>
<a-col :sx="24" :md="22">JSON: <a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a></a-col> <tr-info-row class="tr-info-row">
<a-col :sx="24" :md="2" style="text-align: right; margin-top: 5px;"> <tr-info-title class="tr-info-title">
<a-tag color="purple">Json Link</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-subJson-link" @click="copyToClipboard('copy-subJson-link', infoModal.subJsonLink)"> <a-button size="small" icon="snippets" id="copy-subJson-link" @click="copyToClipboard('copy-subJson-link', infoModal.subJsonLink)"></a-button>
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip> </a-tooltip>
</a-col> </tr-info-title>
</a-row> <a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a>
</tr-info-row>
</template> </template>
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId"> <template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
<a-divider>Telegram ID</a-divider> <a-divider>Telegram ID</a-divider>
<a-row> <tr-info-row class="tr-info-row">
<a-col :sx="24" :md="22">[[ infoModal.clientSettings.tgId ]]</a-col> <tr-info-title class="tr-info-title">
<a-col :sx="24" :md="2" style="text-align: right;"> <a-tag color="blue">[[ infoModal.clientSettings.tgId ]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', infoModal.clientSettings.tgId)"> <a-button size="small" icon="snippets" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', infoModal.clientSettings.tgId)"></a-button>
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip> </a-tooltip>
</a-col> </tr-info-title>
</a-row> </tr-info-row>
</template> </template>
<template v-if="dbInbound.hasLink()"> <template v-if="dbInbound.hasLink()">
<a-divider>URL</a-divider> <a-divider>URL</a-divider>
<a-row v-for="(link,index) in infoModal.links"> <tr-info-row v-for="(link,index) in infoModal.links" class="tr-info-row">
<a-col :sx="24" :md="22"><a-tag color="green">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col> <tr-info-title class="tr-info-title">
<a-col :sx="24" :md="2" style="text-align: right;"> <a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)"> <a-button style="min-width: 24px;" size="small" icon="snippets" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)"></a-button>
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip> </a-tooltip>
</a-col> </tr-info-title>
</a-row> <code>[[ link.link ]]</code>
</tr-info-row>
</template> </template>
</template> </template>
<template v-else> <template v-else>
<template v-if="dbInbound.isSS && !inbound.isSSMultiUser"> <template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
<a-divider>URL</a-divider> <a-divider>URL</a-divider>
<a-row v-for="(link,index) in infoModal.links"> <tr-info-row v-for="(link,index) in infoModal.links" class="tr-info-row">
<a-col :span="22"><a-tag color="green">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col> <tr-info-title class="tr-info-title">
<a-col :span="2" style="text-align: right;"> <a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)"> <a-button style="min-width: 24px;" size="small" icon="snippets" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)"></a-button>
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip> </a-tooltip>
</a-col> </tr-info-title>
</a-row> <code>[[ link.link ]]</code>
</tr-info-row>
</template> </template>
<table v-if="inbound.protocol == Protocols.DOKODEMO" style="margin-bottom: 10px; width: 100%;"> <table v-if="inbound.protocol == Protocols.DOKODEMO" class="tr-info-table">
<tr> <tr>
<th>{{ i18n "pages.inbounds.targetAddress" }}</th> <th>{{ i18n "pages.inbounds.targetAddress" }}</th>
<th>{{ i18n "pages.inbounds.destinationPort" }}</th> <th>{{ i18n "pages.inbounds.destinationPort" }}</th>
<th>{{ i18n "pages.inbounds.network" }}</th> <th>{{ i18n "pages.inbounds.network" }}</th>
<th>FollowRedirect</th> <th>FollowRedirect</th>
</tr><tr> </tr>
<td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td> <tr>
<td><a-tag color="green">[[ inbound.settings.port ]]</a-tag></td> <td>
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td> <a-tag color="green">[[ inbound.settings.address ]]</a-tag>
<td><a-tag color="green">[[ inbound.settings.followRedirect ]]</a-tag></td> </td>
<td>
<a-tag color="green">[[ inbound.settings.port ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.network ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.followRedirect ]]</a-tag>
</td>
</tr> </tr>
</table> </table>
<table v-if="dbInbound.isSocks" style="margin-bottom: 10px; width: 100%;"> <table v-if="dbInbound.isSocks" class="tr-info-table">
<tr> <tr>
<th>{{ i18n "password" }} Auth</th> <th>{{ i18n "password" }} Auth</th>
<th>{{ i18n "pages.inbounds.enable" }} udp</th> <th>{{ i18n "pages.inbounds.enable" }} udp</th>
<th>IP</th> <th>IP</th>
</tr> </tr>
<tr> <tr>
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td> <td>
<td><a-tag color="green">[[ inbound.settings.udp]]</a-tag></td> <a-tag color="green">[[ inbound.settings.auth ]]</a-tag>
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td> </td>
<td>
<a-tag color="green">[[ inbound.settings.udp]]</a-tag>
</td>
<td>
<a-tag color="green">[[ inbound.settings.ip ]]</a-tag>
</td>
</tr> </tr>
<template v-if="inbound.settings.auth == 'password'"> <template v-if="inbound.settings.auth == 'password'">
<tr> <tr>
<td> </td> <td></td>
<td>{{ i18n "username" }}</td> <td>{{ i18n "username" }}</td>
<td>{{ i18n "password" }}</td> <td>{{ i18n "password" }}</td>
</tr><tr v-for="account,index in inbound.settings.accounts"> </tr>
<tr v-for="account,index in inbound.settings.accounts">
<td>[[ index ]]</td> <td>[[ index ]]</td>
<td><a-tag color="green">[[ account.user ]]</a-tag></td> <td>
<td><a-tag color="green">[[ account.pass ]]</a-tag></td> <a-tag color="green">[[ account.user ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ account.pass ]]</a-tag>
</td>
</tr> </tr>
</template> </template>
</table> </table>
<table v-if="dbInbound.isHTTP" style="margin-bottom: 10px; width: 100%;"> <table v-if="dbInbound.isHTTP" class="tr-info-table">
<tr> <tr>
<th> </th> <th></th>
<th>{{ i18n "username" }}</th> <th>{{ i18n "username" }}</th>
<th>{{ i18n "password" }}</th> <th>{{ i18n "password" }}</th>
</tr><tr v-for="account,index in inbound.settings.accounts"> </tr>
<tr v-for="account,index in inbound.settings.accounts">
<td>[[ index ]]</td> <td>[[ index ]]</td>
<td><a-tag color="green">[[ account.user ]]</a-tag></td> <td>
<td><a-tag color="green">[[ account.pass ]]</a-tag></td> <a-tag color="green">[[ account.user ]]</a-tag>
</td>
<td>
<a-tag color="green">[[ account.pass ]]</a-tag>
</td>
</tr> </tr>
</table> </table>
<table v-if="dbInbound.isWireguard" style="margin-bottom: 10px; width: 100%;"> <table v-if="dbInbound.isWireguard" class="tr-info-table">
<tr class="client-table-odd-row"> <tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td> <td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
<td>[[ inbound.settings.secretKey ]]</td> <td>[[ inbound.settings.secretKey ]]</td>
@ -293,7 +382,9 @@
</tr> </tr>
<template v-for="(peer, index) in inbound.settings.peers"> <template v-for="(peer, index) in inbound.settings.peers">
<tr> <tr>
<td colspan="2"><a-divider>Peer [[ index + 1 ]]</a-divider></td> <td colspan="2">
<a-divider>Peer [[ index + 1 ]]</a-divider>
</td>
</tr> </tr>
<tr class="client-table-odd-row"> <tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td> <td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
@ -317,24 +408,16 @@
</tr> </tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<a-row> <tr-info-row v-for="(link,index) in infoModal.links" class="tr-info-row">
<a-col :span="22" style="overflow-wrap: anywhere;"> <tr-info-title class="tr-info-title">
<a-tag color="blue">Config</a-tag> <a-tag color="blue">Config</a-tag>
<div
v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)"
style="border-radius: 1rem; padding: 0.5rem;"
class="client-table-odd-row"></div>
</a-col>
<a-col :span="2" style="text-align: right;">
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" <a-button style="min-width: 24px;" size="small" icon="snippets" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, infoModal.links[index])"></a-button>
:id="'copy-url-link-'+index"
@click="copyToClipboard('copy-url-link-'+index, infoModal.links[index])">
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip> </a-tooltip>
</a-col> </tr-info-title>
</a-row> <div v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)" style="border-radius: 1rem; padding: 0.5rem;" class="client-table-odd-row">
</div>
</tr-info-row>
</td> </td>
</tr> </tr>
</table> </table>
@ -361,9 +444,9 @@
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
this.dbInbound = new DBInbound(dbInbound); this.dbInbound = new DBInbound(dbInbound);
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null; this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry; this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index) : this.dbInbound.isExpiry;
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : []; this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
if (this.inbound.protocol == Protocols.WIREGUARD){ if (this.inbound.protocol == Protocols.WIREGUARD) {
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n') this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
} else { } else {
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings); this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
@ -380,13 +463,12 @@
infoModal.visible = false; infoModal.visible = false;
}, },
genSubLink(subID) { genSubLink(subID) {
return app.subSettings.subURI+subID; return app.subSettings.subURI + subID;
}, },
genSubJsonLink(subID) { genSubJsonLink(subID) {
return app.subSettings.subJsonURI+subID; return app.subSettings.subJsonURI + subID;
} }
}; };
const infoModalApp = new Vue({ const infoModalApp = new Vue({
delimiters: ['[[', ']]'], delimiters: ['[[', ']]'],
el: '#inbound-info-modal', el: '#inbound-info-modal',
@ -399,20 +481,20 @@
return this.infoModal.inbound; return this.infoModal.inbound;
}, },
get isActive() { get isActive() {
if(infoModal.clientStats){ if (infoModal.clientStats) {
return infoModal.clientStats.enable; return infoModal.clientStats.enable;
} }
return true; return true;
}, },
get isEnable() { get isEnable() {
if(infoModal.clientSettings){ if (infoModal.clientSettings) {
return infoModal.clientSettings.enable; return infoModal.clientSettings.enable;
} }
return infoModal.dbInbound.isEnable; return infoModal.dbInbound.isEnable;
}, },
}, },
methods: { methods: {
copyToClipboard(elmentId,content) { copyToClipboard(elmentId, content) {
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, { this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
text: () => content, text: () => content,
}); });
@ -426,11 +508,9 @@
}, },
getRemStats() { getRemStats() {
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down; remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down;
return remained>0 ? sizeFormat(remained) : '-'; return remained > 0 ? sizeFormat(remained) : '-';
}, },
}, },
}); });
</script> </script>
{{end}} {{end}}

View file

@ -2,6 +2,34 @@
<html lang="en"> <html lang="en">
{{template "head" .}} {{template "head" .}}
<style> <style>
.ant-table:not(.ant-table-expanded-row .ant-table) {
outline: 1px solid #f0f0f0;
outline-offset: -1px;
border-radius: 1rem;
overflow-x: hidden;
}
.dark .ant-table:not(.ant-table-expanded-row .ant-table) {
outline-color: var(--dark-color-table-ring);
}
.ant-table .ant-table-content .ant-table-scroll .ant-table-body {
overflow-y: hidden;
}
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper {
margin:-10px 22px -10px !important;
}
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper .ant-table {
border-bottom-left-radius: 1rem;
border-bottom-right-radius: 1rem;
}
.ant-table .ant-table-content .ant-table-tbody tr:last-child tr:last-child td {
border-bottom-color: transparent;
}
.ant-table .ant-table-tbody tr:last-child.ant-table-expanded-row .ant-table-wrapper .ant-table-tbody>tr:last-child>td:first-child {
border-bottom-left-radius: 6px;
}
.ant-table .ant-table-tbody tr:last-child.ant-table-expanded-row .ant-table-wrapper .ant-table-tbody>tr:last-child>td:last-child {
border-bottom-right-radius: 6px;
}
@media (min-width: 769px) { @media (min-width: 769px) {
.ant-layout-content { .ant-layout-content {
margin: 24px 16px; margin: 24px 16px;
@ -11,6 +39,9 @@
.ant-card-body { .ant-card-body {
padding: .5rem; padding: .5rem;
} }
.ant-table .ant-table-content .ant-table-tbody tr:last-child .ant-table-wrapper {
margin:-10px 2px -10px !important;
}
} }
.ant-col-sm-24 { .ant-col-sm-24 {
margin: 0.5rem -2rem 0.5rem 2rem; margin: 0.5rem -2rem 0.5rem 2rem;
@ -22,13 +53,14 @@
padding: 0 5px; padding: 0 5px;
border-radius: 2rem; border-radius: 2rem;
min-width: 50px; min-width: 50px;
min-height: 22px;
} }
.infinite-bar .ant-progress-inner .ant-progress-bg { .infinite-bar .ant-progress-inner .ant-progress-bg {
background-color: #F2EAF1; background-color: #F2EAF1;
border: #D5BED2 solid 1px; border: #D5BED2 solid 1px;
} }
.dark .infinite-bar .ant-progress-inner .ant-progress-bg { .dark .infinite-bar .ant-progress-inner .ant-progress-bg {
background-color: #7a316f; background-color: #7a316f !important;
border: #7a316f solid 1px; border: #7a316f solid 1px;
} }
.ant-collapse { .ant-collapse {
@ -53,6 +85,41 @@
opacity: .2; opacity: .2;
} }
} }
.tr-table-box {
display: flex;
gap: 4px;
justify-content: center;
align-items: center;
}
.tr-table-rt {
flex-basis: 70px;
min-width: 70px;
text-align: end;
}
.tr-table-lt {
flex-basis: 70px;
min-width: 70px;
text-align: start;
}
.tr-table-bar {
flex-basis: 160px;
min-width: 60px;
}
.tr-infinity-ch {
font-size: 14pt;
max-height: 24px;
display: inline-flex;
align-items: center;
}
.ant-table-expanded-row .ant-table .ant-table-body {
overflow-x: hidden;
}
.ant-table-expanded-row .ant-table-tbody>tr>td {
padding: 10px 2px;
}
.ant-table-expanded-row .ant-table-thead>tr>th {
padding: 12px 2px;
}
</style> </style>
<body> <body>
@ -324,8 +391,8 @@
[[ sizeFormat(dbInbound.total) ]] [[ sizeFormat(dbInbound.total) ]]
</template> </template>
<template v-else> <template v-else>
<svg style="fill: currentColor; height: 10px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" /> <path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg> </svg>
</template> </template>
</a-tag> </a-tag>
@ -343,7 +410,11 @@
[[ remainedDays(dbInbound._expiryTime) ]] [[ remainedDays(dbInbound._expiryTime) ]]
</a-tag> </a-tag>
</a-popover> </a-popover>
<a-tag v-else color="purple" class="infinite-tag">&infin;</a-tag> <a-tag v-else color="purple" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg>
</a-tag>
</template> </template>
<template slot="info" slot-scope="text, dbInbound"> <template slot="info" slot-scope="text, dbInbound">
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click"> <a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
@ -415,7 +486,11 @@
<template v-if="dbInbound.total > 0"> <template v-if="dbInbound.total > 0">
[[ sizeFormat(dbInbound.total) ]] [[ sizeFormat(dbInbound.total) ]]
</template> </template>
<template v-else>&infin;</template> <template v-else>
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg>
</template>
</a-tag> </a-tag>
</a-popover> </a-popover>
</td> </td>
@ -426,7 +501,11 @@
<a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0" :color="dbInbound.isExpiry? 'red': 'blue'"> <a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0" :color="dbInbound.isExpiry? 'red': 'blue'">
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]] [[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
</a-tag> </a-tag>
<a-tag v-else style="text-align: center;" color="purple" class="infinite-tag">&infin;</a-tag> <a-tag v-else style="text-align: center;" color="purple" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg>
</a-tag>
</td> </td>
</tr> </tr>
</table> </table>
@ -445,7 +524,7 @@
:columns="isMobile ? innerMobileColumns : innerColumns" :columns="isMobile ? innerMobileColumns : innerColumns"
:data-source="getInboundClients(record)" :data-source="getInboundClients(record)"
:pagination=pagination(getInboundClients(record)) :pagination=pagination(getInboundClients(record))
:style="isMobile ? 'margin: -12px 2px -13px;' : 'margin: -12px 22px -13px;'"> :style="isMobile ? 'margin: -10px 2px -11px;' : 'margin: -10px 22px -11px;'">
{{template "client_table"}} {{template "client_table"}}
</a-table> </a-table>
</template> </template>
@ -543,7 +622,7 @@
{ title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } }, { title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } },
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } }, { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } }, { title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 100, align: 'center', scopedSlots: { customRender: 'expiryTime' } }, { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
]; ];
const innerMobileColumns = [ const innerMobileColumns = [

View file

@ -9,6 +9,9 @@
.ant-card-hoverable { .ant-card-hoverable {
margin-inline: 0.3rem; margin-inline: 0.3rem;
} }
.ant-alert-error {
margin-inline: 0.3rem;
}
} }
.ant-col-sm-24 { .ant-col-sm-24 {
margin-top: 10px; margin-top: 10px;

View file

@ -1,14 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
{{template "head" .}} {{template "head" .}}
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css?{{ .cur_ver }}"> <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.min.css?{{ .cur_ver }}">
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css"> <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css?{{ .cur_ver }}"> <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.min.css?{{ .cur_ver }}">
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css"> <link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
<script src="{{ .base_path }}assets/base64/base64.min.js"></script> <script src="{{ .base_path }}assets/base64/base64.min.js"></script>
<script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/codemirror/codemirror.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/codemirror/codemirror.min.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script> <script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script> <script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
<script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script> <script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
@ -29,8 +29,8 @@
margin: 0; margin: 0;
padding: 12px .5rem; padding: 12px .5rem;
} }
.ant-table-thead > tr > th, .ant-table-thead>tr>th,
.ant-table-tbody > tr > td { .ant-table-tbody>tr>td {
padding: 10px 0px; padding: 10px 0px;
} }
} }
@ -47,14 +47,14 @@
padding: 10px 20px; padding: 10px 20px;
border-bottom: 2px solid; border-bottom: 2px solid;
} }
.collapse-title > i { .collapse-title>i {
color: inherit; color: inherit;
font-size: 24px; font-size: 24px;
} }
.ant-collapse-content-box > li { .ant-collapse-content-box>li {
padding: 12px 0 0 0 !important; padding: 12px 0 0 0 !important;
} }
.ant-list-item > li { .ant-list-item>li {
padding: 10px 20px !important; padding: 10px 20px !important;
} }
</style> </style>
@ -443,7 +443,7 @@
<a-table :columns="outboundColumns" bordered <a-table :columns="outboundColumns" bordered
:row-key="r => r.key" :row-key="r => r.key"
:data-source="outboundData" :data-source="outboundData"
:scroll="isMobile ? {} : { x: 200 }" :scroll="isMobile ? {} : { x: 800 }"
:pagination="false" :pagination="false"
:indent-size="0" :indent-size="0"
:style="isMobile ? 'padding: 5px 5px' : 'margin-right: 1px;'"> :style="isMobile ? 'padding: 5px 5px' : 'margin-right: 1px;'">

View file

@ -1,7 +1,5 @@
{{define "ruleModal"}} {{define "ruleModal"}}
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok" <a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok" :confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false" :ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='Domain Matcher'> <a-form-item label='Domain Matcher'>
<a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme">
@ -13,8 +11,7 @@
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> </template> Source IPs <a-icon type="question-circle"></a-icon>
Source IPs <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="ruleModal.rule.source"></a-input> <a-input v-model.trim="ruleModal.rule.source"></a-input>
@ -24,8 +21,7 @@
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> </template> Source Port <a-icon type="question-circle"></a-icon>
Source Port <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="ruleModal.rule.sourcePort"></a-input> <a-input v-model.trim="ruleModal.rule.sourcePort"></a-input>
@ -41,7 +37,7 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Attributes'> <a-form-item label='Attributes'>
<a-button size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])">+</a-button> <a-button icon="plus" size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])"></a-button>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span: 24}"> <a-form-item :wrapper-col="{span: 24}">
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs"> <a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
@ -49,7 +45,7 @@
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input> </a-input>
<a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)">-</a-button> <a-button icon="minus" slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)"></a-button>
</a-input> </a-input>
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
@ -58,8 +54,7 @@
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> </template> IP <a-icon type="question-circle"></a-icon>
IP <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="ruleModal.rule.ip"></a-input> <a-input v-model.trim="ruleModal.rule.ip"></a-input>
@ -69,8 +64,7 @@
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> </template> Domain <a-icon type="question-circle"></a-icon>
Domain <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="ruleModal.rule.domain"></a-input> <a-input v-model.trim="ruleModal.rule.domain"></a-input>
@ -80,8 +74,7 @@
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> </template> User <a-icon type="question-circle"></a-icon>
User <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="ruleModal.rule.user"></a-input> <a-input v-model.trim="ruleModal.rule.user"></a-input>
@ -91,8 +84,7 @@
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> </template> Port <a-icon type="question-circle"></a-icon>
Port <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input v-model.trim="ruleModal.rule.port"></a-input> <a-input v-model.trim="ruleModal.rule.port"></a-input>
@ -112,19 +104,16 @@
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.balancer.balancerDesc" }}</span> <span>{{ i18n "pages.xray.balancer.balancerDesc" }}</span>
</template> </template> Balancer Tag <a-icon type="question-circle"></a-icon>
Balancer Tag <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-select v-model="ruleModal.rule.balancerTag" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="ruleModal.rule.balancerTag" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ruleModal.balancerTags" :value="tag">[[ tag ]]</a-select-option> <a-select-option v-for="tag in ruleModal.balancerTags" :value="tag">[[ tag ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</table>
</a-form> </a-form>
</a-modal> </a-modal>
<script> <script>
const ruleModal = { const ruleModal = {
title: '', title: '',
visible: false, visible: false,
@ -156,12 +145,18 @@
newRule = ruleModal.getResult(); newRule = ruleModal.getResult();
ObjectUtil.execute(ruleModal.confirm, newRule); ObjectUtil.execute(ruleModal.confirm, newRule);
}, },
show({ title='', okText='{{ i18n "sure" }}', rule, confirm=(rule)=>{}, isEdit=false }) { show({
title = '',
okText = '{{ i18n "sure" }}',
rule,
confirm = (rule) => {},
isEdit = false
}) {
this.title = title; this.title = title;
this.okText = okText; this.okText = okText;
this.confirm = confirm; this.confirm = confirm;
this.visible = true; this.visible = true;
if(isEdit) { if (isEdit) {
this.rule.domainMatcher = rule.domainMatcher; this.rule.domainMatcher = rule.domainMatcher;
this.rule.domain = rule.domain ? rule.domain.join(',') : []; this.rule.domain = rule.domain ? rule.domain.join(',') : [];
this.rule.ip = rule.ip ? rule.ip.join(',') : []; this.rule.ip = rule.ip ? rule.ip.join(',') : [];
@ -197,22 +192,21 @@
this.inboundTags.push(...app.inboundTags); this.inboundTags.push(...app.inboundTags);
if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag) if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag)
this.outboundTags = ["", ...app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)]; this.outboundTags = ["", ...app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
if(app.templateSettings.reverse){ if (app.templateSettings.reverse) {
if(app.templateSettings.reverse.bridges) { if (app.templateSettings.reverse.bridges) {
this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag)); this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag));
} }
if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag)); if (app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag));
} }
if (app.templateSettings.routing && app.templateSettings.routing.balancers) { if (app.templateSettings.routing && app.templateSettings.routing.balancers) {
this.balancerTags = [ "", ...app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)]; this.balancerTags = ["", ...app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
} }
}, },
close() { close() {
ruleModal.visible = false; ruleModal.visible = false;
ruleModal.loading(false); ruleModal.loading(false);
}, },
loading(loading=true) { loading(loading = true) {
ruleModal.confirmLoading = loading; ruleModal.confirmLoading = loading;
}, },
getResult() { getResult() {
@ -221,34 +215,26 @@
newRule = {}; newRule = {};
rule.type = "field"; rule.type = "field";
rule.domainMatcher = value.domainMatcher; rule.domainMatcher = value.domainMatcher;
rule.domain = value.domain.length>0 ? value.domain.split(',') : []; rule.domain = value.domain.length > 0 ? value.domain.split(',') : [];
rule.ip = value.ip.length>0 ? value.ip.split(',') : []; rule.ip = value.ip.length > 0 ? value.ip.split(',') : [];
rule.port = value.port; rule.port = value.port;
rule.sourcePort = value.sourcePort; rule.sourcePort = value.sourcePort;
rule.network = value.network; rule.network = value.network;
rule.source = value.source.length>0 ? value.source.split(',') : []; rule.source = value.source.length > 0 ? value.source.split(',') : [];
rule.user = value.user.length>0 ? value.user.split(',') : []; rule.user = value.user.length > 0 ? value.user.split(',') : [];
rule.inboundTag = value.inboundTag; rule.inboundTag = value.inboundTag;
rule.protocol = value.protocol; rule.protocol = value.protocol;
rule.attrs = Object.fromEntries(value.attrs); rule.attrs = Object.fromEntries(value.attrs);
rule.outboundTag = value.outboundTag == "" ? undefined : value.outboundTag; rule.outboundTag = value.outboundTag == "" ? undefined : value.outboundTag;
rule.balancerTag = value.balancerTag == "" ? undefined : value.balancerTag; rule.balancerTag = value.balancerTag == "" ? undefined : value.balancerTag;
for (const [key, value] of Object.entries(rule)) { for (const [key, value] of Object.entries(rule)) {
if ( if (value !== null && value !== undefined && !(Array.isArray(value) && value.length === 0) && !(typeof value === 'object' && Object.keys(value).length === 0) && value !== '') {
value !== null &&
value !== undefined &&
!(Array.isArray(value) && value.length === 0) &&
!(typeof value === 'object' && Object.keys(value).length === 0) &&
value !== ''
) {
newRule[key] = value; newRule[key] = value;
} }
} }
return newRule; return newRule;
} }
}; };
new Vue({ new Vue({
delimiters: ['[[', ']]'], delimiters: ['[[', ']]'],
el: '#rule-modal', el: '#rule-modal',
@ -256,6 +242,5 @@
ruleModal: ruleModal, ruleModal: ruleModal,
} }
}); });
</script> </script>
{{end}} {{end}}

View file

@ -36,7 +36,7 @@
"status" = "Status" "status" = "Status"
"enabled" = "Enabled" "enabled" = "Enabled"
"disabled" = "Disabled" "disabled" = "Disabled"
"depleted" = "Depleted" "depleted" = "Ended"
"depletingSoon" = "Depleting" "depletingSoon" = "Depleting"
"offline" = "Offline" "offline" = "Offline"
"online" = "Online" "online" = "Online"