index.blade.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. @extends('admin.table_layouts')
  2. @push('css')
  3. <link href="/assets/custom/range.min.css" rel="stylesheet">
  4. <style>
  5. #swal2-content {
  6. display: grid !important;
  7. }
  8. .table a {
  9. color: #76838f;
  10. text-decoration: none;
  11. }
  12. </style>
  13. @endpush
  14. @section('content')
  15. <div class="page-content container-fluid">
  16. <div class="panel">
  17. <div class="panel-heading">
  18. <h2 class="panel-title">{{ trans('admin.menu.user.list') }}</h2>
  19. @canany(['admin.user.batch', 'admin.user.create'])
  20. <div class="panel-actions">
  21. @can('admin.user.batch')
  22. <button class="btn btn-outline-default" onclick="batchAddUsers()">
  23. <i class="icon wb-plus" aria-hidden="true"></i> {{ trans('common.generate') }}
  24. </button>
  25. @endcan
  26. @can('admin.user.create')
  27. <a class="btn btn-outline-primary" href="{{ route('admin.user.create') }}">
  28. <i class="icon wb-user-add" aria-hidden="true"></i> {{ trans('common.add') }}
  29. </a>
  30. @endcan
  31. </div>
  32. @endcanany
  33. </div>
  34. <div class="panel-body">
  35. <form class="form-row">
  36. <div class="form-group col-xxl-1 col-lg-1 col-md-1 col-sm-4">
  37. <input class="form-control" name="id" type="number" value="{{ Request::query('id') }}" placeholder="{{ trans('model.user.id') }}" />
  38. </div>
  39. <div class="form-group col-xxl-2 col-lg-3 col-md-3 col-sm-4">
  40. <input class="form-control" name="username" type="text" value="{{ Request::query('username') }}"
  41. placeholder="{{ trans('model.user.username') }}" />
  42. </div>
  43. <div class="form-group col-xxl-2 col-lg-3 col-md-3 col-sm-4">
  44. <input class="form-control" name="wechat" type="text" value="{{ Request::query('wechat') }}"
  45. placeholder="{{ trans('model.user.wechat') }}" />
  46. </div>
  47. <div class="form-group col-xxl-2 col-lg-3 col-md-3 col-sm-4">
  48. <input class="form-control" name="qq" type="number" value="{{ Request::query('qq') }}" placeholder="{{ trans('model.user.qq') }}" />
  49. </div>
  50. <div class="form-group col-xxl-1 col-lg-2 col-md-2 col-sm-4">
  51. <input class="form-control" name="port" type="number" value="{{ Request::query('port') }}"
  52. placeholder="{{ trans('model.user.port') }}" />
  53. </div>
  54. <div class="form-group col-xxl-1 col-lg-3 col-md-3 col-4">
  55. <select class="form-control" id="user_group_id" name="user_group_id" data-plugin="selectpicker" data-style="btn-outline btn-primary"
  56. title="{{ trans('model.user_group.attribute') }}">
  57. @foreach ($userGroups as $id => $name)
  58. <option value="{{ $id }}">{{ $name }}</option>
  59. @endforeach
  60. </select>
  61. </div>
  62. <div class="form-group col-xxl-1 col-lg-3 col-md-3 col-4">
  63. <select class="form-control" id="level" name="level" data-plugin="selectpicker" data-style="btn-outline btn-primary"
  64. title="{{ trans('model.common.level') }}">
  65. @foreach ($levels as $level => $name)
  66. <option value="{{ $level }}">{{ $name }}</option>
  67. @endforeach
  68. </select>
  69. </div>
  70. <div class="form-group col-xxl-1 col-lg-3 col-md-3 col-4">
  71. <select class="form-control" id="status" name="status" data-plugin="selectpicker" data-style="btn-outline btn-primary"
  72. title="{{ trans('model.user.account_status') }}">
  73. <option value="-1">{{ trans('common.status.banned') }}</option>
  74. <option value="0">{{ trans('common.status.inactive') }}</option>
  75. <option value="1">{{ trans('common.status.normal') }}</option>
  76. </select>
  77. </div>
  78. <div class="form-group col-xxl-1 col-lg-3 col-md-3 col-4">
  79. <select class="form-control" id="enable" name="enable" data-plugin="selectpicker" data-style="btn-outline btn-primary"
  80. title="{{ trans('model.user.proxy_status') }}">
  81. <option value="1">{{ trans('common.status.enabled') }}</option>
  82. <option value="0">{{ trans('common.status.banned') }}</option>
  83. </select>
  84. </div>
  85. <div class="form-group col-xxl-1 col-lg-3 col-md-3 col-4 btn-group">
  86. <button class="btn btn-primary" type="submit">{{ trans('common.search') }}</button>
  87. <button class="btn btn-danger" type="button" onclick="resetSearchForm()">{{ trans('common.reset') }}</button>
  88. </div>
  89. </form>
  90. <table class="text-md-center" data-toggle="table" data-mobile-responsive="true">
  91. <thead class="thead-default">
  92. <tr>
  93. <th> @sortablelink('id', '#')</th>
  94. <th> {{ trans('model.user.username') }}</th>
  95. <th> @sortablelink('credit', trans('model.user.credit'))</th>
  96. <th> @sortablelink('port', trans('model.user.port'))</th>
  97. <th> {{ trans('model.subscribe.code') }}</th>
  98. <th> {{ trans('model.user.traffic_used') }}</th>
  99. <th> @sortablelink('t', trans('common.latest_at'))</th>
  100. <th> @sortablelink('expired_at', trans('common.expired_at'))</th>
  101. <th> {{ trans('common.account') }}</th>
  102. <th> {{ trans('model.user.service') }}</th>
  103. <th> {{ trans('common.action') }}</th>
  104. </tr>
  105. </thead>
  106. <tbody>
  107. @foreach ($userList as $user)
  108. <tr class="{{ $user->ban_time ? 'table-danger' : '' }}">
  109. <td> {{ $user->id }} </td>
  110. <td> {{ $user->username }} </td>
  111. <td> {{ $user->credit }} </td>
  112. <td>
  113. {!! $user->port ?: '<span class="badge badge-lg badge-danger"> ' . trans('common.none') . ' </span>' !!}
  114. </td>
  115. <td>
  116. <a class="copySubscribeLink" data-clipboard-action="copy" data-clipboard-text="{{ $user->sub_url }}"
  117. href="javascript:">{{ $user->subscribe->code }}</a>
  118. </td>
  119. <td> {{ formatBytes($user->used_traffic) }} / {{ $user->transfer_enable_formatted }} </td>
  120. <td> {{ $user->t ? date('Y-m-d H:i', $user->t) : trans('common.status.unused') }} </td>
  121. <td>
  122. @if ($user->expiration_status !== 3)
  123. <span class="badge badge-lg badge-{{ ['danger', 'warning', 'default'][$user->expiration_status] }}">
  124. {{ $user->expiration_date }} </span>
  125. @else
  126. {{ $user->expiration_date }}
  127. @endif
  128. </td>
  129. <td>
  130. @if ($user->status > 0)
  131. <span class="badge badge-lg badge-primary">
  132. <i class="wb-check" aria-hidden="true"></i>
  133. </span>
  134. @elseif ($user->status < 0)
  135. <span class="badge badge-lg badge-danger">
  136. <i class="wb-close" aria-hidden="true"></i>
  137. </span>
  138. @else
  139. <span class="badge badge-lg badge-default">
  140. <i class="wb-minus" aria-hidden="true"></i>
  141. </span>
  142. @endif
  143. </td>
  144. <td>
  145. <span class="badge badge-lg badge-{{ $user->enable ? 'info' : 'danger' }}">
  146. <i class="wb-{{ $user->enable ? 'check' : 'close' }}" aria-hidden="true"></i>
  147. </span>
  148. </td>
  149. <td>
  150. @canany(['admin.user.edit', 'admin.user.destroy', 'admin.user.export', 'admin.user.monitor', 'admin.user.online',
  151. 'admin.user.reset', 'admin.user.switch'])
  152. <button class="btn btn-primary dropdown-toggle" data-boundary="viewport" data-toggle="dropdown" type="button"
  153. aria-expanded="false">
  154. <i class="icon wb-wrench" aria-hidden="true"></i>
  155. </button>
  156. <div class="dropdown-menu" role="menu">
  157. @can('admin.user.edit')
  158. <a class="dropdown-item" href="{{ route('admin.user.edit', ['user' => $user->id, Request::getQueryString()]) }}"
  159. role="menuitem">
  160. <i class="icon wb-edit" aria-hidden="true"></i> {{ trans('common.edit') }}
  161. </a>
  162. @endcan
  163. @can('admin.user.destroy')
  164. <a class="dropdown-item"
  165. href="javascript:delUser('{{ route('admin.user.destroy', $user->id) }}','{{ $user->username }}')" role="menuitem">
  166. <i class="icon wb-trash" aria-hidden="true"></i> {{ trans('common.delete') }}
  167. </a>
  168. @endcan
  169. @can('admin.user.export')
  170. <a class="dropdown-item" href="{{ route('admin.user.export', $user) }}" role="menuitem">
  171. <i class="icon wb-code" aria-hidden="true"></i> {{ trans('admin.user.proxy_info') }}
  172. </a>
  173. @endcan
  174. @can('admin.user.monitor')
  175. <a class="dropdown-item" href="{{ route('admin.user.monitor', $user) }}" role="menuitem">
  176. <i class="icon wb-stats-bars" aria-hidden="true"></i> {{ trans('admin.user.traffic_monitor') }}
  177. </a>
  178. @endcan
  179. @can('admin.user.online')
  180. <a class="dropdown-item" href="{{ route('admin.user.online', $user) }}" role="menuitem">
  181. <i class="icon wb-cloud" aria-hidden="true"></i> {{ trans('admin.user.online_monitor') }}
  182. </a>
  183. @endcan
  184. @can('admin.user.reset')
  185. <a class="dropdown-item" href="javascript:resetTraffic('{{ $user->id }}','{{ $user->username }}')" role="menuitem">
  186. <i class="icon wb-reload" aria-hidden="true"></i> {{ trans('admin.user.reset_traffic') }}
  187. </a>
  188. @endcan
  189. @can('admin.user.switch')
  190. <a class="dropdown-item" href="javascript:switchToUser('{{ $user->id }}')" role="menuitem">
  191. <i class="icon wb-user" aria-hidden="true"></i> {{ trans('admin.user.user_view') }}
  192. </a>
  193. @endcan
  194. @can('admin.user.VNetInfo')
  195. <a class="dropdown-item" href="javascript:VNetInfo('{{ $user->id }}')" role="menuitem">
  196. <i class="icon wb-link-broken" id="vent_{{ $user->id }}" aria-hidden="true"></i>
  197. {{ trans('admin.user.connection_test') }}
  198. </a>
  199. @endcan
  200. </div>
  201. @endcanany
  202. </td>
  203. </tr>
  204. @endforeach
  205. </tbody>
  206. </table>
  207. </div>
  208. <div class="panel-footer">
  209. <div class="row">
  210. <div class="col-sm-4">
  211. {!! trans('admin.user.counts', ['num' => $userList->total()]) !!}
  212. </div>
  213. <div class="col-sm-8">
  214. <nav class="Page navigation float-right">
  215. {{ $userList->links() }}
  216. </nav>
  217. </div>
  218. </div>
  219. </div>
  220. </div>
  221. </div>
  222. @endsection
  223. @push('javascript')
  224. <script src="/assets/custom/clipboardjs/clipboard.min.js"></script>
  225. <script>
  226. $(document).ready(function() {
  227. $('#user_group_id').selectpicker('val', @json(Request::query('user_group_id')));
  228. $('#level').selectpicker('val', @json(Request::query('level')));
  229. $('#status').selectpicker('val', @json(Request::query('status')));
  230. $('#enable').selectpicker('val', @json(Request::query('enable')));
  231. });
  232. @can('admin.user.batch')
  233. // 批量生成账号
  234. function batchAddUsers() {
  235. swal.fire({
  236. title: '{{ trans('admin.user.bulk_account_quantity') }}',
  237. input: 'range',
  238. inputAttributes: {
  239. min: 1,
  240. max: 10
  241. },
  242. inputValue: 1,
  243. icon: 'question',
  244. showCancelButton: true,
  245. cancelButtonText: '{{ trans('common.close') }}',
  246. confirmButtonText: '{{ trans('common.confirm') }}',
  247. }).then((result) => {
  248. if (result.value) {
  249. $.post('{{ route('admin.user.batch') }}', {
  250. _token: '{{ csrf_token() }}',
  251. amount: result.value,
  252. }, function(ret) {
  253. if (ret.status === 'success') {
  254. swal.fire({
  255. title: ret.message,
  256. icon: 'success',
  257. timer: 1000,
  258. showConfirmButton: false,
  259. }).then(() => window.location.reload());
  260. } else {
  261. swal.fire({
  262. title: ret.message,
  263. icon: 'error'
  264. }).then(() => window.location.reload());
  265. }
  266. });
  267. }
  268. });
  269. }
  270. @endcan
  271. @can('admin.user.destroy')
  272. // 删除账号
  273. function delUser(url, username) {
  274. swal.fire({
  275. title: '{{ trans('common.warning') }}',
  276. text: '{{ trans('admin.confirm.delete.0', ['attribute' => trans('model.user.attribute')]) }}' + username +
  277. '{{ trans('admin.confirm.delete.1') }}',
  278. icon: 'warning',
  279. showCancelButton: true,
  280. cancelButtonText: '{{ trans('common.close') }}',
  281. confirmButtonText: '{{ trans('common.confirm') }}',
  282. }).then((result) => {
  283. if (result.value) {
  284. $.ajax({
  285. method: 'DELETE',
  286. url: url,
  287. data: {
  288. _token: '{{ csrf_token() }}'
  289. },
  290. dataType: 'json',
  291. success: function(ret) {
  292. if (ret.status === 'success') {
  293. swal.fire({
  294. title: ret.message,
  295. icon: 'success',
  296. timer: 1000,
  297. showConfirmButton: false,
  298. }).then(() => window.location.reload());
  299. } else {
  300. swal.fire({
  301. title: ret.message,
  302. icon: 'error'
  303. }).then(() => window.location.reload());
  304. }
  305. },
  306. });
  307. }
  308. });
  309. }
  310. @endcan
  311. @can('admin.user.reset')
  312. // 重置流量
  313. function resetTraffic(id, username) {
  314. swal.fire({
  315. title: '{{ trans('common.warning') }}',
  316. text: '{{ trans('admin.user.reset_confirm.0') }}' + username + '{{ trans('admin.user.reset_confirm.1') }}',
  317. icon: 'warning',
  318. showCancelButton: true,
  319. cancelButtonText: '{{ trans('common.close') }}',
  320. confirmButtonText: '{{ trans('common.confirm') }}',
  321. }).then((result) => {
  322. if (result.value) {
  323. $.post('{{ route('admin.user.reset', '') }}/' + id, {
  324. _token: '{{ csrf_token() }}'
  325. }, function(ret) {
  326. if (ret.status === 'success') {
  327. swal.fire({
  328. title: ret.message,
  329. icon: 'success',
  330. timer: 1000,
  331. showConfirmButton: false,
  332. }).then(() => window.location.reload());
  333. } else {
  334. swal.fire({
  335. title: ret.message,
  336. icon: 'error'
  337. }).then(() => window.location.reload());
  338. }
  339. });
  340. }
  341. });
  342. }
  343. @endcan
  344. @can('admin.user.switch')
  345. // 切换用户身份
  346. function switchToUser(id) {
  347. $.post('{{ route('admin.user.switch', '') }}/' + id, {
  348. _token: '{{ csrf_token() }}'
  349. }, function(ret) {
  350. if (ret.status === 'success') {
  351. swal.fire({
  352. title: ret.message,
  353. icon: 'success',
  354. timer: 1000,
  355. showConfirmButton: false,
  356. }).then(() => window.location.href = '/');
  357. } else {
  358. swal.fire({
  359. title: ret.message,
  360. icon: 'error'
  361. }).then(() => window.location.reload());
  362. }
  363. });
  364. }
  365. @endcan
  366. @can('admin.user.VNetInfo')
  367. // 节点连通性测试
  368. function VNetInfo(id) {
  369. $.ajax({
  370. method: 'POST',
  371. url: '{{ route('admin.user.VNetInfo', '') }}/' + id,
  372. data: {
  373. _token: '{{ csrf_token() }}'
  374. },
  375. beforeSend: function() {
  376. $('#vent_' + id).removeClass('wb-link-broken').addClass('wb-loop icon-spin');
  377. },
  378. success: function(ret) {
  379. if (ret.status === 'success') {
  380. let str = '';
  381. for (let i in ret.data) {
  382. str += '<tr><td>' + ret.data[i]['id'] + '</td><td>' + ret.data[i]['name'] + '</td><td>' +
  383. ret.data[i]['avaliable'] + '</td></tr>';
  384. }
  385. swal.fire({
  386. title: ret.title,
  387. icon: 'info',
  388. html: '<table class="my-20"><thead class="thead-default"><tr><th> ID </th><th> {{ trans('model.node.attribute') }} </th> <th> {{ trans('common.status.attribute') }} </th></thead><tbody>' +
  389. str + '</tbody></table>',
  390. showConfirmButton: false,
  391. });
  392. } else {
  393. swal.fire({
  394. title: ret.title,
  395. text: ret.data,
  396. icon: 'error'
  397. });
  398. }
  399. },
  400. complete: function() {
  401. $('#vent_' + id).removeClass('wb-loop icon-spin').addClass('wb-link-broken');
  402. },
  403. });
  404. }
  405. @endcan
  406. const clipboard = new ClipboardJS('.copySubscribeLink');
  407. clipboard.on('success', function() {
  408. swal.fire({
  409. title: '{{ trans('common.copy.success') }}',
  410. icon: 'success',
  411. timer: 1000,
  412. showConfirmButton: false,
  413. });
  414. });
  415. clipboard.on('error', function() {
  416. swal.fire({
  417. title: '{{ trans('common.copy.failed') }}',
  418. icon: 'error',
  419. timer: 1500,
  420. showConfirmButton: false,
  421. });
  422. });
  423. </script>
  424. @endpush