inbounds.html 12 KB

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