inbounds.html 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. {{template "head" .}}
  4. <style>
  5. @media (min-width: 769px) {
  6. .ant-layout-content {
  7. margin: 24px 16px;
  8. }
  9. }
  10. .ant-col-sm-24 {
  11. margin-top: 10px;
  12. }
  13. </style>
  14. <body>
  15. <a-layout id="app" v-cloak>
  16. {{ template "commonSider" . }}
  17. <a-layout id="content-layout">
  18. <a-layout-content>
  19. <a-spin :spinning="spinning" :delay="500" tip="loading">
  20. <transition name="list" appear>
  21. <a-tag v-if="false" color="red" style="margin-bottom: 10px">
  22. Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
  23. </a-tag>
  24. </transition>
  25. <transition name="list" appear>
  26. <a-card hoverable style="margin-bottom: 20px;">
  27. <a-row>
  28. <a-col :xs="24" :sm="24" :lg="12">
  29. 总上传 / 下载:
  30. <a-tag color="green">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag>
  31. </a-col>
  32. <a-col :xs="24" :sm="24" :lg="12">
  33. 总用量:
  34. <a-tag color="green">[[ sizeFormat(total.up + total.down) ]]</a-tag>
  35. </a-col>
  36. <a-col :xs="24" :sm="24" :lg="12">
  37. 入站数量:
  38. <a-tag color="green">[[ dbInbounds.length ]]</a-tag>
  39. </a-col>
  40. </a-row>
  41. </a-card>
  42. </transition>
  43. <transition name="list" appear>
  44. <a-card hoverable>
  45. <div slot="title">
  46. <a-button type="primary" icon="plus" @click="openAddInbound"></a-button>
  47. </div>
  48. <a-input v-model="searchKey" placeholder="search" autofocus style="max-width: 300px"></a-input>
  49. <a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
  50. :data-source="dbInbounds"
  51. :loading="spinning" :scroll="{ x: 1500 }"
  52. :pagination="false"
  53. style="margin-top: 20px"
  54. @change="() => getDBInbounds()">
  55. <template slot="protocol" slot-scope="text, dbInbound">
  56. <a-tag color="blue">[[ dbInbound.protocol ]]</a-tag>
  57. </template>
  58. <template slot="traffic" slot-scope="text, dbInbound">
  59. <a-tag color="blue">[[ sizeFormat(dbInbound.up) ]]</a-tag>
  60. <a-tag color="green">[[ sizeFormat(dbInbound.down) ]]</a-tag>
  61. <a-tag v-if="dbInbound.total > 0" color="cyan">[[ sizeFormat(dbInbound.total) ]]</a-tag>
  62. <a-tag v-else color="cyan">无限制</a-tag>
  63. </template>
  64. <template slot="settings" slot-scope="text, dbInbound">
  65. <a-button type="link" @click="showInfo(dbInbound)">查看</a-button>
  66. </template>
  67. <template slot="stream" slot-scope="text, dbInbound, index">
  68. <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
  69. <a-tag color="green">[[ inbounds[index].stream.network ]]</a-tag>
  70. <a-tag v-if="inbounds[index].stream.isTls" color="blue">tls</a-tag>
  71. <a-tag v-if="inbounds[index].stream.isXTls" color="blue">xtls</a-tag>
  72. </template>
  73. <template v-else>无</template>
  74. </template>
  75. <template slot="enable" slot-scope="text, dbInbound">
  76. <a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound)"></a-switch>
  77. </template>
  78. <template slot="expiryTime" slot-scope="text, dbInbound">
  79. <span v-if="dbInbound.expiryTime > 0" color="red">[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]</span>
  80. <span v-else>无限期</span>
  81. </template>
  82. <template slot="action" slot-scope="text, dbInbound">
  83. <a-button v-if="dbInbound.hasLink()" type="primary" icon="qrcode" @click="showQrcode(dbInbound)"></a-button>
  84. <a-button type="primary" icon="edit" @click="openEditInbound(dbInbound)"></a-button>
  85. <a-button icon="retweet" @click="resetTraffic(dbInbound)"></a-button>
  86. <a-button type="danger" icon="delete" @click="delInbound(dbInbound)"></a-button>
  87. </template>
  88. </a-table>
  89. </a-card>
  90. </transition>
  91. </a-spin>
  92. </a-layout-content>
  93. </a-layout>
  94. </a-layout>
  95. {{template "js" .}}
  96. <script>
  97. const columns = [{
  98. title: "id",
  99. align: 'center',
  100. dataIndex: "id",
  101. width: 60,
  102. }, {
  103. title: "协议",
  104. align: 'center',
  105. width: 60,
  106. scopedSlots: { customRender: 'protocol' },
  107. }, {
  108. title: "端口",
  109. align: 'center',
  110. dataIndex: "port",
  111. width: 60,
  112. }, {
  113. title: "流量↑|↓",
  114. align: 'center',
  115. width: 80,
  116. scopedSlots: { customRender: 'traffic' },
  117. }, {
  118. title: "详细信息",
  119. align: 'center',
  120. width: 60,
  121. scopedSlots: { customRender: 'settings' },
  122. }, {
  123. title: "传输配置",
  124. align: 'center',
  125. width: 60,
  126. scopedSlots: { customRender: 'stream' },
  127. }, {
  128. title: "启用",
  129. align: 'center',
  130. width: 60,
  131. scopedSlots: { customRender: 'enable' },
  132. }, {
  133. title: "操作",
  134. align: 'center',
  135. width: 60,
  136. scopedSlots: { customRender: 'action' },
  137. }];
  138. const app = new Vue({
  139. delimiters: ['[[', ']]'],
  140. el: '#app',
  141. data: {
  142. siderDrawer,
  143. spinning: false,
  144. inbounds: [],
  145. dbInbounds: [],
  146. searchKey: '',
  147. },
  148. methods: {
  149. loading(spinning=true) {
  150. this.spinning = spinning;
  151. },
  152. async getDBInbounds() {
  153. this.loading();
  154. const msg = await HttpUtil.post('/xui/inbound/list');
  155. this.loading(false);
  156. if (!msg.success) {
  157. return;
  158. }
  159. this.setInbounds(msg.obj);
  160. },
  161. setInbounds(dbInbounds) {
  162. this.inbounds.splice(0);
  163. this.dbInbounds.splice(0);
  164. for (const inbound of dbInbounds) {
  165. const dbInbound = new DBInbound(inbound);
  166. this.inbounds.push(dbInbound.toInbound());
  167. this.dbInbounds.push(dbInbound);
  168. }
  169. },
  170. searchInbounds(key) {
  171. if (ObjectUtil.isEmpty(key)) {
  172. this.searchedInbounds = this.dbInbounds.slice();
  173. } else {
  174. this.searchedInbounds.splice(0, this.searchedInbounds.length);
  175. this.dbInbounds.forEach(inbound => {
  176. if (ObjectUtil.deepSearch(inbound, key)) {
  177. this.searchedInbounds.push(inbound);
  178. }
  179. });
  180. }
  181. },
  182. openAddInbound() {
  183. inModal.show({
  184. title: '添加入站',
  185. okText: '添加',
  186. confirm: async (inbound, dbInbound) => {
  187. inModal.loading();
  188. await this.addInbound(inbound, dbInbound);
  189. inModal.close();
  190. }
  191. });
  192. },
  193. openEditInbound(dbInbound) {
  194. const inbound = dbInbound.toInbound();
  195. inModal.show({
  196. title: '修改入站',
  197. okText: '修改',
  198. inbound: inbound,
  199. dbInbound: dbInbound,
  200. confirm: async (inbound, dbInbound) => {
  201. inModal.loading();
  202. await this.updateInbound(inbound, dbInbound);
  203. inModal.close();
  204. }
  205. });
  206. },
  207. async addInbound(inbound, dbInbound) {
  208. const data = {
  209. up: dbInbound.up,
  210. down: dbInbound.down,
  211. total: dbInbound.total,
  212. remark: dbInbound.remark,
  213. enable: dbInbound.enable,
  214. listen: inbound.listen,
  215. port: inbound.port,
  216. protocol: inbound.protocol,
  217. settings: inbound.settings.toString(),
  218. streamSettings: inbound.stream.toString(),
  219. sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}',
  220. };
  221. await this.submit('/xui/inbound/add', data, inModal);
  222. },
  223. async updateInbound(inbound, dbInbound) {
  224. const data = {
  225. up: dbInbound.up,
  226. down: dbInbound.down,
  227. total: dbInbound.total,
  228. remark: dbInbound.remark,
  229. enable: dbInbound.enable,
  230. listen: inbound.listen,
  231. port: inbound.port,
  232. protocol: inbound.protocol,
  233. settings: inbound.settings.toString(),
  234. streamSettings: inbound.stream.toString(),
  235. sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}',
  236. };
  237. await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
  238. },
  239. resetTraffic(dbInbound) {
  240. this.$confirm({
  241. title: '重置流量',
  242. content: '确定要重置流量吗?',
  243. okText: '重置',
  244. cancelText: '取消',
  245. onOk: () => {
  246. const inbound = dbInbound.toInbound();
  247. dbInbound.up = 0;
  248. dbInbound.down = 0;
  249. this.updateInbound(inbound, dbInbound);
  250. },
  251. });
  252. },
  253. delInbound(dbInbound) {
  254. this.$confirm({
  255. title: '删除入站',
  256. content: '确定要删除入站吗?',
  257. okText: '删除',
  258. cancelText: '取消',
  259. onOk: () => this.submit('/xui/inbound/del/' + dbInbound.id),
  260. });
  261. },
  262. showQrcode(dbInbound) {
  263. const link = dbInbound.genLink();
  264. qrModal.show('二维码', link);
  265. },
  266. showInfo(dbInbound) {
  267. infoModal.show(dbInbound);
  268. },
  269. switchEnable(dbInbound) {
  270. this.submit(`/xui/inbound/update/${dbInbound.id}`, dbInbound);
  271. },
  272. async submit(url, data, modal) {
  273. const msg = await HttpUtil.postWithModal(url, data, modal);
  274. if (msg.success) {
  275. await this.getDBInbounds();
  276. }
  277. },
  278. },
  279. watch: {
  280. searchKey(value) {
  281. this.searchInbounds(value);
  282. }
  283. },
  284. mounted() {
  285. this.getDBInbounds();
  286. },
  287. computed: {
  288. total() {
  289. let down = 0, up = 0;
  290. for (let i = 0; i < this.dbInbounds.length; ++i) {
  291. down += this.dbInbounds[i].down;
  292. up += this.dbInbounds[i].up;
  293. }
  294. return {
  295. down: down,
  296. up: up,
  297. };
  298. }
  299. },
  300. });
  301. </script>
  302. {{template "inboundModal"}}
  303. {{template "promptModal"}}
  304. {{template "qrcodeModal"}}
  305. {{template "textModal"}}
  306. {{template "inboundInfoModal"}}
  307. </body>
  308. </html>