inbounds.html 11 KB

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