inbounds.html 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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="true" 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-popconfirm title="Are you sure you want to reset all traffic to 0? It\'s unrecoverable"
  35. @confirm="resetAllTraffic()"
  36. ok-text="confirm" cancel-text="cancel">
  37. <a-tag color="green">[[ sizeFormat(total.up + total.down) ]]</a-tag>
  38. </a-popconfirm>
  39. </a-col>
  40. <a-col :xs="24" :sm="24" :lg="12">
  41. number of accounts:
  42. <a-tag color="green">[[ dbInbounds.length ]]</a-tag>
  43. </a-col>
  44. </a-row>
  45. </a-card>
  46. </transition>
  47. <transition name="list" appear>
  48. <a-card hoverable>
  49. <div slot="title">
  50. <a-button type="primary" icon="plus" @click="openAddInbound"></a-button>
  51. </div>
  52. <a-input v-model="searchKey" placeholder="search" autofocus style="max-width: 300px"></a-input>
  53. <a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
  54. :data-source="dbInbounds"
  55. :loading="spinning" :scroll="{ x: 1500 }"
  56. :pagination="false"
  57. style="margin-top: 20px"
  58. @change="() => getDBInbounds()">
  59. <template slot="protocol" slot-scope="text, dbInbound">
  60. <a-tag color="blue">[[ dbInbound.protocol ]]</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-tag v-if="dbInbound.enable" color="green">启用</a-tag>
  70. <a-tag v-else color="red">禁用</a-tag>
  71. </template>
  72. <template slot="expiryTime" slot-scope="text, dbInbound">
  73. <span v-if="dbInbound.expiryTime > 0" color="red">[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]</span>
  74. <span v-else>无限期</span>
  75. </template>
  76. <template slot="action" slot-scope="text, dbInbound">
  77. <a-button v-if="dbInbound.hasLink()" type="primary" icon="qrcode" @click="showQrcode(dbInbound)"></a-button>
  78. <a-button type="primary" icon="edit" @click="openEditInbound(dbInbound)"></a-button>
  79. <a-button type="danger" icon="delete" @click="delInbound(dbInbound)"></a-button>
  80. </template>
  81. </a-table>
  82. </a-card>
  83. </transition>
  84. </a-spin>
  85. </a-layout-content>
  86. </a-layout>
  87. </a-layout>
  88. {{template "js" .}}
  89. <script>
  90. const columns = [{
  91. title: "id",
  92. align: 'center',
  93. dataIndex: "id",
  94. width: 60,
  95. }, {
  96. title: "protocol",
  97. align: 'center',
  98. width: 60,
  99. scopedSlots: { customRender: 'protocol' },
  100. }, {
  101. title: "port",
  102. align: 'center',
  103. dataIndex: "port",
  104. width: 60,
  105. }, {
  106. title: "settings",
  107. align: 'center',
  108. width: 60,
  109. scopedSlots: { customRender: 'settings' },
  110. }, {
  111. title: "streamSettings",
  112. align: 'center',
  113. width: 60,
  114. scopedSlots: { customRender: 'streamSettings' },
  115. }, {
  116. title: "enable",
  117. align: 'center',
  118. width: 60,
  119. scopedSlots: { customRender: 'enable' },
  120. }, {
  121. title: "expiryTime",
  122. align: 'center',
  123. width: 60,
  124. scopedSlots: { customRender: 'expiryTime' },
  125. }, {
  126. title: "action",
  127. align: 'center',
  128. width: 60,
  129. scopedSlots: { customRender: 'action' },
  130. }];
  131. const app = new Vue({
  132. delimiters: ['[[', ']]'],
  133. el: '#app',
  134. data: {
  135. siderDrawer,
  136. spinning: false,
  137. dbInbounds: [],
  138. searchKey: '',
  139. },
  140. methods: {
  141. loading(spinning=true) {
  142. this.spinning = spinning;
  143. },
  144. async getDBInbounds() {
  145. this.loading();
  146. const msg = await HttpUtil.post('/xui/inbounds');
  147. this.loading(false);
  148. if (!msg.success) {
  149. return;
  150. }
  151. this.setInbounds(msg.obj);
  152. },
  153. setInbounds(dbInbounds) {
  154. this.dbInbounds.splice(0);
  155. for (const inbound of dbInbounds) {
  156. this.dbInbounds.push(new DBInbound(inbound));
  157. }
  158. },
  159. searchInbounds(key) {
  160. if (ObjectUtil.isEmpty(key)) {
  161. this.searchedInbounds = this.dbInbounds.slice();
  162. } else {
  163. this.searchedInbounds.splice(0, this.searchedInbounds.length);
  164. this.dbInbounds.forEach(inbound => {
  165. if (ObjectUtil.deepSearch(inbound, key)) {
  166. this.searchedInbounds.push(inbound);
  167. }
  168. });
  169. }
  170. },
  171. openAddInbound() {
  172. inModal.show({
  173. title: 'add account',
  174. okText: 'add',
  175. confirm: async (inbound, dbInbound) => {
  176. inModal.loading();
  177. await this.addInbound(inbound, dbInbound);
  178. inModal.close();
  179. }
  180. });
  181. },
  182. openEditInbound(dbInbound) {
  183. const inbound = dbInbound.toInbound();
  184. inModal.show({
  185. title: 'update account',
  186. okText: 'update',
  187. inbound: inbound,
  188. dbInbound: dbInbound,
  189. confirm: async (inbound, dbInbound) => {
  190. inModal.loading();
  191. await this.updateInbound(inbound, dbInbound);
  192. inModal.close();
  193. }
  194. });
  195. },
  196. async addInbound(inbound, dbInbound) {
  197. const data = {
  198. remark: dbInbound.remark,
  199. enable: dbInbound.enable,
  200. listen: inbound.listen,
  201. port: inbound.port,
  202. protocol: inbound.protocol,
  203. settings: inbound.settings.toString(),
  204. stream_settings: inbound.stream.toString(),
  205. sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}',
  206. };
  207. await this.submit('/xui/inbound/add', data, inModal);
  208. },
  209. async updateInbound(inbound, dbInbound) {
  210. const data = {
  211. remark: dbInbound.remark,
  212. enable: dbInbound.enable,
  213. listen: inbound.listen,
  214. port: inbound.port,
  215. protocol: inbound.protocol,
  216. settings: inbound.settings.toString(),
  217. stream_settings: inbound.stream.toString(),
  218. sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}',
  219. };
  220. await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
  221. },
  222. delInbound(dbInbound) {
  223. this.$confirm({
  224. title: 'delete account',
  225. content: 'Cannot be restored after deletion, confirm deletion?',
  226. okText: 'delete',
  227. cancelText: 'cancel',
  228. onOk: () => this.submit('/xui/inbound/del/' + dbInbound.id),
  229. });
  230. },
  231. showQrcode(dbInbound) {
  232. let address = location.hostname;
  233. if (!ObjectUtil.isEmpty(dbInbound.listen) || dbInbound.listen !== "0.0.0.0") {
  234. address = dbInbound.listen;
  235. }
  236. const link = dbInbound.genLink(address);
  237. qrModal.show('二维码', link);
  238. },
  239. resetTraffic(inbound) {
  240. this.submit(`/xui/reset_traffic/${inbound.id}`);
  241. },
  242. resetAllTraffic() {
  243. this.submit('/xui/reset_all_traffic');
  244. },
  245. setEnable(inbound, enable) {
  246. let data = {enable: enable};
  247. this.submit(`/xui/inbound/update/${inbound.id}`, data);
  248. },
  249. async submit(url, data, modal) {
  250. const msg = await HttpUtil.postWithModal(url, data, modal);
  251. if (msg.success) {
  252. this.getDBInbounds();
  253. }
  254. },
  255. },
  256. watch: {
  257. searchKey(value) {
  258. this.searchInbounds(value);
  259. }
  260. },
  261. mounted() {
  262. this.getDBInbounds();
  263. },
  264. computed: {
  265. total() {
  266. let down = 0, up = 0;
  267. for (let i = 0; i < this.dbInbounds.length; ++i) {
  268. down += this.dbInbounds[i].down;
  269. up += this.dbInbounds[i].up;
  270. }
  271. return {
  272. down: down,
  273. up: up,
  274. };
  275. }
  276. },
  277. });
  278. </script>
  279. {{template "inboundModal"}}
  280. {{template "promptModal"}}
  281. {{template "qrcodeModal"}}
  282. {{template "textModal"}}
  283. </body>
  284. </html>