inbounds.html 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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. <template v-if="dbInbound.total > 0">
  62. <a-tag v-if="dbInbound.up + dbInbound.down < dbInbound.total" color="cyan">[[ sizeFormat(dbInbound.total) ]]</a-tag>
  63. <a-tag v-else color="red">[[ sizeFormat(dbInbound.total) ]]</a-tag>
  64. </template>
  65. <a-tag v-else color="cyan">无限制</a-tag>
  66. </template>
  67. <template slot="settings" slot-scope="text, dbInbound">
  68. <a-button type="link" @click="showInfo(dbInbound)">查看</a-button>
  69. </template>
  70. <template slot="stream" slot-scope="text, dbInbound, index">
  71. <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
  72. <a-tag color="green">[[ inbounds[index].stream.network ]]</a-tag>
  73. <a-tag v-if="inbounds[index].stream.isTls" color="blue">tls</a-tag>
  74. <a-tag v-if="inbounds[index].stream.isXTls" color="blue">xtls</a-tag>
  75. </template>
  76. <template v-else>无</template>
  77. </template>
  78. <template slot="enable" slot-scope="text, dbInbound">
  79. <a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound)"></a-switch>
  80. </template>
  81. <template slot="expiryTime" slot-scope="text, dbInbound">
  82. <span v-if="dbInbound.expiryTime > 0" color="red">[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]</span>
  83. <span v-else>无限期</span>
  84. </template>
  85. <template slot="action" slot-scope="text, dbInbound">
  86. <a-button v-if="dbInbound.hasLink()" type="primary" icon="qrcode" @click="showQrcode(dbInbound)"></a-button>
  87. <a-button type="primary" icon="edit" @click="openEditInbound(dbInbound)"></a-button>
  88. <a-button icon="retweet" @click="resetTraffic(dbInbound)"></a-button>
  89. <a-button type="danger" icon="delete" @click="delInbound(dbInbound)"></a-button>
  90. </template>
  91. </a-table>
  92. </a-card>
  93. </transition>
  94. </a-spin>
  95. </a-layout-content>
  96. </a-layout>
  97. </a-layout>
  98. {{template "js" .}}
  99. <script>
  100. const columns = [{
  101. title: "id",
  102. align: 'center',
  103. dataIndex: "id",
  104. width: 60,
  105. }, {
  106. title: "备注",
  107. align: 'center',
  108. width: 60,
  109. scopedSlots: { customRender: 'remark' },
  110. }, {
  111. title: "协议",
  112. align: 'center',
  113. width: 60,
  114. scopedSlots: { customRender: 'protocol' },
  115. }, {
  116. title: "端口",
  117. align: 'center',
  118. dataIndex: "port",
  119. width: 60,
  120. }, {
  121. title: "流量↑|↓",
  122. align: 'center',
  123. width: 80,
  124. scopedSlots: { customRender: 'traffic' },
  125. }, {
  126. title: "详细信息",
  127. align: 'center',
  128. width: 60,
  129. scopedSlots: { customRender: 'settings' },
  130. }, {
  131. title: "传输配置",
  132. align: 'center',
  133. width: 60,
  134. scopedSlots: { customRender: 'stream' },
  135. }, {
  136. title: "启用",
  137. align: 'center',
  138. width: 60,
  139. scopedSlots: { customRender: 'enable' },
  140. }, {
  141. title: "操作",
  142. align: 'center',
  143. width: 60,
  144. scopedSlots: { customRender: 'action' },
  145. }];
  146. const app = new Vue({
  147. delimiters: ['[[', ']]'],
  148. el: '#app',
  149. data: {
  150. siderDrawer,
  151. spinning: false,
  152. inbounds: [],
  153. dbInbounds: [],
  154. searchKey: '',
  155. },
  156. methods: {
  157. loading(spinning=true) {
  158. this.spinning = spinning;
  159. },
  160. async getDBInbounds() {
  161. this.loading();
  162. const msg = await HttpUtil.post('/xui/inbound/list');
  163. this.loading(false);
  164. if (!msg.success) {
  165. return;
  166. }
  167. this.setInbounds(msg.obj);
  168. },
  169. setInbounds(dbInbounds) {
  170. this.inbounds.splice(0);
  171. this.dbInbounds.splice(0);
  172. for (const inbound of dbInbounds) {
  173. const dbInbound = new DBInbound(inbound);
  174. this.inbounds.push(dbInbound.toInbound());
  175. this.dbInbounds.push(dbInbound);
  176. }
  177. },
  178. searchInbounds(key) {
  179. if (ObjectUtil.isEmpty(key)) {
  180. this.searchedInbounds = this.dbInbounds.slice();
  181. } else {
  182. this.searchedInbounds.splice(0, this.searchedInbounds.length);
  183. this.dbInbounds.forEach(inbound => {
  184. if (ObjectUtil.deepSearch(inbound, key)) {
  185. this.searchedInbounds.push(inbound);
  186. }
  187. });
  188. }
  189. },
  190. openAddInbound() {
  191. inModal.show({
  192. title: '添加入站',
  193. okText: '添加',
  194. confirm: async (inbound, dbInbound) => {
  195. inModal.loading();
  196. await this.addInbound(inbound, dbInbound);
  197. inModal.close();
  198. }
  199. });
  200. },
  201. openEditInbound(dbInbound) {
  202. const inbound = dbInbound.toInbound();
  203. inModal.show({
  204. title: '修改入站',
  205. okText: '修改',
  206. inbound: inbound,
  207. dbInbound: dbInbound,
  208. confirm: async (inbound, dbInbound) => {
  209. inModal.loading();
  210. await this.updateInbound(inbound, dbInbound);
  211. inModal.close();
  212. }
  213. });
  214. },
  215. async addInbound(inbound, dbInbound) {
  216. const data = {
  217. up: dbInbound.up,
  218. down: dbInbound.down,
  219. total: dbInbound.total,
  220. remark: dbInbound.remark,
  221. enable: dbInbound.enable,
  222. listen: inbound.listen,
  223. port: inbound.port,
  224. protocol: inbound.protocol,
  225. settings: inbound.settings.toString(),
  226. streamSettings: inbound.stream.toString(),
  227. sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}',
  228. };
  229. await this.submit('/xui/inbound/add', data, inModal);
  230. },
  231. async updateInbound(inbound, dbInbound) {
  232. const data = {
  233. up: dbInbound.up,
  234. down: dbInbound.down,
  235. total: dbInbound.total,
  236. remark: dbInbound.remark,
  237. enable: dbInbound.enable,
  238. listen: inbound.listen,
  239. port: inbound.port,
  240. protocol: inbound.protocol,
  241. settings: inbound.settings.toString(),
  242. streamSettings: inbound.stream.toString(),
  243. sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}',
  244. };
  245. await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
  246. },
  247. resetTraffic(dbInbound) {
  248. this.$confirm({
  249. title: '重置流量',
  250. content: '确定要重置流量吗?',
  251. okText: '重置',
  252. cancelText: '取消',
  253. onOk: () => {
  254. const inbound = dbInbound.toInbound();
  255. dbInbound.up = 0;
  256. dbInbound.down = 0;
  257. this.updateInbound(inbound, dbInbound);
  258. },
  259. });
  260. },
  261. delInbound(dbInbound) {
  262. this.$confirm({
  263. title: '删除入站',
  264. content: '确定要删除入站吗?',
  265. okText: '删除',
  266. cancelText: '取消',
  267. onOk: () => this.submit('/xui/inbound/del/' + dbInbound.id),
  268. });
  269. },
  270. showQrcode(dbInbound) {
  271. const link = dbInbound.genLink();
  272. qrModal.show('二维码', link);
  273. },
  274. showInfo(dbInbound) {
  275. infoModal.show(dbInbound);
  276. },
  277. switchEnable(dbInbound) {
  278. this.submit(`/xui/inbound/update/${dbInbound.id}`, dbInbound);
  279. },
  280. async submit(url, data, modal) {
  281. const msg = await HttpUtil.postWithModal(url, data, modal);
  282. if (msg.success) {
  283. await this.getDBInbounds();
  284. }
  285. },
  286. },
  287. watch: {
  288. searchKey(value) {
  289. this.searchInbounds(value);
  290. }
  291. },
  292. mounted() {
  293. this.getDBInbounds();
  294. },
  295. computed: {
  296. total() {
  297. let down = 0, up = 0;
  298. for (let i = 0; i < this.dbInbounds.length; ++i) {
  299. down += this.dbInbounds[i].down;
  300. up += this.dbInbounds[i].up;
  301. }
  302. return {
  303. down: down,
  304. up: up,
  305. };
  306. }
  307. },
  308. });
  309. </script>
  310. {{template "inboundModal"}}
  311. {{template "promptModal"}}
  312. {{template "qrcodeModal"}}
  313. {{template "textModal"}}
  314. {{template "inboundInfoModal"}}
  315. </body>
  316. </html>